diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..3abe3a6ca --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +github: midoks + +custom: https://afdian.net/a/mdserver-web \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE_1.md b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE_1.md new file mode 100644 index 000000000..992c716dc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE_1.md @@ -0,0 +1,29 @@ +--- +name: BUG提交 +about: 问题描述 +--- + +## 什么系统 ? + +Debian x, Centos x, Centos x stream, Ubuntu x? + +## 错误信息 ? + +Ex: +```bash +一个issue,一个问题。多余问题,视情况而答。 +``` + +## 错误截图 ? + +... + + +## 浏览器版本 ? + +... + + +## 软件版本(插件/面板) ? + +... diff --git a/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE_2.md b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE_2.md new file mode 100644 index 000000000..8b1420e96 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE_2.md @@ -0,0 +1,14 @@ +--- +name: 需求提交 +about: 需求描述 +--- + +## 需求 + +想开发一个什么插件? +想有什么功能? + + +## 提供参考 + +给开发者提供参考! \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..3b4c015c7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,24 @@ +name: Bug提交 +description: 提交错误报告以帮助我们改进 +labels: ["\U0001F48A bug"] +body: + - type: markdown + attributes: + value: | + 感谢您花时间填写此错误报告! + + - 如有疑问,请讨论[Discussions](https://github.com/midoks/mdserver-web/discussions). + - 检查以确保尚未有人打开类似的[issue](https://github.com/midoks/mdserver-web/issues). + - type: input + attributes: + label: 操作系统 + description: | + 请指定您报告的确切操作系统名称和版本,例如"CentOS 7"、"Ubuntu 20.04"。 + validations: + required: true + - type: textarea + attributes: + label: 描述错误 + description: 对bug的清晰而简洁的描述。 + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 000000000..832866744 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: 提出问题 + url: https://github.com/midoks/mdserver-web/discussions + about: 请在讨论中提出问题。 + - name: 提出建议 + url: https://github.com/midoks/mdserver-web/discussions/categories/proposal + about: 请在讨论中提出建议。 diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 000000000..3b4c015c7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,24 @@ +name: Bug提交 +description: 提交错误报告以帮助我们改进 +labels: ["\U0001F48A bug"] +body: + - type: markdown + attributes: + value: | + 感谢您花时间填写此错误报告! + + - 如有疑问,请讨论[Discussions](https://github.com/midoks/mdserver-web/discussions). + - 检查以确保尚未有人打开类似的[issue](https://github.com/midoks/mdserver-web/issues). + - type: input + attributes: + label: 操作系统 + description: | + 请指定您报告的确切操作系统名称和版本,例如"CentOS 7"、"Ubuntu 20.04"。 + validations: + required: true + - type: textarea + attributes: + label: 描述错误 + description: 对bug的清晰而简洁的描述。 + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..3b4c015c7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,24 @@ +name: Bug提交 +description: 提交错误报告以帮助我们改进 +labels: ["\U0001F48A bug"] +body: + - type: markdown + attributes: + value: | + 感谢您花时间填写此错误报告! + + - 如有疑问,请讨论[Discussions](https://github.com/midoks/mdserver-web/discussions). + - 检查以确保尚未有人打开类似的[issue](https://github.com/midoks/mdserver-web/issues). + - type: input + attributes: + label: 操作系统 + description: | + 请指定您报告的确切操作系统名称和版本,例如"CentOS 7"、"Ubuntu 20.04"。 + validations: + required: true + - type: textarea + attributes: + label: 描述错误 + description: 对bug的清晰而简洁的描述。 + validations: + required: true \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..310a84308 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ +### 合并描述 + +- 我添加了测试用例来覆盖新代码。 diff --git a/.github/workflows/python-app-mysql55.yml b/.github/workflows/python-app-mysql55.yml new file mode 100644 index 000000000..a1866f13a --- /dev/null +++ b/.github/workflows/python-app-mysql55.yml @@ -0,0 +1,38 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: MW APP MySQL55 + +on: + push: + branches: [ "run_test" ] + pull_request: + branches: [ "run_test" ] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [ubuntu-20.04, ubuntu-18.04, ubuntu-22.04] + runs-on: ${{ matrix.os }} + #runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install MW + run: | + sudo bash scripts/install_dev.sh + - name: Install MySQL55 + run: | + cd /www/server/mdserver-web/plugins/mysql && sudo bash install.sh install 5.5 + - name: Start DEBUG + run: | + cd web && gunicorn -c setting.py app:app + python3 panel_task.py & diff --git a/.github/workflows/python-app-mysql56.yml b/.github/workflows/python-app-mysql56.yml new file mode 100644 index 000000000..d4b4a2618 --- /dev/null +++ b/.github/workflows/python-app-mysql56.yml @@ -0,0 +1,38 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: MW APP MySQL56 + +on: + push: + branches: [ "run_test" ] + pull_request: + branches: [ "run_test" ] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [ubuntu-20.04, ubuntu-18.04, ubuntu-22.04] + runs-on: ${{ matrix.os }} + #runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install MW + run: | + sudo bash scripts/install_dev.sh + - name: Install MySQL56 + run: | + cd /www/server/mdserver-web/plugins/mysql && sudo bash install.sh install 5.6 + - name: Start DEBUG + run: | + cd web && gunicorn -c setting.py app:app + python3 panel_task.py & diff --git a/.github/workflows/python-app-mysql57.yml b/.github/workflows/python-app-mysql57.yml new file mode 100644 index 000000000..fa5871402 --- /dev/null +++ b/.github/workflows/python-app-mysql57.yml @@ -0,0 +1,38 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: MW APP MySQL57 + +on: + push: + branches: [ "run_test" ] + pull_request: + branches: [ "run_test" ] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [ubuntu-20.04, ubuntu-18.04, ubuntu-22.04] + runs-on: ${{ matrix.os }} + #runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install MW + run: | + sudo bash scripts/install_dev.sh + - name: Install MySQL57 + run: | + cd /www/server/mdserver-web/plugins/mysql && sudo bash install.sh install 5.7 + - name: Start DEBUG + run: | + cd web && gunicorn -c setting.py app:app + python3 panel_task.py & diff --git a/.github/workflows/python-app-mysql80.yml b/.github/workflows/python-app-mysql80.yml new file mode 100644 index 000000000..c51d2ff7a --- /dev/null +++ b/.github/workflows/python-app-mysql80.yml @@ -0,0 +1,38 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: MW APP MySQL80 + +on: + push: + branches: [ "run_test" ] + pull_request: + branches: [ "run_test" ] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [ubuntu-20.04, ubuntu-18.04, ubuntu-22.04] + runs-on: ${{ matrix.os }} + #runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install MW + run: | + sudo bash scripts/install_dev.sh + - name: Install MySQL80 + run: | + cd /www/server/mdserver-web/plugins/mysql && sudo bash install.sh install 8.0 + - name: Start DEBUG + run: | + cd web && gunicorn -c setting.py app:app + python3 panel_task.py & diff --git a/.github/workflows/python-app-op.yml b/.github/workflows/python-app-op.yml new file mode 100644 index 000000000..bd26948b7 --- /dev/null +++ b/.github/workflows/python-app-op.yml @@ -0,0 +1,38 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: MW APP OpenResty + +on: + push: + branches: [ "master", "dev" ] + pull_request: + branches: [ "master", "dev" ] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + #runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install MW + run: | + sudo bash scripts/install_dev.sh + - name: Install OpenResty + run: | + cd /www/server/mdserver-web/plugins/openresty && sudo bash install.sh install 1.27.1 + - name: Start DEBUG + run: | + source /www/server/mdserver-web/bin/activate && cd web && gunicorn -c setting.py app:app + cd /www/server/mdserver-web && python3 panel_task.py & diff --git a/.github/workflows/python-app-php.yml b/.github/workflows/python-app-php.yml new file mode 100644 index 000000000..71796d15b --- /dev/null +++ b/.github/workflows/python-app-php.yml @@ -0,0 +1,68 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: MW APP PHP + +on: + push: + branches: [ "run_test" ] + pull_request: + branches: [ "run_test" ] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + #runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install MW + run: | + sudo bash scripts/install_dev.sh + - name: Install PHP53 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 53 + - name: Install PHP54 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 54 + - name: Install PHP55 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 55 + - name: Install PHP56 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 56 + - name: Install PHP70 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 70 + - name: Install PHP71 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 71 + - name: Install PHP72 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 72 + - name: Install PHP73 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 73 + - name: Install PHP74 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 74 + - name: Install PHP80 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 80 + - name: Install PHP81 + run: | + cd /www/server/mdserver-web/plugins/php && sudo bash install.sh install 81 + - name: Start DEBUG + run: | + source /www/server/mdserver-web/bin/activate && cd web && gunicorn -c setting.py app:app + cd /www/server/mdserver-web && python3 panel_task.py & diff --git a/.github/workflows/sync2gitee.yml b/.github/workflows/sync2gitee.yml new file mode 100644 index 000000000..229a65912 --- /dev/null +++ b/.github/workflows/sync2gitee.yml @@ -0,0 +1,16 @@ +name: sync2gitee +on: [push] + +jobs: + repo-sync: + runs-on: ubuntu-latest + steps: + - name: Mirror the Github organization repos to Gitee. + uses: Yikun/hub-mirror-action@master + with: + src: 'github/midoks' + dst: 'gitee/midoks' + dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} + dst_token: ${{ secrets.GITEE_TOKEN }} + static_list: "mdserver-web" + force_update: true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..9f4ba0ac1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,198 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +lib/python* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +bin +pyvenv.cfg +include +share +pip-selfcheck.json + +# mypy +.mypy_cache/ + +.DS_Store +.idea/*.xml +.idea/*.iml +tmp/* +*.swp +*.pem +*.zip +debug.out +mdioks.session-journal +*.session + +temp + +logs/panel.pid +logs/panel.log +logs/panel_task.log +logs/panel_exec.log +logs/panel_task.lock + +ssl/local +ssl/nginx +ssl/choose.pl + +scripts/init.d/mw +scripts/mdserver-web + +data/api_login.txt +data/sessions + +data/*.db +data/iplist.txt +data/json/index.json +data/json/config.json +data/site.pl +data/admin_path.pl +data/close.pl +data/datadir.pl +data/502Task.pl +data/default.pl +data/backup.pl +data/debug.pl +data/default_site.pl +data/ssl.pl + +data/port.pl +data/ipv6.pl +data/restart.pl +data/edate.pl +data/osname.pl +data/only_netio_counters.pl +data/system.db-journal +data/hook_*.json +data/letsencrypt.json +data/basic_auth.json +data/notify.json +data/api.json +data/bind_domain.pl +data/unauthorized_status.pl +data/auth_secret.pl +data/panelSpeed.pl + +plugins/vip_* +plugins/own_* +plugins/my_* +plugins/op_auth +plugins/l2tp +plugins/openlitespeed +plugins/tamper_proof +plugins/zimg +plugins/bk_demo +plugins/mail +plugins/fastdfs +plugins/v2ray +plugins/frp +plugins/file_search +plugins/proxysql +plugins/tidb +plugins/goedge-admin +plugins/goedge-node +plugins/goedge-happy + +/logs +/data +/plugins/flexcdn +/plugins/tools +/plugins/choose-linux-python +*.md5 +/bak diff --git a/LICENSE b/LICENSE index ee1d4eb98..f37a89ca4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2026 AoodyConcorde - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [midoks] [midoks of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PowerLinux master.zip b/PowerLinux master.zip deleted file mode 100644 index e9f375f66..000000000 Binary files a/PowerLinux master.zip and /dev/null differ diff --git a/README.md b/README.md index 3c64029a6..894efd4d7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,88 @@ -# PowerLinux -下一代开源Linux面板 +

+ PowerLinux 3 +

+ +# PowerLinux 3.0 + +#### 更现代、更统一的 Linux 控制面板 + +PowerLinux 3.0 是 PowerLinux 的第三代界面与体验升级版本。它延续了原有的安装、更新与卸载方式,在不改变使用习惯的前提下,重新梳理了首页、软件、网站、文件、日志与设置等核心页面,让整体视觉更统一,交互更顺手,也更适合浅色 / 深色 / 自定义背景图的使用场景。 + +## 3.0 版本亮点 + +- 面板默认标题统一为 `PowerLinux 3` +- 全局视觉语言重做,统一卡片、按钮、状态展示与层级关系 +- 支持浅色 / 深色 / 跟随系统 +- 支持背景图、自定义透明度与更柔和的主题色 +- 首页状态、软件管理、网站管理、文件管理、日志与设置页全面优化 +- 保留原有插件生态与常用命令,不改变安装和升级习惯 +- 更适合长期使用的阅读体验与信息密度 + +## 截图预留 + +### 首页 + + +### 软件管理 + + +### 网站管理 + + +### 文件管理 + + +### 设置页面 + + +## 安装 / 更新 / 卸载 + +下面这些命令保持不变,可以直接使用。 + +- **一键安装** + +```bash +bash <(curl --insecure -fsSL https://cdn.jsdelivr.net/gh/AndyXeCM/PowerLinux@master/scripts/install.sh) +``` + +- **更新** + +```bash +bash <(curl --insecure -fsSL https://cdn.jsdelivr.net/gh/AndyXeCM/PowerLinux@master/scripts/update.sh) +``` + +- **卸载** + +```bash +wget --no-check-certificate -O uninstall.sh https://cdn.jsdelivr.net/gh/AndyXeCM/PowerLinux@master/scripts/uninstall.sh && bash uninstall.sh +``` + +## 文档入口 + +- [兼容性测试报告](./compatibility.md) +- [常用命令说明](./cmd.md) + +## 生态说明 + +PowerLinux 3.0 继续兼容原有插件体系,常用能力包括但不限于: + +- OpenResty +- PHP +- MySQL / MariaDB +- MongoDB +- PostgreSQL +- Redis +- Memcached +- phpMyAdmin +- Docker +- 其他第三方插件 + +欢迎继续通过 Issue 和 Pull Request 补充插件、修复问题和优化体验。 + +## 致谢 + +PowerLinux 3.0 基于 MWPanel 继续演进而来,感谢原项目与社区的长期贡献。 + +## License + +本项目采用 Apache 开源授权许可证,完整授权说明请见 [LICENSE](./LICENSE)。 diff --git a/clear.sh b/clear.sh new file mode 100755 index 000000000..9a0b59668 --- /dev/null +++ b/clear.sh @@ -0,0 +1,8 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/usr/local/lib/python2.7/bin + + + +find . -name .DS_Store -print0 | xargs -0 git rm -f --ignore-unmatch +find . -name .DS_Store | xargs rm -rf +find . -type d -name "*.pyc" | xargs rm -rf \ No newline at end of file diff --git a/cli.sh b/cli.sh new file mode 100755 index 000000000..9c9d89015 --- /dev/null +++ b/cli.sh @@ -0,0 +1,126 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +DIR=$(cd "$(dirname "$0")"; pwd) +MDIR=$(dirname "$DIR") + +export LC_ALL="en_US.UTF-8" + +# echo $DIR + +PATH=$PATH:$DIR/bin +if [ -f ${DIR}/bin/activate ];then + source ${DIR}/bin/activate + + if [ "$?" != "0" ];then + echo "load local python env fail!" + fi +fi + +mw_start_task() +{ + isStart=$(ps aux |grep 'panel_task.py'|grep -v grep|awk '{print $2}') + if [ "$isStart" == '' ];then + echo -e "starting mw-tasks... \c" + cd $DIR && python3 panel_task.py >> ${DIR}/logs/panel_task.log 2>&1 & + sleep 0.3 + isStart=$(ps aux |grep 'panel_task.py'|grep -v grep|awk '{print $2}') + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo '------------------------------------------------------' + tail -n 20 $DIR/logs/panel_task.log + echo '------------------------------------------------------' + echo -e "\033[31mError: mw-tasks service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "starting mw-tasks... mw-tasks (pid $(echo $isStart)) already running" + fi +} + +mw_start(){ + # 后台任务 + mw_start_task + + cd ${DIR}/web && gunicorn -c setting.py app:app +} + + +mw_start_debug(){ + if [ ! -f $DIR/logs/panel_task.log ];then + echo '' > $DIR/logs/panel_task.log + fi + + python3 panel_task.py >> $DIR/logs/panel_task.log 2>&1 & + port=7200 + if [ -f /www/server/mdserver-web/data/port.pl ];then + port=$(cat /www/server/mdserver-web/data/port.pl) + fi + + if [ -f ${DIR}/data/port.pl ];then + port=$(cat ${DIR}/data/port.pl) + fi + cd ${DIR}/web && gunicorn -b :${port} -k eventlet -w 1 app:app + # cd ${DIR}/web && gunicorn -b :${port} -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 app:app +} + +mw_start_panel(){ + port=7200 + if [ -f ${DIR}/data/port.pl ];then + port=$(cat ${DIR}/data/port.pl) + fi + cd ${DIR}/web && gunicorn -b :${port} -k eventlet -w 1 app:app + # cd ${DIR}/web && gunicorn -b :${port} -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 app:app + +} + +mw_start_bgtask(){ + cd ${DIR}/web && gunicorn -c setting.py app:app + cd ${DIR} && python3 panel_task.py +} + + +mw_stop() +{ + APP_LIST=`ps -ef|grep app:app |grep -v grep|awk '{print $2}'` + APP_LIST=($APP_LIST) + for p in ${APP_LIST[@]} + do + kill -9 $p > /dev/null 2>&1 + done + + TASK_LIST=`ps -ef|grep panel_task.py | grep -v grep |awk '{print $2}'` + TASK_LIST=($TASK_LIST) + for p in ${TASK_LIST[@]} + do + kill -9 $p > /dev/null 2>&1 + done + + zzpids=`ps -A -o stat,ppid,pid | grep -e '^[Zz]' | awk '{print $2}'` + zzpids=($zzpids) + for p in ${zzpids[@]} + do + kill -9 ${p} > /dev/null 2>&1 + done +} + +case "$1" in + 'start') mw_start;; + 'stop') mw_stop;; + 'restart') + mw_stop + mw_start + ;; + 'debug') + mw_stop + mw_start_debug + ;; + 'panel') + mw_stop + mw_start_panel + ;; + 'task') + # mw_stop + mw_start_bgtask + ;; +esac \ No newline at end of file diff --git a/cmd.md b/cmd.md new file mode 100644 index 000000000..f3ebd1df9 --- /dev/null +++ b/cmd.md @@ -0,0 +1,87 @@ +### 常用命令说明 + + +- 面板相关命令 + +``` +mw start | 启动面板服务器 +mw stop | 停止面板服务器 +mw restart | 重启面板服务器 +mw default | 显示登录信息 +mw db | 快捷连接MySQL +mw redis | 快捷连接Redis +mw valkey | 快捷连接valkey +mw mongodb | 快捷连接MongoDB +mw pgdb | 快捷连接PostgreSQL +mw ssh | 快捷连接ssh +---------------------------------------- +mw open | 开启面板 +mw close | 关闭面板 + +mw debug | 开发测试 +mw venv | 进入虚拟环境 +mw mirror | 切换镜像 +mw install_app | 快捷安装常用软件 +mw update | 更新到正式 +mw dev/update_dev | 更新到开发 + +service mw [start|stop|reload|restart|status] +``` + +- OpenResty + +``` + +systemctl [start|stop|reload|restart|status] openresty + +``` + +- MySQL + +``` + +systemctl [start|stop|reload|restart|status] mysql + +``` + +- MariaDB + +``` + +systemctl [start|stop|reload|restart|status] mariadb + +``` + +- PHP + +``` + +systemctl [start|stop|reload|restart|status] php[54-81] + +systemctl start php71 +``` + +- Redis + +``` + +systemctl [start|stop|reload|restart|status] redis + +``` + +- Memcached + +``` + +systemctl [start|stop|reload|restart|status] memcached + +``` + + +- sphinx + +``` + +systemctl [start|stop|reload|restart|status] sphinx + +``` diff --git a/compatibility.md b/compatibility.md new file mode 100644 index 000000000..37b61457a --- /dev/null +++ b/compatibility.md @@ -0,0 +1,71 @@ +### 兼容性测试报告 + +快速测试命令 + +``` +cd /www/server/mdserver-web/scripts/quick && bash debug.sh +cd /www/server/mdserver-web/plugins/php/versions && bash all_test.sh +``` + + +| 系统名称 | 面板 | OpenResty | +| ----------------- |---------------|---------------| +| CentOS 7.9 |✅ |✅ | +| CentOS 8.4 |✅ |✅ | +| CentOS 8 Stream |✅ |✅ | +| CentOS 9 Stream |✅ |✅ | +| Debian 10.3 |✅ |✅ | +| Debian 11.3 |✅ |✅ | +| Debian 12.0 |✅ |✅ | +| Ubuntu 18.04 |✅ |✅ | +| Ubuntu 20.04 |✅ |✅ | +| Ubuntu 22.04 |✅ |✅ | +| Fedora 31 |✅ |✅ | +| Fedora 32 |✅ |✅ | +| AlmaLinux 9 |✅ |✅ | +| RockyLinux 8.6 |✅ |✅ | +| Arch Linux  |✅ |✅ | +| openSUSE 15.4 |✅ |✅ | + + + +| 系统名称 |PHP53 |PHP54 |PHP55 |PHP56 |PHP70 |PHP71 |PHP72 |PHP73 |PHP74 |PHP80 |PHP81 |PHP82 |PHP83 |PHP84 | +| ----------------- |-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------| +| CentOS 7.9 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| CentOS 8.4 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| CentOS 8 Stream |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| CentOS 9 Stream |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| Debian 10.3 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| Debian 11.3 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| Debian 12 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| Ubuntu 18.04 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| Ubuntu 20.04 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| Ubuntu 22.04 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| Fedora 31 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| Fedora 32 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| AlmaLinux 9 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| RockyLinux 8.6 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| Arch Linux |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | +| openSUSE 15.4 |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ | + + + +| 系统名称 | MySQL55|MySQL56|MySQL57|MySQL80| +| ----------------- |-------|--------|-------|-------| +| CentOS 7.9 |✅ |✅ |✅ |✅ | +| CentOS 8.4 |✅ |✅ |✅ |✅ | +| CentOS 8 Stream |✅ |✅ |✅ |✅ | +| CentOS 9 Stream |✅ |✅ |✅ |✅ | +| Debian 10.3 |✅ |✅ |✅ |✅ | +| Debian 11.3 |✅ |✅ |✅ |✅ | +| Debian 12 |✅ |✅ |✅ |✅ | +| Ubuntu 18.04 |✅ |✅ |✅ |✅ | +| Ubuntu 20.04 |✅ |✅ |✅ |✅ | +| Ubuntu 22.04 |✅ |✅ |✅ |✅ | +| Fedora 31 |✅ |✅ |✅ |✅ | +| Fedora 32 |✅ |✅ |✅ |✅ | +| AlmaLinux 9 |✅ |✅ |✅ |✅ | +| RockyLinux 8.6 |✅ |✅ |✅ |✅ | +| Arch Linux |✅ |✅ |✅ |✅ | +| openSUSE 15.4 |✅ |✅ |✅ |✅ | + diff --git a/data/README.md b/data/README.md new file mode 100644 index 000000000..bc8dc9fc5 --- /dev/null +++ b/data/README.md @@ -0,0 +1,4 @@ +# 自动生成 + +certificate.pem - 证书信息 +privateKey.pem - 私钥 \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..c3fafd629 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,62 @@ +FROM debian:10.12-slim + +STOPSIGNAL SIGRTMIN+3 + +RUN rm -f /lib/systemd/system/multi-user.target.wants/* \ + /etc/systemd/system/*.wants/* \ + /lib/systemd/system/local-fs.target.wants/* \ + /lib/systemd/system/sockets.target.wants/*udev* \ + /lib/systemd/system/sockets.target.wants/*initctl* \ + /lib/systemd/system/sysinit.target.wants/systemd-tmpfiles-setup* \ + /lib/systemd/system/systemd-update-utmp* + +RUN apt update -y && \ + apt install -y devscripts && \ + apt install -y wget zip unzip && \ + apt-get install -y locales && \ + localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 && \ + locale-gen en_US.UTF-8 && \ + export LC_ALL=en_US.UTF-8 && \ + export LANG=en_US.UTF-8 && \ + export LANGUAGE=en_US.UTF-8 && \ + localedef -i en_US -f UTF-8 en_US.UTF-8 && \ + mkdir -p /www/server && \ + mkdir -p /www/wwwroot && \ + mkdir -p /www/wwwlogs && \ + mkdir -p /www/backup/database && \ + mkdir -p /www/backup/site && \ + wget -O /tmp/dev.zip https://github.com/midoks/mdserver-web/archive/refs/heads/dev.zip && \ + cd /tmp && unzip /tmp/dev.zip && \ + mv -f /tmp/mdserver-web-dev /www/server/mdserver-web && \ + rm -rf /tmp/dev.zip && \ + rm -rf /tmp/mdserver-web-dev && \ + cd /www/server/mdserver-web && \ + bash scripts/install/debian.sh && \ + cd /www/server/mdserver-web/ && \ + /www/server/mdserver-web/bin/python tools.py username username && \ + cd /www/server/mdserver-web/ && \ + /www/server/mdserver-web/bin/python tools.py panel password + +RUN cd /www/server/mdserver-web/plugins/php && \ + bash install.sh install 74 && \ + cd /www/server/mdserver-web/plugins/openresty && \ + bash install.sh install 1.21.4 && \ + cd /www/server/mdserver-web/plugins/mysql && \ + bash install.sh install 5.6 && \ + cd /www/server/mdserver-web/plugins/phpmyadmin && \ + bash install.sh install 4.4.15 && \ + systemctl enable openresty && \ + systemctl enable php74 && \ + systemctl enable mysql + +RUN rm -rf /www/server/mysql/data + +ADD ./start.sh /start.sh +ADD start.service /usr/lib/systemd/system/start.service +#RUN systemctl enable start + +CMD [ "/lib/systemd/systemd", "log-level=info", "unit=sysinit.target" ] + +EXPOSE 7200 80 443 888 + +VOLUME [ "/www" ] \ No newline at end of file diff --git a/docker/start.service b/docker/start.service new file mode 100644 index 000000000..d7dda1a0b --- /dev/null +++ b/docker/start.service @@ -0,0 +1,9 @@ +[Unit] +Description=Customize the script +After=network.target + +[Service] +ExecStart=/start.sh + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/docker/start.sh b/docker/start.sh new file mode 100644 index 000000000..cc1f786e8 --- /dev/null +++ b/docker/start.sh @@ -0,0 +1 @@ +#!/bin/bash \ No newline at end of file diff --git a/logs/README.md b/logs/README.md new file mode 100644 index 000000000..cd3d22536 --- /dev/null +++ b/logs/README.md @@ -0,0 +1 @@ +logs \ No newline at end of file diff --git a/panel_task.py b/panel_task.py new file mode 100755 index 000000000..e17df2a0f --- /dev/null +++ b/panel_task.py @@ -0,0 +1,361 @@ +# coding: utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 计划任务 +# --------------------------------------------------------------------------------- + +import sys +import os +import json +import time +import threading +import psutil + + +web_dir = os.getcwd() + "/web" +os.chdir(web_dir) +sys.path.append(web_dir) + +import core.mw as mw +import thisdb + +from admin import setup +setup.init() + +g_log_file = mw.getPanelTaskExecLog() +if not os.path.exists(g_log_file): + os.system("touch " + g_log_file) + +def execShell(cmdstring, cwd=None, timeout=None, shell=True): + cmd = cmdstring + ' > ' + g_log_file + ' 2>&1' + return mw.execShell(cmd) + +def writeLogs(data): + # 写输出日志 + try: + fp = open(g_log_file, 'w+') + fp.write(data) + fp.close() + except: + pass + +def downloadFile(url, filename): + # 下载文件 + try: + import urllib + import socket + socket.setdefaulttimeout(300) + + headers = ('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36') + opener = urllib.request.build_opener() + opener.addheaders = [headers] + urllib.request.install_opener(opener) + + urllib.request.urlretrieve(url, filename=filename, reporthook=downloadHook) + + if not mw.isAppleSystem(): + os.system('chown www.www ' + filename) + + writeLogs(filename + ' download success!') + except Exception as e: + writeLogs(str(e)) + return True + +def downloadHook(count, blockSize, totalSize): + # 下载文件进度回调 + used = count * blockSize + pre = int((100.0 * used / totalSize)) + speed = {'total': totalSize, 'used': used, 'pre': pre} + writeLogs(json.dumps(speed)) + +def runPanelTask(): + # 站点过期检查 + siteEdateCheck() + + lock_file = mw.getTriggerTaskLockFile() + try: + if os.path.exists(lock_file): + bash_list = thisdb.getTaskList(status=-1) + for task in bash_list: + thisdb.setTaskStatus(task['id'], 0) + + run_list = thisdb.getTaskList(status=0) + for run_task in run_list: + start = int(time.time()) + thisdb.setTaskData(run_task['id'], start=start) + thisdb.setTaskStatus(run_task['id'], -1) + + if run_task['type'] == 'download': + argv = run_task['cmd'].split('|mw|') + downloadFile(argv[0], argv[1]) + elif run_task['type'] == 'execshell': + execShell(run_task['cmd']) + + end = int(time.time()) + thisdb.setTaskData(run_task['id'], end=end) + thisdb.setTaskStatus(run_task['id'], 1) + + if thisdb.getTaskUnexecutedCount() < 1: + os.remove(lock_file) + except Exception as e: + print('runPanelTask:',mw.getTracebackInfo()) + +# 网站到期处理 +def siteEdateCheck(): + try: + from utils.site import sites as MwSites + website_edate = thisdb.getOption('website_edate', default='0000-00-00') + now_time_ymd = time.strftime('%Y-%m-%d', time.localtime()) + + if website_edate == now_time_ymd: + return False + site_list = thisdb.getSitesEdateList(now_time_ymd) + for site in site_list: + MwSites.instance().stop(site['id']) + thisdb.setOption('website_edate', now_time_ymd) + except Exception as e: + print('siteEdateCheck:',mw.getTracebackInfo()) + +# 任务队列 +def startPanelTask(): + try: + while True: + runPanelTask() + time.sleep(5) + except Exception as e: + print('startPanelTask:',mw.getTracebackInfo()) + time.sleep(30) + startPanelTask() + +def systemTask(): + # 系统监控任务 + from utils.system import monitor + try: + while True: + monitor_status = thisdb.getOption('monitor_status',type='monitor',default='open') + if monitor_status == 'open': + monitor.instance().run() + time.sleep(5) + except Exception as ex: + print('systemTask:',mw.getTracebackInfo()) + time.sleep(30) + systemTask() + + +def panelPluginStatusCheck(): + # 系统监控任务 + from utils.plugin import plugin + try: + while True: + # start_t = time.time() + plugin.instance().autoCachePluginStatus() + # end_t = time.time() + time.sleep(60) + except Exception as ex: + print('panelPluginStatusCheck:',mw.getTracebackInfo()) + time.sleep(120) + panelPluginStatusCheck() + +# -------------------------------------- PHP监控 start --------------------------------------------- # +# 502错误检查线程 +def check502Task(): + try: + check_file = mw.getPanelDir() + '/data/502Task.pl' + while True: + if os.path.exists(check_file): + check502() + time.sleep(10) + except: + time.sleep(30) + check502Task() + + +def check502(): + try: + verlist = [ + '52', '53', '54', '55', '56', '70', + '71', '72', '73', '74', '80', '81', + '82', '83', '84' + ] + for ver in verlist: + server_dir = mw.getServerDir() + php_path = server_dir + '/php/' + ver + '/sbin/php-fpm' + if not os.path.exists(php_path): + continue + if checkPHPVersion(ver): + continue + if startPHPVersion(ver): + print('检测到PHP-' + ver + '处理异常,已自动修复!') + mw.writeLog('PHP守护程序', '检测到PHP-' + ver + '处理异常,已自动修复!') + + except Exception as e: + mw.writeLog('PHP守护程序', '自动修复异常:'+str(e)) + + +# 处理指定PHP版本 +def startPHPVersion(version): + server_dir = mw.getServerDir() + try: + # system + phpService = mw.systemdCfgDir() + '/php' + version + '.service' + if os.path.exists(phpService): + mw.execShell("systemctl restart php" + version) + if checkPHPVersion(version): + return True + + # initd + fpm = server_dir + '/php/init.d/php' + version + php_path = server_dir + '/php/' + version + '/sbin/php-fpm' + if not os.path.exists(php_path): + if os.path.exists(fpm): + os.remove(fpm) + return False + + if not os.path.exists(fpm): + return False + + # 尝试重载服务 + os.system(fpm + ' reload') + if checkPHPVersion(version): + return True + # 尝试重启服务 + cgi = '/tmp/php-cgi-' + version + '.sock' + pid = server_dir + '/php/' + version + '/var/run/php-fpm.pid' + data = mw.execShell("ps -ef | grep php/" + version +" | grep -v grep|grep -v python |awk '{print $2}'") + if data[0] != '': + os.system("ps -ef | grep php/" + version + " | grep -v grep|grep -v python |awk '{print $2}' | xargs kill ") + time.sleep(0.5) + if not os.path.exists(cgi): + os.system('rm -f ' + cgi) + if not os.path.exists(pid): + os.system('rm -f ' + pid) + os.system(fpm + ' start') + if checkPHPVersion(version): + return True + + # 检查是否正确启动 + if os.path.exists(cgi): + return True + except Exception as e: + print('startPHPVersion:',mw.getTracebackInfo()) + mw.writeLog('PHP守护程序', '自动修复异常:'+str(e)) + return True + + +def checkPHPVersion(version): + # 检查指定PHP版本 + try: + sock = mw.getFpmAddress(version) + data = mw.requestFcgiPHP(sock, '/phpfpm_status_' + version + '?json') + result = str(data, encoding='utf-8') + except Exception as e: + result = 'Bad Gateway' + # 检查openresty + if result.find('Bad Gateway') != -1: + return False + if result.find('HTTP Error 404: Not Found') != -1: + return False + + # 检查Web服务是否启动 + if result.find('Connection refused') != -1: + return False + return True + +# -------------------------------------- PHP监控 end --------------------------------------------- # + + +# --------------------------------------OpenResty Auto Restart Start --------------------------------------------- # +# 解决acme.sh续签后,未起效。 +def openrestyAutoRestart(): + try: + while True: + # 检查是否安装 + odir = mw.getServerDir() + '/openresty' + if not os.path.exists(odir): + time.sleep(86400) + continue + mw.opWeb('reload') + time.sleep(86400) + except Exception as e: + mw.writeLog('OpenResty检测', '自动修复异常:'+str(e)) + time.sleep(86400) + +# --------------------------------------OpenResty Auto Restart End --------------------------------------------- # + +# ------------------------------------ OpenResty Restart At Once Start ------------------------------------------ # + + +def openrestyRestartAtOnce(): + restart_nginx_tip = mw.getPanelDir()+'/data/restart_nginx.pl' + while True: + if os.path.exists(restart_nginx_tip): + os.remove(restart_nginx_tip) + mw.opWeb('reload') + time.sleep(1) +# ----------------------------------- OpenResty Restart At Once End ------------------------------------------ # + + +# --------------------------------------Panel Restart Start --------------------------------------------- # +def restartPanelService(): + restart_tip = mw.getPanelDir()+'/data/restart.pl' + while True: + if os.path.exists(restart_tip): + print("restart panel") + os.remove(restart_tip) + mw.panelCmd('restart_panel') + time.sleep(1) +# --------------------------------------Panel Restart End --------------------------------------------- # + + +def setDaemon(t): + if sys.version_info.major == 3 and sys.version_info.minor >= 10: + t.daemon = True + else: + t.setDaemon(True) + return t + +def run(): + # 系统监控 + sysTask = threading.Thread(target=systemTask) + sysTask = setDaemon(sysTask) + sysTask.start() + + # PHP 502错误检查线程 + php502 = threading.Thread(target=check502Task) + php502 = setDaemon(php502) + php502.start() + + # OpenResty Restart At Once Start + oraos = threading.Thread(target=openrestyRestartAtOnce) + oraos = setDaemon(oraos) + oraos.start() + + # OpenResty Auto Restart Start + oar = threading.Thread(target=openrestyAutoRestart) + oar = setDaemon(oar) + oar.start() + + # Panel Plugin Status Check + pps = threading.Thread(target=panelPluginStatusCheck) + pps = setDaemon(pps) + pps.start() + + # Panel Restart Start + rps = threading.Thread(target=restartPanelService) + rps = setDaemon(rps) + rps.start() + + # 面板后台任务 + startPanelTask() + +if __name__ == "__main__": + run() + diff --git a/panel_tools.py b/panel_tools.py new file mode 100755 index 000000000..c246541db --- /dev/null +++ b/panel_tools.py @@ -0,0 +1,433 @@ +# coding=utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 工具箱 +# --------------------------------------------------------------------------------- + + +import sys +import os +import json +import time +import re + +web_dir = os.getcwd() + "/web" +os.chdir(web_dir) +sys.path.append(web_dir) + +from utils.firewall import Firewall as MwFirewall +import core.mw as mw +import thisdb + +INIT_DIR = "/etc/rc.d/init.d" +if mw.isAppleSystem(): + INIT_DIR = mw.getPanelDir() + "/scripts/init.d" + +INIT_CMD = INIT_DIR + "/mw" + + +def mw_input_cmd(msg): + if sys.version_info[0] == 2: + in_val = raw_input(msg) + else: + in_val = input(msg) + return in_val + +def getRemainLen(cmd, max_length=100): + cmd_len = len(cmd) + cmd_u8_len = len(cmd.encode('utf-8')) + return max_length-int((cmd_u8_len - cmd_len)/2+cmd_len) + +def mwcli(mw_input=0): + panel_dir = mw.getPanelDir() + + raw_tip = "========================================================================" + if not mw_input: + print("========================mdserver-web cli tools==========================") + cmd_list = [ + '(1) 重启面板服务', + '(2) 停止面板服务', + '(3) 启动面板服务', + '(4) 重载面板服务', + '(5) 修改面板IP', + '(6) 修改面板端口', + '(7) 关闭安全入口', + '(10) 查看面板默认信息', + '(11) 修改面板密码', + '(12) 修改面板用户名', + '(13) 显示面板错误日志', + '(14) 关闭面板访问', + '(15) 开启面板访问', + '(20) 关闭BasicAuth认证', + '(21) 解除域名绑定', + '(22) 解除面板SSL绑定', + '(23) 开启IPV6支持', + '(24) 关闭IPV6支持', + '(25) 开启防火墙SSH端口', + '(26) 关闭二次验证', + '(27) 查看防火墙信息', + '(28) 自动识别防火墙端口到面板', + '(29) 自动识别配置站点信息', + '(100) 开启PHP52显示', + '(101) 关闭PHP52显示', + '(200) 切换Linux系统软件源', + '(201) 简单速度测试', + '(202) SSH终端管理', + '(0) 取消' + ] + cmd_list_num = len(cmd_list) + for index in range(cmd_list_num): + cmd = cmd_list[index] + if index % 2 == 0: + if index == (cmd_list_num-1): + print(cmd) + else: + print(cmd + " " * getRemainLen(cmd, 40), end="") + if index % 2 == 1: + print(cmd) + print(raw_tip) + try: + mw_input = input("请输入命令编号:") + if sys.version_info[0] == 3: + mw_input = int(mw_input) + except: + mw_input = 0 + + nums = [ + 1, 2, 3, 4, 5, 6, 7, + 10, 11, 12, 13, 14, 15, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 100, 101, + 200, 201, 202 + ] + if not mw_input in nums: + print(raw_tip) + print("已取消!") + exit() + + if mw_input == 1: + os.system(INIT_CMD + " restart") + elif mw_input == 2: + os.system(INIT_CMD + " stop") + elif mw_input == 3: + os.system(INIT_CMD + " start") + elif mw_input == 4: + os.system(INIT_CMD + " reload") + elif mw_input == 5: + in_ip = mw_input_cmd("请输入设置的面板IP:") + in_ip = in_ip.strip() + ip_text = panel_dir + '/data/iplist.txt' + if not mw.isVaildIp(in_ip): + mw.echoInfo("【"+in_ip+"】: IP不合法") + return + mw.writeFile(ip_text, in_ip) + thisdb.setOption('server_ip', in_ip) + mw.echoInfo("设置面板IP: " + in_ip) + elif mw_input == 6: + in_port = mw_input_cmd("请输入新的面板端口:") + in_port_int = int(in_port.strip()) + if in_port_int < 65536 and in_port_int > 0: + MwFirewall.instance().addAcceptPort(in_port, 'WEB面板[TOOLS修改]', 'port') + panel_port = panel_dir + '/data/port.pl' + mw.writeFile(panel_port, in_port) + os.system(INIT_CMD + " restart_panel") + os.system(INIT_CMD + " default") + else: + mw.echoInfo("端口范围在0-65536之间") + return + elif mw_input == 7: + thisdb.setOption('admin_path', '') + mw.echoInfo("关闭安全入口成功!") + elif mw_input == 10: + os.system(INIT_CMD + " default") + elif mw_input == 11: + input_pwd = mw_input_cmd("请输入新的面板密码:") + if len(input_pwd.strip()) < 5: + mw.echoInfo("错误,密码长度不能小于5位") + return + set_panel_pwd(input_pwd.strip(), True) + elif mw_input == 12: + input_user = mw_input_cmd("请输入新的面板用户名(>=5位):") + set_panel_username(input_user.strip()) + elif mw_input == 13: + os.system('tail -100 ' + panel_dir + '/logs/panel_error.log') + elif mw_input == 14: + admin_close = thisdb.getOption('admin_close') + if admin_close == 'no': + thisdb.setOption('admin_close', 'yes') + mw.echoInfo("关闭面板访问成功!") + else: + mw.echoInfo("已关闭面板访问!") + elif mw_input == 15: + admin_close = thisdb.getOption('admin_close') + if admin_close == 'yes': + thisdb.setOption('admin_close', 'no') + mw.echoInfo("开启面板访问成功!") + else: + mw.echoInfo("已开启面板访问!") + elif mw_input == 20: + basic_auth = thisdb.getOptionByJson('basic_auth', default={'open':False}) + if basic_auth['open']: + basic_auth['open'] = False + thisdb.setOption('basic_auth', json.dumps(basic_auth)) + os.system(INIT_CMD + " restart") + mw.echoInfo("关闭basic_auth成功") + elif mw_input == 21: + panel_domain = thisdb.getOption('panel_domain', default='') + if panel_domain != '': + thisdb.setOption('panel_domain', '') + os.system(INIT_CMD + " unbind_domain") + mw.echoInfo("解除域名绑定成功") + else: + mw.echoInfo("面板未绑定域名!") + elif mw_input == 22: + panel_ssl = thisdb.getOptionByJson('panel_ssl', default={'open':False}) + if panel_ssl['open']: + panel_ssl['open'] = False + thisdb.setOption('panel_ssl', json.dumps(panel_ssl)) + os.system(INIT_CMD + " unbind_ssl") + mw.echoInfo("解除面板SSL绑定成功") + elif mw_input == 23: + listen_ipv6 = panel_dir + '/data/ipv6.pl' + if not os.path.exists(listen_ipv6): + mw.writeFile(listen_ipv6, 'True') + os.system(INIT_CMD + " restart") + mw.echoInfo("开启IPv6支持了") + else: + mw.echoInfo("已开启IPv6支持!") + elif mw_input == 24: + listen_ipv6 = panel_dir + '/data/ipv6.pl' + if not os.path.exists(listen_ipv6): + mw.echoInfo("已关闭IPv6支持!") + else: + os.remove(listen_ipv6) + os.system(INIT_CMD + " restart") + mw.echoInfo("关闭IPv6支持了") + elif mw_input == 25: + open_ssh_port() + mw.echoInfo("已开启!") + elif mw_input == 26: + two_step_verification = thisdb.getOptionByJson('two_step_verification', default={'open':False}) + if two_step_verification['open']: + two_step_verification['open'] = False + thisdb.setOption('two_step_verification', json.dumps(two_step_verification)) + mw.echoInfo("关闭二次验证成功!") + else: + mw.echoInfo("二次验证已关闭!") + elif mw_input == 27: + cmd = 'which ufw' + run_cmd = False + find_cmd = mw.execShell(cmd) + if find_cmd[0].strip() != '': + run_cmd = True + os.system('ufw status') + + cmd = 'which firewall-cmd' + find_cmd = mw.execShell(cmd) + if find_cmd[0].strip() != '': + run_cmd = True + os.system('firewall-cmd --list-all') + if not run_cmd: + mw.echoInfo("未检测到防火墙!") + elif mw_input == 28: + MwFirewall.instance().aIF() + mw.echoInfo("执行自动识别防火墙端口到面板成功!") + elif mw_input == 29: + from utils.site_reflect import parse as MwParse + MwParse() + mw.echoInfo("自动识别配置站点信息成功!") + elif mw_input == 100: + php_conf = panel_dir + '/plugins/php/info.json' + if os.path.exists(php_conf): + cont = mw.readFile(php_conf) + cont = re.sub("\"53\"", "\"52\",\"53\"", cont) + cont = re.sub("\"5.3.29\"", "\"5.2.17\",\"5.3.29\"", cont) + mw.writeFile(php_conf, cont) + mw.echoInfo("执行PHP52显示成功!") + elif mw_input == 101: + php_conf = panel_dir + '/plugins/php/info.json' + if os.path.exists(php_conf): + cont = mw.readFile(php_conf) + cont = re.sub("\"52\",", "", cont) + cont = re.sub("\"5.2.17\",", cont) + mw.writeFile(php_conf, cont) + mw.echoInfo("执行PHP52隐藏成功!") + elif mw_input == 200: + os.system("bash <(curl -sSL https://linuxmirrors.cn/main.sh)") + # os.system(INIT_CMD + " mirror") + elif mw_input == 201: + os.system('curl -Lso- bench.sh | bash') + elif mw_input == 202: + package = mw.getPanelDir()+'/plugins' + + dst_plugin = mw.getServerDir() + "/webssh" + if not os.path.exists(dst_plugin): + mw.echoInfo("未安装!") + exit(1) + + if not package in sys.path: + sys.path.append(package) + from webssh.index import App + + obj = App() + data = obj.get_server_list() + data = json.loads(data) + + if data['status']: + wlist = data['data'] + wlist_len = len(wlist) + if wlist_len == 0: + mw.echoInfo("SSH终端管理,数据为空!") + exit(1) + + if len(sys.argv) == 4: + pos = int(sys.argv[3]) + if pos >= wlist_len: + mw.echoInfo("SSH终端管理,超过限制!") + exit(1) + dst_data = wlist[pos] + tmp_host = dst_data["host"] + info = obj.get_server_by_host_data(tmp_host) + pp_info = tmp_host+"|"+info["port"]+"|"+info['username']+"|"+info['password']+"" + print(pp_info) + exit(0) + + mw.echoInfo("请选择:") + for x in range(wlist_len): + dst_data = wlist[x] + tag = dst_data['host'] + if dst_data['ps'] != "": + tag = dst_data['ps'] + print(str(x) +") " + tag) + +def open_ssh_port(): + + find_ssh_port_cmd = "cat /etc/ssh/sshd_config | grep '^Port \\d*' | tail -1" + cmd_data = mw.execShell(find_ssh_port_cmd) + ssh_port = cmd_data[0].replace("Port ", '').strip() + if ssh_port == '': + ssh_port = '22' + + mw.echoInfo("SSH端口: "+ str(ssh_port)) + MwFirewall.instance().addAcceptPort(ssh_port, 'SSH远程管理服务', 'port') + return True + + +def set_panel_pwd(password, ncli=False): + info = thisdb.getUserByRoot() + thisdb.setUserByRoot(password=password) + if ncli: + mw.echoInfo("username: " + info['name']) + mw.echoInfo("password: " + password) + else: + print(username) + + +def show_panel_pwd(): + # 面板密码展示 + info = thisdb.getUserByRoot() + defailt_pwd_file = mw.getPanelDir()+'/data/default.pl' + pwd = '' + if os.path.exists(defailt_pwd_file): + pwd = mw.readFile(defailt_pwd_file).strip() + + if mw.md5(pwd) == info['password']: + mw.echoInfo('password: ' + pwd) + return + print("*-password has been changed!") + +def show_panel_adminpath(): + admin_path = thisdb.getOption('admin_path') + if admin_path == '': + print('/login') + else: + print('/'+admin_path) + + +def set_panel_username(username=None): + # 随机面板用户名 + if username: + if len(username) < 5: + mw.echoInfo("错误,用户名长度不能少于5位") + return + if username in ['admin', 'root']: + mw.echoInfo("错误,不能使用过于简单的用户名") + return + + thisdb.setUserByRoot(name=username) + mw.echoInfo("username: %s" % username) + return + + info = thisdb.getUserByRoot() + if info['name'] == 'admin': + username = mw.getRandomString(8).lower() + thisdb.setUserByRoot(name=username) + mw.echoInfo('username: ' + info['name']) + + +def getServerIp(): + version = sys.argv[2] + # ip = mw.execShell( + # "curl --insecure -{} -sS --connect-timeout 5 -m 60 https://v6r.ipip.net/?format=text".format(version)) + ip = mw.execShell( + "curl --insecure -{} -sS --connect-timeout 5 -m 60 https://ip.cachecha.com/?format=text".format(version)) + # print(ip[0]) + return ip[0] + + +def getPanelSslType(): + scheme = 'http' + panel_ssl = thisdb.getOptionByJson('panel_ssl', default={'open':False}) + if panel_ssl['open']: + scheme = 'https' + return scheme + +def getPanelBindDomain(): + return thisdb.getOption('panel_domain', default='') + + +def main(): + if len(sys.argv) == 1: + print('ERROR: Parameter error!') + exit(-2) + method = sys.argv[1] + if method == 'panel': + set_panel_pwd(sys.argv[2]) + elif method == 'username': + if len(sys.argv) > 2: + set_panel_username(sys.argv[2]) + else: + set_panel_username() + elif method == 'password': + show_panel_pwd() + elif method == 'test': + thisdb.getOption('admin_path') + elif method == 'admin_path': + show_panel_adminpath() + elif method == 'getServerIp': + print(getServerIp()) + elif method == 'panel_ssl_type': + print(getPanelSslType()) + elif method == 'panel_bind_domain': + print(getPanelBindDomain()) + elif method == "cli": + clinum = 0 + try: + if len(sys.argv) > 2: + clinum = int(sys.argv[2]) if sys.argv[2][:6] else sys.argv[2] + except: + clinum = sys.argv[2] + mwcli(clinum) + else: + print('ERROR: Parameter error') + +if __name__ == "__main__": + main() diff --git a/plugins/acme_pandominassl_apply/conf/acme.sql b/plugins/acme_pandominassl_apply/conf/acme.sql new file mode 100644 index 000000000..87a38c27a --- /dev/null +++ b/plugins/acme_pandominassl_apply/conf/acme.sql @@ -0,0 +1,34 @@ +CREATE TABLE IF NOT EXISTS `dnsapi` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `name` TEXT, + `type` TEXT, + `val` TEXT, + `remark` TEXT, + `addtime` TEXT +); + +CREATE TABLE IF NOT EXISTS `email` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `addr` TEXT, + `remark` TEXT, + `addtime` TEXT +); + + +CREATE TABLE IF NOT EXISTS `domain` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `domain` TEXT, + `dnsapi_id` TEXT, + `email` TEXT, + `remark` TEXT, + `effective_date` TEXT default '', + `expiration_date` TEXT default '', + `error` TEXT, + `status` INTEGER default '0', + `addtime` TEXT +); + +-- ALTER TABLE `domain` ADD COLUMN `effective_date` TEXT DEFAULT ''; +-- ALTER TABLE `domain` ADD COLUMN `expiration_date` TEXT DEFAULT ''; +-- ALTER TABLE `domain` ADD COLUMN `error` TEXT DEFAULT ''; +-- ALTER TABLE `domain` ADD COLUMN `status` INTEGER DEFAULT '0'; diff --git a/plugins/acme_pandominassl_apply/hooks/goedge.py b/plugins/acme_pandominassl_apply/hooks/goedge.py new file mode 100644 index 000000000..8cc81f901 --- /dev/null +++ b/plugins/acme_pandominassl_apply/hooks/goedge.py @@ -0,0 +1,136 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import requests +import base64 + + +# 8001 / 7788 +goedge_addr = 'http://127.0.0.2:8009' +access_keyid = "xxx" +access_key = "xxx" + +# 指定用户 +userId = 1 + +if sys.platform != "darwin": + os.chdir("/www/server/mdserver-web") + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +domain = sys.argv[1] +ssl_path = sys.argv[2] + + +def getToken(): + api_url = goedge_addr+'/APIAccessTokenService/getAPIAccessToken' + post_data = { + "type": "admin", + "accessKeyId": access_keyid, + "accessKey": access_key + } + data = requests.post(api_url,json=post_data) + data_obj = data.json() + + return data_obj['data']['token'] + +token = getToken() + +def commonReq(url, data): + headers = { + 'X-Edge-Access-Token': token + } + api_url = goedge_addr+url + resp_data = requests.post(api_url,json=data, headers=headers) + return resp_data.json() + +def listSSLCerts(domain): + request_data = { + "userId":userId, + "isCA":False, + "keyword": "ACME泛域名自动上传", + "domains":[domain,"*."+domain], + "size":1 + } + response_data = commonReq('/SSLCertService/listSSLCerts', request_data) + + data = response_data['data']['sslCertsJSON'] + data = mw.base64StrDecode(data) + data = mw.getObjectByJson(data) + # print(data) + return data + + + +# createSSLCert(domain) +def createSSLCert(domain, did=0): + + ssl_cer_file = ssl_path + '/'+domain+'.cer' + + if not os.path.exists(ssl_cer_file): + print("没有有效证书!") + return '' + # print(ssl_cer_file) + ssl_info = mw.getCertName(ssl_cer_file) + cer_data = mw.readFile(ssl_cer_file) + cer_data = mw.base64StrEncode(cer_data) + # print('cer',cer_data) + + ssl_key_file = ssl_path + '/'+domain+'.key' + key_data = mw.readFile(ssl_key_file) + key_data = mw.base64StrEncode(key_data) + # print('ssl_info',ssl_info) + + timeBeginAt = int(time.mktime(time.strptime(ssl_info['notBefore'], "%Y-%m-%d"))) + timeEndAt = int(time.mktime(time.strptime(ssl_info['notAfter'], "%Y-%m-%d"))) + + request_data = { + "isOn":True, + "userId":userId, + "name": "ACME泛域名自动上传", + "isCA":False, + "description":domain, + "serverName":domain, + "certData":cer_data, + "keyData":key_data, + "timeBeginAt":timeBeginAt, + "timeEndAt": timeEndAt, + "dnsNames":[domain,"*."+domain], + "commonNames":[ssl_info['issuer']] + } + + if did>0: + request_data['sslCertId'] = did + # print(request_data) + response_data = commonReq('/SSLCertService/updateSSLCert', request_data) + print('更新成功',domain,response_data) + return response_data + else: + # print(request_data) + response_data = commonReq('/SSLCertService/createSSLCert', request_data) + print('创建成功',domain,response_data) + return response_data + return response_data + +def autoSyncDomain(domain): + data = listSSLCerts(domain) + if len(data) > 0 : + did = data[0]['id'] + createSSLCert(domain,did) + else: + createSSLCert(domain) + print(data) + + +autoSyncDomain(domain) +print(domain,ssl_path) \ No newline at end of file diff --git a/plugins/acme_pandominassl_apply/hooks/init.py b/plugins/acme_pandominassl_apply/hooks/init.py new file mode 100644 index 000000000..e955f0a1e --- /dev/null +++ b/plugins/acme_pandominassl_apply/hooks/init.py @@ -0,0 +1,12 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +domain = sys.argv[1] +path = sys.argv[2] + +print(domain,path) \ No newline at end of file diff --git a/plugins/acme_pandominassl_apply/ico.png b/plugins/acme_pandominassl_apply/ico.png new file mode 100644 index 000000000..c41018939 Binary files /dev/null and b/plugins/acme_pandominassl_apply/ico.png differ diff --git a/plugins/acme_pandominassl_apply/index.html b/plugins/acme_pandominassl_apply/index.html new file mode 100755 index 000000000..84909da77 --- /dev/null +++ b/plugins/acme_pandominassl_apply/index.html @@ -0,0 +1,34 @@ + + +
+
+
+
+

服务

+

DNSAPI *

+

邮件地址

+

域名SSL *

+

HOOK

+

日志

+

相关说明

+ +
+
+
+
+
+
+ \ No newline at end of file diff --git a/plugins/acme_pandominassl_apply/index.py b/plugins/acme_pandominassl_apply/index.py new file mode 100755 index 000000000..72056fae4 --- /dev/null +++ b/plugins/acme_pandominassl_apply/index.py @@ -0,0 +1,863 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import requests +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'acme_pandominassl_apply' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/hook.py" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + +def runLog(): + return getServerDir() + '/hook.log' + +def getArgs(): + args = sys.argv[3:] + # print(args) + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getHomeDir(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + else: + return '/root' + + +def getRunUser(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + else: + return 'root' + +def configTpl(): + path = getPluginDir() + '/hooks' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/redis') + content = content.replace('{$REDIS_PASS}', mw.getRandomString(10)) + return content + + +def pSqliteDb(dbname='dnsapi'): + file = getServerDir() + '/acme.db' + name = 'acme' + + import_sql = mw.readFile(getPluginDir() + '/conf/acme.sql') + md5_sql = mw.md5(import_sql) + + import_sign = False + save_md5_file = getServerDir() + '/acme.md5' + if os.path.exists(save_md5_file): + save_md5_sql = mw.readFile(save_md5_file) + if save_md5_sql != md5_sql: + import_sign = True + mw.writeFile(save_md5_file, md5_sql) + else: + mw.writeFile(save_md5_file, md5_sql) + + if not os.path.exists(file) or import_sql: + conn = mw.M(dbname).dbPos(getServerDir(), name) + csql_list = import_sql.split(';') + for index in range(len(csql_list)): + conn.execute(csql_list[index], ()) + + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + +def initDreplace(): + + file_tpl = getInitDTpl() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + run_log_file = runLog() + if not os.path.exists(run_log_file): + mw.writeFile(run_log_file,'') + + hook_file = getConf() + if not os.path.exists(hook_file): + mw.writeFile(hook_file,'') + + return file_bin + + +def apaOp(method): + file = initDreplace() + return 'ok' + + # current_os = mw.getOs() + # if current_os == "darwin": + # data = mw.execShell(file + ' ' + method) + # if data[1] == '': + # return 'ok' + # return data[1] + + # if current_os.startswith("freebsd"): + # data = mw.execShell('service ' + getPluginName() + ' ' + method) + # if data[1] == '': + # return 'ok' + # return data[1] + + # data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + # if data[1] == '': + # return 'ok' + # return data[1] + + +def start(): + import tool_cron + tool_cron.createBgTask() + + return apaOp('start') + + +def stop(): + import tool_cron + tool_cron.removeBgTask() + return apaOp('stop') + + +def restart(): + status = apaOp('restart') + return status + + +def reload(): + return apaOp('reload') + + +def dnsapiAdd(): + conn = pSqliteDb('dnsapi') + args = getArgs() + data = checkArgs(args,['id','name', 'type', 'remark']) + if not data[0]: + return data[1] + + name = args['name'].strip() + remark = args['remark'].strip() + stype = args['type'].strip() + val = args['val'].strip() + sid = args['id'].strip() + + if name == '': + return mw.returnJson(False, '名称不能为空!') + + if sid != '0' : #修改操作 + conn.where("id=?", (sid,)).update({ + 'name':name, + 'type':stype, + 'val':val, + 'remark':remark, + }) + return mw.returnJson(True, '修改成功!') + + if conn.where("name=?", (name,)).count(): + return mw.returnJson(False, name+'已存在!') + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + err = conn.add('name,type,val,remark,addtime', (name, stype, val,remark, addTime)) + # print(err) + return mw.returnJson(True, '添加成功!') + +def dnsapiDel(): + args = getArgs() + data = checkArgs(args, ['id', 'name']) + if not data[0]: + return data[1] + + conn = pSqliteDb('dnsapi') + try: + sid = args['id'] + name = args['name'] + conn.where("id=?", (sid,)).delete() + return mw.returnJson(True, '删除成功!') + except Exception as ex: + return mw.returnJson(False, '删除失败!' + str(ex)) + +def dnsapiList(): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('dnsapi') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,name,type,val,remark,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + + count = conn.where(condition, ()).count() + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + +def dnsapiListAll(): + conn = pSqliteDb('dnsapi') + field = 'id,name,type,val,remark,addtime' + data = conn.field(field).limit('1000').select() + return mw.getJson(data) + +def emailList(): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('email') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + if not search == '': + condition = "addr like '%" + search + "%'" + field = 'id,addr,remark,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + + count = conn.where(condition, ()).count() + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + +def emailAdd(): + args = getArgs() + data = checkArgs(args,['addr', 'remark']) + if not data[0]: + return data[1] + + addr = args['addr'].strip() + remark = args['remark'].strip() + + if addr == '': + return mw.returnJson(False, '邮件地址不能为空!') + + conn = pSqliteDb('email') + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + conn.add('addr,remark,addtime', (addr, remark, addTime)) + return mw.returnJson(True, '添加成功!') + +def emailDel(): + args = getArgs() + data = checkArgs(args, ['id', 'name']) + if not data[0]: + return data[1] + + conn = pSqliteDb('email') + try: + sid = args['id'] + name = args['name'] + conn.where("id=?", (sid,)).delete() + return mw.returnJson(True, '删除成功!') + except Exception as ex: + return mw.returnJson(False, '删除失败!' + str(ex)) + +def domainAdd(): + args = getArgs() + data = checkArgs(args,['id','domain', 'dnsapi_id','email','remark']) + if not data[0]: + return data[1] + + sid = args['id'].strip() + domain = args['domain'].strip() + remark = args['remark'].strip() + email = args['email'].strip() + dnsapi_id = args['dnsapi_id'].strip() + + if domain == '': + return mw.returnJson(False, '域名不能为空!') + if email == '': + return mw.returnJson(False, '邮件不能为空!') + + conn = pSqliteDb('domain') + + if sid != '0' : #修改操作 + conn.where("id=?", (sid,)).update({ + 'domain':domain, + 'dnsapi_id':dnsapi_id, + 'email':email, + 'remark':remark, + }) + return mw.returnJson(True, '修改成功!') + + if conn.where("domain=?", (domain,)).count(): + return mw.returnJson(False, domain+'已存在!') + + + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + conn.add('domain,dnsapi_id,email,remark,addtime', (domain, dnsapi_id,email,remark, addTime)) + return mw.returnJson(True, '添加成功!') + +def domainDel(): + args = getArgs() + data = checkArgs(args, ['id', 'name']) + if not data[0]: + return data[1] + + conn = pSqliteDb('domain') + try: + sid = args['id'] + name = args['name'] + conn.where("id=?", (sid,)).delete() + return mw.returnJson(True, '删除成功!') + except Exception as ex: + return mw.returnJson(False, '删除失败!' + str(ex)) + +def domainStatusToggle(): + args = getArgs() + data = checkArgs(args, ['id']) + if not data[0]: + return data[1] + + conn = pSqliteDb('domain') + + field = 'id,status' + fdata = conn.field(field).where('id=?',(args['id'],)).find() + + fstatus = fdata['status'] + if fstatus == 0: + fstatus = 1 + else: + fstatus = 0 + # print(fdata['status'],fstatus) + conn.where("id=?", (args['id'],)).update({ + 'status':fstatus, + }) + return mw.returnJson(True, '切换成功!') + +def domainList(): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('domain') + conn_dnsapi = pSqliteDb('dnsapi') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + if not search == '': + condition = "domain like '%" + search + "%'" + field = 'id,domain,dnsapi_id,email,status,effective_date,expiration_date,remark,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + + for i in range(len(clist)): + tdata = conn_dnsapi.field('id,name').where('id=?',(clist[i]['dnsapi_id'],)).select() + if len(tdata) > 0: + # print(tdata[0]['name']) + clist[i]['dnsapi_id_alias'] = clist[i]['dnsapi_id']+'('+ tdata[0]['name'] +')' + else: + clist[i]['dnsapi_id_alias'] = clist[i]['dnsapi_id']+' (无)' + + count = conn.where(condition, ()).count() + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + +def getDnsapiData(dnsapi_id): + if dnsapi_id == '0': + return {} + conn_dnsapi = pSqliteDb('dnsapi') + tdata = conn_dnsapi.field('id,name,type,val').where('id=?',(dnsapi_id,)).select() + + if len(tdata)>0: + return tdata[0] + return {} + +def getDnsapiExportVar(val): + cmd_list = val.split('~') + def_var = '' + for x in range(len(cmd_list)): + v = cmd_list[x] + vlist = v.split('|') + def_var += 'export '+vlist[0]+'="'+vlist[1]+'"\n' + return def_var + +def getDnsapiKv(val): + cmd_list = val.split('~') + def_var = {} + for x in range(len(cmd_list)): + v = cmd_list[x] + vlist = v.split('|') + kk = vlist[0] + vv = vlist[1] + def_var[kk] = vv + return def_var + +def domainApplyPathJudge(domain): + acme_dir = "/root/.acme.sh" + if mw.isAppleSystem(): + user = getRunUser() + acme_dir = "/Users/"+user+"/.acme.sh" + + abs_domain_path = acme_dir+'/'+domain+'_ecc' + # print(abs_domain_path) + if os.path.exists(abs_domain_path): + return True, abs_domain_path + + abs_domain_path = acme_dir+'/'+domain + # print(abs_domain_path) + if os.path.exists(abs_domain_path): + return True, abs_domain_path + + return False,'' + + +def hookWriteLog(line): + hook_file = runLog() + mdate = time.strftime('%Y-%m-%d %X', time.localtime()) + log = "["+mdate+"]:"+line + print(log) + return mw.writeFile(hook_file,log+"\n",'a+') + +def runHookPy(domain,path): + # print(domain,path) + run_log = runLog() + hook_file = getConf() + cmd = 'cd '+mw.getPanelDir() + cmd += ' && python3 '+hook_file + ' ' + domain + ' ' + path + cmd += ' >> '+ run_log + print(cmd) + return mw.execShell(cmd) + +def autoUpdateSslData(domain, path): + ssl_cer_file = path + '/'+domain+'.cer' + ssl_info = mw.getCertName(ssl_cer_file) + + time_begin = int(time.mktime(time.strptime(ssl_info['notBefore'], "%Y-%m-%d"))) + time_end = int(time.mktime(time.strptime(ssl_info['notAfter'], "%Y-%m-%d"))) + # print(ssl_info) + # print(time_begin,time_end) + conn = pSqliteDb('domain') + conn.where('domain=?', (domain,)).update({ + 'effective_date':str(time_begin), + 'expiration_date':str(time_end), + }) + + return True + +def runHookDstDomain(row): + domain = row['domain'] + email = str(row['email']) + + if row['effective_date'] != '': + effective_date = int(row['effective_date']) + now_int = int(time.time()) + day = (now_int - effective_date)/86400 + if int(day) < 8: + common_log = '【'+domain+'】未过期' + hookWriteLog(common_log) + return + + hookWriteLog('开始申请【'+domain+'】SSL证书') + cmd = "#!/bin/bash\n" + cmd += "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin:/root/.acme.sh\n" + cmd += "export PATH\n" + + if mw.isAppleSystem(): + user = getRunUser() + cmd += "source /Users/"+user+"/.zshrc\n" + + cmd_data = getDnsapiData(row['dnsapi_id']) + # print(cmd_data) + export_val = getDnsapiExportVar(cmd_data['val']) + cmd += export_val + + # acme.sh --register-account -m my@example.com + cmd_register = 'acme.sh --register-account -m '+ email + '\n' + cmd += cmd_register + + + # acme.sh --issue -d "example.com" -d "*.example.com" --dns dns_cf + cmd_apply = 'acme.sh --issue --dns '+str(cmd_data['type'])+' -d '+domain+' -d "*.'+domain+'"' + if row['effective_date'] != '': + effective_date = int(row['effective_date']) + now_int = int(time.time()) + day = (now_int - effective_date)/86400 + if int(day) > 7: + cmd_apply = 'acme.sh --issue --dns '+str(cmd_data['type'])+' -d '+domain+' -d "*.'+domain+'" --force' + cmd += cmd_apply + + run_log = runLog() + cmd += ' >> '+ run_log + print(cmd) + os.system(cmd) + hookWriteLog('结束申请【'+domain+'】SSL证书') + isok, path = domainApplyPathJudge(domain) + print(isok,path) + if isok: + autoUpdateSslData(domain, path) + hookWriteLog('开始执行【'+domain+'】Hook脚本') + runHookPy(domain,path) + hookWriteLog('结束执行【'+domain+'】Hook脚本') + +def getHookIdCmd(): + args = getArgs() + data = checkArgs(args, ['id']) + if not data[0]: + return data[1] + + cmd = "cd "+mw.getPanelDir()+" " + cmd += '&& python3 plugins/acme_pandominassl_apply/index.py run_hook_id 1.0 {"id":"'+args['id']+'"}' + return mw.returnJson(True, 'ok',cmd) + +def runHookId(): + args = getArgs() + data = checkArgs(args, ['id']) + if not data[0]: + return data[1] + + conn = pSqliteDb('domain') + field = 'id,domain,dnsapi_id,email,effective_date,expiration_date,remark' + row = conn.field(field).where('id=?',(args['id'],)).find() + runHookDstDomain(row) + return 'run hook '+args['id']+' end' + +def runHookCmd(): + cmd = "cd "+mw.getPanelDir()+" " + cmd += '&& python3 plugins/acme_pandominassl_apply/index.py run_hook' + return mw.returnJson(True, 'ok',cmd) + +def runHook(): + conn = pSqliteDb('domain') + + field = 'id,domain,dnsapi_id,email,effective_date,expiration_date,remark' + clist = conn.field(field).where('status=?',(1,)).limit('1000').order('id desc').select() + + for idx in range(len(clist)): + row = clist[idx] + # print(row) + runHookDstDomain(row) + time.sleep(1) + return 'run hook end' + +def autoSyncDomain(domain, dnsapi_id, email): + conn = pSqliteDb('domain') + + field = 'id,domain,dnsapi_id,email,effective_date,expiration_date,remark' + fdata = conn.field(field).where('domain=?',(domain,)).select() + # print(fdata) + mdate = time.strftime('%Y-%m-%d %X', time.localtime()) + if len(fdata) == 0: + conn.add('domain,dnsapi_id,email,remark,addtime', (domain, dnsapi_id, email,"备注", mdate)) + print(domain+" 同步成功!") + return True + +def runSyncCfDataRow(row, page = 1): + # print(row, page) + + cf_key = row['kv']['CF_Key'] + cf_mail = row['kv']['CF_Email'] + header = { + 'X-Auth-Email': cf_mail, + 'X-Auth-Key': cf_key, + 'Content-Type': 'application/json' + } + url = "https://api.cloudflare.com/client/v4/zones?status=active&page="+str(page)+"&per_page=20&order=status&direction=desc&match=all" + try: + r = requests.get(url, timeout=30, headers=header) + r.raise_for_status() + r.encoding = r.apparent_encoding + # print(r.text) + jdata = json.loads(r.text) + result = jdata['result'] + result_info = jdata['result_info'] + + # print(len(result)) + for i in result: + autoSyncDomain(i['name'], row['id'], cf_mail) + + if result_info['total_pages'] > page: + page += 1 + runSyncCfDataRow(row, page) + # print(result) + except Exception as e: + print(e) + +# 同步CloudFlare数据 +def runSyncCfData(): + print("同步CloudFlare数据开始") + conn_dnsapi = pSqliteDb('dnsapi') + field = "id,name,type,val" + fdata = conn_dnsapi.field(field).where('type=?', ('dns_cf',)).limit('10').select() + + for x in range(len(fdata)): + row = fdata[x] + dnsapi_kv = getDnsapiKv(row['val']) + row['kv'] = dnsapi_kv + runSyncCfDataRow(row) + + print("同步CloudFlare数据结束") + return '' + +def runSyncCfCmd(): + cmd = "cd "+mw.getPanelDir()+" " + cmd += '&& python3 plugins/acme_pandominassl_apply/index.py run_sync_cf_data' + return mw.returnJson(True, 'ok',cmd) + +def runSyncDnspodDataRow(row, page = 1): + # print(row, page) + + DPI_Id = row['kv']['DPI_Id'] + DPI_Key = row['kv']['DPI_Key'] + data = { + 'login_token': DPI_Id+','+DPI_Key, + 'length':5, + 'format':'json', + } + url = "https://api.dnspod.com/Domain.List" + try: + r = requests.post(url, timeout=30, data=data) + r.raise_for_status() + r.encoding = r.apparent_encoding + + # print(r.text) + jdata = json.loads(r.text) + info = jdata['info'] + domains = jdata['domains'] + + # print(jdata) + for i in domains: + autoSyncDomain(i['name'], row['id'], i['owner']) + + page_total = int(info['domain_total']/data['length']) + if page_total > page: + page += 1 + runSyncDnspodDataRow(row, page) + except Exception as e: + print(e) + +# 同步DnsPod数据 +def runSyncDnsPodData(): + print("同步DnsPod数据开始") + conn_dnsapi = pSqliteDb('dnsapi') + field = "id,name,type,val" + fdata = conn_dnsapi.field(field).where('type=?', ('dns_dpi',)).limit('10').select() + for x in range(len(fdata)): + row = fdata[x] + dnsapi_kv = getDnsapiKv(row['val']) + row['kv'] = dnsapi_kv + runSyncDnspodDataRow(row) + + print(fdata) + print("同步DnsPod数据结束") + return '' + +def runSyncDnsPodCmd(): + cmd = "cd "+mw.getPanelDir()+" " + cmd += '&& python3 plugins/acme_pandominassl_apply/index.py run_sync_dnspod_data' + return mw.returnJson(True, 'ok',cmd) + +if __name__ == "__main__": + func = sys.argv[1] + # print(func) + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'run_info': + print(runInfo()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'dnsapi_list': + print(dnsapiList()) + elif func == 'dnsapi_list_all': + print(dnsapiListAll()) + elif func == 'dnsapi_add': + print(dnsapiAdd()) + elif func == 'dnsapi_del': + print(dnsapiDel()) + elif func == 'email_list': + print(emailList()) + elif func == 'email_add': + print(emailAdd()) + elif func == 'email_del': + print(emailDel()) + elif func == 'domain_list': + print(domainList()) + elif func == 'domain_add': + print(domainAdd()) + elif func == 'domain_del': + print(domainDel()) + elif func == 'domain_status_toggle': + print(domainStatusToggle()) + elif func == 'run_hook': + print(runHook()) + elif func == 'run_hook_cmd': + print(runHookCmd()) + elif func == 'get_run_hook_id_cmd': + print(getHookIdCmd()) + elif func == 'run_hook_id': + print(runHookId()) + elif func == 'run_sync_cf_data': + print(runSyncCfData()) + elif func == 'run_sync_cf_cmd': + print(runSyncCfCmd()) + elif func == 'run_sync_dnspod_data': + print(runSyncDnsPodData()) + elif func == 'run_sync_dnspod_cmd': + print(runSyncDnsPodCmd()) + else: + print('error') diff --git a/plugins/acme_pandominassl_apply/info.json b/plugins/acme_pandominassl_apply/info.json new file mode 100755 index 000000000..f10f7c425 --- /dev/null +++ b/plugins/acme_pandominassl_apply/info.json @@ -0,0 +1,17 @@ +{ + "sort": 7, + "ps": "ACME泛域名SSL申请/管理", + "name": "acme_pandominassl_apply", + "title": "ACME泛域名SSL", + "shell": "install.sh", + "versions":["1.0"], + "tip": "soft", + "checks": "server/acme_pandominassl_apply", + "path": "server/acme_pandominassl_apply", + "display": 1, + "author": "ACME", + "date": "2024-09-12", + "home": "https://github.com/acmesh-official/acme.sh", + "type": 0, + "pid": "5" +} diff --git a/plugins/acme_pandominassl_apply/install.sh b/plugins/acme_pandominassl_apply/install.sh new file mode 100755 index 000000000..42591f085 --- /dev/null +++ b/plugins/acme_pandominassl_apply/install.sh @@ -0,0 +1,54 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +# curl https://get.acme.sh | sh +# acme.sh --uninstall + + +# source /Users/xxx/.zshrc + +# https://github.com/acmesh-official/acme.sh/wiki/dnsapi +# https://docs.dnspod.com/api-legacy/domains.html#get-the-domain-list + +# cd /www/server/mdserver-web && python3 plugins/acme_pandominassl_apply/index.py run_hook +# cd /www/server/mdserver-web && python3 plugins/acme_pandominassl_apply/index.py run_sync_cf_data +# cd /www/server/mdserver-web && python3 plugins/acme_pandominassl_apply/index.py run_sync_dnspod_data + + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +# pip install cloudflare +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/acme_pandominassl_apply + echo "${VERSION}" > $serverPath/acme_pandominassl_apply/version.pl + + cd ${rootPath} && python3 ${rootPath}/plugins/acme_pandominassl_apply/index.py start + echo '安装[ACME泛域名SSL]完成' +} + +Uninstall_App() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/acme_pandominassl_apply/index.py stop + rm -rf $serverPath/acme_pandominassl_apply + echo "卸载[ACME泛域名SSL]成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/acme_pandominassl_apply/js/common.js b/plugins/acme_pandominassl_apply/js/common.js new file mode 100755 index 000000000..d0b0442d0 --- /dev/null +++ b/plugins/acme_pandominassl_apply/js/common.js @@ -0,0 +1,801 @@ +function apaPost(method, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'acme_pandominassl_apply'; + req_data['func'] = method; + req_data['version'] = $('.plugin_version').attr('version'); + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function apaPostN(method, args,callback){ + + var req_data = {}; + req_data['name'] = 'acme_pandominassl_apply'; + req_data['func'] = method; + req_data['version'] = $('.plugin_version').attr('version'); + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function apaPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'acme_pandominassl_apply'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function apaReadme(){ + var readme = ''; + $('.soft-man-con').html(readme); +} + + +function emailDel(id, name){ + safeMessage('删除['+name+']','您真的要删除['+name+']吗?',function(){ + var data='id='+id+'&name='+name; + apaPost('email_del', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + emailList(); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); +} + +function emailAdd(type){ + layer.open({ + type: 1, + area: '500px', + title: '添加邮件地址', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
\ +
\ + 邮件地址\ +
\ +
\ +
\ + 备注\ +
\ +
\ +
", + success:function(){ + $("input[name='addr']").keyup(function(){ + var v = $(this).val(); + $("input[name='remark']").val(v); + }); + }, + yes:function(index) { + var data = $("#email_add").serialize(); + data = decodeURIComponent(data); + // data = toArrayObject(data); + apaPost('email_add', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + emailList(); + } + },{icon: rdata.status ? 1 : 2}, 2000); + }); + } + }); +} + + +function emailList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + apaPost('email_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list +=''; + list += '' + rdata.data[i]['addr'] +''; + list += '' + rdata.data[i]['remark'] +''; + list += ''; + list += '删除' + + ''; + list += ''; + } + + var con = '
\ + \ + \ + \ + \ +
\ +
\ + \ + \ + \ + \ + \ + '+ list +'\ +
邮件地址备注操作
\ +
\ +
\ +
\ +
'; + + $(".soft-man-con").html(con); + $('.dataTables_paginate').html(rdata.page); + + readerTableChecked(); + }); +} + + +function dnsapiDel(id, name){ + safeMessage('删除['+name+']','您真的要删除['+name+']吗?',function(){ + var data='id='+id+'&name='+name; + apaPost('dnsapi_del', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dnsapiList(); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); +} + +var dnsapi_option = [ + {"name":"dns_cf", "title":'cloudflare', 'key':'CF_Key:CF_Email'}, + {"name":"dns_dp", "title":'dnspod/国内', 'key':'DP_Id:DP_Key'}, + {"name":"dns_dpi", "title":'dnspod/国际', 'key':'DPI_Id:DPI_Key'}, + {"name":"dns_gd", "title":'GoDaddy', 'key':'GD_Key:GD_Secret'}, + {"name":"dns_pdns", "title":'PowerDNS', 'key':'PDNS_Url:PDNS_ServerId:PDNS_Token:PDNS_Ttl'}, + {"name":"dns_lua", "title":'LuaDNS', 'key':'LUA_Key:LUA_Email'}, + {"name":"dns_me", "title":'DNSMadeEasy', 'key':'ME_Key:ME_Secret'}, + {"name":"dns_aws", "title":'Amazon Route53', 'key':'AWS_ACCESS_KEY_ID:AWS_SECRET_ACCESS_KEY'}, + {"name":"dns_ali", "title":'Aliyun', 'key':'Ali_Key:Ali_Secret'}, + {"name":"dns_ispconfig", "title":'ISPConfig', 'key':'ISPC_User:ISPC_Password:ISPC_Api:ISPC_Api_Insecure'}, + {"name":"dns_ad", "title":'Alwaysdata', 'key':'AD_API_KEY'}, + {"name":"dns_linode_v4", "title":'Linode', 'key':'LINODE_V4_API_KEY'}, + {"name":"dns_freedns", "title":'FreeDNS', 'key':'FREEDNS_User:FREEDNS_Password'}, + {"name":"dns_cyon", "title":'cyon.ch', 'key':'CY_Username:CY_Password:CY_OTP_Secret'}, + {"name":"dns_gandi_livedns", "title":'LiveDNS', 'key':'GANDI_LIVEDNS_TOKEN'}, + {"name":"dns_knot", "title":'Knot', 'key':'KNOT_SERVER:KNOT_KEY'}, + {"name":"dns_dgon", "title":'DigitalOcean', 'key':'DO_API_KEY'}, + {"name":"dns_cloudns", "title":'ClouDNS.net', 'key':'CLOUDNS_SUB_AUTH_ID:CLOUDNS_AUTH_PASSWORD'}, + {"name":"dns_namesilo", "title":'Namesilo', 'key':'Namesilo_Key'}, + {"name":"dns_azure", "title":'Azure', 'key':'AZUREDNS_SUBSCRIPTIONID:AZUREDNS_TENANTID:AZUREDNS_APPID:AZUREDNS_CLIENTSECRET'}, + {"name":"dns_selectel", "title":'selectel.com', 'key':'SL_Key'}, + {"name":"dns_zonomi", "title":'zonomi.com', 'key':'ZM_Key'}, + {"name":"dns_kinghost", "title":'KingHost', 'key':'KINGHOST_Username:KINGHOST_Password'}, + {"name":"dns_zilore", "title":'Zilore', 'key':'Zilore_Key'}, + {"name":"dns_gcloud", "title":'Google Cloud DNS', 'key':'CLOUDSDK_ACTIVE_CONFIG_NAME'}, + {"name":"dns_mydnsjp", "title":'MyDNS.JP', 'key':'MYDNSJP_MasterID:MYDNSJP_Password'}, + {"name":"dns_doapi", "title":'do.de', 'key':'DO_LETOKEN'}, + {"name":"dns_online", "title":'Online', 'key':'ONLINE_API_KEY'}, + {"name":"dns_cn", "title":'Core-Networks', 'key':'CN_User:CN_Password'}, + {"name":"dns_ultra", "title":'UltraDNS', 'key':'ULTRA_USR:ULTRA_PWD'}, + {"name":"dns_hetzner", "title":'Hetzner', 'key':'HETZNER_Token'}, + {"name":"dns_ddnss", "title":'DDNSS.de', 'key':'DDNSS_Token'}, +]; + +function getDnsapiKey(name){ + for (var i = 0; i < dnsapi_option.length; i++) { + if (dnsapi_option[i]['name'] == name){ + return dnsapi_option[i]['key']; + } + } + return ''; +} + +function getDnsapiTitle(name){ + for (var i = 0; i < dnsapi_option.length; i++) { + if (dnsapi_option[i]['name'] == name){ + return dnsapi_option[i]['title']; + } + } + return '其他'; +} + + + +function dnsapiAdd(row){ + // console.log(row); + var option_name = ''; + var option_remark = ''; + var option_type = 'dns_cf'; + var option_val = ''; + var option_id = 0; + if (typeof(row) != 'undefined'){ + option_name = row['name']; + option_remark = row['remark']; + option_type = row['type']; + option_val = row['val']; + option_id = row['id']; + + } + + function renderDnsapiOption(name, val){ + var vlist = {}; + if (val != ''){ + var t = val.split('~'); + for (var i = 0; i < t.length; i++) { + var kv = t[i].split('|'); + if (kv.length == 2){ + vlist[kv[0]] = kv[1]; + } else { + vlist[kv[0]] = ''; + } + } + // console.log(vlist); + } + + + + var key = getDnsapiKey(name); + var klist = key.split(':'); + var option_html = ''; + for (var i = 0; i < klist.length; i++) { + var klist_val = ''; + if (klist[i] in vlist){ + klist_val = vlist[klist[i]]; + } + option_html += ""+klist[i]+"\ +
\ + \ +
"; + } + $('#dnsapi_option').html(option_html); + } + + layer.open({ + type: 1, + area: '500px', + title: '添加DNSAPI', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
\ +
\ + 名称\ +
\ +
\ +
\ + DNSAPI类型\ +
\ + \ +
\ +
\ +
\ + CF_Key\ +
\ + \ +
\ + CF_Email\ +
\ + \ +
\ +
\ +
\ + 备注\ +
\ +
\ + \ +
", + success:function(){ + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='remark']").val(v); + }); + + var option = ''; + for (var i = 0; i"+dnsapi_option[i]['title']+""; + } else { + option += ""; + } + } + + renderDnsapiOption(option_type, option_val); + $('select[name="type"]').html(option); + $('select[name="type"]').change(function(){ + var name = $(this).val(); + if (option_type == name){ + renderDnsapiOption(name, option_val); + } else { + renderDnsapiOption(name,''); + } + + }); + }, + yes:function(index) { + var data = $("#dnsapi_add").serialize(); + data = decodeURIComponent(data); + data = toArrayObject(data); + + var key = getDnsapiKey(data['type']); + var klist = key.split(':'); + var val = ''; + for (var i = 0; i < klist.length; i++) { + var k = klist[i]; + if (k in data){ + if (klist.length - 1 == i){ + val += k + '|' + data[k]; + } else { + val += k + '|' + data[k]+'~'; + } + } + } + data['val'] = val; + apaPost('dnsapi_add', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + dnsapiList(); + } + },{icon: rdata.status ? 1 : 2}, 2000); + }); + } + }); +} + + +function dnsapiList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + apaPost('dnsapi_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list +=''; + list += '' + rdata.data[i]['name'] +''; + list += '' + getDnsapiTitle(rdata.data[i]['type']) +''; + list += '' + rdata.data[i]['val'].split('~').join("
") +''; + list += '' + rdata.data[i]['remark'] +''; + + list += ''; + list += '编辑 | '; + list += '删除'; + list += ''; + } + + var con = '
\ + \ + \ + \ + \ +
\ +
\ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
名称类型备注操作
\ +
\ +
\ +
\ +
'; + + $(".soft-man-con").html(con); + $('.dataTables_paginate').html(rdata.page); + + $('.edit').click(function(){ + var idx = $(this).attr('index'); + var row = rdata.data[idx]; + // console.log(row); + dnsapiAdd(row); + }) + + readerTableChecked(); + }); +} + + +function domainDel(id, name){ + safeMessage('删除['+name+']','您真的要删除['+name+']吗?',function(){ + var data='id='+id+'&name='+name; + apaPost('domain_del', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + domainList(); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); +} + +function domainStatusToggle(id){ + var data='id='+id; + apaPost('domain_status_toggle', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + domainList(); + },{icon: rdata.status ? 1 : 2}, 600); + }); +} + +function domainIdCmd(id){ + var data='id='+id; + apaPost('get_run_hook_id_cmd', data, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + layer.open({ + title: "手动同步命令", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
\ +
\ +
'+rdata.data+'
\ +
\ +
', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + +function domainHookCmd(){ + apaPost('run_hook_cmd', {}, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + title: "手动同步全部命令", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
\ +
\ +
'+rdata.data+'
\ +
\ +
', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + +function syncCfCmd(){ + apaPost('run_sync_cf_cmd', {}, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + title: "手动同步CloudFlare全部域名命令", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
\ +
\ +
'+rdata.data+'
\ +
\ +
', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + +function syncDnsPodCmd(){ + apaPost('run_sync_dnspod_cmd', {}, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + title: "手动同步DnsPod全部域名命令", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
\ +
\ +
'+rdata.data+'
\ +
\ +
', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + +function domainAdd(row){ + + var option_domian = ''; + var option_remark = '备注'; + var option_email = ''; + var option_id = 0; + var option_dnsapi_id = 0; + if (typeof(row) != 'undefined'){ + option_domian = row['domain']; + option_remark = row['remark']; + option_email = row['email']; + option_id = row['id']; + option_dnsapi_id = row['dnsapi_id']; + } + + layer.open({ + type: 1, + area: '500px', + title: '添加顶级域名', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
\ +
\ + 域名\ +
\ +
\ +
\ + DNSAPI\ +
\ + \ +
\ +
\ +
\ + 邮件\ +
\ +
\ +
\ + 备注\ +
\ +
\ + \ +
", + success:function(){ + // $("input[name='domain']").keyup(function(){ + // var v = $(this).val(); + // $("input[name='remark']").val(v); + // }); + + var dnsapi_id_html = ""; + apaPostN('dnsapi_list_all', {}, function(data){ + var rdata = $.parseJSON(data.data); + for (var i = 0; i < rdata.length; i++) { + if (option_dnsapi_id == rdata[i]['id']){ + dnsapi_id_html += ""; + } else { + dnsapi_id_html += ""; + } + } + + $('select[name="dnsapi_id"]').change(function(){ + var val = $('select[name="dnsapi_id"]').find("option:selected").attr('val'); + var val_arr = val.split("|"); + for (var i = 0; i < val_arr.length; i++) { + var param = val_arr[i]; + var strictEmailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + if(strictEmailRegex.test(param)){ + $('input[name="email"]').val(param); + } + } + + }); + $('select[name="dnsapi_id"]').html(dnsapi_id_html); + }); + }, + yes:function(index) { + var data = $("#domain_add").serialize(); + data = decodeURIComponent(data); + apaPost('domain_add', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + domainList(); + } + },{icon: rdata.status ? 1 : 2}, 2000); + }); + } + }); +} + +function domainList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + apaPost('domain_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var list = ''; + for(i in rdata.data){ + list += ''; + list +=''; + list += '' + rdata.data[i]['domain'] +''; + list += '' + rdata.data[i]['dnsapi_id_alias'] +''; + list += '' + rdata.data[i]['email'] +''; + list += '' + rdata.data[i]['remark'] +''; + + + + if (rdata.data[i]['effective_date'] == ''){ + list += '空/未申请'; + } else { + list += ''+getFormatTime(rdata.data[i]['effective_date'],'yyyy/MM/dd')+''; + } + + if (rdata.data[i]['expiration_date'] == ''){ + list += '空/未申请'; + } else { + list += ''+getFormatTime(rdata.data[i]['expiration_date'],'yyyy/MM/dd')+''; + } + + if (rdata.data[i]['status'] == '0'){ + list += ''; + } else { + list += ''; + } + + list += ''; + list += '命令 | '; + list += '编辑 | '; + list += '删除'; + list += ''; + } + + var con = '
\ + \ + \ + \ + \ +
\ +
\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
域名DNSAPI邮件备注开始结束同步操作
\ +
\ +
\ +
\ +
'; + + $(".soft-man-con").html(con); + $('.dataTables_paginate').html(rdata.page); + + $('.edit').click(function(){ + var idx = $(this).attr('index'); + var row = rdata.data[idx]; + domainAdd(row); + }); + + $('.status').click(function(){ + var idx = $(this).attr('index'); + var row = rdata.data[idx]; + domainStatusToggle(row['id']); + }); + + $('.cmd').click(function(){ + var idx = $(this).attr('index'); + var row = rdata.data[idx]; + domainIdCmd(row['id']); + }); + + readerTableChecked(); + }); +} diff --git a/plugins/acme_pandominassl_apply/tool_cron.py b/plugins/acme_pandominassl_apply/tool_cron.py new file mode 100644 index 000000000..3bad834b6 --- /dev/null +++ b/plugins/acme_pandominassl_apply/tool_cron.py @@ -0,0 +1,142 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +# print(sys.platform) +if sys.platform != "darwin": + os.chdir("/www/server/mdserver-web") + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'acme_pandominassl_apply' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getTaskConf(): + conf = getServerDir() + "/cron_config.json" + return conf + +def getTaskDeltaConf(): + conf = getServerDir() + "/cron_delta_config.json" + return conf + + +def getConfigData(): + try: + return json.loads(mw.readFile(getTaskConf())) + except: + pass + return { + "task_id": -1, + "period": "day-n", + "where1": "1", + "hour": "0", + "minute": "15", + } + + +def createBgTask(): + removeBgTask() + createBgTaskByName(getPluginName()) + return True + + +def createBgTaskByName(name): + args = getConfigData() + _name = "[勿删]ACME泛域名SSL[APA]" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in args and args["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (args["task_id"],)).find() + if res and res["id"] == args["task_id"]: + print("计划任务已经存在!") + return True + + mw_dir = mw.getPanelDir() + cmd = ''' +mw_dir=%s +rname=%s +plugin_path=%s +script_path=%s +logs_file=$plugin_path/${rname}.log +''' % (mw_dir, name, getServerDir(), getPluginDir()) + cmd += 'echo "★【`date +"%Y-%m-%d %H:%M:%S"`】 STSRT★" >> $logs_file' + "\n" + cmd += 'echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" >> $logs_file' + "\n" + cmd += 'echo "python3 $script_path/index.py run_hook"' + "\n" + cmd += 'cd $mw_dir && python3 $script_path/index.py run_hook' + "\n" + cmd += 'echo "【`date +"%Y-%m-%d %H:%M:%S"`】 END★" >> $logs_file' + "\n" + cmd += 'echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" >> $logs_file' + "\n" + + params = { + 'name': _name, + 'type': args['period'], + 'week': "", + 'where1': args['where1'], + 'hour': args['hour'], + 'minute': args['minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + args["task_id"] = task_id + args["name"] = name + mw.writeFile(getTaskConf(), json.dumps(args)) + + +def removeBgTask(): + cfg = getConfigData() + if "task_id" in cfg and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + import crontab_api + api = crontab_api.crontab_api() + data = api.delete(cfg["task_id"]) + if data[0]: + cfg["task_id"] = -1 + mw.writeFile(getTaskConf(), json.dumps(cfg)) + return True + return False + + +if __name__ == "__main__": + if len(sys.argv) > 1: + action = sys.argv[1] + if action == "remove": + removeBgTask() + elif action == "add": + createBgTask() diff --git a/plugins/alist/ico.png b/plugins/alist/ico.png new file mode 100644 index 000000000..b1d76cb4f Binary files /dev/null and b/plugins/alist/ico.png differ diff --git a/plugins/alist/index.html b/plugins/alist/index.html new file mode 100755 index 000000000..fc95b204e --- /dev/null +++ b/plugins/alist/index.html @@ -0,0 +1,31 @@ + + +
+
+
+
+

服务

+

自启动

+

常用功能

+

配置修改

+

运行日志

+

相关说明

+ +
+
+
+
+
+
+ \ No newline at end of file diff --git a/plugins/alist/index.py b/plugins/alist/index.py new file mode 100755 index 000000000..c22f551f4 --- /dev/null +++ b/plugins/alist/index.py @@ -0,0 +1,319 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'alist' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + path = getServerDir() + "/data/config.json" + return path + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + +def __release_port(port, ps = '开启端口'): + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, ps, 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + +def __delete_port(port): + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().delAcceptPortCmd(port, 'tcp') + return port + except Exception as e: + return "Delete failed {}".format(e) + + +def openPort(): + content = mw.readFile(getConf()) + data = json.loads(content) + http_port = data['scheme']['http_port'] + __release_port(http_port, 'alist') + return True + +def status(): + cmd = "ps aux|grep alist |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/'+getPluginName()) + return content + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + time.sleep(1) + return file_bin + + +def alistOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return alistOp('start') + + +def stop(): + return alistOp('stop') + + +def restart(): + status = alistOp('restart') + return status + + +def reload(): + return alistOp('reload') + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + return getServerDir() + '/data/log/log.log' + +def pSqliteDb(dbname='databases'): + pos_file = getServerDir() + '/data/' + file = pos_file + '/data.db' + name = 'data' + conn = mw.M(dbname).dbPos(pos_file, name) + return conn + +def clearCopyTask(): + conn = pSqliteDb('x_task_items') + conn.where('key=?', ('copy',)).setField('persist_data','[]') + restart() + return mw.returnJson(True, '清空成功并重启服务!') + +def homePage(): + content = mw.readFile(getConf()) + data = json.loads(content) + http_port = data['scheme']['http_port'] + ip = mw.getLocalIp() + if mw.isAppleSystem(): + ip = '127.0.0.1' + url = 'http://'+ip+":"+str(http_port) + # print(url) + return mw.returnJson(True, 'ok!', url) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'clear_copy_task': + print(clearCopyTask()) + elif func == 'home_page': + print(homePage()) + else: + print('error') diff --git a/plugins/alist/info.json b/plugins/alist/info.json new file mode 100755 index 000000000..542b534be --- /dev/null +++ b/plugins/alist/info.json @@ -0,0 +1,17 @@ +{ + "sort": 7, + "ps": "一个支持多种存储的文件列表程序", + "name": "alist", + "title": "Alist", + "shell": "install.sh", + "versions":["3.37.4","3.38.0","3.45.0"], + "tip": "soft", + "checks": "server/alist", + "path": "server/alist", + "display": 1, + "author": "alist", + "date": "2022-10-09", + "home": "https://alist.nn.ci", + "type": 0, + "pid": "5" +} diff --git a/plugins/alist/init.d/alist.service.tpl b/plugins/alist/init.d/alist.service.tpl new file mode 100644 index 000000000..e1b8efb29 --- /dev/null +++ b/plugins/alist/init.d/alist.service.tpl @@ -0,0 +1,13 @@ +[Unit] +Description=A file list program that supports multiple storage +After=network.target + +[Service] +Type=simple +WorkingDirectory={$SERVER_PATH}/alist +ExecStart={$SERVER_PATH}/alist/alist server +ExecReload=/bin/kill -USR2 $MAINPID +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/alist/init.d/alist.tpl b/plugins/alist/init.d/alist.tpl new file mode 100644 index 000000000..bcd15d6d7 --- /dev/null +++ b/plugins/alist/init.d/alist.tpl @@ -0,0 +1,46 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: alist Service + +### BEGIN INIT INFO +# Provides: alist +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts alist +# Description: starts the MDW-Web +### END INIT INFO + +# Simple alist init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +app_start(){ + echo "Starting alist server..." + cd {$SERVER_PATH}/alist + ./alist server >> {$SERVER_PATH}/alist/logs.pl 2>&1 & +} + +app_stop(){ + echo "dztasks stopped" + ps -ef| grep alist | grep -v grep | grep -v python | grep -v sh | awk '{print $2}'| xargs kill -9 +} + + +case "$1" in + start) + app_start + ;; + stop) + app_stop + ;; + restart|reload) + app_stop + sleep 0.3 + app_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/alist/install.sh b/plugins/alist/install.sh new file mode 100755 index 000000000..64aabc565 --- /dev/null +++ b/plugins/alist/install.sh @@ -0,0 +1,93 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/alist && bash install.sh install 3.37.4 +# cd /www/server/mdserver-web/plugins/alist && bash install.sh install 3.45.0 + +VERSION=$2 + +sysArch=`arch` +sysName=`uname` + +ALIST_ARCH_NAME=amd64 +if [ "$sysArch" == "arm64" ];then + ALIST_ARCH_NAME=arm64 +elif [ "$sysArch" == "x86_64" ]; then + ALIST_ARCH_NAME=amd64 +elif [ "$sysArch" == "aarch64" ]; then + ALIST_ARCH_NAME=arm64 +fi + +ALIST_NAME=linux +if [ "$sysName" == "Darwin" ];then + ALIST_NAME=darwin +fi + +Install_App() +{ + echo '正在安装脚本文件...' + + mkdir -p $serverPath/source + mkdir -p $serverPath/source/alist + + FILE_TGZ=alist-${ALIST_NAME}-${ALIST_ARCH_NAME}.tar.gz + + ALIST_DIR=$serverPath/source/alist + + if [ ! -f $ALIST_DIR/${FILE_TGZ} ];then + wget --no-check-certificate -O $ALIST_DIR/${FILE_TGZ} https://github.com/alist-org/alist/releases/download/v${VERSION}/${FILE_TGZ} + fi + + mkdir -p $serverPath/alist + + cd $ALIST_DIR && tar -zxvf ${FILE_TGZ} -C $serverPath/alist + echo "${VERSION}" > $serverPath/alist/version.pl + + cd ${rootPath} && python3 ${rootPath}/plugins/alist/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/alist/index.py initd_install + + cd $serverPath/alist && ./alist admin set admin + # cd /www/server/alist && ./alist admin set admin + + echo '安装完成' +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/alist.service ];then + systemctl stop alist + systemctl disable alist + rm -rf /usr/lib/systemd/system/alist.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/alist.service ];then + systemctl stop alist + systemctl disable alist + rm -rf /lib/systemd/system/alist.service + systemctl daemon-reload + fi + + if [ -f $serverPath/alist/initd/alist ];then + $serverPath/alist/initd/alist stop + fi + + if [ -d $serverPath/alist ];then + rm -rf $serverPath/alist + fi + + echo "卸载alist成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/alist/js/alist.js b/plugins/alist/js/alist.js new file mode 100755 index 000000000..e66b4438a --- /dev/null +++ b/plugins/alist/js/alist.js @@ -0,0 +1,93 @@ +function alistPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'alist'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function alistPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'alist'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function clearTaskCopy(){ + layer.confirm('您真的要清空复制任务吗?', { icon: 3, closeBtn: 2 }, function() { + alistPost('clear_copy_task', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function(){},{ icon: rdata.status ? 1 : 2 }); + }); + }); +} + +function commonHomePage(){ + alistPost('home_page', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + window.open(rdata.data); + }); +} + +function alistCommonFunc(){ + var con = ''; + con += '

\ + \ + \ +

'; + + $(".soft-man-con").html(con); +} + +function alistReadme(){ + + + var readme = '
    '; + readme += '
  • 手动开启默认端口:5244
  • '; + readme += '
  • 默认admin:admin
  • '; + readme += '
  • 手动改密码: cd /www/server/alist && ./alist admin set NEW_PASSWORD
  • '; + readme += '
'; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/backup_ftp/class/ftp_client.py b/plugins/backup_ftp/class/ftp_client.py new file mode 100644 index 000000000..53a0e7ea8 --- /dev/null +++ b/plugins/backup_ftp/class/ftp_client.py @@ -0,0 +1,424 @@ +# coding:utf-8 + +''' +doc: https://docs.python.org/zh-cn/3/library/ftplib.html +''' + +import sys +import io +import os +import time +import re +import json + +import paramiko +import ftplib + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +DEBUG = True +BLOCK_SIZE = 1024 * 1024 * 2 +# BLOCK_SIZE = 50 +PROGRESS_FILE_NAME = "PROGRESS_FILE_NAME" + +""" +=============自定义异常=================== +""" + + +class OsError(Exception): + """OS端异常""" + + +class ObjectNotFound(OsError): + """对象不存在时抛出的异常""" + + def __init__(self, *args, **kwargs): + message = "文件对象不存在。" + super(ObjectNotFound, self).__init__(message, *args, **kwargs) + + +class APIError(Exception): + """API参数错误异常""" + + def __init__(self, *args, **kwargs): + _api_error_msg = 'API资料校验失败,请核实!' + super(APIError, self).__init__(_api_error_msg, *args, **kwargs) + + +class FtpPSClient: + _title = "FTP" + _name = "ftp" + __host = None + __port = None + __user = None + __password = None + default_port = 21 + default_backup_path = "/backup/" + config_file = "cfg.json" + + def __init__(self, load_config=True, timeout=600): + self.timeout = timeout + if load_config: + data = self.get_config() + self.injection_config(data) + + def get_config(self): + default_config = { + "ftp_host": '', + "ftp_user": '', + "ftp_pass": '', + "backup_path": self.default_backup_path + } + + cfg = mw.getServerDir() + "/backup_ftp/" + self.config_file + if os.path.exists(cfg): + data = mw.readFile(cfg) + return json.loads(data) + else: + return default_config + + def injection_config(self, data): + self.__host = host = data["ftp_host"].strip() + if host.find(':') == -1: + self.__port = self.default_port + else: + self.__host = host.split(':')[0] + self.__port = host.split(':')[1] + + self.__user = data['ftp_user'].strip() + self.__password = data['ftp_pass'].strip() + bp = data['backup_path'].strip() + if bp: + self.backup_path = self.getPath(bp) + else: + self.backup_path = self.getPath(self.default_backup_path) + + def authorize(self): + try: + if self.timeout is not None: + ftp = ftplib.FTP(timeout=self.timeout) + else: + ftp = ftplib.FTP() + + debuglevel = 0 + # if DEBUG: + # debuglevel = 3 + ftp.set_debuglevel(debuglevel) + # ftp.set_pasv(True) + ftp.connect(self.__host, int(self.__port)) + ftp.login(self.__user, self.__password) + return ftp + except Exception as e: + raise OsError("无法连接FTP客户端,请检查配置参数是否正确!") + + # 取目录路径 + def getPath(self, path): + if path[-1:] != '/': + path += '/' + if path[:1] != '/': + path = '/' + path + return path.replace('//', '/') + + def generateDownloadUrl(self, object_name): + + return 'ftp://' + \ + self.__user + ':' + \ + self.__password + '@' + \ + self.__host + ':' + \ + "/" + object_name + + def buildDirName(self, data_type, file_name): + import re + prefix_dict = { + "site": "web", + "database": "db", + "path": "path", + } + file_regx = prefix_dict.get(data_type) + r"_(.+)_20\d+_\d+\." + sub_search = re.search(file_regx, file_name) + sub_path_name = "" + if sub_search: + sub_path_name = sub_search.groups()[0] + sub_path_name += '/' + + # 构建OS存储路径 + object_name = self.backup_path + \ + data_type + '/' + \ + sub_path_name + \ + file_name + return object_name + + def uploadFile(self, filename, data_type=None, *args, **kwargs): + + client = self.authorize() + + local_file_name = filename + filename = os.path.abspath(filename) + dirname = os.path.dirname(filename) + temp_name = os.path.split(filename)[1] + + object_name = self.buildDirName(data_type, temp_name) + + upload_tmp_dir = os.path.join(dirname, ".upload_tmp") + if not os.path.exists(upload_tmp_dir): + os.mkdir(upload_tmp_dir) + + print("|-正在上传文件到 {}".format(object_name)) + + total_bytes = os.path.getsize(filename) + object_md5_name = mw.md5(object_name) + pg_file = os.path.join(upload_tmp_dir, object_md5_name + ".pl") + + block_size = BLOCK_SIZE + if kwargs.get("block_size"): + try: + block_size = float(kwargs.get("block_size")) + except: + pass + + remote_file_size = None + if not os.path.exists(pg_file): + # import uuid + # uid = str(uuid.uuid1()) + progress_info = { + "filename": local_file_name, + "total_bytes": total_bytes, + "uploaded_bytes": 0, + } + mw.writeFile(pg_file, json.dumps(progress_info)) + else: + progress_info = json.loads(mw.readFile(pg_file)) + if total_bytes == progress_info.get("total_bytes"): + # 取远程文件大小 + _max_loop = 10 + while _max_loop > 0: + try: + time.sleep(1) + remote_file_size = client.size(object_name) + if remote_file_size > total_bytes: + remote_file_size = None + break + except Exception as e: + if DEBUG: + print(type(e)) + print(e) + _max_loop -= 1 + else: + remote_file_size = None + + uploaded_bytes = 0 if remote_file_size is None else remote_file_size + + dir_name = os.path.split(object_name)[0] + if dir_name: + self.createDirP(dir_name) + + upload_start = time.time() + + try: + if total_bytes > 1024 * 1024 * 1024: + with open(local_file_name, 'rb') as file_handler: + if remote_file_size is not None: + file_handler.seek(remote_file_size) + + client.voidcmd("TYPE I") + datasock = '' + esize = '' + + datasock, esize = client.ntransfercmd( + "STOR " + object_name, remote_file_size) + + while True: + buf = file_handler.read(block_size) + if not len(buf): + break + datasock.sendall(buf) + uploaded_bytes += len(buf) + if DEBUG: + print('\ruploading %.2f%%' % + (float(uploaded_bytes) / total_bytes * 100)) + + print("uploaded_bytes", uploaded_bytes) + if uploaded_bytes == total_bytes: + break + datasock.close() + + if DEBUG: + print('close data handle') + try: + client.voidcmd('NOOP') + except Exception as e: + if DEBUG: + print("Send NOOP command error:") + print(e) + else: + if DEBUG: + print('keep alive cmd success') + client.voidresp() + if DEBUG: + print('No loop cmd') + else: + # 小于1G文件直接上传 + file_handler = open(local_file_name, "rb") + client.storbinary('STOR %s' % object_name, + file_handler, blocksize=block_size) + file_handler.close() + except Exception as e: + print(str(e)) + + completed_file_size = None + _max_loop = 10 + while _max_loop > 0: + try: + time.sleep(1) + completed_file_size = client.size(object_name) + break + except Exception as e: + _max_loop -= 1 + if DEBUG: + print("size error:" + str(e)) + + # 上传完成 + if completed_file_size == total_bytes: + if DEBUG: + upload_completed = time.time() + upload_diff = upload_completed - upload_start + print("文件上传成功, 耗时: {}s。".format(upload_diff)) + if os.path.exists(pg_file): + os.remove(pg_file) + return True + else: + if os.path.exists(pg_file): + os.remove(pg_file) + print("文件上传后大小不一致!") + + print("completed_file_size:" + str(completed_file_size)) + print("total_bytes:", total_bytes) + print("object_md5_name:", object_md5_name) + print("pg_file:", pg_file) + print("filename:", filename) + print("dirname:", dirname) + print("object_name:", object_name) + return False + + def createDirP(self, dir_name): + """创建远程目录 + + :param dir_name: 目录名称 + :return: + """ + try: + dirnames = dir_name.split('/') + ftp = self.authorize() + # ftp.cwd(get.path); + for dirname in dirnames: + if not dirname or not dirname.strip(): + continue + try: + flist = ftp.nlst() + if not dirname in flist: + ftp.mkd(dirname) + except: + # print("mlsd mode.") + try: + flist = list(ftp.mlsd())[1:] + for f in flist: + if dirname == f[0]: + break + else: + ftp.mkd(dirname) + except: + return False + ftp.cwd(dirname) + return True + except: + return False + + def createDir(self, path, name): + ftp = self.authorize() + path = self.getPath(path) + ftp.cwd(path) + try: + ftp.mkd(name) + ftp.close() + return True + except Exception as e: + print(str(e)) + ftp.close() + return False + + def deleteDir(self, path, dir_name): + try: + ftp = self.authorize() + ftp.rmd(dir_name) + return True + except ftplib.error_perm as e: + print("deleteDir:" + str(e) + ":" + dir_name) + except Exception as e: + print(e) + return False + + def deleteFile(self, filename): + try: + ftp = self.authorize() + ftp.delete(filename) + return True + except Exception as e: + print(str(e)) + return False + + def getList(self, path="/"): + ftp = self.authorize() + path = self.getPath(path) + ftp.cwd(path) + mlsd = False + try: + files = list(ftp.mlsd()) + mlsd = True + except: + try: + files = ftp.nlst(path) + mlsd = False + except: + raise RuntimeError("FTP服务器数据返回异常!") + ftp.close() + # print(files) + f_list = [] + dirs = [] + data = [] + default_time = '1971/01/01 01:01:01' + for dt in files: + # print(dt) + if mlsd: + dt_name = dt[0] + dt_info = dt[1] + else: + if dt.find("/") >= 0: + dt = dt.split("/")[-1] + tmp = {} + tmp['name'] = dt_name + if dt_name == '.' or dt_name == '..': + continue + + tmp['time'] = dt_info['modify'] + try: + tmp['size'] = dt_info['size'] + tmp['type'] = "File" + tmp['download'] = self.generateDownloadUrl(path + dt_name) + f_list.append(tmp) + except: + tmp['size'] = dt_info['sizd'] + tmp['type'] = None + tmp['download'] = '' + dirs.append(tmp) + data = dirs + f_list + + mlist = {} + mlist['path'] = path + mlist['list'] = data + return mlist diff --git a/plugins/backup_ftp/ico.png b/plugins/backup_ftp/ico.png new file mode 100644 index 000000000..2f7bc6873 Binary files /dev/null and b/plugins/backup_ftp/ico.png differ diff --git a/plugins/backup_ftp/index.html b/plugins/backup_ftp/index.html new file mode 100755 index 000000000..9ac6ae489 --- /dev/null +++ b/plugins/backup_ftp/index.html @@ -0,0 +1,101 @@ + +
+
+ + +
+
    +
    + + + +
    + +
    +
    + + + +
    名称大小更新时间操作
    +
    +
    +
    + + \ No newline at end of file diff --git a/plugins/backup_ftp/index.py b/plugins/backup_ftp/index.py new file mode 100755 index 000000000..d243dde41 --- /dev/null +++ b/plugins/backup_ftp/index.py @@ -0,0 +1,311 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +# print(sys.platform) +if sys.platform != "darwin": + os.chdir("/www/server/mdserver-web") + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import core.db as db + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'backup_ftp' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +sys.path.append(getPluginDir() + "/class") +from ftp_client import FtpPSClient + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + return 'start' + + +def getConf(): + cfg = getServerDir() + "/cfg.json" + if not os.path.exists(cfg): + rdata = { + 'use_sftp':False, + } + return mw.returnJson(False, "未配置", rdata) + data = mw.readFile(cfg) + data = json.loads(data) + return mw.returnJson(True, "OK", data) + + +def setConf(): + args = getArgs() + data = checkArgs(args, ['use_sftp', 'ftp_user', + 'ftp_pass', 'ftp_host', 'backup_path']) + if not data[0]: + return data[1] + + cfg = getServerDir() + "/cfg.json" + + values = ['ftp_user', + 'ftp_pass', + 'ftp_host'] + for v in values: + if args[v] == '': + return mw.returnJson(False, '必填资料不能为空,请核实!', []) + + if args['backup_path'] == '': + args['backup_path'] = "/backup" + + try: + ftp = FtpPSClient(load_config=False) + ftp.injection_config(args) + data = ftp.getList("/") + if data: + mw.writeFile(cfg, mw.getJson(args)) + return mw.returnJson(True, '设置成功', []) + except Exception as e: + return mw.returnJson(False, "FTP校验失败,请核实!\n" + str(e), []) + + +def getList(): + cfg = getServerDir() + "/cfg.json" + if not os.path.exists(cfg): + return mw.returnJson(False, "未配置FTP,请点击`账户设置`", []) + + args = getArgs() + data = checkArgs(args, ['path']) + if not data[0]: + return data[1] + + try: + ftp = FtpPSClient() + flist = ftp.getList(args['path']) + return mw.returnJson(True, "ok", flist) + except Exception as e: + return mw.returnJson(False, str(e), []) + + +def createDir(): + cfg = getServerDir() + "/cfg.json" + if not os.path.exists(cfg): + return mw.returnJson(False, "未配置FTP,请点击`账户设置`", []) + + args = getArgs() + data = checkArgs(args, ['path', 'name']) + if not data[0]: + return data[1] + + ftp = FtpPSClient() + isok = ftp.createDir(args['path'], args['name']) + if isok: + return mw.returnJson(True, "创建成功") + return mw.returnJson(False, "创建失败") + + +def deleteDir(): + args = getArgs() + data = checkArgs(args, ['dir_name', 'path']) + if not data[0]: + return data[1] + + ftp = FtpPSClient() + isok = ftp.deleteDir(args['path'], args['dir_name']) + if isok: + return mw.returnJson(True, "删除成功") + return mw.returnJson(False, "删除失败") + + +def deleteFile(): + args = getArgs() + data = checkArgs(args, ['path', 'filename']) + if not data[0]: + return data[1] + + ftp = FtpPSClient() + isok = ftp.deleteFile(args['path'] + "/" + args['filename']) + if isok: + return mw.returnJson(True, "删除成功") + return mw.returnJson(False, "删除失败") + +def findPathName(path, filename): + f = os.scandir(path) + l = [] + for ff in f: + t = {} + if ff.name.find(filename) > -1: + t['filename'] = path + '/' + ff.name + l.append(t) + return l + +def backupAllFunc(stype): + + name = sys.argv[2] + num = sys.argv[3] + + prefix_dict = { + "site": "web", + "database": "db", + "path": "path", + } + + backups = [] + # print("stype:", stype) + # 提前获取-清理多余备份 + if stype == 'site': + pid = mw.M('sites').where('name=?', (name,)).getField('id') + backups = mw.M('backup').where('type=? and pid=?', ('0', pid)).field('id,filename').select() + if stype == 'database': + db_path = mw.getServerDir() + '/mysql' + pid = mw.M('databases').dbPos(db_path, 'mysql').where('name=?', (name,)).getField('id') + backups = mw.M('backup').where('type=? and pid=?', ('1', pid)).field('id,filename').select() + if stype == 'path': + backup_dir = mw.getBackupDir() + backup_path = backup_dir + '/path' + _name = 'path_{}'.format(os.path.basename(name)) + backups = findPathName(backup_path, _name) + + # 其他类型关系性数据库(mysql类的) + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + db_path = mw.getServerDir() + '/' + plugin_name + pid = mw.M('databases').dbPos(db_path, 'mysql').where('name=?', (name,)).getField('id') + backups = mw.M('backup').where('type=? and pid=?', ('1', pid)).field('id,filename').select() + + args = stype + " " + name + " " + num + cmd = 'python3 ' + mw.getPanelDir() + '/scripts/backup.py ' + args + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + args = "database " + name + " " + num + cmd = 'python3 ' + mw.getPanelDir() + '/plugins/' + plugin_name + '/scripts/backup.py ' + args + + os.system(cmd) + + # 开始执行上传信息. + + if stype.find('database_') > -1: + bk_name = 'database' + plugin_name = stype.replace('database_', '') + bk_prefix = plugin_name + '/db' + stype = 'database' + else: + bk_prefix = prefix_dict[stype] + bk_name = stype + + find_path = mw.getBackupDir() + '/' + bk_name + '/' + bk_prefix + '_' + name + if stype == 'path': + _name = 'path_{}'.format(os.path.basename(name)) + find_path = mw.getBackupDir() + '/path/'+_name + + find_new_file = "ls " + find_path + "_* | grep '.gz' | cut -d \\ -f 1 | awk 'END {print}'" + + filename = mw.execShell(find_new_file)[0].strip() + if filename == "": + mw.echoInfo("not find upload file!") + return '' + + mw.echoInfo("准备上传文件 {}".format(filename)) + ftp = FtpPSClient() + ftp.uploadFile(filename, stype) + + # print(backups) + backups = sorted(backups, key=lambda x: x['filename'], reverse=False) + mw.echoStart('开始删除远程备份') + num = int(num) + sep = len(backups) - num + if sep > -1: + for backup in backups: + fn = os.path.basename(backup['filename']) + object_name = ftp.buildDirName(stype, fn) + ftp.deleteFile(object_name) + mw.echoInfo("已清理远程过期备份文件:" + object_name) + sep -= 1 + if sep < 0: + break + mw.echoEnd('结束删除远程备份') + + return '' + + +def in_array(name, arr=[]): + for x in arr: + if name == x: + return True + return False + + +def installPreInspection(): + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'set_config': + print(setConf()) + elif func == "get_list": + print(getList()) + elif func == "create_dir": + print(createDir()) + elif func == "delete_dir": + print(deleteDir()) + elif func == 'delete_file': + print(deleteFile()) + elif in_array(func, ['site', 'database', 'path']) or func.find('database_') > -1: + print(backupAllFunc(func)) + else: + print('error') diff --git a/plugins/backup_ftp/info.json b/plugins/backup_ftp/info.json new file mode 100755 index 000000000..951b04e84 --- /dev/null +++ b/plugins/backup_ftp/info.json @@ -0,0 +1,18 @@ +{ + "sort":6, + "title":"FTP存储空间", + "hook":["backup"], + "tip":"soft", + "name":"backup_ftp", + "type":"运行环境", + "ps":"将网站或数据库打包备份到FTP存储空间", + "versions":["1.0"], + "install_pre_inspection":false, + "shell":"install.sh", + "checks":"server/backup_ftp", + "path": "server/backup_ftp", + "author":"midoks", + "home":"https://github.com/midoks/mdserver-web", + "date":"2022-10-23", + "pid": "5" +} \ No newline at end of file diff --git a/plugins/backup_ftp/install.sh b/plugins/backup_ftp/install.sh new file mode 100755 index 000000000..9d37cbf5d --- /dev/null +++ b/plugins/backup_ftp/install.sh @@ -0,0 +1,31 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sys_os=`uname` + + +Install_App() +{ + mkdir -p ${serverPath}/backup_ftp + echo "${1}" > ${serverPath}/backup_ftp/version.pl + echo '安装完成' + +} + +Uninstall_App() +{ + rm -rf ${serverPath}/backup_ftp +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App $2 +else + Uninstall_App $2 +fi diff --git a/plugins/backup_ftp/js/backup_ftp.js b/plugins/backup_ftp/js/backup_ftp.js new file mode 100755 index 000000000..f4557b340 --- /dev/null +++ b/plugins/backup_ftp/js/backup_ftp.js @@ -0,0 +1,258 @@ + + +function bkfPost(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'backup_ftp', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function getFtpLocalTime(data){ + var str = data.slice(0,4)+"/"+data.slice(4,6)+"/"+data.slice(6,8) + + " " + data.slice(8,10)+":"+data.slice(10,12)+":"+data.slice(12,14); + return str; +} + +// 自定义部分 +var i = null; +//设置API +function upyunApi(){ + + bkfPost('conf', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var token = rdata.data; + var check_status = token.use_sftp; + var sftp_checked = check_status === "true" ? " checked=\"checked\"" : ""; + + if (typeof(token.ftp_host) == 'undefined'){ + token.ftp_host = ''; + } + + if (typeof(token.ftp_user) == 'undefined'){ + token.ftp_user = ''; + } + + if (typeof(token.ftp_pass) == 'undefined'){ + token.ftp_pass = ''; + } + + if (typeof(token.backup_path) == 'undefined'){ + token.backup_path = ''; + } + + var apicon = '
    \ +

    \ + 使用SFTP:\ 是否使用SFTP进行数据传输 \ +

    \ +

    \ + Host:\ + *服务器地址,FTP默认端口21, SFTP默认端口22\ +

    \ +

    \ + 用户名:\ + *指定用户名\ +

    \ +

    \ + 密码:\ + *登录密码\ +

    \ +

    \ + 存储位置:\ + *相对于根目录的路径,默认是/backup\ +

    \ +
    '; + layer.open({ + type: 1, + area: "600px", + title: "FTP/SFTP帐户设置", + closeBtn: 1, + shift: 5, + shadeClose: false, + btn: ['确定','取消'], + content:apicon, + yes:function(index,layero){ + var data = { + use_sftp:$("input[name='use_sftp']").prop('checked'), + ftp_user:$("input[name='ftp_username']").val(), + ftp_pass:$("input[name='ftp_password']").val(), + ftp_host:$("input[name='upyun_service']").val(), + backup_path:$("input[name='backup_path']").val() + } + bkfPost('set_config', data, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (rdata.status){ + showMsg(rdata.msg,function(){ + layer.close(index); + osList("/"); + },{icon:1},2000); + } else{ + layer.msg(rdata.msg,{icon:2}); + } + }) + }, + }); + }); +} + +function createDir(){ + layer.open({ + type: 1, + area: "400px", + title: "创建目录", + closeBtn: 1, + shift: 5, + shadeClose: false, + btn: ['确定','取消'], + content:'
    \ +

    \ + 目录名称:\ + \ +

    \ +
    ', + success:function(){ + $("input[name='newPath']").focus().keyup(function(e){ + if(e.keyCode == 13) $(".layui-layer-btn0").click(); + }); + }, + yes:function(index,layero){ + var name = $("input[name='newPath']").val(); + if(name == ''){ + layer.msg('目录名称不能为空!',{icon:2}); + return; + } + var path = $("#myPath").val(); + var dirname = name; + // var loadT = layer.msg('正在创建目录['+dirname+']...',{icon:16,time:0,shade: [0.3, '#000']}); + bkfPost('create_dir', {path:path,name:dirname}, function(data){ + var rdata = $.parseJSON(data.data); + if(rdata.status) { + showMsg(rdata.msg, function(){ + layer.close(index); + osList(path); + } ,{icon:1}, 2000); + } else{ + layer.msg(rdata.msg,{icon:2}); + } + }); + } + }); +} + +//删除文件 +function deleteFile(name, is_dir){ + if (is_dir === false){ + safeMessage('删除文件','删除后将无法恢复,真的要删除['+name+']吗?',function(){ + var path = $("#myPath").val(); + var filename = name; + bkfPost('delete_file', {filename:filename,path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + osList(path); + },{icon:rdata.status?1:2},2000); + }); + }); + } else { + safeMessage('删除文件夹','删除后将无法恢复,真的要删除['+name+']吗?',function(){ + var path = $("#myPath").val(); + bkfPost('delete_dir', {dir_name:name,path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + osList(path); + },{icon:rdata.status?1:2},2000); + }); + }); + } +} + +function osList(path){ + bkfPost('get_list', {path:path}, function(rdata){ + + var rdata = $.parseJSON(rdata.data); + if(rdata.status === false){ + showMsg(rdata.msg,function(){ + upyunApi(); + },{icon:2},2000); + return; + } + + var mlist = rdata.data; + // console.log(mlist); + var listBody = '' + var listFiles = '' + for(var i=0;i\'+mlist.list[i].name+'\ + -\ + -\ + 删除' + }else{ + listFiles += '\'+mlist.list[i].name+'\ + '+toSize(mlist.list[i].size)+'\ + '+getFtpLocalTime(mlist.list[i].time)+'\ + 下载 | 删除' + } + } + listBody += listFiles; + + var pathLi=''; + var tmp = path.split('/') + var pathname = ''; + var n = 0; + for(var i=0;i 0 && tmp[i] == '') continue; + var dirname = tmp[i]; + if(dirname == '') { + dirname = '根目录'; + n++; + } + pathname += '/' + tmp[i]; + pathname = pathname.replace('//','/'); + pathLi += '
  • '+dirname+'
  • '; + } + var um = 1; + if(tmp[tmp.length-1] == '') um = 2; + var backPath = tmp.slice(0,tmp.length-um).join('/') || '/'; + $('#myPath').val(path); + $(".upyunCon .place-input ul").html(pathLi); + $(".upyunlist .list-list").html(listBody); + + upPathLeft(); + + $('#backBtn').unbind().click(function() { + osList(backPath); + }); + + $('.upyunCon .refreshBtn').unbind().click(function(){ + osList(path); + }); + }); +} + +//计算当前目录偏移 +function upPathLeft(){ + var UlWidth = $(".place-input ul").width(); + var SpanPathWidth = $(".place-input").width() - 20; + var Ml = UlWidth - SpanPathWidth; + if(UlWidth > SpanPathWidth ){ + $(".place-input ul").css("left",-Ml) + } + else{ + $(".place-input ul").css("left",0) + } +} +// $('.layui-layer-page').css('height','670px'); \ No newline at end of file diff --git a/plugins/clean/ico.png b/plugins/clean/ico.png new file mode 100644 index 000000000..2d534cabc Binary files /dev/null and b/plugins/clean/ico.png differ diff --git a/plugins/clean/index.html b/plugins/clean/index.html new file mode 100755 index 000000000..fd6bf4cf5 --- /dev/null +++ b/plugins/clean/index.html @@ -0,0 +1,74 @@ +
    +
    +
    +

    服务

    +

    配置修改

    +

    常用功能

    +

    运行日志

    +

    说明

    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/plugins/clean/index.py b/plugins/clean/index.py new file mode 100755 index 000000000..3564cd3ea --- /dev/null +++ b/plugins/clean/index.py @@ -0,0 +1,302 @@ +# coding:utf-8 + +import sys +import io +import os +import time + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'clean' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/clean.conf" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def getLockFile(): + return getServerDir() + "/installed_lock.pl" + + +def runLog(): + return getServerDir() + "/clean.log" + + +def status(): + initConf() + if os.path.exists(getLockFile()): + return "start" + return 'stop' + + +def initConf(): + conf = getConf() + if not os.path.exists(conf): + content = "" + + clog = [ + "/var/log/cron-*", + "/var/log/maillog-*", + "/var/log/secure-*", + "/var/log/spooler-*", + "/var/log/yum.log-*", + "/var/log/messages-*", + "/var/log/btmp-*", + "/var/log/auth.*", + "/var/log/messages.*", + "/var/log/debug.*", + "/var/log/syslog.*", + "/var/log/syslog", + "/var/log/mail.*", + "/var/log/mail.err.*", + "/var/log/mail.info.*", + "/var/log/mail.warn.*", + "/var/log/btmp.*", + "/var/log/sa/sa*", + "/var/log/sysstat/sa*", + "/var/log/atop/atop*", + "/var/log/anaconda/*.log", + + "/var/log/dpkg.log.*", + "/var/log/alternatives.log.*", + "/var/log/user.log.*", + "/var/log/kern.log.*", + "/var/log/daemon.log.*", + + "/var/log/*.gz", + "/var/log/*.xz", + "/var/log/*.log.*", + + "/var/log/audit/audit.log.*", + "/var/log/hawkey.log-*", + "/var/log/apt/*.gz", + "/var/log/apt/*.xz", + "/var/log/rhsm/rhsm.log-*", + "/var/log/rhsm/rhsmcertd.log-*", + "/var/log/exim4/*.gz", + "/var/log/journal/*", + "/var/spool/clientmqueue/*", + + "/tmp/yum_save_*", + "/tmp/tmp.*", + + "/www/server/dztasks/logs/dztasks.*.log", + "/www/server/dztasks/logs/dztasks_*", + ] + for i in clog: + content += i + "\n" + + # 常用日志 + clogcom = [ + "/var/log/messages", + "/var/log/btmp", + "/var/log/wtmp", + "/var/log/secure", + "/var/log/lastlog", + "/var/log/syslog", + "/var/log/cron", + "/www/wwwlogs", + "/www/server/rsyncd", + "/www/server/acme_pandominassl_apply", + "/www/server/sphinx/index", + "/www/server/mongodb/logs", + "/www/server/php/53/var/log", + "/www/server/php/54/var/log", + "/www/server/php/55/var/log", + "/www/server/php/56/var/log", + "/www/server/php/70/var/log", + "/www/server/php/71/var/log", + "/www/server/php/72/var/log", + "/www/server/php/73/var/log", + "/www/server/php/74/var/log", + "/www/server/php/80/var/log", + "/www/server/php/81/var/log", + "/www/server/php/82/var/log", + "/www/server/php/83/var/log", + "/www/server/php/84/var/log", + "/www/server/php/85/var/log", + "/www/server/openresty/nginx/logs", + "/www/server/phpmyadmin", + "/www/server/redis/data", + "/www/server/alist/data/log", + "/www/server/dztasks/logs", + "/www/server/rsyncd/lsyncd.log" + "/www/server/cron", + ] + for i in clogcom: + if os.path.exists(i): + content += i + "\n" + + # 清理日志 + rootDir = "/var/log" + + l = os.listdir(rootDir) + for x in range(len(l)): + abspath = rootDir + "/" + l[x] + content += abspath + "\n" + mw.writeFile(conf, content) + + +def start(): + initConf() + + lock_file = getLockFile() + if not os.path.exists(lock_file): + mw.writeFile(lock_file, "") + + import tool_task + tool_task.createBgTask() + return 'ok' + + return 'fail' + + +def stop(): + initConf() + lock_file = getLockFile() + if os.path.exists(lock_file): + os.remove(lock_file) + import tool_task + tool_task.removeBgTask() + return 'ok' + + return 'fail' + + +def reload(): + return 'ok' + + +def get_filePath_fileName_fileExt(filename): + (filepath, tempfilename) = os.path.split(filename) + (shotname, extension) = os.path.splitext(tempfilename) + return filepath, shotname, extension + + +def cleanFileLog(path): + filepath, shotname, extension = get_filePath_fileName_fileExt(path) + if extension == ".log": + cmd = "echo \"\" > " + path + tmp = mw.execShell(cmd) + if tmp[1] != "": + cmd += " | error:" + tmp[1].strip() + print(cmd) + + +def cleanSelfFileLog(path): + filepath, shotname, extension = get_filePath_fileName_fileExt(path) + if extension == ".log": + cmd = "echo \"\" > " + path + tmp = mw.execShell(cmd) + if tmp[1] != "": + cmd += " | error:" + tmp[1].strip() + print(cmd) + + +def cleanDirLog(path): + l = os.listdir(path) + for x in range(len(l)): + abspath = path + "/" + l[x] + if os.path.isfile(abspath): + cleanFileLog(abspath) + if os.path.isdir(abspath): + cleanDirLog(abspath) + + +def cleanRun(): + plugin_dir = getPluginDir() + log_file = getServerDir()+'/clean.log' + cmd = 'cd '+mw.getPanelDir()+' && python3 '+plugin_dir+'/index.py clean > '+log_file + os.system(cmd) + return mw.returnJson(True, '执行成功!') + +def cleanLog(): + conf = getConf() + clist = mw.readFile(conf).strip() + clist = clist.split("\n") + + for x in clist: + abspath = x.strip() + + if abspath.find('*') > 1: + cmd = 'rm -rf ' + abspath + print(cmd) + data = mw.execShell(cmd) + # print(data) + continue + + if os.path.exists(abspath): + if os.path.isfile(abspath): + cleanSelfFileLog(abspath) + continue + + if os.path.isdir(abspath): + cleanDirLog(abspath) + continue + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'clean': + cleanLog() + elif func == 'clean_run': + print(cleanRun()) + else: + print('error') diff --git a/plugins/clean/info.json b/plugins/clean/info.json new file mode 100755 index 000000000..2a5bff614 --- /dev/null +++ b/plugins/clean/info.json @@ -0,0 +1,17 @@ +{ + "sort":2, + "title":"日志清理", + "tip":"soft", + "name":"clean", + "type":"运行环境", + "ps":"日志清理", + "versions":"1.0", + "updates":"1.0", + "shell":"install.sh", + "checks":"server/clean", + "path": "server/clean", + "author":"clean", + "home":"https://github.com/midoks", + "date":"2021-11-23", + "pid": "5" +} \ No newline at end of file diff --git a/plugins/clean/install.sh b/plugins/clean/install.sh new file mode 100755 index 000000000..d52b1c1c2 --- /dev/null +++ b/plugins/clean/install.sh @@ -0,0 +1,42 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/mongodb && /bin/bash install.sh install 5.0.4 +# cd /www/server/mdserver-web/plugins/mongodb && /bin/bash install.sh install 5.0.4 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + + +Install_app() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/clean + + cd ${rootPath} && python3 ${rootPath}/plugins/clean/index.py start + echo "${VERSION}" > $serverPath/clean/version.pl + echo '安装完成' +} + +Uninstall_app() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/clean/index.py stop + rm -rf $serverPath/clean + echo "Uninstall_clean" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/clean/tool_task.py b/plugins/clean/tool_task.py new file mode 100644 index 000000000..4fedd9d7e --- /dev/null +++ b/plugins/clean/tool_task.py @@ -0,0 +1,129 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'clean' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getTaskConf(): + conf = getServerDir() + "/task_config.json" + return conf + + +def getConfigData(): + conf = getTaskConf() + if os.path.exists(conf): + return json.loads(mw.readFile(getTaskConf())) + return { + "task_id": -1, + "period": "day-n", + "where1": "7", + "hour": "0", + "minute": "15", + } + + +def createBgTask(): + removeBgTask() + createBgTaskByName(getPluginName()) + + +def createBgTaskByName(name): + args = getConfigData() + _name = "[勿删]日志清理[" + name + "]" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in args and args["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (args["task_id"],)).find() + if res and res["id"] == args["task_id"]: + print("计划任务已经存在!") + return True + + mw_dir = mw.getPanelDir() + cmd = ''' +mw_dir=%s +rname=%s +plugin_path=%s +script_path=%s +logs_file=$plugin_path/${rname}.log +''' % (mw_dir, name, getServerDir(), getPluginDir()) + cmd += 'echo "★【`date +"%Y-%m-%d %H:%M:%S"`】 STSRT★" >> $logs_file' + "\n" + cmd += 'echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" >> $logs_file' + "\n" + cmd += 'echo "python3 $script_path/index.py clean >> $logs_file 2>&1"' + "\n" + cmd += 'cd $mw_dir && python3 $script_path/index.py clean >> $logs_file 2>&1' + "\n" + cmd += 'echo "【`date +"%Y-%m-%d %H:%M:%S"`】 END★" >> $logs_file' + "\n" + cmd += 'echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" >> $logs_file' + "\n" + + params = { + 'name': _name, + 'type': args['period'], + 'week': "", + 'where1': args['where1'], + 'hour': args['hour'], + 'minute': args['minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + args["task_id"] = task_id + args["name"] = name + mw.writeFile(getTaskConf(), json.dumps(args)) + + +def removeBgTask(): + cfg = getConfigData() + if "task_id" in cfg and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data["status"]: + cfg["task_id"] = -1 + mw.writeFile(getTaskConf(), json.dumps(cfg)) + return True + return False + + +if __name__ == "__main__": + if len(sys.argv) > 1: + action = sys.argv[1] + if action == "remove": + removeBgTask() + elif action == "add": + createBgTask() diff --git a/plugins/cloudreve/ico.png b/plugins/cloudreve/ico.png new file mode 100644 index 000000000..82bcc51f3 Binary files /dev/null and b/plugins/cloudreve/ico.png differ diff --git a/plugins/cloudreve/index.html b/plugins/cloudreve/index.html new file mode 100755 index 000000000..d50a2be40 --- /dev/null +++ b/plugins/cloudreve/index.html @@ -0,0 +1,31 @@ + + +
    +
    +
    +
    +

    服务

    +

    自启动

    +

    常用功能

    +

    配置修改

    +

    运行日志

    +

    相关说明

    + +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/plugins/cloudreve/index.py b/plugins/cloudreve/index.py new file mode 100755 index 000000000..d01cf0865 --- /dev/null +++ b/plugins/cloudreve/index.py @@ -0,0 +1,292 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'cloudreve' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + path = getServerDir() + "/conf.ini" + return path + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + + +def status(): + data = mw.execShell( + "ps aux|grep cloudreve |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + + if data[0] == '': + return 'stop' + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/'+getPluginName()) + return content + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def alistOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return alistOp('start') + + +def stop(): + return alistOp('stop') + + +def restart(): + status = alistOp('restart') + return status + + +def reload(): + return alistOp('reload') + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + return getServerDir() + '/logs.pl' + +def pSqliteDb(dbname='databases'): + pos_file = getServerDir() + '/data/' + file = pos_file + '/data.db' + name = 'data' + conn = mw.M(dbname).dbPos(pos_file, name) + return conn + +def getCloudrevePort(): + file = getConf() + content = mw.readFile(file) + rep = r'Listen\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def homePage(): + http_port = getCloudrevePort() + ip = mw.getLocalIp() + if mw.isAppleSystem(): + ip = '127.0.0.1' + url = 'http://'+ip+""+str(http_port) + # print(url) + return mw.returnJson(True, 'ok!', url) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'home_page': + print(homePage()) + else: + print('error') diff --git a/plugins/cloudreve/info.json b/plugins/cloudreve/info.json new file mode 100755 index 000000000..f89aee960 --- /dev/null +++ b/plugins/cloudreve/info.json @@ -0,0 +1,17 @@ +{ + "sort": 7, + "ps": "一个支持多种存储的文件列表程序", + "name": "cloudreve", + "title": "cloudreve", + "shell": "install.sh", + "versions":["3.8.3"], + "tip": "soft", + "checks": "server/cloudreve", + "path": "server/cloudreve", + "display": 1, + "author": "cloudreve", + "date": "2024-10-14", + "home": "https://cloudreve.org/", + "type": 0, + "pid": "5" +} diff --git a/plugins/cloudreve/init.d/cloudreve.service.tpl b/plugins/cloudreve/init.d/cloudreve.service.tpl new file mode 100644 index 000000000..42c1fbc57 --- /dev/null +++ b/plugins/cloudreve/init.d/cloudreve.service.tpl @@ -0,0 +1,13 @@ +[Unit] +Description=A file list program that supports multiple storage +After=network.target + +[Service] +Type=simple +WorkingDirectory={$SERVER_PATH}/cloudreve +ExecStart={$SERVER_PATH}/cloudreve/cloudreve +ExecReload=/bin/kill -USR2 $MAINPID +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/cloudreve/init.d/cloudreve.tpl b/plugins/cloudreve/init.d/cloudreve.tpl new file mode 100644 index 000000000..f740d1cd7 --- /dev/null +++ b/plugins/cloudreve/init.d/cloudreve.tpl @@ -0,0 +1,46 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: cloudreve Service + +### BEGIN INIT INFO +# Provides: cloudreve +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts cloudreve +# Description: starts the MDW-Web +### END INIT INFO + +# Simple cloudreve init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +app_start(){ + echo "Starting cloudreve server..." + cd {$SERVER_PATH}/cloudreve + ./cloudreve >> {$SERVER_PATH}/cloudreve/logs.pl 2>&1 & +} + +app_stop(){ + echo "dztasks stopped" + ps -ef| grep cloudreve | grep -v grep | grep -v python | grep -v sh | awk '{print $2}'| xargs kill +} + + +case "$1" in + start) + app_start + ;; + stop) + app_stop + ;; + restart|reload) + app_stop + sleep 0.3 + app_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/cloudreve/install.sh b/plugins/cloudreve/install.sh new file mode 100755 index 000000000..11ac5d927 --- /dev/null +++ b/plugins/cloudreve/install.sh @@ -0,0 +1,90 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/cloudreve && bash install.sh install 3.8.3 +# cd /www/server/mdserver-web/plugins/cloudreve && bash install.sh install 3.8.3 + +VERSION=$2 + +sysArch=`arch` +sysName=`uname` + +ALIST_ARCH_NAME=amd64 +if [ "$sysArch" == "arm64" ];then + ALIST_ARCH_NAME=arm64 +elif [ "$sysArch" == "x86_64" ]; then + ALIST_ARCH_NAME=amd64 +elif [ "$sysArch" == "aarch64" ]; then + ALIST_ARCH_NAME=aarch64 +fi + +ALIST_NAME=linux +if [ "$sysName" == "Darwin" ];then + ALIST_NAME=darwin +fi + +Install_App() +{ + echo '正在安装脚本文件...' + + mkdir -p $serverPath/source + mkdir -p $serverPath/source/cloudreve + + FILE_TGZ=cloudreve_${VERSION}_${ALIST_NAME}_${ALIST_ARCH_NAME}.tar.gz + CLOUDREVE_DIR=$serverPath/source/cloudreve + + if [ ! -f $CLOUDREVE_DIR/${FILE_TGZ} ];then + wget -O $CLOUDREVE_DIR/${FILE_TGZ} https://github.com/cloudreve/Cloudreve/releases/download/${VERSION}/${FILE_TGZ} + fi + + mkdir -p $serverPath/cloudreve + + cd $CLOUDREVE_DIR && tar -zxvf ${FILE_TGZ} -C $serverPath/cloudreve + echo "${VERSION}" > $serverPath/cloudreve/version.pl + + cd ${rootPath} && python3 ${rootPath}/plugins/cloudreve/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/cloudreve/index.py initd_install + + # cd $serverPath/cloudreve && ./alist admin set admin + echo '安装cloudreve完成' +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/cloudreve.service ];then + systemctl stop cloudreve + systemctl disable cloudreve + rm -rf /usr/lib/systemd/system/cloudreve.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/cloudreve.service ];then + systemctl stop cloudreve + systemctl disable cloudreve + rm -rf /lib/systemd/system/cloudreve.service + systemctl daemon-reload + fi + + if [ -f $serverPath/cloudreve/initd/cloudreve ];then + $serverPath/cloudreve/initd/cloudreve stop + fi + + if [ -d $serverPath/cloudreve ];then + rm -rf $serverPath/cloudreve + fi + + echo "卸载cloudreve成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/cloudreve/js/cloudreve.js b/plugins/cloudreve/js/cloudreve.js new file mode 100755 index 000000000..5f20fc88a --- /dev/null +++ b/plugins/cloudreve/js/cloudreve.js @@ -0,0 +1,82 @@ +function alistPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'cloudreve'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function alistPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'cloudreve'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function commonHomePage(){ + alistPost('home_page', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + window.open(rdata.data); + }); +} + +function alistCommonFunc(){ + var con = ''; + con += '

    \ + \ +

    '; + + $(".soft-man-con").html(con); +} + +function alistReadme(){ + + + var readme = '
      '; + readme += '
    • 记住安装时生成的用户+密码
    • '; + readme += '
    '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/cryptocurrency_trade/README.md b/plugins/cryptocurrency_trade/README.md new file mode 100644 index 000000000..8ec1d9a27 --- /dev/null +++ b/plugins/cryptocurrency_trade/README.md @@ -0,0 +1,78 @@ +# plugins_cryptocurrency_trade + +- https://docs.ccxt.com/en/latest/install.html#python-proxies + +数字货币量化交易插件 + +``` +cd /www/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/okex/strategy/st_demo.py + +python3 plugins/cryptocurrency_trade/ccxt/okex/strategy/st_demo.py + +miniconda3 + + + +``` + +# 免费TV策略 +https://cn.tradingview.com/scripts/?script_type=strategies + + +### tradingview pine 指标 +``` +// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ +// © TenCloud + +//@version=5 +indicator("第一个指标",shorttitle = "开心吧") + +ema10 = ta.ema(close, 25) +ema100 = ta.ema(close, 100) +l1=plot(ema10, color=color.red,title = "ema10") +l2=plot(ema100,color = color.green, title = "ema100") + + +buy = ta.crossover(ema10,ema100) +sell = ta.crossunder(ema10,ema100) + +plotchar(buy, text = "buy", color=color.green) +plotchar(sell, text="sell", color=color.red) + +l_color = ema10>ema100 ? color.green:color.red + +fill(l1,l2,color=color.new(l_color,70)) + +``` + +### tradingview pine 策略 +``` +// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ +// © TenCloud + +//@version=5 +strategy("第一个指标",shorttitle = "开心吧", overlay = true, initial_capital = 1000) + +ema10 = ta.ema(close, 25) +ema100 = ta.ema(close, 100) +l1=plot(ema10, color=color.red,title = "ema10") +l2=plot(ema100,color = color.green, title = "ema100") + + +buy = ta.crossover(ema10,ema100) +sell = ta.crossunder(ema10,ema100) + +if buy + strategy.entry("long1", strategy.long,1) + +if sell + strategy.close("long1", qty_percent = 100,comment = "平仓多单") +// plotchar(buy, text = "buy", color=color.green) +// plotchar(sell, text="sell", color=color.red) + +// l_color = ema10>ema100 ? color.green:color.red + +// fill(l1,l2,color=color.new(l_color,70)) + + +``` \ No newline at end of file diff --git a/plugins/cryptocurrency_trade/ccxt/public_data/data.py b/plugins/cryptocurrency_trade/ccxt/public_data/data.py new file mode 100644 index 000000000..bdd0b1aec --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/public_data/data.py @@ -0,0 +1,555 @@ +import ccxt + + +from datetime import datetime +import time +import sys +import json +import os +import glob +import threading +from pprint import pprint + + +# print(os.getcwd()) +sys.path.append(os.getcwd() + "/class/core") +import mw + +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/public_data/data.py run +# python3 plugins/cryptocurrency_trade/ccxt/public_data/data.py long + +# 查看支持的交易所 +# print(ccxt.exchanges) + +# 代理设置 +# exchange = ccxt.poloniex({ +# 'proxies': { +# 'http': 'http://127.0.0.1:1088' +# }, +# }) + +# exchange = ccxt.poloniex() +exchange = ccxt.okex() + + +def getPluginName(): + return 'cryptocurrency_trade' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConfigData(): + cfg_path = getServerDir() + "/data.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + log_file = getServerDir() + '/logs/datasource.log' + mw.writeFileLog(log_str, log_file, 1 * 1024 * 1024) + return True + + +def isSetDbConf(): + data = getConfigData() + if 'db' in data: + return True + return False + + +def beforeDate(day=360): + dd = time.time() - day * 86400 + d = datetime.fromtimestamp(dd) + day = d.strftime("%Y-%m-%d") + return day + + +def getTextTimeShow(time): + d = datetime.fromtimestamp(time) + day = d.strftime("%Y-%m-%d %H:%M:%S") + return day + + +def isSqlError(mysqlMsg): + # 检测数据库执行错误 + mysqlMsg = str(mysqlMsg) + if "MySQLdb" in mysqlMsg: + return mw.returnJson(False, 'MySQLdb组件缺失!
    进入SSH命令行输入: pip install mysql-python | pip install mysqlclient==2.0.3') + if "2002," in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "2003," in mysqlMsg: + return mw.returnJson(False, "Can't connect to MySQL server on '127.0.0.1' (61)") + if "using password:" in mysqlMsg: + return mw.returnJson(False, '数据库密码错误') + if "1045" in mysqlMsg: + return mw.returnJson(False, '连接错误!') + if "SQL syntax" in mysqlMsg: + return mw.returnJson(False, 'SQL语法错误!') + if "Connection refused" in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "1133" in mysqlMsg: + return mw.returnJson(False, '数据库用户不存在!') + if "1007" in mysqlMsg: + return mw.returnJson(False, '数据库已经存在!') + return None + + +def pMysqlDb(): + # pymysql + db = mw.getMyORM() + data = getConfigData() + db_data = data['db'] + db.setHost(db_data['db_host']) + db.setPort(db_data['db_port']) + db.setUser(db_data['db_user']) + db.setPwd(db_data['db_pass']) + db.setDbName(db_data['db_name']) + return db + + +def makeInsertSql(table, item): + sql = "insert into " + table + keyStr = '(' + valueStr = ' values(' + for i in item: + # print i, item[i] + keyStr += '`' + str(i) + '`,' + valueStr += "'" + str(item[i]) + "'," + + keyStrLen = len(keyStr) + keyStr = keyStr[0:keyStrLen - 1] + keyStr += ') ' + + valueStrLen = len(valueStr) + valueStr = valueStr[0:valueStrLen - 1] + valueStr += ') ' + + sql += keyStr + sql += valueStr + + return sql + + +def makeReplaceSql(table, item): + sql = "replace into " + table + keyStr = '(' + valueStr = ' values(' + for i in item: + # print i, item[i] + keyStr += '`' + str(i) + '`,' + valueStr += "'" + str(item[i]) + "'," + + keyStrLen = len(keyStr) + keyStr = keyStr[0:keyStrLen - 1] + keyStr += ') ' + + valueStrLen = len(valueStr) + valueStr = valueStr[0:valueStrLen - 1] + valueStr += ') ' + + sql += keyStr + sql += valueStr + + return sql + + +def makeUpdateSql(table, item, mid): + sql = "update " + table + " set " + keyStr = '' + for i in item: + keyStr += '`' + str(i) + '`=' + "'" + item[i] + "'," + + keyStrLen = len(keyStr) + keyStr = keyStr[0:keyStrLen - 1] + sql += keyStr + sql += " where id = '" + str(mid) + "'" + return sql + + +def dataToDb(table_name, data): + pdb = pMysqlDb() + for i in data: + rdata = { + 'addtime': int(i[0] / 1000), + 'open': i[1], + 'high': i[2], + 'low': i[3], + 'close': i[4], + 'vol': i[5], + } + + # sql = """SELECT id FROM %s where addtime='%s' LIMIT 1""" % ( + # table_name, rdata['addtime']) + # fdata = pdb.query(sql) + # if fdata: + # print(table_name + ":" + str(rdata['addtime']) + ", old to db ok") + # else: + # isql = makeReplaceSql(table_name, rdata) + # r = pdb.execute(isql) + # print(table_name + ":" + str(rdata['addtime']) + ", go to db ok") + + isql = makeReplaceSql(table_name, rdata) + r = pdb.execute(isql) + # print(table_name + ":" + str(rdata['addtime']) + ", go to db ok") + return True + + +def makeTableName(input_type="btc", input_tf="1m"): + table_name = "ct_%s_%s" % (input_type, input_tf) + return table_name + + +def isHasTable(input_type="btc", input_tf="1m"): + pdb = pMysqlDb() + + # input_type = 'btc' + # input_tf = '1m' + table_name = makeTableName(input_type, input_tf) + mtable = pdb.query("show tables like '%s'" % (table_name,)) + if len(mtable) != 0: + return True + return False + + +def createSql(input_type="btc", input_tf="1m"): + pdb = pMysqlDb() + + # input_type = 'btc' + # input_tf = '1m' + table_name = makeTableName(input_type, input_tf) + + mtable = pdb.query("show tables like '%s'" % (table_name,)) + if len(mtable) != 0: + return True + + sql_tpl = getPluginDir() + "/conf/create.sql" + content = mw.readFile(sql_tpl) + content = content.replace("xx1", input_type) + content = content.replace("xx2", input_tf) + + res = pdb.execute(content) + # pprint(res) + return True + + +def getDataFetch(symbol, start_time="2023-3-1", input_tf="1m", limit=500): + start_time = datetime.strptime(start_time, '%Y-%m-%d') + end_time = datetime.strptime(end_time, '%Y-%m-%d') + + start_time_stamp = int(time.mktime(start_time.timetuple())) * 1000 + data = exchange.fetch_ohlcv(symbol, timeframe=input_tf, + since=start_time_stamp, limit=limit) + return data + + +def getPointData(symbol, start_time=1677713220000, input_tf="1m", limit=500): + data = exchange.fetch_ohlcv( + symbol, timeframe=input_tf, since=start_time, limit=limit) + return data + + +def toUnixTimeSecond(tf="1m"): + if tf.find("m") > -1: + v = int(tf.replace("m", '')) + return v * 60 + + if tf.find("h") > -1: + v = int(tf.replace("h", '')) + return v * 3600 + + if tf.find("d") > -1: + v = int(tf.replace("d", '')) + return v * 86400 + return 0 + + +def findAndUpdateData(tag, input_tf="1m", start_time="2023-1-1", limit=300): + pdb = pMysqlDb() + table_name = makeTableName(tag, input_tf) + sql = 'SELECT addtime FROM ' + table_name + ' order by addtime desc LIMIT 1' + qdata = pdb.query(sql) + + start_time = datetime.strptime(start_time, '%Y-%m-%d') + start_time = int(time.mktime(start_time.timetuple())) * 1000 + if len(qdata) != 0: + start_time = int(qdata[0]['addtime']) * 1000 + + pre_time = toUnixTimeSecond(input_tf) * 5 * 1000 + start_time = start_time - pre_time + # pprint(start_time) + symbol = tag.upper() + "/USDT" + data = getPointData(symbol, start_time, input_tf, limit) + + # pprint(data) + # print("------------lini===========") + # pprint(data[1:]) + + if len(qdata) == 1: + data = data[1:] + + dataToDb(table_name, data) + + now = time.strftime("%m/%d %H:%M:%S") + data_len = len(data) + msg_head = now + "|虚拟币:" + tag + ",tf:" + \ + input_tf + "!,总数:" + str(data_len) + writeLog(msg_head) + if data_len > 0: + # print("new data time", data[data_len - 1][0]) + dt = getTextTimeShow(data[data_len - 1][0] / 1000) + msg = now + "|最新日期:" + dt + writeLog(msg) + + table_name = makeTableName(tag, input_tf) + + sql = "SELECT addtime FROM " + table_name + \ + " order by addtime desc limit 10000,1" + qdata = pdb.query(sql) + + if len(qdata) > 0: + print(qdata) + + # input_tf_time = toUnixTimeSecond(input_tf) + # now_t = int(time.time()) + + # del_before_t = now_t - input_tf_time * 1000 + + sql = "delete from %s where addtime<'%d' " % ( + table_name, qdata[0]['addtime'],) + writeLog("删除冗余数据:" + str(sql)) + pdb.execute(sql) + return True + + +def dataToDbTpl(tag='btc', input_tf="1m", start_time="2023-1-1", limit=300): + # tag = "btc" + # tf = "1m" + # start_time = "2023-1-1" + # end_time = "2023-3-2" + symbol = tag.lower() + '/USDT' + if not isHasTable(tag, input_tf): + createSql(tag, input_tf) + data = getDataFetch(symbol, start_time, input_tf, limit) + table_name = makeTableName(tag, input_tf) + + dataToDb(table_name, data) + now = time.strftime("%m/%d %H:%M:%S") + data_len = len(data) + writeLog(now + "|数据获取成功!,总数:" + str(data_len)) + else: + findAndUpdateData(tag, input_tf, start_time, limit) + + return True + + +def dataToDbList(input_tf="1m", start_time="2023-1-1"): + + data = getConfigData() + if not 'token' in data: + writeLog("未设置同步配置,需要添加币种!") + return + + for t in data['token']: + dataToDbTpl(t, input_tf, start_time) + time.sleep(4) + + +def dataRunToDb(): + data = getConfigData() + if not 'db' in data: + writeLog("数据库未设置!") + return + + tag = "btc" + symbol = tag.upper() + '/USDT' + + # pprint(exchange.fetch_ticker('XRP/USDT')) + limit_count = 1 + start_time = "2023-1-1" + end_time = "2023-3-2" + tf = "1m" + + if not isHasTable(tag, tf): + createSql(tag, tf) + data = getDataFetch(symbol, start_time, end_time, tf, limit_count) + table_name = makeTableName(tag, tf) + + dataToDb(table_name, data) + china_datetime = str(datetime.now()) + data_len = len(data) + writeLog(china_datetime + ":数据获取成功!,总数:" + str(data_len)) + else: + findAndUpdateData(tag, tf, start_time, 30) + return data + + +def startTask(): + # 任务队列 + try: + while True: + time.sleep(5) + except Exception as e: + time.sleep(60) + startTask() + + +def setDaemon(t): + if sys.version_info.major == 3 and sys.version_info.minor >= 10: + t.daemon = True + else: + t.setDaemon(True) + return t + + +def dataDay(): + try: + while True: + if not isSetDbConf(): + print("数据库未设置!") + else: + start_time = beforeDate(2 * 365) + dataToDbList("1d", start_time=start_time) + time.sleep(3600) + # time.sleep(10) + except Exception as e: + time.sleep(60) + dataDay() + + +def data4h(): + try: + while True: + if not isSetDbConf(): + print("数据库未设置!") + else: + start_time = beforeDate(365) + dataToDbList("4h", start_time=start_time) + time.sleep(10) + except Exception as e: + time.sleep(60) + data4h() + + +def data1h(): + try: + while True: + if not isSetDbConf(): + print("数据库未设置!") + else: + start_time = beforeDate(365) + dataToDbList("1h", start_time=start_time) + time.sleep(10) + except Exception as e: + print(e) + time.sleep(60) + data1h() + + +def data15m(): + try: + while True: + if not isSetDbConf(): + print("数据库未设置!") + else: + start_time = beforeDate(365) + dataToDbList("15m", start_time=start_time) + time.sleep(10) + except Exception as e: + time.sleep(60) + data15m() + + +def data5m(): + try: + while True: + if not isSetDbConf(): + print("数据库未设置!") + else: + start_time = beforeDate(180) + dataToDbList("5m", start_time=start_time) + time.sleep(10) + except Exception as e: + time.sleep(60) + data5m() + + +def data1m(): + try: + while True: + if not isSetDbConf(): + print("数据库未设置!") + else: + start_time = beforeDate(30) + dataToDbList("1m", start_time=start_time) + time.sleep(10) + except Exception as e: + time.sleep(60) + data1m() + + +def longRun(): + + # 日线 + d1_tt = threading.Thread(target=dataDay) + d1_tt = setDaemon(d1_tt) + d1_tt.start() + + # 4h + h4_tt = threading.Thread(target=data4h) + h4_tt = setDaemon(h4_tt) + h4_tt.start() + + # 1h + h1_tt = threading.Thread(target=data1h) + h1_tt = setDaemon(h1_tt) + h1_tt.start() + + # 15m + m15_tt = threading.Thread(target=data15m) + m15_tt = setDaemon(m15_tt) + m15_tt.start() + + # 5m + m5_tt = threading.Thread(target=data5m) + m5_tt = setDaemon(m5_tt) + m5_tt.start() + + # 1m + # h1m_tt = threading.Thread(target=data1m) + # h1m_tt = setDaemon(h1m_tt) + # h1m_tt.start() + + startTask() + + +def dataRun(): + do_num = 0 + while True: + if do_num > 1: + break + do_num = do_num + 1 + dataRunToDb() + time.sleep(6) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'run': + data5m()() + elif func == 'long': + longRun() + elif func == 'demo': + writeLog('111') + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/public_data/data.sh b/plugins/cryptocurrency_trade/ccxt/public_data/data.sh new file mode 100644 index 000000000..974b1bb20 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/public_data/data.sh @@ -0,0 +1,12 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +curPath=`pwd` + +# bash plugins/cryptocurrency_trade/ccxt/public_data/data.sh + +if [ -f ${curPath}/bin/activate ];then + source ${curPath}/bin/activate +fi + +python3 plugins/cryptocurrency_trade/ccxt/public_data/data.py long diff --git a/plugins/cryptocurrency_trade/ccxt/public_data/demo.py b/plugins/cryptocurrency_trade/ccxt/public_data/demo.py new file mode 100644 index 000000000..321929883 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/public_data/demo.py @@ -0,0 +1,19 @@ +import time + +from datetime import datetime +china_datetime = datetime.now() +print(china_datetime) + + +# date() +tt = time.time() - 180 * 86400 + +# print(date("%Y-%m-%d", time.time())) +# t = datetime.strptime(str(time.time() - 180 * 86400), "%Y-%m-%d") +# print(t) + + +d = datetime.fromtimestamp(tt) +# 精确到毫秒 +str1 = d.strftime("%Y-%m-%d") +print(str1) diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/abs.py b/plugins/cryptocurrency_trade/ccxt/strategy/abs.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/common.py b/plugins/cryptocurrency_trade/ccxt/strategy/common.py new file mode 100644 index 000000000..e787f1968 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/common.py @@ -0,0 +1,352 @@ +import ccxt + + +import time +import sys +import json +import os +import glob +import threading + +import pandas as pd +from decimal import Decimal +from pprint import pprint +from datetime import datetime + +# print(os.getcwd()) +sys.path.append(os.getcwd() + "/class/core") +import mw + +exchange = ccxt.poloniex() + + +def calc_ClosingPriceWithOutStopLossPrice(open_price, stype='buy', profit=0.5): + # profit 百分比 % + v = 0 + vf_len = len(str(open_price).split('.')[1]) + if stype == 'buy': + v = open_price * (100 + profit) / 100 + else: + v = open_price * (100 - profit) / 100 + return round(v, vf_len) + + +def roundVal(price, compare_price): + price = float(price) + csplite = str(compare_price).split('.') + clen = len(csplite) + if clen == 2: + vf_len = len(csplite[1]) + return round(price, vf_len) + return price + + +def roundValCeil(price, compare_price): + price = float(price) + csplite = str(compare_price).split('.') + clen = len(csplite) + if clen == 2: + vf_len = len(csplite[1]) - 1 + return round(price, vf_len) + return price + + +def multiply(a1, a2): + v = Decimal(str(a1)) * Decimal(str(a2)) + return v + + +def addition(a1, a2): + v = Decimal(str(a1)) + Decimal(str(a2)) + return v + + +def subtract(a1, a2): + v = Decimal(str(a1)) - Decimal(str(a2)) + return v + + +def divided(a1, a2): + v = Decimal(str(a1)) / Decimal(str(a2)) + return v + + +def calc_ClosingPrice(open_price, stop_loss_price, stype='buy', profit=0.5): + # profit 百分比 % + v = 0 + vf_len = len(str(open_price).split('.')[1]) + if stype == 'buy': + v = open_price * (100 + profit) / 100 + diff = open_price - stop_loss_price + profit_price = open_price + diff * 1.5 + if profit_price > v: + return round(profit_price, vf_len) + else: + v = open_price * (100 - profit) / 100 + diff = stop_loss_price - open_price + profit_price = open_price - diff * 1.5 + if profit_price < v: + return round(profit_price, vf_len) + return round(v, vf_len) + + +def toDateFromInt(time_unix, tf_format="%Y-%m-%d %H:%M:%S", time_zone="Asia/Shanghai"): + # 取格式时间 + import time + os.environ['TZ'] = time_zone + time_str = time.localtime(time_unix) + time.tzset() + + return time.strftime(tf_format, time_str) + + +def getPluginName(): + return 'cryptocurrency_trade' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConfigData(): + cfg_path = getServerDir() + "/data.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def getUserCfgData(): + data = getConfigData() + if 'user' in data: + try: + udata = mw.deDoubleCrypt('mw', data['user']) + udata = json.loads(udata) + return udata + except Exception as e: + pass + return [] + + +def initEx(): + data = getUserCfgData() + # print(data) + if (len(data) > 0): + exchange = ccxt.okex({ + "apiKey": data['app_key'], + "secret": data['secret'], + "password": data['password'], + }) + return exchange + else: + print("初始化失败,检查原因!") + exchange = ccxt.poloniex() + return exchange + + +def toUnixTimeSecond(tf="1m"): + if tf.find("m") > -1: + v = int(tf.replace("m", '')) + return v * 60 + + if tf.find("h") > -1: + v = int(tf.replace("h", '')) + return v * 3600 + + if tf.find("d") > -1: + v = int(tf.replace("d", '')) + return v * 86400 + return 0 + + +def notifyMsg(msg, tf='15m', tag='btc'): + trigger_time = toUnixTimeSecond(tf) + return mw.notifyMessage(msg, '量化交易/' + tag, trigger_time) + + +def makeTableName(input_type="btc", input_tf="1m"): + table_name = "ct_%s_%s" % (input_type, input_tf,) + return table_name + + +def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + log_file = getServerDir() + '/logs/strategy.log' + mw.writeFileLog(log_str, log_file) + return True + + +def writeLogEx(log_str, tag='btc'): + tag = tag.replace('/', '_') + # 各种币的详细API日志 + if __name__ == "__main__": + print(log_str) + + log_file = getServerDir() + '/logs/strategy_' + tag + '.log' + mw.writeFileLog(log_str, log_file) + return True + + +def writeLogErrorEx(log_str, tag='btc'): + tag = tag.replace('/', '_') + # 各种币的详细API日志 + if __name__ == "__main__": + print(log_str) + + log_file = getServerDir() + '/logs/strategy_' + tag + '.err.log' + mw.writeFileLog(log_str, log_file) + return True + + +def pMysqlDb(): + # pymysql + db = mw.getMyORM() + data = getConfigData() + db_data = data['db'] + + # print(db_data) + db.setHost(db_data['db_host']) + db.setPort(db_data['db_port']) + db.setUser(db_data['db_user']) + db.setPwd(db_data['db_pass']) + db.setDbName(db_data['db_name']) + return db + + +def getOnlineData(symbol, input_tf="15m", limit=200): + bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=limit) + df = pd.DataFrame(bars[:], columns=['timestamp', + 'open', 'high', 'low', 'close', 'volume']) + df['dt'] = pd.to_datetime(df['timestamp'], unit='ms') + return df + + +def toDataFrame(data): + # print(data) + data = sorted(data, key=lambda x: x["addtime"], reverse=False) + + dfield = ['addtime', 'open', 'high', 'low', 'close'] + v = {} + for dx in dfield: + v[dx] = [] + + for x in data: + for i in range(len(x)): + field = dfield[i] + # print(i, field) + v[field].append(x[field]) + # pprint(data) + frame = pd.DataFrame(v) + # frame = frame.sort_values(by=['addtime']) + + frame['dt'] = pd.to_datetime(frame['addtime'], unit='s') + frame.set_index('dt', inplace=True) + frame.index = frame.index.tz_localize('UTC').tz_convert('Asia/Shanghai') + return frame + + +def getDataFromDb(tf="1m", tag='btc', limit=1000): + tn = makeTableName(tag, tf) + sql = 'select addtime,open,high,low,close from ' + \ + tn + ' order by addtime desc limit ' + str(limit) + + pdb = pMysqlDb() + fdata = pdb.query(sql) + # print(fdata) + return fdata + + +def getDataFromDb_DF(tf="5m", tag='btc', limit=1000): + data = getDataFromDb(tf, tag, limit) + rdata = toDataFrame(data) + return rdata + + +# 消息模板类 +class MsgTpl(): + + __name = '' + __strategy_name = '' + __time_frame = '' + __content = '' + __open_time = '' + __strategy_dt = '' + + __stop_loss_price = '' + __closing_price = '' + __open_price = '' + + __msg = '' + + def setName(self, name): + self.__name = name + + def setStrategyName(self, name): + self.__strategy_name = name + + def setTimeFrame(self, tf): + self.__time_frame = tf + + def setContent(self, content): + self.__content = content + + def setOpenTime(self, time): + self.__open_time = toDateFromInt(time) + + def setStrategicDt(self, stype='buy'): + if stype == 'buy': + self.__strategy_dt = '做多' + else: + self.__strategy_dt = '做空' + + def setStopLossPrice(self, price): + self.__stop_loss_price = price + + def setClosingPrice(self, price): + self.__closing_price = price + + def setOpenPrice(self, price): + self.__open_price = price + + def setMsg(self, msg): + self.__msg = msg + + def toText(self): + msg = '' + msg += '名称:' + self.__name + "\n" + + if self.__strategy_name != '': + msg += '策略名称:' + self.__strategy_name + "\n" + + if self.__time_frame != '': + msg += '时间周期:' + self.__time_frame + "\n" + + if self.__content != '': + msg += '策略描述:' + self.__content + "\n" + + if self.__open_time != '': + msg += '开盘时间:' + self.__open_time + "\n" + + if self.__strategy_dt != '': + msg += '开仓方向:' + self.__strategy_dt + "\n" + + if self.__open_price != '': + msg += '开仓价:' + str(self.__open_price) + "\n" + + if self.__stop_loss_price != '': + msg += '止损价:' + str(self.__stop_loss_price) + "\n" + + if self.__closing_price != '': + msg += '止盈价:' + str(self.__closing_price) + "\n" + + msg += '发送时间:' + mw.getDateFromNow() + "\n" + + if self.__msg != '': + msg += __msg + return msg diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/func_test.py b/plugins/cryptocurrency_trade/ccxt/strategy/func_test.py new file mode 100644 index 000000000..8b79839e0 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/func_test.py @@ -0,0 +1,57 @@ +# import ccxt +# import talib + +import sys +import os +import time +# import pandas as pd +from pprint import pprint +from decimal import Decimal + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/strategy/func_test.py run + +# common.notifyMsg("任务开始") + + +def toUnixTimeSecond(tf="1m"): + if tf.find("m") > -1: + v = int(tf.replace("m", '')) + return v * 60 + + if tf.find("h") > -1: + v = int(tf.replace("h", '')) + return v * 3600 + + if tf.find("d") > -1: + v = int(tf.replace("d", '')) + return v * 86400 + return 0 + + +def multiply(a1, a2): + v = Decimal(str(a1)) * Decimal(str(a2)) + return v + +# print(toUnixTimeSecond("1d")) + +# f = 19911.2 +# s = common.calc_ClosingPrice(f, 19890.2, 'buy') +# print(s) + +# print(sys.version_info) +# os.environ['TZ'] = 'Europe/London' +# time.tzset() +# t = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) +# print(t) + +# v = multiply(26236.8, 0.003) +# print(float(v)) +# print(v) + +print(float('0.00255234') + float('-0.00000255234')) +v1 = common.addition('0.00255234', '-0.00000255234') +print(v1) diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/gate_100.py b/plugins/cryptocurrency_trade/ccxt/strategy/gate_100.py new file mode 100644 index 000000000..f15b29875 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/gate_100.py @@ -0,0 +1,440 @@ +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/strategy/gate_100.py run +# python3 plugins/cryptocurrency_trade/ccxt/strategy/gate_100.py long + +import ccxt +import talib + +import sys +import os +import time +import json +import pandas as pd +# import pandas_ta as ta +from pprint import pprint +import numpy as np + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +sys.path.append(os.getcwd() + "/class/core") +import mw + +exchange = ccxt.gate({ + "apiKey": '756e1ff80526cb0ac9620c75680fc506', + "secret": '95ac89362056bcf12ce37aabff6eb7ec78185483105f39b4a35e8dc8db8b4d3c', +}) + +exchange.load_markets() + +# 默认开仓数据 +# default_open_num = 10 + +default_open = { + "BTC/USDT": 0.01, + 'ETH/USDT': 30, + 'DOT/USDT': 30, + 'CEL/USDT': 30, +} + +default_sell = { + "BTC/USDT": 0.01, + 'ETH/USDT': 0.01, + 'DOT/USDT': 5, + 'CEL/USDT': 85, +} + + +# 做多开仓 +def onBuyOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + + # 信号只在一个周期内执行一次|start + lock_file = common.getServerDir() + '/signal.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + stype = symbol.replace('/', '_') + '_' + timeframe + trigger_time = common.toUnixTimeSecond(timeframe) + lock_data = json.loads(mw.readFile(lock_file)) + if stype in lock_data: + diff_time = time.time() - lock_data[stype]['do_time'] + if diff_time >= trigger_time: + lock_data[stype]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[stype] = {'do_time': time.time()} + + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + + common.writeLogEx('------做多----------------------------------', symbol) + + default_open_num = default_open[symbol] + # 做多开仓 | 市价 + data = exchange.createMarketBuyOrder( + symbol, default_open_num, {"tdMode": "cross"}) + + common.writeLogEx('开仓数据:', symbol) + common.writeLogEx(json.dumps(data), symbol) + + order_id = data['info']['ordId'] + order_data = exchange.fetchOrder(order_id, symbol) + + common.writeLogEx('订单数据:', symbol) + common.writeLogEx(json.dumps(order_data), symbol) + + # 实际开场平均价 + open_price = order_data['info']['avgPx'] + + # 做多-止损价大于开仓价,重设止损价 + if float(stop_loss_price) <= float(open_price): + stop_loss_price = float(open_price) * float((1 - 0.003)) + + # property_val = float(order_data['info']['accFillSz']) + float(order_data['info']['fee']) + property_val = common.addition( + order_data['info']['accFillSz'], order_data['info']['fee']) + property_val = float(property_val) + + # 可平仓的数量 + property_val = common.roundValCeil( + property_val, order_data['info']['accFillSz']) + + common.writeLogEx('可平仓资产:' + str(property_val), symbol) + + # 止盈价 + diff = float(open_price) - float(stop_loss_price) + closing_price_c = float(open_price) + (diff * 2) + closing_price = float(open_price) * float((1 + profit)) + # 选择盈利多的 + if closing_price_c > closing_price: + closing_price = closing_price_c + + closing_price = common.roundVal(closing_price, stop_loss_price) + # stop_loss_price = common.roundVal(stop_loss_price, open_price) + common.writeLogEx('实际开仓价:' + str(open_price), symbol) + common.writeLogEx('止盈价:' + str(closing_price), symbol) + common.writeLogEx('止损价:' + str(stop_loss_price), symbol) + + # 设置 - 止损价/止盈价 + sl_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + + # 止损条件单 + common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'sell', + property_val, stop_loss_price, sl_exchange_params]), symbol) + sl_cond_data = exchange.create_order( + symbol, 'limit', 'sell', property_val, stop_loss_price, sl_exchange_params) + + common.writeLogEx('止损价数据:', symbol) + common.writeLogEx(json.dumps(sl_cond_data), symbol) + + # 止赢条件单 + tp_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + + common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'sell', + property_val, closing_price, tp_exchange_params]), symbol) + tp_cond_data = exchange.create_order( + symbol, 'limit', 'sell', property_val, closing_price, tp_exchange_params) + + common.writeLogEx('止赢数据:', symbol) + common.writeLogEx(json.dumps(tp_cond_data), symbol) + + common.writeLogEx('------做多 end----------------------------------', symbol) + return True, open_price, closing_price + + +def onBuyOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + # 做多开仓 + # profit 百分比 + try: + return onBuyOrderTry(symbol, stop_loss_price, profit, timeframe) + except Exception as e: + common.writeLogErrorEx(mw.getTracebackInfo(), symbol) + return False, 0, 0 + + +def onSellOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + + # 信号只在一个周期内执行一次|start + lock_file = common.getServerDir() + '/signal.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + stype = symbol.replace('/', '_') + '_' + timeframe + trigger_time = common.toUnixTimeSecond(timeframe) + lock_data = json.loads(mw.readFile(lock_file)) + if stype in lock_data: + diff_time = time.time() - lock_data[stype]['do_time'] + if diff_time >= trigger_time: + lock_data[stype]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[stype] = {'do_time': time.time()} + + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + common.writeLogEx('------做空----------------------------------', symbol) + + # 计算借币卖币多多少,以USDT为基准 + # sell_num = float(default_open_num) / float(stop_loss_price) + # sell_num = round(sell_num, 8) + + sell_num = default_sell[symbol] + # 做空开仓 | 市价 + data = exchange.createMarketSellOrder( + symbol, sell_num, {"tdMode": "cross", 'ccy': "USDT"}) + + common.writeLogEx('开仓数据:', symbol) + common.writeLogEx(json.dumps(data), symbol) + + order_id = data['info']['ordId'] + order_data = exchange.fetchOrder(order_id, symbol) + + common.writeLogEx('订单数据:', symbol) + common.writeLogEx(json.dumps(order_data), symbol) + + # 实际开场平均价 + open_price = order_data['info']['avgPx'] + + common.writeLogEx('可平仓资产:' + str(sell_num), symbol) + + # 做空-止损价小于开仓价,重设止损价 + if float(stop_loss_price) <= float(open_price): + stop_loss_price = float(open_price) * float((1 + 0.003)) + + # 止盈价 + diff = float(stop_loss_price) - float(open_price) + closing_price_c = float(open_price) - (diff * 2) + closing_price = float(open_price) * float((1 - profit)) + # 选择盈利多的 + if closing_price_c < closing_price: + closing_price = closing_price_c + + closing_price = common.roundVal(closing_price, open_price) + stop_loss_price = common.roundVal(stop_loss_price, open_price) + common.writeLogEx('实际开仓价:' + str(open_price), symbol) + common.writeLogEx('止盈价:' + str(closing_price), symbol) + common.writeLogEx('止损价:' + str(stop_loss_price), symbol) + + # 设置 - 止损价 + sl_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + + sl_amount = common.multiply(stop_loss_price, sell_num) + # 解决平仓时,未全部平仓 + sl_amount = common.addition(sl_amount, 0.1) + common.writeLogEx('止损总价值:' + str(sl_amount), symbol) + common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'buy', float( + sl_amount), stop_loss_price, sl_exchange_params]), symbol) + sl_cond_data = exchange.create_order( + symbol, 'limit', 'buy', sl_amount, stop_loss_price, sl_exchange_params) + + common.writeLogEx('止损价数据:', symbol) + common.writeLogEx(json.dumps(sl_cond_data), symbol) + + tp_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + + # 设置 -止盈价 + tp_amount = common.multiply(closing_price, sell_num) + # 解决平仓时,未全部平仓 + tp_amount = common.addition(tp_amount, 0.1) + common.writeLogEx('止盈总价值:' + str(tp_amount), symbol) + common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'buy', float( + tp_amount), closing_price, tp_exchange_params]), symbol) + tp_cond_data = exchange.create_order( + symbol, 'limit', 'buy', tp_amount, closing_price, tp_exchange_params) + + common.writeLogEx('止盈价数据:', symbol) + common.writeLogEx(json.dumps(tp_cond_data), symbol) + + common.writeLogEx('------做空 end----------------------------------', symbol) + return True, open_price, closing_price + + +def onSellOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + # 做空开仓 + # profit 百分比 + try: + return onSellOrderTry(symbol, stop_loss_price, profit, timeframe) + except Exception as e: + common.writeLogErrorEx(mw.getTracebackInfo(), symbol) + return False, 0, 0 + + +def getOnlineData(symbol, input_tf="15m", limit=230): + bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=limit) + df = pd.DataFrame(bars[:], columns=['timestamp', + 'open', 'high', 'low', 'close', 'volume']) + df['dt'] = pd.to_datetime(df['timestamp'], unit='ms') + df.set_index('dt', inplace=True) + df.index = df.index.tz_localize('UTC').tz_convert('Asia/Shanghai') + return df + + +def isKdj(last, last_pre): + # 判断是否是金叉 + if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0): + return True + return False + + +def isDeadFork(last, last_pre): + # 判断是否是死叉 + if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0): + return True + return False + + +def getMACD(df, lenght=-60): + if len(df['close'].values) < 100: + return False, None + + close_p = df['close'].values + df['dif'], df['dea'], df['macd'] = talib.MACD(close_p, + fastperiod=12, + slowperiod=26, + signalperiod=9) + return df[lenght:] + + +def getEMA(df): + close_p = df['close'].values + df['ema'] = talib.EMA(np.array(close_p), timeperiod=200) + return df + + +def doneMacd(data, tag, timeframe): + data = getEMA(data) + + ma_data = getMACD(data) + # alen = len(data) + # print(ma_data) + + t_data = ma_data.tail(3) + print(t_data) + + last_pre = t_data.iloc[0] + last = t_data.iloc[1] + + print(last) + # print(last.index) + + # print('close:', last['close'], 'ema:', last['ema']) + + obj = common.MsgTpl() + symbol = tag.upper() + '/USDT' + obj.setName(symbol) + obj.setStrategyName("MACD检查") + obj.setTimeFrame(timeframe) + obj.setOpenTime(last['timestamp'] / 1000) + + now_data = t_data.iloc[2] + # print('now_data:', now_data) + + # msg = obj.toText() + # print(msg) + + if isKdj(last, last_pre) and last['close'] > last['ema']: + # if isKdj(last, last_pre): + + # closing_price = common.calc_ClosingPrice( + # now_data['close'], last_pre['low'], 'buy') + # # print('closing_price:', closing_price) + + # 做多止损点 + stop_loss_price = last_pre['low'] + buy_status, open_price, closing_price = onBuyOrder( + symbol, stop_loss_price, 0.005, timeframe) + + if buy_status: + obj.setStrategicDt('buy') + # 做多止损点 + obj.setStopLossPrice(str(stop_loss_price)) + obj.setOpenPrice(str(open_price)) + obj.setClosingPrice(str(closing_price)) + obj.setContent("检查到金叉状态") + msg = obj.toText() + print(msg) + common.notifyMsg(msg, timeframe, tag) + common.writeLog(msg) + + if isDeadFork(last, last_pre) and last['close'] < last['ema']: + # if isDeadFork(last, last_pre): + # closing_price = common.calc_ClosingPrice( + # now_data['close'], last_pre['high'], 'sell') + + # 做空止损点 + stop_loss_price = last_pre['high'] + sell_status, open_price, closing_price = onSellOrder( + symbol, stop_loss_price, 0.005, timeframe) + if sell_status: + obj.setStopLossPrice(str(stop_loss_price)) + obj.setOpenPrice(str(open_price)) + obj.setClosingPrice(str(closing_price)) + obj.setStrategicDt('sell') + obj.setContent("检查到死叉状态") + msg = obj.toText() + print(msg) + common.notifyMsg(msg, timeframe, tag) + common.writeLog(msg) + + +def mainProcess(tag, timeframe='15m'): + symbol = tag.upper() + '/USDT' + data = getOnlineData(symbol, timeframe) + + doneMacd(data, tag, timeframe) + + +def foreachList(): + tag_list = ['btc'] + + for tag in tag_list: + mainProcess(tag, '15m') + time.sleep(1) + + +def longRun(): + while True: + foreachList() + time.sleep(3) + + +def debug(): + while True: + mainProcess('xrp', '1m') + time.sleep(3) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'long': + longRun() + elif func == 'run': + debug() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/hammer_robot.py b/plugins/cryptocurrency_trade/ccxt/strategy/hammer_robot.py new file mode 100644 index 000000000..c03b643cf --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/hammer_robot.py @@ -0,0 +1,138 @@ +import ccxt +import talib + +import sys +import os +import time +import pandas as pd +import pandas_ta as ta +from pprint import pprint + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/strategy/hammer_robot.py run + + +pd.set_option('display.max_rows', None) + +exchange = common.initEx() + +exchange.load_markets() + +entry_rsi = 30 +exit_rsi = 40 + + +symbol = 'XRP/USDT' +timeframe = '15m' + +tf_mult = exchange.parse_timeframe(timeframe) * 1000 + + +def indicators(data): + + data['rsi'] = data.ta.rsi(length=10) + data['ema'] = data.ta.ema(length=200) + + # close_p = data['close'].values + # data['rsi'] = talib.RSI(close_p, timeperiod=10) + # data['ema'] = talib.EMA(close_p, timeperiod=200) + return data + + +def check_buy_sell_signals(df): + last_row_index = len(df.index) - 1 + lastest_rsi = round(df['rsi'].iloc[-1], 2) + lastest_price = round(df['close'].iloc[-1], 5) + lastest_ema = round(df['ema'].iloc[-1], 5) + lastest_ts = df['timestamp'].iloc[-1] + + msg = "lastest_rsi:" + str(lastest_rsi) + " < entry_rsi:" + str(entry_rsi) + msg += ",lastest_price:" + \ + str(lastest_price) + " > lastest_ema:" + str(lastest_ema) + print(msg) + + long_cond = (lastest_rsi < entry_rsi) and (lastest_price > lastest_ema) + if long_cond: + print("买入") + order = exchange.create_market_buy_order(symbol, 1) + + closed_orders = exchange.fetchClosedOrders(symbol, limit=2) + if len(closed_orders) > 0: + print("closed_orders:", closed_orders) + most_recent_closed_order = closed_orders[-1] + diff = lastest_ts - most_recent_closed_order['timestamp'] + last_buy_signal_cnt = int(diff / tf_mult) + + exit_cond = (lastest_rsi > exit_rsi) and (last_buy_signal_cnt > 10) + if exit_cond: + print("卖出") + order = exchange.create_market_sell_order(symbol, 1) + return + + +def get_hammer(df, lenght): + # 影线要大于body的多少倍 + factor = 2 + hl_range = df['high'] - df['low'] + + body_hi = df.apply(lambda x: max(x['close'], x['open']), axis=1) + body_lo = df.apply(lambda x: min(x['close'], x['open']), axis=1) + body = body_hi - body_lo + + body_avg = ta.ema(body, lenght=lenght) + small_body = body < body_avg + + # 上下影线站body的百分比 + shadow_percent = 10 + + # 上影线 + up_shadow = df['high'] - body_hi + dn_shadow = body_lo - df['low'] + has_up_shadow = up_shadow > shadow_percent / 100 * body + has_dn_shadow = dn_shadow > shadow_percent / 100 * body + + downtrend = df['close'] < ta.ema(df['close'], 50) + bullish_hammer = downtrend & small_body & (body > 0) & ( + dn_shadow >= factor * body) & (has_up_shadow == False) + return bullish_hammer + + +def runBot(): + bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=200) + df = pd.DataFrame(bars[:], columns=['timestamp', + 'open', 'high', 'low', 'close', 'volume']) + + # format='%Y-%m-%d %H:%M:%S', + df['dt'] = pd.to_datetime( + df['timestamp'], unit="ms") + + df['hammer'] = get_hammer(df, 10) + + lastest_hammer = df.iloc[-1, -1] + lastest_price = df.iloc[-1, 0] + + print("lastest_price:" + str(lastest_price)) + print("lastest_hammer:" + str(lastest_hammer)) + print(df.tail()) + + if lastest_hammer: + print("购买,做多") + notifyMsg("购买,做多") + + +def longRunBot(): + common.notifyMsg("任务开始") + while True: + runBot() + time.sleep(10) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'run': + longRunBot() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/info.json b/plugins/cryptocurrency_trade/ccxt/strategy/info.json new file mode 100644 index 000000000..2e5071221 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/info.json @@ -0,0 +1,33 @@ +[ + { + "id":"01", + "name": "MACD-KDJ策略检测|1h", + "file": "macd_kdj.py" + }, + { + "id":"02", + "name": "通知测试|60s", + "file": "notify_demo.py" + }, + { + "id":"03", + "name": "okex|macd交易策略|15m", + "file": "online_macd_trade.py" + }, + { + "id":"04", + "name": "okex|动量交易策略|5m", + "file": "momentun_trade.py" + }, + { + "id":"05", + "name": "gate.io|动量交易策略|5m", + "file": "gate_100.py" + }, + { + "id":"06", + "name": "仅提醒|Vega交易策略|5m", + "file": "vega_trade.py" + } + +] \ No newline at end of file diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/macd_kdj.py b/plugins/cryptocurrency_trade/ccxt/strategy/macd_kdj.py new file mode 100644 index 000000000..de8f023b8 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/macd_kdj.py @@ -0,0 +1,121 @@ +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/strategy/macd_kdj.py run +# python3 plugins/cryptocurrency_trade/ccxt/strategy/macd_kdj.py long + + +import ccxt +import talib +import pandas as pd +import time + +import sys +import os +from pprint import pprint + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +sys.path.append(os.getcwd() + "/class/core") +import mw + + +pd.set_option('display.max_rows', None) + +# import warnings +# warnings.filterwarnings('error') + + +def getMACD(df, lenght=-60): + if len(df['close'].values) < 100: + return False, None + + close_p = df['close'].values + df['dif'], df['dea'], df['macd'] = talib.MACD(close_p, + fastperiod=12, + slowperiod=26, + signalperiod=9) + return True, df[lenght:] + + +def isKdj(last, last_pre): + # 判断是否是金叉 + if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0): + return True + return False + + +def isDeadFork(last, last_pre): + # 判断是否是死叉 + if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0): + return True + return False + + +def checkData(): + tag = 'btc' + tf_frame = '1h' + + data = common.getDataFromDb_DF(tf_frame, tag, 300) + + # print(data) + b, r = getMACD(data) + if b: + # rlen = len(r) + # r = r.copy() + # r['dt'] = pd.to_datetime( + # r['addtime'], unit='s') + + # r['dt'] = r['dt'].dt.tz_convert('Asia/Shanghai') + # print(r) + rlen = len(r) + + t_data = r.tail(2) + # print(r.tail(2)) + + last_pre = t_data.iloc[0] + last = t_data.iloc[1] + + # print(last_pre) + # print(last) + # print(last_pre['addtime']) + # print(last_pre['open']) + # print(last_pre['high']) + # print(last_pre['low']) + # print(last_pre['close']) + + # print(last['addtime']) + # print(last['open']) + # print(last['high']) + # print(last['low']) + # print(last['close']) + + now = mw.getDateFromNow() + if isKdj(last, last_pre): + msg = now + "|{}|{}|检查到金叉状态!".format(tag, tf_frame) + common.notifyMsg(msg, tf_frame, tag) + common.writeLog(msg) + + if isDeadFork(last, last_pre): + msg = now + "|{}|{}|检查到死叉状态!".format(tag, tf_frame) + common.notifyMsg(msg, tf_frame, tag) + common.writeLog(msg) + + +def run(): + checkData() + + +def longRun(): + while True: + checkData() + time.sleep(3) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'run': + run() + elif func == 'long': + longRun() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/momentun_trade.py b/plugins/cryptocurrency_trade/ccxt/strategy/momentun_trade.py new file mode 100644 index 000000000..fe798278e --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/momentun_trade.py @@ -0,0 +1,505 @@ +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/strategy/momentun_trade.py run +# python3 plugins/cryptocurrency_trade/ccxt/strategy/momentun_trade.py long + + +# 动量策略交易 + +import ccxt +import talib + +import sys +import os +import time +import json +import pandas as pd +# import pandas_ta as ta +from pprint import pprint +import numpy as np + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +sys.path.append(os.getcwd() + "/class/core") +import mw + +exchange = common.initEx() +exchange.load_markets() + +# 默认开仓数据 +# default_open_num = 70 +# default_sell_num = 0.003 + +default_open = { + 'BTC/USDT': 30, + 'XRP/USDT': 30, +} + +default_sell = { + 'BTC/USDT': 0.001, + 'XRP/USDT': 100, +} + + +# 做多开仓 +def onBuyOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + + # 信号只在一个周期内执行一次|start + lock_file = common.getServerDir() + '/signal.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + stype = symbol.replace('/', '_') + '_' + timeframe + trigger_time = common.toUnixTimeSecond(timeframe) + lock_data = json.loads(mw.readFile(lock_file)) + if stype in lock_data: + diff_time = time.time() - lock_data[stype]['do_time'] + if diff_time >= trigger_time: + lock_data[stype]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[stype] = {'do_time': time.time()} + + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + + common.writeLogEx('------做多----------------------------------', symbol) + + default_open_num = default_open[symbol] + # 做多开仓 | 市价 + data = exchange.createMarketBuyOrder( + symbol, default_open_num, {"tdMode": "cross"}) + + common.writeLogEx('开仓数据:', symbol) + common.writeLogEx(json.dumps(data), symbol) + + order_id = data['info']['ordId'] + order_data = exchange.fetchOrder(order_id, symbol) + + common.writeLogEx('订单数据:', symbol) + common.writeLogEx(json.dumps(order_data), symbol) + + # 实际开场平均价 + open_price = order_data['info']['avgPx'] + + # 修正小数点位数 + open_price = common.roundVal(open_price, stop_loss_price) + common.writeLogEx('实际开仓价:' + str(open_price), symbol) + + # 做多-止损价大于开仓价,重设止损价 + if float(stop_loss_price) <= float(open_price): + stop_loss_price = float(open_price) * float((1 - 0.003)) + + # property_val = float(order_data['info']['accFillSz']) + float(order_data['info']['fee']) + + property_val = common.addition(order_data['info'][ + 'accFillSz'], order_data['info']['fee']) + property_val = float(property_val) + # 可平仓的数量 + # property_val = common.roundValCeil( + # property_val, order_data['info']['accFillSz']) + + common.writeLogEx('可平仓资产:' + str(property_val), symbol) + + # 止盈价 + diff = float(open_price) - float(stop_loss_price) + closing_price = float(open_price) + (diff * 1.5) + # closing_price = float(open_price) * float((1 + profit)) + # # 选择盈利多的 + # if closing_price_c > closing_price: + # closing_price = closing_price_c + + closing_price = common.roundVal(closing_price, open_price) + stop_loss_price = common.roundVal(stop_loss_price, open_price) + common.writeLogEx('止盈价:' + str(closing_price), symbol) + common.writeLogEx('止损价:' + str(stop_loss_price), symbol) + + # 设置 - 止损价/止盈价 + sl_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + + # 止损条件单 + common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'sell', + property_val, stop_loss_price, sl_exchange_params]), symbol) + sl_cond_data = exchange.create_order( + symbol, 'limit', 'sell', property_val, stop_loss_price, sl_exchange_params) + + common.writeLogEx('止损价数据:', symbol) + common.writeLogEx(json.dumps(sl_cond_data), symbol) + + # 止赢条件单 + tp_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'sell', + property_val, closing_price, tp_exchange_params]), symbol) + tp_cond_data = exchange.create_order( + symbol, 'limit', 'sell', property_val, closing_price, tp_exchange_params) + + common.writeLogEx('止盈数据:', symbol) + common.writeLogEx(json.dumps(tp_cond_data), symbol) + + common.writeLogEx('------做多 end----------------------------------', symbol) + return True, open_price, closing_price + + +def onBuyOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + # 做多开仓 + # profit 百分比 + try: + return onBuyOrderTry(symbol, stop_loss_price, profit, timeframe) + except Exception as e: + common.writeLogErrorEx(mw.getTracebackInfo(), symbol) + return False, 0, 0 + + +def onSellOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + + # 信号只在一个周期内执行一次|start + lock_file = common.getServerDir() + '/signal.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + stype = symbol.replace('/', '_') + '_' + timeframe + trigger_time = common.toUnixTimeSecond(timeframe) + lock_data = json.loads(mw.readFile(lock_file)) + if stype in lock_data: + diff_time = time.time() - lock_data[stype]['do_time'] + if diff_time >= trigger_time: + lock_data[stype]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[stype] = {'do_time': time.time()} + + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + common.writeLogEx('------做空----------------------------------', symbol) + + # 计算借币卖币多多少,以USDT为基准 + # sell_num = float(default_open_num) / float(stop_loss_price) + # sell_num = round(sell_num, 8) + # sell_num = default_sell_num + sell_num = default_sell[symbol] + + # 做空开仓 | 市价 + data = exchange.createMarketSellOrder( + symbol, sell_num, {"tdMode": "cross", 'ccy': "USDT"}) + + common.writeLogEx('开仓数据:', symbol) + common.writeLogEx(json.dumps(data), symbol) + + order_id = data['info']['ordId'] + order_data = exchange.fetchOrder(order_id, symbol) + + common.writeLogEx('订单数据:', symbol) + common.writeLogEx(json.dumps(order_data), symbol) + + # 实际开场平均价 + open_price = order_data['info']['avgPx'] + + # 修正 + open_price = common.roundVal(open_price, stop_loss_price) + + common.writeLogEx('实际开仓价:' + str(open_price), symbol) + common.writeLogEx('可平仓资产:' + str(sell_num), symbol) + + # 做空-止损价小于开仓价,重设止损价 + if float(stop_loss_price) <= float(open_price): + stop_loss_price = float(open_price) * float((1 + 0.003)) + + # 止盈价 + diff = float(stop_loss_price) - float(open_price) + closing_price = float(open_price) - (diff * 1.5) + # closing_price = float(open_price) * float((1 - profit)) + # 选择盈利多的 + # if closing_price_c < closing_price: + # closing_price = closing_price_c + + closing_price = common.roundVal(closing_price, open_price) + stop_loss_price = common.roundVal(stop_loss_price, open_price) + + common.writeLogEx('止盈价:' + str(closing_price), symbol) + common.writeLogEx('止损价:' + str(stop_loss_price), symbol) + + # 设置 - 止损价 + sl_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + + sl_amount = common.multiply(stop_loss_price, sell_num) + # 解决平仓时,未全部平仓 + sl_amount = common.addition(sl_amount, 0.1) + common.writeLogEx('止损总价值:' + str(sl_amount), symbol) + common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'buy', float( + sl_amount), stop_loss_price, sl_exchange_params]), symbol) + sl_cond_data = exchange.create_order( + symbol, 'limit', 'buy', float(sl_amount), stop_loss_price, sl_exchange_params) + + common.writeLogEx('止损价数据:', symbol) + common.writeLogEx(json.dumps(sl_cond_data), symbol) + + tp_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + + # 设置 -止盈价 + # tp_amount = closing_price * sell_num + tp_amount = common.multiply(closing_price, sell_num) + # 解决平仓时,未全部平仓 + tp_amount = common.addition(tp_amount, 0.1) + common.writeLogEx('止盈总价值:' + str(tp_amount), symbol) + common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'buy', float( + tp_amount), closing_price, tp_exchange_params]), symbol) + tp_cond_data = exchange.create_order( + symbol, 'limit', 'buy', float(tp_amount), closing_price, tp_exchange_params) + + common.writeLogEx('止盈价数据:', symbol) + common.writeLogEx(json.dumps(tp_cond_data), symbol) + + common.writeLogEx('------做空 end----------------------------------', symbol) + return True, open_price, closing_price + + +def onSellOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + # 做空开仓 + # profit 百分比 + try: + return onSellOrderTry(symbol, stop_loss_price, profit, timeframe) + except Exception as e: + common.writeLogErrorEx(mw.getTracebackInfo(), symbol) + return False, 0, 0 + + +def getOnlineData(symbol, input_tf="15m", limit=230): + bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=limit) + df = pd.DataFrame(bars[:], columns=['timestamp', + 'open', 'high', 'low', 'close', 'volume']) + df['dt'] = pd.to_datetime(df['timestamp'], unit='ms') + df.set_index('dt', inplace=True) + df.index = df.index.tz_localize('UTC').tz_convert('Asia/Shanghai') + return df + + +def isKdj(last, last_pre): + # 判断是否是金叉 + if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0): + return True + return False + + +def isDeadFork(last, last_pre): + # 判断是否是死叉 + if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0): + return True + return False + + +def getMACD(df, lenght=-60): + if len(df['close'].values) < 100: + return False, None + + close_p = df['close'].values + df['dif'], df['dea'], df['macd'] = talib.MACD(close_p, + fastperiod=12, + slowperiod=26, + signalperiod=9) + return df[lenght:] + + +def getTarget(df): + close = df['close'].values + df['ema'] = talib.EMA(np.array(close), timeperiod=10) + df['ema_200'] = talib.EMA(np.array(close), timeperiod=200) + df['ma'] = talib.MA(close, timeperiod=10) + df['rsi'] = talib.RSI(close, timeperiod=14) + return df + + +# 多头信号 +def isBuyCrondSignal(last): + if last['ema'] > last['ma'] and (last['rsi'] > 50 and last['rsi'] < 70) and last['low'] >= last['ma'] and last['close'] > last['open']: + return True + return False + + +def isBuyFristSignal(data): + data = data.sort_values(by=['timestamp'], ascending=False) + # print(data) + data_len = len(data) + signal_num = 0 + + first_data = data.iloc[1] + # print(1, first_data['close'], first_data['ema']) + is_buy_signal = isBuyCrondSignal(first_data) + + for x in range(2, data_len): + tmp = data.iloc[x] + # print(data.iloc[x]) + # print(x, tmp['close'], tmp['ema']) + if isBuyCrondSignal(tmp): + signal_num = + 1 + + # print('signal_num:', signal_num) + + if (tmp['ema'] < tmp['ma']): + break + + if str(tmp['ema']) == 'nan': + break + + print("is_buy_signal:", is_buy_signal, 'signal_num:', signal_num) + if is_buy_signal and signal_num == 0: + return True + + return False + + +# 空头信号 +def isSellCrondSignal(last): + if last['ema'] < last['ma'] and (last['rsi'] > 30 and last['rsi'] < 50) and last['high'] <= last['ema'] and last['close'] < last['open']: + return True + return False + + +def isSellFristSignal(data): + data = data.sort_values(by=['timestamp'], ascending=False) + # print(data) + data_len = len(data) + signal_num = 0 + + first_data = data.iloc[1] + # print(1, first_data['close'], first_data['ema']) + is_sell_signal = isSellCrondSignal(first_data) + + for x in range(2, data_len): + tmp = data.iloc[x] + # print(data.iloc[x]) + # print(x, tmp['close'], tmp['ema']) + if isSellCrondSignal(tmp): + signal_num = + 1 + + # print('signal_num:', signal_num) + + if (tmp['ema'] < tmp['ma']): + break + + if str(tmp['ema']) == 'nan': + break + + print("is_sell_signal:", is_sell_signal, 'signal_num:', signal_num) + if is_sell_signal and signal_num == 0: + return True + + return False + + +def monentunTrade(data, tag, timeframe): + data = getTarget(data) + # print(data) + + key_data = data.tail(3) + print(key_data) + last_pre = key_data.iloc[0] + last = key_data.iloc[1] + + obj = common.MsgTpl() + symbol = tag.upper() + '/USDT' + obj.setName(symbol) + obj.setStrategyName("动量交易策略") + obj.setTimeFrame(timeframe) + obj.setOpenTime(last['timestamp'] / 1000) + + # 买入信号,并且收盘价要大于200 ema + if isBuyFristSignal(data) and last['close'] > last['ema_200']: + obj.setStrategicDt('buy') + + # 做多止损点 + stop_loss_price = last['ma'] + stop_loss_price = common.roundVal(stop_loss_price, last['open']) + buy_status, open_price, closing_price = onBuyOrder( + symbol, stop_loss_price, 0.005, timeframe) + + if buy_status: + # 做多止损点 + obj.setStopLossPrice(str(stop_loss_price)) + obj.setOpenPrice(str(open_price)) + obj.setClosingPrice(str(closing_price)) + obj.setContent("动量交易策略做多!") + msg = obj.toText() + print(msg) + common.notifyMsg(msg, timeframe, tag) + common.writeLog(msg) + + # 卖出信号,并且收盘价要小于200 ema + # if isSellFristSignal(data) and last['close'] < last['ema_200']: + # obj.setStrategicDt('sell') + + # stop_loss_price = last['ma'] + # stop_loss_price = common.roundVal(stop_loss_price, last['open']) + # sell_status, open_price, closing_price = onSellOrder( + # symbol, stop_loss_price, 0.005, timeframe) + # if sell_status: + # obj.setStopLossPrice(str(stop_loss_price)) + # obj.setOpenPrice(str(open_price)) + # obj.setClosingPrice(str(closing_price)) + # obj.setStrategicDt('sell') + # obj.setContent("动量交易策略作空!") + # msg = obj.toText() + # print(msg) + # common.notifyMsg(msg, timeframe, tag) + # common.writeLog(msg) + + +def mainProcess(tag, timeframe='15m'): + symbol = tag.upper() + '/USDT' + data = getOnlineData(symbol, timeframe) + + monentunTrade(data, tag, timeframe) + + +def foreachList(): + tag_list = ['btc', 'xrp'] + for tag in tag_list: + mainProcess(tag, '15m') + time.sleep(1) + + +def longRun(): + while True: + foreachList() + time.sleep(1) + + +def debug(): + while True: + mainProcess('btc', '5m') + time.sleep(1) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'long': + longRun() + elif func == 'run': + debug() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/notify_demo.py b/plugins/cryptocurrency_trade/ccxt/strategy/notify_demo.py new file mode 100644 index 000000000..72474e729 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/notify_demo.py @@ -0,0 +1,48 @@ +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/strategy/notify_demo.py run +# python3 plugins/cryptocurrency_trade/ccxt/strategy/notify_demo.py long + + +import ccxt +import talib +import pandas as pd +import time + +import sys +import os +from pprint import pprint + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +sys.path.append(os.getcwd() + "/class/core") +import mw + + +def run(): + print('debug') + + +def longRun(): + while True: + + obj = common.MsgTpl() + obj.setName("通知测试") + obj.setStrategyName("无") + obj.setTimeFrame("1m") + obj.setContent("60s通知测试") + msg = obj.toText() + print(msg) + common.notifyMsg(msg, '1m', 'debug') + common.writeLog(msg) + time.sleep(3) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'run': + run() + if func == 'long': + longRun() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py b/plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py new file mode 100644 index 000000000..50b6d4ac7 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py @@ -0,0 +1,294 @@ +# cd /www/server/mdserver-web && source bin/activate + +# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_buy_open +# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_buy_close + + +# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_sell_open +# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_sell_cloe + +# 获取仓位数据 +# python3 plugins/cryptocurrency_trade/ccxt/strategy/on_g_test.py t_get_trade + +# API地址 +# https://www.gate.io/docs/developers/apiv4/zh_CN/#api + +import ccxt +import talib + +import sys +import os +import time +import pandas as pd +# import pandas_ta as ta +from pprint import pprint +import numpy as np + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +sys.path.append(os.getcwd() + "/class/core") +import mw + +exchange = ccxt.gate({ + "apiKey": '756e1ff80526cb0ac9620c75680fc506', + "secret": '95ac89362056bcf12ce37aabff6eb7ec78185483105f39b4a35e8dc8db8b4d3c', +}) + +exchange.load_markets() + + +def t_get_trade(): + data = exchange.fetchPositions() + print(data) + + +def btc_test(): + closing_price = 24599.9 + stop_loss_price = 24740.4 + + # --------------------------- + stop_loss_args = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + print('---------止损价 执行 START ----------------------------------') + + sl_amount = stop_loss_price * 0.001 + print(amount) + data = exchange.create_order( + 'BTC/USDT', 'limit', 'buy', sl_amount, stop_loss_price, stop_loss_args) + print(data) + print('---------止损价 执行 END ----------------------------------') + + print('---------止盈价 执行 START ----------------------------------') + + closing_price_args = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + + cp_amount = closing_price * 0.001 + print(amount) + data = exchange.create_order( + 'BTC/USDT', 'limit', 'buy', cp_amount, closing_price, closing_price_args) + print(data) + print('---------止盈价 执行 END ----------------------------------') + + +def testK_buy_open_sz(): + # 做多开仓 + + data = exchange.fetchTicker('BTC/USDT') + print(data) + # data = exchange.createMarketBuyOrder('DOT/USDT', 1, {"tdMode": "cross"}) + # print(data) + # print(type(data)) + + +def testK_buy_open(): + # 做多开仓 + + # print(dir(exchange)) + + # exchange['options']['createMarketBuyOrderRequiresPrice'] = False + data = exchange.fetch_ticker('BTC/USDT') + print("now price:", data['ask']) + + amount = 0.001 + price = data['ask'] + cost = amount * float(price) + print('total price:', cost) + print('amount price:', amount) + # a_amount = round(amount / data['ask'], 5) + # print('amount :', amount) + # print('amount :', str(amount)) + + # + data = exchange.createOrder( + "BTC/USDT", type="limit", side="buy", amount=amount, price=price, params={'account': "cross_margin"}) + # data = exchange.createMarketBuyOrder('BTC/USDT', amount) + print(data) + print(type(data)) + + # 数据 + # {'info': {'clOrdId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'ordId': '554437054223302656', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'e847386590ce4dBC'}, 'id': '554437054223302656', 'clientOrderId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': None, 'fees': []} + + # 查询委托单 + # order_id = data['info']['ordId'] + # order_id = '554437054223302656' + # data = exchange.fetchOrder(order_id, 'BTC/USDT') + # print(data['info']) + # {'info': {'accFillSz': '0.190548', 'algoClOrdId': '', 'algoId': '', 'avgPx': '5.248', 'cTime': '1678441709955', 'cancelSource': '', 'cancelSourceReason': '', 'category': 'normal', 'ccy': 'USDT', 'clOrdId': 'e847386590ce4dBCeaddfd1494dc7080', 'fee': '-0.000190548', 'feeCcy': 'DOT', 'fillPx': '5.248', 'fillSz': '0.190548', 'fillTime': '1678441709957', 'instId': 'DOT-USDT', 'instType': 'MARGIN', 'lever': '10', 'ordId': '554359943143833600', 'ordType': 'market', 'pnl': '0', 'posSide': 'net', 'px': '', 'quickMgnType': '', 'rebate': '0', 'rebateCcy': 'USDT', 'reduceOnly': 'false', 'side': 'buy', 'slOrdPx': '', 'slTriggerPx': '', 'slTriggerPxType': '', 'source': '', 'state': 'filled', 'sz': '1', 'tag': 'e847386590ce4dBC', 'tdMode': 'cross', 'tgtCcy': '', 'tpOrdPx': '', 'tpTriggerPx': '', 'tpTriggerPxType': '', 'tradeId': '81184616', 'uTime': '1678441709960'}, 'id': '554359943143833600', 'clientOrderId': 'e847386590ce4dBCeaddfd1494dc7080', 'timestamp': 1678441709955, 'datetime': '2023-03-10T09:48:29.955Z', 'lastTradeTimestamp': 1678441709957, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': 'IOC', 'postOnly': None, 'side': 'buy', 'price': 5.248, 'stopPrice': None, 'triggerPrice': None, 'average': 5.248, 'cost': 0.999995904, 'amount': 1.0, 'filled': 0.190548, 'remaining': 0.809452, 'status': 'closed', 'fee': {'cost': 0.000190548, 'currency': 'DOT'}, 'trades': [], 'reduceOnly': False, 'fees': [{'cost': 0.000190548, 'currency': 'DOT'}]} + + # open_price = data['info']['avgPx'] + # print("开仓平均价", open_price) + + # # 止盈价 + # closing_price = float(open_price) * float((1 + 0.005)) + # closing_price = common.roundVal(closing_price, open_price) + # print("止盈价", closing_price) + + # # 止损价 + # stop_loss_price = float(open_price) * float((1 - 0.01)) + # stop_loss_price = common.roundVal(stop_loss_price, open_price) + # print("止损价", stop_loss_price) + + # property_val = float(data['info']['accFillSz']) + \ + # float(data['info']['fee']) + + # # 相同位数 + # property_val = common.roundValCeil(property_val, data['info']['accFillSz']) + # print("可平仓资产", property_val) + + # closed_orders = exchange.fetchClosedOrders('DOT/USDT', limit=2) + # print('closed_orders', closed_orders) + + # print(exchange.fetchBalance()) + + # 可以用,限价单 + # time.sleep(1) + # data = exchange.createLimitSellOrder( + # 'DOT/USDT', property_val, closing_price, {"tdMode": "cross", 'ccy': 'USDT', "reduceOnly": True}) + # print(data) + # print(type(data)) + + # 止损价 + # 止盈价 + + # exchange_params = { + # 'ccy': "USDT", + # 'reduceOnly': True, + # 'tdMode': "cross", + # 'tpOrdPx': "-1", + # 'tpTriggerPx': closing_price, + # 'slOrdPx': "-1", + # 'slTriggerPx': stop_loss_price, + # } + # print('---------止损价 执行----------------------------------') + + # data = exchange.create_order( + # 'BTC/USDT', 'limit', 'sell', property_val, closing_price, exchange_params) + # print(data) + # print(type(data)) + + # print('---------止盈价 执行----------------------------------') + + # exchange_params = { + # 'stopPrice': closing_price, + # 'type': 'stopLimit', + # } + # data = exchange.create_order( + # 'DOT/USDT', 'limit', 'sell', property_val, closing_price, exchange_params) + # print(data) + # print(type(data)) + + +def testK_buy_close(): + # 平多 + data = exchange.create_limit_buy_order( + 'DOT/USDT', 1, 5, {"tdMode": "cross"}) + print(data) + print(type(data)) + + # closed_orders = exchange.fetchClosedOrders('BTC/USDT', limit=2) + # print(closed_orders) + + # closed_orders = exchange.fetchMyTrades('ETH/USDT', limit=2) + # print(closed_orders) + + +def testK_sell_open(): + # 做空开仓 + + # data = exchange.createMarketSellOrder( + # 'DOT/USDT', 1, {"tdMode": "cross", 'ccy': "USDT", }) + # print(data) + # print(type(data)) + + # 数据 + # {'info': {'clOrdId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'ordId': '554437054223302656', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'e847386590ce4dBC'}, 'id': '554437054223302656', 'clientOrderId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': None, 'fees': []} + + # # 查询委托单 + # order_id = data['info']['ordId'] + order_id = '554516809819832320' + data = exchange.fetchOrder(order_id, 'DOT/USDT') + print(data['info']) + # {'info': {'accFillSz': '0.190548', 'algoClOrdId': '', 'algoId': '', 'avgPx': '5.248', 'cTime': '1678441709955', 'cancelSource': '', 'cancelSourceReason': '', 'category': 'normal', 'ccy': 'USDT', 'clOrdId': 'e847386590ce4dBCeaddfd1494dc7080', 'fee': '-0.000190548', 'feeCcy': 'DOT', 'fillPx': '5.248', 'fillSz': '0.190548', 'fillTime': '1678441709957', 'instId': 'DOT-USDT', 'instType': 'MARGIN', 'lever': '10', 'ordId': '554359943143833600', 'ordType': 'market', 'pnl': '0', 'posSide': 'net', 'px': '', 'quickMgnType': '', 'rebate': '0', 'rebateCcy': 'USDT', 'reduceOnly': 'false', 'side': 'buy', 'slOrdPx': '', 'slTriggerPx': '', 'slTriggerPxType': '', 'source': '', 'state': 'filled', 'sz': '1', 'tag': 'e847386590ce4dBC', 'tdMode': 'cross', 'tgtCcy': '', 'tpOrdPx': '', 'tpTriggerPx': '', 'tpTriggerPxType': '', 'tradeId': '81184616', 'uTime': '1678441709960'}, 'id': '554359943143833600', 'clientOrderId': 'e847386590ce4dBCeaddfd1494dc7080', 'timestamp': 1678441709955, 'datetime': '2023-03-10T09:48:29.955Z', 'lastTradeTimestamp': 1678441709957, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': 'IOC', 'postOnly': None, 'side': 'buy', 'price': 5.248, 'stopPrice': None, 'triggerPrice': None, 'average': 5.248, 'cost': 0.999995904, 'amount': 1.0, 'filled': 0.190548, 'remaining': 0.809452, 'status': 'closed', 'fee': {'cost': 0.000190548, 'currency': 'DOT'}, 'trades': [], 'reduceOnly': False, 'fees': [{'cost': 0.000190548, 'currency': 'DOT'}]} + + open_price = data['info']['avgPx'] + + print("开仓平均价", open_price) + + # 止盈价 + closing_price = float(open_price) * float((1 - 0.005)) + closing_price = common.roundVal(closing_price, open_price) + print("止盈价", closing_price) + + # 止损价 + stop_loss_price = float(open_price) * float((1 + 0.01)) + stop_loss_price = common.roundVal(stop_loss_price, open_price) + print("止损价", stop_loss_price) + + property_val = float(data['info']['accFillSz']) + # 相同位数 + print("可平仓资产", property_val) + + # 止损价 + # 止盈价 + exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + print('---------止损价 执行----------------------------------') + + data = exchange.create_order( + 'DOT/USDT', 'limit', 'buy', property_val, closing_price, exchange_params) + print(data) + print(type(data)) + + +def testK_sell_close(): + # 平多 + data = exchange.create_limit_buy_order( + 'DOT/USDT', 1, 5, {"tdMode": "cross"}) + print(data) + print(type(data)) + + # closed_orders = exchange.fetchClosedOrders('BTC/USDT', limit=2) + # print(closed_orders) + + # closed_orders = exchange.fetchMyTrades('ETH/USDT', limit=2) + # print(closed_orders) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'long': + longRun() + elif func == 'run': + debug() + elif func == 'test': + testKdan() + elif func == 't_get_trade': + t_get_trade() + elif func == 't_buy_open': + testK_buy_open() + elif func == 't_buy_close': + testK_buy_close() + elif func == 't_sell_open': + testK_sell_open() + elif func == 't_sell_cloe': + testK_sell_close() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/online_macd_trade.py b/plugins/cryptocurrency_trade/ccxt/strategy/online_macd_trade.py new file mode 100644 index 000000000..d4fc36864 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/online_macd_trade.py @@ -0,0 +1,437 @@ +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_macd_trade.py run +# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_macd_trade.py long + + +import ccxt +import talib + +import sys +import os +import time +import json +import pandas as pd +# import pandas_ta as ta +from pprint import pprint +import numpy as np + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +sys.path.append(os.getcwd() + "/class/core") +import mw + +exchange = common.initEx() +exchange.load_markets() + +# 默认开仓数据 +# default_open_num = 10 + +default_open = { + 'ETH/USDT': 30, + 'DOT/USDT': 30, + 'CEL/USDT': 30, +} + +default_sell = { + 'ETH/USDT': 0.02, + 'DOT/USDT': 5, + 'CEL/USDT': 85, +} + + +# 做多开仓 +def onBuyOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + + # 信号只在一个周期内执行一次|start + lock_file = common.getServerDir() + '/signal.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + stype = symbol.replace('/', '_') + '_' + timeframe + trigger_time = common.toUnixTimeSecond(timeframe) + lock_data = json.loads(mw.readFile(lock_file)) + if stype in lock_data: + diff_time = time.time() - lock_data[stype]['do_time'] + if diff_time >= trigger_time: + lock_data[stype]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[stype] = {'do_time': time.time()} + + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + + common.writeLogEx('------做多----------------------------------', symbol) + + default_open_num = default_open[symbol] + # 做多开仓 | 市价 + data = exchange.createMarketBuyOrder( + symbol, default_open_num, {"tdMode": "cross"}) + + common.writeLogEx('开仓数据:', symbol) + common.writeLogEx(json.dumps(data), symbol) + + order_id = data['info']['ordId'] + order_data = exchange.fetchOrder(order_id, symbol) + + common.writeLogEx('订单数据:', symbol) + common.writeLogEx(json.dumps(order_data), symbol) + + # 实际开场平均价 + open_price = order_data['info']['avgPx'] + + # 做多-止损价大于开仓价,重设止损价 + if float(stop_loss_price) <= float(open_price): + stop_loss_price = float(open_price) * float((1 - 0.003)) + + # property_val = float(order_data['info']['accFillSz']) + float(order_data['info']['fee']) + property_val = common.addition( + order_data['info']['accFillSz'], order_data['info']['fee']) + property_val = float(property_val) + + # 可平仓的数量 + property_val = common.roundValCeil( + property_val, order_data['info']['accFillSz']) + + common.writeLogEx('可平仓资产:' + str(property_val), symbol) + + # 止盈价 + diff = float(open_price) - float(stop_loss_price) + closing_price_c = float(open_price) + (diff * 2) + closing_price = float(open_price) * float((1 + profit)) + # 选择盈利多的 + if closing_price_c > closing_price: + closing_price = closing_price_c + + closing_price = common.roundVal(closing_price, stop_loss_price) + # stop_loss_price = common.roundVal(stop_loss_price, open_price) + common.writeLogEx('实际开仓价:' + str(open_price), symbol) + common.writeLogEx('止盈价:' + str(closing_price), symbol) + common.writeLogEx('止损价:' + str(stop_loss_price), symbol) + + # 设置 - 止损价/止盈价 + sl_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + + # 止损条件单 + common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'sell', + property_val, stop_loss_price, sl_exchange_params]), symbol) + sl_cond_data = exchange.create_order( + symbol, 'limit', 'sell', property_val, stop_loss_price, sl_exchange_params) + + common.writeLogEx('止损价数据:', symbol) + common.writeLogEx(json.dumps(sl_cond_data), symbol) + + # 止赢条件单 + tp_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + + common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'sell', + property_val, closing_price, tp_exchange_params]), symbol) + tp_cond_data = exchange.create_order( + symbol, 'limit', 'sell', property_val, closing_price, tp_exchange_params) + + common.writeLogEx('止赢数据:', symbol) + common.writeLogEx(json.dumps(tp_cond_data), symbol) + + common.writeLogEx('------做多 end----------------------------------', symbol) + return True, open_price, closing_price + + +def onBuyOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + # 做多开仓 + # profit 百分比 + try: + return onBuyOrderTry(symbol, stop_loss_price, profit, timeframe) + except Exception as e: + common.writeLogErrorEx(mw.getTracebackInfo(), symbol) + return False, 0, 0 + + +def onSellOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + + # 信号只在一个周期内执行一次|start + lock_file = common.getServerDir() + '/signal.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + stype = symbol.replace('/', '_') + '_' + timeframe + trigger_time = common.toUnixTimeSecond(timeframe) + lock_data = json.loads(mw.readFile(lock_file)) + if stype in lock_data: + diff_time = time.time() - lock_data[stype]['do_time'] + if diff_time >= trigger_time: + lock_data[stype]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[stype] = {'do_time': time.time()} + + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + common.writeLogEx('------做空----------------------------------', symbol) + + # 计算借币卖币多多少,以USDT为基准 + # sell_num = float(default_open_num) / float(stop_loss_price) + # sell_num = round(sell_num, 8) + + sell_num = default_sell[symbol] + # 做空开仓 | 市价 + data = exchange.createMarketSellOrder( + symbol, sell_num, {"tdMode": "cross", 'ccy': "USDT"}) + + common.writeLogEx('开仓数据:', symbol) + common.writeLogEx(json.dumps(data), symbol) + + order_id = data['info']['ordId'] + order_data = exchange.fetchOrder(order_id, symbol) + + common.writeLogEx('订单数据:', symbol) + common.writeLogEx(json.dumps(order_data), symbol) + + # 实际开场平均价 + open_price = order_data['info']['avgPx'] + + common.writeLogEx('可平仓资产:' + str(sell_num), symbol) + + # 做空-止损价小于开仓价,重设止损价 + if float(stop_loss_price) <= float(open_price): + stop_loss_price = float(open_price) * float((1 + 0.003)) + + # 止盈价 + diff = float(stop_loss_price) - float(open_price) + closing_price_c = float(open_price) - (diff * 2) + closing_price = float(open_price) * float((1 - profit)) + # 选择盈利多的 + if closing_price_c < closing_price: + closing_price = closing_price_c + + closing_price = common.roundVal(closing_price, open_price) + stop_loss_price = common.roundVal(stop_loss_price, open_price) + common.writeLogEx('实际开仓价:' + str(open_price), symbol) + common.writeLogEx('止盈价:' + str(closing_price), symbol) + common.writeLogEx('止损价:' + str(stop_loss_price), symbol) + + # 设置 - 止损价 + sl_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + + sl_amount = common.multiply(stop_loss_price, sell_num) + # 解决平仓时,未全部平仓 + sl_amount = common.addition(sl_amount, 0.1) + common.writeLogEx('止损总价值:' + str(sl_amount), symbol) + common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'buy', float( + sl_amount), stop_loss_price, sl_exchange_params]), symbol) + sl_cond_data = exchange.create_order( + symbol, 'limit', 'buy', sl_amount, stop_loss_price, sl_exchange_params) + + common.writeLogEx('止损价数据:', symbol) + common.writeLogEx(json.dumps(sl_cond_data), symbol) + + tp_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + + # 设置 -止盈价 + tp_amount = common.multiply(closing_price, sell_num) + # 解决平仓时,未全部平仓 + tp_amount = common.addition(tp_amount, 0.1) + common.writeLogEx('止盈总价值:' + str(tp_amount), symbol) + common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'buy', float( + tp_amount), closing_price, tp_exchange_params]), symbol) + tp_cond_data = exchange.create_order( + symbol, 'limit', 'buy', tp_amount, closing_price, tp_exchange_params) + + common.writeLogEx('止盈价数据:', symbol) + common.writeLogEx(json.dumps(tp_cond_data), symbol) + + common.writeLogEx('------做空 end----------------------------------', symbol) + return True, open_price, closing_price + + +def onSellOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + # 做空开仓 + # profit 百分比 + try: + return onSellOrderTry(symbol, stop_loss_price, profit, timeframe) + except Exception as e: + common.writeLogErrorEx(mw.getTracebackInfo(), symbol) + return False, 0, 0 + + +def getOnlineData(symbol, input_tf="15m", limit=230): + bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=limit) + df = pd.DataFrame(bars[:], columns=['timestamp', + 'open', 'high', 'low', 'close', 'volume']) + df['dt'] = pd.to_datetime(df['timestamp'], unit='ms') + df.set_index('dt', inplace=True) + df.index = df.index.tz_localize('UTC').tz_convert('Asia/Shanghai') + return df + + +def isKdj(last, last_pre): + # 判断是否是金叉 + if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0): + return True + return False + + +def isDeadFork(last, last_pre): + # 判断是否是死叉 + if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0): + return True + return False + + +def getMACD(df, lenght=-60): + if len(df['close'].values) < 100: + return False, None + + close_p = df['close'].values + df['dif'], df['dea'], df['macd'] = talib.MACD(close_p, + fastperiod=12, + slowperiod=26, + signalperiod=9) + return df[lenght:] + + +def getEMA(df): + close_p = df['close'].values + df['ema'] = talib.EMA(np.array(close_p), timeperiod=200) + return df + + +def doneMacd(data, tag, timeframe): + data = getEMA(data) + + ma_data = getMACD(data) + # alen = len(data) + # print(ma_data) + + t_data = ma_data.tail(3) + # print(t_data) + + last_pre = t_data.iloc[0] + last = t_data.iloc[1] + + # print(last) + # print(last.index) + + # print('close:', last['close'], 'ema:', last['ema']) + + obj = common.MsgTpl() + symbol = tag.upper() + '/USDT' + obj.setName(symbol) + obj.setStrategyName("MACD检查") + obj.setTimeFrame(timeframe) + obj.setOpenTime(last['timestamp'] / 1000) + + now_data = t_data.iloc[2] + # print('now_data:', now_data) + + # msg = obj.toText() + # print(msg) + + if isKdj(last, last_pre) and last['close'] > last['ema']: + # if isKdj(last, last_pre): + + # closing_price = common.calc_ClosingPrice( + # now_data['close'], last_pre['low'], 'buy') + # # print('closing_price:', closing_price) + + # 做多止损点 + stop_loss_price = last_pre['low'] + buy_status, open_price, closing_price = onBuyOrder( + symbol, stop_loss_price, 0.005, timeframe) + + if buy_status: + obj.setStrategicDt('buy') + # 做多止损点 + obj.setStopLossPrice(str(stop_loss_price)) + obj.setOpenPrice(str(open_price)) + obj.setClosingPrice(str(closing_price)) + obj.setContent("检查到金叉状态") + msg = obj.toText() + print(msg) + common.notifyMsg(msg, timeframe, tag) + common.writeLog(msg) + + if isDeadFork(last, last_pre) and last['close'] < last['ema']: + # if isDeadFork(last, last_pre): + # closing_price = common.calc_ClosingPrice( + # now_data['close'], last_pre['high'], 'sell') + + # 做空止损点 + stop_loss_price = last_pre['high'] + sell_status, open_price, closing_price = onSellOrder( + symbol, stop_loss_price, 0.005, timeframe) + if sell_status: + obj.setStopLossPrice(str(stop_loss_price)) + obj.setOpenPrice(str(open_price)) + obj.setClosingPrice(str(closing_price)) + obj.setStrategicDt('sell') + obj.setContent("检查到死叉状态") + msg = obj.toText() + print(msg) + common.notifyMsg(msg, timeframe, tag) + common.writeLog(msg) + + +def mainProcess(tag, timeframe='15m'): + symbol = tag.upper() + '/USDT' + data = getOnlineData(symbol, timeframe) + + doneMacd(data, tag, timeframe) + + +def foreachList(): + tag_list = [ + 'eth', 'dot' + ] + + for tag in tag_list: + mainProcess(tag, '15m') + time.sleep(1) + + +def longRun(): + while True: + foreachList() + time.sleep(3) + + +def debug(): + while True: + mainProcess('xrp', '1m') + time.sleep(3) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'long': + longRun() + elif func == 'run': + debug() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/online_test.py b/plugins/cryptocurrency_trade/ccxt/strategy/online_test.py new file mode 100644 index 000000000..3ac993513 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/online_test.py @@ -0,0 +1,273 @@ +# cd /www/server/mdserver-web && source bin/activate + +# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_buy_open +# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_buy_close + + +# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_sell_open +# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_sell_cloe + +# 获取仓位数据 + +# python3 plugins/cryptocurrency_trade/ccxt/strategy/online_test.py t_get_trade + +import ccxt +import talib + +import sys +import os +import time +import pandas as pd +# import pandas_ta as ta +from pprint import pprint +import numpy as np + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +sys.path.append(os.getcwd() + "/class/core") +import mw + +exchange = common.initEx() + +exchange.load_markets() + + +def t_get_trade(): + data = exchange.fetchPositions() + print(data) + + +def btc_test(): + closing_price = 24599.9 + stop_loss_price = 24740.4 + + # --------------------------- + stop_loss_args = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + print('---------止损价 执行 START ----------------------------------') + + sl_amount = stop_loss_price * 0.003 + print(amount) + data = exchange.create_order( + 'BTC/USDT', 'limit', 'buy', sl_amount, stop_loss_price, stop_loss_args) + print(data) + print('---------止损价 执行 END ----------------------------------') + + print('---------止盈价 执行 START ----------------------------------') + + closing_price_args = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + + cp_amount = closing_price * 0.003 + print(amount) + data = exchange.create_order( + 'BTC/USDT', 'limit', 'buy', cp_amount, closing_price, closing_price_args) + print(data) + print('---------止盈价 执行 END ----------------------------------') + + +def testK_buy_open_sz(): + # 做多开仓 + + data = exchange.fetchTicker('DOT/USDT') + print(data) + # data = exchange.createMarketBuyOrder('DOT/USDT', 1, {"tdMode": "cross"}) + # print(data) + # print(type(data)) + + +def testK_buy_open(): + # 做多开仓 + + data = exchange.createMarketBuyOrder( + 'DOT/USDT', 1, {"tdMode": "cross"}) + print(data) + print(type(data)) + + # 数据 + # {'info': {'clOrdId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'ordId': '554437054223302656', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'e847386590ce4dBC'}, 'id': '554437054223302656', 'clientOrderId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': None, 'fees': []} + + # 查询委托单 + order_id = data['info']['ordId'] + # order_id = '554437054223302656' + data = exchange.fetchOrder(order_id, 'DOT/USDT') + print(data['info']) + # {'info': {'accFillSz': '0.190548', 'algoClOrdId': '', 'algoId': '', 'avgPx': '5.248', 'cTime': '1678441709955', 'cancelSource': '', 'cancelSourceReason': '', 'category': 'normal', 'ccy': 'USDT', 'clOrdId': 'e847386590ce4dBCeaddfd1494dc7080', 'fee': '-0.000190548', 'feeCcy': 'DOT', 'fillPx': '5.248', 'fillSz': '0.190548', 'fillTime': '1678441709957', 'instId': 'DOT-USDT', 'instType': 'MARGIN', 'lever': '10', 'ordId': '554359943143833600', 'ordType': 'market', 'pnl': '0', 'posSide': 'net', 'px': '', 'quickMgnType': '', 'rebate': '0', 'rebateCcy': 'USDT', 'reduceOnly': 'false', 'side': 'buy', 'slOrdPx': '', 'slTriggerPx': '', 'slTriggerPxType': '', 'source': '', 'state': 'filled', 'sz': '1', 'tag': 'e847386590ce4dBC', 'tdMode': 'cross', 'tgtCcy': '', 'tpOrdPx': '', 'tpTriggerPx': '', 'tpTriggerPxType': '', 'tradeId': '81184616', 'uTime': '1678441709960'}, 'id': '554359943143833600', 'clientOrderId': 'e847386590ce4dBCeaddfd1494dc7080', 'timestamp': 1678441709955, 'datetime': '2023-03-10T09:48:29.955Z', 'lastTradeTimestamp': 1678441709957, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': 'IOC', 'postOnly': None, 'side': 'buy', 'price': 5.248, 'stopPrice': None, 'triggerPrice': None, 'average': 5.248, 'cost': 0.999995904, 'amount': 1.0, 'filled': 0.190548, 'remaining': 0.809452, 'status': 'closed', 'fee': {'cost': 0.000190548, 'currency': 'DOT'}, 'trades': [], 'reduceOnly': False, 'fees': [{'cost': 0.000190548, 'currency': 'DOT'}]} + + open_price = data['info']['avgPx'] + + print("开仓平均价", open_price) + + # 止盈价 + closing_price = float(open_price) * float((1 + 0.005)) + closing_price = common.roundVal(closing_price, open_price) + print("止盈价", closing_price) + + # 止损价 + stop_loss_price = float(open_price) * float((1 - 0.01)) + stop_loss_price = common.roundVal(stop_loss_price, open_price) + print("止损价", stop_loss_price) + + property_val = float(data['info']['accFillSz']) + \ + float(data['info']['fee']) + + # 相同位数 + property_val = common.roundValCeil(property_val, data['info']['accFillSz']) + print("可平仓资产", property_val) + + # closed_orders = exchange.fetchClosedOrders('DOT/USDT', limit=2) + # print('closed_orders', closed_orders) + + # print(exchange.fetchBalance()) + + # 可以用,限价单 + # time.sleep(1) + # data = exchange.createLimitSellOrder( + # 'DOT/USDT', property_val, closing_price, {"tdMode": "cross", 'ccy': 'USDT', "reduceOnly": True}) + # print(data) + # print(type(data)) + + # 止损价 + # 止盈价 + + exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + print('---------止损价 执行----------------------------------') + + data = exchange.create_order( + 'DOT/USDT', 'limit', 'sell', property_val, closing_price, exchange_params) + print(data) + print(type(data)) + + # print('---------止盈价 执行----------------------------------') + + # exchange_params = { + # 'stopPrice': closing_price, + # 'type': 'stopLimit', + # } + # data = exchange.create_order( + # 'DOT/USDT', 'limit', 'sell', property_val, closing_price, exchange_params) + # print(data) + # print(type(data)) + + +def testK_buy_close(): + # 平多 + data = exchange.create_limit_buy_order( + 'DOT/USDT', 1, 5, {"tdMode": "cross"}) + print(data) + print(type(data)) + + # closed_orders = exchange.fetchClosedOrders('BTC/USDT', limit=2) + # print(closed_orders) + + # closed_orders = exchange.fetchMyTrades('ETH/USDT', limit=2) + # print(closed_orders) + + +def testK_sell_open(): + # 做空开仓 + + # data = exchange.createMarketSellOrder( + # 'DOT/USDT', 1, {"tdMode": "cross", 'ccy': "USDT", }) + # print(data) + # print(type(data)) + + # 数据 + # {'info': {'clOrdId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'ordId': '554437054223302656', 'sCode': '0', 'sMsg': 'Order placed', 'tag': 'e847386590ce4dBC'}, 'id': '554437054223302656', 'clientOrderId': 'e847386590ce4dBC58e8b0afb0fe70cc', 'timestamp': None, 'datetime': None, 'lastTradeTimestamp': None, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': None, 'postOnly': None, 'side': 'buy', 'price': None, 'stopPrice': None, 'triggerPrice': None, 'average': None, 'cost': None, 'amount': None, 'filled': None, 'remaining': None, 'status': None, 'fee': None, 'trades': [], 'reduceOnly': None, 'fees': []} + + # # 查询委托单 + # order_id = data['info']['ordId'] + order_id = '554516809819832320' + data = exchange.fetchOrder(order_id, 'DOT/USDT') + print(data['info']) + # {'info': {'accFillSz': '0.190548', 'algoClOrdId': '', 'algoId': '', 'avgPx': '5.248', 'cTime': '1678441709955', 'cancelSource': '', 'cancelSourceReason': '', 'category': 'normal', 'ccy': 'USDT', 'clOrdId': 'e847386590ce4dBCeaddfd1494dc7080', 'fee': '-0.000190548', 'feeCcy': 'DOT', 'fillPx': '5.248', 'fillSz': '0.190548', 'fillTime': '1678441709957', 'instId': 'DOT-USDT', 'instType': 'MARGIN', 'lever': '10', 'ordId': '554359943143833600', 'ordType': 'market', 'pnl': '0', 'posSide': 'net', 'px': '', 'quickMgnType': '', 'rebate': '0', 'rebateCcy': 'USDT', 'reduceOnly': 'false', 'side': 'buy', 'slOrdPx': '', 'slTriggerPx': '', 'slTriggerPxType': '', 'source': '', 'state': 'filled', 'sz': '1', 'tag': 'e847386590ce4dBC', 'tdMode': 'cross', 'tgtCcy': '', 'tpOrdPx': '', 'tpTriggerPx': '', 'tpTriggerPxType': '', 'tradeId': '81184616', 'uTime': '1678441709960'}, 'id': '554359943143833600', 'clientOrderId': 'e847386590ce4dBCeaddfd1494dc7080', 'timestamp': 1678441709955, 'datetime': '2023-03-10T09:48:29.955Z', 'lastTradeTimestamp': 1678441709957, 'symbol': 'DOT/USDT', 'type': 'market', 'timeInForce': 'IOC', 'postOnly': None, 'side': 'buy', 'price': 5.248, 'stopPrice': None, 'triggerPrice': None, 'average': 5.248, 'cost': 0.999995904, 'amount': 1.0, 'filled': 0.190548, 'remaining': 0.809452, 'status': 'closed', 'fee': {'cost': 0.000190548, 'currency': 'DOT'}, 'trades': [], 'reduceOnly': False, 'fees': [{'cost': 0.000190548, 'currency': 'DOT'}]} + + open_price = data['info']['avgPx'] + + print("开仓平均价", open_price) + + # 止盈价 + closing_price = float(open_price) * float((1 - 0.005)) + closing_price = common.roundVal(closing_price, open_price) + print("止盈价", closing_price) + + # 止损价 + stop_loss_price = float(open_price) * float((1 + 0.01)) + stop_loss_price = common.roundVal(stop_loss_price, open_price) + print("止损价", stop_loss_price) + + property_val = float(data['info']['accFillSz']) + # 相同位数 + print("可平仓资产", property_val) + + # 止损价 + # 止盈价 + exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + print('---------止损价 执行----------------------------------') + + data = exchange.create_order( + 'DOT/USDT', 'limit', 'buy', property_val, closing_price, exchange_params) + print(data) + print(type(data)) + + +def testK_sell_close(): + # 平多 + data = exchange.create_limit_buy_order( + 'DOT/USDT', 1, 5, {"tdMode": "cross"}) + print(data) + print(type(data)) + + # closed_orders = exchange.fetchClosedOrders('BTC/USDT', limit=2) + # print(closed_orders) + + # closed_orders = exchange.fetchMyTrades('ETH/USDT', limit=2) + # print(closed_orders) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'long': + longRun() + elif func == 'run': + debug() + elif func == 'test': + testKdan() + elif func == 't_get_trade': + t_get_trade() + elif func == 't_buy_open': + btc_test() + elif func == 't_buy_close': + testK_buy_close() + elif func == 't_sell_open': + testK_sell_open() + elif func == 't_sell_cloe': + testK_sell_close() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/rsi_robot.py b/plugins/cryptocurrency_trade/ccxt/strategy/rsi_robot.py new file mode 100644 index 000000000..9a4129c1e --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/rsi_robot.py @@ -0,0 +1,102 @@ +import ccxt +import talib + +import sys +import os +import time +import pandas as pd +import pandas_ta as ta +from pprint import pprint + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/strategy/rsi_robot.py run + + +pd.set_option('display.max_rows', None) + +exchange = common.initEx() + +# 查看隐形方法 +# print(dir(exchange)) + + +exchange.load_markets() + +entry_rsi = 30 +exit_rsi = 40 + + +symbol = 'XRP/USDT' +timeframe = '15m' + +tf_mult = exchange.parse_timeframe(timeframe) * 1000 + + +def indicators(data): + + data['rsi'] = data.ta.rsi(length=10) + data['ema'] = data.ta.ema(length=200) + + # close_p = data['close'].values + # data['rsi'] = talib.RSI(close_p, timeperiod=10) + # data['ema'] = talib.EMA(close_p, timeperiod=200) + return data + + +def check_buy_sell_signals(df): + last_row_index = len(df.index) - 1 + lastest_rsi = round(df['rsi'].iloc[-1], 2) + lastest_price = round(df['close'].iloc[-1], 5) + lastest_ema = round(df['ema'].iloc[-1], 5) + lastest_ts = df['timestamp'].iloc[-1] + + msg = "lastest_rsi:" + str(lastest_rsi) + " < entry_rsi:" + str(entry_rsi) + msg += ",lastest_price:" + \ + str(lastest_price) + " > lastest_ema:" + str(lastest_ema) + print(msg) + + long_cond = (lastest_rsi < entry_rsi) and (lastest_price > lastest_ema) + if long_cond: + print("买入") + order = exchange.create_market_buy_order(symbol, 1) + + closed_orders = exchange.fetchClosedOrders(symbol, limit=2) + if len(closed_orders) > 0: + print("closed_orders:", closed_orders) + most_recent_closed_order = closed_orders[-1] + diff = lastest_ts - most_recent_closed_order['timestamp'] + last_buy_signal_cnt = int(diff / tf_mult) + + exit_cond = (lastest_rsi > exit_rsi) and (last_buy_signal_cnt > 10) + if exit_cond: + print("卖出") + order = exchange.create_market_sell_order(symbol, 1) + return + + +def runBot(): + bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=200) + df = pd.DataFrame(bars[:], columns=['timestamp', + 'open', 'high', 'low', 'close', 'volume']) + df['dt'] = pd.to_datetime(df['timestamp'], unit='ms') + + df = indicators(df).tail(30) + + check_buy_sell_signals(df) + + +def longRunBot(): + while True: + runBot() + time.sleep(10) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'run': + longRunBot() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/strategy/vega_trade.py b/plugins/cryptocurrency_trade/ccxt/strategy/vega_trade.py new file mode 100644 index 000000000..a51348a68 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/strategy/vega_trade.py @@ -0,0 +1,536 @@ +# cd /www/server/mdserver-web && source bin/activate +# python3 plugins/cryptocurrency_trade/ccxt/strategy/vega_trade.py run +# python3 plugins/cryptocurrency_trade/ccxt/strategy/vega_trade.py long + + +# 动量策略交易 +################ + + +import ccxt +import talib + +import sys +import os +import time +import json +import pandas as pd +# import pandas_ta as ta +from pprint import pprint +import numpy as np + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/strategy") +import common + +sys.path.append(os.getcwd() + "/class/core") +import mw + +exchange = common.initEx() +exchange.load_markets() + +# 默认开仓数据 +# default_open_num = 70 +# default_sell_num = 0.003 + +default_open = { + 'BTC/USDT': 70, + 'XRP/USDT': 70, +} + +default_sell = { + 'BTC/USDT': 0.003, + 'XRP/USDT': 185, +} + + +# 做多开仓 +def onBuyOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + + # 信号只在一个周期内执行一次|start + lock_file = common.getServerDir() + '/signal.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + stype = symbol.replace('/', '_') + '_' + timeframe + trigger_time = common.toUnixTimeSecond(timeframe) + lock_data = json.loads(mw.readFile(lock_file)) + if stype in lock_data: + diff_time = time.time() - lock_data[stype]['do_time'] + if diff_time >= trigger_time: + lock_data[stype]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[stype] = {'do_time': time.time()} + + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + + common.writeLogEx('------做多----------------------------------', symbol) + + default_open_num = default_open[symbol] + # 做多开仓 | 市价 + data = exchange.createMarketBuyOrder( + symbol, default_open_num, {"tdMode": "cross"}) + + common.writeLogEx('开仓数据:', symbol) + common.writeLogEx(json.dumps(data), symbol) + + order_id = data['info']['ordId'] + order_data = exchange.fetchOrder(order_id, symbol) + + common.writeLogEx('订单数据:', symbol) + common.writeLogEx(json.dumps(order_data), symbol) + + # 实际开场平均价 + open_price = order_data['info']['avgPx'] + + # 修正小数点位数 + open_price = common.roundVal(open_price, stop_loss_price) + common.writeLogEx('实际开仓价:' + str(open_price), symbol) + + # 做多-止损价大于开仓价,重设止损价 + if float(stop_loss_price) <= float(open_price): + stop_loss_price = float(open_price) * float((1 - 0.003)) + + # property_val = float(order_data['info']['accFillSz']) + float(order_data['info']['fee']) + + property_val = common.addition(order_data['info'][ + 'accFillSz'], order_data['info']['fee']) + property_val = float(property_val) + # 可平仓的数量 + # property_val = common.roundValCeil( + # property_val, order_data['info']['accFillSz']) + + common.writeLogEx('可平仓资产:' + str(property_val), symbol) + + # 止盈价 + diff = float(open_price) - float(stop_loss_price) + closing_price = float(open_price) + (diff * 1.5) + # closing_price = float(open_price) * float((1 + profit)) + # # 选择盈利多的 + # if closing_price_c > closing_price: + # closing_price = closing_price_c + + closing_price = common.roundVal(closing_price, open_price) + stop_loss_price = common.roundVal(stop_loss_price, open_price) + common.writeLogEx('止盈价:' + str(closing_price), symbol) + common.writeLogEx('止损价:' + str(stop_loss_price), symbol) + + # 设置 - 止损价/止盈价 + sl_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + + # 止损条件单 + common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'sell', + property_val, stop_loss_price, sl_exchange_params]), symbol) + sl_cond_data = exchange.create_order( + symbol, 'limit', 'sell', property_val, stop_loss_price, sl_exchange_params) + + common.writeLogEx('止损价数据:', symbol) + common.writeLogEx(json.dumps(sl_cond_data), symbol) + + # 止赢条件单 + tp_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'sell', + property_val, closing_price, tp_exchange_params]), symbol) + tp_cond_data = exchange.create_order( + symbol, 'limit', 'sell', property_val, closing_price, tp_exchange_params) + + common.writeLogEx('止盈数据:', symbol) + common.writeLogEx(json.dumps(tp_cond_data), symbol) + + common.writeLogEx('------做多 end----------------------------------', symbol) + return True, open_price, closing_price + + +def onBuyOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + # 做多开仓 + # profit 百分比 + try: + return onBuyOrderTry(symbol, stop_loss_price, profit, timeframe) + except Exception as e: + common.writeLogErrorEx(mw.getTracebackInfo(), symbol) + return False, 0, 0 + + +def onSellOrderTry(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + + # 信号只在一个周期内执行一次|start + lock_file = common.getServerDir() + '/signal.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + stype = symbol.replace('/', '_') + '_' + timeframe + trigger_time = common.toUnixTimeSecond(timeframe) + lock_data = json.loads(mw.readFile(lock_file)) + if stype in lock_data: + diff_time = time.time() - lock_data[stype]['do_time'] + if diff_time >= trigger_time: + lock_data[stype]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[stype] = {'do_time': time.time()} + + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + common.writeLogEx('------做空----------------------------------', symbol) + + # 计算借币卖币多多少,以USDT为基准 + # sell_num = float(default_open_num) / float(stop_loss_price) + # sell_num = round(sell_num, 8) + # sell_num = default_sell_num + sell_num = default_sell[symbol] + + # 做空开仓 | 市价 + data = exchange.createMarketSellOrder( + symbol, sell_num, {"tdMode": "cross", 'ccy': "USDT"}) + + common.writeLogEx('开仓数据:', symbol) + common.writeLogEx(json.dumps(data), symbol) + + order_id = data['info']['ordId'] + order_data = exchange.fetchOrder(order_id, symbol) + + common.writeLogEx('订单数据:', symbol) + common.writeLogEx(json.dumps(order_data), symbol) + + # 实际开场平均价 + open_price = order_data['info']['avgPx'] + + # 修正 + open_price = common.roundVal(open_price, stop_loss_price) + + common.writeLogEx('实际开仓价:' + str(open_price), symbol) + common.writeLogEx('可平仓资产:' + str(sell_num), symbol) + + # 做空-止损价小于开仓价,重设止损价 + if float(stop_loss_price) <= float(open_price): + stop_loss_price = float(open_price) * float((1 + 0.003)) + + # 止盈价 + diff = float(stop_loss_price) - float(open_price) + closing_price = float(open_price) - (diff * 1.5) + # closing_price = float(open_price) * float((1 - profit)) + # 选择盈利多的 + # if closing_price_c < closing_price: + # closing_price = closing_price_c + + closing_price = common.roundVal(closing_price, open_price) + stop_loss_price = common.roundVal(stop_loss_price, open_price) + + common.writeLogEx('止盈价:' + str(closing_price), symbol) + common.writeLogEx('止损价:' + str(stop_loss_price), symbol) + + # 设置 - 止损价 + sl_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'slOrdPx': "-1", + 'slTriggerPx': stop_loss_price, + } + + sl_amount = common.multiply(stop_loss_price, sell_num) + # 解决平仓时,未全部平仓 + sl_amount = common.addition(sl_amount, 0.1) + common.writeLogEx('止损总价值:' + str(sl_amount), symbol) + common.writeLogEx('止损参数:' + json.dumps([symbol, 'limit', 'buy', float( + sl_amount), stop_loss_price, sl_exchange_params]), symbol) + sl_cond_data = exchange.create_order( + symbol, 'limit', 'buy', float(sl_amount), stop_loss_price, sl_exchange_params) + + common.writeLogEx('止损价数据:', symbol) + common.writeLogEx(json.dumps(sl_cond_data), symbol) + + tp_exchange_params = { + 'ccy': "USDT", + 'reduceOnly': True, + 'tdMode': "cross", + 'tpOrdPx': "-1", + 'tpTriggerPx': closing_price, + } + + # 设置 -止盈价 + # tp_amount = closing_price * sell_num + tp_amount = common.multiply(closing_price, sell_num) + # 解决平仓时,未全部平仓 + tp_amount = common.addition(tp_amount, 0.1) + common.writeLogEx('止盈总价值:' + str(tp_amount), symbol) + common.writeLogEx('止盈参数:' + json.dumps([symbol, 'limit', 'buy', float( + tp_amount), closing_price, tp_exchange_params]), symbol) + tp_cond_data = exchange.create_order( + symbol, 'limit', 'buy', float(tp_amount), closing_price, tp_exchange_params) + + common.writeLogEx('止盈价数据:', symbol) + common.writeLogEx(json.dumps(tp_cond_data), symbol) + + common.writeLogEx('------做空 end----------------------------------', symbol) + return True, open_price, closing_price + + +def onSellOrder(symbol, stop_loss_price, profit=0.005, timeframe='15m'): + # 做空开仓 + # profit 百分比 + try: + return onSellOrderTry(symbol, stop_loss_price, profit, timeframe) + except Exception as e: + common.writeLogErrorEx(mw.getTracebackInfo(), symbol) + return False, 0, 0 + + +def getOnlineData(symbol, input_tf="15m", limit=500): + bars = exchange.fetch_ohlcv(symbol, timeframe=input_tf, limit=500) + df = pd.DataFrame(bars[:], columns=['timestamp', + 'open', 'high', 'low', 'close', 'volume']) + df['dt'] = pd.to_datetime(df['timestamp'], unit='ms') + df.set_index('dt', inplace=True) + df.index = df.index.tz_localize('UTC').tz_convert('Asia/Shanghai') + return df + + +def isKdj(last, last_pre): + # 判断是否是金叉 + if (float(last_pre['macd']) < 0) and (float(last['macd']) > 0): + return True + return False + + +def isDeadFork(last, last_pre): + # 判断是否是死叉 + if (float(last_pre['macd']) > 0) and (float(last['macd']) < 0): + return True + return False + + +def getTarget(df): + close = df['close'].values + + # vega + df['ema144'] = talib.EMA(np.array(close), timeperiod=144) + df['ema169'] = talib.EMA(np.array(close), timeperiod=169) + + df['ema288'] = talib.EMA(np.array(close), timeperiod=288) + df['ema388'] = talib.EMA(np.array(close), timeperiod=388) + + df['rsi'] = talib.RSI(close, timeperiod=14) + return df + + +# 多头信号 +def isBuyCrondSignal(last): + if last['ema'] > last['ma'] and (last['rsi'] > 50 and last['rsi'] < 70) and last['low'] >= last['ma'] and last['close'] > last['open']: + return True + return False + + +def isBuyFristSignal(data): + data = data.sort_values(by=['timestamp'], ascending=False) + # print(data) + data_len = len(data) + signal_num = 0 + + first_data = data.iloc[1] + # print(1, first_data['close'], first_data['ema']) + is_buy_signal = isBuyCrondSignal(first_data) + + for x in range(2, data_len): + tmp = data.iloc[x] + # print(data.iloc[x]) + # print(x, tmp['close'], tmp['ema']) + if isBuyCrondSignal(tmp): + signal_num = + 1 + + # print('signal_num:', signal_num) + + if (tmp['ema'] < tmp['ma']): + break + + if str(tmp['ema']) == 'nan': + break + + print("is_buy_signal:", is_buy_signal, 'signal_num:', signal_num) + if is_buy_signal and signal_num == 0: + return True + + return False + + +# 空头信号 +def isSellCrondSignal(last): + if last['ema'] < last['ma'] and (last['rsi'] > 30 and last['rsi'] < 50) and last['high'] <= last['ema'] and last['close'] < last['open']: + return True + return False + + +def isSellFristSignal(data): + data = data.sort_values(by=['timestamp'], ascending=False) + # print(data) + data_len = len(data) + signal_num = 0 + + first_data = data.iloc[1] + # print(1, first_data['close'], first_data['ema']) + is_sell_signal = isSellCrondSignal(first_data) + + for x in range(2, data_len): + tmp = data.iloc[x] + # print(data.iloc[x]) + # print(x, tmp['close'], tmp['ema']) + if isSellCrondSignal(tmp): + signal_num = + 1 + + # print('signal_num:', signal_num) + + if (tmp['ema'] < tmp['ma']): + break + + if str(tmp['ema']) == 'nan': + break + + print("is_sell_signal:", is_sell_signal, 'signal_num:', signal_num) + if is_sell_signal and signal_num == 0: + return True + + return False + + +def isDownTrendCover(data, tag, timeframe): + # print(tag, timeframe, "开始检测是否下跌趋势") + # 下跌趋势回调 + key_data = data.tail(2) + last_pre = key_data.iloc[0] + last = key_data.iloc[1] + + pdata = data.tail(11) + + for x in range(10): + t = pdata.iloc[x] + if t['low'] > t['ema388']: + print(tag, timeframe, "上升震荡行情过滤") + # print(t) + return False + + if last['ema144'] > last['ema388']: + return False + + print(tag, timeframe, "检查是下跌趋势!") + # 检查是否是下跌趋势 + if last['high'] > last['ema388']: + return True + + return False + + +def isUpTrendAndCover(data, tag, timeframe): + # print(tag, timeframe, "开始检测是否上升趋势") + # 上升趋势回调 + key_data = data.tail(2) + # print(key_data) + last_pre = key_data.iloc[0] + last = key_data.iloc[1] + + pdata = data.tail(11) + + for x in range(10): + t = pdata.iloc[x] + if t['low'] < t['ema144']: + print(tag, timeframe, "上升震荡行情过滤") + return False + + if last['ema144'] < last['ema388']: + return False + + print(tag, timeframe, "检查是上升趋势") + + # 检查是否是上升趋势 + if last['low'] < last['ema144']: + return True + return False + + +def vegaTrade(data, tag, timeframe): + data = getTarget(data) + # print(data) + + key_data = data.tail(2) + # print(key_data) + # last_pre = key_data.iloc[0] + last = key_data.iloc[1] + # print(last) + + obj = common.MsgTpl() + symbol = tag.upper() + '/USDT' + obj.setName(symbol) + obj.setStrategyName("VEGA交易策略|仅具有指导作用") + obj.setTimeFrame(timeframe) + obj.setOpenTime(last['addtime']) + + if isUpTrendAndCover(data, tag, timeframe): + obj.setStrategicDt('buy') + obj.setContent("VEGA上升趋势!回调做多~建议!") + + msg = obj.toText() + common.notifyMsg(msg, timeframe, tag) + common.writeLog(msg) + + if isDownTrendCover(data, tag, timeframe): + obj.setStrategicDt('sell') + obj.setContent("Vega下跌趋势!回调做空~建议!") + msg = obj.toText() + common.notifyMsg(msg, timeframe, tag) + common.writeLog(msg) + + +def mainProcess(tag, timeframe='5m'): + symbol = tag.upper() + '/USDT' + data = common.getDataFromDb_DF(timeframe, tag.lower()) + vegaTrade(data, tag, timeframe) + return True + + +def foreachList(): + tag_list = ['btc', 'xrp', 'eth', 'ltc', 'okb'] + for tag in tag_list: + trame_list = ['5m', '15m', '1h', '4h'] + for tf in trame_list: + mainProcess(tag, tf) + # time.sleep(1) + + +def longRun(): + while True: + try: + foreachList() + time.sleep(1) + except Exception as e: + print(mw.getTracebackInfo()) + time.sleep(3) + + +def debug(): + while True: + mainProcess('btc', '5m') + time.sleep(1) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'long': + longRun() + elif func == 'run': + debug() + else: + print('error') diff --git a/plugins/cryptocurrency_trade/ccxt/test/p1.py b/plugins/cryptocurrency_trade/ccxt/test/p1.py new file mode 100644 index 000000000..5ee453968 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/test/p1.py @@ -0,0 +1,128 @@ +''' +pip install git+https://github.com/catalyst-team/catalyst@master --upgrade + + +cd /www/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/test/p1.py + + +cd /Users/midoks/Desktop/mwdev/server/mdserver-web && source bin/activate + +python3 plugins/cryptocurrency_trade/ccxt/test/p1.py +''' + +from datetime import datetime +import akshare as ak +import pandas as pd +import backtrader as bt + + +class BollStrategy(bt.Strategy): # BOLL策略程序 + params = (("nk", 13), # 求均值的天数 + ('printlog', False),) # 打印log + + def __init__(self): # 初始化 + self.data_close = self.datas[0].close # 指定价格序列 + # 初始化交易指令、买卖价格和手续费 + self.order = None + self.buy_price = None + self.buy_comm = None + # Boll指标计算 + self.top = bt.indicators.BollingerBands( + self.datas[0], period=self.params.nk).top + self.bot = bt.indicators.BollingerBands( + self.datas[0], period=self.params.nk).bot + # 添加移动均线指标 + self.sma = bt.indicators.SimpleMovingAverage( + self.datas[0], period=self.params.nk) + + def next(self): # 买卖策略 + if self.order: # 检查是否有指令等待执行 + return + # 检查是否持仓 + """ + if not self.position: # 没有持仓 + if self.data_close[0] > self.sma[0]: # 执行买入条件判断:收盘价格上涨突破20日均线 + self.order = self.buy(size=100) # 执行买入 + else: + if self.data_close[0] < self.sma[0]: # 执行卖出条件判断:收盘价格跌破20日均线 + self.order = self.sell(size=100) # 执行卖出 + """ + if not self.position: # 没有持仓 + if self.data_close[0] < self.bot[0]: # 收盘价格跌破下轨 + self.log("BUY CREATE, %.2f" % self.data_close[0]) + self.order = self.buy() # 执行买入 + else: + if self.data_close[0] > self.top[0]: # 收盘价格上涨突破上轨 + self.log("SELL CREATE, %.2f" % self.data_close[0]) + self.order = self.sell() # 执行卖出 + + def log(self, txt, dt=None, do_print=False): # 日志函数 + if self.params.printlog or do_print: + dt = dt or self.datas[0].datetime.date(0) + print('%s, %s' % (dt.isoformat(), txt)) + + def notify_order(self, order): # 记录交易执行情况 + # 如果order为submitted/accepted,返回空 + if order.status in [order.Submitted, order.Accepted]: + return + # 指令为buy/sell,报告价格结果 + if order.status in [order.Completed]: + if order.isbuy(): + self.log( + f"买入:\n价格:{order.executed.price},\ + 成本:{order.executed.value},\ + 手续费:{order.executed.comm}" + ) + self.buyprice = order.executed.price + self.buycomm = order.executed.comm + else: + self.log( + f"卖出:\n价格:{order.executed.price},\ + 成本: {order.executed.value},\ + 手续费{order.executed.comm}" + ) + self.bar_executed = len(self) + + elif order.status in [order.Canceled, order.Margin, order.Rejected]: + self.log("交易失败") # 指令取消/交易失败, 报告结果 + self.order = None + + def notify_trade(self, trade): # 记录交易收益情况 + if not trade.isclosed: + return + self.log(f"策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}") + + def stop(self): # 回测结束后输出结果 + self.log("(BOLL线: %2d日) 期末总资金 %.2f" % + (self.params.nk, self.broker.getvalue()), do_print=True) + +code = "600036" # 股票代码 +start_cash = 1000000 # 初始自己为1000000 +stake = 100 # 单次交易数量为1手 +commfee = 0.0005 # 佣金为万5 +sdate = '20210101' # 回测时间段 +edate = '20220930' +cerebro = bt.Cerebro() # 创建回测系统实例 +# 利用AKShare获取股票的前复权数据的前6列 +df_qfq = ak.stock_zh_a_hist( + symbol=code, adjust="qfq", start_date=sdate, end_date=edate).iloc[:, :6] +# 处理字段命名,以符合Backtrader的要求 +df_qfq.columns = ['date', 'open', 'close', 'high', 'low', 'volume', ] +# 把date作为日期索引,以符合Backtrader的要求 +df_qfq.index = pd.to_datetime(df_qfq['date']) +start_date = datetime.strptime(sdate, "%Y%m%d") # 转换日期格式 +end_date = datetime.strptime(edate, "%Y%m%d") +# start_date=datetime(2022,1,4) +# end_date=datetime(2022,9,16) +data = bt.feeds.PandasData( + dataname=df_qfq, fromdate=start_date, todate=end_date) # 规范化数据格式 +cerebro.adddata(data) # 加载数据 +cerebro.addstrategy(BollStrategy, nk=13, printlog=True) # 加载交易策略 +cerebro.broker.setcash(start_cash) # broker设置资金 +cerebro.broker.setcommission(commission=commfee) # broker手续费 +cerebro.addsizer(bt.sizers.FixedSize, stake=stake) # 设置买入数量 +print("期初总资金: %.2f" % start_cash) +cerebro.run() # 运行回测 +end_value = cerebro.broker.getvalue() # 获取回测结束后的总资金 +print("期末总资金: %.2f" % end_value) +cerebro.plot() diff --git a/plugins/cryptocurrency_trade/ccxt/test/p2.py b/plugins/cryptocurrency_trade/ccxt/test/p2.py new file mode 100644 index 000000000..e5b84b8f5 --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/test/p2.py @@ -0,0 +1,154 @@ +''' +pip install git+https://github.com/catalyst-team/catalyst@master --upgrade + + +cd /www/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/test/p1.py + + +cd /Users/midoks/Desktop/mwdev/server/mdserver-web && source bin/activate + +python3 plugins/cryptocurrency_trade/ccxt/test/p1.py +''' + +from datetime import datetime +import akshare as ak +import pandas as pd +import backtrader as bt + + +import sys +import os + +sys.path.append(os.getcwd() + "/plugins/cryptocurrency_trade/ccxt/strategy") +import common + + +class BollStrategy(bt.Strategy): # BOLL策略程序 + params = (("nk", 13), # 求均值的天数 + ('printlog', False),) # 打印log + + def __init__(self): # 初始化 + self.data_close = self.datas[0].close # 指定价格序列 + # 初始化交易指令、买卖价格和手续费 + self.order = None + self.buy_price = None + self.buy_comm = None + # Boll指标计算 + self.top = bt.indicators.BollingerBands( + self.datas[0], period=self.params.nk).top + self.bot = bt.indicators.BollingerBands( + self.datas[0], period=self.params.nk).bot + # 添加移动均线指标 + self.sma = bt.indicators.SimpleMovingAverage( + self.datas[0], period=self.params.nk) + + def next(self): # 买卖策略 + if self.order: # 检查是否有指令等待执行 + return + # 检查是否持仓 + """ + if not self.position: # 没有持仓 + if self.data_close[0] > self.sma[0]: # 执行买入条件判断:收盘价格上涨突破20日均线 + self.order = self.buy(size=100) # 执行买入 + else: + if self.data_close[0] < self.sma[0]: # 执行卖出条件判断:收盘价格跌破20日均线 + self.order = self.sell(size=100) # 执行卖出 + """ + if not self.position: # 没有持仓 + if self.data_close[0] < self.bot[0]: # 收盘价格跌破下轨 + self.log("BUY CREATE, %.2f" % self.data_close[0]) + self.order = self.buy() # 执行买入 + else: + if self.data_close[0] > self.top[0]: # 收盘价格上涨突破上轨 + self.log("SELL CREATE, %.2f" % self.data_close[0]) + self.order = self.sell() # 执行卖出 + + def log(self, txt, dt=None, do_print=False): # 日志函数 + if self.params.printlog or do_print: + dt = dt or self.datas[0].datetime.date(0) + print('%s, %s' % (dt.isoformat(), txt)) + + def notify_order(self, order): # 记录交易执行情况 + # 如果order为submitted/accepted,返回空 + if order.status in [order.Submitted, order.Accepted]: + return + # 指令为buy/sell,报告价格结果 + if order.status in [order.Completed]: + if order.isbuy(): + self.log( + f"买入:\n价格:{order.executed.price},\ + 成本:{order.executed.value},\ + 手续费:{order.executed.comm}" + ) + self.buyprice = order.executed.price + self.buycomm = order.executed.comm + else: + self.log( + f"卖出:\n价格:{order.executed.price},\ + 成本: {order.executed.value},\ + 手续费{order.executed.comm}" + ) + self.bar_executed = len(self) + + elif order.status in [order.Canceled, order.Margin, order.Rejected]: + self.log("交易失败") # 指令取消/交易失败, 报告结果 + self.order = None + + def notify_trade(self, trade): # 记录交易收益情况 + if not trade.isclosed: + return + self.log(f"策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}") + + def stop(self): # 回测结束后输出结果 + self.log("(BOLL线: %2d日) 期末总资金 %.2f" % + (self.params.nk, self.broker.getvalue()), do_print=True) + +code = "600036" # 股票代码 +start_cash = 1000000 # 初始自己为1000000 +stake = 100 # 单次交易数量为1手 +commfee = 0.0005 # 佣金为万5 +sdate = '20210101' # 回测时间段 +edate = '20220930' +cerebro = bt.Cerebro() # 创建回测系统实例 +# 利用AKShare获取股票的前复权数据的前6列 +# df_qfq = ak.stock_zh_a_hist( +# symbol=code, adjust="qfq", start_date=sdate, end_date=edate).iloc[:, :6] +# 日期 开盘 收盘 最高 最低 成交量 +# 0 2021-01-04 40.46 40.40 41.01 39.25 1549523 +# 1 2021-01-05 39.99 39.41 40.03 38.67 1387177 +# 2 2021-01-06 39.33 41.38 41.43 39.23 1200646 +# 3 2021-01-07 41.52 43.13 43.20 41.49 1078322 +# 4 2021-01-08 43.52 43.83 44.25 43.04 1299595 +# .. ... ... ... ... ... ... +# 420 2022-09-26 34.02 33.95 34.52 33.84 525491 +# 421 2022-09-27 33.89 33.90 34.05 33.44 475414 +# 422 2022-09-28 33.79 33.67 34.00 33.42 487299 +# 423 2022-09-29 33.99 33.15 34.32 33.00 617859 +# 424 2022-09-30 33.26 33.65 33.95 33.12 539717 + +tag = 'btc' +symbol = tag.upper() + '/USDT' +df_qfq = common.getDataFromDb_DF('15m', tag.lower()) +print(df_qfq) + +# # 处理字段命名,以符合Backtrader的要求 +# df_qfq.columns = ['date', 'open', 'close', 'high', 'low', 'volume', ] +# # 把date作为日期索引,以符合Backtrader的要求 +# df_qfq.index = pd.to_datetime(df_qfq['date']) +# start_date = datetime.strptime(sdate, "%Y%m%d") # 转换日期格式 +# end_date = datetime.strptime(edate, "%Y%m%d") +start_date = datetime(2023, 4, 6) +end_date = datetime(2023, 4, 9) +data = bt.feeds.PandasData(dataname=df_qfq, fromdate=start_date, + todate=end_date) # 规范化数据格式 + +cerebro.adddata(data) # 加载数据 +cerebro.addstrategy(BollStrategy, nk=13, printlog=True) # 加载交易策略 +cerebro.broker.setcash(start_cash) # broker设置资金 +cerebro.broker.setcommission(commission=commfee) # broker手续费 +cerebro.addsizer(bt.sizers.FixedSize, stake=stake) # 设置买入数量 +print("期初总资金: %.2f" % start_cash) +cerebro.run() # 运行回测 +end_value = cerebro.broker.getvalue() # 获取回测结束后的总资金 +print("期末总资金: %.2f" % end_value) +# cerebro.plot(style='candle') diff --git a/plugins/cryptocurrency_trade/ccxt/test/p_test.py b/plugins/cryptocurrency_trade/ccxt/test/p_test.py new file mode 100644 index 000000000..df28468cf --- /dev/null +++ b/plugins/cryptocurrency_trade/ccxt/test/p_test.py @@ -0,0 +1,61 @@ +# coding:utf-8 + +import pandas as pd + +import matplotlib.pyplot as plt + + +from catalyst import run_algorithm +from catalyst.api import order, record, symbol + +''' +cd /Users/midoks/Desktop/mwdev/server/mdserver-web && source bin/activate + + +cd /www/server/mdserver-web && source bin/activate && source activate catalys + +cd /Users/midoks/Desktop/mwdev/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/test/p_test.py +catalyst ingest-exchange -x binance -i btc_usdt -f minute + +cd /www/server/mdserver-web && python3 plugins/cryptocurrency_trade/ccxt/test/p_test.py +''' + + +def initialize(context): + # 初始化 + context.asset = symbol('btc_usdt') + + +def handle_data(context, data): + # 循环策略 + order(context.asset, 1) + record(btc=data.current(context.asset, 'price')) + + +def analyze(context, perf): + + print(perf.portfolio_value) + + ax1 = plt.subplot(211) + perf.portfolio_value.plot(ax=ax1) + + ax1.set_ylabel('portfolio value') + + ax2 = plt.subplot(212, sharex=ax1) + perf.btc.plot(ax=ax2) + ax2.set_ylabel('bitcoin value') + plt.show() + + +if __name__ == "__main__": + run_algorithm( + capital_base=10000, + data_frequency='daily', + initialize=initialize, + handle_data=handle_data, + analyze=analyze, + exchange_name='binance', + quote_currenty='usdt', + start=pd.to_datetime("2018-01-01", utc=True), + end=pd.to_datetime("2018-10-01", utc=True), + ) diff --git a/plugins/cryptocurrency_trade/conf/create.sql b/plugins/cryptocurrency_trade/conf/create.sql new file mode 100644 index 000000000..19e087958 --- /dev/null +++ b/plugins/cryptocurrency_trade/conf/create.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS `ct_xx1_xx2` ( + `addtime` BIGINT(20) not NULL, + `open` float NOT NULL, + `high` float NOT NULL, + `low` float NOT NULL, + `close` float NOT NULL, + `vol` float NOT NULL, + UNIQUE KEY `addtime` (`addtime`) +); diff --git a/plugins/cryptocurrency_trade/conf/order.sql b/plugins/cryptocurrency_trade/conf/order.sql new file mode 100644 index 000000000..9e7f14c95 --- /dev/null +++ b/plugins/cryptocurrency_trade/conf/order.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS `ct_order_list` ( + `id` BIGINT(20) not NULL, + 'strategy_name' varchar(50) NULL, + `symbol` varchar(50) NOT NULL, + `fee` float NOT NULL, + `price` float NOT NULL, + `closing_price` float NOT NULL comment '', + `profit` float NOT NULL, + `source` TEXT, + `addtime` BIGINT(20) not NULL +); diff --git a/plugins/cryptocurrency_trade/conf/sup_strategy.tpl b/plugins/cryptocurrency_trade/conf/sup_strategy.tpl new file mode 100644 index 000000000..c19824932 --- /dev/null +++ b/plugins/cryptocurrency_trade/conf/sup_strategy.tpl @@ -0,0 +1,14 @@ +[program:{$NAME}] +command=bash -c "cd {$RUN_ROOT} && source bin/activate && python3 {$ABS_FILE} long" +directory={$RUN_ROOT} +autorestart=true +startsecs=3 +startretries=3 +stdout_logfile={$SUP_ROOT}/log/{$NAME}.out.log +stderr_logfile={$SUP_ROOT}/log/{$NAME}.err.log +stdout_logfile_maxbytes=2MB +stderr_logfile_maxbytes=2MB +user=root +priority=999 +numprocs=1 +process_name=%(program_name)s \ No newline at end of file diff --git a/plugins/cryptocurrency_trade/conf/sup_task.tpl b/plugins/cryptocurrency_trade/conf/sup_task.tpl new file mode 100644 index 000000000..fe0c99d7f --- /dev/null +++ b/plugins/cryptocurrency_trade/conf/sup_task.tpl @@ -0,0 +1,14 @@ +[program:{$NAME}] +command=bash -c "cd {$RUN_ROOT} && source bin/activate && python3 plugins/cryptocurrency_trade/ccxt/public_data/data.py long" +directory={$RUN_ROOT} +autorestart=true +startsecs=3 +startretries=3 +stdout_logfile={$SUP_ROOT}/log/{$NAME}.out.log +stderr_logfile={$SUP_ROOT}/log/{$NAME}.err.log +stdout_logfile_maxbytes=2MB +stderr_logfile_maxbytes=2MB +user=root +priority=999 +numprocs=1 +process_name=%(program_name)s \ No newline at end of file diff --git a/plugins/cryptocurrency_trade/cryptocurrency_trade.py b/plugins/cryptocurrency_trade/cryptocurrency_trade.py new file mode 100644 index 000000000..c87d8e8b0 --- /dev/null +++ b/plugins/cryptocurrency_trade/cryptocurrency_trade.py @@ -0,0 +1,350 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import string +import subprocess +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'cryptocurrency_trade' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace( + '{$SERVER_APP}', service_path + '/' + getPluginName()) + return content + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def isSqlError(mysqlMsg): + # 检测数据库执行错误 + mysqlMsg = str(mysqlMsg) + if "MySQLdb" in mysqlMsg: + return mw.returnJson(False, 'MySQLdb组件缺失!
    进入SSH命令行输入: pip install mysql-python | pip install mysqlclient==2.0.3') + if "2002," in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "2003," in mysqlMsg: + return mw.returnJson(False, "Can't connect to MySQL server on '127.0.0.1' (61)") + if "using password:" in mysqlMsg: + return mw.returnJson(False, '数据库密码错误') + if "1045" in mysqlMsg: + return mw.returnJson(False, '连接错误!') + if "SQL syntax" in mysqlMsg: + return mw.returnJson(False, 'SQL语法错误!') + if "Connection refused" in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "1133," in mysqlMsg: + return mw.returnJson(False, '数据库用户不存在!') + if "1007," in mysqlMsg: + return mw.returnJson(False, '数据库已经存在!') + return None + + +def getConfigData(): + cfg_path = getServerDir() + "/data.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeConf(data): + cfg_path = getServerDir() + "/data.cfg" + mw.writeFile(cfg_path, json.dumps(data)) + return True + + +def getDbConf(): + data = getConfigData() + if 'db' in data: + return mw.returnJson(True, 'ok', data['db']) + return mw.returnJson(False, 'ok', {}) + + +def getUserConf(): + data = getConfigData() + if 'user' in data: + return mw.returnJson(True, 'ok', data['user']) + return mw.returnJson(False, 'ok', {}) + + +def restartSup(): + cmd = 'python3 plugins/supervisor/index.py restart' + mw.execShell(cmd) + + +def restartSupDst(name): + cmd = 'python3 plugins/supervisor/index.py restart_job {"name":"' + \ + name + '","status":"stop"}' + mw.execShell(cmd) + + +def syncDataAddTaskUninstall(): + sup_path = mw.getServerDir() + '/supervisor' + if not os.path.exists(sup_path): + return mw.returnJson(False, '需要安装并启动supervisor插件') + + name = "ct_task" + sup_task_dst = sup_path + '/conf.d/' + name + '.ini' + if os.path.exists(sup_task_dst): + mw.execShell('rm -rf ' + sup_task_dst) + + restartSup() + return mw.returnJson(True, '删除同步数据任务成功!') + + +def syncDataAddTaskInstall(): + sup_path = mw.getServerDir() + '/supervisor' + if not os.path.exists(sup_path): + return mw.returnJson(False, '需要安装并启动supervisor插件') + + name = "ct_task" + sup_task_tpl = getPluginDir() + '/conf/sup_task.tpl' + sup_task_dst = sup_path + '/conf.d/' + name + '.ini' + content = mw.readFile(sup_task_tpl) + content = content.replace( + '{$RUN_ROOT}', mw.getServerDir() + '/mdserver-web') + content = content.replace( + '{$SUP_ROOT}', sup_path) + content = content.replace( + '{$NAME}', name) + + mw.writeFile(sup_task_dst, content) + restartSup() + return mw.returnJson(True, '添加同步数据任务成功!') + + +def syncDataAddTask(): + args = getArgs() + data_args = checkArgs(args, ['check']) + if not data_args[0]: + return data_args[1] + + if args['check'] == "0": + return syncDataAddTaskUninstall() + return syncDataAddTaskInstall() + + +def syncDataDelete(): + args = getArgs() + data_args = checkArgs(args, ['token']) + if not data_args[0]: + return data_args[1] + + del_token = args['token'] + data = getConfigData() + + if 'token' in data: + data['token'].remove(del_token) + writeConf(data) + + return mw.returnJson(True, '删除成功!') + + +# callback ---------------------------------- start +def get_datasource_logs(args): + log_file = getServerDir() + '/logs/datasource.log' + if not os.path.exists(log_file): + return '暂无日志' + data = mw.getLastLine(log_file, 10) + return data + + +def get_strategy_logs(args): + log_file = getServerDir() + '/logs/strategy.log' + if not os.path.exists(log_file): + return '暂无日志' + data = mw.getLastLine(log_file, 10) + return data + + +def save_body(args): + + path = args['path'] + encoding = args['encoding'] + data = args['data'] + + tag = args['tag'] + + if not os.path.exists(path): + return mw.returnData(False, '文件不存在') + try: + if encoding == 'ascii': + encoding = 'utf-8' + + data = data.encode( + encoding, errors='ignore').decode(encoding) + + fp = open(path, 'w+', encoding=encoding) + fp.write(data) + fp.close() + + set_strategy_restart({'id': tag}) + return mw.returnData(True, '文件保存成功') + except Exception as ex: + return mw.returnData(False, '文件保存错误:' + str(ex)) + + +def get_strategy_path(args): + abs_id = args['id'] + name = "ct_strategy_" + abs_id + + abs_file = get_strategy_absfile(abs_id) + return mw.returnData(True, abs_file) + + +def set_strategy_restart(args): + sup_path = mw.getServerDir() + '/supervisor' + if not os.path.exists(sup_path): + return mw.returnData(False, '需要安装并启动supervisor插件') + + abs_id = args['id'] + name = "ct_strategy_" + abs_id + + sup_strategy_dst = sup_path + '/conf.d/' + name + '.ini' + + if not os.path.exists(sup_strategy_dst): + return mw.returnData(False, '策略任务' + abs_id + '未添加!') + + restartSupDst(name) + return mw.returnData(True, '重启策略任务' + abs_id + '成功!') + + +def set_strategy_status(args): + sup_path = mw.getServerDir() + '/supervisor' + if not os.path.exists(sup_path): + return mw.returnData(False, '需要安装并启动supervisor插件') + + abs_id = args['id'] + name = "ct_strategy_" + abs_id + sup_strategy_dst = sup_path + '/conf.d/' + name + '.ini' + + if args['status'] == 'stop': + if os.path.exists(sup_strategy_dst): + os.remove(sup_strategy_dst) + restartSup() + return mw.returnData(True, '删除策略任务' + abs_id + '成功!') + + abs_file = get_strategy_absfile(abs_id) + sup_strategy_tpl = getPluginDir() + '/conf/sup_strategy.tpl' + + content = mw.readFile(sup_strategy_tpl) + content = content.replace( + '{$RUN_ROOT}', mw.getServerDir() + '/mdserver-web') + content = content.replace( + '{$SUP_ROOT}', sup_path) + content = content.replace( + '{$NAME}', name) + content = content.replace( + '{$ABS_FILE}', abs_file) + + mw.writeFile(sup_strategy_dst, content) + restartSup() + return mw.returnData(True, '添加策略任务' + abs_id + '成功!') + + +def get_strategy_absfile(abs_id): + info = getPluginDir() + '/ccxt/strategy/info.json' + info = json.loads(mw.readFile(info)) + + path = getPluginDir() + '/ccxt/strategy' + for x in range(len(info)): + if info[x]['id'] == abs_id: + return path + '/' + info[x]['file'] + + return path + '/abs.py' + + +def get_strategy_list(args): + info = getPluginDir() + '/ccxt/strategy/info.json' + info = json.loads(mw.readFile(info)) + + st_path = mw.getServerDir() + '/supervisor/conf.d' + + page = 1 + page_size = 5 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + dlist_sum = len(info) + + page_start = int((page - 1) * page_size) + page_end = page_start + page_size + + if page_end >= dlist_sum: + ret_data = info[page_start:] + else: + ret_data = info[page_start:page_end] + + for x in range(len(ret_data)): + strategy_dst = st_path + '/ct_strategy_' + ret_data[x]['id'] + '.ini' + if os.path.exists(strategy_dst): + ret_data[x]['status'] = 'start' + else: + ret_data[x]['status'] = 'stop' + + data['data'] = ret_data + data['args'] = args + data['list'] = mw.getPage( + {'count': dlist_sum, 'p': page, 'row': page_size, 'tojs': 'getStrategyList'}) + + return data +# callback ---------------------------------- end diff --git a/plugins/cryptocurrency_trade/ico.png b/plugins/cryptocurrency_trade/ico.png new file mode 100644 index 000000000..91a234ba1 Binary files /dev/null and b/plugins/cryptocurrency_trade/ico.png differ diff --git a/plugins/cryptocurrency_trade/index.html b/plugins/cryptocurrency_trade/index.html new file mode 100755 index 000000000..88ca8ef2e --- /dev/null +++ b/plugins/cryptocurrency_trade/index.html @@ -0,0 +1,26 @@ + +
    +
    +
    +

    服务

    +

    数据库配置

    +

    同步数据配置

    +

    账户配置

    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/plugins/cryptocurrency_trade/index.py b/plugins/cryptocurrency_trade/index.py new file mode 100644 index 000000000..1aaf159ad --- /dev/null +++ b/plugins/cryptocurrency_trade/index.py @@ -0,0 +1,347 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import string +import subprocess +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'cryptocurrency_trade' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace( + '{$SERVER_APP}', service_path + '/' + getPluginName()) + return content + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def isSqlError(mysqlMsg): + # 检测数据库执行错误 + mysqlMsg = str(mysqlMsg) + if "MySQLdb" in mysqlMsg: + return mw.returnJson(False, 'MySQLdb组件缺失!
    进入SSH命令行输入: pip install mysql-python | pip install mysqlclient==2.0.3') + if "2002," in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "2003," in mysqlMsg: + return mw.returnJson(False, "Can't connect to MySQL server on '127.0.0.1' (61)") + if "using password:" in mysqlMsg: + return mw.returnJson(False, '数据库密码错误') + if "1045" in mysqlMsg: + return mw.returnJson(False, '连接错误!') + if "SQL syntax" in mysqlMsg: + return mw.returnJson(False, 'SQL语法错误!') + if "Connection refused" in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "1133" in mysqlMsg: + return mw.returnJson(False, '数据库用户不存在!') + if "1007" in mysqlMsg: + return mw.returnJson(False, '数据库已经存在!') + return None + + +def initDreplace(): + log_dir = getServerDir() + '/logs' + if not os.path.exists(log_dir): + d = mw.execShell('mkdir -p ' + log_dir) + + data = getConfigData() + data['token'] = ['btc'] + writeConf(data) + + return True + + +def status(): + initDreplace() + return 'start' + + +def start(): + syncDataAddTaskInstall() + return 'ok' + + +def stop(): + syncDataAddTaskUninstall() + return 'ok' + + +def op(): + return 'ok' + + +def getConfigData(): + cfg_path = getServerDir() + "/data.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeConf(data): + cfg_path = getServerDir() + "/data.cfg" + mw.writeFile(cfg_path, json.dumps(data)) + return True + + +def getDbConf(): + data = getConfigData() + if 'db' in data: + return mw.returnJson(True, 'ok', data['db']) + return mw.returnJson(False, 'ok', {}) + + +def setDbConf(): + args = getArgs() + data_args = checkArgs(args, ['db_host', 'db_port', + 'db_name', 'db_user', 'db_pass']) + if not data_args[0]: + return data_args[1] + + data = getConfigData() + data['db'] = args + writeConf(data) + + db = mw.getMyORM() + + db.setHost(args['db_host']) + db.setPort(args['db_port']) + db.setUser(args['db_user']) + db.setPwd(args['db_pass']) + testdata = db.query('select version()') + + isError = isSqlError(testdata) + if isError != None: + return isError + + return mw.returnJson(True, '保存成功,并连通成功!', []) + + +def getUserConf(): + data = getConfigData() + if 'user' in data: + return mw.returnJson(True, 'ok', data['user']) + return mw.returnJson(False, 'ok', {}) + + +def getUserConf(): + data = getConfigData() + if 'user' in data: + try: + udata = mw.deDoubleCrypt('mw', data['user']) + udata = json.loads(udata) + + return mw.returnJson(True, 'ok', udata) + except Exception as e: + pass + return mw.returnJson(False, 'ok', {}) + + +def setUserConf(): + args = getArgs() + data_args = checkArgs(args, ['app_key', 'secret', + 'password', 'uid', 'exchange']) + if not data_args[0]: + return data_args[1] + + data = getConfigData() + data['user'] = mw.enDoubleCrypt('mw', json.dumps(args)) + writeConf(data) + + return mw.returnJson(True, '保存成功!', []) + + +def syncDataList(): + data = getConfigData() + + name = "ct_task" + if 'token' in data: + + rdata = {} + rdata['task_status'] = False + sup_path = mw.getServerDir() + '/supervisor' + sup_task_dst = sup_path + '/conf.d/' + name + '.ini' + if os.path.exists(sup_task_dst): + rdata['task_status'] = True + + rdata['list'] = data['token'] + return mw.returnJson(True, 'ok', rdata) + return mw.returnJson(False, 'ok') + + +def syncDataAdd(): + args = getArgs() + data_args = checkArgs(args, ['token']) + if not data_args[0]: + return data_args[1] + + add_token = args['token'] + data = getConfigData() + + if 'token' in data and data['token']: + if not add_token in data['token']: + data['token'].append(add_token) + else: + data['token'] = [add_token] + writeConf(data) + + return mw.returnJson(True, '保存成功!') + + +def restartSup(): + cmd = 'python3 plugins/supervisor/index.py restart' + mw.execShell(cmd) + + +def restartSupDst(name): + cmd = 'python3 plugins/supervisor/index.py restart_job {"name":"' + \ + name + '","status":"stop"}' + mw.execShell(cmd) + + +def syncDataAddTaskUninstall(): + sup_path = mw.getServerDir() + '/supervisor' + if not os.path.exists(sup_path): + return mw.returnJson(False, '需要安装并启动supervisor插件') + + name = "ct_task" + sup_task_dst = sup_path + '/conf.d/' + name + '.ini' + if os.path.exists(sup_task_dst): + mw.execShell('rm -rf ' + sup_task_dst) + + restartSup() + return mw.returnJson(True, '删除同步数据任务成功!') + + +def syncDataAddTaskInstall(): + sup_path = mw.getServerDir() + '/supervisor' + if not os.path.exists(sup_path): + return mw.returnJson(False, '需要安装并启动supervisor插件') + + name = "ct_task" + sup_task_tpl = getPluginDir() + '/conf/sup_task.tpl' + sup_task_dst = sup_path + '/conf.d/' + name + '.ini' + content = mw.readFile(sup_task_tpl) + content = content.replace( + '{$RUN_ROOT}', mw.getServerDir() + '/mdserver-web') + content = content.replace( + '{$SUP_ROOT}', sup_path) + content = content.replace( + '{$NAME}', name) + + mw.writeFile(sup_task_dst, content) + restartSup() + return mw.returnJson(True, '添加同步数据任务成功!') + + +def syncDataAddTask(): + args = getArgs() + data_args = checkArgs(args, ['check']) + if not data_args[0]: + return data_args[1] + + if args['check'] == "0": + return syncDataAddTaskUninstall() + return syncDataAddTaskInstall() + + +def syncDataDelete(): + args = getArgs() + data_args = checkArgs(args, ['token']) + if not data_args[0]: + return data_args[1] + + del_token = args['token'] + data = getConfigData() + + if 'token' in data: + data['token'].remove(del_token) + writeConf(data) + + return mw.returnJson(True, '删除成功!') + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(op()) + elif func == 'reload': + print(op()) + elif func == 'get_db_conf': + print(getDbConf()) + elif func == 'set_db_conf': + print(setDbConf()) + elif func == 'get_user_conf': + print(getUserConf()) + elif func == 'set_user_conf': + print(setUserConf()) + elif func == 'sync_data_list': + print(syncDataList()) + elif func == 'sync_data_add': + print(syncDataAdd()) + elif func == 'sync_data_delete': + print(syncDataDelete()) + elif func == 'sync_data_add_task': + print(syncDataAddTask()) + else: + print('error') diff --git a/plugins/cryptocurrency_trade/info.json b/plugins/cryptocurrency_trade/info.json new file mode 100755 index 000000000..48647f7d4 --- /dev/null +++ b/plugins/cryptocurrency_trade/info.json @@ -0,0 +1,36 @@ +{ + "hook":[ + { + "tag":"menu", + "menu": { + "title":"量化交易", + "name":"cryptocurrency_trade", + "path":"static/html/index.html", + "css_path":"static/css/cryptocurrency_trade.css", + "js_path":"static/js/cryptocurrency_trade.js" + } + }, + { + "tag":"global_static", + "global_static": { + "title":"量化交易", + "name":"cryptocurrency_trade", + "css_path":"static/css/ico.css" + } + } + ], + "ps": "基于CCXT的数字货币量化交易插件", + "name": "cryptocurrency_trade", + "title": "量化交易", + "versions": ["1.0"], + "tip": "soft", + "checks": "server/cryptocurrency_trade", + "path":"server/cryptocurrency_trade", + "author": "midoks", + "date": "2022-02-25", + "home": "https://github.com/ccxt/ccxt", + "type": "量化交易", + "shell": "install.sh", + "pid": "5", + "sort": 7 +} \ No newline at end of file diff --git a/plugins/cryptocurrency_trade/install.sh b/plugins/cryptocurrency_trade/install.sh new file mode 100755 index 000000000..5a24fde9c --- /dev/null +++ b/plugins/cryptocurrency_trade/install.sh @@ -0,0 +1,63 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +# cd /www/server/mdserver-web/plugins/cryptocurrency_trade && bash install.sh install + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + + +VERSION=$2 + +# pip3 install ccxt +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi +pip3 install ccxt +pip3 install pandas +pip3 install pandas_ta +pip3 install pyTelegramBotAPI +pip3 install catalyst +pip3 install matplotlib==3.2.2 + + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/cryptocurrency_trade + mkdir -p $serverPath/cryptocurrency_trade + echo "${VERSION}" > $serverPath/cryptocurrency_trade/version.pl + + if [ ! -f $serverPath/source/cryptocurrency_trade/ta-lib-0.4.0-src.tar.gz ];then + wget -O $serverPath/source/cryptocurrency_trade/ta-lib-0.4.0-src.tar.gz https://sourceforge.net/projects/ta-lib/files/ta-lib/0.4.0/ta-lib-0.4.0-src.tar.gz + fi + + if [ ! -d $serverPath/source/cryptocurrency_trade/ta-lib ];then + cd $serverPath/source/cryptocurrency_trade/ta-lib && tar -xzf ta-lib-0.4.0-src.tar.gz + cd ta-lib && ./configure --prefix=/usr && make && make install + rm -rf $serverPath/source/cryptocurrency_trade/ta-lib + + pip3 install ta-lib + fi + + cd ${rootPath} && python3 ${rootPath}/plugins/cryptocurrency_trade/index.py start + echo '安装完成' + +} + +Uninstall_App() +{ + rm -rf $serverPath/cryptocurrency_trade + cd ${rootPath} && python3 ${rootPath}/plugins/cryptocurrency_trade/index.py stop + echo "卸载完成" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/cryptocurrency_trade/static/css/cryptocurrency_trade.css b/plugins/cryptocurrency_trade/static/css/cryptocurrency_trade.css new file mode 100644 index 000000000..c55e5f461 --- /dev/null +++ b/plugins/cryptocurrency_trade/static/css/cryptocurrency_trade.css @@ -0,0 +1,11 @@ +.screen-all { + padding: 5px; +} + +.s-right .panel-default{ + margin-bottom: 10px; +} + +.s-left .panel-default{ + margin-bottom: 10px; +} diff --git a/plugins/cryptocurrency_trade/static/css/ico.css b/plugins/cryptocurrency_trade/static/css/ico.css new file mode 100644 index 000000000..86f5b6b81 --- /dev/null +++ b/plugins/cryptocurrency_trade/static/css/ico.css @@ -0,0 +1,11 @@ + +/* menu start */ +.menu .current .menu_plugin_cryptocurrency_trade:hover { + background-image: url("/plugins/file?name=cryptocurrency_trade&f=ico.png"); +} + +.menu .menu_plugin_cryptocurrency_trade { + background-image: url("/plugins/file?name=cryptocurrency_trade&f=ico.png"); +} + +/* menu end */ \ No newline at end of file diff --git a/plugins/cryptocurrency_trade/static/html/index.html b/plugins/cryptocurrency_trade/static/html/index.html new file mode 100644 index 000000000..c6280700a --- /dev/null +++ b/plugins/cryptocurrency_trade/static/html/index.html @@ -0,0 +1,65 @@ + +
    +
    +
    + + +
    + +
    +
    订单数据
    +
    Panel content
    +
    + +
    +
    交易图表
    +
    +
    +
    +
    +
    + +
    +
    策略交易日志
    +
    +
    当前没有日志.
    +
    +
    + +
    + +
    + +
    +
    策略配置
    +
    + + + + + + + + + + +
    序列说明状态操作
    + +
    +
    + +
    +
    数据源日志
    +
    +
    当前没有日志.
    +
    +
    + +
    + + +
    +
    +
    \ No newline at end of file diff --git a/plugins/cryptocurrency_trade/static/js/cryptocurrency_trade.js b/plugins/cryptocurrency_trade/static/js/cryptocurrency_trade.js new file mode 100644 index 000000000..9bcc03a68 --- /dev/null +++ b/plugins/cryptocurrency_trade/static/js/cryptocurrency_trade.js @@ -0,0 +1,964 @@ + +function appPost(method,args,callback, title){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'cryptocurrency_trade', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function appPostN(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + $.post('/plugins/run', {name:'cryptocurrency_trade', func:method, args:_args}, function(data) { + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function appAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + return syncPost('/plugins/run', {name:'cryptocurrency_trade', func:method, args:_args}); +} + + +function appPostCallbak(method, args,callback, script){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'cryptocurrency_trade'; + req_data['func'] = method; + + if (typeof(script) != 'undefined'){ + req_data['script'] = script; + } + + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function appPostCallbakNoMsg(method, args,callback, script){ + + var req_data = {}; + req_data['name'] = 'cryptocurrency_trade'; + req_data['func'] = method; + + if (typeof(script) != 'undefined'){ + req_data['script'] = script; + } else { + req_data['script'] = req_data['name']; + } + + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function dbConf(){ + appPost('get_db_conf','',function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var db_host = '127.0.0.1'; + var db_port = '3306'; + var db_name = 'cryptocurrency_trade'; + var db_user = 'cryptocurrency_trade'; + var db_pass = 'cryptocurrency_trade'; + if(rdata['status']){ + db_data = rdata['data']; + db_host = db_data['db_host']; + db_port = db_data['db_port']; + db_name = db_data['db_name']; + db_user = db_data['db_user']; + db_pass = db_data['db_pass']; + } + + var mlist = ''; + mlist += '

    数据库地址

    ' + mlist += '

    数据库端口

    ' + mlist += '

    数据库名称

    ' + mlist += '

    用户名

    ' + mlist += '

    密码

    ' + + var option = '\ +
    \ + ' + mlist + '\ +
    \ + \ +
    \ +
    '; + $(".soft-man-con").html(option); + }); +} + +function submitDbConf(){ + + var pull_data = {}; + + pull_data['db_host'] = $('input[name="db_host"]').val(); + pull_data['db_port'] = $('input[name="db_port"]').val(); + pull_data['db_name'] = $('input[name="db_name"]').val(); + pull_data['db_user'] = $('input[name="db_user"]').val(); + pull_data['db_pass'] = $('input[name="db_pass"]').val(); + + appPost('set_db_conf',pull_data,function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000,shade: [0.3, '#000']}); + }); +} + + +function userConf(){ + appPost('get_user_conf','',function(data){ + var rdata = $.parseJSON(data.data); + var app_key = 'app_key'; + var secret = 'secret'; + var password = 'password'; + var uid = 'uid'; + var exchange = 'okex'; + if(rdata['status']){ + db_data = rdata['data']; + app_key = db_data['app_key']; + secret = db_data['secret']; + password = db_data['password']; + uid = db_data['uid']; + exchange = db_data['exchange'];; + } + + var mlist = ''; + mlist += '

    交易所必填写[okex, binance]

    ' + mlist += '

    apiKey必填写

    ' + mlist += '

    secret必填写

    ' + mlist += '

    password根据情况填写

    ' + mlist += '

    uid根据情况填写

    ' + + var option = '\ +
    \ + ' + mlist + '\ +
    \ + \ +
    \ +
    '; + $(".soft-man-con").html(option); + }); +} + + +function submitUserConf(){ + var pull_data = {}; + + pull_data['app_key'] = $('input[name="app_key"]').val(); + pull_data['secret'] = $('input[name="secret"]').val(); + pull_data['password'] = $('input[name="password"]').val(); + pull_data['uid'] = $('input[name="uid"]').val(); + pull_data['exchange'] = $('input[name="exchange"]').val(); + + appPost('set_user_conf',pull_data,function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000,shade: [0.3, '#000']}); + }); +} + +function syncDataList(){ + appPost('sync_data_list', {}, function(data){ + var rdata = $.parseJSON(data.data); + + + var list = ''; + if (rdata['status']){ + var dlist = rdata['data']['list']; + for(i in dlist){ + + list += ''; + list += '' + dlist[i] +''; + list += '' + + '删除' + + ''; + list += ''; + } + } + + + if( list == '' ){ + list = "当前没有数据"; + } + + var task_status = rdata['data']['task_status']; + var task_status_check = ''; + if (task_status){ + task_status_check = 'checked'; + } + + var con = '
    \ +
    \ + \ +
    \ + 是否启动\ +
    \ + \ + \ +
    \ +
    \ +
    \ +
    \ +
    \ + \ + \ + \ + \ + \ + '+ list +'\ +
    名称操作
    \ +
    \ +
    \ +
    '; + + con += '
    \ + 详细如下:\ + *:添加同步的币种,都小写,以USDT为本币同步数据。\ + *:需要提前安装supervisor插件。\ +
    ' + + $(".soft-man-con").html(con); + $('#databasePage').html(rdata.page); + }); +} + +function syncDataAddTask(){ + var at_check = $('#add_task').prop('checked'); + appPost("sync_data_add_task", {'check':at_check?'0':'1'}, function(data){ + rdata = $.parseJSON(data.data); + + showMsg(rdata.msg,function(){ + if (rdata.status){ + syncDataList(); + } + },{icon:rdata.status?1:2}); + }); +} + +function syncDataDelete(name){ + appPost("sync_data_delete", {"token":name}, function(data){ + rdata = $.parseJSON(data.data); + + showMsg(rdata.msg,function(){ + if (rdata.status){ + syncDataList(); + } + },{icon:rdata.status?1:2}); + }); +} + +function syncDataAdd() { + layer.open({ + type: 1, + area: '500px', + title: '添加同步数据', + closeBtn: 2, + shift: 0, + shadeClose: false, + btn: ['确定', '取消'], + content: "
    \ +
    \ + 名称\ +
    \ + \ +
    \ +
    \ +
      \ +
    • 注意:币种名称用小写!
    • \ +
    \ +
    ", + yes: function(index, layero){ + + var token = $('input[name="name"]').val(); + appPost("sync_data_add", {"token":token}, function(data){ + rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + syncDataList(); + } + },{icon:rdata.status?1:2}); + }) + return; + } + }); +} + + +function onlineEditStrategyFile(k, f, tag) { + if(k != 0) { + var l = $("#PathPlace input").val(); + var h = $("#textBody").val(); + var a = $("select[name=encoding]").val(); + var loadT = layer.msg("正在保存中...", {icon: 16,time: 0}); + appPostCallbakNoMsg('save_body',{'data':h,'path':f,'encoding':a,"tag":tag}, function(data){ + var rdata = data.data; + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(loadT); + } + },{icon: rdata.status ? 1 : 2}); + }); + return + } + var e = layer.msg("正在读取文件,请稍候...", {icon: 16,time: 0}); + var g = f.split("."); + var b = g[g.length - 1]; + var d; + switch(b) { + case "html": + var j = { + name: "htmlmixed", + scriptTypes: [{ + matches: /\/x-handlebars-template|\/x-mustache/i, + mode: null + }, { + matches: /(text|application)\/(x-)?vb(a|script)/i, + mode: "vbscript" + }] + }; + d = j; + break; + case "htm": + var j = { + name: "htmlmixed", + scriptTypes: [{ + matches: /\/x-handlebars-template|\/x-mustache/i, + mode: null + }, { + matches: /(text|application)\/(x-)?vb(a|script)/i, + mode: "vbscript" + }] + }; + d = j; + break; + case "js": + d = "text/javascript"; + break; + case "json": + d = "application/ld+json"; + break; + case "css": + d = "text/css"; + break; + case "php": + d = "application/x-httpd-php"; + break; + case "tpl": + d = "application/x-httpd-php"; + break; + case "xml": + d = "application/xml"; + break; + case "sql": + d = "text/x-sql"; + break; + case "conf": + d = "text/x-nginx-conf"; + break; + default: + var j = { + name: "htmlmixed", + scriptTypes: [{ + matches: /\/x-handlebars-template|\/x-mustache/i, + mode: null + }, { + matches: /(text|application)\/(x-)?vb(a|script)/i, + mode: "vbscript" + }] + }; + d = j + } + $.post("/files/get_body", "path=" + encodeURIComponent(f), function(s) { + if(s.status === false){ + layer.msg(s.msg,{icon:5}); + return; + } + layer.close(e); + var u = ["utf-8", "GBK", "GB2312", "BIG5"]; + var n = ""; + var m = ""; + var o = ""; + for(var p = 0; p < u.length; p++) { + m = s.data.encoding == u[p] ? "selected" : ""; + n += '"; + } + var code_mirror = null; + var r = layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: ["90%", "90%"], + title: "在线编辑[" + f + "]", + btn:['保存','关闭'], + content: '
    \ +
    \ +

    提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!\ + \ +

    \ + \ +
    ', + success:function(i,l){ + renderSQL(); + } + }); +} + +function mysqlCommonFuncSlaveStatus(){ + + function renderSQL(){ + var sid = mysqlGetSid(); + myPostCBN('get_slave_status',{'sid':sid} ,function(rdata){ + var data = rdata.data; + $('#info_log').html(data.data); + var ob = document.getElementById('info_log'); + ob.scrollTop = ob.scrollHeight; + }); + } + + layer.open({ + type: 1, + title: "查看主从复制信息", + area: ['800px', '400px'], + closeBtn: 1, + shadeClose: false, + content: '
    \ + \ +
    ', + success:function(i,l){ + renderSQL(); + } + }); +} + +function mysqlCommonFunc(){ + $('#mysql_common').unbind('click').click(function(){ + layer.open({ + type: 1, + title: "MySQL常用功能", + area: ['600px', '200px'], + closeBtn: 1, + shadeClose: false, + content: '
    \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
    ', + success:function(i,l){ + $('#mysql_top_nsql').click(function(){ + mysqlCommonFuncMysqlNSQL(); + }); + + $('#mysql_net_stat').click(function(){ + mysqlCommonFuncMysqlNet(); + }); + + $('#mysql_redundant_indexes').click(function(){ + mysqlCommonFuncRedundantIndexes(); + }); + + $('#mysql_table_info').click(function(){ + mysqlCommonFuncTableInfo(); + }); + + $('#mysql_conn_count').click(function(){ + mysqlCommonFuncConnCount(); + }); + + $('#mysql_fpk_info').click(function(){ + mysqlCommonFuncFpkInfo(); + }); + + $('#mysql_lock_sql').click(function(){ + mysqlCommonFuncLockSQL(); + }); + + $('#mysql_deadlock_info').click(function(){ + mysqlCommonFuncDeadlockInfo(); + }); + + $('#mysql_slave_status').click(function(){ + mysqlCommonFuncSlaveStatus(); + }); + } + }); + }); +} + +function mysqlRunMysqlTab(name){ + switch(name){ + case 'proccess':mysqlProcessList();break; + case 'status':mysqlStatusList();break; + case 'stats':mysqlStatsList();break; + } +} + +// ------------------------- mysql start ------------------------------- +function mysqlGetSid(){ + return $('#mysql select[name=sid]').val(); + // return 0; +} + +function mysqlGetDbName(){ + return $('#mysql .mysql_db_list select[name=mysql_db]').val(); +} + +function mysqlGetTableName(){ + var table = $('#mysql .mysql_table_list select[name=mysql_table]').val(); + if (!table){ + return ''; + } + return table; +} + +function mysqlInitField(f, data){ + var option_html = ''; + for (var i = 0; i < f.length; i++) { + if (data['soso_field'] == f[i]){ + option_html+= ''; + } else { + option_html+= ''; + } + + + } + + $('select[name="mysql_field_key"]').html(option_html); + + $('#mysql_find').unbind('click').click(function(){ + var val = $('input[name="mysql_field_value"]').val(); + if (val == ''){ + layer.msg('搜索不能为空!',{icon:7}); + return; + } + mysqlGetDataList(1); + }); +} + + +function mysqlGetServerList(call_func){ + myPostCBN('get_server_list', {}, function(rdata){ + var rdata = rdata.data; + if (rdata.data.length != 0){ + var items = rdata.data; + var content = ''; + for (var i = 0; i < items.length; i++) { + var t = items[i]; + if (i == 0){ + content += ''; + } else { + content += ''; + } + } + + + $('#mysql select[name=sid]').html(content); + $('#mysql select[name=sid]').change(function(){ + mysqlGetDbList(); + }); + if (typeof(call_func) == 'function'){ + call_func(); + } + closeInstallLayer(); + } else { + showInstallLayer(); + } + }); +} + +function mysqlGetDbList(){ + var sid = mysqlGetSid(); + myPostCBN('get_db_list',{'sid':sid} ,function(rdata){ + if (rdata.data.status){ + var items = rdata.data.data['list']; + var content = ''; + for (var i = 0; i < items.length; i++) { + var name = items[i]; + if (i == 0){ + content += ''; + } else { + content += ''; + } + } + // console.log(content); + $('#mysql .mysql_db_list select[name=mysql_db]').html(content); + $('#mysql .mysql_db_list select[name=mysql_db]').change(function(){ + mysqlGetTableList(1); + }); + + mysqlGetTableList(1); + } + }); +} + +function mysqlGetTableList(p){ + // console.log('mysqlGetTableList',p); + var sid = mysqlGetSid(); + var db = mysqlGetDbName(); + + if (!db){ + return; + } + + myPostCBN('get_table_list',{'sid':sid,'db':db} ,function(rdata){ + if (rdata.data.status){ + + var items = rdata.data.data['list']; + var content = ''; + for (var i = 0; i < items.length; i++) { + var name = items[i]; + if (i == 0){ + content += ''; + } else { + content += ''; + } + } + // console.log(content); + $('#mysql .mysql_table_list select[name=mysql_table]').html(content); + $('#mysql .mysql_table_list select[name=mysql_table]').change(function(){ + mysqlGetDataList(1); + }); + + mysqlGetDataList(1); + } + }); +} + +function mysqlGetDataList(p){ + var sid = mysqlGetSid(); + var db = mysqlGetDbName(); + var table = mysqlGetTableName(); + + var mysql_field = $('select[name="mysql_field_key"]').val(); + var mysql_value = $('input[name="mysql_field_value"]').val(); + + var request_data = { + 'sid':sid, + 'db':db, + 'table':table, + 'p':p + }; + + if (mysql_field != '0'){ + request_data['where'] = { + field : mysql_field, + value : mysql_value + }; + } else { + request_data['where'] = {}; + } + + myPostCB('get_data_list',request_data ,function(rdata){ + + if (rdata.data.status){ + var data = rdata.data.data; + var dlist = data['list']; + + var fields = mongodbGetDataFields(dlist); + if (fields.length != 0 ){ + mysqlInitField(fields,data); + } + + var header_field = ''; + for (var i =0 ; i'; + } + $('#mysql_table thead tr').html(header_field); + + var tbody = ''; + for (var i = 0; i < dlist.length; i++) { + tbody += ''; + for (var j = 0; j < fields.length; j++) { + var f = fields[j]; + if (f in dlist[i]) { + tbody += ''+dlist[i][f]+''; + } else { + tbody += 'undefined'; + } + } + tbody += ''; + } + + $('#mysql_table tbody').html(tbody); + $('#mysql .mysql_list_page').html(data.page); + } + // + }); +} + + +function mysqlProcessList(){ + var sid = mysqlGetSid(); + var request_data = {}; + request_data['sid'] = sid; + myPostCBN('get_proccess_list',request_data ,function(rdata){ + if (rdata.data.status){ + var data = rdata.data.data; + var dlist = data['list']; + + var fields = mongodbGetDataFields(dlist); + + var header_field = ''; + for (var i =0 ; i'; + } + $('#mysql_ot_table thead tr').html(header_field); + + var tbody = ''; + for (var i = 0; i < dlist.length; i++) { + tbody += ''; + for (var j = 0; j < fields.length; j++) { + var f = fields[j]; + if (f in dlist[i]) { + tbody += ''+dlist[i][f]+''; + } else { + tbody += 'undefined'; + } + } + tbody += ''; + } + + $('#mysql_ot_table tbody').html(tbody); + } + }); +} + +function mysqlStatusList(){ + var sid = mysqlGetSid(); + var request_data = {}; + request_data['sid'] = sid; + myPostCBN('get_status_list',request_data ,function(rdata){ + // console.log(rdata); + if (rdata.data.status){ + var data = rdata.data.data; + var dlist = data['list']; + + var fields = mongodbGetDataFields(dlist); + + var header_field = ''; + for (var i =0 ; i'; + } + $('#mysql_ot_table thead tr').html(header_field); + + var tbody = ''; + for (var i = 0; i < dlist.length; i++) { + tbody += ''; + for (var j = 0; j < fields.length; j++) { + var f = fields[j]; + if (f in dlist[i]) { + tbody += ''+dlist[i][f]+''; + } else { + tbody += 'undefined'; + } + } + tbody += ''; + } + + $('#mysql_ot_table tbody').html(tbody); + } + }); +} + +function mysqlStatsList(){ + var sid = mysqlGetSid(); + var request_data = {}; + request_data['sid'] = sid; + myPostCBN('get_stats_list',request_data ,function(rdata){ + // console.log(rdata); + if (rdata.data.status){ + var data = rdata.data.data; + var dlist = data['list']; + + var fields = mongodbGetDataFields(dlist); + + var header_field = ''; + for (var i =0 ; i'; + } + $('#mysql_ot_table thead tr').html(header_field); + + var tbody = ''; + for (var i = 0; i < dlist.length; i++) { + tbody += ''; + for (var j = 0; j < fields.length; j++) { + var f = fields[j]; + if (f in dlist[i]) { + tbody += ''+dlist[i][f]+''; + } else { + tbody += 'undefined'; + } + } + tbody += ''; + } + + $('#mysql_ot_table tbody').html(tbody); + } + }); +} + + +// ------------------------- mysql start ------------------------------- + +// ------------------------- memcached start ------------------------------- +function memcachedGetSid(){ + return 0; +} + +function memcachedGetItem(){ + return $('#memcached .item_list select').val(); +} + +function memcachedGetList(){ + var sid = memcachedGetSid(); + memPostCB('get_items',{'sid':sid} ,function(rdata){ + if (rdata.data.status){ + + var items = rdata.data.data['items']; + var content = ''; + for (var i = 0; i < items.length; i++) { + var name = items[i]; + if (i == 0){ + content += ''; + } else { + content += ''; + } + } + $('#memcached .item_list select').html(content); + $('#memcached .item_list select').change(function(){ + memcachedGetKeyList(1); + }); + closeInstallLayer(); + } else { + showInstallLayer(); + } + memcachedGetKeyList(1); + }); +} + +function memcachedGetKeyList(p){ + var item_id = memcachedGetItem(); + var sid = memcachedGetSid(); + memPostCB('get_key_list',{'sid':sid,'item_id':item_id,'p':p} ,function(rdata){ + if (rdata.data.status){ + var data = rdata.data.data; + var dlist = data['list']; + + var tbody = ''; + for (var i = 0; i < dlist.length; i++) { + tbody += ''; + + tbody += ""; + + tbody += ''+ dlist[i]['k'] +''; + tbody += ''+dlist[i]['v']+''; + tbody += ''+ dlist[i]['s'] +''; + + if (dlist[i]['t'] == '0'){ + tbody += '永久'; + } else { + tbody += ''+ dlist[i]['t'] +''; + } + + tbody += '\ + 删除\ + '; + + tbody += ''; + } + + $('.memcached_table_content tbody').html(tbody); + $('.memcached_list_page').html(data.page); + + + $('.del').click(function(){ + var i = $(this).data('index'); + memcachedDeleteKey(dlist[i]['k']); + }); + + $('.copy').click(function(){ + var i = $(this).data('index'); + copyText(dlist[i]['v']); + }); + } else { + $('.memcached_table_content tbody').html(''); + } + }); +} + +function memcachedDeleteKey(key){ + layer.confirm('确定要删除?', {btn: ['确定', '取消']}, function(){ + var data = {}; + data['sid'] = memcachedGetSid(); + data['key'] = key; + memPostCB('del_val', data, function(rdata){ + showMsg(rdata.data.msg,function(){ + if (rdata.data.status){ + memcachedGetKeyList(1); + } + },{icon: rdata.data.status ? 1 : 2}, 2000); + }); + }); +} + + +function memcachedAdd(){ + layer.open({ + type: 1, + area: '480px', + title: '添加Key至服务器', + closeBtn: 1, + shift: 0, + shadeClose: false, + btn:['确定','取消'], + content: "\ +
    \ + \ +
    \ + \ +
    \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ +
    \ + 有效期\ +
    \ + \ +
    \ +
    \ +
    \ +
    \ +
    • 有效期为0表示永久
    • \ +
    \ +
    \ + ", + success:function(){ + }, + yes: function(index){ + var data = {}; + data['sid'] = memcachedGetSid(); + data['key'] = $('input[name="key"]').val(); + data['val'] = $('textarea[name="val"]').val(); + data['endtime'] = $('input[name="endtime"]').val(); + + memPostCB('set_kv', data ,function(rdata){ + showMsg(rdata.data.msg,function(){ + layer.close(index); + memcachedGetList(); + },{icon: rdata.data.status ? 1 : 2}, 1000); + }); + } + }); +} + +// ------------------------- memcached end --------------------------------- + +// ------------------------- mongodb start --------------------------------- +function mongodbGetSid(){ + return 0; +} + +function mongodbGetDbName(){ + return $('.db_list select[name="db"]').val(); +} + +function mongodbInitField(f, data){ + var option_html = ''; + for (var i = 0; i < f.length; i++) { + if (data['soso_field'] == f[i]){ + option_html+= ''; + } else { + option_html+= ''; + } + + + } + + $('select[name="mongodb_field_key"]').html(option_html); + + $('#mongodb .mongodb_find').unbind('click').click(function(){ + var val = $('input[name="mongodb_field_value"]').val(); + if (val == ''){ + layer.msg('搜索不能为空!',{icon:7}); + return; + } + mongodbDataList(1); + }); + + $('#mongodb .mongodb_refresh').unbind('click').click(function(){ + mongodbDataList(1); + }); +} + +var mogodb_db_list; +function mongodbCollectionName(){ + // console.log(mogodb_db_list); + var v = mogodb_db_list.getValue('value'); + if (v.length == 0){ + // console.log($('#mongodb').data('collection')); + return $('#mongodb').data('collection'); + } + return v[0]; +} + +function mongodbGetList(){ + var sid = mongodbGetSid(); + mgdbPostCB('get_db_list',{'sid':sid} ,function(rdata){ + // console.log(rdata); + if (rdata.data.status){ + var list = rdata.data.data['list']; + var content = ''; + for (var i = 0; i < list.length; i++) { + var name = list[i]; + if (i == 0){ + content += ''; + } else { + content += ''; + } + } + $('.db_list select').html(content); + + if (list.length > 0) { + mongodbGetCollections(list[0]); + } + + $('#mongodb_select .db_list select[name="db"]').change(function(){ + var collection_name = $(this).val(); + mongodbGetCollections(collection_name); + }); + + closeInstallLayer(); + } else { + showInstallLayer(); + } + }); +} + + +function mongodbGetCollections(name){ + var sid = mongodbGetSid(); + + mgdbPostCB('get_collections_list',{'sid':sid,'name':name} ,function(rdata){ + // console.log(rdata); + if (rdata.data.status){ + var list = rdata.data.data['collections']; + + var select_list = []; + for (var i = 0; i < list.length; i++) { + var t = {}; + t['name'] = list[i]; + t['value'] = list[i]; + + if (i == 0){ + t['selected'] = true; + } + + select_list.push(t); + } + + mogodb_db_list = xmSelect.render({ + el: '#mongodb_search', + radio: true, + toolbar: {show: true}, + data: select_list, + on: function(data){ + //arr: 当前多选已选中的数据 + var arr = data.arr; + //change, 此次选择变化的数据,数组 + var change = data.change; + //isAdd, 此次操作是新增还是删除 + var isAdd = data.isAdd; + if (isAdd){ + $('#mongodb').data('collection',change[0].value); + + setTimeout(function(){ + mongodbDataList(1); + },200); + } + }, + }); + + if (select_list.length > 0){ + + setTimeout(function(){ + mongodbDataList(1); + },200); + } + } + }); +} + +function mongodbGetDataFields(data){ + var fields = []; + for (var i = 0; i < data.length; i++) { + var d = data[i]; + for (var j in d) { + if (fields.indexOf(j) == -1 ){ + fields.push(j); + } + } + } + return fields; +} + +function mongodbDataList(p){ + var sid = mongodbGetSid(); + var db = mongodbGetDbName(); + var collection = mongodbCollectionName(); + + var mongodb_field = $('select[name="mongodb_field_key"]').val(); + var mongodb_value = $('input[name="mongodb_field_value"]').val(); + + var request_data = { + 'sid':sid, + 'db':db, + 'collection':collection, + "p":p, + }; + + if (mongodb_field != '0'){ + request_data['where'] = { + field : mongodb_field, + value : mongodb_value + }; + } else { + request_data['where'] = {}; + } + + // console.log({'sid':sid,'db':db,'collection':collection,"p":p}); + mgdbPostCB('get_data_list', request_data, function(rdata){ + if (rdata.data.status){ + var data = rdata.data.data; + var dlist = data['list']; + // console.log(dlist); + + var fields = mongodbGetDataFields(dlist); + if (fields.length != 0 ){ + mongodbInitField(fields,data); + } + + // console.log(fields); + + var header_field = ''; + for (var i =0 ; i'; + } + header_field += '操作'; + + $('#mongodb .mongodb_table thead tr').html(header_field); + + var tbody = ''; + for (var i = 0; i < dlist.length; i++) { + tbody += ''; + for (var j = 0; j < fields.length; j++) { + var f = fields[j]; + + if (f in dlist[i]) { + if (f == '_id' ){ + tbody += ''+dlist[i]['_id']['$oid']+''; + } else { + tbody += ''+dlist[i][f]+''; + } + } else { + tbody += 'undefined'; + } + } + + tbody += '\ + 删除\ + '; + + tbody += ''; + } + + // console.log($(window).width()-230); + $('#mongodb_table').css('width', $(document).width()+240).parent().css('width', $(document).width()-240).css('overflow','scroll'); + $('#mongodb').css('width',$(document).width()-240).css('overflow','hidden'); + $('#mongodb .mongodb_table tbody').html(tbody); + $('#mongodb .mongodb_list_page').html(data.page); + + $('#mongodb .del').click(function(){ + var i = $(this).data('index'); + mongodbDel(dlist[i]['_id']['$oid']); + }); + } + }); +} + +function mongodbDel(mgdb_id){ + // console.log(mgdb_id); + var sid = mongodbGetSid(); + var db = mongodbGetDbName(); + var collection = mongodbCollectionName(); + mgdbPostCB('del_by_id',{'sid':sid,'db':db,'collection':collection,"_id":mgdb_id} ,function(rdata){ + showMsg(rdata.data.msg,function(){ + if (rdata.data.status){ + mongodbDataList(1); + } + },{icon: rdata.data.status ? 1 : 2}, 2000); + }); +} + +// ------------------------- mongodb end --------------------------------- + +// ------------------------- redis start --------------------------------- +function redisGetSid(){ + return 0; +} + +function redisGetIdx(){ + return $('#redis_list_tab .tab-nav span.on').data('id'); +} + +function redisGetList(){ + var sid = redisGetSid(); + redisPostCB('get_list',{'sid':sid} ,function(rdata){ + if (rdata.data.status){ + var list = rdata.data.data; + var content = ''; + for (var i = 0; i < list.length; i++) { + if (i == 0){ + content += ''+list[i]['name'] + '('+ list[i]['keynum'] +')'; + } else { + content += ''+list[i]['name'] + '('+ list[i]['keynum'] +')'; + } + } + $('#redis_list_tab .tab-nav').html(content); + + $('#redis_list_tab .tab-nav span').click(function(){ + $('#redis_list_tab .tab-nav span').removeClass('on'); + $(this).addClass('on'); + redisGetKeyList(1); + }); + redisGetKeyList(1); + closeInstallLayer(); + } else { + showInstallLayer(); + } + }); +} + +function redisGetKeyList(page,search = ''){ + + var args = {}; + args['sid'] = redisGetSid(); + args['idx'] = redisGetIdx(); + args['p'] = page; + args['search'] = search; + + var input_search_val = $('#redis_ksearch').val(); + if (input_search_val!=''){ + args['search'] = input_search_val; + } + + redisPostCB('get_dbkey_list', args, function(rdata){ + if (rdata.data.status){ + var data = rdata.data.data.data; + var tbody = ''; + for (var i = 0; i < data.length; i++) { + + + tbody += ''; + tbody += ""; + tbody += ''+data[i].name+''; + tbody += ''+data[i].val+''; + tbody += ''+data[i].type+''; + tbody += ''+data[i].len+''; + + if (data[i].endtime == -1){ + tbody += '永久'; + } else { + tbody += ''+data[i].endtime+''; + } + + tbody += '\ + 编辑 | \ + 删除\ + '; + + tbody += ''; + } + + // console.log(tbody); + $('.redis_table_content tbody').html(tbody); + $('.redis_list_page').html(rdata.data.data.page); + + + $('.edit').click(function(){ + var i = $(this).data('index'); + redisEditKv(data[i].name,data[i].val,data[i].endtime); + }); + + $('.copy').click(function(){ + var i = $(this).data('index'); + copyText(data[i].val); + }); + + } + }); +} + +function redisDeleteKey(name){ + layer.confirm('确定要删除?', {btn: ['确定', '取消']}, function(){ + var data = {}; + data['idx'] = redisGetIdx(); + data['sid'] = redisGetSid(); + data['name'] = name; + redisPostCB('del_val', data, function(rdata){ + showMsg(rdata.data.msg,function(){ + if (rdata.data.status){ + redisGetList(); + } + },{icon: rdata.data.status ? 1 : 2}, 2000); + }); + }); +} + +function redisAdd(){ + layer.open({ + type: 1, + area: '480px', + title: '添加Key至服务器', + closeBtn: 1, + shift: 0, + shadeClose: false, + btn:['确定','取消'], + content: "
    \ +
    \ + 数据库\ +
    \ + \ +
    \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ +
    \ + 有效期\ +
    \ + \ +
    \ +
    \ +
    \ +
    \ +
    • 有效期为0表示永久
    • \ +
    \ +
    \ +
    ", + success:function(){ + var db_list = $('#redis_list_tab .tab-nav span'); + var db_list_count = db_list.length; + + var idx_html = ''; + for (var i = 0; i < db_list_count; i++) { + idx_html += ""; + } + $('select[name=idx]').html(idx_html); + }, + yes: function(index){ + var data = {}; + data['idx'] = $('select[name=idx]').val(); + data['sid'] = redisGetSid(); + data['name'] = $('input[name="key"]').val(); + data['val'] = $('textarea[name="val"]').val(); + data['endtime'] = $('input[name="endtime"]').val(); + + redisPostCB('set_kv', data ,function(rdata){ + showMsg(rdata.data.msg,function(){ + layer.close(index); + redisGetList(); + },{icon: rdata.data.status ? 1 : 2}, 1000); + }); + } + }); +} + +function redisEditKv(name, val, endtime){ + layer.open({ + type: 1, + area: '480px', + title: '编辑['+name+']Key', + closeBtn: 1, + shift: 0, + shadeClose: false, + btn:['确定','取消'], + content: "
    \ +
    \ + 数据库\ +
    \ + \ +
    \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ +
    \ + 有效期\ +
    \ + \ +
    \ +
    \ +
    \ +
    \ +
    • 有效期为0表示永久
    • \ +
    \ +
    \ +
    ", + success:function(){ + var idx = redisGetIdx(); + var idx_html = ""; + $('select[name=idx]').html(idx_html).attr('readonly','readonly'); + $('input[name="key"]').val(name).attr('readonly','readonly'); + $('textarea[name="val"]').val(val); + + if (endtime == -1){ + $('input[name="endtime"]').val(0); + } else { + $('input[name="endtime"]').val(endtime); + } + }, + yes: function(index){ + var data = {}; + data['idx'] = $('select[name=idx]').val(); + data['sid'] = redisGetSid(); + data['name'] = $('input[name="key"]').val(); + data['val'] = $('textarea[name="val"]').val(); + data['endtime'] = $('input[name="endtime"]').val(); + redisPostCB('set_kv', data ,function(rdata){ + showMsg(rdata.data.msg,function(){ + if (rdata.data.status){ + layer.close(index); + redisGetList(); + } + },{icon: rdata.data.status ? 1 : 2}, 1000); + }); + } + }); +} + +function redisBatchDel(){ + var keys = []; + $('input[type="checkbox"].check:checked').each(function () { + keys.push($(this).val()); + }); + if (keys.length == 0){ + layer.msg('没有选中数据!',{icon:7}); + return; + } + + layer.confirm('确定要批量删除?', {btn: ['确定', '取消']}, function(){ + var data = {}; + data['idx'] = redisGetIdx(); + data['sid'] = redisGetSid(); + data['keys'] = keys; + redisPostCB('batch_del_val', data, function(rdata){ + showMsg(rdata.data.msg,function(){ + if (rdata.data.status){ + redisGetList(); + } + },{icon: rdata.data.status ? 1 : 2}, 2000); + }); + }); +} + +function redisBatchClear(){ + var xm_db_list; + layer.open({ + type: 1, + area: ['480px','180px'], + title: '清空【本地服务器】数据库', + closeBtn: 1, + shift: 0, + shadeClose: false, + btn:['确定','取消'], + content: "
    \ +
    \ + 选择数据库\ +
    \ +
    \ +
    \ +
    \ +
    ", + success:function(l,i){ + var db_list = $('#redis_list_tab .tab-nav span'); + var db_list_count = db_list.length; + + var idx_db = []; + for (var i = 0; i < db_list_count; i++) { + var t = {}; + t['name'] = "DB("+i+")"; + t['value'] = i; + idx_db.push(t); + } + + xm_db_list = xmSelect.render({ + el: '#select_db', + repeat: false, + toolbar: {show: true}, + data: idx_db, + }); + + $(l).find('.layui-layer-content').css('overflow','visible'); + }, + yes: function(index){ + var xm_db_val = xm_db_list.getValue('value'); + layer.confirm('确定要批量清空?', {btn: ['确定', '取消']}, function(){ + var data = {}; + data['sid'] = redisGetSid(); + data['idxs'] = xm_db_val; + redisPostCB('clear_flushdb', data, function(rdata){ + showMsg(rdata.data.msg,function(){ + if (rdata.data.status){ + redisGetList(); + layer.close(index); + } + },{icon: rdata.data.status ? 1 : 2}, 2000); + }); + }); + } + }); +} +// ------------------------- redis end --------------------------------- + diff --git a/plugins/docker/ico.png b/plugins/docker/ico.png new file mode 100644 index 000000000..db39a8cab Binary files /dev/null and b/plugins/docker/ico.png differ diff --git a/plugins/docker/index.html b/plugins/docker/index.html new file mode 100755 index 000000000..fd9647867 --- /dev/null +++ b/plugins/docker/index.html @@ -0,0 +1,58 @@ + + +
    +
    +
    +
    +

    服务

    +

    自启动

    +

    容器列表

    +

    镜像列表

    +

    镜像导出

    +

    IP地址池

    +

    仓库

    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/plugins/docker/index.py b/plugins/docker/index.py new file mode 100755 index 000000000..7fa29f9c3 --- /dev/null +++ b/plugins/docker/index.py @@ -0,0 +1,868 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +try: + import docker +except Exception as e: + pass + + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getDClient(): + try: + client = docker.from_env() + except Exception as e: + client = docker.DockerClient( + base_url='unix:///Users/midoks/.docker/run/docker.sock') + return client + + +def getPluginName(): + return 'docker' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/redis.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + "/config/redis.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(self, data, ck=[]): + for i in range(len(ck)): + print(data[i]) + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + data = mw.execShell( + "ps -ef|grep docker |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + + if data[0] == '': + return 'stop' + return 'start' + + +def initDreplace(): + return '' + + +def dockerOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' docker') + if data[1] == '': + return 'ok' + return data[1] + return 'fail' + + +def start(): + return dockerOp('start') + + +def stop(): + return dockerOp('stop') + + +def restart(): + status = dockerOp('restart') + + log_file = runLog() + mw.execShell("echo '' > " + log_file) + return status + + +def reload(): + return dockerOp('reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + +# UTC时间转换为时间戳 + + +def utc_to_local(utc_time_str, utc_format='%Y-%m-%dT%H:%M:%S'): + import pytz + import datetime + import time + local_tz = pytz.timezone('Asia/Chongqing') + local_format = "%Y-%m-%d %H:%M" + utc_dt = datetime.datetime.strptime(utc_time_str, utc_format) + local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz) + time_str = local_dt.strftime(local_format) + return int(time.mktime(time.strptime(time_str, local_format))) + + +def conList(): + c = getDClient() + clist = c.containers.list(all=True) + conList = [] + for con in clist: + tmp = con.attrs + tmp['Created'] = utc_to_local(tmp['Created'].split('.')[0]) + conList.append(tmp) + return conList + + +def conListData(): + try: + clist = conList() + except Exception as e: + return mw.returnJson(False, '未开启Docker') + return mw.returnJson(True, 'ok', clist) + + +def dockerRemoveCon(): + args = getArgs() + data = checkArgs(args, ['Hostname']) + if not data[0]: + return data[1] + + Hostname = args['Hostname'] + + c = getDClient() + try: + conFind = c.containers.get(Hostname) + try: + path_list = conFind.attrs['GraphDriver'][ + 'Data']['LowerDir'].split(':') + for i in path_list: + mw.execShell('chattr -R -i %s' % i) + except: + pass + conFind.remove(force=True) + return mw.returnJson(True, '成功删除!') + except docker.errors.APIError as ex: + return mw.returnJson(False, '删除失败!' + str(ex)) + + +def dockerLogCon(): + + args = getArgs() + data = checkArgs(args, ['Hostname']) + if not data[0]: + return data[1] + + Hostname = args['Hostname'] + + c = getDClient() + try: + conFind = c.containers.get(Hostname) + if not conFind: + return mw.returnJson(False, 'The specified container does not exist!') + log = conFind.logs() + if not isinstance(log, str): + log = log.decode() + return mw.returnJson(True, log) + except docker.errors.APIError as ex: + return mw.returnJson(False, 'Get Logs failed') + + +def dockerRunCon(): + # 启动容器 + args = getArgs() + data = checkArgs(args, ['Hostname']) + if not data[0]: + return data[1] + + Hostname = args['Hostname'] + c = getDClient() + try: + conFind = c.containers.get(Hostname) + if not conFind: + return mw.returnJson(False, 'The specified container does not exist!') + conFind.start() + return mw.returnJson(True, '启动成功!') + except docker.errors.APIError as ex: + return mw.returnJson(False, '启动失败!' + str(ex)) + + +def dockerStopCon(): + # 停止容器 + args = getArgs() + data = checkArgs(args, ['Hostname']) + if not data[0]: + return data[1] + + Hostname = args['Hostname'] + c = getDClient() + try: + conFind = c.containers.get(Hostname) + if not conFind: + return mw.returnJson(False, 'The specified container does not exist!') + conFind.stop() + return mw.returnJson(True, '停止成功!') + except docker.errors.APIError as ex: + return mw.returnJson(False, '停止失败!' + str(ex)) + + +def dockerExec(): + # 容器执行命令 + args = getArgs() + data = checkArgs(args, ['Hostname']) + if not data[0]: + return data[1] + + Hostname = args['Hostname'] + + debug_path = 'data/debug.pl' + if os.path.exists(debug_path): + return mw.returnJson(False, '开发模式不能进入!') + + c = getDClient() + try: + conFind = c.containers.get(Hostname) + cmd = 'docker container exec -it %s /bin/sh' % Hostname + return mw.returnJson(True, cmd) + except docker.errors.APIError as ex: + return mw.returnJson(False, '连接失败!') + + +def imageList(): + imageList = [] + c = getDClient() + ilist = c.images.list() + for image in ilist: + tmp_attrs = image.attrs + if len(tmp_attrs['RepoTags']) == 1: + tmp_image = {} + tmp_image['Id'] = tmp_attrs['Id'].split(':')[1][:12] + tmp_image['RepoTags'] = tmp_attrs['RepoTags'][0] + tmp_image['Size'] = tmp_attrs['Size'] + tmp_image['Labels'] = tmp_attrs['Config']['Labels'] + tmp_image['Comment'] = tmp_attrs['Comment'] + tmp_image['Created'] = utc_to_local( + tmp_attrs['Created'].split('.')[0]) + imageList.append(tmp_image) + else: + for i in range(len(tmp_attrs['RepoTags'])): + tmp_image = {} + tmp_image['Id'] = tmp_attrs['Id'].split(':')[1][:12] + tmp_image['RepoTags'] = tmp_attrs['RepoTags'][i] + tmp_image['Size'] = tmp_attrs['Size'] + tmp_image['Labels'] = tmp_attrs['Config']['Labels'] + tmp_image['Comment'] = tmp_attrs['Comment'] + tmp_image['Created'] = utc_to_local( + tmp_attrs['Created'].split('.')[0]) + imageList.append(tmp_image) + imageList = sorted(imageList, key=lambda x: x['Created'], reverse=True) + return imageList + + +def dockerPull(): + # pull Dockr 官方镜像 + args = getArgs() + data = checkArgs(args, ['images']) + if not data[0]: + return data[1] + + images = args['images'] + if ':' in images: + pass + else: + images = images + ':latest' + + c = getDClient() + try: + ret = c.images.pull(images) + if ret: + return mw.returnJson(True, '拉取成功!') + else: + return mw.returnJson(False, '拉取失败,请检查镜像名称或是否需要登录docker进行下载') + except: + ret = mw.execShell('docker image pull %s' % images) + if 'invalid' in ret[-1]: + return mw.returnJson(False, '拉取失败,请检查镜像名称或是否需要登录docker进行下载') + else: + return mw.returnJson(True, '拉取成功!') + + +def dockerPlulPath(path): + if not path and path == '': + return mw.returnJson(False, 'Invalid address') + + ret = mw.execShell('docker image pull %s' % path) + if 'invalid' in ret[-1]: + return mw.returnJson(False, '拉取失败,请检查镜像名称或是否需要登录docker进行下载') + else: + return mw.returnJson(True, '拉取成功!') + + +def dockerPullReg(): + # pull Dockr 官方镜像 + args = getArgs() + data = checkArgs(args, ['path']) + if not data[0]: + return data[1] + + path = args['path'] + return dockerPlulPath(path) + + +# 判断镜像是否存在 +def checkImage(path): + image_list = imageList() + for i in image_list: + if path == i["RepoTags"]: + return mw.returnData(False, '镜像已存在!') + + +def dockerPullPrivateNew(): + # pull Dockr 官方镜像 + args = getArgs() + data = checkArgs(args, ['path']) + if not data[0]: + return data[1] + + path = args['path'] + check = checkImage(path) + if check: + return mw.getJson(check) + + my_repo = repoList() + if not my_repo: + return mw.returnJson(False, '未登录任何私人存储库,请登录然后拉取') + return dockerPlulPath(path) + + +def imageListData(): + try: + ilist = imageList() + except Exception as e: + return mw.returnJson(False, '未开启Docker') + return mw.returnJson(True, 'ok', ilist) + + +def dockerRemoveImage(): + args = getArgs() + data = checkArgs(args, ['imageId', 'repoTags']) + if not data[0]: + return data[1] + + repoTags = args['repoTags'] + imageId = args['imageId'] + + c = getDClient() + try: + c.images.remove(repoTags) + return mw.returnJson(True, '成功删除') + except: + try: + c.images.remove(imageId) + return mw.returnJson(True, '成功删除!') + except docker.errors.APIError as ex: + return mw.returnJson(False, '删除失败, 当前镜像正在使用!') + + +def getImageListFunc(dbname=''): + bkDir = mw.getFatherDir() + '/backup/docker' + blist = os.listdir(bkDir) + r = [] + + bname = 'db_' + dbname + blen = len(bname) + for x in blist: + fbstr = x[0:blen] + if fbstr == bname: + r.append(x) + return r + + +def dockerImagePickDir(): + bkDir = mw.getFatherDir() + '/backup/docker' + return mw.returnJson(True, 'ok', bkDir) + + +def dockerImagePickList(): + + bkDir = mw.getFatherDir() + '/backup/docker' + if not os.path.exists(bkDir): + os.mkdir(bkDir) + + r = os.listdir(bkDir) + rr = [] + for x in range(0, len(r)): + p = bkDir + '/' + r[x] + data = {} + data['name'] = r[x] + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + return mw.returnJson(True, 'ok', rr) + + +def dockerImagePickSave(): + # image 导出 + args = getArgs() + data = checkArgs(args, ['images']) + if not data[0]: + return data[1] + + bkDir = mw.getFatherDir() + '/backup/docker/' + images = args['images'] + try: + file_name = bkDir + \ + str(time.strftime('%Y%m%d_%H%M%S', time.localtime())) + '.tar.gz' + mw.execShell('docker image save %s | gzip > %s' % + (images, file_name)) + return mw.returnJson(True, '导出镜像 {} 成功!'.format(file_name)) + except docker.errors.APIError as ex: + return mw.returnJson(False, '操作失败: ' + str(ex)) + + +def dockerImagePickLoad(): + # 镜像文件导入 + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + try: + file_path = args['file'] + if not os.path.exists(file_path): + return mw.returnJson(False, '文件不存在') + if file_path.endswith('.tar'): + mw.execShell('docker image load < %s' % file_path) + elif file_path.endswith('.tar.gz'): + mw.execShell('gunzip -c %s | docker image load' % file_path) + else: + return mw.returnJson(False, '不支持改文件类型!') + return mw.returnJson(True, '导入镜像文件成功!') + except docker.errors.APIError as ex: + return mw.returnJson(False, '操作失败: ' + str(ex)) + + +def dockerLoginCheck(user_name, user_pass, registry): + # 登陆验证 + cmd = 'docker login -u=%s -p %s %s' % (user_name, user_pass, registry) + # print(cmd) + login_test = mw.execShell(cmd) + # print(login_test) + ret = 'required$|Error' + ret2 = re.findall(ret, login_test[-1]) + if len(ret2) == 0: + return True + else: + return False + + +def getDockerIpListData(): + # 取IP列表 + path = getServerDir() + ipConf = path + '/iplist.json' + if not os.path.exists(ipConf): + return [] + iplist = json.loads(mw.readFile(ipConf)) + return iplist + + +def getDockerIpList(): + data = getDockerIpListData() + return mw.returnJson(True, 'ok!', data) + + +def dockerAddIP(): + # 添加IP + args = getArgs() + data = checkArgs(args, ['address', 'netmask', 'gateway']) + if not data[0]: + return data[1] + + path = getServerDir() + ipConf = path + '/iplist.json' + if not os.path.exists(ipConf): + iplist = [] + mw.writeFile(ipConf, json.dumps(iplist)) + + iplist = json.loads(mw.readFile(ipConf)) + ipInfo = { + 'address': args['address'], + 'netmask': args['netmask'], + 'gateway': args['gateway'], + } + iplist.append(ipInfo) + mw.writeFile(ipConf, json.dumps(iplist)) + return mw.returnJson(True, '添加成功!') + + +def dockerDelIP(): + # 删除IP + args = getArgs() + data = checkArgs(args, ['address']) + if not data[0]: + return data[1] + + path = getServerDir() + ipConf = path + '/iplist.json' + if not os.path.exists(ipConf): + return mw.returnJson(False, '指定的IP不存在。!') + iplist = json.loads(mw.readFile(ipConf)) + newList = [] + for ipInfo in iplist: + if ipInfo['address'] == args['address']: + continue + newList.append(ipInfo) + mw.writeFile(ipConf, json.dumps(newList)) + return mw.returnJson(True, '成功删除!') + + +def getDockerCreateInfo(): + # 取创建依赖 + import psutil + data = {} + data['images'] = imageList() + data['memSize'] = int(psutil.virtual_memory().total / 1024 / 1024) + data['iplist'] = getDockerIpListData() + return mw.returnJson(True, 'ok!', data) + + +def __release_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'docker', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def dockerPortCheck(): + args = getArgs() + data = checkArgs(args, ['port']) + if not data[0]: + return data[1] + + port = args['port'] + is_ok = IsPortExists(port) + if is_ok: + return mw.returnJson(True, 'ok') + return mw.returnJson(False, 'fail') + + +def IsPortExists(port): + # 判断端口是否被占用 + ret = __check_dst_port(ip='localhost', port=port) + ret2 = __check_dst_port(ip='0.0.0.0', port=port) + if ret: + return ret + if not ret and ret2: + return ret2 + if not ret and not ret2: + return False + + +def __check_dst_port(ip, port, timeout=3): + # 端口检测 + import socket + ok = True + try: + s = socket.socket() + s.settimeout(timeout) + s.connect((ip, port)) + s.close() + except: + ok = False + return ok + + +def dockerCreateCon(): + args = getArgs() + data = checkArgs(args, ['environments', 'command', + 'entrypoint', 'image', 'mem_limit', 'ports', 'volumes']) + if not data[0]: + return data[1] + + environments = args['environments'] + environments = environments.strip().split() + + command = args['command'] + entrypoint = args['entrypoint'] + image = args['image'] + mem_limit = args['mem_limit'] + ports = args['ports'] + ports = ports.replace('[', '(').replace(']', ')') + volumes = args['volumes'] + + # if __name__ == "__main__": + # print(args) + try: + + c = getDClient() + conObject = c.containers.run( + image=image, + mem_limit=mem_limit + 'M', + ports=eval(ports), + auto_remove=False, + command=command, + detach=True, + stdin_open=True, + tty=True, + entrypoint=entrypoint, + privileged=True, + volumes=json.loads(volumes), + cpu_shares=10, + environment=environments + ) + if conObject: + __release_port(ports) + return mw.returnJson(True, '创建成功!') + + return mw.returnJson(False, '创建失败!') + except docker.errors.APIError as ex: + return mw.returnJson(False, '创建失败!' + str(ex)) + + +def dockerLogin(): + args = getArgs() + + # print(args) + data = checkArgs(args, ['user', 'passwd', 'hub_name', + 'namespace', 'registry', 'repository_name']) + if not data[0]: + return data[1] + + user_name = args['user'] + user_pass = args['passwd'] + registry = args['registry'] + hub_name = args['hub_name'] + namespace = args['namespace'] + repository_name = args['repository_name'] + + ret_status = dockerLoginCheck(user_name, user_pass, registry) + path = getServerDir() + if ret_status: + user_file = path + '/user.json' + user_info = mw.readFile(user_file) + if not user_info: + user_info = [] + else: + user_info = json.loads(user_info) + + ret = {} + ret['user_name'] = user_name + ret['user_pass'] = user_pass + ret['registry'] = registry + ret['hub_name'] = hub_name + ret['namespace'] = namespace + ret['repository_name'] = repository_name + if not registry: + ret['registry'] = "docker.io" + user_info.append(ret) + mw.writeFile(user_file, json.dumps(user_info)) + return mw.returnJson(True, '成功登录!') + return mw.returnJson(False, '登录失败!') + + +# 删除用户信息 +def delete_user_info(registry): + path = getServerDir() + user_file = path + '/user.json' + user_info = mw.readFile(user_file) + if user_info: + user_info = json.loads(user_info) + for i in range(len(user_info)): + if registry in user_info[i].values(): + del(user_info[i]) + mw.writeFile(user_file, json.dumps(user_info)) + return True + + +def dockerLogout(): + args = getArgs() + data = checkArgs(args, ['registry']) + if not data[0]: + return data[1] + + registry = args['registry'] + if registry == "docker.io": + registry = "" + login_test = mw.execShell('docker logout %s' % registry) + if registry == "": + registry = "docker.io" + ret = 'required$|Error' + ret2 = re.findall(ret, login_test[-1]) + delete_user_info(registry) + if len(ret2) == 0: + return mw.returnJson(True, '退出成功') + else: + return mw.returnJson(True, '退出失败') + + +def repoList(): + path = getServerDir() + repostory_info = [] + user_file = path + '/user.json' + + if os.path.exists(user_file): + user_info = mw.readFile(user_file) + user_info = json.loads(user_info) + for i in user_info: + tmp = {} + tmp["hub_name"] = i["hub_name"] + tmp["registry"] = i["registry"] + tmp["namespace"] = i["namespace"] + tmp['repository_name'] = i["repository_name"] + repostory_info.append(tmp) + + return mw.returnJson(True, 'ok', repostory_info) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(getConf()) + elif func == 'con_list': + print(conListData()) + elif func == 'docker_con_log': + print(dockerLogCon()) + elif func == 'docker_remove_con': + print(dockerRemoveCon()) + elif func == 'docker_run_con': + print(dockerRunCon()) + elif func == 'docker_stop_con': + print(dockerStopCon()) + elif func == 'docker_exec': + print(dockerExec()) + elif func == 'docker_pull': + print(dockerPull()) + elif func == 'docker_pull_reg': + print(dockerPullReg()) + elif func == 'image_list': + print(imageListData()) + elif func == 'image_pick_dir': + print(dockerImagePickDir()) + elif func == 'image_pick_save': + print(dockerImagePickSave()) + elif func == 'image_pick_load': + print(dockerImagePickLoad()) + elif func == 'image_pick_list': + print(dockerImagePickList()) + elif func == 'docker_get_iplist': + print(getDockerIpList()) + elif func == 'docker_del_ip': + print(dockerDelIP()) + elif func == 'docker_add_ip': + print(dockerAddIP()) + elif func == 'get_docker_create_info': + print(getDockerCreateInfo()) + elif func == 'docker_create_con': + print(dockerCreateCon()) + elif func == 'docker_remove_image': + print(dockerRemoveImage()) + elif func == 'docker_port_check': + print(dockerPortCheck()) + elif func == 'docker_login': + print(dockerLogin()) + elif func == 'docker_logout': + print(dockerLogout()) + elif func == 'repo_list': + print(repoList()) + else: + print('error') diff --git a/plugins/docker/info.json b/plugins/docker/info.json new file mode 100755 index 000000000..f3ac77339 --- /dev/null +++ b/plugins/docker/info.json @@ -0,0 +1,19 @@ +{ + "sort": 1000, + "ps": "Docker是一个开源的应用容器引擎", + "name": "docker", + "title": "Docker", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "checks": "server/docker", + "path": "server/docker", + "display": 1, + "author": "Zend", + "date": "2017-04-01", + "home": "https://docker.io", + "doc1":"https://docker-py.readthedocs.io/", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/docker/install.sh b/plugins/docker/install.sh new file mode 100755 index 000000000..75ec2fa1f --- /dev/null +++ b/plugins/docker/install.sh @@ -0,0 +1,104 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/docker && /bin/bash install.sh uninstall 1.0 +# cd /www/server/mdserver-web/plugins/docker && /bin/bash install.sh install 1.0 + +VERSION=$2 + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +# { +# "registry-mirrors": [ +# "http://hub-mirror.c.163.com", +# "https://docker.mirrors.ustc.edu.cn", +# "https://registry.docker-cn.com" +# ] +# } + +# sudo mkdir -p /etc/docker +# sudo tee /etc/docker/daemon.json <<-'EOF' +# { +# "registry-mirrors": [ +# "http://hub-mirror.c.163.com", +# "https://docker.mirrors.ustc.edu.cn", +# "https://registry.docker-cn.com" +# ] +# } +# EOF +# sudo systemctl daemon-reload +# sudo systemctl restart docker + +Install_Docker() +{ + # which docker + # if [ "$?" == "0" ];then + # echo '安装已经完成docker' + # exit 0 + # fi + + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + + if [ ! -d $serverPath/docker ];then + curl -fsSL https://get.docker.com | bash + mkdir -p $serverPath/docker + fi + + pip install docker + pip install pytz + + if [ -d $serverPath/docker ];then + echo "${VERSION}" > $serverPath/docker/version.pl + echo '安装Docker完成' + + cd ${rootPath} && python3 ${rootPath}/plugins/docker/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/docker/index.py initd_install + fi +} + +Uninstall_Docker() +{ + CMD=yum + which apt + if [ "$?" == "0" ];then + CMD=apt + fi + + if [ -f /usr/lib/systemd/system/docker.service ];then + systemctl stop docker + systemctl disable docker + rm -rf /usr/lib/systemd/system/docker.service + systemctl daemon-reload + fi + + $CMD remove -y docker docker-ce-cli containerd.io + # docker-client \ + # docker-client-latest \ + # docker-common \ + # docker-latest \ + # docker-latest-logrotate \ + # docker-logrotate \ + # docker-selinux \ + # docker-engine-selinux \ + # docker-engine \ + # docker-ce + + rm -rf $serverPath/docker + echo "卸载Docker" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_Docker +else + Uninstall_Docker +fi diff --git a/plugins/docker/js/docker.js b/plugins/docker/js/docker.js new file mode 100755 index 000000000..d2c35d6f3 --- /dev/null +++ b/plugins/docker/js/docker.js @@ -0,0 +1,1084 @@ +function dPostOrgin(args, callback) { + $.post('/plugins/run', args, function(data) { + callback(data); + }, 'json'); +} + +function dPost(method, version, args, callback) { + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'docker'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string') { + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + dPostOrgin(req_data, function(data) { + layer.close(loadT); + if (!data.status) { + //错误展示10S + layer.msg(data.msg, { icon: 0, time: 2000, shade: [10, '#000'] }); + return; + } + + if (typeof(callback) == 'function') { + callback(data); + } + }); +} + +function dPostCallbak(method, version, args, callback) { + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'docker'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string') { + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status) { + layer.msg(data.msg, { icon: 0, time: 2000, shade: [0.3, '#000'] }); + return; + } + + if (typeof(callback) == 'function') { + callback(data); + } + }, 'json'); +} + + +function logsCon(id) { + dPost('docker_con_log', '', { Hostname: id }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + if (!rdata.status) { + layer.msg(rdata.msg, { icon: 2 }); + return; + }; + layer.open({ + type: 1, + title: 'Docker日志', + area: '600px', + closeBtn: 2, + content: '
    ' + + '
    ' + (rdata.msg == '' ? 'No logs' : rdata.msg) + '
    ' + + '
    ', + success: function(index, layers) { + $(".crontab-log").scrollTop(1000000); + } + }); + }); +} + +function deleteCon(Hostname) { + // 删除容器 + safeMessage('删除容器 ', '删除容器 [' + Hostname + '], 确定?', function() { + dPost('docker_remove_con', '', { Hostname: Hostname }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + if (rdata.status) { + dockerConListRender(); + } + }, { icon: rdata.status ? 1 : 2 }); + }); + }); +} + + +function startCon(Hostname) { + dPost('docker_run_con', '', { Hostname: Hostname }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + if (rdata.status) { + dockerConListRender(); + } + }, { icon: rdata.status ? 1 : 2 }); + }); +} + +function stopCon(Hostname) { + dPost('docker_stop_con', '', { Hostname: Hostname }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + if (rdata.status) { + dockerConListRender(); + } + }, { icon: rdata.status ? 1 : 2 }); + }); +} + +function execCon(Hostname) { + webShell(); + var pdata_socket = {}; + var shell = setInterval(function() { + if ($('.term-box').length == 0) { + pdata_socket['data'] = 'exit\n'; + socket.emit('webssh', pdata_socket); + setTimeout(function() { socket.emit('webssh', pdata_socket['data']); }, 1000); + clearInterval(shell); + } + }, 500); + setTimeout(function() { + dPost('docker_exec', '', { Hostname: Hostname }, function(res) { + var res = $.parseJSON(res.data); + if (!res.status) { + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + } else { + pdata_socket['data'] = 'clear && ' + res.msg + '\n' + socket.emit('webssh', pdata_socket); + setTimeout(function() { socket.emit('webssh', pdata_socket['data']); }, 1000); + } + }); + }); +} + +function dockerConListRender() { + dPost('con_list', '', {}, function(rdata) { + var rdata = $.parseJSON(rdata.data); + console.log(rdata); + if (!rdata.status) { + layer.msg(rdata.msg, { icon: 2, time: 2000 }); + return; + } + + + var list = ''; + var rlist = rdata.data; + + for (var i = 0; i < rlist.length; i++) { + var docker_status = 'stop'; + var status = ''; + if (rlist[i]['State']['Status'] == 'running') { + docker_status = 'start'; + status = ''; + } + + var op = ''; + op += '终端 | '; + op += '日志 | '; + op += '删除'; + + list += ''; + list += '' + rlist[i]['Name'].substring(1) + ''; + list += '' + rlist[i]['Config']['Image'] + ''; + list += '' + getFormatTime(rlist[i]['Created']) + ''; + + + if (docker_status == 'start') { + list += '' + status + ''; + } else { + list += '' + status + ''; + } + list += '' + op + ''; + list += ''; + } + + $('#con_list tbody').html(list); + }); +} + +function createConTemplate() { + + dPost('get_docker_create_info', '', {}, function(rdata) { + var rdata = $.parseJSON(rdata.data); + // console.log(rdata); + var rdata = rdata.data; + var imageOpt = ''; + for (var i = 0; i < rdata.images.length; i++) { + var imageName = rdata.images[i].RepoTags.indexOf('panel') == -1 ? rdata.images[i].RepoTags : 'aaPanel:' + rdata.images[i].RepoTags.split(':')[1]; + imageOpt += ''; + } + var iplistOpt = ''; + for (var i = 0; i < rdata.iplist.length; i++) { + iplistOpt += ''; + } + + var layer_index = layer.open({ + type: 1, + title: "创建容器", + area: '556', + closeBtn: 2, + shadeClose: false, + btn: ['确定', '取消'], + content: '
    \ +
    \ + 镜像\ +
    \ +
    \ +
    \ + 绑定IP\ +
    \ +
    \ +
    \ + 端口映射\ +
    \ +
    \ + \ + \ + \ + \ +
    \ +
    \ + \ + \ +
    当前未添加端口映射
    \ +
    \ +
    \ +
    \ +
    \ + 目录映射\ +
    \ +
    \ + \ + \ + \ + \ +
    \ +
    \ + \ + \ +
    当前未添加目录映射
    \ +
    \ +
    \ +
    \ +
    \ + 环境变量
    (每行一个)
    \ +
    \ +
    \ + \ +
    \ +
    \ +
    \ +
    \ + 内存配额\ +
    \ + MB不超过, ' + rdata.memSize + 'MB
    \ +
    \ +
    \ + CPU配额\ +
    \ + \ + 越大,占用的CPU越多
    \ +
    \ +
    \ + 执行命令\ +
    \ +
    \ + \ +
    ', + success: function() { + + $(".plus").click(function() { + var name1 = $(".type-port input[name='name1']").val(); + var name2 = $(".type-port input[name='name2']").val(); + if (name1 < 1 || name1 > 65535 || name2 < 1 || name2 > 65535 || isNaN(name1) || isNaN(name2)) { + layer.msg('端口设置值范围无效,范围 [1-65535]', { icon: 2 }); + return; + } + + var portval = $('#portabletr')[0].childNodes; + for (var i = 0; i < portval.length; i++) { + if (portval[i].childNodes[0].innerText == '当前未添加端口映射') continue; + var sport = portval[i].childNodes[2].innerText; + if (name2 == sport) { + layer.msg('端口 [' + name2 + '] 已在映射列表中!', { icon: 2 }); + return; + } + } + var address = $('.docker-address').val(); + if (address == '0.0.0.0') { + address = '*'; + } + var port = address + ':' + name2; + var loadT = layer.msg('正在检测中... ', { icon: 16, time: 0, shade: [0.3, "#000"] }); + dPost('docker_port_check', '',{port:port}, function(rdata){ + layer.close(loadT); + var rdata = $.parseJSON(rdata.data); + if (rdata.status){ + layer.msg('端口 [' + name2 + ']已在映射列表中!', { icon: 2 }); + return; + } + + var selecttype = $(".type-port select").val(); + var portable = '\ + ' + name1 + '\ + ' + selecttype + '\ + ' + name2 + '\ + 删除\ + '; + $("#portabletr").append(portable); + $(".more1").remove(); + $(".minus").click(function() { + $(this).parents("tr").remove(); + }); + }); + }); + + $(".plus2").click(function() { + var path1 = $(".type-volumes input[name='path1']").val(); + var path2 = $(".type-volumes input[name='path2']").val(); + + var notPath = ['/boot', '/bin', '/sbin', '/etc', '/usr/bin', '/usr/sbin', '/dev'] + if ($.inArray(path2, notPath) != -1) { + layer.msg('Cannot map' + path2, { icon: 2 }); + return; + } + var portval = $('#portabletr2')[0].childNodes; + for (var i = 0; i < portval.length; i++) { + if (portval[i].childNodes[0].innerText == '当前未添加端口映射') continue; + var sport = portval[i].childNodes[2].innerText; + if (path2 == sport) { + layer.msg('目录 [' + path2 + '] 已在映射列表中!', { icon: 2 }); + return; + } + } + var selecttype = $(".type-volumes select").val(); + var portable = '\ + ' + path1 + '\ + ' + selecttype + '' + path2 + '\ + 删除\ + '; + $("#portabletr2").append(portable); + $(".more2").remove(); + $(".minus2").click(function() { + $(this).parents("tr").remove(); + }); + }); + }, + yes: function(layero, layer_id) { + var ports = {}; + var volumes = {}; + var portval = $('#portabletr')[0].childNodes; + var address = $('.docker-address').val(); + var portval2 = $('#portabletr2')[0].childNodes; + var command = $('.docker-command').val() + var entrypoint = $('.docker-entrypoint').val() + var accept = []; + + //遍历端口映射 + for (var i = 0; i < portval.length; i++) { + + if (portval[i].childNodes[0].innerText == '当前未添加端口映射') { + continue; + } + + // console.log(i,portval[i].children[0].innerText); + // console.log(i,portval[i].childNodes[0].innerText); + + var port = portval[i].children[0].innerText.replace(/\s/g, ''); + var dport = port + '/' + portval[i].children[1].innerText.toLowerCase().replace(/\s/g, ''); + var sport = [address, parseInt(portval[i].children[2].innerText.replace(/\s/g, ''))]; + ports[dport] = sport + accept.push(port); + } + + //遍历目录映射 + volumes['/sys/fs/cgroup'] = { + 'bind': '/sys/fs/cgroup', + 'mode': 'rw' + }; + for (var i = 0; i < portval2.length; i++) { + if (portval2[i].childNodes[0].innerText.replace(/\s/g, ' ') == '当前未添加目录映射') { + continue; + } + var dpath = portval2[i].childNodes[2].innerText.replace(/\s/g, ''); + var spath = { + 'bind': portval2[i].childNodes[0].innerText.replace(/\s/g, ''), + 'mode': portval2[i].childNodes[1].innerText.toLowerCase().replace(/\s/g, '') + }; + volumes[dpath] = spath; + } + + var data = { + image: $('.docker-image').val(), + ports: JSON.stringify(ports), + accept: JSON.stringify(accept), + volumes: JSON.stringify(volumes), + environments: $('.docker-environments').val(), + mem_limit: $('.docker-mem').val(), + cpu_shares: $('.docker-cpu').val(), + command: command, + entrypoint: entrypoint + } + + if (data.mem_limit > rdata.memSize) { + layer.msg('内存配额不能大于物理内存 [' + rdata.memSize + ']!', { icon: 2 }); + return; + } + + if (data.cpu_shares > 100 || data.cpu_shares < 1) { + layer.msg('CPU配额设置值范围应为 [1-100]!', { icon: 2 }); + return; + } + + // console.log(data); + dPost('docker_create_con','', data, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + if(rdata.status) { + layer.close(layer_index); + dockerConListRender(); + } + },{ icon: rdata.status ? 1 : 2 }); + }); + } + }); + }); + +} + +function dockerConList() { + + var con = '
    \ + \ +
    \ +
    \ + \ + \ + \ + \ + \ + \ + \ +
    名称镜像创建时间状态操作
    \ +
    \ +
    \ +
    '; + + $(".soft-man-con").html(con); + dockerConListRender(); +} + +function deleteImages(tag, id) { + safeMessage('删除镜像', '删除镜像[' + tag + '],确定?', function() { + dPost('docker_remove_image', '', { imageId: id, repoTags: tag }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + if (rdata.status) { + dockerImageListRender(); + } + }, { icon: rdata.status ? 1 : 2 }); + }); + }); +} + +function pullImages(tag, id) { + console.log(tag, id); + layer.msg('开发中!', { icon: 2 }); +} + +function dockerImageListRender() { + dPost('image_list', '', {}, function(rdata) { + var rdata = $.parseJSON(rdata.data); + // console.log(rdata); + if (!rdata.status) { + layer.msg(rdata.msg, { icon: 2, time: 2000 }); + return; + } + + var list = ''; + var rlist = rdata.data; + + for (var i = 0; i < rlist.length; i++) { + + var tag = rlist[i]['RepoTags'].split(":")[1]; + + var license = 'null'; + var desc = 'null'; + if (rlist[i]['Labels'] == null) { + license = 'free'; + } + + var op = ''; + op += '拉取 | '; + op += '删除'; + + list += ''; + list += '' + rlist[i]['RepoTags'] + ''; + list += '' + tag + ''; + list += '' + toSize(rlist[i]['Size']) + ''; + list += '' + license + ''; + list += '' + desc + ''; + list += '' + op + ''; + list += ''; + } + + $('#con_list tbody').html(list); + }); +} + +function dockerPullImagesFileTemplate() { + // 拉取镜像文件模板 + var layer_index = layer.open({ + type: 1, + title: "获取镜像", + area: '500px', + closeBtn: 2, + shadeClose: false, + content: '
    ' + + '
    ' + + '官方库' + + '公共库' + + '私有库' + + '
    ' + + '
    ' + + '
    ' + + '镜像名:\ +
    \ + \ + \ +
    ' + + '
    ' + + '' + + '\ +
    \ +
    ', + success: function(layero, layer_id) { + + $('.docker-sub span').click(function() { + var index = $(this).index(); + $(this).addClass('on').siblings().removeClass('on'); + $(this).parent().next().find('.conter').eq(index).show().siblings().hide(); + }); + + $('.official_pull_btn').click(function() { + var name = $('[name="official_pull_name"]').val(); + if (name == '') { + layer.msg('镜像名不能为空!'); + return; + } + dPost('docker_pull', '', { images: name }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + if (rdata.status) { + layer.close(layer_index); + dockerImageListRender(); + + } + }, { icon: rdata.status ? 1 : 2 }); + }); + }); + + $('.public_pull_btn').click(function() { + var path = $('[name="public_pull_path"]').val(); + if (path == '') { + layer.msg('公共网络镜像地址不能为空。'); + return; + } + + dPost('docker_pull_reg', '', { path: path }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + if (rdata.status) { + layer.close(layer_index); + dockerImageListRender(); + } + }, { icon: rdata.status ? 1 : 2 }); + }); + }); + $('.private_pull_btn').click(function() { + var path = $('[name="private_pull_path"]').val(); + if (path == '') { + layer.msg('专用镜像地址不能为空!'); + return + } + + dPost('docker_pull_private_new', '', { path: path }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + if (rdata.status) { + layer.close(layer_index); + dockerImageListRender(); + } + }, { icon: rdata.status ? 1 : 2 }); + }); + }); + } + + }); +} + +function dockerImageList() { + + var con = '
    \ + \ +
    \ +
    \ + \ + \ + \ + \ + \ + \ + \ + \ +
    名称版本大小证书描述操作
    \ +
    \ +
    \ +
    \ +
    '; + + $(".soft-man-con").html(con); + + dockerImageListRender(); +} + +//获取文件数据 +function dockerGetFileBytes(fileName) { + window.open('/files/download?filename=' + encodeURIComponent(fileName)); +} + +//删除文件 +function dockerDeleteFile(fileName) { + layer.confirm(lan.get('recycle_bin_confirm', [fileName]), { title: '删除文件', closeBtn: 2, icon: 3 }, function() { + layer.msg('正在处理,请稍候...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/files/delete', 'path=' + encodeURIComponent(fileName), function(rdata) { + showMsg(rdata.msg, function() { + dockerImageOutputRender(); + }, { icon: rdata.status ? 1 : 2 }); + }, 'json'); + }); +} + +function dockerLoadFile(fileName) { + dPost('image_pick_load', '', { file: fileName }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + dockerImageOutputRender(); + }, { icon: rdata.status ? 1 : 2 }); + }); +} + +function dockerImageOutputRender() { + dPost('image_pick_list', '', {}, function(rdata) { + var rdata = $.parseJSON(rdata.data); + if (!rdata.status) { + layer.msg(rdata.msg, { icon: 2, time: 10000 }); + return; + } + + var list = ''; + var rlist = rdata.data; + + for (var i = 0; i < rlist.length; i++) { + + var op = ''; + op += '下载 | '; + op += '导入 | '; + op += '删除'; + + list += ''; + list += '' + rlist[i]['name'] + ''; + list += '' + rlist[i]['size'] + ''; + list += '' + rlist[i]['time'] + ''; + list += '' + op + ''; + list += ''; + } + + $('#con_list tbody').html(list); + }); +} + +//上传文件 +function uploadImageFiles(upload_dir) { + var image_layer = layer.open({ + type: 1, + closeBtn: 1, + title: "上传导入文件[" + upload_dir + ']', + area: ['500px', '300px'], + shadeClose: false, + content: '
    \ + \ + \ + \ + \ + \ + \ + 文件编码:\ + \ + \ + \ +
      \ +
      ', + success: function() { + $('#filesClose').click(function() { + layer.close(image_layer); + }); + } + + }); + uploadStart(function() { + showMsg('上传成功!', function() { + dockerImageOutputRender(); + layer.close(image_layer); + }, { icon: 1, time: 2000 }); + }); +} + +function dockerImagePick() { + + dPost('image_list', '', {}, function(rdata) { + var rdata = $.parseJSON(rdata.data); + var imageList = rdata.data; + // console.log(imageList); + var _tbody = ''; + for (var i = 0; i < imageList.length; i++) { + if (imageList[i] == null) { + _tbody = '当前无镜像'; + continue; + } + var versionData = imageList[i].RepoTags, + version, reg = new RegExp('((?<=:)[0-9A-z/.-]*)$'); + version = versionData.match(reg); + _tbody += "\ + " + imageList[i].RepoTags + "\ + " + version[0] + "\ + " + toSize(imageList[i].Size) + ""; + } + + + var layerS = layer.open({ + type: 1, + title: "选择镜像", + area: '500px', + closeBtn: 2, + btn: ['打包', '取消'], + shadeClose: false, + content: '
      \ + \ + \ + \ + \ + \ + \ + \ + ' + _tbody + '\ +
      名称版本大小
      \ +
      ', + success: function() { + readerTableChecked(); + tableFixed('images_table'); + }, + yes: function(layers, index) { + var data = '', + tit = '\xa0', + choose_num = $(".images_pull tbody [name=images]:checked").length; + for (var i = 0; i < choose_num; i++) { + if (choose_num == 0) { + layer.msg('Please choose the images which need to pack', { icon: 2 }); + return false; + } + data += $(".images_pull tbody [name=images]:checked").eq(i).attr('data-name'); + if (i != (choose_num - 1)) data += ' '; + } + + dPost('image_pick_save', '', { images: data }, function(rdata) { + var rdata = $.parseJSON(rdata['data']); + showMsg(rdata.msg, function() { + dockerImageOutputRender(); + layer.close(layerS); + }, { icon: rdata.status ? 1 : 2, time: 2000 }); + }); + } + }); + }); +} + +function dockerImageOutput() { + var con = '
      \ + \ + \ +
      \ +
      \ + \ + \ + \ + \ + \ + \ +
      名称大小时间操作
      \ +
      \ +
      \ +
      \ +
      '; + + $(".soft-man-con").html(con); + + + $('#btn_image_upload').click(function() { + dPostOrgin({ + name: 'docker', + func: 'image_pick_dir', + version: '', + }, function(rdata) { + var rdata = $.parseJSON(rdata['data']); + var upload_dir = rdata['data']; + uploadImageFiles(upload_dir); + }); + }); + + dockerImageOutputRender(); +} + +function deleteIpList(address) { + safeMessage('删除IP', '你将删除从IP地址池[' + address + '],确定?', function() { + dPost('docker_del_ip', '', { address: address }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + if (rdata.status) { + dockerIpListRender(); + } + }, { icon: rdata.status ? 1 : 2 }); + }); + }); +} + +function dockerIpListRender() { + dPost('docker_get_iplist', '', {}, function(rdata) { + var rdata = $.parseJSON(rdata.data); + // console.log(rdata); + if (!rdata.status) { + layer.msg(rdata.msg, { icon: 2, time: 2000 }); + return; + } + + var list = ''; + var rlist = rdata.data; + + for (var i = 0; i < rlist.length; i++) { + + var op = ''; + op += '删除'; + + list += ''; + list += '' + rlist[i]['address'] + ''; + list += '' + rlist[i]['netmask'] + ''; + list += '' + rlist[i]['gateway'] + ''; + list += '' + op + ''; + list += ''; + } + + $('#ip_list tbody').html(list); + }); +} + +function dockerAddIpPool() { + var address = $('input[name="address"]').val(); + var netmask = $('input[name="netmask"]').val(); + var gateway = $('input[name="gateway"]').val(); + dPost('docker_add_ip', '', { address: address, netmask: netmask, gateway: gateway }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function() { + dockerIpListRender(); + }, { icon: rdata.status ? 1 : 2 }) + }); +} + +function dockerIpList() { + var con = '
      \ +
      \ + \ + \ + \ + \ +
      \ +
      \ +
      \ + \ + \ + \ + \ + \ + \ +
      IP地址子网掩码网关操作
      \ +
      \ +
      \ +
      \ +
      '; + + $(".soft-man-con").html(con); + + dockerIpListRender(); +} + +// login +function repoLogin() { + var _option1 = ""; + var obj = { hub_name: "", namespace: "", name: "", registry: "", user_pass: "", user_name: "", arry: ['Docker Repository', 'Other Repository'] }; + for (var i = 0; i < obj.arry.length; i++) { + _option1 += ''; + } + var layer_index = layer.open({ + type: 1, + title: "登录到存储库", + area: '450px', + closeBtn: 2, + shadeClose: false, + content: '
      ' + + '' + + '
      ' + + '
      Repository Type
      ' + + '
      Name:
      ' + + '
      Username:
      ' + + '
      Password:
      ' + + '
      Repository Name:
      ' + + '
      Namespaces:
      ' + + '' + + '
      ' + + '
      ' + + '
      ', + success: function() { + $('[name="dtype"]').change(function(e) { + var docker_type = $(this).val(); + if (docker_type == 'Other Repository') { + $('.docker_content .line').show(); + } else { + $('.docker_content .line').filter(":lt(3)").show().end().filter(":gt(4)").hide(); + } + }); + $('.login_aliyun').click(function() { + var user = $('[name="user"]').val(), + passwd = $('[name="passwd"]').val(), + registry = $('[name="registry"]').val(), + name = $('[name="ctm_name"]').val(), + hub_name = $('[name="hub_name"]').val(), + namespace = $('[name="namespace"]').val(); + + var args = { + user: user, + passwd: passwd, + registry: '', + repository_name: name, + hub_name: hub_name, + namespace: namespace + }; + if ($('[name="dtype"]').val() == 'Docker Repository') { + args.registry = ''; + } else { + args.registry = registry; + } + + console.log(obj); + dPost('docker_login', '', args, function(rdata) { + var rdata = $.parseJSON(rdata.data); + console.log(rdata); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (res.status) { + repoListRender(); + layer.close(layer_index); + } + }); + }); + } + }); + +} + + +function delRepo(address) { + safeMessage('退出', '你将退出 [' + address + '],确定?', function() { + dPost('docker_logout', '', { registry: address }, + function(rdata) { + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + repoListRender(); + } + } + ); + }); +} + + +function repoListRender() { + dPost('repo_list', '', {}, function(rdata) { + var rdata = $.parseJSON(rdata.data); + console.log(rdata); + if (!rdata.status) { + layer.msg(rdata.msg, { icon: 2, time: 2000 }); + return; + } + + var list = ''; + var rlist = rdata.data; + + for (var i = 0; i < rlist.length; i++) { + + list += ''; + list += '' + rlist[i]['hub_name'] + ''; + list += '' + rlist[i]['repository_name'] + ''; + list += '' + rlist[i]['namespace'] + ''; + list += '' + rlist[i]['registry'] + ''; + list += '删除'; + list += ''; + } + + $('#con_list tbody').html(list); + }); +} + +function repoList() { + + var con = '
      \ + \ +
      \ +
      \ + \ + \ + \ + \ + \ + \ + \ + \ + ' + '
      NameRepository NameNameSpace地址操作
      \ +
      \ +
      \ +
      \ +
      '; + + $(".soft-man-con").html(con); + + //login + $('#docker_login').click(function() { + repoLogin(); + }); + + repoListRender(); +} \ No newline at end of file diff --git a/plugins/doh/config/config.toml b/plugins/doh/config/config.toml new file mode 100644 index 000000000..c358cf803 --- /dev/null +++ b/plugins/doh/config/config.toml @@ -0,0 +1,48 @@ +[general] +# 监听地址和端口 +listen_addr = "0.0.0.0:8080" + +# DoH 路径 +path = "/dns-query" + +# TLS 配置(如果需要 HTTPS) +# tls_cert_path = "/etc/doh-proxy/cert.pem" +# tls_key_path = "/etc/doh-proxy/key.pem" + +# 并发请求限制 +max_concurrent_requests = 512 + +# 日志文件 +log_file = "{$SERVER_PATH}/doh/doh-proxy.log" + +[upstream] +# 上游 DNS 服务器 +upstream_addr = "1.1.1.1:443" +bootstrap_addr = "1.1.1.1:53" + +# 上游 DoH 服务器 URL(如果使用标准的 DoH 端点) +# upstream_url = "https://cloudflare-dns.com/dns-query" + +# 超时设置 +timeout = 10 + +# 重试次数 +tries = 3 + +# 启用 TCP 保活 +tcp_keepalive = 30 + +[cache] +# 缓存设置 +max_entries = 65536 +min_ttl = 60 +max_ttl = 3600 + +[network] +# 网络设置 +tcp_fastopen = true +reuse_port = true + +[log] +# 日志级别:debug, info, warn, error +level = "info" \ No newline at end of file diff --git a/plugins/doh/ico.png b/plugins/doh/ico.png new file mode 100644 index 000000000..2260f976a Binary files /dev/null and b/plugins/doh/ico.png differ diff --git a/plugins/doh/index.html b/plugins/doh/index.html new file mode 100755 index 000000000..000fe9a45 --- /dev/null +++ b/plugins/doh/index.html @@ -0,0 +1,26 @@ + +
      +
      +
      +

      服务

      +

      自启动

      +

      使用说明

      +
      +
      +
      +
      +
      +
      + \ No newline at end of file diff --git a/plugins/doh/index.py b/plugins/doh/index.py new file mode 100755 index 000000000..6d667fdb0 --- /dev/null +++ b/plugins/doh/index.py @@ -0,0 +1,248 @@ +# coding: utf-8 + + +import time +import os +import sys +import re +import subprocess + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'doh' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':', 1) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getInitdConfTpl(): + path = getPluginDir() + "/init.d/gitea.tpl" + return path + + +def getInitdConf(): + path = getServerDir() + "/init.d/doh" + return path + + if not os.path.exists(path): + return mw.returnJson(False, "请先安装初始化!
      默认地址:http://" + mw.getLocalIp() + ":3000") + return path + + +def getConfTpl(): + path = getPluginDir() + "/config/config.toml" + return path + + +def status(): + data = mw.execShell( + "ps -ef|grep " + getPluginName() + " |grep -v grep | grep -v python | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + + +def getHomeDir(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + else: + return 'www' + + + +def contentReplace(content): + + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + return content + + +def initDreplace(): + + file_tpl = getInitdConfTpl() + service_path = mw.getServerDir() + + + conf_toml = getServerDir() + '/config.toml' + if not os.path.exists(conf_toml): + conf_tpl = getConfTpl() + content = mw.readFile(conf_tpl) + mw.writeFile(conf_toml, content) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/doh.service' + systemServiceTpl = getPluginDir() + '/init.d/doh.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + log_path = getServerDir() + '/log' + if not os.path.exists(log_path): + os.mkdir(log_path) + + return '' + + + + +def appOp(method): + initDreplace() + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return 'fail' + return "fail" + + +def start(): + return appOp('start') + + +def stop(): + return appOp('stop') + + +def restart(): + return appOp('restart') + + +def reload(): + return appOp('reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status ' + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + log_path = getServerDir() + '/log/doh.log' + return log_path + +def getTotalStatistics(): + st = status() + data = {} + if st.strip() == 'start': + list_count = pQuery('select count(id) as num from repository') + count = list_count[0]["num"] + data['status'] = True + data['count'] = count + data['ver'] = mw.readFile(getServerDir() + '/version.pl').strip() + return mw.returnJson(True, 'ok', data) + + data['status'] = False + data['count'] = 0 + return mw.returnJson(False, 'fail', data) + + +def uninstallPreInspection(): + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'run_log': + print(runLog()) + elif func == 'post_receive_log': + print(postReceiveLog()) + elif func == 'conf': + print(getConf()) + elif func == 'init_conf': + print(getInitdConf()) + elif func == 'get_total_statistics': + print(getTotalStatistics()) + else: + print('fail') diff --git a/plugins/doh/info.json b/plugins/doh/info.json new file mode 100755 index 000000000..21edc77d9 --- /dev/null +++ b/plugins/doh/info.json @@ -0,0 +1,18 @@ +{ + "ps": "DNS over HTTPS(DoH)是一种通过HTTPS协议加密域名解析请求的技术!", + "name": "doh", + "title": "DoH", + "versions": ["0.9.15"], + "tip": "soft", + "install_pre_inspection":false, + "uninstall_pre_inspection":true, + "checks": "server/doh", + "path":"server/doh", + "author": "doh", + "date": "2025-11-23", + "home": "https://github.com/DNSCrypt/doh-server", + "type": "doh", + "shell": "install.sh", + "pid": "4", + "sort": 7 +} \ No newline at end of file diff --git a/plugins/doh/init.d/doh.service.tpl b/plugins/doh/init.d/doh.service.tpl new file mode 100644 index 000000000..fb7a35366 --- /dev/null +++ b/plugins/doh/init.d/doh.service.tpl @@ -0,0 +1,21 @@ +[Unit] +Description=DOH(DNS over HTTPS) +After=syslog.target +After=network.target + +[Service] +RestartSec=2s +Type=simple +User=www +Group=www +WorkingDirectory={$SERVER_PATH}/doh +# /www/server/doh/doh-proxy -u 127.0.0.1:53 -l 127.0.0.1:3000 +# /www/server/doh/doh-proxy -h +ExecStart={$SERVER_PATH}/doh/doh-proxy -u 127.0.0.1:53 -l 127.0.0.1:3000 +Restart=always +RemainAfterExit=yes +#AmbientCapabilities=CAP_NET_BIND_SERVICE +#CapabilityBoundingSet=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target diff --git a/plugins/doh/install.sh b/plugins/doh/install.sh new file mode 100755 index 000000000..361fb51a0 --- /dev/null +++ b/plugins/doh/install.sh @@ -0,0 +1,102 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + + +# cd /www/server/mdserver-web/plugins/doh && bash install.sh install 0.9.15 + +# /www/server/doh/doh-proxy --config /www/server/doh/config.toml +# /www/server/doh/doh-proxy --config /www/server/doh/config.toml --check + + +# /www/server/doh/doh-proxy -u 127.0.0.1:53 -l 127.0.0.1:3000 + +# 详细状态信息 +# sudo systemctl status doh -l +# 查看完整日志 +# sudo journalctl -u doh -n 100 +# 实时日志跟踪 +# sudo journalctl -u doh -f + + +URL_DOWNLOAD=https://github.com/DNSCrypt/doh-server/releases/download + + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +OSNAME_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +Install_App() +{ + + mkdir -p $serverPath/source/doh + + echo '正在安装脚本文件...' + version=$1 + + if [ "macos" == "$OSNAME" ];then + echo "not support!" + exit + else + file=doh-proxy_${version}_linux-x86_64 + fi + + # https://github.com/DNSCrypt/doh-server/releases/download/0.9.15/doh-proxy_0.9.15_linux-aarch64.tar.bz2 + file_xz="${file}.tar.bz2" + echo "wget -O $serverPath/source/doh/$file_xz ${URL_DOWNLOAD}/${version}/${file_xz}" + if [ ! -f $serverPath/source/doh/$file_xz ];then + wget --no-check-certificate -O $serverPath/source/doh/$file_xz ${URL_DOWNLOAD}/${version}/${file_xz} + fi + + if [ -f $serverPath/source/doh/$file_xz ];then + cd $serverPath/source/doh && tar -xjf $file_xz + fi + + + echo "mv $serverPath/source/doh/doh-proxy $serverPath/doh" + if [ -f $serverPath/source/doh/doh-proxy ];then + mv $serverPath/source/doh/doh-proxy $serverPath/doh + fi + + + if [ -d $serverPath/doh ];then + echo $version > $serverPath/doh/version.pl + + cd ${rootPath} && python3 plugins/doh/index.py start + cd ${rootPath} && python3 plugins/doh/index.py initd_install + fi + + echo 'install doh success' +} + +Uninstall_App() +{ + + if [ -f /usr/lib/systemd/system/doh.service ];then + systemctl stop doh + systemctl disable doh + rm -rf /usr/lib/systemd/system/doh.service + systemctl daemon-reload + fi + + rm -rf $serverPath/doh + echo 'uninstall doh success' +} + + +action=$1 +version=$2 +if [ "${1}" == 'install' ];then + Install_App $version +else + Uninstall_App $version +fi diff --git a/plugins/doh/js/doh.js b/plugins/doh/js/doh.js new file mode 100755 index 000000000..c2d7b6397 --- /dev/null +++ b/plugins/doh/js/doh.js @@ -0,0 +1,37 @@ + +function dohPost(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'doh', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function dohRead(){ + + var readme = '
        '; + readme += '
      • DNS服务
      • '; + readme += '
      '; + + $('.soft-man-con').html(readme); +} \ No newline at end of file diff --git a/plugins/doh/t.md b/plugins/doh/t.md new file mode 100644 index 000000000..1eaf2b6a9 --- /dev/null +++ b/plugins/doh/t.md @@ -0,0 +1,2 @@ +🌐 Custom DoH Proxy +Use /dns-query endpoint \ No newline at end of file diff --git a/plugins/dynamic-tracking/ico.png b/plugins/dynamic-tracking/ico.png new file mode 100644 index 000000000..cef792edd Binary files /dev/null and b/plugins/dynamic-tracking/ico.png differ diff --git a/plugins/dynamic-tracking/index.html b/plugins/dynamic-tracking/index.html new file mode 100755 index 000000000..0583f4900 --- /dev/null +++ b/plugins/dynamic-tracking/index.html @@ -0,0 +1,19 @@ +
      +
      +
      +
      +

      服务

      +

      自启动

      +
      +
      +
      +
      +
      +
      + \ No newline at end of file diff --git a/plugins/dynamic-tracking/index.py b/plugins/dynamic-tracking/index.py new file mode 100755 index 000000000..a54965cdc --- /dev/null +++ b/plugins/dynamic-tracking/index.py @@ -0,0 +1,233 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'dynamic-tracking' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + dir_path = getServerDir() + '/trace' + if not os.path.exists(dir_path): + os.mkdir(dir_path) + return 'start' + + +def dtOp(method): + return 'ok' + + +def start(): + return dtOp('start') + + +def stop(): + return dtOp('stop') + + +def restart(): + status = dtOp('restart') + return status + + +def reload(): + return dtOp('reload') + + +def initdStatus(): + return 'ok' + + +def initdInstall(): + return 'ok' + + +def initdUinstall(): + return 'ok' + + +def get_file(args): + dir_path = getServerDir() + '/trace' + + path = dir_path + '/' + args['file'] + '/main.svg' + + if os.path.exists(path): + d = mw.readFile(path) + return mw.returnData(True, 'ok', d) + else: + return mw.returnData(False, '无效目录') + + +def get_file_path(args): + dir_path = getServerDir() + '/trace' + path = dir_path + '/' + args['file'] + '/main.svg' + if os.path.exists(path): + return mw.returnData(True, 'ok', path) + else: + return mw.returnData(False, '无效目录') + + +def dtGetFilePath(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + dir_path = getServerDir() + '/trace' + path = dir_path + '/' + args['file'] + '/main.svg' + if os.path.exists(path): + return mw.returnJson(True, 'ok', path) + else: + return mw.returnJson(False, '无效目录') + + +def dtRemoveFilePath(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + dir_path = getServerDir() + '/trace' + path = dir_path + '/' + args['file'] + if os.path.exists(path): + mw.execShell('rm -rf ' + path) + return mw.returnJson(True, '删除成功!') + + +def dtFileList(): + dir_path = getServerDir() + '/trace' + if not os.path.exists(dir_path): + os.mkdir(dir_path) + + file_info = [] + for name in os.listdir(dir_path): + if name == ".DS_Store": + continue + + # print(name) + info = {} + try: + info['name'] = name + info['abs_path'] = dir_path + '/' + name + '/main.svg' + except Exception as e: + return mw.returnJson(False, str(e)) + + file_info.append(info) + + file_info = sorted(file_info, key=lambda x: x['name'], reverse=False) + return mw.returnJson(True, 'ok!', file_info) + + +def dtSimpleTrace(): + if mw.isAppleSystem(): + return mw.returnJson(False, 'macosx只能手动执行!') + + args = getArgs() + data = checkArgs(args, ['pid']) + if not data[0]: + return data[1] + + plugins_shell = getPluginDir() + '/shell/simple_trace.sh' + cmd = plugins_shell + ' "' + args['pid'] + '"' + + data = mw.execShell("bash " + cmd + " &") + return mw.returnJson(True, '执行成功!') + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'file_list': + print(dtFileList()) + elif func == 'get_file_path': + print(dtGetFilePath()) + elif func == 'remove_file_path': + print(dtRemoveFilePath()) + elif func == 'simple_trace': + print(dtSimpleTrace()) + else: + print('error') diff --git a/plugins/dynamic-tracking/info.json b/plugins/dynamic-tracking/info.json new file mode 100755 index 000000000..e72837d9a --- /dev/null +++ b/plugins/dynamic-tracking/info.json @@ -0,0 +1,36 @@ +{ + "hook":[ + { + "tag":"menu", + "menu": { + "title":"动态追踪", + "name":"dynamic-tracking", + "path":"static/html/index.html", + "css_path":"static/css/dynamic-tracking.css", + "js_path":"static/js/dynamic-tracking.js" + } + }, + { + "tag":"global_static", + "global_static": { + "title":"动态追踪", + "name":"dynamic-tracking", + "css_path":"static/css/ico.css" + } + } + ], + "sort": 7, + "ps": "专业使用,动态追踪调试程序<无侵入>![潜龙勿用|实验]", + "name": "dynamic-tracking", + "title": "动态追踪", + "shell": "install.sh", + "versions":["0.1"], + "tip": "soft", + "checks": "server/dynamic-tracking", + "path": "server/dynamic-tracking", + "display": 1, + "author": "midoks", + "date": "2023-08-17", + "type": 0, + "pid": "4" +} diff --git a/plugins/dynamic-tracking/install.sh b/plugins/dynamic-tracking/install.sh new file mode 100755 index 000000000..16d62325e --- /dev/null +++ b/plugins/dynamic-tracking/install.sh @@ -0,0 +1,90 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/dynamic-tracking && /bin/bash install.sh uninstall 1.0 +# cd /www/server/mdserver-web/plugins/dynamic-tracking && /bin/bash install.sh install 1.0 + +VERSION=$2 + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + + +OSNAME=`bash ${rootPath}/scripts/getos.sh` + +if [ "" == "$OSNAME" ];then + OSNAME=`cat ${rootPath}/data/osname.pl` +fi + +if [ "macos" != "$OSNAME" ];then + SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +fi + +Install_App() +{ + + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/dynamic-tracking + + cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") + HTTP_PREFIX="https://" + if [ ! -z "$cn" ];then + HTTP_PREFIX="https://mirror.ghproxy.com/" + fi + + # FlameGraph start + if [ ! -d $serverPath/dynamic-tracking/FlameGraph ];then + if [ ! -f $serverPath/source/FlameGraph.zip ]; then + wget --no-check-certificate -O $serverPath/source/FlameGraph.zip ${HTTP_PREFIX}github.com/brendangregg/FlameGraph/archive/refs/heads/master.zip + fi + + cd $serverPath/source && unzip $serverPath/source/FlameGraph.zip + mv $serverPath/source/FlameGraph-master $serverPath/dynamic-tracking/FlameGraph + fi + # FlameGraph end + + # apt install linux-tools-$(uname -r) linux-tools-generic -y + # apt install linux-tools-$(uname -r) linux-tools-generic -y --fix-missing + + + + shell_file=${curPath}/versions/${OSNAME}.sh + echo $shell_file + if [ ! -f $shell_file ];then + echo '不支持...' + exit 1 + fi + + bash -x $shell_file + + echo "cp -rf ${curPath}/shell $serverPath/dynamic-tracking" + cp -rf ${curPath}/shell $serverPath/dynamic-tracking + + echo "${VERSION}" > $serverPath/dynamic-tracking/version.pl + echo '安装完成' + + cd ${rootPath} && python3 ${rootPath}/plugins/dynamic-tracking/index.py start + # cd ${rootPath} && python3 ${rootPath}/plugins/dynamic-tracking/index.py initd_install + +} + +Uninstall_App() +{ + rm -rf $serverPath/dynamic-tracking + echo "Uninstall_App" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/dynamic-tracking/shell/simple_macosx_trace.sh b/plugins/dynamic-tracking/shell/simple_macosx_trace.sh new file mode 100644 index 000000000..a8929266d --- /dev/null +++ b/plugins/dynamic-tracking/shell/simple_macosx_trace.sh @@ -0,0 +1,39 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + + +# debug +# cd /www/server/mdserver-web +# bash /www/server/mdserver-web/plugins/dynamic-tracking/shell/simple_macosx_trace.sh "22431" + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web +# bash /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/dynamic-tracking/shell/simple_macosx_trace.sh "22431" + +# dtrace -x ustackframes=100 -n 'pid$target::mach_msg_trap:entry { @[ustack()] = count(); } tick-30s { exit(0); }' -p 18572 -o out.SystemUIServer_stacks +# /Users/midoks/Desktop/mwdev/server/dynamic-tracking/FlameGraph/stackcollapse.pl out.SystemUIServer_stacks > kernel.cbt +# /Users/midoks/Desktop/mwdev/server/dynamic-tracking/FlameGraph/flamegraph.pl kernel.cbt > kernel.svg + +curPath=`pwd` +rootPath=$(dirname "$curPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +PID=$1 + +echo $rootPath # /Users/midoks/Desktop/mwdev/server +echo $curPath # /Users/midoks/Desktop/mwdev/server/mdserver-web + +APP_DIR=${rootPath}/dynamic-tracking +DST_FILE_DIR=${APP_DIR}/trace/PID_${PID} +mkdir -p $DST_FILE_DIR + +DST_FILE=${DST_FILE_DIR}/out.SystemUIServer_stacks + +if [ ! -f $DST_FILE ];then + sudo dtrace -x ustackframes=100 -n 'pid$target::mach_msg_trap:entry { @[ustack()] = count(); } tick-30s { exit(0); }' -p "$PID" -o $DST_FILE +fi + +${APP_DIR}/FlameGraph/stackcollapse.pl $DST_FILE > ${DST_FILE_DIR}/kernel.cbt +${APP_DIR}/FlameGraph/flamegraph.pl ${DST_FILE_DIR}/kernel.cbt > ${DST_FILE_DIR}/main.svg + diff --git a/plugins/dynamic-tracking/shell/simple_trace.sh b/plugins/dynamic-tracking/shell/simple_trace.sh new file mode 100644 index 000000000..d16795236 --- /dev/null +++ b/plugins/dynamic-tracking/shell/simple_trace.sh @@ -0,0 +1,43 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + + +# debug +# cd /www/server/mdserver-web +# bash /www/server/mdserver-web/plugins/dynamic-tracking/shell/simple_trace.sh "2401699" + + +curPath=`pwd` +rootPath=$(dirname "$curPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +PID=$1 + +echo $rootPath # /Users/midoks/Desktop/mwdev/server +echo $curPath # /Users/midoks/Desktop/mwdev/server/mdserver-web + +APP_DIR=/www/server/dynamic-tracking +DST_FILE_DIR=${APP_DIR}/trace/PID_${PID} +mkdir -p $DST_FILE_DIR + +# DST_FILE=${DST_FILE_DIR}/main.strace + +# perf record -F 99 -p 335584 -g -- sleep 30 + +cd ${DST_FILE_DIR} +perf record -F 99 -p ${PID} -g -- sleep 30 + +perf script > out.perf +${APP_DIR}/FlameGraph/stackcollapse-perf.pl ${DST_FILE_DIR}/out.perf > ${DST_FILE_DIR}/out.folded +${APP_DIR}/FlameGraph/flamegraph.pl ${DST_FILE_DIR}/out.folded > ${DST_FILE_DIR}/main.svg + +# perf record -F 99 -p 2401699 -g -- sleep 30 +# perf script > out.perf +# /www/server/dynamic-tracking/FlameGraph/stackcollapse-perf.pl out.perf > out.folded +# /www/server/dynamic-tracking/FlameGraph/flamegraph.pl out.folded > php-zend-flame-graph.svg + +# ${APP_DIR}/FlameGraph/stackcollapse.pl $DST_FILE > ${DST_FILE_DIR}/kernel.cbt +# ${APP_DIR}/FlameGraph/flamegraph.pl ${DST_FILE_DIR}/kernel.cbt > ${DST_FILE_DIR}/main.svg + diff --git a/plugins/dynamic-tracking/static/css/dynamic-tracking.css b/plugins/dynamic-tracking/static/css/dynamic-tracking.css new file mode 100644 index 000000000..a6c04f524 --- /dev/null +++ b/plugins/dynamic-tracking/static/css/dynamic-tracking.css @@ -0,0 +1,75 @@ +.screen-all { + padding: 5px; +} + +.s-right .panel-default{ + margin-bottom: 10px; +} + +.s-left .panel-default{ + margin-bottom: 10px; +} + + +.data-collect { + width: 45px; + height: 30px; + border: 0; + cursor: pointer; + margin-right: 2px; + margin-top: 10px; + color: #fff; + background-color: #20a53a; + border-color: #20a53a; +} + + +#file_list .list { + list-style: none; + border-top: none; + border: 1px solid #ececec; + overflow-y: auto; +} + +#file_list .list li.active { + background-color: #f2f2f2; +} + +#file_list .tab-con{ + padding: 0px; + margin-top: 10px; +} + +#file_list .list li>span { + vertical-align: top; + display: inline-block; +} + +#file_list .list li { + display: inline-block; + height: 38px; + line-height: 38px; + color: #444; + border-bottom: 1px solid #ececec; + font-size: 13px; + cursor: pointer; + position: relative; + width: 100%; +} + +#file_list .file { + padding-left: 10px; +} + +#file_list .list li .tootls{ + width: 70px; + text-align: right; + padding-right: 10px; + /* display: none; */ + position: absolute; + right: 5px; +} + +/*.tab-con .tab-block.on { + display: inline-block; +}*/ \ No newline at end of file diff --git a/plugins/dynamic-tracking/static/css/ico.css b/plugins/dynamic-tracking/static/css/ico.css new file mode 100644 index 000000000..679eac375 --- /dev/null +++ b/plugins/dynamic-tracking/static/css/ico.css @@ -0,0 +1,11 @@ + +/* menu start */ +.menu .current .menu_dynamic-tracking:hover { + background-image: url("/plugins/file?name=dynamic-tracking&f=ico.png"); +} + +.menu .menu_plugin_dynamic-tracking { + background-image: url("/plugins/file?name=dynamic-tracking&f=ico.png"); +} + +/* menu end */ \ No newline at end of file diff --git a/plugins/dynamic-tracking/static/html/index.html b/plugins/dynamic-tracking/static/html/index.html new file mode 100644 index 000000000..2560182d5 --- /dev/null +++ b/plugins/dynamic-tracking/static/html/index.html @@ -0,0 +1,81 @@ + + +
      +
      +
      +
      + 首页/动态追踪 +
      + + +
      + + +
      + +
      +
      + 文件列表 +
      + +
      +
      +
        +
        +
        +
        + +
        +
        + 火焰图 +
        + +
        + +
        +
        + +
        + +
        +
        + + diff --git a/plugins/dynamic-tracking/static/js/dynamic-tracking.js b/plugins/dynamic-tracking/static/js/dynamic-tracking.js new file mode 100755 index 000000000..466613e40 --- /dev/null +++ b/plugins/dynamic-tracking/static/js/dynamic-tracking.js @@ -0,0 +1,178 @@ +function changeDivH(){ + var l = $(window).height(); + var w = $(window).width(); + + $('#ff_box').css('height',l-80-60); + + $('#file_list').css('height',l-80-60); + $('#file_list .tab-con .list').css('height', l-80-60-70); + + + $('#flame_graph').css('height',l-80-60).css('width',w-300-200-40); + $('#flame_graph iframe').css('height',l-80-60-70); +} + + +$(document).ready(function(){ + var tag = $.getUrlParam('tag'); + if(tag == 'dynamic-tracking'){ + dynamicTrackingLoad(); + } +}); + +function dynamicTrackingLoad(){ + changeDivH(); + $(window).resize(function(){ + changeDivH(); + }); + + //加载采样数据列表 + dtFileList(); +} + + +$('.data-collect').click(function(){ + layer.msg('开始采样',{icon:0,time:2000}); + var pid = $('#searchValue').val(); + + dtPost('simple_trace', '', {pid:pid}, function(rdata){ + // console.log(rdata); + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000,shade: [0.3, '#000']}); + }); +}); + + +function dtPost(method, version, args,callback){ + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + var req_data = {}; + req_data['name'] = 'dynamic-tracking'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function dtPostCb(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var pdata = {}; + pdata['name'] = 'dynamic-tracking'; + pdata['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + pdata['args'] = JSON.stringify(toArrayObject(args)); + } else { + pdata['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', pdata, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function dtFileList(){ + dtPost('file_list', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + var alist = rdata.data; + + if (alist.length == 0){ + var em ='
      • 无数据
      • '; + $('#file_list .list').html(em); + return; + } + + var tli = ''; + for (var i = 0; i < alist.length; i++) { + if (i==0){ + tli +='
      • \ + '+alist[i]['name']+'\ + \ + \ + \ + \ +
      • '; + } else{ + tli +='
      • \ + '+alist[i]['name']+'\ + \ + \ + \ + \ +
      • '; + } + } + + $('#file_list .list').html(tli); + + dtGetFile(alist[0]['name']); + $('#file_list li .file').click(function(){ + $('#file_list li').removeClass('active'); + $(this).parent().addClass('active'); + var i = $(this).parent().data('index'); + dtGetFile(alist[i]['name']); + }); + + + $('#file_list li .glyphicon-link').click(function(){ + var i = $(this).parent().parent().data('index'); + var abs_p = alist[i]['abs_path']; + + var durl = '/files/download?filename='+abs_p; + window.open(durl); + }); + + $('#file_list li .glyphicon-trash').click(function(){ + var i = $(this).parent().parent().data('index'); + var f = alist[i]['name']; + dtPost('remove_file_path', '',{file:f}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000}); + if (rdata.status){ + dtFileList(); + } + }); + }); + }); +} + +function dtGetFile(file){ + dtPost('get_file_path', '', {file:file}, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [2, '#000']}); + return; + } + var durl = '/files/download?filename='+rdata.data; + $('#flame_graph .tab-con .tab-block').attr('src',durl); + }); +} \ No newline at end of file diff --git a/plugins/dynamic-tracking/toolkit/readme.md b/plugins/dynamic-tracking/toolkit/readme.md new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/dynamic-tracking/versions/centos.sh b/plugins/dynamic-tracking/versions/centos.sh new file mode 100644 index 000000000..e74a8469f --- /dev/null +++ b/plugins/dynamic-tracking/versions/centos.sh @@ -0,0 +1,10 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +SYS_ARCH=`arch` \ No newline at end of file diff --git a/plugins/dynamic-tracking/versions/debian.sh b/plugins/dynamic-tracking/versions/debian.sh new file mode 100644 index 000000000..c87f300fb --- /dev/null +++ b/plugins/dynamic-tracking/versions/debian.sh @@ -0,0 +1,25 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +SYS_ARCH=`arch` + +# apt-get install linux-perf +# apt-get update -y +# strace -p $(ps -ef|grep "pool www" | awk '{print $2}' | grep -v grep | tr '\n' ',' ) + +# perf record -F 99 -p 2401699 -g -- sleep 30 +# perf script > out.perf +# /www/server/dynamic-tracking/FlameGraph/stackcollapse-perf.pl out.perf > out.folded +# /www/server/dynamic-tracking/FlameGraph/flamegraph.pl out.folded > php-zend-flame-graph.svg + +# WWW_PID=$(ps -ef|grep "pool www" | awk '{print $2}' | grep -v grep | tr '\n' ',') +# WWW_PID=${WWW_PID//,/} +# WWW_PID=${var%,} +# perf record -F 99 -p "1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1127,1128,1129,1130,1131,1473,1768,2848,2962,21582,35891" \ +# -g -- sleep 30 \ No newline at end of file diff --git a/plugins/dynamic-tracking/versions/macos.sh b/plugins/dynamic-tracking/versions/macos.sh new file mode 100644 index 000000000..f0522e5c3 --- /dev/null +++ b/plugins/dynamic-tracking/versions/macos.sh @@ -0,0 +1,20 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +SYS_ARCH=`arch` + +# 关于macosx使用dtrace + +# https://groups.google.com/g/openresty/c/MswlH_8DDHA +# http://dtrace.org/blogs/brendan/2012/11/14/dtracing-in-anger/ + + +# dtrace -x ustackframes=100 -n 'pid$target::mach_msg_trap:entry { @[ustack()] = count(); } tick-30s { exit(0); }' -p 18572 -o out.SystemUIServer_stacks +# /Users/midoks/Desktop/mwdev/server/dynamic-tracking/FlameGraph/stackcollapse.pl out.SystemUIServer_stacks > kernel.cbt +# /Users/midoks/Desktop/mwdev/server/dynamic-tracking/FlameGraph/flamegraph.pl kernel.cbt > kernel.svg diff --git a/plugins/dynamic-tracking/versions/ubuntu.sh b/plugins/dynamic-tracking/versions/ubuntu.sh new file mode 100644 index 000000000..b12e95d0f --- /dev/null +++ b/plugins/dynamic-tracking/versions/ubuntu.sh @@ -0,0 +1,21 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +SYS_ARCH=`arch` + +# export GOPROXY=https://goproxy.io +# export GO111MODULE=on + + +apt -y install build-essential git make libelf-dev strace tar bpfcc-tools libbpf-dev + +# clang -v +apt -y install clang llvm + +apt -y install golang diff --git a/plugins/dztasks/LICENSE b/plugins/dztasks/LICENSE new file mode 100644 index 000000000..72eb80c30 --- /dev/null +++ b/plugins/dztasks/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mw-plugin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/dztasks/README.md b/plugins/dztasks/README.md new file mode 100644 index 000000000..a677538b8 --- /dev/null +++ b/plugins/dztasks/README.md @@ -0,0 +1,8 @@ +# dztasks +任务调度 + + +### 安装 +``` +rm -rf /www/server/mdserver-web/plugins/dztasks && cd /www/server/mdserver-web/plugins && rm -rf dztasks && git clone https://github.com/mw-plugin/dztasks && cd dztasks && rm -rf .git && cd /www/server/mdserver-web/plugins/dztasks && bash install.sh install 1.2 +``` diff --git a/plugins/dztasks/config/dztasks.conf b/plugins/dztasks/config/dztasks.conf new file mode 100644 index 000000000..6e1763b51 --- /dev/null +++ b/plugins/dztasks/config/dztasks.conf @@ -0,0 +1,19 @@ +app_name = dztasks +run_mode = prod + +[web] +http_port = 11011 + +[session] +provider = file + +[admin] +user = {$ADMIN_NAME} +pass = {$ADMIN_PASS} + +[plugins] +path = {$SERVER_APP}/plugins + +[security] +install_lock = true +secret_key = VQOXEKGNIAIDXQI diff --git a/plugins/dztasks/ico.png b/plugins/dztasks/ico.png new file mode 100644 index 000000000..5aafecd96 Binary files /dev/null and b/plugins/dztasks/ico.png differ diff --git a/plugins/dztasks/index.html b/plugins/dztasks/index.html new file mode 100755 index 000000000..2c720f057 --- /dev/null +++ b/plugins/dztasks/index.html @@ -0,0 +1,32 @@ + + +
        +
        +
        +
        +

        服务

        +

        自启动

        +

        常用功能

        +

        配置修改

        +

        运行日志

        +

        相关说明

        + +
        +
        +
        +
        +
        +
        + \ No newline at end of file diff --git a/plugins/dztasks/index.py b/plugins/dztasks/index.py new file mode 100755 index 000000000..4bd9f79c9 --- /dev/null +++ b/plugins/dztasks/index.py @@ -0,0 +1,343 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'dztasks' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/custom/conf/app.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + "/config/dztasks.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def configTpl(): + # path = getPluginDir() + '/tpl' + # pathFile = os.listdir(path) + tmp = [] + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + cmd = "ps aux|grep dztasks|grep -v grep|grep -v python|grep -v mdserver-web|awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/dztasks') + content = content.replace('{$ADMIN_NAME}', mw.getRandomString(6)) + content = content.replace('{$ADMIN_PASS}', mw.getRandomString(10)) + return content + + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # log + dataLog = getServerDir() + '/data' + if not os.path.exists(dataLog): + mw.execShell('chmod +x ' + file_bin) + + app_dir = getServerDir() + '/custom/conf' + if not os.path.exists(app_dir): + mw.execShell('mkdir -p ' + app_dir) + + # config replace + dst_conf = getServerDir() + '/custom/conf/app.conf' + dst_conf_init = getServerDir() + '/init.pl' + if not os.path.exists(dst_conf_init): + content = mw.readFile(getConfTpl()) + # print(content) + content = contentReplace(content) + mw.writeFile(dst_conf, content) + mw.writeFile(dst_conf_init, 'ok') + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + content = mw.readFile(systemServiceTpl) + content = contentReplace(content) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def dzOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return dzOp('start') + + +def stop(): + return dzOp('stop') + + +def restart(): + status = dzOp('restart') + + log_file = runLog() + mw.execShell("echo '' > " + log_file) + return status + +def reload(): + return dzOp('reload') + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + return getServerDir() + '/logs.pl' + + +def getDzPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getDzUsername(): + file = getConf() + content = mw.readFile(file) + rep = r'user\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getDzPassword(): + file = getConf() + content = mw.readFile(file) + rep = r'pass\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getHomePage(): + http_port = getDzPort() + ip = mw.getLocalIp() + if mw.isAppleSystem(): + ip = '127.0.0.1' + url = 'http://'+ip+":"+str(http_port) + return url + +def homePage(): + return mw.returnJson(True, 'ok!', getHomePage()) + + +def runInfo(): + data = {} + data['url'] = getHomePage() + data['user'] = getDzUsername() + data['pass'] = getDzPassword() + + return mw.returnJson(True, 'ok!', data) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'home_page': + print(homePage()) + else: + print('error') diff --git a/plugins/dztasks/info.json b/plugins/dztasks/info.json new file mode 100755 index 000000000..aa57fa9c1 --- /dev/null +++ b/plugins/dztasks/info.json @@ -0,0 +1,17 @@ +{ + "sort": 7, + "ps": "任务调度管理", + "name": "dztasks", + "title": "dztasks", + "shell": "install.sh", + "versions":["1.2"], + "tip": "soft", + "checks": "server/dztasks", + "path": "server/dztasks", + "display": 1, + "author": "dztasks", + "date": "2024-10-06", + "home": "", + "type": 0, + "pid": "5" +} diff --git a/plugins/dztasks/init.d/dztasks.service.tpl b/plugins/dztasks/init.d/dztasks.service.tpl new file mode 100644 index 000000000..fd57a35ec --- /dev/null +++ b/plugins/dztasks/init.d/dztasks.service.tpl @@ -0,0 +1,21 @@ +[Unit] +Description=dztasks server +After=network.service +After=syslog.target + +[Service] +User=root +Group=root +Type=simple +WorkingDirectory={$SERVER_PATH}/dztasks +ExecStart={$SERVER_PATH}/dztasks/dztasks web +ExecReload=/bin/kill -USR2 $MAINPID +PermissionsStartOnly=true +LimitNOFILE=5000 +Restart=on-failure +RestartSec=10 +RestartPreventExitStatus=1 +PrivateTmp=false + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/dztasks/init.d/dztasks.tpl b/plugins/dztasks/init.d/dztasks.tpl new file mode 100644 index 000000000..dff29eb38 --- /dev/null +++ b/plugins/dztasks/init.d/dztasks.tpl @@ -0,0 +1,64 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: dztasks Service + +### BEGIN INIT INFO +# Provides: dztasks +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts dztasks +# Description: starts the MDW-Web +### END INIT INFO + +# Simple dztasks init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +PLAIN='\033[0m' +BOLD='\033[1m' +SUCCESS='[\033[32mOK\033[0m]' +COMPLETE='[\033[32mDONE\033[0m]' +WARN='[\033[33mWARN\033[0m]' +ERROR='[\033[31mERROR\033[0m]' +WORKING='[\033[34m*\033[0m]' + +app_start(){ + echo "Starting dztasks server..." + cd {$SERVER_PATH}/dztasks + ./dztasks web >> {$SERVER_PATH}/dztasks/logs.pl 2>&1 & +} + +app_stop(){ + echo "dztasks stopp start" + pids=`ps -ef| grep dztasks | grep -v grep | grep -v python | grep -v sh | awk '{print $2}'` + arr=($pids) + for p in ${arr[@]} + do + kill -9 $p > /dev/null 2>&1 + done + echo "dztasks stopp end" +} + + +case "$1" in + start) + app_start + ;; + stop) + app_stop + ;; + restart|reload) + app_stop + sleep 0.3 + app_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/dztasks/install.sh b/plugins/dztasks/install.sh new file mode 100755 index 000000000..89e2c2e01 --- /dev/null +++ b/plugins/dztasks/install.sh @@ -0,0 +1,85 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/dztasks && bash install.sh install 1.0 +# cd /wwww/server/mdserver-web/plugins/dztasks && bash install.sh install 1.0 +VERSION=$2 +sysArch=`arch` +sysName=`uname` + +DZ_ARCH_NAME=amd64 +if [ "$sysArch" == "arm64" ];then + DZ_ARCH_NAME=arm64 +elif [ "$sysArch" == "x86_64" ]; then + DZ_ARCH_NAME=amd64 +elif [ "$sysArch" == "aarch64" ]; then + DZ_ARCH_NAME=aarch64 +fi + +DZ_NAME=linux +if [ "$sysName" == "Darwin" ];then + DZ_NAME=darwin +fi +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/source/dztasks + mkdir -p $serverPath/dztasks + + FILE_TGZ=dztasks_${VERSION}_${DZ_NAME}_${DZ_ARCH_NAME}.tar.gz + DZ_DIR=$serverPath/source/dztasks + + echo $FILE_TGZ + # https://github.com/midoks/dztasks/releases/download/1.0/dztasks_v1.0_darwin_amd64.tar.gz + if [ ! -f $DZ_DIR/${FILE_TGZ} ];then + wget --no-check-certificate -O $DZ_DIR/${FILE_TGZ} https://github.com/midoks/dztasks/releases/download/${VERSION}/${FILE_TGZ} + fi + + cd $DZ_DIR && tar -zxvf ${FILE_TGZ} -C $serverPath/dztasks + echo "${VERSION}" > $serverPath/dztasks/version.pl + cd ${rootPath} && python3 ${rootPath}/plugins/dztasks/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/dztasks/index.py initd_install + + echo '安装dztasks成功!' +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/dztasks.service ];then + systemctl stop dztasks + systemctl disable dztasks + rm -rf /usr/lib/systemd/system/dztasks.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/dztasks.service ];then + systemctl stop dztasks + systemctl disable dztasks + rm -rf /lib/systemd/system/dztasks.service + systemctl daemon-reload + fi + + if [ -f $serverPath/dztasks/initd/dztasks ];then + $serverPath/dztasks/initd/dztasks stop + fi + + if [ -d $serverPath/dztasks ];then + rm -rf $serverPath/dztasks + fi + + echo "卸载dztasks成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/dztasks/js/dztasks.js b/plugins/dztasks/js/dztasks.js new file mode 100755 index 000000000..4178e1c9c --- /dev/null +++ b/plugins/dztasks/js/dztasks.js @@ -0,0 +1,114 @@ +function zdPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'dztasks'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function zdPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'dztasks'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function commonHomePage(){ + zdPost('home_page', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + window.open(rdata.data); + }); +} + +function postCrossPort(data, url) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.onreadystatechange = function() { + if(xhr.readyState == 4 && xhr.status == 200) { + console.log(xhr.responseText); + } + }; + xhr.send(data); +} + +function dzLogin(){ + + zdPost('run_info', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + var info = rdata.data; + var post_url = info['url']+'/do_login'; + + $("#toSite").attr('action',post_url); + $("#username").val(info['user']); + $("#password").val(info['pass']); + layer.msg('正在打开dztasks',{icon:16,shade: [0.3, '#000'],time:2000}); + + setTimeout(function(){ + $("#toSite").submit(); + },2000); + }); +} + +function dzCommonFunc(){ + var con = ''; + con += '

        \ + \ + \ +

        '; + + con += ''; + $(".soft-man-con").html(con); +} + +function zdReadme(){ + var readme = '
          '; + readme += '
        • 自己修改配置
        • '; + readme += '
        '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/fail2ban/conf/fail2ban.conf b/plugins/fail2ban/conf/fail2ban.conf new file mode 100644 index 000000000..4d8646795 --- /dev/null +++ b/plugins/fail2ban/conf/fail2ban.conf @@ -0,0 +1,594 @@ +[INCLUDES] +before = paths-debian.conf + +[DEFAULT] +backend = systemd +ignorecommand = +bantime = 10m +findtime = 10m +maxretry = 5 + +maxmatches = %(maxretry)s +usedns = warn +logencoding = auto +enabled = false +mode = normal +filter = %(__name__)s[mode=%(mode)s] + +destemail = root@localhost + +sender = root@ + +mta = sendmail + +protocol = tcp + +chain = + +port = 0:65535 + +fail2ban_agent = Fail2Ban/%(fail2ban_version)s + + +banaction = iptables-multiport +banaction_allports = iptables-allports + +action_ = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] + +action_mw = %(action_)s + %(mta)s-whois[sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"] + +action_mwl = %(action_)s + %(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"] + +action_xarf = %(action_)s + xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath="%(logpath)s", port="%(port)s"] + +action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"] + %(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"] + +action_blocklist_de = blocklist_de[email="%(sender)s", service="%(__name__)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"] + +action_badips = badips.py[category="%(__name__)s", banaction="%(banaction)s", agent="%(fail2ban_agent)s"] +action_badips_report = badips[category="%(__name__)s", agent="%(fail2ban_agent)s"] + +action_abuseipdb = abuseipdb + +action = %(action_)s + +[sshd] +port = ssh +logpath = %(sshd_log)s +backend = %(sshd_backend)s + +[dropbear] +port = ssh +logpath = %(dropbear_log)s +backend = %(dropbear_backend)s + + +[selinux-ssh] +port = ssh +logpath = %(auditd_log)s + + +[apache-auth] +port = http,https +logpath = %(apache_error_log)s + +[apache-badbots] +port = http,https +logpath = %(apache_access_log)s +bantime = 48h +maxretry = 1 + +[apache-noscript] +port = http,https +logpath = %(apache_error_log)s + + +[apache-overflows] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-nohome] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-botsearch] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-fakegooglebot] + +port = http,https +logpath = %(apache_access_log)s +maxretry = 1 +ignorecommand = %(ignorecommands_dir)s/apache-fakegooglebot + + +[apache-modsecurity] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-shellshock] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 1 + + +[openhab-auth] + +filter = openhab +banaction = %(banaction_allports)s +logpath = /opt/openhab/logs/request.log + + +[nginx-http-auth] + +port = http,https +logpath = %(nginx_error_log)s + +[nginx-limit-req] +port = http,https +logpath = %(nginx_error_log)s + +[nginx-botsearch] + +port = http,https +logpath = %(nginx_error_log)s +maxretry = 2 + + + +[php-url-fopen] + +port = http,https +logpath = %(nginx_access_log)s + %(apache_access_log)s + + +[suhosin] + +port = http,https +logpath = %(suhosin_log)s + + +[lighttpd-auth] +port = http,https +logpath = %(lighttpd_error_log)s + + + +[roundcube-auth] + +port = http,https +logpath = %(roundcube_errors_log)s + + +[openwebmail] + +port = http,https +logpath = /var/log/openwebmail.log + + +[horde] + +port = http,https +logpath = /var/log/horde/horde.log + + +[groupoffice] + +port = http,https +logpath = /home/groupoffice/log/info.log + + +[sogo-auth] +port = http,https +logpath = /var/log/sogo/sogo.log + + +[tine20] + +logpath = /var/log/tine20/tine20.log +port = http,https + + + +[drupal-auth] + +port = http,https +logpath = %(syslog_daemon)s +backend = %(syslog_backend)s + +[guacamole] + +port = http,https +logpath = /var/log/tomcat*/catalina.out + +[monit] +port = 2812 +logpath = /var/log/monit + /var/log/monit.log + + +[webmin-auth] + +port = 10000 +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +[froxlor-auth] + +port = http,https +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + + +[squid] + +port = 80,443,3128,8080 +logpath = /var/log/squid/access.log + + +[3proxy] + +port = 3128 +logpath = /var/log/3proxy.log + + + + +[proftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(proftpd_log)s +backend = %(proftpd_backend)s + + +[pure-ftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(pureftpd_log)s +backend = %(pureftpd_backend)s + + +[gssftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(syslog_daemon)s +backend = %(syslog_backend)s + + +[wuftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(wuftpd_log)s +backend = %(wuftpd_backend)s + + +[vsftpd] +port = ftp,ftp-data,ftps,ftps-data +logpath = %(vsftpd_log)s + + + +[assp] + +port = smtp,465,submission +logpath = /root/path/to/assp/logs/maillog.txt + + +[courier-smtp] + +port = smtp,465,submission +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[postfix] +mode = more +port = smtp,465,submission +logpath = %(postfix_log)s +backend = %(postfix_backend)s + + +[postfix-rbl] + +filter = postfix[mode=rbl] +port = smtp,465,submission +logpath = %(postfix_log)s +backend = %(postfix_backend)s +maxretry = 1 + + +[sendmail-auth] + +port = submission,465,smtp +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[sendmail-reject] +port = smtp,465,submission +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[qmail-rbl] + +filter = qmail +port = smtp,465,submission +logpath = /service/qmail/log/main/current + + +[dovecot] + +port = pop3,pop3s,imap,imaps,submission,465,sieve +logpath = %(dovecot_log)s +backend = %(dovecot_backend)s + + +[sieve] + +port = smtp,465,submission +logpath = %(dovecot_log)s +backend = %(dovecot_backend)s + + +[solid-pop3d] + +port = pop3,pop3s +logpath = %(solidpop3d_log)s + + +[exim] +port = smtp,465,submission +logpath = %(exim_main_log)s + + +[exim-spam] + +port = smtp,465,submission +logpath = %(exim_main_log)s + + +[kerio] + +port = imap,smtp,imaps,465 +logpath = /opt/kerio/mailserver/store/logs/security.log + + + +[courier-auth] +port = smtp,465,submission,imap,imaps,pop3,pop3s +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[postfix-sasl] +filter = postfix[mode=auth] +port = smtp,465,submission,imap,imaps,pop3,pop3s +logpath = %(postfix_log)s +backend = %(postfix_backend)s + + +[perdition] +port = imap,imaps,pop3,pop3s +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[squirrelmail] +port = smtp,465,submission,imap,imap2,imaps,pop3,pop3s,http,https,socks +logpath = /var/lib/squirrelmail/prefs/squirrelmail_access_log + + +[cyrus-imap] +port = imap,imaps +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[uwimap-auth] +port = imap,imaps +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + + + + + +[named-refused] +port = domain,953 +logpath = /var/log/named/security.log + + +[nsd] + +port = 53 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/nsd.log + + + +[asterisk] +port = 5060,5061 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/asterisk/messages +maxretry = 10 + + +[freeswitch] +port = 5060,5061 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/freeswitch.log +maxretry = 10 + + +[znc-adminlog] +port = 6667 +logpath = /var/lib/znc/moddata/adminlog/znc.log + + +[mysqld-auth] + +port = 3306 +logpath = %(mysql_log)s +backend = %(mysql_backend)s + + +[mongodb-auth] +port = 27017 +logpath = /var/log/mongodb/mongodb.log + + +[recidive] + +logpath = /var/log/fail2ban.log +banaction = %(banaction_allports)s +bantime = 1w +findtime = 1d + +[pam-generic] +banaction = %(banaction_allports)s +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +[xinetd-fail] + +banaction = iptables-multiport-log +logpath = %(syslog_daemon)s +backend = %(syslog_backend)s +maxretry = 2 + + +[stunnel] + +logpath = /var/log/stunnel4/stunnel.log + + +[ejabberd-auth] + +port = 5222 +logpath = /var/log/ejabberd/ejabberd.log + + +[counter-strike] + +logpath = /opt/cstrike/logs/L[0-9]*.log +tcpport = 27030,27031,27032,27033,27034,27035,27036,27037,27038,27039 +udpport = 1200,27000,27001,27002,27003,27004,27005,27006,27007,27008,27009,27010,27011,27012,27013,27014,27015 +action_ = %(default/action_)s[name=%(__name__)s-tcp, port="%(tcpport)s", protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, port="%(udpport)s", protocol="udp"] + +[softethervpn] +port = 500,4500 +protocol = udp +logpath = /usr/local/vpnserver/security_log/*/sec.log + +[gitlab] +port = http,https +logpath = /var/log/gitlab/gitlab-rails/application.log + +[grafana] +port = http,https +logpath = /var/log/grafana/grafana.log + +[bitwarden] +port = http,https +logpath = /home/*/bwdata/logs/identity/Identity/log.txt + +[centreon] +port = http,https +logpath = /var/log/centreon/login.log + +[nagios] + +logpath = %(syslog_daemon)s ; nrpe.cfg may define a different log_facility +backend = %(syslog_backend)s +maxretry = 1 + + +[oracleims] +logpath = /opt/sun/comms/messaging64/log/mail.log_current +banaction = %(banaction_allports)s + +[directadmin] +logpath = /var/log/directadmin/login.log +port = 2222 + +[portsentry] +logpath = /var/lib/portsentry/portsentry.history +maxretry = 1 + +[pass2allow-ftp] +port = ftp,ftp-data,ftps,ftps-data +knocking_url = /knocking/ +filter = apache-pass[knocking_url="%(knocking_url)s"] +logpath = %(apache_access_log)s +blocktype = RETURN +returntype = DROP +action = %(action_)s[blocktype=%(blocktype)s, returntype=%(returntype)s, + actionstart_on_demand=false, actionrepair_on_unban=true] +bantime = 1h +maxretry = 1 +findtime = 1 + + +[murmur] +port = 64738 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/mumble-server/mumble-server.log + + +[screensharingd] +logpath = /var/log/system.log +logencoding = utf-8 + +[haproxy-http-auth] +logpath = /var/log/haproxy.log + +[slapd] +port = ldap,ldaps +logpath = /var/log/slapd.log + +[domino-smtp] +port = smtp,ssmtp +logpath = /home/domino01/data/IBM_TECHNICAL_SUPPORT/console.log + +[phpmyadmin-syslog] +port = http,https +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +[zoneminder] +port = http,https +logpath = %(apache_error_log)s + +[traefik-auth] +port = http,https +logpath = /var/log/traefik/access.log diff --git a/plugins/fail2ban/ico.png b/plugins/fail2ban/ico.png new file mode 100644 index 000000000..64912b452 Binary files /dev/null and b/plugins/fail2ban/ico.png differ diff --git a/plugins/fail2ban/index.html b/plugins/fail2ban/index.html new file mode 100755 index 000000000..176ab2181 --- /dev/null +++ b/plugins/fail2ban/index.html @@ -0,0 +1,32 @@ + + +
        +
        +
        +
        +

        服务

        +

        自启动

        +

        配置修改

        +

        IP黑名单

        + +

        运行日志

        +
        +
        +
        +
        +
        +
        + \ No newline at end of file diff --git a/plugins/fail2ban/index.py b/plugins/fail2ban/index.py new file mode 100755 index 000000000..6d9994da4 --- /dev/null +++ b/plugins/fail2ban/index.py @@ -0,0 +1,387 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'fail2ban' + +def f2bDir(): + return '/run/'+getPluginName() + +def f2bEtcDir(): + return '/etc/'+getPluginName() + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = f2bEtcDir() + "/fail2ban.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + "/tpl/fail2ban.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def configTpl(): + path = f2bEtcDir() + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + if one.endswith("conf"): + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + +def runLog(): + return '/var/log/fail2ban.log' + +def getPidFile(): + f2dir = f2bDir() + return f2dir+'/fail2ban.pid' + +def status(): + pid_file = getPidFile() + if not os.path.exists(pid_file): + return 'stop' + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + return content + + +def initFail2BanD(): + dst_conf = f2bEtcDir() + '/fail2ban.d/default.conf' + dst_conf_tpl = getPluginDir() + '/tpl/fail2ban.d/default.conf' + if not os.path.exists(dst_conf): + content = mw.readFile(dst_conf_tpl) + content = contentReplace(content) + mw.writeFile(dst_conf, content) + +def initJailD(): + dst_conf = f2bEtcDir() + '/jail.d/default.conf' + dst_conf_tpl = getPluginDir() + '/tpl/jail.d/default.conf' + if not os.path.exists(dst_conf): + content = mw.readFile(dst_conf_tpl) + content = contentReplace(content) + mw.writeFile(dst_conf, content) + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # config replace + # dst_conf = getConf() + # dst_conf_init = getServerDir() + '/init.pl' + # if not os.path.exists(dst_conf_init): + # content = mw.readFile(getConfTpl()) + # content = contentReplace(content) + # mw.writeFile(dst_conf, content) + # mw.writeFile(dst_conf_init, 'ok') + + initFail2BanD() + initJailD() + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def f2bOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return f2bOp('start') + + +def stop(): + return f2bOp('stop') + + +def restart(): + status = f2bOp('restart') + + log_file = runLog() + mw.execShell("echo '' > " + log_file) + return status + + +def reload(): + return f2bOp('reload') + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +# 读取配置 +def _read_conf(path, l=None): + conf = mw.readFile(path) + if not conf: + if not l: + conf = {} + else: + conf = [] + mw.writeFile(path, json.dumps(conf)) + return conf + return json.loads(conf) + +def getBlackFile(): + return getServerDir() + "/black_list.json" + + +def getConfigFile(): + return getServerDir() + "/config.json" + + +def getBlackListArr(): + _black_list = getBlackFile() + conf = _read_conf(_black_list, l=1) + if not conf: + conf = [] + return conf + + +def getBlackList(): + conf = getBlackListArr() + content = "\n".join(conf) + return mw.returnJson(True, 'ok', content) + +def setBlackIp(): + ip_list = getBlackListArr() + + args = getArgs() + data = checkArgs(args, ['black_ip']) + if not data[0]: + return data[1] + + new_ip_list = args['black_ip'] + add_ip_list = [new_ip for new_ip in new_ip_list if new_ip not in ip_list] + del_ip_list = [del_ip for del_ip in ip_list if del_ip not in new_ip_list] + rep_ip = "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}($|[\\/\\d]+$)" + rep_ipv6 = "^\\s*((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}(:|((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4}){0,4}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})))(%.+)?\\s*$" + + data = _read_conf(getConfigFile()) + + if new_ip_list == '': + for d in data: + for ip in ip_list: + mw.execShell('fail2ban-client -vvv set {jail} unbanip {ip}'.format(jail=d, ip=ip)) + + mw.writeFile(getBlackFile(), json.dumps([])) + return nw.returnJson(True, "禁止IP成功") + + # 检查IP格式 + for ip in add_ip_list: + if not re.search(rep_ip, ip) and not re.search(rep_ipv6, ip): + return mw.returnJson(False, "IP格式错误 {}".format(ip)) + + # 添加新域名到黑名单 + for d in data: + for ip in add_ip_list: + mw.execShell('fail2ban-client -vvv set {jail} banip {ip}'.format(jail=d, ip=ip)) + + # 检查是否有清理掉的IP + for d in data: + for ip in del_ip_list: + mw.execShell('fail2ban-client -vvv set {jail} unbanip {ip}'.format(jail=d, ip=ip)) + + for ip in add_ip_list: + ip_list.append(ip) + + mw.writeFile(getBlackFile(), json.dumps(ip_list)) + return mw.returnJson(True, "添加黑名单成功") + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'get_black_list': + print(getBlackList()) + elif func == 'set_black_ip': + print(setBlackIp()) + else: + print('error') diff --git a/plugins/fail2ban/info.json b/plugins/fail2ban/info.json new file mode 100755 index 000000000..c7260dc7f --- /dev/null +++ b/plugins/fail2ban/info.json @@ -0,0 +1,18 @@ +{ + "sort": 9, + "ps": "防止恶意IP地址暴力破解服务、站点,禁止导致多个身份验证错误的主机", + "name": "fail2ban", + "title": "fail2ban", + "shell": "install.sh", + "versions":["1.1.0"], + "updates":["1.1.0"], + "tip": "soft", + "checks": "server/fail2ban", + "path": "server/fail2ban", + "display": 1, + "author": "fail2ban", + "date": "2024-07-28", + "home": "https://www.fail2ban.org", + "type": 0, + "pid": "4" +} diff --git a/plugins/fail2ban/init.d/fail2ban.service.tpl b/plugins/fail2ban/init.d/fail2ban.service.tpl new file mode 100644 index 000000000..5d7f37aca --- /dev/null +++ b/plugins/fail2ban/init.d/fail2ban.service.tpl @@ -0,0 +1,22 @@ +[Unit] +Description=Fail2Ban Service +Documentation=man:fail2ban(1) +After=network.target iptables.service firewalld.service ip6tables.service ipset.service nftables.service +PartOf=firewalld.service + +[Service] +Type=simple +Environment="PYTHONNOUSERSITE=1" +ExecStartPre=/bin/mkdir -p /run/fail2ban +ExecStart=/usr/bin/fail2ban-server -xf start +# if should be logged in systemd journal, use following line or set logtarget to sysout in fail2ban.local +# ExecStart=/usr/bin/fail2ban-server -xf --logtarget=sysout start +ExecStop=/usr/bin/fail2ban-client stop +ExecReload=/usr/bin/fail2ban-client reload +PIDFile=/run/fail2ban/fail2ban.pid +Restart=on-failure +RestartPreventExitStatus=0 255 +Environment="PYTHONNOUSERSITE=yes" + +[Install] +WantedBy=multi-user.target diff --git a/plugins/fail2ban/install.sh b/plugins/fail2ban/install.sh new file mode 100755 index 000000000..a07496351 --- /dev/null +++ b/plugins/fail2ban/install.sh @@ -0,0 +1,74 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +# cd /www/server/mdserver-web/plugins/fail2ban && bash install.sh install 1.1.0 +# cd /www/server/mdserver-web && python3 plugins/fail2ban/index.py config_tpl + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + + which yum && yum install -y fail2ban + which apt && apt install -y fail2ban + + mkdir -p $serverPath/fail2ban + echo "${VERSION}" > $serverPath/fail2ban/version.pl + echo '安装fail2ban完成' + + cd ${rootPath} && python3 ${rootPath}/plugins/fail2ban/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/fail2ban/index.py initd_install +} + +Uninstall_App() +{ + which apt && apt remove -y fail2ban + which apt && apt purge -y fail2ban + + which yum && yum remove -y fail2ban + which yum && yum purge -y fail2ban + + if [ -f /usr/lib/systemd/system/fail2ban.service ];then + systemctl stop fail2ban + systemctl disable fail2ban + rm -rf /usr/lib/systemd/system/fail2ban.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/fail2ban.service ];then + systemctl stop fail2ban + systemctl disable fail2ban + rm -rf /lib/systemd/system/fail2ban.service + systemctl daemon-reload + fi + + if [ -f $serverPath/fail2ban/initd/fail2ban ];then + $serverPath/fail2ban/initd/fail2ban stop + fi + + if [ -d $serverPath/fail2ban ];then + rm -rf $serverPath/fail2ban + fi + + if [ -d /etc/fail2ban ];then + rm -rf /etc/fail2ban + fi + + echo "卸载fail2ban成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/fail2ban/js/fail2ban.js b/plugins/fail2ban/js/fail2ban.js new file mode 100755 index 000000000..2db4bd4d4 --- /dev/null +++ b/plugins/fail2ban/js/fail2ban.js @@ -0,0 +1,110 @@ +function getVersion(){ + return $('.plugin_version').attr('version'); +} + +function f2bPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'fail2ban'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function f2bPostCallbak(method, version, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'fail2ban'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function f2bBanIpSave(black_ip){ + var ver = getVersion(); + f2bPost('set_black_ip', ver, {'black_ip':black_ip}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function f2bBanIp(){ + var con = '

        提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!

        \ + \ + \ +
          \ +
        • 如有多个请以换行隔开例:
          \ + 192.168.1.1
          \ + 192.168.1.0/24\ +
        • \ +
        '; + + $(".soft-man-con").html(con); + $("#textBody").empty(); + var editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + $("#textBody").text(editor.getValue()); + f2bPost('set_black_list', '', {'black_ip':editor.getValue()}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + + f2bBanIpSave(editor.getValue()); + } + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + + f2bPost('get_black_list', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + $("#textBody").text(rdata.data); + }); + + $("#onlineEditFileBtn").click(function(){ + f2bBanIpSave(editor.getValue()); + }); +} + diff --git a/plugins/fail2ban/tpl/fail2ban.conf b/plugins/fail2ban/tpl/fail2ban.conf new file mode 100644 index 000000000..4d8646795 --- /dev/null +++ b/plugins/fail2ban/tpl/fail2ban.conf @@ -0,0 +1,594 @@ +[INCLUDES] +before = paths-debian.conf + +[DEFAULT] +backend = systemd +ignorecommand = +bantime = 10m +findtime = 10m +maxretry = 5 + +maxmatches = %(maxretry)s +usedns = warn +logencoding = auto +enabled = false +mode = normal +filter = %(__name__)s[mode=%(mode)s] + +destemail = root@localhost + +sender = root@ + +mta = sendmail + +protocol = tcp + +chain = + +port = 0:65535 + +fail2ban_agent = Fail2Ban/%(fail2ban_version)s + + +banaction = iptables-multiport +banaction_allports = iptables-allports + +action_ = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] + +action_mw = %(action_)s + %(mta)s-whois[sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"] + +action_mwl = %(action_)s + %(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"] + +action_xarf = %(action_)s + xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath="%(logpath)s", port="%(port)s"] + +action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"] + %(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"] + +action_blocklist_de = blocklist_de[email="%(sender)s", service="%(__name__)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"] + +action_badips = badips.py[category="%(__name__)s", banaction="%(banaction)s", agent="%(fail2ban_agent)s"] +action_badips_report = badips[category="%(__name__)s", agent="%(fail2ban_agent)s"] + +action_abuseipdb = abuseipdb + +action = %(action_)s + +[sshd] +port = ssh +logpath = %(sshd_log)s +backend = %(sshd_backend)s + +[dropbear] +port = ssh +logpath = %(dropbear_log)s +backend = %(dropbear_backend)s + + +[selinux-ssh] +port = ssh +logpath = %(auditd_log)s + + +[apache-auth] +port = http,https +logpath = %(apache_error_log)s + +[apache-badbots] +port = http,https +logpath = %(apache_access_log)s +bantime = 48h +maxretry = 1 + +[apache-noscript] +port = http,https +logpath = %(apache_error_log)s + + +[apache-overflows] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-nohome] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-botsearch] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-fakegooglebot] + +port = http,https +logpath = %(apache_access_log)s +maxretry = 1 +ignorecommand = %(ignorecommands_dir)s/apache-fakegooglebot + + +[apache-modsecurity] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-shellshock] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 1 + + +[openhab-auth] + +filter = openhab +banaction = %(banaction_allports)s +logpath = /opt/openhab/logs/request.log + + +[nginx-http-auth] + +port = http,https +logpath = %(nginx_error_log)s + +[nginx-limit-req] +port = http,https +logpath = %(nginx_error_log)s + +[nginx-botsearch] + +port = http,https +logpath = %(nginx_error_log)s +maxretry = 2 + + + +[php-url-fopen] + +port = http,https +logpath = %(nginx_access_log)s + %(apache_access_log)s + + +[suhosin] + +port = http,https +logpath = %(suhosin_log)s + + +[lighttpd-auth] +port = http,https +logpath = %(lighttpd_error_log)s + + + +[roundcube-auth] + +port = http,https +logpath = %(roundcube_errors_log)s + + +[openwebmail] + +port = http,https +logpath = /var/log/openwebmail.log + + +[horde] + +port = http,https +logpath = /var/log/horde/horde.log + + +[groupoffice] + +port = http,https +logpath = /home/groupoffice/log/info.log + + +[sogo-auth] +port = http,https +logpath = /var/log/sogo/sogo.log + + +[tine20] + +logpath = /var/log/tine20/tine20.log +port = http,https + + + +[drupal-auth] + +port = http,https +logpath = %(syslog_daemon)s +backend = %(syslog_backend)s + +[guacamole] + +port = http,https +logpath = /var/log/tomcat*/catalina.out + +[monit] +port = 2812 +logpath = /var/log/monit + /var/log/monit.log + + +[webmin-auth] + +port = 10000 +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +[froxlor-auth] + +port = http,https +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + + +[squid] + +port = 80,443,3128,8080 +logpath = /var/log/squid/access.log + + +[3proxy] + +port = 3128 +logpath = /var/log/3proxy.log + + + + +[proftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(proftpd_log)s +backend = %(proftpd_backend)s + + +[pure-ftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(pureftpd_log)s +backend = %(pureftpd_backend)s + + +[gssftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(syslog_daemon)s +backend = %(syslog_backend)s + + +[wuftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(wuftpd_log)s +backend = %(wuftpd_backend)s + + +[vsftpd] +port = ftp,ftp-data,ftps,ftps-data +logpath = %(vsftpd_log)s + + + +[assp] + +port = smtp,465,submission +logpath = /root/path/to/assp/logs/maillog.txt + + +[courier-smtp] + +port = smtp,465,submission +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[postfix] +mode = more +port = smtp,465,submission +logpath = %(postfix_log)s +backend = %(postfix_backend)s + + +[postfix-rbl] + +filter = postfix[mode=rbl] +port = smtp,465,submission +logpath = %(postfix_log)s +backend = %(postfix_backend)s +maxretry = 1 + + +[sendmail-auth] + +port = submission,465,smtp +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[sendmail-reject] +port = smtp,465,submission +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[qmail-rbl] + +filter = qmail +port = smtp,465,submission +logpath = /service/qmail/log/main/current + + +[dovecot] + +port = pop3,pop3s,imap,imaps,submission,465,sieve +logpath = %(dovecot_log)s +backend = %(dovecot_backend)s + + +[sieve] + +port = smtp,465,submission +logpath = %(dovecot_log)s +backend = %(dovecot_backend)s + + +[solid-pop3d] + +port = pop3,pop3s +logpath = %(solidpop3d_log)s + + +[exim] +port = smtp,465,submission +logpath = %(exim_main_log)s + + +[exim-spam] + +port = smtp,465,submission +logpath = %(exim_main_log)s + + +[kerio] + +port = imap,smtp,imaps,465 +logpath = /opt/kerio/mailserver/store/logs/security.log + + + +[courier-auth] +port = smtp,465,submission,imap,imaps,pop3,pop3s +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[postfix-sasl] +filter = postfix[mode=auth] +port = smtp,465,submission,imap,imaps,pop3,pop3s +logpath = %(postfix_log)s +backend = %(postfix_backend)s + + +[perdition] +port = imap,imaps,pop3,pop3s +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[squirrelmail] +port = smtp,465,submission,imap,imap2,imaps,pop3,pop3s,http,https,socks +logpath = /var/lib/squirrelmail/prefs/squirrelmail_access_log + + +[cyrus-imap] +port = imap,imaps +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[uwimap-auth] +port = imap,imaps +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + + + + + +[named-refused] +port = domain,953 +logpath = /var/log/named/security.log + + +[nsd] + +port = 53 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/nsd.log + + + +[asterisk] +port = 5060,5061 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/asterisk/messages +maxretry = 10 + + +[freeswitch] +port = 5060,5061 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/freeswitch.log +maxretry = 10 + + +[znc-adminlog] +port = 6667 +logpath = /var/lib/znc/moddata/adminlog/znc.log + + +[mysqld-auth] + +port = 3306 +logpath = %(mysql_log)s +backend = %(mysql_backend)s + + +[mongodb-auth] +port = 27017 +logpath = /var/log/mongodb/mongodb.log + + +[recidive] + +logpath = /var/log/fail2ban.log +banaction = %(banaction_allports)s +bantime = 1w +findtime = 1d + +[pam-generic] +banaction = %(banaction_allports)s +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +[xinetd-fail] + +banaction = iptables-multiport-log +logpath = %(syslog_daemon)s +backend = %(syslog_backend)s +maxretry = 2 + + +[stunnel] + +logpath = /var/log/stunnel4/stunnel.log + + +[ejabberd-auth] + +port = 5222 +logpath = /var/log/ejabberd/ejabberd.log + + +[counter-strike] + +logpath = /opt/cstrike/logs/L[0-9]*.log +tcpport = 27030,27031,27032,27033,27034,27035,27036,27037,27038,27039 +udpport = 1200,27000,27001,27002,27003,27004,27005,27006,27007,27008,27009,27010,27011,27012,27013,27014,27015 +action_ = %(default/action_)s[name=%(__name__)s-tcp, port="%(tcpport)s", protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, port="%(udpport)s", protocol="udp"] + +[softethervpn] +port = 500,4500 +protocol = udp +logpath = /usr/local/vpnserver/security_log/*/sec.log + +[gitlab] +port = http,https +logpath = /var/log/gitlab/gitlab-rails/application.log + +[grafana] +port = http,https +logpath = /var/log/grafana/grafana.log + +[bitwarden] +port = http,https +logpath = /home/*/bwdata/logs/identity/Identity/log.txt + +[centreon] +port = http,https +logpath = /var/log/centreon/login.log + +[nagios] + +logpath = %(syslog_daemon)s ; nrpe.cfg may define a different log_facility +backend = %(syslog_backend)s +maxretry = 1 + + +[oracleims] +logpath = /opt/sun/comms/messaging64/log/mail.log_current +banaction = %(banaction_allports)s + +[directadmin] +logpath = /var/log/directadmin/login.log +port = 2222 + +[portsentry] +logpath = /var/lib/portsentry/portsentry.history +maxretry = 1 + +[pass2allow-ftp] +port = ftp,ftp-data,ftps,ftps-data +knocking_url = /knocking/ +filter = apache-pass[knocking_url="%(knocking_url)s"] +logpath = %(apache_access_log)s +blocktype = RETURN +returntype = DROP +action = %(action_)s[blocktype=%(blocktype)s, returntype=%(returntype)s, + actionstart_on_demand=false, actionrepair_on_unban=true] +bantime = 1h +maxretry = 1 +findtime = 1 + + +[murmur] +port = 64738 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/mumble-server/mumble-server.log + + +[screensharingd] +logpath = /var/log/system.log +logencoding = utf-8 + +[haproxy-http-auth] +logpath = /var/log/haproxy.log + +[slapd] +port = ldap,ldaps +logpath = /var/log/slapd.log + +[domino-smtp] +port = smtp,ssmtp +logpath = /home/domino01/data/IBM_TECHNICAL_SUPPORT/console.log + +[phpmyadmin-syslog] +port = http,https +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +[zoneminder] +port = http,https +logpath = %(apache_error_log)s + +[traefik-auth] +port = http,https +logpath = /var/log/traefik/access.log diff --git a/plugins/fail2ban/tpl/fail2ban.d/default.conf b/plugins/fail2ban/tpl/fail2ban.d/default.conf new file mode 100644 index 000000000..378ff9dff --- /dev/null +++ b/plugins/fail2ban/tpl/fail2ban.d/default.conf @@ -0,0 +1,25 @@ +[DEFAULT] +allowipv6 = auto + +action_ = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] + +action_mw = %(action_)s + %(mta)s-whois[sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"] + +action_mwl = %(action_)s + %(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"] + +action_xarf = %(action_)s + xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath="%(logpath)s", port="%(port)s"] + +action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"] + %(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"] + +action_blocklist_de = blocklist_de[email="%(sender)s", service="%(__name__)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"] + +action_badips = badips.py[category="%(__name__)s", banaction="%(banaction)s", agent="%(fail2ban_agent)s"] +action_badips_report = badips[category="%(__name__)s", agent="%(fail2ban_agent)s"] + +action_abuseipdb = abuseipdb + +action = %(action_)s \ No newline at end of file diff --git a/plugins/fail2ban/tpl/fail2ban.note.conf b/plugins/fail2ban/tpl/fail2ban.note.conf new file mode 100644 index 000000000..48e8ceeec --- /dev/null +++ b/plugins/fail2ban/tpl/fail2ban.note.conf @@ -0,0 +1,964 @@ +# +# WARNING: heavily refactored in 0.9.0 release. Please review and +# customize settings for your setup. +# +# Changes: in most of the cases you should not modify this +# file, but provide customizations in jail.local file, +# or separate .conf files under jail.d/ directory, e.g.: +# +# HOW TO ACTIVATE JAILS: +# +# YOU SHOULD NOT MODIFY THIS FILE. +# +# It will probably be overwritten or improved in a distribution update. +# +# Provide customizations in a jail.local file or a jail.d/customisation.local. +# For example to change the default bantime for all jails and to enable the +# ssh-iptables jail the following (uncommented) would appear in the .local file. +# See man 5 jail.conf for details. +# +# [DEFAULT] +# bantime = 1h +# +# [sshd] +# enabled = true +# +# See jail.conf(5) man page for more information + + + +# Comments: use '#' for comment lines and ';' (following a space) for inline comments + + +[INCLUDES] + +#before = paths-distro.conf +before = paths-debian.conf + +# The DEFAULT allows a global definition of the options. They can be overridden +# in each jail afterwards. + +[DEFAULT] + +# +# MISCELLANEOUS OPTIONS +# + +# "bantime.increment" allows to use database for searching of previously banned ip's to increase a +# default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32... +#bantime.increment = true + +# "bantime.rndtime" is the max number of seconds using for mixing with random time +# to prevent "clever" botnets calculate exact time IP can be unbanned again: +#bantime.rndtime = + +# "bantime.maxtime" is the max number of seconds using the ban time can reach (doesn't grow further) +#bantime.maxtime = + +# "bantime.factor" is a coefficient to calculate exponent growing of the formula or common multiplier, +# default value of factor is 1 and with default value of formula, the ban time +# grows by 1, 2, 4, 8, 16 ... +#bantime.factor = 1 + +# "bantime.formula" used by default to calculate next value of ban time, default value below, +# the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32... +#bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor +# +# more aggressive example of formula has the same values only for factor "2.0 / 2.885385" : +#bantime.formula = ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor) + +# "bantime.multipliers" used to calculate next value of ban time instead of formula, coresponding +# previously ban count and given "bantime.factor" (for multipliers default is 1); +# following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count, +# always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours +#bantime.multipliers = 1 2 4 8 16 32 64 +# following example can be used for small initial ban time (bantime=60) - it grows more aggressive at begin, +# for bantime=60 the multipliers are minutes and equal: 1 min, 5 min, 30 min, 1 hour, 5 hour, 12 hour, 1 day, 2 day +#bantime.multipliers = 1 5 30 60 300 720 1440 2880 + +# "bantime.overalljails" (if true) specifies the search of IP in the database will be executed +# cross over all jails, if false (dafault), only current jail of the ban IP will be searched +#bantime.overalljails = false + +# -------------------- + +# "ignoreself" specifies whether the local resp. own IP addresses should be ignored +# (default is true). Fail2ban will not ban a host which matches such addresses. +#ignoreself = true + +# "ignoreip" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban +# will not ban a host which matches an address in this list. Several addresses +# can be defined using space (and/or comma) separator. +#ignoreip = 127.0.0.1/8 ::1 + +# External command that will take an tagged arguments to ignore, e.g. , +# and return true if the IP is to be ignored. False otherwise. +# +# ignorecommand = /path/to/command +ignorecommand = + +# "bantime" is the number of seconds that a host is banned. +bantime = 10m + +# A host is banned if it has generated "maxretry" during the last "findtime" +# seconds. +findtime = 10m + +# "maxretry" is the number of failures before a host get banned. +maxretry = 5 + +# "maxmatches" is the number of matches stored in ticket (resolvable via tag in actions). +maxmatches = %(maxretry)s + +# "backend" specifies the backend used to get files modification. +# Available options are "pyinotify", "gamin", "polling", "systemd" and "auto". +# This option can be overridden in each jail as well. +# +# pyinotify: requires pyinotify (a file alteration monitor) to be installed. +# If pyinotify is not installed, Fail2ban will use auto. +# gamin: requires Gamin (a file alteration monitor) to be installed. +# If Gamin is not installed, Fail2ban will use auto. +# polling: uses a polling algorithm which does not require external libraries. +# systemd: uses systemd python library to access the systemd journal. +# Specifying "logpath" is not valid for this backend. +# See "journalmatch" in the jails associated filter config +# auto: will try to use the following backends, in order: +# pyinotify, gamin, polling. +# +# Note: if systemd backend is chosen as the default but you enable a jail +# for which logs are present only in its own log files, specify some other +# backend for that jail (e.g. polling) and provide empty value for +# journalmatch. See https://github.com/fail2ban/fail2ban/issues/959#issuecomment-74901200 +backend = systemd + +# "usedns" specifies if jails should trust hostnames in logs, +# warn when DNS lookups are performed, or ignore all hostnames in logs +# +# yes: if a hostname is encountered, a DNS lookup will be performed. +# warn: if a hostname is encountered, a DNS lookup will be performed, +# but it will be logged as a warning. +# no: if a hostname is encountered, will not be used for banning, +# but it will be logged as info. +# raw: use raw value (no hostname), allow use it for no-host filters/actions (example user) +usedns = warn + +# "logencoding" specifies the encoding of the log files handled by the jail +# This is used to decode the lines from the log file. +# Typical examples: "ascii", "utf-8" +# +# auto: will use the system locale setting +logencoding = auto + +# "enabled" enables the jails. +# By default all jails are disabled, and it should stay this way. +# Enable only relevant to your setup jails in your .local or jail.d/*.conf +# +# true: jail will be enabled and log files will get monitored for changes +# false: jail is not enabled +enabled = false + + +# "mode" defines the mode of the filter (see corresponding filter implementation for more info). +mode = normal + +# "filter" defines the filter to use by the jail. +# By default jails have names matching their filter name +# +filter = %(__name__)s[mode=%(mode)s] + + +# +# ACTIONS +# + +# Some options used for actions + +# Destination email address used solely for the interpolations in +# jail.{conf,local,d/*} configuration files. +destemail = root@localhost + +# Sender email address used solely for some actions +sender = root@ + +# E-mail action. Since 0.8.1 Fail2Ban uses sendmail MTA for the +# mailing. Change mta configuration parameter to mail if you want to +# revert to conventional 'mail'. +mta = sendmail + +# Default protocol +protocol = tcp + +# Specify chain where jumps would need to be added in ban-actions expecting parameter chain +chain = + +# Ports to be banned +# Usually should be overridden in a particular jail +port = 0:65535 + +# Format of user-agent https://tools.ietf.org/html/rfc7231#section-5.5.3 +fail2ban_agent = Fail2Ban/%(fail2ban_version)s + +# +# Action shortcuts. To be used to define action parameter + +# Default banning action (e.g. iptables, iptables-new, +# iptables-multiport, shorewall, etc) It is used to define +# action_* variables. Can be overridden globally or per +# section within jail.local file +banaction = iptables-multiport +banaction_allports = iptables-allports + +# The simplest action to take: ban only +action_ = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] + +# ban & send an e-mail with whois report to the destemail. +action_mw = %(action_)s + %(mta)s-whois[sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"] + +# ban & send an e-mail with whois report and relevant log lines +# to the destemail. +action_mwl = %(action_)s + %(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"] + +# See the IMPORTANT note in action.d/xarf-login-attack for when to use this action +# +# ban & send a xarf e-mail to abuse contact of IP address and include relevant log lines +# to the destemail. +action_xarf = %(action_)s + xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath="%(logpath)s", port="%(port)s"] + +# ban IP on CloudFlare & send an e-mail with whois report and relevant log lines +# to the destemail. +action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"] + %(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"] + +# Report block via blocklist.de fail2ban reporting service API +# +# See the IMPORTANT note in action.d/blocklist_de.conf for when to use this action. +# Specify expected parameters in file action.d/blocklist_de.local or if the interpolation +# `action_blocklist_de` used for the action, set value of `blocklist_de_apikey` +# in your `jail.local` globally (section [DEFAULT]) or per specific jail section (resp. in +# corresponding jail.d/my-jail.local file). +# +action_blocklist_de = blocklist_de[email="%(sender)s", service="%(__name__)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"] + +# Report ban via badips.com, and use as blacklist +# +# See BadIPsAction docstring in config/action.d/badips.py for +# documentation for this action. +# +# NOTE: This action relies on banaction being present on start and therefore +# should be last action defined for a jail. +# +action_badips = badips.py[category="%(__name__)s", banaction="%(banaction)s", agent="%(fail2ban_agent)s"] +# +# Report ban via badips.com (uses action.d/badips.conf for reporting only) +# +action_badips_report = badips[category="%(__name__)s", agent="%(fail2ban_agent)s"] + +# Report ban via abuseipdb.com. +# +# See action.d/abuseipdb.conf for usage example and details. +# +action_abuseipdb = abuseipdb + +# Choose default action. To change, just override value of 'action' with the +# interpolation to the chosen action shortcut (e.g. action_mw, action_mwl, etc) in jail.local +# globally (section [DEFAULT]) or per specific section +action = %(action_)s + + +# +# JAILS +# + +# +# SSH servers +# + +[sshd] + +# To use more aggressive sshd modes set filter parameter "mode" in jail.local: +# normal (default), ddos, extra or aggressive (combines all). +# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details. +#mode = normal +port = ssh +logpath = %(sshd_log)s +backend = %(sshd_backend)s + + +[dropbear] + +port = ssh +logpath = %(dropbear_log)s +backend = %(dropbear_backend)s + + +[selinux-ssh] + +port = ssh +logpath = %(auditd_log)s + + +# +# HTTP servers +# + +[apache-auth] + +port = http,https +logpath = %(apache_error_log)s + + +[apache-badbots] +# Ban hosts which agent identifies spammer robots crawling the web +# for email addresses. The mail outputs are buffered. +port = http,https +logpath = %(apache_access_log)s +bantime = 48h +maxretry = 1 + + +[apache-noscript] + +port = http,https +logpath = %(apache_error_log)s + + +[apache-overflows] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-nohome] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-botsearch] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-fakegooglebot] + +port = http,https +logpath = %(apache_access_log)s +maxretry = 1 +ignorecommand = %(ignorecommands_dir)s/apache-fakegooglebot + + +[apache-modsecurity] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 2 + + +[apache-shellshock] + +port = http,https +logpath = %(apache_error_log)s +maxretry = 1 + + +[openhab-auth] + +filter = openhab +banaction = %(banaction_allports)s +logpath = /opt/openhab/logs/request.log + + +[nginx-http-auth] + +port = http,https +logpath = %(nginx_error_log)s + +# To use 'nginx-limit-req' jail you should have `ngx_http_limit_req_module` +# and define `limit_req` and `limit_req_zone` as described in nginx documentation +# http://nginx.org/en/docs/http/ngx_http_limit_req_module.html +# or for example see in 'config/filter.d/nginx-limit-req.conf' +[nginx-limit-req] +port = http,https +logpath = %(nginx_error_log)s + +[nginx-botsearch] + +port = http,https +logpath = %(nginx_error_log)s +maxretry = 2 + + +# Ban attackers that try to use PHP's URL-fopen() functionality +# through GET/POST variables. - Experimental, with more than a year +# of usage in production environments. + +[php-url-fopen] + +port = http,https +logpath = %(nginx_access_log)s + %(apache_access_log)s + + +[suhosin] + +port = http,https +logpath = %(suhosin_log)s + + +[lighttpd-auth] +# Same as above for Apache's mod_auth +# It catches wrong authentifications +port = http,https +logpath = %(lighttpd_error_log)s + + +# +# Webmail and groupware servers +# + +[roundcube-auth] + +port = http,https +logpath = %(roundcube_errors_log)s +# Use following line in your jail.local if roundcube logs to journal. +#backend = %(syslog_backend)s + + +[openwebmail] + +port = http,https +logpath = /var/log/openwebmail.log + + +[horde] + +port = http,https +logpath = /var/log/horde/horde.log + + +[groupoffice] + +port = http,https +logpath = /home/groupoffice/log/info.log + + +[sogo-auth] +# Monitor SOGo groupware server +# without proxy this would be: +# port = 20000 +port = http,https +logpath = /var/log/sogo/sogo.log + + +[tine20] + +logpath = /var/log/tine20/tine20.log +port = http,https + + +# +# Web Applications +# +# + +[drupal-auth] + +port = http,https +logpath = %(syslog_daemon)s +backend = %(syslog_backend)s + +[guacamole] + +port = http,https +logpath = /var/log/tomcat*/catalina.out +#logpath = /var/log/guacamole.log + +[monit] +#Ban clients brute-forcing the monit gui login +port = 2812 +logpath = /var/log/monit + /var/log/monit.log + + +[webmin-auth] + +port = 10000 +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +[froxlor-auth] + +port = http,https +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +# +# HTTP Proxy servers +# +# + +[squid] + +port = 80,443,3128,8080 +logpath = /var/log/squid/access.log + + +[3proxy] + +port = 3128 +logpath = /var/log/3proxy.log + + +# +# FTP servers +# + + +[proftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(proftpd_log)s +backend = %(proftpd_backend)s + + +[pure-ftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(pureftpd_log)s +backend = %(pureftpd_backend)s + + +[gssftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(syslog_daemon)s +backend = %(syslog_backend)s + + +[wuftpd] + +port = ftp,ftp-data,ftps,ftps-data +logpath = %(wuftpd_log)s +backend = %(wuftpd_backend)s + + +[vsftpd] +# or overwrite it in jails.local to be +# logpath = %(syslog_authpriv)s +# if you want to rely on PAM failed login attempts +# vsftpd's failregex should match both of those formats +port = ftp,ftp-data,ftps,ftps-data +logpath = %(vsftpd_log)s + + +# +# Mail servers +# + +# ASSP SMTP Proxy Jail +[assp] + +port = smtp,465,submission +logpath = /root/path/to/assp/logs/maillog.txt + + +[courier-smtp] + +port = smtp,465,submission +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[postfix] +# To use another modes set filter parameter "mode" in jail.local: +mode = more +port = smtp,465,submission +logpath = %(postfix_log)s +backend = %(postfix_backend)s + + +[postfix-rbl] + +filter = postfix[mode=rbl] +port = smtp,465,submission +logpath = %(postfix_log)s +backend = %(postfix_backend)s +maxretry = 1 + + +[sendmail-auth] + +port = submission,465,smtp +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[sendmail-reject] +# To use more aggressive modes set filter parameter "mode" in jail.local: +# normal (default), extra or aggressive +# See "tests/files/logs/sendmail-reject" or "filter.d/sendmail-reject.conf" for usage example and details. +#mode = normal +port = smtp,465,submission +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[qmail-rbl] + +filter = qmail +port = smtp,465,submission +logpath = /service/qmail/log/main/current + + +# dovecot defaults to logging to the mail syslog facility +# but can be set by syslog_facility in the dovecot configuration. +[dovecot] + +port = pop3,pop3s,imap,imaps,submission,465,sieve +logpath = %(dovecot_log)s +backend = %(dovecot_backend)s + + +[sieve] + +port = smtp,465,submission +logpath = %(dovecot_log)s +backend = %(dovecot_backend)s + + +[solid-pop3d] + +port = pop3,pop3s +logpath = %(solidpop3d_log)s + + +[exim] +# see filter.d/exim.conf for further modes supported from filter: +#mode = normal +port = smtp,465,submission +logpath = %(exim_main_log)s + + +[exim-spam] + +port = smtp,465,submission +logpath = %(exim_main_log)s + + +[kerio] + +port = imap,smtp,imaps,465 +logpath = /opt/kerio/mailserver/store/logs/security.log + + +# +# Mail servers authenticators: might be used for smtp,ftp,imap servers, so +# all relevant ports get banned +# + +[courier-auth] + +port = smtp,465,submission,imap,imaps,pop3,pop3s +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[postfix-sasl] + +filter = postfix[mode=auth] +port = smtp,465,submission,imap,imaps,pop3,pop3s +# You might consider monitoring /var/log/mail.warn instead if you are +# running postfix since it would provide the same log lines at the +# "warn" level but overall at the smaller filesize. +logpath = %(postfix_log)s +backend = %(postfix_backend)s + + +[perdition] + +port = imap,imaps,pop3,pop3s +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[squirrelmail] + +port = smtp,465,submission,imap,imap2,imaps,pop3,pop3s,http,https,socks +logpath = /var/lib/squirrelmail/prefs/squirrelmail_access_log + + +[cyrus-imap] + +port = imap,imaps +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +[uwimap-auth] + +port = imap,imaps +logpath = %(syslog_mail)s +backend = %(syslog_backend)s + + +# +# +# DNS servers +# + + +# !!! WARNING !!! +# Since UDP is connection-less protocol, spoofing of IP and imitation +# of illegal actions is way too simple. Thus enabling of this filter +# might provide an easy way for implementing a DoS against a chosen +# victim. See +# http://nion.modprobe.de/blog/archives/690-fail2ban-+-dns-fail.html +# Please DO NOT USE this jail unless you know what you are doing. +# +# IMPORTANT: see filter.d/named-refused for instructions to enable logging +# This jail blocks UDP traffic for DNS requests. +# [named-refused-udp] +# +# filter = named-refused +# port = domain,953 +# protocol = udp +# logpath = /var/log/named/security.log + +# IMPORTANT: see filter.d/named-refused for instructions to enable logging +# This jail blocks TCP traffic for DNS requests. + +[named-refused] + +port = domain,953 +logpath = /var/log/named/security.log + + +[nsd] + +port = 53 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/nsd.log + + +# +# Miscellaneous +# + +[asterisk] + +port = 5060,5061 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/asterisk/messages +maxretry = 10 + + +[freeswitch] + +port = 5060,5061 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/freeswitch.log +maxretry = 10 + + +# enable adminlog; it will log to a file inside znc's directory by default. +[znc-adminlog] + +port = 6667 +logpath = /var/lib/znc/moddata/adminlog/znc.log + + +# To log wrong MySQL access attempts add to /etc/my.cnf in [mysqld] or +# equivalent section: +# log-warnings = 2 +# +# for syslog (daemon facility) +# [mysqld_safe] +# syslog +# +# for own logfile +# [mysqld] +# log-error=/var/log/mysqld.log +[mysqld-auth] + +port = 3306 +logpath = %(mysql_log)s +backend = %(mysql_backend)s + + +# Log wrong MongoDB auth (for details see filter 'filter.d/mongodb-auth.conf') +[mongodb-auth] +# change port when running with "--shardsvr" or "--configsvr" runtime operation +port = 27017 +logpath = /var/log/mongodb/mongodb.log + + +# Jail for more extended banning of persistent abusers +# !!! WARNINGS !!! +# 1. Make sure that your loglevel specified in fail2ban.conf/.local +# is not at DEBUG level -- which might then cause fail2ban to fall into +# an infinite loop constantly feeding itself with non-informative lines +# 2. Increase dbpurgeage defined in fail2ban.conf to e.g. 648000 (7.5 days) +# to maintain entries for failed logins for sufficient amount of time +[recidive] + +logpath = /var/log/fail2ban.log +banaction = %(banaction_allports)s +bantime = 1w +findtime = 1d + + +# Generic filter for PAM. Has to be used with action which bans all +# ports such as iptables-allports, shorewall + +[pam-generic] +# pam-generic filter can be customized to monitor specific subset of 'tty's +banaction = %(banaction_allports)s +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +[xinetd-fail] + +banaction = iptables-multiport-log +logpath = %(syslog_daemon)s +backend = %(syslog_backend)s +maxretry = 2 + + +# stunnel - need to set port for this +[stunnel] + +logpath = /var/log/stunnel4/stunnel.log + + +[ejabberd-auth] + +port = 5222 +logpath = /var/log/ejabberd/ejabberd.log + + +[counter-strike] + +logpath = /opt/cstrike/logs/L[0-9]*.log +tcpport = 27030,27031,27032,27033,27034,27035,27036,27037,27038,27039 +udpport = 1200,27000,27001,27002,27003,27004,27005,27006,27007,27008,27009,27010,27011,27012,27013,27014,27015 +action_ = %(default/action_)s[name=%(__name__)s-tcp, port="%(tcpport)s", protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, port="%(udpport)s", protocol="udp"] + +[softethervpn] +port = 500,4500 +protocol = udp +logpath = /usr/local/vpnserver/security_log/*/sec.log + +[gitlab] +port = http,https +logpath = /var/log/gitlab/gitlab-rails/application.log + +[grafana] +port = http,https +logpath = /var/log/grafana/grafana.log + +[bitwarden] +port = http,https +logpath = /home/*/bwdata/logs/identity/Identity/log.txt + +[centreon] +port = http,https +logpath = /var/log/centreon/login.log + +# consider low maxretry and a long bantime +# nobody except your own Nagios server should ever probe nrpe +[nagios] + +logpath = %(syslog_daemon)s ; nrpe.cfg may define a different log_facility +backend = %(syslog_backend)s +maxretry = 1 + + +[oracleims] +# see "oracleims" filter file for configuration requirement for Oracle IMS v6 and above +logpath = /opt/sun/comms/messaging64/log/mail.log_current +banaction = %(banaction_allports)s + +[directadmin] +logpath = /var/log/directadmin/login.log +port = 2222 + +[portsentry] +logpath = /var/lib/portsentry/portsentry.history +maxretry = 1 + +[pass2allow-ftp] +# this pass2allow example allows FTP traffic after successful HTTP authentication +port = ftp,ftp-data,ftps,ftps-data +# knocking_url variable must be overridden to some secret value in jail.local +knocking_url = /knocking/ +filter = apache-pass[knocking_url="%(knocking_url)s"] +# access log of the website with HTTP auth +logpath = %(apache_access_log)s +blocktype = RETURN +returntype = DROP +action = %(action_)s[blocktype=%(blocktype)s, returntype=%(returntype)s, + actionstart_on_demand=false, actionrepair_on_unban=true] +bantime = 1h +maxretry = 1 +findtime = 1 + + +[murmur] +# AKA mumble-server +port = 64738 +action_ = %(default/action_)s[name=%(__name__)s-tcp, protocol="tcp"] + %(default/action_)s[name=%(__name__)s-udp, protocol="udp"] +logpath = /var/log/mumble-server/mumble-server.log + + +[screensharingd] +# For Mac OS Screen Sharing Service (VNC) +logpath = /var/log/system.log +logencoding = utf-8 + +[haproxy-http-auth] +# HAProxy by default doesn't log to file you'll need to set it up to forward +# logs to a syslog server which would then write them to disk. +# See "haproxy-http-auth" filter for a brief cautionary note when setting +# maxretry and findtime. +logpath = /var/log/haproxy.log + +[slapd] +port = ldap,ldaps +logpath = /var/log/slapd.log + +[domino-smtp] +port = smtp,ssmtp +logpath = /home/domino01/data/IBM_TECHNICAL_SUPPORT/console.log + +[phpmyadmin-syslog] +port = http,https +logpath = %(syslog_authpriv)s +backend = %(syslog_backend)s + + +[zoneminder] +# Zoneminder HTTP/HTTPS web interface auth +# Logs auth failures to apache2 error log +port = http,https +logpath = %(apache_error_log)s + +[traefik-auth] +# to use 'traefik-auth' filter you have to configure your Traefik instance, +# see `filter.d/traefik-auth.conf` for details and service example. +port = http,https +logpath = /var/log/traefik/access.log diff --git a/plugins/fail2ban/tpl/jail.d/default.conf b/plugins/fail2ban/tpl/jail.d/default.conf new file mode 100644 index 000000000..9ce9c7f4f --- /dev/null +++ b/plugins/fail2ban/tpl/jail.d/default.conf @@ -0,0 +1,2 @@ +[DEFAULT] +backend = systemd \ No newline at end of file diff --git a/plugins/fail2ban/tpl/jail.d/sshd.conf b/plugins/fail2ban/tpl/jail.d/sshd.conf new file mode 100644 index 000000000..f3e3b9fd9 --- /dev/null +++ b/plugins/fail2ban/tpl/jail.d/sshd.conf @@ -0,0 +1,11 @@ +#sshd-START +[sshd] +enabled = true +filter = sshd +port = 2022,no +maxretry = 5 +findtime = 300 +bantime = 86400 +action = %(action_mwl)s +logpath = /var/log/auth.log +#sshd-END \ No newline at end of file diff --git a/plugins/gdrive/class/gdriveclient.py b/plugins/gdrive/class/gdriveclient.py new file mode 100644 index 000000000..527375ed9 --- /dev/null +++ b/plugins/gdrive/class/gdriveclient.py @@ -0,0 +1,374 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import io + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +gd_dir = mw.getServerDir() +'/gdrive/lib' +cmd = 'ls '+gd_dir+' | grep python | cut -d \\ -f 1 | awk \'END {print}\'' +info = mw.execShell(cmd) +p = gd_dir +'/'+ info[0].strip() + "/site-packages" +sys.path.append(p) + +# ----------------------------- +import google.oauth2.credentials +import google_auth_oauthlib.flow +from googleapiclient.discovery import build +from google_auth_oauthlib.flow import InstalledAppFlow +from google.auth.transport.requests import Request +from googleapiclient.http import MediaFileUpload +from googleapiclient.http import MediaIoBaseDownload + + +class gdriveclient(): + __plugin_dir = '' + __server_dir = '' + + __credentials = "/root/credentials.json" + __backup_dir_name = "backup" + __creds = None + __exclude = "" + __scpos = ['https://www.googleapis.com/auth/drive.file'] + _title = 'Google Drive' + _name = 'Google Drive' + __debug = False + + _DEFAULT_AUTH_PROMPT_MESSAGE = ( + 'Please visit this URL to authorize this application: {url}') + """str: The message to display when prompting the user for + authorization.""" + _DEFAULT_AUTH_CODE_MESSAGE = ( + 'Enter the authorization code: ') + """str: The message to display when prompting the user for the + authorization code. Used only by the console strategy.""" + + _DEFAULT_WEB_SUCCESS_MESSAGE = ( + 'The authentication flow has completed, you may close this window.') + + def __init__(self, plugin_dir, server_dir): + self.__plugin_dir = plugin_dir + self.__server_dir = server_dir + self.set_creds() + + # self.get_exclode() + + def setDebug(self, d=False): + self.__debug = d + + def D(self, msg=''): + if self.__debug: + print(msg) + + # 检查gdrive连接 + def _check_connect(self): + try: + service = build('drive', 'v3', credentials=self.__creds) + results = service.files().list( + pageSize=10, fields="nextPageToken, files(id, name)").execute() + results.get('files', []) + except: + return False + return True + + # 设置creds + def set_creds(self): + token_file = self.__server_dir + '/token.json' + if os.path.exists(token_file): + with open(token_file, 'rb') as token: + tmp_data = json.load(token)['credentials'] + self.__creds = google.oauth2.credentials.Credentials( + tmp_data['token'], + tmp_data['refresh_token'], + tmp_data['id_token'], + tmp_data['token_uri'], + tmp_data['client_id'], + tmp_data['client_secret'], + tmp_data['scopes']) + # if not self._check_connect(): + # return False + # else: + # return True + + def get_sign_in_url(self): + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + self.__plugin_dir + '/credentials.json', + scopes=self.__scpos) + # flow.redirect_uri = 'https://drive.aapanel.com' + flow.redirect_uri = 'https://drive.aapanel.com' + auth_url, state = flow.authorization_url( + access_type='offline', + prompt='consent', + include_granted_scopes='false') + return auth_url, state + + def set_auth_url(self, url): + token_file = self.__server_dir + '/token.json' + if os.path.exists(token_file): + return mw.returnJson(True, "验证成功") + + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + self.__plugin_dir + '/credentials.json', + scopes=self.__scpos, + state=url.split('state=')[1].split('&code=')[0]) + # flow.redirect_uri = 'https://localhost' + flow.redirect_uri = 'https://drive.aapanel.com' + flow.fetch_token(authorization_response=url) + credentials = flow.credentials + + credentials_data = {} + credentials_data['credentials'] = { + 'token': credentials.token, + 'id_token': credentials.id_token, + 'refresh_token': credentials.refresh_token, + 'token_uri': credentials.token_uri, + 'client_id': credentials.client_id, + 'client_secret': credentials.client_secret, + 'scopes': credentials.scopes} + with open(token_file, 'w') as token: + json.dump(credentials_data, token) + if not self.set_creds(): + return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证") + return mw.returnJson(True, "验证成功") + + # 获取token + def get_token(self, get): + token_file = self.__server_dir + '/token.json' + import requests + try: + respone = requests.get("https://www.google.com", timeout=2) + except: + return mw.returnJson(False, "连接谷歌失败") + if respone.status_code != 200: + return mw.returnJson(False, "连接谷歌失败") + if not self.set_creds(): + return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证") + if not os.path.exists(token_file): + return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证") + return mw.returnJson(True, "验证成功") + + # 获取auth_url + def get_auth_url(self, get): + self.get_sign_in_url() + if os.path.exists("/tmp/auth_url"): + return mw.readFile("/tmp/auth_url") + + # 检查连接 + def check_connect(self, get): + token_file = self.__server_dir + '/token.json' + if os.path.exists(token_file): + with open(token_file, 'rb') as token: + self.set_creds() + else: + self.D("Failed to get Google token, please verify before use") + return mw.returnJson(True, "Failed to get Google token, please verify before use") + service = build('drive', 'v3', credentials=self.__creds) + results = service.files().list( + pageSize=10, fields="nextPageToken, files(id, name)").execute() + try: + results.get('files', []) + return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证") + return mw.returnJson(True, "验证成功") + except: + return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证") + + def _get_filename(self, filename): + l = filename.split("/") + return l[-1] + + def _create_folder_cycle(self, filepath): + l = filepath.split("/") + fid_list = [] + for i in l: + if not i: + continue + fid = self.__get_folder_id(i) + if fid: + fid_list.append(fid) + continue + if not fid_list: + fid_list.append("") + fid_list.append(self.create_folder(i, fid_list[-1])) + return fid_list[-1] + + def build_object_name(self, data_type, file_name): + """根据数据类型构建对象存储名称 + + :param data_type: + :param file_name: + :return: + """ + + import re + prefix_dict = { + "site": "web", + "database": "db", + "path": "path", + } + + if not prefix_dict.get(data_type): + print("data_type 类型错误!!!") + exit(1) + + file_regx = prefix_dict.get(data_type) + r"_(.+)_20\d+_\d+\." + sub_search = re.search(file_regx.lower(), file_name) + sub_path_name = "" + if sub_search: + sub_path_name = sub_search.groups()[0] + sub_path_name += '/' + # 构建OS存储路径 + object_name = self.__backup_dir_name + \ + '/{}/{}'.format(data_type, sub_path_name) + + if object_name[:1] == "/": + object_name = object_name[1:] + return object_name + + # 上传文件 + def upload_file(self, filename, data_type=None): + """ + get.filename 上传后的文件名 + get.filepath 上传文件路径 + 被面板新版计划任务调用时 + get表示file_name + :param get: + :return: + """ + # filename = filename + filepath = self.build_object_name(data_type, filename) + _filename = self._get_filename(filename) + self.D(filepath) + self.D(filename) + + parents = self._create_folder_cycle(filepath) + self.D(parents) + drive_service = build('drive', 'v3', credentials=self.__creds) + file_metadata = {'name': _filename, 'parents': [parents]} + media = MediaFileUpload(filename, resumable=True) + file = drive_service.files().create( + body=file_metadata, media_body=media, fields='id').execute() + self.D('Upload Success ,File ID: %s' % file.get('id')) + return True + + def _get_file_id(self, filename): + service = build('drive', 'v3', credentials=self.__creds) + results = service.files().list(pageSize=10, q="name='{}'".format( + filename), fields="nextPageToken, files(id, name)").execute() + items = results.get('files', []) + if not items: + return [] + else: + for item in items: + return item["id"] + + def delete_file(self, filename=None, data_type=None): + file_id = self._get_file_id(filename) + self.delete_file_by_id(file_id) + return True + + def delete_file_by_id(self, file_id): + self.D("delete id:{}".format(file_id)) + try: + drive_service = build('drive', 'v3', credentials=self.__creds) + drive_service.files().delete(fileId=file_id).execute() + return True + except Exception as e: + return False + + # 创建目录 + def create_folder(self, folder_name, parents=""): + self.D(self.__creds) + self.D("folder_name: {}".format(folder_name)) + self.D("parents: {}".format(parents)) + service = build('drive', 'v3', credentials=self.__creds) + file_metadata = { + 'name': folder_name, + 'mimeType': 'application/vnd.google-apps.folder' + } + if parents: + file_metadata['parents'] = [parents] + folder = service.files().create(body=file_metadata, fields='id').execute() + self.D('Create Folder ID: %s' % folder.get('id')) + return folder.get('id') + + def get_rootdir_id(self, folder_name='backup'): + service = build('drive', 'v3', credentials=self.__creds) + results = service.files().list(pageSize=10, q="name='{}' and mimeType='application/vnd.google-apps.folder'".format(folder_name), + fields="nextPageToken, files(id, name,size,parents,webViewLink)").execute() + items = results.get('files', []) + if len(items) == 0: + self.create_folder(folder_name) + return self.get_rootdir_id(folder_name) + + return items[0]['parents'][0] + + # 获取目录id + def __get_folder_id(self, floder_name): + service = build('drive', 'v3', credentials=self.__creds) + results = service.files().list(pageSize=10, q="name='{}' and mimeType='application/vnd.google-apps.folder'".format(floder_name), + fields="nextPageToken, files(id, name)").execute() + items = results.get('files', []) + if not items: + return [] + else: + for item in items: + return item["id"] + + def get_res_info(self, rid): + service = build('drive', 'v3', credentials=self.__creds) + results = service.files().get(fileId='{}'.format(rid)).execute() + return results + + def get_id_list(self, driveId=''): + service = build('drive', 'v3', credentials=self.__creds) + results = service.files().list(pageSize=10, driveId="{}".format(driveId), + fields="nextPageToken, files(id, name,size,parents)").execute() + items = results.get('files', []) + return items + + def get_list(self, dir_id='', next_page_token=''): + if dir_id == '': + dir_id = self.get_rootdir_id(self.__backup_dir_name) + + service = build('drive', 'v3', credentials=self.__creds) + cmd_query = "trashed=false and '{}' in parents".format(dir_id) + results = service.files().list(pageSize=10, q=cmd_query, orderBy='folder asc', + fields="nextPageToken, files(id, name,size,createdTime,parents,webViewLink)").execute() + items = results.get('files', []) + nextPageToken = results.get('nextPageToken', []) + # print(items) + # print(nextPageToken) + return items + + def get_exclode(self, exclude=[]): + if not exclude: + tmp_exclude = os.getenv('MW_EXCLUDE') + if tmp_exclude: + exclude = tmp_exclude.split(',') + if not exclude: + return "" + for ex in exclude: + self.__exclude += " --exclude=\"" + ex + "\"" + self.__exclude += " " + return self.__exclude + + def download_file(self, filename): + file_id = self._get_file_id(filename) + service = build('drive', 'v3', credentials=self.__creds) + request = service.files().get_media(fileId=file_id) + with open('/tmp/{}'.format(filename), 'wb') as fh: + downloader = MediaIoBaseDownload(fh, request) + done = False + while done is False: + status, done = downloader.next_chunk() + print("Download %d%%." % int(status.progress() * 100)) diff --git a/plugins/gdrive/credentials.json b/plugins/gdrive/credentials.json new file mode 100644 index 000000000..066a2e7e0 --- /dev/null +++ b/plugins/gdrive/credentials.json @@ -0,0 +1,13 @@ +{ + "web": { + "client_id": "226011946234-d3e1vashgag64utjedu1ljt9u39ncrpq.apps.googleusercontent.com", + "project_id": "light-willow-341609", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "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", + "redirect_uris": [ + "https://drive.aapanel.com" + ] + } +} \ No newline at end of file diff --git a/plugins/gdrive/credentials_bak.json b/plugins/gdrive/credentials_bak.json new file mode 100644 index 000000000..cb209bdcd --- /dev/null +++ b/plugins/gdrive/credentials_bak.json @@ -0,0 +1,11 @@ +{ + "web": { + "client_id": "540181629246-1horo9i4htamdbhiqar9rcbq33bqe2ob.apps.googleusercontent.com", + "project_id": "plated-inn-369901", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "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", + "redirect_uris": ["https://drive.aapanel.com"] + } +} \ No newline at end of file diff --git a/plugins/gdrive/ico.png b/plugins/gdrive/ico.png new file mode 100644 index 000000000..3cd2b985f Binary files /dev/null and b/plugins/gdrive/ico.png differ diff --git a/plugins/gdrive/index.html b/plugins/gdrive/index.html new file mode 100644 index 000000000..0a5a01079 --- /dev/null +++ b/plugins/gdrive/index.html @@ -0,0 +1,310 @@ + +
        +
        + + + +
        +
          +
          + + + +
          + +
          +
          + + + +
          名称大小更新时间操作
          +
          +
          +
          + + + + + \ No newline at end of file diff --git a/plugins/gdrive/index.py b/plugins/gdrive/index.py new file mode 100644 index 000000000..9a41d9326 --- /dev/null +++ b/plugins/gdrive/index.py @@ -0,0 +1,321 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + + +# print(sys.platform) +if sys.platform != "darwin": + os.chdir("/www/server/mdserver-web") + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import core.db as db + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'gdrive' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def in_array(name, arr=[]): + for x in arr: + if name == x: + return True + return False + + +sys.path.append(getPluginDir() + "/class") +from gdriveclient import gdriveclient + + +gd = gdriveclient(getPluginDir(), getServerDir()) +gd.setDebug(False) + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + return 'start' + + +def isAuthApi(): + cfg = getServerDir() + "/token.json" + if os.path.exists(cfg): + return True + return False + + +def getConf(): + if not isAuthApi(): + sign_in_url, state = gd.get_sign_in_url() + return mw.returnJson(False, "未授权!", {'auth_url': sign_in_url}) + return mw.returnJson(True, "OK") + + +def setAuthUrl(): + args = getArgs() + data = checkArgs(args, ['url']) + if not data[0]: + return data[1] + + url = args['url'] + + try: + if url.startswith("http://"): + url = url.replace("http://", "https://") + token = gd.set_auth_url(url) + # gd.store_token(token) + # gd.store_user() + return mw.returnJson(True, "授权成功!") + except Exception as e: + return mw.returnJson(False, "授权失败2!:" + str(e)) + return mw.returnJson(False, "授权失败!:" + str(e)) + + +def clearAuth(): + token = getServerDir() + "/token.json" + if os.path.exists(token): + os.remove(token) + return mw.returnJson(True, "清空授权成功!") + + +def getList(): + if not isAuthApi(): + return mw.returnJson(False, "未配置,请点击`授权`", []) + + args = getArgs() + data = checkArgs(args, ['file_id']) + if not data[0]: + return data[1] + + try: + flist = gd.get_list(args['file_id']) + return mw.returnJson(True, "ok", flist) + except Exception as e: + return mw.returnJson(False, str(e), []) + + +def createDir(): + if not isAuthApi(): + return mw.returnJson(False, "未配置,请点击`授权`", []) + + args = getArgs() + data = checkArgs(args, ['parents', 'name']) + if not data[0]: + return data[1] + isok = gd.create_folder(args['name'], args['parents']) + if isok: + return mw.returnJson(True, "创建成功") + return mw.returnJson(False, "创建失败") + + +def deleteDir(): + args = getArgs() + data = checkArgs(args, ['dir_name', 'path']) + if not data[0]: + return data[1] + + isok = gd.delete_file_by_id(args['dir_name']) + if isok: + return mw.returnJson(True, "删除成功") + return mw.returnJson(False, "文件不为空,删除失败!") + + +def deleteFile(): + args = getArgs() + data = checkArgs(args, ['path', 'filename']) + if not data[0]: + return data[1] + + isok = gd.delete_file(args['filename']) + if isok: + return mw.returnJson(True, "删除成功") + return mw.returnJson(False, "删除失败") + + +def findPathName(path, filename): + f = os.scandir(path) + l = [] + for ff in f: + t = {} + if ff.name.find(filename) > -1: + t['filename'] = path + '/' + ff.name + l.append(t) + return l + + +def backupAllFunc(stype): + if not isAuthApi(): + mw.echoInfo("未授权API,无法使用!!!") + return '' + + backup_dir = mw.getBackupDir() + run_dir = mw.getPanelDir() + + stype = sys.argv[1] + name = sys.argv[2] + num = sys.argv[3] + + prefix_dict = { + "site": "web", + "database": "db", + "path": "path", + } + + backups = [] + # print("stype:", stype) + # 提前获取-清理多余备份 + if stype == 'site': + pid = mw.M('sites').where('name=?', (name,)).getField('id') + backups = mw.M('backup').where('type=? and pid=?', ('0', pid)).field('id,filename').select() + if stype == 'database': + db_path = mw.getServerDir() + '/mysql' + pid = mw.M('databases').dbPos(db_path, 'mysql').where('name=?', (name,)).getField('id') + backups = mw.M('backup').where('type=? and pid=?', ('1', pid)).field('id,filename').select() + if stype == 'path': + backup_path = backup_dir + '/path' + _name = 'path_{}'.format(os.path.basename(name)) + backups = findPathName(backup_path, _name) + + # 其他类型关系性数据库(mysql类的) + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + db_path = mw.getServerDir() + '/' + plugin_name + pid = mw.M('databases').dbPos(db_path, 'mysql').where('name=?', (name,)).getField('id') + backups = mw.M('backup').where('type=? and pid=?', ('1', pid)).field('id,filename').select() + + args = stype + " " + name + " " + num + cmd = 'python3 ' + run_dir + '/scripts/backup.py ' + args + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + args = "database " + name + " " + num + cmd = 'python3 ' + run_dir + '/plugins/' + plugin_name + '/scripts/backup.py ' + args + + if stype == 'path': + name = os.path.basename(name) + + # print("cmd:", cmd) + os.system(cmd) + + # 开始执行上传信息. + if stype.find('database_') > -1: + bk_name = 'database' + plugin_name = stype.replace('database_', '') + bk_prefix = plugin_name + '/db' + stype = 'database' + else: + bk_prefix = prefix_dict[stype] + bk_name = stype + + find_path = backup_dir + '/' + bk_name + '/' + bk_prefix + '_' + name + find_new_file = "ls " + find_path + "_* | grep '.gz' | cut -d \\ -f 1 | awk 'END {print}'" + + # print(find_new_file) + + filename = mw.execShell(find_new_file)[0].strip() + if filename == "": + mw.echoInfo("not find upload file!") + return False + + mw.echoInfo("准备上传文件 {}".format(filename)) + mw.echoStart('开始上传') + # gd.setDebug(False) + gd.upload_file(filename, stype) + mw.echoEnd('上传成功') + + # print(backups) + backups = sorted(backups, key=lambda x: x['filename'], reverse=False) + mw.echoStart('开始删除远程备份') + num = int(num) + sep = len(backups) - num + if sep > -1: + for backup in backups: + fn = os.path.basename(backup['filename']) + gd.delete_file(fn, stype) + mw.echoInfo("---已清理远程过期备份文件:" + fn) + sep -= 1 + if sep < 0: + break + mw.echoEnd('结束删除远程备份') + + return '' + + +def installPreInspection(): + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'set_auth_url': + print(setAuthUrl()) + elif func == 'clear_auth': + print(clearAuth()) + elif func == "get_list": + print(getList()) + elif func == "create_dir": + print(createDir()) + elif func == "delete_dir": + print(deleteDir()) + elif func == 'delete_file': + print(deleteFile()) + elif in_array(func, ['site', 'database', 'path']) or func.find('database_') > -1: + print(backupAllFunc(func)) + else: + print('error') diff --git a/plugins/gdrive/info.json b/plugins/gdrive/info.json new file mode 100644 index 000000000..4cd39b39f --- /dev/null +++ b/plugins/gdrive/info.json @@ -0,0 +1,19 @@ +{ + "hook":["backup"], + "sort":3, + "title":"谷歌云网盘", + "tip":"lib", + "name":"gdrive", + "type":"扩展", + "ps":"备份你的数据到谷歌云网盘", + "versions":"2.0", + "shell":"install.sh", + "checks": "server/gdrive", + "path":"server/gdrive", + "author":"google", + "home":"https://drive.google.com/", + "api_doc":"https://developers.google.com/drive/api/guides/about-sdk?hl=zh_CN", + "api_doc2":"https://developers.google.cn/drive/api/reference/rest/v3/comments/list?hl=zh-cn", + "date":"2022-06-26", + "pid":"5" +} \ No newline at end of file diff --git a/plugins/gdrive/install.sh b/plugins/gdrive/install.sh new file mode 100644 index 000000000..ad81d8318 --- /dev/null +++ b/plugins/gdrive/install.sh @@ -0,0 +1,108 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } +function version_le() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" == "$1"; } +function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" != "$1"; } +function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } + +# cd /www/server/mdserver-web/plugins/gdrive && /bin/bash install.sh install 2.0 + +P_VER=`python3 -V | awk '{print $2}'` +echo "python:$P_VER" + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + + +PATH=$PATH:${rootPath}/bin +export PATH + +# if [ -f ${rootPath}/bin/activate ];then +# source ${rootPath}/bin/activate +# fi + +VERSION=$2 + +Install_App() +{ + tmp_ping=`ping -c 1 google.com 2>&1` + echo $tmp_ping + if [ $? -eq 0 ];then + GDDIR=$serverPath/gdrive + mkdir -p $GDDIR + + if [ ! -f ${GDDIR}/bin/activate ];then + if version_ge "$P_VER" "3.11.0" ;then + echo "python3 > 3.11" + cd ${GDDIR} && python3 -m venv ${GDDIR} + else + echo "python3 < 3.10" + cd ${GDDIR} && python3 -m venv . + fi + cd ${GDDIR} && source ${GDDIR}/bin/activate + else + cd ${GDDIR} && source ${GDDIR}/bin/activate + fi + + tmp=`python3 -V 2>&1|awk '{print $2}'` + pVersion=${tmp:0:3} + + which pip + if [ "$?" -eq "0" ];then + tmp=$(pip list|grep google-api-python-client|awk '{print $2}') + if [ "$tmp" != '2.39.0' ];then + pip install --upgrade google-api-python-client + # pip uninstall google-api-python-client -y + pip install -I google-api-python-client==2.39.0 -i https://pypi.Python.org/simple + fi + tmp=$(pip list|grep google-auth-httplib2|awk '{print $2}') + if [ "$tmp" != '0.1.0' ];then + pip uninstall google-auth-httplib2 -y + pip install -I google-auth-httplib2==0.1.0 -i https://pypi.Python.org/simple + fi + tmp=$(pip list|grep google-auth-oauthlib|awk '{print $2}') + if [ "$tmp" != '0.5.0' ];then + # pip uninstall google-auth-oauthlib -y + pip install -I google-auth-oauthlib==0.5.0 -i https://pypi.Python.org/simple + fi + tmp=$(pip list|grep -E '^httplib2'|awk '{print $2}') + if [ "$tmp" != '0.18.1' ];then + # pip uninstall httplib2 -y + pip install -I httplib2==0.18.1 -i https://pypi.Python.org/simple + fi + else + pip install -I pyOpenSSL + pip install -I google-api-python-client==2.39.0 google-auth-httplib2==0.1.0 google-auth-oauthlib==0.5.0 -i https://pypi.Python.org/simple + pip install -I httplib2==0.18.1 -i https://pypi.Python.org/simple + fi + echo '正在安装脚本文件...' + + + echo "${VERSION}" > $serverPath/gdrive/version.pl + echo '安装完成' + else + echo '服务器连接不上谷歌云!安装失败!' + exit 1 + fi +} + +Uninstall_App() +{ + rm -rf $serverPath/gdrive + echo '卸载完成' +} + + +action=$1 +type=$2 + +echo $action $type +if [ "${action}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/gdrive/js/gdrive.js b/plugins/gdrive/js/gdrive.js new file mode 100644 index 000000000..029d7ff4f --- /dev/null +++ b/plugins/gdrive/js/gdrive.js @@ -0,0 +1,260 @@ + +function gdPost(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'gdrive', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function createDir(){ + layer.open({ + type: 1, + area: "400px", + title: "创建目录", + closeBtn: 1, + shift: 5, + shadeClose: false, + btn: ['确定','取消'], + content:'
          \ +

          \ + 目录名称:\ + \ +

          \ +
          ', + success:function(){ + $("input[name='newPath']").focus().keyup(function(e){ + if(e.keyCode == 13) $(".layui-layer-btn0").click(); + }); + }, + yes:function(index,layero){ + var name = $("input[name='newPath']").val(); + if(name == ''){ + layer.msg('目录名称不能为空!',{icon:2}); + return; + } + var parents = $("#myPath").val(); + var cur_file_id = $('#curPath').val(); + if (cur_file_id!=''){ + parents = cur_file_id; + } + + var dirname = name; + var loadT = layer.msg('正在创建目录['+dirname+']...',{icon:16,time:0,shade: [0.3, '#000']}); + gdPost('create_dir', {parents:parents,name:dirname}, function(data){ + layer.close(loadT); + var rdata = $.parseJSON(data.data); + if(rdata.status) { + showMsg(rdata.msg, function(){ + layer.close(index); + var file_id = $('#myPath').val(); + if (cur_file_id!=''){ + file_id = cur_file_id; + } + gdList(file_id); + } ,{icon:1}, 2000); + } else{ + layer.msg(rdata.msg,{icon:2}); + } + }); + } + }); +} + + +//设置API +function authApi(){ + + gdPost('conf', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + + // console.log(rdata); + // console.log(rdata.data.auth_url); + var apicon = ''; + if (rdata.status){ + + var html = ''; + html += ''; + + var loadOpen = layer.open({ + type: 1, + title: '已授权', + area: '240px', + content:'
          '+html+'
          ', + success: function(){ + $('#clear_auth').click(function(){ + gdPost('clear_auth', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + layer.close(loadOpen); + gdList(''); + },{icon:rdata.status?1:2},2000); + }); + }); + } + }); + return true; + + } else{ + apicon = '
          '+$("#check_api").html()+'
          '; + } + + var layer_auth = layer.open({ + type: 1, + area: "620px", + title: "Google Drive 授权", + closeBtn: 1, + shift: 5, + shadeClose: false, + content:apicon, + success:function(layero,index){ + // console.log(layero,index); + if (!rdata.status){ + $('.check_api .step_two_url').val(rdata.data['auth_url']); + $('.check_api .open_btlink').attr('href',rdata.data['auth_url']); + + $('.check_api .ico-copy').click(function(){ + copyPass(rdata.data['auth_url']); + }); + + $('.check_api .set_auth_btn').click(function(){ + + var url = $('.check_api .google_drive').val(); + if ( url == ''){ + layer.msg("验证URL不能为空",{icon:2}); + return; + } + // console.log(url); + gdPost('set_auth_url', {url:url}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var show_time = 2000; + if (!rdata.status){ + show_time = 10000; + } + + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(layer_auth); + gdList(''); + } + },{icon:rdata.status?1:2},show_time); + }); + }); + + + } + + } + }); + }); +} + +//计算当前目录偏移 +function upPathLeft(){ + var UlWidth = $(".place-input ul").width(); + var SpanPathWidth = $(".place-input").width() - 20; + var Ml = UlWidth - SpanPathWidth; + if(UlWidth > SpanPathWidth ){ + $(".place-input ul").css("left",-Ml) + } + else{ + $(".place-input ul").css("left",0) + } +} + +function getGDTime(a) { + return new Date(a).format("yyyy/MM/dd hh:mm:ss") +} + +function gdList(file_id){ + $('#curPath').val(file_id); + gdPost('get_list', {file_id:file_id}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + console.log(rdata); + if(rdata.status === false){ + showMsg(rdata.msg,function(){ + authApi(); + },{icon:2}); + return; + } + + var mlist = rdata.data; + var listBody = ''; + var listFiles = ''; + for(var i=0;i\'+mlist[i].name+'\ + -\ + -\ + 删除' + }else{ + listFiles += ''+mlist[i].name+'\ + '+toSize(mlist[i].size)+'\ + '+getGDTime(mlist[i].createdTime)+'\ + 下载 | 删除' + } + } + listBody += listFiles; + var pathLi = '
        • 根目录
        • '; + + if (mlist.length>0){ + $('#myPath').val(mlist[0]['parents'][0]); + } + + $(".upyunCon .place-input ul").html(pathLi); + $(".upyunlist .list-list").html(listBody); + + $('#backBtn').unbind().click(function() { + gdList(''); + }); + + $('.upyunCon .refreshBtn').unbind().click(function(){ + var file_id = $('#myPath').val(); + gdList(file_id); + }); + }); +} + + +//删除文件 +function deleteFile(name, is_dir){ + if (is_dir === false){ + safeMessage('删除文件','删除后将无法恢复,真的要删除['+name+']吗?',function(){ + var path = $("#myPath").val(); + var filename = name; + gdPost('delete_file', {filename:filename,path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + var file_id = $('#myPath').val(); + gdList(file_id); + },{icon:rdata.status?1:2},2000); + }); + }); + } else { + safeMessage('删除文件夹','删除后将无法恢复,真的要删除文件资源['+name+']吗?',function(){ + var path = $("#myPath").val(); + gdPost('delete_dir', {dir_name:name,path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + var file_id = $('#myPath').val(); + gdList(file_id); + },{icon:rdata.status?1:2},2000); + }); + }); + } +} \ No newline at end of file diff --git a/plugins/gdrive/t/test.py b/plugins/gdrive/t/test.py new file mode 100644 index 000000000..20dff5773 --- /dev/null +++ b/plugins/gdrive/t/test.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# coding: utf-8 + +# python3 plugins/gdrive/t/test.py +# https://console.cloud.google.com/apis/credentials/consent?project=plated-inn-369901 + +# https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=226011946234-d3e1vashgag64utjedu1ljt9u39ncrpq.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fdrive.aapanel.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file&state=I2da4aZUwqgmikrgrqAwwSjyoEHVs1&access_type=offline&prompt=consent&include_granted_scopes=false + + +# https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/08125e6b-6502-4ac9-9548-ad682f00848d/objectId/62b1d655-9828-47ed-be99-65eb18c3a929/isMSAApp~/false/defaultBlade/Overview/appSignInAudience/AzureADandPersonalMicrosoftAccount/servicePrincipalCreated~/true + +# 0WA8Q~sZkZFZKv50ryP4ux~.fpVtbHw7BuTZmbQB +# client_id:d9878fac-8526-4ff6-8036-e1c92dd9dd80 + +# 08125e6b-6502-4ac9-9548-ad682f00848d + + +# /drive/v2internal/files?openDrive=false&reason=102&syncType=0&errorRecovery=false&q=trashed = false and '1-z6gUXseXmlxmntvkLzOe_tYFT5tdhM9' in parents&fields=kind,nextPageToken,items(kind,modifiedDate,hasVisitorPermissions,containsUnsubscribedChildren,modifiedByMeDate,lastViewedByMeDate,alternateLink,fileSize,owners(kind,permissionId,emailAddressFromAccount,id),lastModifyingUser(kind,permissionId,emailAddressFromAccount,id),customerId,ancestorHasAugmentedPermissions,hasThumbnail,thumbnailVersion,title,id,resourceKey,abuseIsAppealable,abuseNoticeReason,shared,accessRequestsCount,sharedWithMeDate,userPermission(role),explicitlyTrashed,mimeType,quotaBytesUsed,copyable,subscribed,folderColor,hasChildFolders,fileExtension,primarySyncParentId,sharingUser(kind,permissionId,emailAddressFromAccount,id),flaggedForAbuse,folderFeatures,spaces,sourceAppId,recency,recencyReason,version,actionItems,teamDriveId,hasAugmentedPermissions,createdDate,primaryDomainName,organizationDisplayName,passivelySubscribed,trashingUser(kind,permissionId,emailAddressFromAccount,id),trashedDate,parents(id),capabilities(canMoveItemIntoTeamDrive,canUntrash,canModifyContentRestriction,canMoveItemWithinTeamDrive,canMoveItemOutOfTeamDrive,canDeleteChildren,canTrashChildren,canRequestApproval,canReadCategoryMetadata,canEditCategoryMetadata,canAddMyDriveParent,canRemoveMyDriveParent,canShareChildFiles,canShareChildFolders,canRead,canMoveItemWithinDrive,canMoveChildrenWithinDrive,canAddFolderFromAnotherDrive,canChangeSecurityUpdateEnabled,canBlockOwner,canReportSpamOrAbuse,canCopy,canDownload,canEdit,canAddChildren,canDelete,canRemoveChildren,canShare,canTrash,canRename,canReadTeamDrive,canMoveTeamDriveItem),contentRestrictions(readOnly),approvalMetadata(approvalVersion,approvalSummaries,hasIncomingApproval),shortcutDetails(targetId,targetMimeType,targetLookupStatus,targetFile,canRequestAccessToTarget),spamMetadata(markedAsSpamDate,inSpamView),labels(starred,trashed,restricted,viewed)),incompleteSearch&appDataFilter=NO_APP_DATA&spaces=drive&maxResults=200&supportsTeamDrives=true&includeItemsFromAllDrives=true&corpora=default&orderBy=folder,title_natural asc&retryCount=0&key=AIzaSyD_InbmSFufIEps5UAt2NmB_3LvBH3Sz_8 HTTP/1.1 + +# http://localhost/?code=M.C106_BAY.2.3e12c859-6107-0c5b-9ef4-14b3fb8269ba&state=JzHdzHXmA7x6zl7Be6cJ6uOlf9Bg69 + + +# python3 /www/mdserver-web/plugins/gdrive/index.py site bbs.midoks.icu 3 +# python3 /www/mdserver-web/plugins/gdrive/index.py database t1 3 +# python3 /www/server/mdserver-web/plugins/msonedrive/index.py path +# /Users/midoks/Desktop/dev/python 3 + + +import sys +import io +import os +import time +import re +import json + + +sys.path.append(os.getcwd() + "/class/core") +import mw + + +def getPluginName(): + return 'gdrive' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +# print(getPluginDir()) +sys.path.append(getPluginDir() + "/class") +from gdriveclient import gdriveclient + + +gd = gdriveclient(getPluginDir(), getServerDir()) +gd.setDebug(True) +# sign_in_url, state = gd.get_sign_in_url() +# print(sign_in_url) + +# url = 'https://localhost/?state=GH2YZ1VeytzVB1BqJJpQZIBk2GGAub&code=4/0Adeu5BXnD2dQvXx8Sg0WPn1XiMpihcRBNaG1yaFKIo86gUiG7q65KU1MaNCxrj_f2bjkwQ&scope=https://www.googleapis.com/auth/drive.file' +# t = gd.set_auth_url(url) +# print(t) + +# def set_auth_url(url): +# try: +# if url.startswith("http://"): +# url = url.replace("http://", "https://") +# token = msodc.get_token_from_authorized_url( +# authorized_url=url) +# msodc.store_token(token) +# msodc.store_user() +# return mw.returnJson(True, "授权成功!") +# except Exception as e: +# print(e) +# return mw.returnJson(False, "授权失败2!:" + str(e)) +# return mw.returnJson(False, "授权失败!:" + str(e)) + +# url = 'http://localhost/?code=M.C106_BAY.2.310112f3-a158-c400-9667-d158cbd1de6c&state=jEJz0ucR9bpZYD9PGxp2GgRDotrzO6' +# token = set_auth_url(url) +# print(token) + +# token = msodc.get_token() +# print("token:", token) + +# t = gd.get_list('') +# print(t) + +# t = gd.get_id_list('1u7LjXGj1KoN-ltAdTRaib7IZJpsEnPdz') +# print(t) + +# t = gd.create_folder('backup_demo') +# print(t) + +# t = msodc.delete_object('backup') +# print(t) + + +# t = gd.upload_file('web_t1.cn_20230830_134549.tar.gz', 'site') +# print(t) +# print(gd.error_msg) + +# /Users/midoks/Desktop/mwdev/server/mdserver-web/paramiko.log +# backup/site/paramiko.log +# |-正在上传到 backup/site/paramiko.log... +# True + + +t = gd.upload_file( + 'db_zzzvps_20230830_210727.sql.gz', 'datebase') +print(t) diff --git a/plugins/gitea/hook/commit.tpl b/plugins/gitea/hook/commit.tpl new file mode 100755 index 000000000..727a2b9cc --- /dev/null +++ b/plugins/gitea/hook/commit.tpl @@ -0,0 +1,60 @@ +#!/bin/bash + +echo `date` + +GITADDR="{$GITROOTURL}/{$USERNAME}/{$PROJECT}.git" +GIT_SDIR="{$CODE_DIR}" + +GIT_USER_DIR="${GIT_SDIR}/{$USERNAME}" +GIT_PROJECT_DIR="${GIT_USER_DIR}/{$PROJECT}" + +if [ ! -d $GIT_USER_DIR ];then + mkdir -p $GIT_USER_DIR + chown -R www:www $GIT_USER_DIR +fi + +git config --global credential.helper store +git config --global pull.rebase false + +if [ ! -d $GIT_PROJECT_DIR ];then + git config --global --add safe.directory $GIT_PROJECT_DIR +fi + +# echo $GIT_PROJECT_DIR +if [ ! -d $GIT_PROJECT_DIR ];then + mkdir -p $GIT_USER_DIR && cd $GIT_USER_DIR + git clone $GITADDR +fi + +unset GIT_DIR + + + +# cd $GIT_PROJECT_DIR && git pull +cd $GIT_PROJECT_DIR && sudo git pull + +# func 2 +# cd $GIT_PROJECT_DIR && env -i git pull origin master + + +#更新的目的地址 +WEB_PATH={$WEB_ROOT}/{$USERNAME}/{$PROJECT} + +if [ ! -d $WEB_PATH ];then + mkdir -p $WEB_PATH + rsync -vauP --delete --exclude=".*" $GIT_PROJECT_DIR/ $WEB_PATH +else + if [ -f $GIT_PROJECT_DIR/exclude.list ];then + rsync -vauP --exclude-from="$GIT_PROJECT_DIR/exclude.list" $GIT_PROJECT_DIR/ $WEB_PATH + else + rsync -vauP --exclude=".*" $GIT_PROJECT_DIR/ $WEB_PATH + fi +fi + +sysName=`uname` +if [ $sysName == 'Darwin' ]; then + USER=$(who | sed -n "2,1p" |awk '{print $1}') + chown -R $USER:staff $WEB_PATH +else + chown -R www:www $WEB_PATH +fi \ No newline at end of file diff --git a/plugins/gitea/hook/commit.tpl.ssh b/plugins/gitea/hook/commit.tpl.ssh new file mode 100755 index 000000000..a122b5c49 --- /dev/null +++ b/plugins/gitea/hook/commit.tpl.ssh @@ -0,0 +1,40 @@ +#!/bin/bash + +echo `date` + +GITADDR="{$GITROOTURL}/{$USERNAME}/{$PROJECT}" +GIT_SDIR="{$CODE_DIR}" + +GIT_USER_DIR="${GIT_SDIR}/{$USERNAME}" +GIT_PROJECT_DIR="${GIT_USER_DIR}/{$PROJECT}" + +# echo $GIT_PROJECT_DIR +if [ ! -d $GIT_PROJECT_DIR ];then + mkdir -p $GIT_USER_DIR && cd $GIT_USER_DIR + git clone $GITADDR +fi + +unset GIT_DIR + +git config pull.rebase true +git config credential.helper store + +cd $GIT_PROJECT_DIR && git pull + +# func 2 +# cd $GIT_PROJECT_DIR && env -i git pull origin master + + + +WEB_PATH={$WEB_ROOT}/{$USERNAME}/{$PROJECT} +mkdir -p $WEB_PATH + +rsync -vauP --delete --exclude=".*" $GIT_PROJECT_DIR/ $WEB_PATH + +sysName=`uname` +if [ $sysName == 'Darwin' ]; then + USER=$(who | sed -n "2,1p" |awk '{print $1}') + chown -R $USER:staff $WEB_PATH +else + chown -R www:www $WEB_PATH +fi \ No newline at end of file diff --git a/plugins/gitea/hook/post-receive.tpl b/plugins/gitea/hook/post-receive.tpl new file mode 100755 index 000000000..0c747ce2f --- /dev/null +++ b/plugins/gitea/hook/post-receive.tpl @@ -0,0 +1,3 @@ +#!/bin/bash + +sh -x {$PATH}/commit 2>{$PATH}/sh.log \ No newline at end of file diff --git a/plugins/gitea/hook/self_hook.tpl b/plugins/gitea/hook/self_hook.tpl new file mode 100644 index 000000000..266f053e8 --- /dev/null +++ b/plugins/gitea/hook/self_hook.tpl @@ -0,0 +1,14 @@ +#!/bin/bash + +H_DIR={$HOOK_DIR} +HL_DIR={$HOOK_LOGS_DIR} + + +SH_LIST=`cd ${H_DIR} && ls | grep ".sh$"` + +for sh_f in $SH_LIST; do + ABS_FILE=${H_DIR}/${sh_f} + ABS_LOGS=${HL_DIR}/${sh_f}.log + echo "sh ${ABS_FILE} 2>${ABS_LOGS}" + sh -x ${ABS_FILE} 2>${ABS_LOGS} +done \ No newline at end of file diff --git a/plugins/gitea/ico.png b/plugins/gitea/ico.png new file mode 100644 index 000000000..5dacd7735 Binary files /dev/null and b/plugins/gitea/ico.png differ diff --git a/plugins/gitea/index.html b/plugins/gitea/index.html new file mode 100755 index 000000000..c7e762b08 --- /dev/null +++ b/plugins/gitea/index.html @@ -0,0 +1,33 @@ + +
          +
          +
          +

          服务

          +

          自启动

          +

          手动编辑

          +

          配置文件

          +

          配置修改

          +

          用户列表

          +

          项目列表

          + + +

          使用说明

          +
          +
          +
          +
          +
          +
          + \ No newline at end of file diff --git a/plugins/gitea/index.py b/plugins/gitea/index.py new file mode 100755 index 000000000..b263398ac --- /dev/null +++ b/plugins/gitea/index.py @@ -0,0 +1,1154 @@ +# coding: utf-8 + + +import time +import os +import sys +import re +import subprocess + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'gitea' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':', 1) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getInitdConfTpl(): + path = getPluginDir() + "/init.d/gitea.tpl" + return path + + +def getInitdConf(): + path = getServerDir() + "/init.d/gitea" + return path + + +def getConf(): + path = getServerDir() + "/custom/conf/app.ini" + + if not os.path.exists(path): + return mw.returnJson(False, "请先安装初始化!
          默认地址:http://" + mw.getLocalIp() + ":3000") + return path + + +def getConfTpl(): + path = getPluginDir() + "/conf/app.ini" + return path + + +def status(): + data = mw.execShell( + "ps -ef|grep " + getPluginName() + " |grep -v grep | grep -v python | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + + +def getHomeDir(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + else: + return 'www' + + +def getRunUser(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + else: + return 'www' + +__SR = '''#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export USER=%s +export HOME=%s && ''' % ( getRunUser(), getHomeDir()) + + +def contentReplace(content): + + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$RUN_USER}', getRunUser()) + content = content.replace('{$HOME_DIR}', getHomeDir()) + + return content + + +def initDreplace(): + + file_tpl = getInitdConfTpl() + service_path = mw.getServerDir() + + git_dir = mw.getServerDir() + '/git' + if not os.path.exists(git_dir): + mw.execShell('mkdir -p ' + git_dir) + mw.execShell('chown -R www:www ' + git_dir) + + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/gitea.service' + systemServiceTpl = getPluginDir() + '/init.d/gitea.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + log_path = getServerDir() + '/log' + if not os.path.exists(log_path): + os.mkdir(log_path) + + return file_bin + + +def getRootUrl(): + content = mw.readFile(getConf()) + rep = r'ROOT_URL\s*=\s*(.*)' + tmp = re.search(rep, content) + if tmp: + return tmp.groups()[0] + + rep = r'EXTERNAL_URL\s*=\s*(.*)' + tmp = re.search(rep, content) + if tmp: + return tmp.groups()[0] + return '' + + +def getSshPort(): + content = mw.readFile(getConf()) + rep = r'SSH_PORT\s*=\s*(.*)' + tmp = re.search(rep, content) + if not tmp: + return '' + return tmp.groups()[0] + + +def getHttpPort(): + content = mw.readFile(getConf()) + rep = r'HTTP_PORT\s*=\s*(.*)' + tmp = re.search(rep, content) + if not tmp: + return '' + return tmp.groups()[0] + + +def getRootPath(): + content = mw.readFile(getConf()) + rep = r'ROOT\s*=\s*(.*)' + tmp = re.search(rep, content) + if not tmp: + return '' + return tmp.groups()[0] + + +def getDbConfValue(): + conf = getConf() + if not os.path.exists(conf): + return {} + + content = mw.readFile(conf) + rep_scope = r"\[database\](.*?)\[" + tmp = re.findall(rep_scope, content, re.S) + + rep = '(\\w*)\\s*=\\s*(.*)' + tmp = re.findall(rep, tmp[0]) + r = {} + for x in range(len(tmp)): + k = tmp[x][0] + v = tmp[x][1] + r[k] = v + return r + + +def pMysqlDb(conf): + host = conf['HOST'].split(':') + # pymysql + db = mw.getMyORM() + # MySQLdb | + # db = mw.getMyORMDb() + + db.setPort(int(host[1])) + db.setUser(conf['USER']) + + if 'PASSWD' in conf: + db.setPwd(conf['PASSWD']) + else: + db.setPwd(conf['PASSWORD']) + + db.setDbName(conf['NAME']) + # db.setSocket(getSocketFile()) + db.setCharset("utf8") + return db + + +def pSqliteDb(conf): + # print(conf) + import db + psDb = db.Sql() + + # 默认 + gsdir = getServerDir() + '/data' + dbname = 'gitea' + if conf['PATH'][0] == '/': + # 绝对路径 + pass + else: + path = conf['PATH'].split('/') + gsdir = getServerDir() + '/' + path[0] + dbname = path[1].split('.')[0] + + # print(gsdir, dbname) + psDb.dbPos(gsdir, dbname) + return psDb + + +def getGiteaDbType(conf): + + if 'DB_TYPE' in conf: + return conf['DB_TYPE'] + + if 'TYPE' in conf: + return conf['TYPE'] + + return 'NONE' + + +def pQuery(sql): + conf = getDbConfValue() + gtype = getGiteaDbType(conf) + if gtype == 'sqlite3': + db = pSqliteDb(conf) + data = db.query(sql, []).fetchall() + return data + elif gtype == 'mysql': + db = pMysqlDb(conf) + return db.query(sql) + + print("仅支持mysql|sqlite3配置") + exit(0) + + +def isSqlError(mysqlMsg): + # 检测数据库执行错误 + _mysqlMsg = str(mysqlMsg) + # print _mysqlMsg + if "MySQLdb" in _mysqlMsg: + return mw.returnData(False, 'MySQLdb组件缺失!
          进入SSH命令行输入: pip install mysql-python') + if "2002," in _mysqlMsg: + return mw.returnData(False, '数据库连接失败,请检查数据库服务是否启动!') + if "using password:" in _mysqlMsg: + return mw.returnData(False, '数据库管理密码错误!') + if "Connection refused" in _mysqlMsg: + return mw.returnData(False, '数据库连接失败,请检查数据库服务是否启动!') + if "1133," in _mysqlMsg: + return mw.returnData(False, '数据库用户不存在!') + if "1007," in _mysqlMsg: + return mw.returnData(False, '数据库已经存在!') + if "1044," in _mysqlMsg: + return mw.returnData(False, mysqlMsg[1]) + if "2003," in _mysqlMsg: + return mw.returnData(False, "Can't connect to MySQL server on '127.0.0.1' (61)") + return mw.returnData(True, 'OK') + + +def appOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(__SR + file + ' ' + method) + if data[1] == '': + return 'ok' + return data[0] + + +def start(): + return appOp('start') + + +def stop(): + return appOp('stop') + + +def restart(): + return appOp('restart') + + +def reload(): + return appOp('reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status gitea | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable gitea') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable gitea') + return 'ok' + + +def runLog(): + log_path = getServerDir() + '/log/gitea.log' + return log_path + + +def postReceiveLog(): + log_path = getServerDir() + '/log/hooks/post-receive.log' + return log_path + + +def getGogsConf(): + conf = getConf() + if not os.path.exists(conf): + return mw.returnJson(False, "请先安装初始化!
          默认地址:http://" + mw.getLocalIp() + ":3000") + + gets = [ + {'name': 'DOMAIN', 'type': -1, 'ps': '服务器域名'}, + {'name': 'ROOT_URL', 'type': -1, 'ps': '公开的完整URL路径'}, + {'name': 'HTTP_ADDR', 'type': -1, 'ps': '应用HTTP监听地址'}, + {'name': 'HTTP_PORT', 'type': -1, 'ps': '应用 HTTP 监听端口号'}, + + {'name': 'START_SSH_SERVER', 'type': 2, 'ps': '启动内置SSH服务器'}, + {'name': 'SSH_PORT', 'type': -1, 'ps': 'SSH 端口号'}, + + {'name': 'REQUIRE_SIGNIN_VIEW', 'type': 2, 'ps': '强制登录浏览'}, + {'name': 'ENABLE_CAPTCHA', 'type': 2, 'ps': '启用验证码服务'}, + {'name': 'DISABLE_REGISTRATION', 'type': 2, 'ps': '禁止注册,只能由管理员创建帐号'}, + {'name': 'ENABLE_NOTIFY_MAIL', 'type': 2, 'ps': '是否开启邮件通知'}, + + {'name': 'FORCE_PRIVATE', 'type': 2, 'ps': '强制要求所有新建的仓库都是私有'}, + + {'name': 'SHOW_FOOTER_BRANDING', 'type': 2, 'ps': 'Gogs推广信息'}, + {'name': 'SHOW_FOOTER_VERSION', 'type': 2, 'ps': 'Gogs版本信息'}, + {'name': 'SHOW_FOOTER_TEMPLATE_LOAD_TIME', 'type': 2, 'ps': 'Gogs模板加载时间'}, + ] + conf = mw.readFile(conf) + result = [] + + for g in gets: + rep = g['name'] + '\\s*=\\s*(.*)' + tmp = re.search(rep, conf) + if not tmp: + continue + g['value'] = tmp.groups()[0] + result.append(g) + return mw.returnJson(True, 'OK', result) + + +def submitGogsConf(): + gets = ['DOMAIN', + 'ROOT_URL', + 'HTTP_ADDR', + 'HTTP_PORT', + 'START_SSH_SERVER', + 'SSH_PORT', + 'REQUIRE_SIGNIN_VIEW', + 'FORCE_PRIVATE', + 'ENABLE_CAPTCHA', + 'DISABLE_REGISTRATION', + 'ENABLE_NOTIFY_MAIL', + 'SHOW_FOOTER_BRANDING', + 'SHOW_FOOTER_VERSION', + 'SHOW_FOOTER_TEMPLATE_LOAD_TIME'] + args = getArgs() + filename = getConf() + conf = mw.readFile(filename) + for g in gets: + if g in args: + rep = g + '\\s*=\\s*(.*)' + val = g + ' = ' + args[g] + conf = re.sub(rep, val, conf) + mw.writeFile(filename, conf) + restart() + return mw.returnJson(True, '设置成功') + + +def gogsEditTpl(): + data = {} + data['post_receive'] = getPluginDir() + '/hook/post-receive.tpl' + data['commit'] = getPluginDir() + '/hook/commit.tpl' + return mw.getJson(data) + + +def userList(): + + conf = getConf() + if not os.path.exists(conf): + return mw.returnJson(False, "请先安装初始化!
          默认地址:http://" + mw.getLocalIp() + ":3000") + + conf = getDbConfValue() + gtype = getGiteaDbType(conf) + if gtype != 'mysql': + return mw.returnJson(False, "仅支持mysql数据操作!") + + import math + args = getArgs() + + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + search = '' + if 'search' in args: + search = args['search'] + + user_where1 = '' + user_where2 = '' + if search != '': + user_where1 = ' where name like "%' + search + '%"' + user_where2 = ' where name like "%' + search + '%"' + + data = {} + + data['root_url'] = getRootUrl() + + start = (page - 1) * page_size + list_count = pQuery('select count(id) as num from user' + user_where1) + count = list_count[0]["num"] + list_data = pQuery( + 'select id,name,email from user ' + user_where2 + ' order by id desc limit ' + str(start) + ',' + str(page_size)) + data['list'] = mw.getPage({'count': count, 'p': page, + 'row': page_size, 'tojs': 'gogsUserList'}) + data['page'] = page + data['page_size'] = page_size + data['page_count'] = int(math.ceil(count / page_size)) + data['data'] = list_data + return mw.returnJson(True, 'OK', data) + + +def checkRepoListIsHasScript(data): + path = getRootPath() + for x in range(len(data)): + name = data[x]['name'] + '/' + data[x]['repo'] + '.git' + path_tmp = path + '/' + name + '/custom_hooks/commit' + if os.path.exists(path_tmp): + data[x]['has_hook'] = True + else: + data[x]['has_hook'] = False + return data + + +def repoList(): + + conf = getConf() + if not os.path.exists(conf): + return mw.returnJson(False, "请先安装初始化!
          默认地址:http://" + mw.getLocalIp() + ":3000") + + conf = getDbConfValue() + gtype = getGiteaDbType(conf) + if gtype != 'mysql': + return mw.returnJson(False, "仅支持mysql数据操作!") + + import math + args = getArgs() + + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + search = '' + if 'search' in args: + search = args['search'] + + data = {} + + data['root_url'] = getRootUrl() + + repo_where1 = '' + repo_where2 = '' + if search != '': + repo_where1 = ' where name like "%' + search + '%"' + repo_where2 = ' where r.name like "%' + search + '%"' + + start = (page - 1) * page_size + list_count = pQuery( + 'select count(id) as num from repository' + repo_where1) + count = list_count[0]["num"] + sql = 'select r.id,r.owner_id,r.name as repo, u.name from repository r left join user u on r.owner_id=u.id ' + repo_where2 + ' order by r.id desc limit ' + \ + str(start) + ',' + str(page_size) + # print(sql) + list_data = pQuery(sql) + # print(list_data) + list_data = checkRepoListIsHasScript(list_data) + + data['list'] = mw.getPage({'count': count, 'p': page, + 'row': page_size, 'tojs': 'gogsRepoListPage'}) + data['page'] = page + data['page_size'] = page_size + data['page_count'] = int(math.ceil(count / page_size)) + data['data'] = list_data + return mw.returnJson(True, 'OK', data) + + +def getAllUserProject(user, search=''): + path = getRootPath() + '/' + user + dlist = [] + if os.path.exists(path): + for filename in os.listdir(path): + tmp = {} + filePath = path + '/' + filename + if os.path.isdir(filePath): + if search == '': + tmp['name'] = filename.replace('.git', '') + dlist.append(tmp) + else: + if filename.find(search) != -1: + tmp['name'] = filename.replace('.git', '') + dlist.append(tmp) + return dlist + + +def checkProjectListIsHasScript(user, data): + path = getRootPath() + '/' + user + for x in range(len(data)): + name = data[x]['name'] + '.git' + path_tmp = path + '/' + name + '/hooks/post-receive.d/post-receive' + if os.path.exists(path_tmp): + data[x]['has_hook'] = True + else: + data[x]['has_hook'] = False + return data + + +def userProjectList(): + import math + args = getArgs() + # print args + + page = 1 + page_size = 5 + search = '' + + if not 'name' in args: + return mw.returnJson(False, '缺少参数name') + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + data = {} + + ulist = getAllUserProject(args['name']) + dlist_sum = len(ulist) + + start = (page - 1) * page_size + ret_data = ulist[start:start + page_size] + ret_data = checkProjectListIsHasScript(args['name'], ret_data) + + data['root_url'] = getRootUrl() + data['data'] = ret_data + data['args'] = args + data['list'] = mw.getPage( + {'count': dlist_sum, 'p': page, 'row': page_size, 'tojs': 'userProjectListPost'}) + + return mw.returnJson(True, 'OK', data) + + +def projectScriptEdit(): + args = getArgs() + + if not 'user' in args: + return mw.returnJson(True, 'username missing') + + if not 'name' in args: + return mw.returnJson(True, 'project name missing') + + user = args['user'] + name = args['name'] + '.git' + post_receive = getRootPath() + '/' + user + '/' + name + \ + '/custom_hooks/commit' + if os.path.exists(post_receive): + return mw.returnJson(True, 'OK', {'path': post_receive}) + else: + return mw.returnJson(False, 'file does not exist') + + +def projectScriptLoad(): + args = getArgs() + data = checkArgs(args, ['user', 'name']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + + path = getRootPath() + '/' + user + '/' + name + post_receive_tpl = getPluginDir() + '/hook/post-receive.tpl' + post_receive = path + '/hooks/post-receive.d/post-receive' + + if not os.path.exists(path + '/custom_hooks'): + mw.execShell('mkdir -p ' + path + '/custom_hooks') + mw.execShell('chown -R www:www ' + path + '/custom_hooks') + + pct_content = mw.readFile(post_receive_tpl) + pct_content = pct_content.replace('{$PATH}', path + '/custom_hooks') + mw.writeFile(post_receive, pct_content) + mw.execShell('chmod 777 ' + post_receive) + mw.execShell('chown -R www:www ' + post_receive) + + commit_tpl = getPluginDir() + '/hook/commit.tpl' + commit = path + '/custom_hooks/commit' + + codeDir = mw.getFatherDir() + '/git' + + cc_content = mw.readFile(commit_tpl) + + gitPath = getRootPath() + cc_content = cc_content.replace('{$GITROOTURL}', gitPath) + cc_content = cc_content.replace('{$CODE_DIR}', codeDir) + cc_content = cc_content.replace('{$USERNAME}', user) + cc_content = cc_content.replace('{$PROJECT}', args['name']) + cc_content = cc_content.replace('{$WEB_ROOT}', mw.getWwwDir()) + mw.writeFile(commit, cc_content) + mw.execShell('chmod 777 ' + commit) + mw.execShell('chown -R www:www ' + commit) + + return 'ok' + + +def projectScriptUnload(): + args = getArgs() + data = checkArgs(args, ['user', 'name']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + + post_receive = getRootPath() + '/' + user + '/' + name + \ + '/hooks/post-receive.d/post-receive' + mw.execShell('rm -f ' + post_receive) + + commit = getRootPath() + '/' + user + '/' + name + \ + '/custom_hooks/commit' + mw.execShell('rm -f ' + commit) + return 'ok' + + +def projectScriptDebug(): + args = getArgs() + data = checkArgs(args, ['user', 'name']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + commit_log = getRootPath() + '/' + user + '/' + name + \ + '/custom_hooks/sh.log' + + data = {} + if os.path.exists(commit_log): + data['status'] = True + data['path'] = commit_log + else: + data['status'] = False + data['msg'] = '没有日志文件' + + return mw.getJson(data) + + +def projectScriptRun(): + args = getArgs() + data = checkArgs(args, ['user', 'name']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + + path = getRootPath() + '/' + user + '/' + name + commit_sh = path + '/custom_hooks/commit' + commit_log = path + '/custom_hooks/sh.log' + script_run = 'sh -x ' + commit_sh + ' 2>' + commit_log + + if not os.path.exists(commit_sh): + return mw.returnJson(False, '脚本文件不存在!') + + repo_dir = mw.getServerDir()+'/git/'+ args['name'] + + # mw.execShell(script_run) + subprocess.Popen(script_run, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + subprocess.Popen('chown -R www:www ' + repo_dir, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + return mw.returnJson(True, '脚本文件执行成功,观察日志!') + + +def projectScriptSelf(): + args = getArgs() + data = checkArgs(args, ['user', 'name']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + + self_path = custom_hooks + '/self' + if not os.path.exists(self_path): + os.mkdir(self_path) + mw.execShell("chown -R www:www " + self_path) + + self_logs_path = custom_hooks + '/self_logs' + if not os.path.exists(self_logs_path): + os.mkdir(self_logs_path) + mw.execShell("chown -R www:www " + self_logs_path) + + self_hook_file = custom_hooks + '/self_hook.sh' + self_hook_exist = False + if os.path.exists(self_hook_file): + self_hook_exist = True + + dlist = [] + if os.path.exists(self_path): + for filename in os.listdir(self_path): + tmp = {} + filePath = self_path + '/' + filename + if os.path.isfile(filePath): + tmp['path'] = filePath + tmp['name'] = os.path.basename(filePath) + tmp['is_hidden'] = False + if tmp['name'].endswith('.txt'): + tmp['is_hidden'] = True + + dlist.append(tmp) + + dlist_sum = len(dlist) + # print(dlist) + rdata = {} + rdata['data'] = dlist + rdata['self_hook'] = self_hook_exist + rdata['list'] = mw.getPage( + {'count': dlist_sum, 'p': 1, 'row': 100, 'tojs': 'self_page'}) + + return mw.returnJson(True, 'ok', rdata) + + +def projectScriptSelf_Create(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + + self_path = path = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks/self' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + abs_file = self_path + '/' + file + '.sh' + if os.path.exists(abs_file): + return mw.returnJson(False, '脚本已经存在!') + + mw.writeFile(abs_file, "#!/bin/bash\necho `date +'%Y-%m-%d %H:%M:%S'`\n") + mw.execShell('chown -R www:www ' + abs_file) + rdata = {} + rdata['abs_file'] = abs_file + return mw.returnJson(True, '创建文件成功!', rdata) + + +def projectScriptSelf_Del(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + self_path = custom_hooks + '/self' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + abs_file = self_path + '/' + file + # print(abs_file) + if not os.path.exists(abs_file): + return mw.returnJson(False, '脚本已经删除!') + + os.remove(abs_file) + + # 日志也删除 + log_file = custom_hooks + '/self_logs/' + file + '.log' + if os.path.exists(log_file): + os.remove(log_file) + + return mw.returnJson(True, '脚本删除成功!') + + +def projectScriptSelf_Logs(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + + self_path = path = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks/self_logs' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + logs_file = self_path + '/' + file + '.log' + if os.path.exists(logs_file): + rdata = {} + rdata['path'] = logs_file + return mw.returnJson(True, 'ok', rdata) + + return mw.returnJson(False, '日志不存在!') + + +def projectScriptSelf_Run(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + self_path = custom_hooks + '/self/' + file + self_logs_path = custom_hooks + '/self_logs/' + file + '.log' + + shell = "sh -x " + self_path + " 2>" + self_logs_path + ' &' + mw.execShell(shell) + mw.execShell("chown -R www:www " + self_logs_path) + return mw.returnJson(True, '执行成功!') + + +def projectScriptSelf_Rename(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'o_file', 'n_file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + o_file = args['o_file'] + n_file = args['n_file'] + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + self_path = custom_hooks + '/self' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + o_file_abs = self_path + '/' + o_file + '.sh' + if not os.path.exists(o_file_abs): + return mw.returnJson(False, '原文件已经不存在了!') + + n_file_abs = self_path + '/' + n_file + '.sh' + + os.rename(o_file_abs, n_file_abs) + + # 日志也删除 + log_file = custom_hooks + '/self_logs/' + o_file + '.sh.log' + if os.path.exists(log_file): + os.remove(log_file) + + return mw.returnJson(True, '重命名成功!') + + +def projectScriptSelf_Enable(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'enable']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + enable = args['enable'] + + custom_path = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + + # 替换commit配置 + commit_path = custom_path + '/commit' + note = '#Gogs Script Don`t Remove and Change' + + self_file = custom_path + '/self_hook.sh' + self_hook_tpl = getPluginDir() + '/hook/self_hook.tpl' + + if enable == '1': + content = mw.readFile(self_hook_tpl) + content = content.replace('{$HOOK_DIR}', custom_path + '/self') + content = content.replace( + '{$HOOK_LOGS_DIR}', custom_path + '/self_logs') + mw.writeFile(self_file, content) + mw.execShell("chmod 777 " + self_file) + mw.execShell("chown -R www:www " + self_file) + + commit_content = mw.readFile(commit_path) + commit_content += "\n\n" + "bash " + self_file + " " + note + mw.writeFile(commit_path, commit_content) + + return mw.returnJson(True, '开启成功!') + else: + commit_content = mw.readFile(commit_path) + rep = ".*" + note + commit_content = re.sub(rep, '', commit_content, re.M) + commit_content = commit_content.strip() + mw.writeFile(commit_path, commit_content) + if os.path.exists(self_file): + os.remove(self_file) + return mw.returnJson(True, '关闭成功!') + + +def projectScriptSelf_Status(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file', 'status']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + status = args['status'] + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + self_path = custom_hooks + '/self' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + # 日志也删除 + log_file = custom_hooks + '/self_logs/' + file + '.log' + if os.path.exists(log_file): + os.remove(log_file) + + if status == '1': + file_abs = self_path + '/' + file + file_text_abs = self_path + '/' + file + '.txt' + os.rename(file_abs, file_text_abs) + return mw.returnJson(True, '开始禁用成功!') + else: + file_abs = self_path + '/' + file.strip('.txt') + file_text_abs = self_path + '/' + file + os.rename(file_text_abs, file_abs) + return mw.returnJson(True, '开始使用成功!') + + return mw.returnJson(True, '禁用成功!') + + +def getRsaPublic(): + path = getHomeDir() + path += '/.ssh/id_rsa.pub' + + content = mw.readFile(path) + + data = {} + data['mw'] = content + return mw.getJson(data) + + +def getTotalStatistics(): + st = status() + data = {} + if st.strip() == 'start': + list_count = pQuery('select count(id) as num from repository') + count = list_count[0]["num"] + data['status'] = True + data['count'] = count + data['ver'] = mw.readFile(getServerDir() + '/version.pl').strip() + return mw.returnJson(True, 'ok', data) + + data['status'] = False + data['count'] = 0 + return mw.returnJson(False, 'fail', data) + + +def uninstallPreInspection(): + repo_dir = getServerDir() + "/data/gitea-repositories" + if not os.path.exists(repo_dir): + return 'ok' + dir_list = os.listdir(repo_dir) + if len(dir_list) > 0: + return "有项目数据!请手动删除Gitea
          rm -rf {}".format(getServerDir()) + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'run_log': + print(runLog()) + elif func == 'post_receive_log': + print(postReceiveLog()) + elif func == 'conf': + print(getConf()) + elif func == 'init_conf': + print(getInitdConf()) + elif func == 'get_gogs_conf': + print(getGogsConf()) + elif func == 'submit_gogs_conf': + print(submitGogsConf()) + elif func == 'gogs_edit_tpl': + print(gogsEditTpl()) + elif func == 'user_list': + print(userList()) + elif func == 'repo_list': + print(repoList()) + elif func == 'user_project_list': + print(userProjectList()) + elif func == 'project_script_edit': + print(projectScriptEdit()) + elif func == 'project_script_load': + print(projectScriptLoad()) + elif func == 'project_script_unload': + print(projectScriptUnload()) + elif func == 'project_script_debug': + print(projectScriptDebug()) + elif func == 'project_script_run': + print(projectScriptRun()) + elif func == 'project_script_self': + print(projectScriptSelf()) + elif func == 'project_script_self_create': + print(projectScriptSelf_Create()) + elif func == 'project_script_self_del': + print(projectScriptSelf_Del()) + elif func == 'project_script_self_logs': + print(projectScriptSelf_Logs()) + elif func == 'project_script_self_run': + print(projectScriptSelf_Run()) + elif func == 'project_script_self_rename': + print(projectScriptSelf_Rename()) + elif func == 'project_script_self_enable': + print(projectScriptSelf_Enable()) + elif func == 'project_script_self_status': + print(projectScriptSelf_Status()) + elif func == 'get_rsa_public': + print(getRsaPublic()) + elif func == 'get_total_statistics': + print(getTotalStatistics()) + else: + print('fail') diff --git a/plugins/gitea/info.json b/plugins/gitea/info.json new file mode 100755 index 000000000..49f663fe2 --- /dev/null +++ b/plugins/gitea/info.json @@ -0,0 +1,18 @@ +{ + "ps": "Gitea是一个开源社区驱动的轻量级代码托管解决方案。", + "name": "gitea", + "title": "Gitea", + "versions": ["1.17.2","1.18.5","1.22.1","1.24.3"], + "tip": "soft", + "install_pre_inspection":false, + "uninstall_pre_inspection":true, + "checks": "server/gitea", + "path":"server/gitea", + "author": "gitea", + "date": "2022-10-03", + "home": "https://dl.gitea.io/", + "type": "Git服务器", + "shell": "install.sh", + "pid": "3", + "sort": 7 +} \ No newline at end of file diff --git a/plugins/gitea/init.d/gitea.service.tpl b/plugins/gitea/init.d/gitea.service.tpl new file mode 100644 index 000000000..1bc231bc6 --- /dev/null +++ b/plugins/gitea/init.d/gitea.service.tpl @@ -0,0 +1,20 @@ +[Unit] +Description=Gitea (Git with a cup of tea) +After=syslog.target +After=network.target + +[Service] +RestartSec=2s +Type=simple +User=www +Group=www +WorkingDirectory={$SERVER_PATH}/gitea +ExecStart={$SERVER_PATH}/gitea/gitea web +Restart=always +Environment=USER=www HOME=/home/www GITEA_WORK_DIR={$SERVER_PATH}/gitea +RemainAfterExit=yes +#AmbientCapabilities=CAP_NET_BIND_SERVICE +#CapabilityBoundingSet=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target diff --git a/plugins/gitea/init.d/gitea.tpl b/plugins/gitea/init.d/gitea.tpl new file mode 100644 index 000000000..b00abdf6f --- /dev/null +++ b/plugins/gitea/init.d/gitea.tpl @@ -0,0 +1,110 @@ +#!/bin/sh +# +# /etc/rc.d/init.d/Gitea +# +# Runs the Gogs +# +# +# chkconfig: - 85 15 +# + +### BEGIN INIT INFO +# Provides: Gitea +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Should-Start: mysql postgresql +# Should-Stop: mysql postgresql +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start Gitea at boot time. +# Description: Control Gitea. +### END INIT INFO + +# Source function library. +if [ -f /etc/init.d/functions ];then + . /etc/init.d/functions +fi + +if [ -f /etc/rc.d/init.d/functions ];then + . /etc/rc.d/init.d/functions +fi + +# Default values +export HOME={$HOME_DIR} +export USER={$RUN_USER} +NAME=gitea +GOGS_HOME={$SERVER_PATH}/gitea +GOGS_PATH=${GOGS_HOME}/$NAME +GOGS_USER={$RUN_USER} +SERVICENAME="gitea" +LOCKFILE=/tmp/gitea.lock +LOGPATH=${GOGS_HOME}/log +LOGFILE=${LOGPATH}/gitea.log +RETVAL=0 + + +[ -r /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME +DAEMON_OPTS="--check $NAME" +[ ! -z "$GOGS_USER" ] && DAEMON_OPTS="$DAEMON_OPTS --user=${GOGS_USER}" + + +status(){ + isStart=`ps -ef|grep 'gitea web' |grep -v grep|awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "${SERVICENAME} not running" + else + echo -e "${SERVICENAME}(pid $(echo $isStart)) already running" + fi +} + +start() { + isStart=`ps -ef|grep 'gitea web' |grep -v grep|awk '{print $2}'` + if [ "$isStart" != '' ];then + echo "${SERVICENAME}(pid $(echo $isStart)) already running" + return $RETVAL + fi + + cd ${GOGS_HOME} + echo -e "Starting ${SERVICENAME}: \c" + ${GOGS_PATH} web > ${LOGFILE} 2>&1 & + RETVAL=$? + [ $RETVAL = 0 ] && touch ${LOCKFILE} && echo -e "\033[32mdone\033[0m" + return $RETVAL +} + +stop() { + + pids=`ps -ef|grep 'gitea web' |grep -v grep|awk '{print $2}'` + arr=($pids) + echo -e "Stopping gitea... \c" + for p in ${arr[@]} + do + kill -9 $p + done + echo -e "\033[32mdone\033[0m" +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status + ;; + restart) + stop + start + ;; + reload) + stop + start + ;; + *) + echo "Usage: ${NAME} {start|stop|status|restart}" + exit 1 + ;; +esac +exit $RETVAL diff --git a/plugins/gitea/install.sh b/plugins/gitea/install.sh new file mode 100755 index 000000000..c03be4b16 --- /dev/null +++ b/plugins/gitea/install.sh @@ -0,0 +1,126 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + + +URL_DOWNLOAD=https://dl.gitea.com + + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +OSNAME_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +getBit(){ + echo `getconf LONG_BIT` +} + +Install_Rsync(){ + if [ "$OSNAME" == "debian" ] || [ "$OSNAME" == "ubuntu" ];then + apt install -y rsync + elif [[ "$OSNAME" == "arch" ]]; then + echo y | pacman -Sy rsync + elif [[ "$OSNAME" == "macos" ]]; then + # brew install rsync + # brew install lsyncd + echo "ok" + else + yum install -y rsync + fi +} + + +Install_App() +{ + Install_Rsync + + mkdir -p $serverPath/source/gitea + + if id www &> /dev/null ;then + echo "www uid is `id -u www`" + echo "www shell is `grep "^www:" /etc/passwd |cut -d':' -f7 `" + else + groupadd www + useradd -g www www + fi + + if [ "macos" != "$OSNAME" ];then + if [ ! -d /home/www ];then + mkdir -p /home/www + chown -R www:www /home/www + fi + fi + + echo '正在安装脚本文件...' + version=$1 + + + git config --global push.default simple + + if [ "macos" == "$OSNAME" ];then + file=gitea-${version}-darwin-10.12-amd64 + else + file=gitea-${version}-linux-amd64 + fi + + file_xz="${file}.xz" + echo "wget -O $serverPath/source/gitea/$file_xz ${URL_DOWNLOAD}/gitea/${version}/${file_xz}" + if [ ! -f $serverPath/source/gitea/$file_xz ];then + wget --no-check-certificate -O $serverPath/source/gitea/$file_xz ${URL_DOWNLOAD}/gitea/${version}/${file_xz} + fi + + cd $serverPath/source/gitea && xz -k -d $file_xz + if [ -f $file ];then + mkdir -p $serverPath/gitea + mv $serverPath/source/gitea/$file $serverPath/gitea/gitea + chmod +x $serverPath/gitea/gitea + + chown -R www:www $serverPath/gitea + fi + + + if [ -d $serverPath/gitea ];then + echo $version > $serverPath/gitea/version.pl + + cd ${rootPath} && python3 plugins/gitea/index.py start + cd ${rootPath} && python3 plugins/gitea/index.py initd_install + fi + + echo 'install success' +} + +Uninstall_App() +{ + + if [ -f /usr/lib/systemd/system/gitea.service ];then + systemctl stop gitea + systemctl disable gitea + rm -rf /usr/lib/systemd/system/gitea.service + systemctl daemon-reload + fi + + if [ -f $serverPath/gitea/initd/gitea ];then + $serverPath/gitea/initd/gitea stop + fi + + rm -rf $serverPath/gitea + echo 'uninstall success' +} + + +action=$1 +version=$2 +if [ "${1}" == 'install' ];then + Install_App $version +else + Uninstall_App $version +fi diff --git a/plugins/gitea/js/gitea.js b/plugins/gitea/js/gitea.js new file mode 100755 index 000000000..dbf71f84f --- /dev/null +++ b/plugins/gitea/js/gitea.js @@ -0,0 +1,774 @@ + +function gogsPost(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'gitea', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function gogsSetConfig(){ + gogsPost('get_gogs_conf', '', function(data){ + var rrdata = $.parseJSON(data.data); + if (!rrdata.status){ + layer.msg(rrdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + var rdata = rrdata.data; + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '140'; + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = ''; + break; + case 1: + var selected_1 = (rdata[i].value == 'On') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'Off') ? 'selected' : ''; + ibody = '' + break; + case 2: + var selected_1 = (rdata[i].value == 'true') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'false') ? 'selected' : ''; + ibody = '' + break; + } + mlist += '

          ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

          ' + } + var html = '
          \ + ' + mlist + '\ +
          \ + \ +
          \ +
          '; + $(".soft-man-con").html(html); + }); +} + + +//提交PHP配置 +function submitGogsConf() { + var data = { + DOMAIN: $("input[name='DOMAIN']").val(), + ROOT_URL: $("input[name='ROOT_URL']").val(), + HTTP_ADDR: $("select[name='HTTP_ADDR']").val(), + HTTP_PORT: $("input[name='HTTP_PORT']").val(), + START_SSH_SERVER: $("select[name='START_SSH_SERVER']").val() || 'false', + SSH_PORT: $("input[name='SSH_PORT']").val(), + REQUIRE_SIGNIN_VIEW: $("select[name='REQUIRE_SIGNIN_VIEW']").val() || 'false', + ENABLE_CAPTCHA: $("select[name='ENABLE_CAPTCHA']").val() || 'true', + DISABLE_REGISTRATION: $("select[name='DISABLE_REGISTRATION']").val() || 'false', + ENABLE_NOTIFY_MAIL: $("select[name='ENABLE_NOTIFY_MAIL']").val() || 'false', + FORCE_PRIVATE: $("select[name='FORCE_PRIVATE']").val() || 'false', + SHOW_FOOTER_BRANDING: $("select[name='SHOW_FOOTER_BRANDING']").val() || 'false', + SHOW_FOOTER_VERSION: $("select[name='SHOW_FOOTER_VERSION']").val() || 'false', + SHOW_FOOTER_TEMPLATE_LOAD_TIME: $("select[name='SHOW_FOOTER_TEMPLATE_LOAD_TIME']").val() || 'false', + }; + + gogsPost('submit_gogs_conf', data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + gogsSetConfig(); + }); +} + +function gogsEdit(){ + + gogsPost('gogs_edit',{} , function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + var edit = '

          通用的手动编辑:

          '; + edit +='
          \ + \ + \ +
          '; + $(".soft-man-con").html(edit); + }); + +} + + +function giteaUserList(page, search) { + + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + gogsPost('user_list', _data, function(data){ + + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + content = '
          '; + content += '
          '; + + content += '
          '; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + + content += ''; + + ulist = rdata['data']['data']; + for (i in ulist){ + + var email = ulist[i]["email"] == '' ? '无' : ulist[i]["email"]; + var user_url = rdata['data']['root_url'] + ulist[i]["name"]; + content += ''+ + ''+ + ''+ + ''+ + ''; + } + + content += ''; + content += '
          序号用户或组织邮件地址操作(WEB管理)
          '+ulist[i]["id"]+''+ulist[i]["name"]+''+email+'项目管理
          '; + + var page_html = ''; + + content += page_html; + + $(".soft-man-con").html(content); + + $('.find_user').click(function(){ + var name = $('#find_user').val(); + giteaUserList(page, name); + }); + }); +} + +function userProjectList(user, search){ + var loadOpen = layer.open({ + type: 1, + title: '用户('+user+')项目列表', + area: '500px', + content:"
          \ +
          \ +
          \ + \ + \ + \ +
          项目操作
          \ + \ +
          \ +
          \ +
          ", + success:function(){ + userProjectListPost(user,search); + } + }); +} + +function userProjectListPost(user, search){ + var req = {}; + if (!isNaN(user)){ + req['page'] = user; + req['name'] = user = getCookie('gogsUserSelected'); + } else { + req['page'] = 1; + req['name'] = user; + setCookie('gogsUserSelected', user); + } + + req['page_size'] = 5; + req['search'] = ''; + if(typeof(search) != 'undefined'){ + req['search'] = search; + } + + gogsPost('user_project_list', req, function(data){ + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e){} + + if (!rdata['status']){ + layer.msg(rdata['msg'], { icon: 2 }); + return; + } + + var list = ''; + // console.log(rdata); + var project_list = rdata['data']['data']; + for (i in project_list) { + var name = project_list[i]['name']; + list += '\ + '+name+'\ + \ + 源码 | \ + 脚本\ + \ + '; + } + + $('#gitea_table tbody').html(list); + + var page = rdata['data']['list']; + $('#gitea_table .gitea_page').html(page); + }); +} + + +function projectScript(user, name,has_hook){ + // console.log(user,name,has_hook); + var html = ''; + if (has_hook){ + html += ''; + html += ''; + html += ''; + html += ''; + } else { + html += ''; + } + + var loadOpen = layer.open({ + type: 1, + title: '['+user+']['+name+']脚本设置', + area: '240px', + content:'
          '+html+'
          ', + success:function(layero,index) { + + $('.hook_edit').click(function(){ + projectScriptEdit(user,name,index); + }); + + $('.hook_log').click(function(){ + projectScriptDebug(user,name,index); + }); + + $('.hook_load').click(function(){ + projectScriptLoad(user,name,index); + }); + + $('.hook_unload').click(function(){ + projectScriptUnload(user,name,index); + }); + } + }); +} + +function projectScriptEdit(user,name,index){ + gogsPost('project_script_edit', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['status']){ + onlineEditFile(0, rdata['data']['path']); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); +} + +function projectScriptLoad(user,name,index){ + gogsPost('project_script_load', {'user':user,'name':name}, function(data){ + if (data.data != 'ok'){ + layer.msg(data.data,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + showMsg('加载成功!',function(){ + layer.close(index); + userProjectListPost(1); + },{icon:1,time:2000,shade: [0.3, '#000']},2000); + }); +} + +function projectScriptUnload(user,name,index){ + gogsPost('project_script_unload', {'user':user,'name':name}, function(data){ + if (data.data != 'ok'){ + layer.msg(data.data,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + showMsg('卸载成功!',function(){ + layer.close(index); + userProjectListPost(1); + },{icon:1,time:2000,shade: [0.3, '#000']},2000); + }); +} + +function projectScriptDebug(user,name,index){ + gogsPost('project_script_debug', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['status']){ + onlineEditFile(0, rdata['path']); + } else { + showMsg(rdata.msg,function(){ + },{icon:1,time:2000,shade: [0.3, '#000']},2000); + } + }); +} + +function gogsRepoListPage(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + gogsPost('repo_list', _data, function(data){ + + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var ulist = rdata['data']['data']; + var body = '' + for (i in ulist){ + // console.log(ulist[i]); + + var option = ''; + if(ulist[i]['has_hook']){ + option += '卸载脚本' + ' | '; + option += '重载' + ' | '; + option += '编辑' + ' | '; + option += '日志' + ' | '; + option += '手动' + ' | '; + option += '自定义'; + } else{ + option += '加载脚本'; + } + + + body += ''+ulist[i]["id"]+''+ + '' + ulist[i]["name"]+''+ + '' + ulist[i]["repo"]+''+ + '' + + '源码' + ' | ' + + option + + '' + + ''; + } + + $('#repo_list tbody').html(body); + $('#repo_list_page').html(rdata['data']['list']); + + $('.find_repo').click(function(){ + var find_repo = $('#find_repo').val(); + gogsRepoListPage(page, find_repo); + }); + + + $('#repo_list .load').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_load', {'user':user,'name':name}, function(data){ + if (data.data != 'ok'){ + layer.msg(data.data,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + layer.msg('加载成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + setTimeout(function(){ + gogsRepoListPage(page, search); + }, 2000); + }); + }); + + $('#repo_list .unload').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_unload', {'user':user,'name':name}, function(data){ + if (data.data != 'ok'){ + layer.msg(data.data,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + layer.msg('卸载成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + setTimeout(function(){ + gogsRepoListPage(page, search); + }, 2000); + }); + }); + + $('#repo_list .edit').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_edit', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['status']){ + onlineEditFile(0, rdata['data']['path']); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + + + $('#repo_list .debug').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_debug', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['status']){ + onlineEditFile(0, rdata['path']); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + + + $('#repo_list .run').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_run', {'user':user,'name':name}, function(data){ + var data = $.parseJSON(data.data); + layer.msg(data.msg,{icon:data.status?1:2,time:2000,shade: [0.3, '#000']}); + }); + }); + + //--------- + }); +} + + +function giteaRepoList() { + content = '
          '; + content += '
          '; + + content += '
          '; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + + content += ''; + content += '
          序号用户/组织项目名操作
          '; + + var page = ''; + + content += page; + + $(".soft-man-con").html(content); + + gogsRepoListPage(1); +} + +function projectScriptSelfRender(user, name){ + gogsPost('project_script_self', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + + var data = rdata['data']['data']; + + if (rdata['data']['self_hook']){ + $('#open_script').prop('checked',true); + } + + var body = ''; + if(data.length == 0 ){ + body += '无脚本数据'; + } else{ + for (var i = 0; i < data.length; i++) { + var b_status = '已使用'; + if (data[i]["is_hidden"]){ + b_status = '已隐藏'; + } + + body += ''+ + '' + data[i]["name"]+''+ + '' + b_status + ''+ + '' + + '删除' + ' | ' + + '编辑' + ' | ' + + '日志' + ' | ' + + '手动' + ' | ' + + '重命名' + + ''; + } + + } + + $('#gogs_self_table tbody').html(body); + $('#gogs_self_table .page').html(rdata['data']['list']); + + $('#gogs_self_table .status').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + var status = '1'; + if (data[i]["is_hidden"]){ + status = '0'; + } + gogsPost('project_script_self_status', {'user':user,'name':name,'file':file, status:status}, function(data){ + var data = $.parseJSON(data.data); + showMsg(data.msg ,function(){ + projectScriptSelfRender(user, name); + },{icon:data.code?2:1,time:2000,shade: [0.3, '#000']},2000); + }); + }); + + $('#gogs_self_table .del').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + gogsPost('project_script_self_del', {'user':user,'name':name,'file':file}, function(data){ + var data = $.parseJSON(data.data); + showMsg(data.msg ,function(){ + projectScriptSelfRender(user, name); + },{icon:data.code?2:1,time:2000,shade: [0.3, '#000']},2000); + }); + }); + + $('#gogs_self_table .edit').click(function(){ + var i = $(this).data('index'); + var path = data[i]["path"]; + onlineEditFile(0,path); + }); + + $('#gogs_self_table .logs').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + gogsPost('project_script_self_logs', {'user':user,'name':name,'file':file}, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + if (rdata['status']){ + onlineEditFile(0, rdata['data']['path']); + } else { + layer.msg(rdata.msg,{icon:data.status?2:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + + $('#gogs_self_table .run').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + if (data[i]["is_hidden"]){ + layer.msg("已经禁用,不能执行!",{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + gogsPost('project_script_self_run', {'user':user,'name':name,'file':file}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:data.status?1:2,time:2000,shade: [0.3, '#000']}); + }); + }); + + + $('#gogs_self_table .rename').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + + if (data[i]["is_hidden"]){ + layer.msg("已经禁用,不能执行!",{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + file = file.split('.sh')[0]; + + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '320px', + title: '重命名', + btn:['设置','关闭'], + content: '
          \ +
          \ + \ +
          \ +
          ', + success:function(){ + $("#newFileName").focus().keyup(function(e){ + if(e.keyCode == 13) $(".layui-layer-btn0").click(); + }); + }, + yes:function(){ + var n_file = $("#newFileName").val(); + var o_file = file; + + gogsPost('project_script_self_rename', {'user':user,'name':name,'o_file':o_file,'n_file':n_file}, function(data){ + var data = $.parseJSON(data.data); + showMsg(data.msg ,function(){ + $(".layui-layer-btn1").click(); + projectScriptSelfRender(user, name); + },{icon:data.code?2:1,time:2000,shade: [0.3, '#000']},2000); + }); + } + }); + //----- + }); + //------ + }); +} + +//新建文件 +function createScriptFile(type, user, name, file) { + if (type == 1) { + gogsPost('project_script_self_create', {'user':user,'name':name,'file': file }, function(data){ + var rdata = $.parseJSON(data.data); + if(!rdata['status']){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + showMsg(rdata.msg, function(){ + $(".layui-layer-btn1").click(); + onlineEditFile(0,rdata['data']['abs_file']); + projectScriptSelfRender(user, name); + }, {icon:1,shade: [0.3, '#000']},2000); + }); + return; + } + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '320px', + title: '新建自定义脚本', + btn:['新建','关闭'], + content: '
          \ +
          \ + \ +
          \ +
          ', + success:function(){ + $("#newFileName").focus().keyup(function(e){ + if(e.keyCode == 13) $(".layui-layer-btn0").click(); + }); + }, + yes:function(){ + var file = $("#newFileName").val();; + createScriptFile(1, user, name, file); + } + }); +} + +function projectScriptSelf(user, name){ + layer.open({ + type: 1, + title: '项目('+user+'/'+name+')自定义脚本', + area: '500px', + content:"
          \ + \ +
          \ + 开启自定义脚本\ + \ + \ +
          \ +
          \ + \ + \ + \ +
          脚本文件名状态操作
          \ + \ +
          \ +
          ", + success:function(){ + projectScriptSelfRender(user, name); + + $('#create_script').click(function(){ + createScriptFile(0, user, name); + }); + + $('#script_hook_enable').click(function(){ + var enable = $('#open_script').prop('checked'); + var enable_option = '0'; + if (!enable){ + enable_option = '1'; + } + gogsPost('project_script_self_enable', {'user':user,'name':name,'enable':enable_option}, function(data){ + var data = $.parseJSON(data.data); + showMsg(data.msg ,function(){ + projectScriptSelfRender(user, name); + },{icon:data.status?1:2,shade: [0.3, '#000']},2000); + }); + + }); + } + }); +} + +function getRsaPublic(){ + gogsPost('get_rsa_public', {}, function(data){ + var rdata = $.parseJSON(data.data); + var con = '
          \ +
          \ + \ +
          \ +
            \ +
            ' + layer.open({ + type: 1, + area: "600px", + title: '本机公钥', + closeBtn: 2, + shift: 5, + shadeClose: false, + content:con + }); + }); +} + +function giteaRead(){ + + var readme = '
              '; + readme += '
            • 默认使用MySQL,第一个启动加载各种配置,并修改成正确的数据库配置
            • '; + readme += '
            • 邮件端口使用456,gitea仅支持使用STARTTLS的SMTP协议
            • '; + readme += '
            • 项目【加载脚本】后,会自动同步到wwwroot目录下
            • '; + readme += '
            • 点击查看本机公钥
            • '; + readme += '
            '; + + $('.soft-man-con').html(readme); +} \ No newline at end of file diff --git a/plugins/gogs/conf/app.ini b/plugins/gogs/conf/app.ini new file mode 100644 index 000000000..d36d96575 --- /dev/null +++ b/plugins/gogs/conf/app.ini @@ -0,0 +1,63 @@ +APP_NAME = Gogs +RUN_USER = {$RUN_USER} +RUN_MODE = prod + +[database] +DB_TYPE = mysql +HOST = 127.0.0.1:3306 +NAME = gogs +USER = gogs +PASSWD = gogs +SSL_MODE = disable +PATH = data/gogs.db + +[repository] +ROOT = {$ROOT_PATH}/gogs-repositories +FORCE_PRIVATE = true + +[server] +DOMAIN = 127.0.0.1 +HTTP_PORT = 3000 +ROOT_URL = http://127.0.0.1:3000/ +DISABLE_SSH = true +SSH_PORT = 2000 +START_SSH_SERVER = false +OFFLINE_MODE = false +LANDING_PAGE = home + +[mailer] +ENABLED = false +HOST = smtp.163.com:465 +USER = midoks@163.com +PASSWD = *** +USE_PLAIN_TEXT = true + +[service] +REGISTER_EMAIL_CONFIRM = false +ENABLE_NOTIFY_MAIL = false +DISABLE_REGISTRATION = true +ENABLE_CAPTCHA = true +REQUIRE_SIGNIN_VIEW = true + +[picture] +DISABLE_GRAVATAR = false +ENABLE_FEDERATED_AVATAR = false + +[session] +PROVIDER = file + +[log] +MODE = console, file +LEVEL = Info +ROOT_PATH = {$SERVER_PATH}/gogs/log + +[security] +INSTALL_LOCK = true +SECRET_KEY = jmGSJXDBH5Ng4wt + + +[other] +SHOW_FOOTER_BRANDING = false +SHOW_FOOTER_VERSION = false +SHOW_FOOTER_TEMPLATE_LOAD_TIME = false + diff --git a/plugins/gogs/hook/commit.tpl b/plugins/gogs/hook/commit.tpl new file mode 100755 index 000000000..bd94d2093 --- /dev/null +++ b/plugins/gogs/hook/commit.tpl @@ -0,0 +1,52 @@ +#!/bin/bash + +echo `date` + +GITADDR="{$GITROOTURL}/{$USERNAME}/{$PROJECT}.git" +GIT_SDIR="{$CODE_DIR}" + +GIT_USER_DIR="${GIT_SDIR}/{$USERNAME}" +GIT_PROJECT_DIR="${GIT_USER_DIR}/{$PROJECT}" + + +git config --global credential.helper store +git config --global pull.rebase false + +# echo $GIT_PROJECT_DIR +if [ ! -d $GIT_PROJECT_DIR ];then + mkdir -p $GIT_USER_DIR && cd $GIT_USER_DIR + git clone $GITADDR --branch main + if [ "$?" != "0" ];then + git clone $GITADDR + fi +fi + +unset GIT_DIR + +cd $GIT_PROJECT_DIR && git pull + +# func 2 +# cd $GIT_PROJECT_DIR && env -i git pull origin master + + +#更新的目的地址 +WEB_PATH={$WEB_ROOT}/{$USERNAME}/{$PROJECT} + +if [ ! -d $WEB_PATH ];then + mkdir -p $WEB_PATH + rsync -vauP --delete --exclude=".*" $GIT_PROJECT_DIR/ $WEB_PATH +else + if [ -f $GIT_PROJECT_DIR/exclude.list ];then + rsync -vauP --delete --exclude-from="$GIT_PROJECT_DIR/exclude.list" $GIT_PROJECT_DIR/ $WEB_PATH + else + rsync -vauP --exclude=".*" $GIT_PROJECT_DIR/ $WEB_PATH + fi +fi + +sysName=`uname` +if [ $sysName == 'Darwin' ]; then + USER=$(who | sed -n "2,1p" |awk '{print $1}') + chown -R $USER:staff $WEB_PATH +else + chown -R www:www $WEB_PATH +fi \ No newline at end of file diff --git a/plugins/gogs/hook/commit.tpl.ssh b/plugins/gogs/hook/commit.tpl.ssh new file mode 100755 index 000000000..6aac78ca8 --- /dev/null +++ b/plugins/gogs/hook/commit.tpl.ssh @@ -0,0 +1,36 @@ +#!/bin/bash + +echo `date` + +GITADDR="{$GITROOTURL}/{$USERNAME}/{$PROJECT}" +GIT_SDIR="{$CODE_DIR}" + +GIT_USER_DIR="${GIT_SDIR}/{$USERNAME}" +GIT_PROJECT_DIR="${GIT_USER_DIR}/{$PROJECT}" + +# echo $GIT_PROJECT_DIR +if [ ! -d $GIT_PROJECT_DIR ];then + mkdir -p $GIT_USER_DIR && cd $GIT_USER_DIR + git clone $GITADDR +fi + +unset GIT_DIR +cd $GIT_PROJECT_DIR && git pull + +# func 2 +# cd $GIT_PROJECT_DIR && env -i git pull origin master + + + +WEB_PATH={$WEB_ROOT}/{$USERNAME}/{$PROJECT} +mkdir -p $WEB_PATH + +rsync -vauP --delete --exclude=".*" $GIT_PROJECT_DIR/ $WEB_PATH + +sysName=`uname` +if [ $sysName == 'Darwin' ]; then + USER=$(who | sed -n "2,1p" |awk '{print $1}') + chown -R $USER:staff $WEB_PATH +else + chown -R www:www $WEB_PATH +fi \ No newline at end of file diff --git a/plugins/gogs/hook/post-receive.tpl b/plugins/gogs/hook/post-receive.tpl new file mode 100755 index 000000000..0c747ce2f --- /dev/null +++ b/plugins/gogs/hook/post-receive.tpl @@ -0,0 +1,3 @@ +#!/bin/bash + +sh -x {$PATH}/commit 2>{$PATH}/sh.log \ No newline at end of file diff --git a/plugins/gogs/hook/self_hook.tpl b/plugins/gogs/hook/self_hook.tpl new file mode 100644 index 000000000..266f053e8 --- /dev/null +++ b/plugins/gogs/hook/self_hook.tpl @@ -0,0 +1,14 @@ +#!/bin/bash + +H_DIR={$HOOK_DIR} +HL_DIR={$HOOK_LOGS_DIR} + + +SH_LIST=`cd ${H_DIR} && ls | grep ".sh$"` + +for sh_f in $SH_LIST; do + ABS_FILE=${H_DIR}/${sh_f} + ABS_LOGS=${HL_DIR}/${sh_f}.log + echo "sh ${ABS_FILE} 2>${ABS_LOGS}" + sh -x ${ABS_FILE} 2>${ABS_LOGS} +done \ No newline at end of file diff --git a/plugins/gogs/ico.png b/plugins/gogs/ico.png new file mode 100644 index 000000000..905e7d141 Binary files /dev/null and b/plugins/gogs/ico.png differ diff --git a/plugins/gogs/index.html b/plugins/gogs/index.html new file mode 100755 index 000000000..2c2d2a340 --- /dev/null +++ b/plugins/gogs/index.html @@ -0,0 +1,34 @@ + +
            +
            +
            +

            服务

            +

            自启动

            + +

            手动编辑

            +

            配置文件

            +

            配置修改

            +

            用户列表

            +

            项目列表

            +

            运行日志

            +

            提交日志

            +

            使用说明

            +
            +
            +
            +
            +
            +
            + \ No newline at end of file diff --git a/plugins/gogs/index.py b/plugins/gogs/index.py new file mode 100755 index 000000000..ee168eef6 --- /dev/null +++ b/plugins/gogs/index.py @@ -0,0 +1,1131 @@ +# coding: utf-8 + + +import time +import os +import sys +import re +import psutil + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'gogs' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':', 1) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getInitdConfTpl(): + path = getPluginDir() + "/init.d/gogs.tpl" + return path + + +def getInitdConf(): + path = getServerDir() + "/init.d/gogs" + return path + + +def getConf(): + path = getServerDir() + "/custom/conf/app.ini" + return path + + +def getConfTpl(): + path = getPluginDir() + "/conf/app.ini" + return path + + +def status(): + data = mw.execShell( + "ps -ef|grep " + getPluginName() + " |grep -v grep | grep -v python | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + + +def getHomeDir(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + else: + return '/root' + + +def getRunUser(): + if mw.isAppleSystem(): + user = mw.execShell("who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + else: + return 'root' + +__SR = '''#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export USER=%s +export HOME=%s && ''' % ( getRunUser(), getHomeDir()) + + +def contentReplace(content): + + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$RUN_USER}', getRunUser()) + content = content.replace('{$HOME_DIR}', getHomeDir()) + + return content + + +def initDreplace(): + + file_tpl = getInitdConfTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # conf_bin = getConf() + # if not os.path.exists(conf_bin): + # mw.execShell('mkdir -p ' + getServerDir() + '/custom/conf') + # conf_tpl = getConfTpl() + # content = mw.readFile(conf_tpl) + # content = contentReplace(content) + # mw.writeFile(conf_bin, content) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/gogs.service' + systemServiceTpl = getPluginDir() + '/init.d/gogs.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + log_path = getServerDir() + '/log' + if not os.path.exists(log_path): + os.mkdir(log_path) + + return file_bin + + +def getRootUrl(): + content = mw.readFile(getConf()) + rep = r'ROOT_URL\\s*=\\s*(.*)' + tmp = re.search(rep, content) + if tmp: + return tmp.groups()[0] + + rep = r'EXTERNAL_URL\\s*=\s*(.*)' + tmp = re.search(rep, content) + if tmp: + return tmp.groups()[0] + return '' + + +def getSshPort(): + content = mw.readFile(getConf()) + rep = r'SSH_PORT\\s*=\\s*(.*)' + tmp = re.search(rep, content) + if not tmp: + return '' + return tmp.groups()[0] + + +def getHttpPort(): + content = mw.readFile(getConf()) + rep = r'HTTP_PORT\\s*=\\s*(.*)' + tmp = re.search(rep, content) + if not tmp: + return '' + return tmp.groups()[0] + + +def getRootPath(): + content = mw.readFile(getConf()) + rep = r'ROOT\\s*=\\s*(.*)' + tmp = re.search(rep, content) + if not tmp: + return '' + return tmp.groups()[0] + + +def getDbConfValue(): + conf = getConf() + if not os.path.exists(conf): + return {} + + content = mw.readFile(conf) + rep_scope = r"\\[database\\](.*?)\\[" + tmp = re.findall(rep_scope, content, re.S) + + rep = r'(\\w*)\\s*=\\s*(.*)' + tmp = re.findall(rep, tmp[0]) + r = {} + for x in range(len(tmp)): + k = tmp[x][0] + v = tmp[x][1] + r[k] = v + return r + + +def pMysqlDb(conf): + host = conf['HOST'].split(':') + # pymysql + db = mw.getMyORM() + # MySQLdb | + # db = mw.getMyORMDb() + + db.setPort(int(host[1])) + db.setUser(conf['USER']) + + if 'PASSWD' in conf: + db.setPwd(conf['PASSWD']) + else: + db.setPwd(conf['PASSWORD']) + + db.setDbName(conf['NAME']) + # db.setSocket(getSocketFile()) + db.setCharset("utf8") + return db + + +def pSqliteDb(conf): + # print(conf) + import db + psDb = db.Sql() + + # 默认 + gsdir = getServerDir() + '/data' + dbname = 'gogs' + if conf['PATH'][0] == '/': + # 绝对路径 + pass + else: + path = conf['PATH'].split('/') + gsdir = getServerDir() + '/' + path[0] + dbname = path[1].split('.')[0] + + # print(gsdir, dbname) + psDb.dbPos(gsdir, dbname) + return psDb + + +def getGogsDbType(conf): + + if 'DB_TYPE' in conf: + return conf['DB_TYPE'] + + if 'TYPE' in conf: + return conf['TYPE'] + + return 'NONE' + + +def pQuery(sql): + conf = getDbConfValue() + gtype = getGogsDbType(conf) + if gtype == 'sqlite3': + db = pSqliteDb(conf) + data = db.query(sql, []).fetchall() + return data + elif gtype == 'mysql': + db = pMysqlDb(conf) + return db.query(sql) + + print("仅支持mysql|sqlite3配置") + exit(0) + + +def isSqlError(mysqlMsg): + # 检测数据库执行错误 + _mysqlMsg = str(mysqlMsg) + # print _mysqlMsg + if "MySQLdb" in _mysqlMsg: + return mw.returnData(False, 'MySQLdb组件缺失!
            进入SSH命令行输入: pip install mysql-python') + if "2002," in _mysqlMsg: + return mw.returnData(False, '数据库连接失败,请检查数据库服务是否启动!') + if "using password:" in _mysqlMsg: + return mw.returnData(False, '数据库管理密码错误!') + if "Connection refused" in _mysqlMsg: + return mw.returnData(False, '数据库连接失败,请检查数据库服务是否启动!') + if "1133," in _mysqlMsg: + return mw.returnData(False, '数据库用户不存在!') + if "1007," in _mysqlMsg: + return mw.returnData(False, '数据库已经存在!') + if "1044," in _mysqlMsg: + return mw.returnData(False, mysqlMsg[1]) + if "2003," in _mysqlMsg: + return mw.returnData(False, "Can't connect to MySQL server on '127.0.0.1' (61)") + return mw.returnData(True, 'OK') + + +def gogsOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' gogs') + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(__SR + file + ' ' + method) + if data[1] == '': + return 'ok' + return data[0] + + +def start(): + return gogsOp('start') + + +def stop(): + return gogsOp('stop') + + +def restart(): + return gogsOp('restart') + + +def reload(): + return gogsOp('reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status gogs | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable gogs') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable gogs') + return 'ok' + + +def runLog(): + log_path = getServerDir() + '/log/gogs.log' + return log_path + + +def postReceiveLog(): + log_path = getServerDir() + '/log/hooks/post-receive.log' + return log_path + + +def getGogsConf(): + conf = getConf() + if not os.path.exists(conf): + return mw.returnJson(False, "请先安装初始化!
            默认地址:http://" + mw.getLocalIp() + ":3000") + + gets = [ + {'name': 'DOMAIN', 'type': -1, 'ps': '服务器域名'}, + {'name': 'ROOT_URL', 'type': -1, 'ps': '公开的完整URL路径'}, + {'name': 'HTTP_ADDR', 'type': -1, 'ps': '应用HTTP监听地址'}, + {'name': 'HTTP_PORT', 'type': -1, 'ps': '应用 HTTP 监听端口号'}, + + {'name': 'START_SSH_SERVER', 'type': 2, 'ps': '启动内置SSH服务器'}, + {'name': 'SSH_PORT', 'type': -1, 'ps': 'SSH 端口号'}, + + {'name': 'REQUIRE_SIGNIN_VIEW', 'type': 2, 'ps': '强制登录浏览'}, + {'name': 'ENABLE_CAPTCHA', 'type': 2, 'ps': '启用验证码服务'}, + {'name': 'DISABLE_REGISTRATION', 'type': 2, 'ps': '禁止注册,只能由管理员创建帐号'}, + {'name': 'ENABLE_NOTIFY_MAIL', 'type': 2, 'ps': '是否开启邮件通知'}, + + {'name': 'FORCE_PRIVATE', 'type': 2, 'ps': '强制要求所有新建的仓库都是私有'}, + + {'name': 'SHOW_FOOTER_BRANDING', 'type': 2, 'ps': 'Gogs推广信息'}, + {'name': 'SHOW_FOOTER_VERSION', 'type': 2, 'ps': 'Gogs版本信息'}, + {'name': 'SHOW_FOOTER_TEMPLATE_LOAD_TIME', 'type': 2, 'ps': 'Gogs模板加载时间'}, + ] + conf = mw.readFile(conf) + result = [] + + for g in gets: + rep = g['name'] + '\\s*=\\s*(.*)' + tmp = re.search(rep, conf) + if not tmp: + continue + g['value'] = tmp.groups()[0] + result.append(g) + return mw.returnJson(True, 'OK', result) + + +def submitGogsConf(): + gets = ['DOMAIN', + 'ROOT_URL', + 'HTTP_ADDR', + 'HTTP_PORT', + 'START_SSH_SERVER', + 'SSH_PORT', + 'REQUIRE_SIGNIN_VIEW', + 'FORCE_PRIVATE', + 'ENABLE_CAPTCHA', + 'DISABLE_REGISTRATION', + 'ENABLE_NOTIFY_MAIL', + 'SHOW_FOOTER_BRANDING', + 'SHOW_FOOTER_VERSION', + 'SHOW_FOOTER_TEMPLATE_LOAD_TIME'] + args = getArgs() + filename = getConf() + conf = mw.readFile(filename) + for g in gets: + if g in args: + rep = g + '\\s*=\\s*(.*)' + val = g + ' = ' + args[g] + conf = re.sub(rep, val, conf) + mw.writeFile(filename, conf) + reload() + return mw.returnJson(True, '设置成功') + + +def gogsEditTpl(): + data = {} + data['post_receive'] = getPluginDir() + '/hook/post-receive.tpl' + data['commit'] = getPluginDir() + '/hook/commit.tpl' + return mw.getJson(data) + + +def userList(): + + conf = getConf() + if not os.path.exists(conf): + return mw.returnJson(False, "请先安装初始化!
            默认地址:http://" + mw.getLocalIp() + ":3000") + + conf = getDbConfValue() + gtype = getGogsDbType(conf) + if gtype != 'mysql': + return mw.returnJson(False, "仅支持mysql数据操作!") + + import math + args = getArgs() + + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + search = '' + if 'search' in args: + search = args['search'] + + user_where1 = '' + user_where2 = '' + if search != '': + user_where1 = ' where name like "%' + search + '%"' + user_where2 = ' where name like "%' + search + '%"' + + data = {} + + data['root_url'] = getRootUrl() + + start = (page - 1) * page_size + list_count = pQuery('select count(id) as num from user' + user_where1) + count = list_count[0]["num"] + list_data = pQuery( + 'select id,name,email from user ' + user_where2 + ' order by id desc limit ' + str(start) + ',' + str(page_size)) + data['list'] = mw.getPage({'count': count, 'p': page, + 'row': page_size, 'tojs': 'gogsUserList'}) + data['page'] = page + data['page_size'] = page_size + data['page_count'] = int(math.ceil(count / page_size)) + data['data'] = list_data + return mw.returnJson(True, 'OK', data) + + +def checkRepoListIsHasScript(data): + path = getRootPath() + for x in range(len(data)): + name = data[x]['name'] + '/' + data[x]['repo'] + '.git' + path_tmp = path + '/' + name + '/custom_hooks/post-receive' + if os.path.exists(path_tmp): + data[x]['has_hook'] = True + else: + data[x]['has_hook'] = False + return data + + +def repoList(): + + conf = getConf() + if not os.path.exists(conf): + return mw.returnJson(False, "请先安装初始化!
            默认地址:http://" + mw.getLocalIp() + ":3000") + + conf = getDbConfValue() + gtype = getGogsDbType(conf) + if gtype != 'mysql': + return mw.returnJson(False, "仅支持mysql数据操作!") + + import math + args = getArgs() + + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + search = '' + if 'search' in args: + search = args['search'] + + data = {} + + data['root_url'] = getRootUrl() + + repo_where1 = '' + repo_where2 = '' + if search != '': + repo_where1 = ' where name like "%' + search + '%"' + repo_where2 = ' where r.name like "%' + search + '%"' + + start = (page - 1) * page_size + list_count = pQuery( + 'select count(id) as num from repository' + repo_where1) + count = list_count[0]["num"] + sql = 'select r.id,r.owner_id,r.name as repo, u.name from repository r left join user u on r.owner_id=u.id ' + repo_where2 + ' order by r.id desc limit ' + \ + str(start) + ',' + str(page_size) + # print(sql) + list_data = pQuery(sql) + # print(list_data) + list_data = checkRepoListIsHasScript(list_data) + + data['list'] = mw.getPage({'count': count, 'p': page, 'row': page_size, 'tojs': 'gogsRepoListPage'}) + data['page'] = page + data['page_size'] = page_size + data['page_count'] = int(math.ceil(count / page_size)) + data['data'] = list_data + return mw.returnJson(True, 'OK', data) + + +def getAllUserProject(user, search=''): + path = getRootPath() + '/' + user + dlist = [] + if os.path.exists(path): + for filename in os.listdir(path): + tmp = {} + filePath = path + '/' + filename + if os.path.isdir(filePath): + if search == '': + tmp['name'] = filename.replace('.git', '') + dlist.append(tmp) + else: + if filename.find(search) != -1: + tmp['name'] = filename.replace('.git', '') + dlist.append(tmp) + return dlist + + +def checkProjectListIsHasScript(user, data): + path = getRootPath() + '/' + user + for x in range(len(data)): + name = data[x]['name'] + '.git' + path_tmp = path + '/' + name + '/custom_hooks/post-receive' + if os.path.exists(path_tmp): + data[x]['has_hook'] = True + else: + data[x]['has_hook'] = False + return data + + +def userProjectList(): + import math + args = getArgs() + # print args + + page = 1 + page_size = 5 + search = '' + + if not 'name' in args: + return mw.returnJson(False, '缺少参数name') + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + data = {} + + ulist = getAllUserProject(args['name']) + dlist_sum = len(ulist) + + start = (page - 1) * page_size + ret_data = ulist[start:start + page_size] + ret_data = checkProjectListIsHasScript(args['name'], ret_data) + + data['root_url'] = getRootUrl() + data['data'] = ret_data + data['args'] = args + data['list'] = mw.getPage( + {'count': dlist_sum, 'p': page, 'row': page_size, 'tojs': 'userProjectListPage'}) + + return mw.returnJson(True, 'OK', data) + + +def projectScriptEdit(): + args = getArgs() + + if not 'user' in args: + return mw.returnJson(True, 'username missing') + + if not 'name' in args: + return mw.returnJson(True, 'project name missing') + + user = args['user'] + name = args['name'] + '.git' + post_receive = getRootPath() + '/' + user + '/' + name + \ + '/custom_hooks/commit' + if os.path.exists(post_receive): + return mw.returnJson(True, 'OK', {'path': post_receive}) + else: + return mw.returnJson(False, 'file does not exist') + + +def projectScriptLoad(): + args = getArgs() + if not 'user' in args: + return mw.returnJson(True, 'username missing') + + if not 'name' in args: + return mw.returnJson(True, 'project name missing') + + user = args['user'] + name = args['name'] + '.git' + + path = getRootPath() + '/' + user + '/' + name + post_receive_tpl = getPluginDir() + '/hook/post-receive.tpl' + post_receive = path + '/custom_hooks/post-receive' + + if not os.path.exists(path + '/custom_hooks'): + mw.execShell('mkdir -p ' + path + '/custom_hooks') + + pct_content = mw.readFile(post_receive_tpl) + pct_content = pct_content.replace('{$PATH}', path + '/custom_hooks') + mw.writeFile(post_receive, pct_content) + mw.execShell('chmod 777 ' + post_receive) + + commit_tpl = getPluginDir() + '/hook/commit.tpl' + commit = path + '/custom_hooks/commit' + + codeDir = mw.getFatherDir() + '/git' + + cc_content = mw.readFile(commit_tpl) + + gitPath = getRootPath() + cc_content = cc_content.replace('{$GITROOTURL}', gitPath) + cc_content = cc_content.replace('{$CODE_DIR}', codeDir) + cc_content = cc_content.replace('{$USERNAME}', user) + cc_content = cc_content.replace('{$PROJECT}', args['name']) + cc_content = cc_content.replace('{$WEB_ROOT}', mw.getWwwDir()) + mw.writeFile(commit, cc_content) + mw.execShell('chmod 777 ' + commit) + + return 'ok' + + +def projectScriptUnload(): + args = getArgs() + data = checkArgs(args, ['user', 'name']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + + post_receive = getRootPath() + '/' + user + '/' + name + \ + '/custom_hooks/post-receive' + mw.execShell('rm -f ' + post_receive) + + commit = getRootPath() + '/' + user + '/' + name + \ + '/custom_hooks/commit' + mw.execShell('rm -f ' + commit) + return 'ok' + + +def projectScriptDebug(): + args = getArgs() + data = checkArgs(args, ['user', 'name']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + commit_log = getRootPath() + '/' + user + '/' + name + \ + '/custom_hooks/sh.log' + + data = {} + if os.path.exists(commit_log): + data['status'] = True + data['path'] = commit_log + else: + data['status'] = False + data['msg'] = '没有日志文件' + + return mw.getJson(data) + + +def projectScriptRun(): + args = getArgs() + data = checkArgs(args, ['user', 'name']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + + path = getRootPath() + '/' + user + '/' + name + commit_sh = path + '/custom_hooks/commit' + commit_log = path + '/custom_hooks/sh.log' + script_run = 'sh -x ' + commit_sh + ' 2>' + commit_log + + if not os.path.exists(commit_sh): + return mw.returnJson(False, '脚本文件不存在!') + + mw.execShell(script_run) + return mw.returnJson(True, '脚本文件执行成功,观察日志!') + + +def projectScriptSelf(): + args = getArgs() + data = checkArgs(args, ['user', 'name']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + + self_path = custom_hooks + '/self' + if not os.path.exists(self_path): + os.mkdir(self_path) + + self_logs_path = custom_hooks + '/self_logs' + if not os.path.exists(self_logs_path): + os.mkdir(self_logs_path) + + self_hook_file = custom_hooks + '/self_hook.sh' + self_hook_exist = False + if os.path.exists(self_hook_file): + self_hook_exist = True + + dlist = [] + if os.path.exists(self_path): + for filename in os.listdir(self_path): + tmp = {} + filePath = self_path + '/' + filename + if os.path.isfile(filePath): + tmp['path'] = filePath + tmp['name'] = os.path.basename(filePath) + tmp['is_hidden'] = False + if tmp['name'].endswith('.txt'): + tmp['is_hidden'] = True + + dlist.append(tmp) + + dlist_sum = len(dlist) + # print(dlist) + rdata = {} + rdata['data'] = dlist + rdata['self_hook'] = self_hook_exist + rdata['list'] = mw.getPage( + {'count': dlist_sum, 'p': 1, 'row': 100, 'tojs': 'self_page'}) + + return mw.returnJson(True, 'ok', rdata) + + +def projectScriptSelf_Create(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + + self_path = path = getRootPath() + '/' + user + '/' + name + '/custom_hooks/self' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + abs_file = self_path + '/' + file + '.sh' + if os.path.exists(abs_file): + return mw.returnJson(False, '脚本已经存在!') + + mw.writeFile(abs_file, "#!/bin/bash\necho `date +'%Y-%m-%d %H:%M:%S'`\n") + + rdata = {} + rdata['abs_file'] = abs_file + return mw.returnJson(True, '创建文件成功!', rdata) + + +def projectScriptSelf_Del(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + self_path = custom_hooks + '/self' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + abs_file = self_path + '/' + file + # print(abs_file) + if not os.path.exists(abs_file): + return mw.returnJson(False, '脚本已经删除!') + + os.remove(abs_file) + + # 日志也删除 + log_file = custom_hooks + '/self_logs/' + file + '.log' + if os.path.exists(log_file): + os.remove(log_file) + + return mw.returnJson(True, '脚本删除成功!') + + +def projectScriptSelf_Logs(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + + self_path = path = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks/self_logs' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + logs_file = self_path + '/' + file + '.log' + if os.path.exists(logs_file): + rdata = {} + rdata['path'] = logs_file + return mw.returnJson(True, 'ok', rdata) + + return mw.returnJson(False, '日志不存在!') + + +def projectScriptSelf_Run(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + self_path = custom_hooks + '/self/' + file + self_logs_path = custom_hooks + '/self_logs/' + file + '.log' + + shell = "sh -x " + self_path + " 2>" + self_logs_path + ' &' + mw.execShell(shell) + return mw.returnJson(True, '执行成功!') + + +def projectScriptSelf_Rename(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'o_file', 'n_file']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + o_file = args['o_file'] + n_file = args['n_file'] + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + self_path = custom_hooks + '/self' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + o_file_abs = self_path + '/' + o_file + '.sh' + if not os.path.exists(o_file_abs): + return mw.returnJson(False, '原文件已经不存在了!') + + n_file_abs = self_path + '/' + n_file + '.sh' + + os.rename(o_file_abs, n_file_abs) + + # 日志也删除 + log_file = custom_hooks + '/self_logs/' + o_file + '.sh.log' + if os.path.exists(log_file): + os.remove(log_file) + + return mw.returnJson(True, '重命名成功!') + + +def projectScriptSelf_Enable(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'enable']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + enable = args['enable'] + + custom_path = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + + # 替换commit配置 + commit_path = custom_path + '/commit' + note = '#Gogs Script Don`t Remove and Change' + + self_file = custom_path + '/self_hook.sh' + self_hook_tpl = getPluginDir() + '/hook/self_hook.tpl' + + if enable == '1': + content = mw.readFile(self_hook_tpl) + content = content.replace('{$HOOK_DIR}', custom_path + '/self') + content = content.replace( + '{$HOOK_LOGS_DIR}', custom_path + '/self_logs') + mw.writeFile(self_file, content) + mw.execShell("chmod 777 " + self_file) + + commit_content = mw.readFile(commit_path) + commit_content += "\n\n" + "bash " + self_file + " " + note + mw.writeFile(commit_path, commit_content) + + return mw.returnJson(True, '开启成功!') + else: + commit_content = mw.readFile(commit_path) + rep = ".*" + note + commit_content = re.sub(rep, '', commit_content, re.M) + commit_content = commit_content.strip() + mw.writeFile(commit_path, commit_content) + if os.path.exists(self_file): + os.remove(self_file) + return mw.returnJson(True, '关闭成功!') + + +def projectScriptSelf_Status(): + args = getArgs() + data = checkArgs(args, ['user', 'name', 'file', 'status']) + if not data[0]: + return data[1] + + user = args['user'] + name = args['name'] + '.git' + file = args['file'] + status = args['status'] + + custom_hooks = getRootPath() + '/' + user + '/' + \ + name + '/custom_hooks' + self_path = custom_hooks + '/self' + + if not os.path.exists(self_path): + os.mkdir(self_path) + + # 日志也删除 + log_file = custom_hooks + '/self_logs/' + file + '.log' + if os.path.exists(log_file): + os.remove(log_file) + + if status == '1': + file_abs = self_path + '/' + file + file_text_abs = self_path + '/' + file + '.txt' + os.rename(file_abs, file_text_abs) + return mw.returnJson(True, '开始禁用成功!') + else: + file_abs = self_path + '/' + file.strip('.txt') + file_text_abs = self_path + '/' + file + os.rename(file_text_abs, file_abs) + return mw.returnJson(True, '开始使用成功!') + + return mw.returnJson(True, '禁用成功!') + + +def getRsaPublic(): + path = getHomeDir() + path += '/.ssh/id_rsa.pub' + + content = mw.readFile(path) + + data = {} + data['mw'] = content + return mw.getJson(data) + + +def getTotalStatistics(): + st = status() + data = {} + if st.strip() == 'start': + list_count = pQuery('select count(id) as num from repository') + count = list_count[0]["num"] + data['status'] = True + data['count'] = count + data['ver'] = mw.readFile(getServerDir() + '/version.pl').strip() + return mw.returnJson(True, 'ok', data) + + data['status'] = False + data['count'] = 0 + return mw.returnJson(False, 'fail', data) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_log': + print(runLog()) + elif func == 'post_receive_log': + print(postReceiveLog()) + elif func == 'conf': + print(getConf()) + elif func == 'init_conf': + print(getInitdConf()) + elif func == 'get_gogs_conf': + print(getGogsConf()) + elif func == 'submit_gogs_conf': + print(submitGogsConf()) + elif func == 'gogs_edit_tpl': + print(gogsEditTpl()) + elif func == 'user_list': + print(userList()) + elif func == 'repo_list': + print(repoList()) + elif func == 'user_project_list': + print(userProjectList()) + elif func == 'project_script_edit': + print(projectScriptEdit()) + elif func == 'project_script_load': + print(projectScriptLoad()) + elif func == 'project_script_unload': + print(projectScriptUnload()) + elif func == 'project_script_debug': + print(projectScriptDebug()) + elif func == 'project_script_run': + print(projectScriptRun()) + elif func == 'project_script_self': + print(projectScriptSelf()) + elif func == 'project_script_self_create': + print(projectScriptSelf_Create()) + elif func == 'project_script_self_del': + print(projectScriptSelf_Del()) + elif func == 'project_script_self_logs': + print(projectScriptSelf_Logs()) + elif func == 'project_script_self_run': + print(projectScriptSelf_Run()) + elif func == 'project_script_self_rename': + print(projectScriptSelf_Rename()) + elif func == 'project_script_self_enable': + print(projectScriptSelf_Enable()) + elif func == 'project_script_self_status': + print(projectScriptSelf_Status()) + elif func == 'get_rsa_public': + print(getRsaPublic()) + elif func == 'get_total_statistics': + print(getTotalStatistics()) + else: + print('fail') diff --git a/plugins/gogs/info.json b/plugins/gogs/info.json new file mode 100755 index 000000000..5f745f2f0 --- /dev/null +++ b/plugins/gogs/info.json @@ -0,0 +1,16 @@ +{ + "ps": "Gogs是一款极易搭建的自助Git服务", + "name": "gogs", + "title": "Gogs", + "versions": ["0.11.86","0.12.9","0.13.0"], + "tip": "soft", + "checks": "server/gogs", + "path":"server/gogs", + "author": "midoks", + "date": "2018-04-01", + "home": "https://gogs.io", + "type": "Git服务器", + "shell": "install.sh", + "pid": "3", + "sort": 7 +} \ No newline at end of file diff --git a/plugins/gogs/init.d/gogs.service.bak.tpl b/plugins/gogs/init.d/gogs.service.bak.tpl new file mode 100644 index 000000000..8b58b1c81 --- /dev/null +++ b/plugins/gogs/init.d/gogs.service.bak.tpl @@ -0,0 +1,29 @@ +[Unit] +Description=Gogs +After=network.target + +[Service] +# Modify these two values and uncomment them if you have +# repos with lots of files and get an HTTP error 500 because +# of that +### +#LimitMEMLOCK=infinity +#LimitNOFILE=65535 +Type=simple +User=git +Group=git +WorkingDirectory={$SERVER_PATH}/gogs +ExecStart={$SERVER_PATH}/gogs/gogs web +ExecReload=/bin/kill -USR2 $MAINPID +Restart=always +Environment=USER=git HOME=/home/git + +# Some distributions may not support these hardening directives. If you cannot start the service due +# to an unknown option, comment out the ones not supported by your version of systemd. +ProtectSystem=full +PrivateDevices=yes +PrivateTmp=yes +NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target diff --git a/plugins/gogs/init.d/gogs.service.tpl b/plugins/gogs/init.d/gogs.service.tpl new file mode 100644 index 000000000..02d19b9de --- /dev/null +++ b/plugins/gogs/init.d/gogs.service.tpl @@ -0,0 +1,12 @@ +[Unit] +Description=Gogs +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/gogs/init.d/gogs start +ExecStop={$SERVER_PATH}/gogs/init.d/gogs stop +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/plugins/gogs/init.d/gogs.tpl b/plugins/gogs/init.d/gogs.tpl new file mode 100644 index 000000000..0d4ff84b3 --- /dev/null +++ b/plugins/gogs/init.d/gogs.tpl @@ -0,0 +1,110 @@ +#!/bin/sh +# +# /etc/rc.d/init.d/gogs +# +# Runs the Gogs +# +# +# chkconfig: - 85 15 +# + +### BEGIN INIT INFO +# Provides: gogs +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Should-Start: mysql postgresql +# Should-Stop: mysql postgresql +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start gogs at boot time. +# Description: Control gogs. +### END INIT INFO + +# Source function library. +if [ -f /etc/init.d/functions ];then + . /etc/init.d/functions +fi + +if [ -f /etc/rc.d/init.d/functions ];then + . /etc/rc.d/init.d/functions +fi + +# Default values +export HOME={$HOME_DIR} +export USER={$RUN_USER} +NAME=gogs +GOGS_HOME={$SERVER_PATH}/gogs +GOGS_PATH=${GOGS_HOME}/$NAME +GOGS_USER={$RUN_USER} +SERVICENAME="Gogs" +LOCKFILE=/tmp/gogs.lock +LOGPATH=${GOGS_HOME}/log +LOGFILE=${LOGPATH}/gogs.log +RETVAL=0 + + +[ -r /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME +DAEMON_OPTS="--check $NAME" +[ ! -z "$GOGS_USER" ] && DAEMON_OPTS="$DAEMON_OPTS --user=${GOGS_USER}" + + +status(){ + isStart=`ps -ef|grep 'gogs web' |grep -v grep|awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "${SERVICENAME} not running" + else + echo -e "${SERVICENAME}(pid $(echo $isStart)) already running" + fi +} + +start() { + isStart=`ps -ef|grep 'gogs web' |grep -v grep|awk '{print $2}'` + if [ "$isStart" != '' ];then + echo "${SERVICENAME}(pid $(echo $isStart)) already running" + return $RETVAL + fi + + cd ${GOGS_HOME} + echo -e "Starting ${SERVICENAME}: \c" + ${GOGS_PATH} web > ${LOGFILE} 2>&1 & + RETVAL=$? + [ $RETVAL = 0 ] && touch ${LOCKFILE} && echo -e "\033[32mdone\033[0m" + return $RETVAL +} + +stop() { + + pids=`ps -ef|grep 'gogs web' |grep -v grep|awk '{print $2}'` + arr=($pids) + echo -e "Stopping gogs... \c" + for p in ${arr[@]} + do + kill -9 $p + done + echo -e "\033[32mdone\033[0m" +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status + ;; + restart) + stop + start + ;; + reload) + stop + start + ;; + *) + echo "Usage: ${NAME} {start|stop|status|restart}" + exit 1 + ;; +esac +exit $RETVAL diff --git a/plugins/gogs/install.sh b/plugins/gogs/install.sh new file mode 100755 index 000000000..d9a3af681 --- /dev/null +++ b/plugins/gogs/install.sh @@ -0,0 +1,107 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + + +GOGS_DOWNLOAD='https://dl.gogs.io' + +getOs(){ + os=`uname` + if [ "Darwin" == "$os" ];then + echo 'darwin' + else + echo 'linux' + fi + return 0 +} + +getBit(){ + echo `getconf LONG_BIT` +} + + +Install_gogs() +{ + + mkdir -p $serverPath/source/gogs + + echo '正在安装脚本文件...' + version=$1 + os=`getOs` + + # if id git &> /dev/null ;then + # echo "git uid is `id -u git`" + # echo "git shell is `grep "^git:" /etc/passwd |cut -d':' -f7 `" + # else + # groupadd git + # useradd -g git git + # fi + + git config --global push.default simple + + if [ "darwin" == "$os" ];then + file=gogs_${version}_darwin_amd64.zip + else + file=gogs_${version}_linux_amd64.zip + fi + + if [ ! -f $serverPath/source/gogs/$file ];then + wget --no-check-certificate -O $serverPath/source/gogs/$file ${GOGS_DOWNLOAD}/${version}/${file} + fi + + cd $serverPath/source/gogs && unzip -o $file -d gogs_${version} + mv $serverPath/source/gogs/gogs_${version}/gogs/ $serverPath/gogs + + + if [ -d $serverPath/gogs ];then + echo $version > $serverPath/gogs/version.pl + + cd ${rootPath} && python3 ${rootPath}/plugins/gogs/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/gogs/index.py initd_install + fi + # if id -u gogs > /dev/null 2>&1; then + # echo "gogs user exists" + # else + # useradd gogs + # cp /etc/sudoers{,.`date +"%Y-%m-%d_%H-%M-%S"`} + # echo "gogs ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + # fi + + echo 'install success' +} + +Uninstall_gogs() +{ + + if [ -f /usr/lib/systemd/system/gogs.service ];then + systemctl stop gogs + systemctl disable gogs + rm -rf /usr/lib/systemd/system/gogs.service + systemctl daemon-reload + fi + + if [ -f $serverPath/gogs/initd/gogs ];then + $serverPath/gogs/initd/gogs stop + fi + + rm -rf $serverPath/gogs + echo 'uninstall success' +} + + +action=$1 +version=$2 +if [ "${1}" == 'install' ];then + Install_gogs $version +else + Uninstall_gogs $version +fi diff --git a/plugins/gogs/js/gogs.js b/plugins/gogs/js/gogs.js new file mode 100755 index 000000000..2999ccbc4 --- /dev/null +++ b/plugins/gogs/js/gogs.js @@ -0,0 +1,789 @@ +function gogsPost(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'gogs', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function gogsSetConfig(){ + gogsPost('get_gogs_conf', '', function(data){ + var rrdata = $.parseJSON(data.data); + if (!rrdata.status){ + layer.msg(rrdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + var rdata = rrdata.data; + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '140'; + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = ''; + break; + case 1: + var selected_1 = (rdata[i].value == 'On') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'Off') ? 'selected' : ''; + ibody = '' + break; + case 2: + var selected_1 = (rdata[i].value == 'true') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'false') ? 'selected' : ''; + ibody = '' + break; + } + mlist += '

            ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

            ' + } + var html = '
            \ + ' + mlist + '\ +
            \ + \ +
            \ +
            '; + $(".soft-man-con").html(html); + }); +} + + +//提交PHP配置 +function submitGogsConf() { + var data = { + DOMAIN: $("input[name='DOMAIN']").val(), + ROOT_URL: $("input[name='ROOT_URL']").val(), + HTTP_ADDR: $("select[name='HTTP_ADDR']").val(), + HTTP_PORT: $("input[name='HTTP_PORT']").val(), + START_SSH_SERVER: $("select[name='START_SSH_SERVER']").val() || 'false', + SSH_PORT: $("input[name='SSH_PORT']").val(), + REQUIRE_SIGNIN_VIEW: $("select[name='REQUIRE_SIGNIN_VIEW']").val() || 'false', + ENABLE_CAPTCHA: $("select[name='ENABLE_CAPTCHA']").val() || 'true', + DISABLE_REGISTRATION: $("select[name='DISABLE_REGISTRATION']").val() || 'false', + ENABLE_NOTIFY_MAIL: $("select[name='ENABLE_NOTIFY_MAIL']").val() || 'false', + FORCE_PRIVATE: $("select[name='FORCE_PRIVATE']").val() || 'false', + SHOW_FOOTER_BRANDING: $("select[name='SHOW_FOOTER_BRANDING']").val() || 'false', + SHOW_FOOTER_VERSION: $("select[name='SHOW_FOOTER_VERSION']").val() || 'false', + SHOW_FOOTER_TEMPLATE_LOAD_TIME: $("select[name='SHOW_FOOTER_TEMPLATE_LOAD_TIME']").val() || 'false', + }; + + gogsPost('submit_gogs_conf', data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + gogsSetConfig(); + }); +} + +function gogsEditTpl(){ + gogsPost('gogs_edit_tpl',{} , function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + var edit = '

            通用的手动编辑:

            '; + edit +='
            \ + \ + \ +
            '; + $(".soft-man-con").html(edit); + }); + +} + +function gogsUserList(page, search) { + + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + gogsPost('user_list', _data, function(data){ + + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + content = '
            '; + content += '
            '; + + content += '
            '; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + + content += ''; + + ulist = rdata['data']['data']; + for (i in ulist){ + + var email = ulist[i]["email"] == '' ? '无' : ulist[i]["email"]; + var user_url = rdata['data']['root_url'] + ulist[i]["name"]; + content += ''+ + ''+ + ''+ + ''+ + ''; + } + + content += ''; + content += '
            序号用户或组织邮件地址操作(WEB管理)
            '+ulist[i]["id"]+''+ulist[i]["name"]+''+email+'项目管理
            '; + + var page_html = ''; + + content += page_html; + + $(".soft-man-con").html(content); + + $('.find_user').click(function(){ + var name = $('#find_user').val(); + gogsUserList(page, name); + }); + }); +} + +function userProjectList(user, search){ + layer.open({ + type: 1, + title: '用户('+user+')项目列表', + area: '500px', + content:"
            \ +
            \ +
            \ + \ + \ + \ +
            项目操作
            \ + \ +
            \ +
            \ +
            ", + success:function(){ + userProjectListPage(user,search); + } + }); +} + +function userProjectList2(user, search){ + layer.open({ + type: 1, + title: '用户('+user+')项目列表', + area: '500px', + content:"
            \ +
            \ +
            \ + \ + \ + \ +
            项目操作
            \ + \ +
            \ +
            \ +
            ", + success:function(){ + userProjectListPage(user,search); + } + }); +} + + +function userProjectListPage(user, search){ + var req = {}; + if (!isNaN(user)){ + req['page'] = user; + req['name'] = user = getCookie('gogsUserSelected'); + } else { + req['page'] = 1; + req['name'] = user; + setCookie('gogsUserSelected', user); + } + + req['page_size'] = 5; + req['search'] = ''; + if(typeof(search) != 'undefined'){ + req['search'] = search; + } + + gogsPost('user_project_list', req, function(data){ + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e){} + + if (!rdata['status']){ + layer.msg(rdata['msg'], { icon: 2 }); + return; + } + + var list = ''; + // console.log(rdata); + var project_list = rdata['data']['data']; + for (i in project_list) { + var name = project_list[i]['name']; + list += '\ + '+name+'\ + \ +
            源码 | \ + 脚本\ + \ + '; + } + + $('#gogs_table tbody').html(list); + + var page = rdata['data']['list']; + $('#gogs_table .gogs_page').html(page); + }); +} + + +function gogsRepoListPage(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + gogsPost('repo_list', _data, function(data){ + + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var ulist = rdata['data']['data']; + var body = '' + for (i in ulist){ + // console.log(ulist[i]); + + var option = ''; + if(ulist[i]['has_hook']){ + option += '卸载脚本' + ' | '; + option += '重载' + ' | '; + option += '编辑' + ' | '; + option += '日志' + ' | '; + option += '手动' + ' | '; + option += '自定义'; + } else{ + option += '加载脚本'; + } + + + body += ''+ulist[i]["id"]+''+ + '' + ulist[i]["name"]+''+ + '' + ulist[i]["repo"]+''+ + '' + + '源码' + ' | ' + + option + + '' + + ''; + } + + $('#repo_list tbody').html(body); + $('#repo_list_page').html(rdata['data']['list']); + + $('.find_repo').click(function(){ + var find_repo = $('#find_repo').val(); + gogsRepoListPage(page, find_repo); + }); + + + $('#repo_list .load').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_load', {'user':user,'name':name}, function(data){ + if (data.data != 'ok'){ + layer.msg(data.data,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + layer.msg('加载成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + setTimeout(function(){ + gogsRepoListPage(page, search); + }, 2000); + }); + }); + + $('#repo_list .unload').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_unload', {'user':user,'name':name}, function(data){ + if (data.data != 'ok'){ + layer.msg(data.data,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + layer.msg('卸载成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + setTimeout(function(){ + gogsRepoListPage(page, search); + }, 2000); + }); + }); + + $('#repo_list .edit').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_edit', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['status']){ + onlineEditFile(0, rdata['data']['path']); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + + + $('#repo_list .debug').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_debug', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['status']){ + onlineEditFile(0, rdata['path']); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + + + $('#repo_list .run').click(function(){ + var i = $(this).data('index'); + var user = ulist[i]["name"]; + var name = ulist[i]["repo"]; + + gogsPost('project_script_run', {'user':user,'name':name}, function(data){ + var data = $.parseJSON(data.data); + layer.msg(data.msg,{icon:data.status?1:2,time:2000,shade: [0.3, '#000']}); + }); + }); + + //--------- + }); +} + + +function gogsRepoList() { + content = '
            '; + content += '
            '; + + content += '
            '; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + + content += ''; + content += '
            序号用户/组织项目名操作
            '; + + var page = ''; + + content += page; + + $(".soft-man-con").html(content); + + gogsRepoListPage(1); +} + + + +function projectScript(user, name,has_hook){ + // console.log(user,name,has_hook); + var html = ''; + if (has_hook){ + html += ''; + html += ''; + html += ''; + html += ''; + } else { + html += ''; + } + + var loadOpen = layer.open({ + type: 1, + title: '['+user+']['+name+']脚本设置', + area: '240px', + content:'
            '+html+'
            ' + }); +} + +function projectScriptEdit(user,name){ + gogsPost('project_script_edit', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['status']){ + onlineEditFile(0, rdata['data']['path']); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); +} + +function projectScriptLoad(user,name){ + gogsPost('project_script_load', {'user':user,'name':name}, function(data){ + if (data.data != 'ok'){ + layer.msg(data.data,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + layer.msg('加载成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + setTimeout(function(){ + gogsRepoListPage(1); + }, 2000); + }); +} + +function projectScriptUnload(user,name){ + gogsPost('project_script_unload', {'user':user,'name':name}, function(data){ + if (data.data != 'ok'){ + layer.msg(data.data,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + layer.msg('卸载成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + setTimeout(function(){ + gogsRepoListPage(1); + }, 2000); + }); +} + +function projectScriptRun(user, name){ + gogsPost('project_script_run', {'user':user,'name':name}, function(data){ + var data = $.parseJSON(data.data); + layer.msg(data.msg,{icon:data.code?2:1,time:2000,shade: [0.3, '#000']}); + }); +} + +function projectScriptDebug(user,name){ + gogsPost('project_script_debug', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['status']){ + onlineEditFile(0, rdata['path']); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); +} + + +function projectScriptSelfRender(user, name){ + gogsPost('project_script_self', {'user':user,'name':name}, function(data){ + var rdata = $.parseJSON(data.data); + + var data = rdata['data']['data']; + + if (rdata['data']['self_hook']){ + $('#open_script').prop('checked',true); + } + + var body = ''; + if(data.length == 0 ){ + body += '无脚本数据'; + } else{ + for (var i = 0; i < data.length; i++) { + var b_status = '已使用'; + if (data[i]["is_hidden"]){ + b_status = '已隐藏'; + } + + body += ''+ + '' + data[i]["name"]+''+ + '' + b_status + ''+ + '' + + '删除' + ' | ' + + '编辑' + ' | ' + + '日志' + ' | ' + + '手动' + ' | ' + + '重命名' + + ''; + } + + } + + $('#gogs_self_table tbody').html(body); + $('#gogs_self_table .page').html(rdata['data']['list']); + + $('#gogs_self_table .status').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + var status = '1'; + if (data[i]["is_hidden"]){ + status = '0'; + } + gogsPost('project_script_self_status', {'user':user,'name':name,'file':file, status:status}, function(data){ + var data = $.parseJSON(data.data); + showMsg(data.msg ,function(){ + projectScriptSelfRender(user, name); + },{icon:data.code?2:1,time:2000,shade: [0.3, '#000']},2000); + }); + }); + + $('#gogs_self_table .del').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + gogsPost('project_script_self_del', {'user':user,'name':name,'file':file}, function(data){ + var data = $.parseJSON(data.data); + showMsg(data.msg ,function(){ + projectScriptSelfRender(user, name); + },{icon:data.code?2:1,time:2000,shade: [0.3, '#000']},2000); + }); + }); + + $('#gogs_self_table .edit').click(function(){ + var i = $(this).data('index'); + var path = data[i]["path"]; + onlineEditFile(0,path); + }); + + $('#gogs_self_table .logs').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + gogsPost('project_script_self_logs', {'user':user,'name':name,'file':file}, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + if (rdata['status']){ + onlineEditFile(0, rdata['data']['path']); + } else { + layer.msg(rdata.msg,{icon:data.status?2:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + + $('#gogs_self_table .run').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + if (data[i]["is_hidden"]){ + layer.msg("已经禁用,不能执行!",{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + gogsPost('project_script_self_run', {'user':user,'name':name,'file':file}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:data.status?1:2,time:2000,shade: [0.3, '#000']}); + }); + }); + + + $('#gogs_self_table .rename').click(function(){ + var i = $(this).data('index'); + var file = data[i]["name"]; + + if (data[i]["is_hidden"]){ + layer.msg("已经禁用,不能执行!",{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + file = file.split('.sh')[0]; + + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '320px', + title: '重命名', + btn:['设置','关闭'], + content: '
            \ +
            \ + \ +
            \ +
            ', + success:function(){ + $("#newFileName").focus().keyup(function(e){ + if(e.keyCode == 13) $(".layui-layer-btn0").click(); + }); + }, + yes:function(){ + var n_file = $("#newFileName").val(); + var o_file = file; + + gogsPost('project_script_self_rename', {'user':user,'name':name,'o_file':o_file,'n_file':n_file}, function(data){ + var data = $.parseJSON(data.data); + showMsg(data.msg ,function(){ + $(".layui-layer-btn1").click(); + projectScriptSelfRender(user, name); + },{icon:data.code?2:1,time:2000,shade: [0.3, '#000']},2000); + }); + } + }); + //----- + }); + //------ + }); +} + +//新建文件 +function createScriptFile(type, user, name, file) { + if (type == 1) { + gogsPost('project_script_self_create', {'user':user,'name':name,'file': file }, function(data){ + var rdata = $.parseJSON(data.data); + if(!rdata['status']){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + showMsg(rdata.msg, function(){ + $(".layui-layer-btn1").click(); + onlineEditFile(0,rdata['data']['abs_file']); + projectScriptSelfRender(user, name); + }, {icon:1,shade: [0.3, '#000']},2000); + }); + return; + } + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '320px', + title: '新建自定义脚本', + btn:['新建','关闭'], + content: '
            \ +
            \ + \ +
            \ +
            ', + success:function(){ + $("#newFileName").focus().keyup(function(e){ + if(e.keyCode == 13) $(".layui-layer-btn0").click(); + }); + }, + yes:function(){ + var file = $("#newFileName").val();; + createScriptFile(1, user, name, file); + } + }); +} + +function projectScriptSelf(user, name){ + layer.open({ + type: 1, + title: '项目('+user+'/'+name+')自定义脚本', + area: '500px', + content:"
            \ + \ +
            \ + 开启自定义脚本\ + \ + \ +
            \ +
            \ + \ + \ + \ +
            脚本文件名状态操作
            \ + \ +
            \ +
            ", + success:function(){ + projectScriptSelfRender(user, name); + + $('#create_script').click(function(){ + createScriptFile(0, user, name); + }); + + $('#script_hook_enable').click(function(){ + var enable = $('#open_script').prop('checked'); + var enable_option = '0'; + if (!enable){ + enable_option = '1'; + } + gogsPost('project_script_self_enable', {'user':user,'name':name,'enable':enable_option}, function(data){ + var data = $.parseJSON(data.data); + showMsg(data.msg ,function(){ + projectScriptSelfRender(user, name); + },{icon:data.status?1:2,shade: [0.3, '#000']},2000); + }); + + }); + } + }); +} + +function getRsaPublic(){ + gogsPost('get_rsa_public', {}, function(data){ + var rdata = $.parseJSON(data.data); + var con = '
            \ +
            \ + \ +
            \ +
              \ +
              ' + layer.open({ + type: 1, + area: "600px", + title: '本机公钥', + closeBtn: 2, + shift: 5, + shadeClose: false, + content:con + }); + }); +} + +function gogsRead(){ + + var readme = '
                '; + readme += '
              • 默认使用MySQL,第一个启动加载各种配置,并修改成正确的数据库配置
              • '; + readme += '
              • 邮件端口使用456,gogs仅支持使用STARTTLS的SMTP协议
              • '; + readme += '
              • 项目【加载脚本】后,会自动同步到wwwroot目录下
              • '; + readme += '
              • 点击查看本机公钥
              • '; + readme += '
              '; + + $('.soft-man-con').html(readme); +} \ No newline at end of file diff --git a/plugins/gorse/LICENSE b/plugins/gorse/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/plugins/gorse/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/gorse/README.md b/plugins/gorse/README.md new file mode 100644 index 000000000..4a6bbba3e --- /dev/null +++ b/plugins/gorse/README.md @@ -0,0 +1,6 @@ +# gorse +推荐系统 + + +# 脚本安装 +cd /www/server/mdserver-web/plugins && rm -rf gorse && git clone https://github.com/mw-plugin/gorse && cd gorse && rm -rf .git && cd /www/server/mdserver-web/plugins/gorse && bash install.sh install 0.4.15 \ No newline at end of file diff --git a/plugins/gorse/config/gorse.toml b/plugins/gorse/config/gorse.toml new file mode 100644 index 000000000..626db045c --- /dev/null +++ b/plugins/gorse/config/gorse.toml @@ -0,0 +1,92 @@ +[database] +cache_store = "{$CONFIG_REDIS}" +data_store = "mongodb://127.0.0.1:27017/gorse" +#data_store = "mysql://gorse:gorse@tcp(127.0.0.1:33206)/gorse" + + +table_prefix = "gorse_" +cache_table_prefix = "gorse_" +data_table_prefix = "gorse_" + +[master] +port = 8086 +host = "0.0.0.0" +http_port = 8088 +http_host = "0.0.0.0" +http_cors_domains = [] +http_cors_methods = [] +n_jobs = 1 +meta_timeout = "10s" +dashboard_user_name = "{$CONFIG_ADMIN}" +dashboard_password = "{$CONFIG_PASS}" +admin_api_key = "" + +[server] +default_n = 10 +api_key = "" +clock_error = "5s" +auto_insert_user = true +auto_insert_item = true +cache_expire = "10s" + +[recommend] +cache_size = 100 +cache_expire = "72h" + +[recommend.data_source] +positive_feedback_types = ["star","like"] +read_feedback_types = ["read"] +positive_feedback_ttl = 0 +item_ttl = 0 + +[recommend.popular] +popular_window = "720h" + +[recommend.user_neighbors] +neighbor_type = "similar" +enable_index = true +index_recall = 0.8 +index_fit_epoch = 3 + +[recommend.item_neighbors] +neighbor_type = "similar" +enable_index = true +index_recall = 0.8 +index_fit_epoch = 3 + +[recommend.collaborative] +enable_index = true +index_recall = 0.9 +index_fit_epoch = 3 +model_fit_period = "60m" +model_search_period = "360m" +model_search_epoch = 100 +model_search_trials = 10 +enable_model_size_search = false + +[recommend.replacement] +enable_replacement = false +positive_replacement_decay = 0.8 +read_replacement_decay = 0.6 + +[recommend.offline] +check_recommend_period = "1m" +refresh_recommend_period = "24h" +enable_latest_recommend = true +enable_popular_recommend = false +enable_user_based_recommend = true +enable_item_based_recommend = false +enable_collaborative_recommend = true +enable_click_through_prediction = true +explore_recommend = { popular = 0.1, latest = 0.2 } + +[recommend.online] +fallback_recommend = ["item_based", "latest"] +num_feedback_fallback_item_based = 10 + +[tracing] +enable_tracing = false +exporter = "jaeger" +collector_endpoint = "http://localhost:14268/api/traces" +sampler = "always" +ratio = 1 \ No newline at end of file diff --git a/plugins/gorse/ico.png b/plugins/gorse/ico.png new file mode 100644 index 000000000..01be3257a Binary files /dev/null and b/plugins/gorse/ico.png differ diff --git a/plugins/gorse/index.html b/plugins/gorse/index.html new file mode 100755 index 000000000..930ccfd72 --- /dev/null +++ b/plugins/gorse/index.html @@ -0,0 +1,31 @@ + + +
              +
              +
              +
              +

              服务

              +

              自启动

              +

              配置修改

              +

              常用功能

              +

              运行日志

              +

              相关说明

              + +
              +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/plugins/gorse/index.py b/plugins/gorse/index.py new file mode 100755 index 000000000..1276f6c10 --- /dev/null +++ b/plugins/gorse/index.py @@ -0,0 +1,381 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'gorse' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/gorse.toml" + return path + + +def getConfTpl(): + path = getPluginDir() + "/config/gorse.toml" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':',1) + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':',1) + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + # pid_file = getPidFile() + # if not os.path.exists(pid_file): + # return 'stop' + + cmd = "ps aux|grep gorse |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'" + data = mw.execShell(cmd) + + if data[0] == '': + return 'stop' + return 'start' + + +def initRedisConf(): + requirepass = "" + conf = mw.getServerDir() + '/redis/redis.conf' + content = mw.readFile(conf) + rep = r"^(requirepass)\s*([.0-9A-Za-z_& ~]+)" + tmp = re.search(rep, content, re.M) + if tmp: + requirepass = tmp.groups()[1] + + port = "6379" + rep = r"^(port)\s*([.0-9A-Za-z_& ~]+)" + tmp = re.search(rep, content, re.M) + if tmp: + port = tmp.groups()[1] + + return 'redis://:'+requirepass+'@127.0.0.1:'+port+'/3' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$CONFIG_ADMIN}', mw.getRandomString(6)) + content = content.replace('{$CONFIG_PASS}', mw.getRandomString(10)) + content = content.replace('{$CONFIG_REDIS}', initRedisConf()) + return content + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + mw.execShell("mkdir -p " + initD_path) + + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # log + dataLog = getServerDir() + '/data' + if not os.path.exists(dataLog): + mw.execShell('chmod +x ' + file_bin) + + # config replace + dst_conf = getServerDir() + '/gorse.toml' + dst_conf_init = getServerDir() + '/init.pl' + if not os.path.exists(dst_conf_init): + content = mw.readFile(getConfTpl()) + content = content.replace('{$SERVER_PATH}', service_path) + content = contentReplace(content) + mw.writeFile(dst_conf, content) + mw.writeFile(dst_conf_init, 'ok') + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def gorseOp(method): + file = initDreplace() + + # print(file) + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return gorseOp('start') + + +def stop(): + return gorseOp('stop') + + +def restart(): + status = gorseOp('restart') + log_file = runLog() + mw.execShell("echo '' > " + log_file) + return status + + +def reload(): + return gorseOp('reload') + + +def getPort(): + conf = getServerDir() + '/gorse.conf' + content = mw.readFile(conf) + + rep = "^(" + 'port' + ')\\s*([.0-9A-Za-z_& ~]+)' + tmp = re.search(rep, content, re.M) + if tmp: + return tmp.groups()[1] + + return '6379' + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + return getServerDir() + '/logs.pl' + +def getGorseInfo(): + conf_file = getConf() + content = mw.readFile(conf_file) + + rdata = {} + + rep = r'dashboard_user_name\s*=\s*"(.*)"' + tmp = re.search(rep, content) + tmp = re.search(rep, content, re.M) + if tmp: + rdata['dashboard_user_name'] = tmp.groups()[0] + + + rep = r'dashboard_password\s*=\s*"(.*)"' + tmp = re.search(rep, content) + tmp = re.search(rep, content, re.M) + if tmp: + rdata['dashboard_password'] = tmp.groups()[0] + + rep = r'http_port\s*=\s*(.*)' + tmp = re.search(rep, content) + tmp = re.search(rep, content, re.M) + if tmp: + rdata['http_port'] = tmp.groups()[0] + + rdata['ip'] = mw.getHostAddr() + return mw.returnJson(True,'ok', rdata) + +def installPreInspection(): + redis_path = mw.getServerDir() + "/redis" + if not os.path.exists(redis_path): + return "默认需要安装Redis" + + mongodb_path = mw.getServerDir() + "/mongodb" + if not os.path.exists(mongodb_path): + return "默认需要安装MongoDB" + + if not mw.isAppleSystem(): + glibc_ver = mw.getGlibcVersion() + if float(glibc_ver) < 2.32: + return '当前libc{}过低,需要大于2.31'.format(glibc_ver) + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'info': + print(getGorseInfo()) + else: + print('error') diff --git a/plugins/gorse/info.json b/plugins/gorse/info.json new file mode 100755 index 000000000..4810d2119 --- /dev/null +++ b/plugins/gorse/info.json @@ -0,0 +1,18 @@ +{ + "sort": 7, + "ps": "一款高效简单的推荐系统【单机】", + "name": "gorse", + "title": "Gorse", + "shell": "install.sh", + "versions":["0.4.15"], + "tip": "soft", + "install_pre_inspection":true, + "checks": "server/gorse", + "path": "server/gorse", + "display": 1, + "author": "midoks", + "date": "2024-06-12", + "home": "https://gorse.io/zh/docs/master/deploy/binary.html", + "type": 0, + "pid": "5" +} diff --git a/plugins/gorse/init.d/gorse.service.tpl b/plugins/gorse/init.d/gorse.service.tpl new file mode 100644 index 000000000..9e0c94cf5 --- /dev/null +++ b/plugins/gorse/init.d/gorse.service.tpl @@ -0,0 +1,13 @@ +[Unit] +Description=Gorse, an open source recommender system service written in Go. +After=network.target + +[Service] +Type=simple +Restart=always +ExecStart={$SERVER_PATH}/gorse/bin/gorse-in-one -c {$SERVER_PATH}/gorse/gorse.toml \ + --log-path {$SERVER_PATH}/gorse/gorse.log \ + --cache-path {$SERVER_PATH}/gorse/data/cache.data + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/gorse/init.d/gorse.tpl b/plugins/gorse/init.d/gorse.tpl new file mode 100644 index 000000000..be9c7bbcc --- /dev/null +++ b/plugins/gorse/init.d/gorse.tpl @@ -0,0 +1,57 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: Gorse Service + +### BEGIN INIT INFO +# Provides: Gorse +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts Gorse +# Description: starts the MDW-Web +### END INIT INFO + +# Simple Gorse init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +CONF="{$SERVER_PATH}/gorse/gorse.toml" + +APP_DIR={$SERVER_PATH}/gorse + +app_start(){ + echo "Starting Gorse server..." + echo "$APP_DIR/bin/gorse-in-one -c $CONF" + nohup $APP_DIR/bin/gorse-in-one -c $CONF >> {$SERVER_PATH}/gorse/logs.pl 2>&1 & +} + +app_stop(){ + echo "Stopping ..." + + find_gorse=`ps -ef | grep gorse | grep -v grep | awk "{print $2}"` + for p in ${find_gorse[@]} + do + kill -9 $p > /dev/null 2>&1 + done + + echo "Gorse stopped" +} + + +case "$1" in + start) + app_start + ;; + stop) + app_stop + ;; + restart|reload) + app_stop + sleep 0.3 + app_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/gorse/install.sh b/plugins/gorse/install.sh new file mode 100755 index 000000000..3edbb195e --- /dev/null +++ b/plugins/gorse/install.sh @@ -0,0 +1,95 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` +sysArch=`arch` + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/gorse && bash install.sh install 0.4.15 +# cd /www/server/mdserver-web/plugins/gorse && bash install.sh install 0.4.15 + +VERSION=$2 + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/source/gorse + + SYSNAME=linux + if [ "$sysName" == "Darwin" ];then + SYSNAME=darwin + fi + + ARCH="amd64" + if [ "$sysArch" == "x86_64" ];then + ARCH="amd64" + elif [ "$sysArch" == "aarch64" ];then + ARCH="arm64" + elif [ "$sysArch" == "arm64" ];then + ARCH="arm64" + else + ARCH="amd64" + fi + + FILE_TGZ=gorse_${SYSNAME}_${ARCH}.zip + GORSE_DIR=$serverPath/source/gorse + + # https://github.com/gorse-io/gorse/releases/download/v0.4.15/gorse_linux_amd64.zip + echo "https://github.com/gorse-io/gorse/releases/download/v${VERSION}/${FILE_TGZ}" + + if [ ! -f $GORSE_DIR/${FILE_TGZ} ];then + wget -O $GORSE_DIR/${FILE_TGZ} https://github.com/gorse-io/gorse/releases/download/v${VERSION}/${FILE_TGZ} + fi + + mkdir -p $serverPath/gorse/bin + mkdir -p $serverPath/gorse/logs + cd $GORSE_DIR && unzip -d $serverPath/gorse/bin ${FILE_TGZ} + + mkdir -p $serverPath/gorse/data + echo "${VERSION}" > $serverPath/gorse/version.pl + echo '安装Gorse完成' + cd ${rootPath} && python3 ${rootPath}/plugins/gorse/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/gorse/index.py initd_install + + if [ -d ${GORSE_DIR}/gorse-${VERSION} ];then + rm -rf ${GORSE_DIR}/gorse-${VERSION} + fi +} + +Uninstall_App() +{ + app_name=gorse + systemctl_dir=/lib/systemd/system + if [ -d /usr/lib/systemd/system ];then + systemctl_dir=/usr/lib/systemd/system + fi + + if [ -f ${systemctl_dir}/${app_name}.service ];then + systemctl stop ${app_name} + systemctl disable ${app_name} + rm -rf ${systemctl_dir}/${app_name}.service + systemctl daemon-reload + fi + + if [ -f $serverPath/${app_name}/initd/${app_name} ];then + $serverPath/${app_name}/initd/${app_name} stop + fi + + if [ -d $serverPath/${app_name} ];then + rm -rf $serverPath/${app_name} + fi + + echo "卸载Gorse成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/gorse/js/gorse.js b/plugins/gorse/js/gorse.js new file mode 100755 index 000000000..51dd8fb55 --- /dev/null +++ b/plugins/gorse/js/gorse.js @@ -0,0 +1,98 @@ +function gorsePost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'gorse'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function gorsePostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'gorse'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function gorseCommonFunc(){ + gorsePost('info', '', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var info = rdata['data']; + var con = '

              \ + 用户名\ + \ +

              '; + + con += '

              \ + 密码\ + \ +

              '; + con += '

              \ + 端口\ + \ +

              '; + + con += '

              \ + \ +

              '; + + $(".soft-man-con").html(con); + $('#open_url').click(function(){ + var url = 'http://' + info.ip + ':' + info.http_port; + window.open(url); + copyText(url); + }); + }); +} + +function gorseReadme(){ + + var readme = '
              '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/gorse/tpl/gorse.toml b/plugins/gorse/tpl/gorse.toml new file mode 100644 index 000000000..127bf91eb --- /dev/null +++ b/plugins/gorse/tpl/gorse.toml @@ -0,0 +1,252 @@ +[database] + +# The database for caching, support Redis, MySQL, Postgres and MongoDB: +# redis://:@:/ +# rediss://:@:/ +# redis+cluster://:@:,:,...,: +# postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full +# postgresql://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full +# mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] +# mongodb+srv://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] +cache_store = "redis://localhost:6379/0" + +# The database for persist data, support MySQL, Postgres, ClickHouse and MongoDB: +# mysql://[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] +# postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full +# postgresql://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full +# clickhouse://user:password@host[:port]/database?param1=value1&...¶mN=valueN +# chhttp://user:password@host[:port]/database?param1=value1&...¶mN=valueN +# chhttps://user:password@host[:port]/database?param1=value1&...¶mN=valueN +# mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] +# mongodb+srv://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] +data_store = "mysql://gorse:gorse_pass@tcp(localhost:3306)/gorse" + +# The naming prefix for tables (collections, keys) in databases. The default value is empty. +table_prefix = "" + +# The naming prefix for tables (collections, keys) in cache storage databases. The default value is `table_prefix`. +cache_table_prefix = "" + +# The naming prefix for tables (collections, keys) in data storage databases. The default value is `table_prefix`. +data_table_prefix = "" + +[master] + +# GRPC port of the master node. The default value is 8086. +port = 8086 + +# gRPC host of the master node. The default values is "0.0.0.0". +host = "0.0.0.0" + +# HTTP port of the master node. The default values is 8088. +http_port = 8088 + +# HTTP host of the master node. The default values is "0.0.0.0". +http_host = "0.0.0.0" + +# AllowedDomains is a list of allowed values for Http Origin. +# The list may contain the special wildcard string ".*" ; all is allowed +# If empty all are allowed. +http_cors_domains = [] + +# AllowedMethods is either empty or has a list of http methods names. Checking is case-insensitive. +http_cors_methods = [] + +# Number of working jobs in the master node. The default value is 1. +n_jobs = 1 + +# Meta information timeout. The default value is 10s. +meta_timeout = "10s" + +# Username for the master node dashboard. +dashboard_user_name = "" + +# Password for the master node dashboard. +dashboard_password = "" + +# Secret key for admin APIs (SSL required). +admin_api_key = "" + +[server] + +# Default number of returned items. The default value is 10. +default_n = 10 + +# Secret key for RESTful APIs (SSL required). +api_key = "" + +# Clock error in the cluster. The default value is 5s. +clock_error = "5s" + +# Insert new users while inserting feedback. The default value is true. +auto_insert_user = true + +# Insert new items while inserting feedback. The default value is true. +auto_insert_item = true + +# Server-side cache expire time. The default value is 10s. +cache_expire = "10s" + +[recommend] + +# The cache size for recommended/popular/latest items. The default value is 10. +cache_size = 100 + +# Recommended cache expire time. The default value is 72h. +cache_expire = "72h" + +[recommend.data_source] + +# The feedback types for positive events. +positive_feedback_types = ["star","like"] + +# The feedback types for read events. +read_feedback_types = ["read"] + +# The time-to-live (days) of positive feedback, 0 means disabled. The default value is 0. +positive_feedback_ttl = 0 + +# The time-to-live (days) of items, 0 means disabled. The default value is 0. +item_ttl = 0 + +[recommend.popular] + +# The time window of popular items. The default values is 4320h. +popular_window = "720h" + +[recommend.user_neighbors] + +# The type of neighbors for users. There are three types: +# similar: Neighbors are found by number of common labels. +# related: Neighbors are found by number of common liked items. +# auto: If a user have labels, neighbors are found by number of common labels. +# If this user have no labels, neighbors are found by number of common liked items. +# The default value is "auto". +neighbor_type = "similar" + +# Enable approximate user neighbor searching using vector index. The default value is true. +enable_index = true + +# Minimal recall for approximate user neighbor searching. The default value is 0.8. +index_recall = 0.8 + +# Maximal number of fit epochs for approximate user neighbor searching vector index. The default value is 3. +index_fit_epoch = 3 + +[recommend.item_neighbors] + +# The type of neighbors for items. There are three types: +# similar: Neighbors are found by number of common labels. +# related: Neighbors are found by number of common users. +# auto: If a item have labels, neighbors are found by number of common labels. +# If this item have no labels, neighbors are found by number of common users. +# The default value is "auto". +neighbor_type = "similar" + +# Enable approximate item neighbor searching using vector index. The default value is true. +enable_index = true + +# Minimal recall for approximate item neighbor searching. The default value is 0.8. +index_recall = 0.8 + +# Maximal number of fit epochs for approximate item neighbor searching vector index. The default value is 3. +index_fit_epoch = 3 + +[recommend.collaborative] + +# Enable approximate collaborative filtering recommend using vector index. The default value is true. +enable_index = true + +# Minimal recall for approximate collaborative filtering recommend. The default value is 0.9. +index_recall = 0.9 + +# Maximal number of fit epochs for approximate collaborative filtering recommend vector index. The default value is 3. +index_fit_epoch = 3 + +# The time period for model fitting. The default value is "60m". +model_fit_period = "60m" + +# The time period for model searching. The default value is "360m". +model_search_period = "360m" + +# The number of epochs for model searching. The default value is 100. +model_search_epoch = 100 + +# The number of trials for model searching. The default value is 10. +model_search_trials = 10 + +# Enable searching models of different sizes, which consume more memory. The default value is false. +enable_model_size_search = false + +[recommend.replacement] + +# Replace historical items back to recommendations. The default value is false. +enable_replacement = false + +# Decay the weights of replaced items from positive feedbacks. The default value is 0.8. +positive_replacement_decay = 0.8 + +# Decay the weights of replaced items from read feedbacks. The default value is 0.6. +read_replacement_decay = 0.6 + +[recommend.offline] + +# The time period to check recommendation for users. The default values is 1m. +check_recommend_period = "1m" + +# The time period to refresh recommendation for inactive users. The default values is 120h. +refresh_recommend_period = "24h" + +# Enable latest recommendation during offline recommendation. The default value is false. +enable_latest_recommend = true + +# Enable popular recommendation during offline recommendation. The default value is false. +enable_popular_recommend = false + +# Enable user-based similarity recommendation during offline recommendation. The default value is false. +enable_user_based_recommend = true + +# Enable item-based similarity recommendation during offline recommendation. The default value is false. +enable_item_based_recommend = false + +# Enable collaborative filtering recommendation during offline recommendation. The default value is true. +enable_collaborative_recommend = true + +# Enable click-though rate prediction during offline recommendation. Otherwise, results from multi-way recommendation +# would be merged randomly. The default value is false. +enable_click_through_prediction = true + +# The explore recommendation method is used to inject popular items or latest items into recommended result: +# popular: Recommend popular items to cold-start users. +# latest: Recommend latest items to cold-start users. +# The default values is { popular = 0.0, latest = 0.0 }. +explore_recommend = { popular = 0.1, latest = 0.2 } + +[recommend.online] + +# The fallback recommendation method is used when cached recommendation drained out: +# item_based: Recommend similar items to cold-start users. +# popular: Recommend popular items to cold-start users. +# latest: Recommend latest items to cold-start users. +# Recommenders are used in order. The default values is ["latest"]. +fallback_recommend = ["item_based", "latest"] + +# The number of feedback used in fallback item-based similar recommendation. The default values is 10. +num_feedback_fallback_item_based = 10 + +[tracing] + +# Enable tracing for REST APIs. The default value is false. +enable_tracing = false + +# The type of tracing exporters should be one of "jaeger", "zipkin", "otlp" and "otlphttp". The default value is "jaeger". +exporter = "jaeger" + +# The endpoint of tracing collector. +collector_endpoint = "http://localhost:14268/api/traces" + +# The type of tracing sampler should be one of "always", "never" and "ratio". The default value is "always". +sampler = "always" + +# The ratio of ratio based sampler. The default value is 1. +ratio = 1 \ No newline at end of file diff --git a/plugins/grafana/ico.png b/plugins/grafana/ico.png new file mode 100644 index 000000000..b923a0a8e Binary files /dev/null and b/plugins/grafana/ico.png differ diff --git a/plugins/grafana/index.html b/plugins/grafana/index.html new file mode 100755 index 000000000..1d8025759 --- /dev/null +++ b/plugins/grafana/index.html @@ -0,0 +1,31 @@ + + +
              +
              +
              +
              +

              服务

              +

              自启动

              +

              常用功能

              +

              配置

              +

              运行日志

              +

              相关说明

              + +
              +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/plugins/grafana/index.py b/plugins/grafana/index.py new file mode 100755 index 000000000..857b6b17b --- /dev/null +++ b/plugins/grafana/index.py @@ -0,0 +1,235 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'grafana' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/conf/defaults.ini" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + cmd = "ps aux|grep grafana |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + +def getInstallVerion(): + version_pl = getServerDir() + "/version.pl" + version = mw.readFile(version_pl).strip() + return version + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + return content + + +def openPort(): + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort('3000', 'grafana', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + return True + + +def initDreplace(): + # 初始化OP配置 + init_file = getServerDir() + '/init.pl' + if not os.path.exists(init_file): + openPort() + mw.writeFile(init_file, 'ok') + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return True + + +def gOp(method): + initDreplace() + + data = mw.execShell('systemctl ' + method + ' '+getPluginName()) + mw.execShell('systemctl ' + method + ' '+getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return gOp('start') + +def stop(): + return gOp('stop') + +def restart(): + return gOp('restart') + +def reload(): + return gOp('reload') + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + shell_cmd = 'systemctl status grafana|grep loaded|grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + data = mw.execShell('systemctl enable grafana') + if data[1] != '': + return data[1] + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + data = mw.execShell('systemctl disable grafana') + if data[1] != '': + return data[1] + return 'ok' + +def runLog(): + return getServerDir() + "/data/log/grafana.log" + +def grafanaUrl(): + ip = mw.getLocalIp() + return 'http://'+ip+':'+"3000" + +def installPreInspection(): + return 'ok' + + +def uninstallPreInspection(): + return 'ok' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'grafana_url': + print(grafanaUrl()) + else: + print('error') diff --git a/plugins/grafana/info.json b/plugins/grafana/info.json new file mode 100755 index 000000000..384881392 --- /dev/null +++ b/plugins/grafana/info.json @@ -0,0 +1,17 @@ +{ + "sort": 7, + "ps": "Grafana是查询、可视化、警报观测平台", + "name": "grafana", + "title": "grafana", + "shell": "install.sh", + "versions":["12.1.0"], + "tip": "soft", + "checks": "server/grafana", + "path": "server/grafana", + "display": 1, + "author": "midoks", + "date": "2025-08-01", + "home": "https://grafana.com/grafana/download", + "type": 0, + "pid": "5" +} diff --git a/plugins/grafana/init.d/grafana.service.tpl b/plugins/grafana/init.d/grafana.service.tpl new file mode 100644 index 000000000..e64766073 --- /dev/null +++ b/plugins/grafana/init.d/grafana.service.tpl @@ -0,0 +1,13 @@ +[Unit] +Description=Grafana instance +Documentation=http://docs.grafana.org +After=network-online.target +[Service] +Type=simple +User=grafana +Group=grafana +Restart=on-failure +ExecStart={$SERVER_PATH}/bin/grafana server --config={$SERVER_PATH}/grafana/conf/defa.defaults --homepath={$SERVER_PATH}/grafana + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/grafana/install.sh b/plugins/grafana/install.sh new file mode 100755 index 000000000..05f459098 --- /dev/null +++ b/plugins/grafana/install.sh @@ -0,0 +1,85 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://www.cnblogs.com/n00dle/p/16916044.html +# cd /www/server/mdserver-web/plugins/grafana && /bin/bash install.sh install 12.1.0 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/grafana/index.py start + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +sysArch=`arch` +sysName=`uname` +echo "use system: ${sysName}" + +OSNAME=`bash ${rootPath}/scripts/getos.sh` +if [ "" == "$OSNAME" ];then + OSNAME=`cat ${rootPath}/data/osname.pl` +fi + +if [ "macos" == "$OSNAME" ];then + echo "不支持Macox" + exit +fi + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +if id grafana &> /dev/null ;then + echo "grafana uid is `id -u grafana`" + echo "grafana shell is `grep "^grafana:" /etc/passwd |cut -d':' -f7 `" +else + groupadd grafana + useradd -g grafana -s /bin/bash grafana +fi + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/grafana + + mkdir -p $serverPath/grafana + echo "${VERSION}" > $serverPath/grafana/version.pl + + shell_file=${curPath}/versions/${VERSION}/linux.sh + + if [ -f $shell_file ];then + bash -x $shell_file install ${VERSION} + else + echo '不支持...' + exit 1 + fi + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/grafana/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/grafana/index.py initd_install + + echo 'Grafana安装完成' +} + +Uninstall_App() +{ + shell_file=${curPath}/versions/${VERSION}/linux.sh + if [ -f $shell_file ];then + bash -x $shell_file uninstall ${VERSION} + fi + + cd ${rootPath} && python3 ${rootPath}/plugins/grafana/index.py stop + cd ${rootPath} && python3 ${rootPath}/plugins/grafana/index.py initd_uninstall + + rm -rf $serverPath/grafana + echo 'Grafana卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/grafana/js/grafana.js b/plugins/grafana/js/grafana.js new file mode 100755 index 000000000..5beae8d3b --- /dev/null +++ b/plugins/grafana/js/grafana.js @@ -0,0 +1,97 @@ +function gPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'grafana'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function gPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'grafana'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function gCommonFunc(){ + var con = '

              \ + \ +

              '; + + $(".soft-man-con").html(con); + + $('#grafana_url').click(function(){ + gPost('grafana_url', '', {}, function(rdata){ + layer.open({ + title: "Grafana连接", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
              \ +
              \ +
              '+rdata.data+'
              \ +
              \ +
              ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); + }); +} + + +function gReadme(){ + var readme = '
                '; + readme += '
              • 初始化账户:admin/admin
              • '; + readme += '
              • https://grafana.com/grafana/download
              • '; + readme += '
              '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/grafana/versions/12.1.0/linux.sh b/plugins/grafana/versions/12.1.0/linux.sh new file mode 100644 index 000000000..8f1c1ae87 --- /dev/null +++ b/plugins/grafana/versions/12.1.0/linux.sh @@ -0,0 +1,61 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +VERSION=$2 + +sysArch=`arch` +sysName=`uname` + +ARCH_NAME=amd64 +if [ "$sysArch" == "arm64" ];then + ARCH_NAME=arm64 +elif [ "$sysArch" == "x86_64" ]; then + ARCH_NAME=amd64 +elif [ "$sysArch" == "aarch64" ]; then + ARCH_NAME=aarch64 +fi + +FILE_TGZ=grafana-${VERSION}.linux-${ARCH_NAME}.tar.gz + +# 检查是否通 +Install_App() +{ + SourceDir=$serverPath/source/grafana + InstallDir=$serverPath/grafana + mkdir -p ${SourceDir} + mkdir -p ${InstallDir} + + if [ ! -f ${SourceDir}/${FILE_TGZ} ];then + wget --no-check-certificate -O ${SourceDir}/${FILE_TGZ} https://dl.grafana.com/oss/release/${FILE_TGZ} + fi + + if [ ! -d $InstallDir/bin/grafana ];then + cd ${SourceDir} && tar -zxvf ${FILE_TGZ} + cd ${SourceDir}/grafana-v* + cp -rf ./* $InstallDir + fi + + chown -R grafana:grafana $InstallDir +} + +Uninstall_App() +{ + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/haproxy/conf/haproxy.conf b/plugins/haproxy/conf/haproxy.conf new file mode 100755 index 000000000..4ef8a87d8 --- /dev/null +++ b/plugins/haproxy/conf/haproxy.conf @@ -0,0 +1,39 @@ +global + daemon + #一般情况下,可关闭 + log 127.0.0.1 local0 + pidfile /tmp/haproxy.pid + maxconn 4000 + user haproxy + group haproxy + +defaults + mode http + log global + option httplog + timeout connect 10s + timeout client 15s + timeout server 15s + + +listen stats + mode http + bind *:10800 + stats enable + stats refresh 10 + stats uri /haproxy + stats realm Haproxy\ Statistics + stats auth admin:admin + +frontend http-in + bind *:1080 + default_backend servers + option forwardfor + #option httpclose + option http-keep-alive + +backend servers + balance roundrobin + server web1 0.0.0.0:9090 check inter 2000 rise 2 fall 5 + + diff --git a/plugins/haproxy/ico.png b/plugins/haproxy/ico.png new file mode 100644 index 000000000..3fc4e8c6d Binary files /dev/null and b/plugins/haproxy/ico.png differ diff --git a/plugins/haproxy/index.html b/plugins/haproxy/index.html new file mode 100755 index 000000000..aa1da8702 --- /dev/null +++ b/plugins/haproxy/index.html @@ -0,0 +1,18 @@ +
              +
              +
              +

              服务

              +

              自启动

              +

              配置修改

              +

              运行日志

              +
              +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/plugins/haproxy/index.py b/plugins/haproxy/index.py new file mode 100755 index 000000000..65812d034 --- /dev/null +++ b/plugins/haproxy/index.py @@ -0,0 +1,253 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import string +import subprocess + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'haproxy' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConfTpl(): + path = getPluginDir() + "/conf/haproxy.conf" + return path + + +def getConf(): + path = getServerDir() + "/haproxy.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$HA_USER}', mw.getRandomString(8)) + content = content.replace('{$HA_PWD}', mw.getRandomString(10)) + content = content.replace('{$SERVER_APP}', service_path + '/haproxy') + return content + + +def status(): + data = mw.execShell( + "ps -ef|grep haproxy |grep -v grep | grep -v python | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # config replace + conf_bin = getConf() + if not os.path.exists(conf_bin): + conf_content = mw.readFile(getConfTpl()) + conf_content = contentReplace(conf_content) + mw.writeFile(getServerDir() + '/haproxy.conf', conf_content) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/haproxy.service' + systemServiceTpl = getPluginDir() + '/init.d/haproxy.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def haOp(method): + file = initDreplace() + + # check config + sdir = getServerDir() + cmd_check = sdir+'/sbin/haproxy -c -f ' + sdir + '/haproxy.conf' + chk_data = mw.execShell(cmd_check) + if chk_data[1]!= '': + return chk_data[1] + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' haproxy') + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return haOp('start') + + +def stop(): + return haOp('stop') + + +def restart(): + return haOp('restart') + + +def reload(): + return haOp('reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status haproxy | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable haproxy') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable haproxy') + return 'ok' + + +def runLog(): + path = getServerDir() + "/haproxy.log" + return path + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(getConf()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'run_log': + print(runLog()) + else: + print('error') diff --git a/plugins/haproxy/info.json b/plugins/haproxy/info.json new file mode 100755 index 000000000..98700f813 --- /dev/null +++ b/plugins/haproxy/info.json @@ -0,0 +1,19 @@ +{ + "sort": 7, + "ps": "可靠、高性能的TCP/HTTP负载平衡器", + "name": "haproxy", + "title": "haproxy", + "shell": "install.sh", + "versions":["2.6"], + "updates":["2.6"], + "tip": "soft", + "checks": "server/haproxy", + "path": "server/haproxy", + "display": 1, + "author": "midoks", + "date": "2022-08-26", + "home": "https://www.haproxy.org/", + "doc1": "https://www.haproxy.org/", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/haproxy/init.d/haproxy.service.tpl b/plugins/haproxy/init.d/haproxy.service.tpl new file mode 100644 index 000000000..d14a5530a --- /dev/null +++ b/plugins/haproxy/init.d/haproxy.service.tpl @@ -0,0 +1,13 @@ +[Unit] +Description=he Reliable, High Performance TCP/HTTP Load Balancer +After=network.target + +[Service] +Type=forking +ExecStartPre={$SERVER_PATH}/haproxy/sbin/haproxy -c -f {$SERVER_PATH}/haproxy/haproxy.conf +ExecStart={$SERVER_PATH}/haproxy/sbin/haproxy -D -f {$SERVER_PATH}/haproxy/haproxy.conf +ExecReload={$SERVER_PATH}/haproxy/sbin/haproxy -f {$SERVER_PATH}/haproxy/haproxy.conf -c -q +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/haproxy/init.d/haproxy.tpl b/plugins/haproxy/init.d/haproxy.tpl new file mode 100644 index 000000000..7309b1dbe --- /dev/null +++ b/plugins/haproxy/init.d/haproxy.tpl @@ -0,0 +1,119 @@ +#!/bin/sh +# +# chkconfig: - 85 15 +# description: HA-Proxy is a TCP/HTTP reverse proxy which is particularly suited \ +# for high availability environments. +# processname: haproxy +# config: /etc/haproxy/haproxy.cfg +# pidfile: /var/run/haproxy.pid +# Script Author: Simon Matter +# Version: 2004060600 +# Source function library. + +if [ -f /etc/init.d/functions ]; then + . /etc/init.d/functions +elif [ -f /etc/rc.d/init.d/functions ] ; then + . /etc/rc.d/init.d/functions +else + echo ".." +fi + +# Source networking configuration. +if [ -f /etc/sysconfig/network ];then + . /etc/sysconfig/network +fi + +# Check that networking is up. +# [ ${NETWORKING} = "no" ] && exit 0 + +HAPROXYDIR={$SERVER_PATH}/haproxy +BASENAME=haproxy + +# This is our service name + +#BASENAME=`basename $0` +#if [ -L $0 ]; then +# BASENAME=`find $0 -name $BASENAME -printf %l` +# BASENAME=`basename $BASENAME` +#fi + +[ -f $HAPROXYDIR/etc/$BASENAME.conf ] || exit 1 + +RETVAL=0 + +start() { + $HAPROXYDIR/sbin/$BASENAME -c -q -f $HAPROXYDIR/etc/$BASENAME.conf + + + if [ $? -ne 0 ]; then + echo "Errors found in configuration file, check it with '$BASENAME check'." + return 1 + fi + + echo -n "Starting $BASENAME: " + daemon $HAPROXYDIR/sbin/$BASENAME -D -f $HAPROXYDIR/etc/$BASENAME.conf + RETVAL=$? + # echo + # [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$BASENAME + return $RETVAL +} + + +stop() { + + echo -n "Shutting down $BASENAME: " + killproc $BASENAME -USR1 + + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$BASENAME + [ $RETVAL -eq 0 ] && rm -f /var/run/$BASENAME.pid + return $RETVAL +} + +restart() { + $HAPROXYDIR/sbin/$BASENAME -c -q -f $HAPROXYDIR/etc/$BASENAME.conf + if [ $? -ne 0 ]; then + echo "Errors found in configuration file, check it with '$BASENAME check'." + return 1 + fi + stop + start +} + +reload() { + $HAPROXYDIR/sbin/$BASENAME -c -q -f $HAPROXYDIR/etc/$BASENAME.conf + if [ $? -ne 0 ]; then + echo "Errors found in configuration file, check it with '$BASENAME check'." + return 1 + fi + $HAPROXYDIR/sbin/$BASENAME -D -f $HAPROXYDIR/$BASENAME.conf -p /var/run/$BASENAME.pid -sf $(cat /var/run/$BASENAME.pid) +} + +check() { + $HAPROXYDIR/sbin/$BASENAME -c -q -V -f $HAPROXYDIR/etc/$BASENAME.conf +} + +rhstatus() { + status $BASENAME +} + +condrestart() { + [ -e /var/lock/subsys/$BASENAME ] && restart || : +} + +# See how we were called. +case "$1" in + start) start ;; + stop) stop ;; + restart) restart;; + reload) reload;; + condrestart) condrestart ;; + status) rhstatus ;; + check) check ;; + *) + echo $"Usage: $BASENAME {start|stop|restart|reload|condrestart|status|check}" + exit 1 +esac + +exit $? \ No newline at end of file diff --git a/plugins/haproxy/install.sh b/plugins/haproxy/install.sh new file mode 100755 index 000000000..8844a1df8 --- /dev/null +++ b/plugins/haproxy/install.sh @@ -0,0 +1,52 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + +# for mac +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +action=$1 +type=$2 + +echo $action $type + +if id haproxy &> /dev/null ;then + echo "haproxy UID is `id -u haproxy`" + echo "haproxy Shell is `grep "^haproxy:" /etc/passwd |cut -d':' -f7 `" +else + groupadd haproxy + useradd -g haproxy haproxy +fi + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + +if [ "${action}" == "uninstall" ];then + + if [ -f /usr/lib/systemd/system/haproxy.service ] || [ -f /lib/systemd/system/haproxy.service ];then + systemctl stop haproxy + systemctl disable haproxy + rm -rf /usr/lib/systemd/system/haproxy.service + rm -rf /lib/systemd/system/haproxy.service + systemctl daemon-reload + fi +fi + +sh -x $curPath/versions/$2/install.sh $1 + +if [ "${action}" == "install" ] && [ -d $serverPath/haproxy ];then + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/haproxy/index.py start ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/haproxy/index.py initd_install ${type} +fi diff --git a/plugins/haproxy/js/haproxy.js b/plugins/haproxy/js/haproxy.js new file mode 100755 index 000000000..be9cd8725 --- /dev/null +++ b/plugins/haproxy/js/haproxy.js @@ -0,0 +1,53 @@ +function spPostMin(method, args, callback){ + + var req_data = {}; + req_data['name'] = 'haproxy'; + req_data['func'] = method; + + if (typeof(args) != 'undefined' && args!=''){ + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:10000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function spPost(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + spPostMin(method,args,function(data){ + layer.close(loadT); + if(typeof(callback) == 'function'){ + callback(data); + } + }); +} + + +function secToTime(s) { + var t; + if(s > -1){ + var hour = Math.floor(s/3600); + var min = Math.floor(s/60) % 60; + var sec = s % 60; + if(hour < 10) { + t = '0'+ hour + ":"; + } else { + t = hour + ":"; + } + + if(min < 10){t += "0";} + t += min + ":"; + if(sec < 10){t += "0";} + t += sec.toFixed(2); + } + return t; +} + diff --git a/plugins/haproxy/tpl/default.tpl b/plugins/haproxy/tpl/default.tpl new file mode 100644 index 000000000..1d7f63798 --- /dev/null +++ b/plugins/haproxy/tpl/default.tpl @@ -0,0 +1,37 @@ +global + daemon + pidfile /tmp/haproxy.pid + maxconn 4000 + user haproxy + group haproxy + + +defaults + mode http + log global + option httplog + timeout connect 10s + timeout client 15s + timeout server 15s + + +listen stats + mode http + bind *:10800 + stats enable + stats refresh 10 + stats uri /haproxy + stats realm Haproxy\ Statistics + stats auth {$HA_USER}:{$HA_PWD} + +frontend http-in + bind *:1080 + default_backend servers + option forwardfor + option http-keep-alive + +backend servers + balance roundrobin + server web1 0.0.0.0:9090 check inter 2000 rise 2 fall 5 + + diff --git a/plugins/haproxy/tpl/window.tpl b/plugins/haproxy/tpl/window.tpl new file mode 100644 index 000000000..d7a7d04cc --- /dev/null +++ b/plugins/haproxy/tpl/window.tpl @@ -0,0 +1,38 @@ +global + daemon + pidfile /tmp/haproxy.pid + maxconn 4000 + user haproxy + group haproxy + + +defaults + mode http + log global + option httplog + timeout connect 10s + timeout client 15s + timeout server 15s + + +listen stats + mode http + bind *:10800 + stats enable + stats refresh 10 + stats uri /haproxy + stats realm Haproxy\ Statistics + stats auth {$HA_USER}:{$HA_PWD} + + +frontend rdp_frontend + bind *:3389 + mode tcp + option tcplog + default_backend rdp_backend + +backend rdp_backend + mode tcp + server rdp1 192.168.1.100:3389 check + + diff --git a/plugins/haproxy/versions/2.6/install.sh b/plugins/haproxy/versions/2.6/install.sh new file mode 100755 index 000000000..77d4ef230 --- /dev/null +++ b/plugins/haproxy/versions/2.6/install.sh @@ -0,0 +1,101 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +OSNAME_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=2.6.4 +MIN_VERSION=2.6 +Install_App() +{ + echo '正在安装Haproxy软件...' + mkdir -p $serverPath/haproxy + + APP_DIR=${serverPath}/source/haproxy + mkdir -p $APP_DIR + echo $MIN_VERSION > $serverPath/haproxy/version.pl + + LOCAL_ADDR=common + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + + if [ "${LOCAL_ADDR}" == "cn" ];then + if [ ! -f ${APP_DIR}/haproxy-${VERSION}.tar.gz ];then + wget -O ${APP_DIR}/haproxy-${VERSION}.tar.gz https://dl.midoks.icu/soft/haproxy/haproxy-${VERSION}.tar.gz + fi + fi + + + if [ ! -f ${APP_DIR}/haproxy-${VERSION}.tar.gz ];then + if [ $sysName == 'Darwin' ]; then + wget --no-check-certificate -O ${APP_DIR}/haproxy-${VERSION}.tar.gz https://www.haproxy.org/download/${MIN_VERSION}/src/haproxy-${VERSION}.tar.gz + else + curl -sSLo ${APP_DIR}/haproxy-${VERSION}.tar.gz https://www.haproxy.org/download/${MIN_VERSION}/src/haproxy-${VERSION}.tar.gz + fi + fi + + if [ ! -f ${APP_DIR}/haproxy-${VERSION}.tar.gz ];then + curl -sSLo ${APP_DIR}/haproxy-${VERSION}.tar.gz https://www.haproxy.org/download/${MIN_VERSION}/src/haproxy-${VERSION}.tar.gz + fi + + + cd ${APP_DIR} && tar -zxvf haproxy-${VERSION}.tar.gz + + if [ "$OSNAME" == "macos" ];then + cd ${APP_DIR}/haproxy-${VERSION} && make TARGET=osx && make install PREFIX=$serverPath/haproxy + else + cd ${APP_DIR}/haproxy-${VERSION} && make TARGET=linux-glibc && make install PREFIX=$serverPath/haproxy + fi + + echo '安装Haproxy成功' + + #Haproxy日志配置 + if [ -f /etc/rsyslog.conf ];then + find_ha=`cat /etc/rsyslog.conf | grep haproxy` + if [ "$find_ha" != "" ];then + echo $find_ha + else + echo "---------------------------------------------" + echo "" > ${serverPath}/haproxy/haproxy.log + echo "local0.* ${serverPath}/haproxy/haproxy.log" >> /etc/rsyslog.conf + systemctl restart syslog + echo "syslog默认的haproxy配置" + echo "local0.* ${serverPath}/haproxy/haproxy.log >> /etc/rsyslog.conf" + echo "---------------------------------------------" + fi + fi + + #删除解压源码 + if [ -d ${APP_DIR}/haproxy-${VERSION} ];then + rm -rf ${APP_DIR}/haproxy-${VERSION} + fi +} + +Uninstall_App() +{ + if [ -f $serverPath/haproxy/initd/haproxy ];then + $serverPath/haproxy/initd/haproxy stop + fi + + rm -rf $serverPath/haproxy + echo "卸载Haproxy成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/imail/LICENSE b/plugins/imail/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/plugins/imail/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/imail/ico.png b/plugins/imail/ico.png new file mode 100755 index 000000000..fd5d7fad1 Binary files /dev/null and b/plugins/imail/ico.png differ diff --git a/plugins/imail/index.html b/plugins/imail/index.html new file mode 100755 index 000000000..f2eb7fb60 --- /dev/null +++ b/plugins/imail/index.html @@ -0,0 +1,20 @@ +
              +
              +
              +

              服务

              +

              自启动

              +

              配置文件

              +

              运行日志

              +
              +
              +
              +
              +
              +
              + + \ No newline at end of file diff --git a/plugins/imail/index.py b/plugins/imail/index.py new file mode 100755 index 000000000..2a9177fd0 --- /dev/null +++ b/plugins/imail/index.py @@ -0,0 +1,222 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import socket +import json + +from datetime import datetime + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +class App: + __setupPath = '/www/server/imail' + __SR = '' + + def __init__(self): + self.__setupPath = self.getServerDir() + + self.__SR = '''#!/bin/bash + PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + export PATH + export USER=%s + export HOME=%s && ''' % ( self.getRunUser(), self.getHomeDir()) + + def getArgs(self): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + def __release_port(self, port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'IMail-Server', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + + def openPort(self): + for i in ["25", "110", "143", "465", "995", "993", "587"]: + self.__release_port(i) + return True + + def getPluginName(self): + return 'imail' + + def getPluginDir(self): + return mw.getPluginDir() + '/' + self.getPluginName() + + def getServerDir(self): + return mw.getServerDir() + '/' + self.getPluginName() + + def getInitdConfTpl(self): + path = self.getPluginDir() + "/init.d/imail.tpl" + return path + + def getHomeDir(self): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + else: + return '/root' + + def getRunUser(self): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + else: + return 'root' + + def status(self): + data = mw.execShell( + "ps -ef|grep " + self.getPluginName() + " |grep -v grep | grep -v python | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + + def contentReplace(self, content): + + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$RUN_USER}', self.getRunUser()) + content = content.replace('{$HOME_DIR}', self.getHomeDir()) + + return content + + def initDreplace(self): + + file_tpl = self.getInitdConfTpl() + service_path = mw.getServerDir() + + initD_path = self.getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + self.openPort() + + file_bin = initD_path + '/' + self.getPluginName() + + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = self.contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/imail.service' + systemServiceTpl = self.getPluginDir() + '/init.d/imail.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + log_path = self.getServerDir() + '/logs' + if not os.path.exists(log_path): + os.mkdir(log_path) + + return file_bin + + def imOp(self, method): + file = self.initDreplace() + + if not mw.isAppleSystem(): + cmd = 'systemctl {} {}'.format(method, self.getPluginName()) + data = mw.execShell(cmd) + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(self.__SR + file + ' ' + method) + if data[1] == '': + return 'ok' + return data[0] + + def start(self): + return self.imOp('start') + + def stop(self): + return self.imOp('stop') + + def restart(self): + return self.imOp('restart') + + def reload(self): + return self.imOp('reload') + + def initd_status(self): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + cmd = 'systemctl status imail | grep loaded | grep "enabled;"' + data = mw.execShell(cmd) + if data[0] == '': + return 'fail' + return 'ok' + + def initd_install(self): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable imail') + return 'ok' + + def initd_uinstall(self): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable imail') + return 'ok' + + def conf(self): + conf_path = self.getServerDir() + '/custom/conf/app.conf' + if not os.path.exists(conf_path): + return mw.returnJson(False, "请先安装初始化!
              默认地址:http://" + mw.getLocalIp() + ":1080") + + return self.getServerDir() + '/custom/conf/app.conf' + + def run_log(self): + ilog = self.getServerDir() + '/logs/imail.log' + if not os.path.exists(ilog): + return mw.returnJson(False, "请先安装初始化!
              默认地址:http://" + mw.getLocalIp() + ":1080") + + return self.getServerDir() + '/logs/imail.log' + + +if __name__ == "__main__": + func = sys.argv[1] + classApp = App() + try: + data = eval("classApp." + func + "()") + print(data) + except Exception as e: + print('error:' + str(e)) diff --git a/plugins/imail/info.json b/plugins/imail/info.json new file mode 100755 index 000000000..c2352c7cb --- /dev/null +++ b/plugins/imail/info.json @@ -0,0 +1,18 @@ +{ + "sort": 7, + "ps": "[DEV]简单邮件服务", + "name": "imail", + "title": "邮件服务", + "shell": "install.sh", + "versions":["0.0.19"], + "updates":["0.0.19"], + "tip": "soft", + "checks": "server/imail", + "path": "server/imail", + "display": 1, + "author": "midoks", + "date": "2022-09-26", + "home": "https://github.com/midoks/mdserver-web", + "type": 0, + "pid": "5" +} \ No newline at end of file diff --git a/plugins/imail/init.d/imail.service.tpl b/plugins/imail/init.d/imail.service.tpl new file mode 100644 index 000000000..af7c6a444 --- /dev/null +++ b/plugins/imail/init.d/imail.service.tpl @@ -0,0 +1,12 @@ +[Unit] +Description=Imail Simple Mail Server +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/imail/init.d/imail start +ExecStop={$SERVER_PATH}/imail/init.d/imail stop +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/plugins/imail/init.d/imail.tpl b/plugins/imail/init.d/imail.tpl new file mode 100644 index 000000000..3006e0944 --- /dev/null +++ b/plugins/imail/init.d/imail.tpl @@ -0,0 +1,89 @@ +#!/bin/bash +# chkconfig: 2345 55 25 +# description: Imail Service + +### BEGIN INIT INFO +# Provides: bt +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts Imail +# Description: starts the Imail +### END INIT INFO + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + +# Source function library. +if [ -f /etc/init.d/functions ];then + . /etc/init.d/functions +fi + +if [ -f /etc/rc.d/init.d/functions ];then + . /etc/rc.d/init.d/functions +fi + +app_path={$SERVER_PATH}/imail +SERVICENAME="imail" + +im_start(){ + isStart=`ps -ef|grep 'imail service' |grep -v grep|awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "Starting imail... \c" + cd $app_path && ${app_path}/imail service & + isStart="" + while [[ "$isStart" == "" ]]; + do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:25|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi + done + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo '------------------------------------------------------' + tail -n 20 ${app_path}/logs/run_away.log + echo '------------------------------------------------------' + echo -e "\033[31mError: ${SERVICENAME} service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "Starting ${SERVICENAME}(pid $(echo $isStart)) already running" + fi +} + +im_stop(){ + pids=`ps -ef|grep 'imail service' |grep -v grep|awk '{print $2}'` + arr=($pids) + echo -e "Stopping ${SERVICENAME}... \c" + for p in ${arr[@]} + do + kill -9 $p + done + echo -e "\033[32mdone\033[0m" +} + +im_status(){ + isStart=`ps -ef|grep 'imail service' |grep -v grep|awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "${SERVICENAME} not running" + else + echo -e "${SERVICENAME}(pid $(echo $isStart)) already running" + fi +} + +case "$1" in + 'start') im_start;; + 'stop') im_stop;; + 'status') im_status;; + 'reload') + im_stop + im_start;; + 'restart') + im_stop + im_start;; +esac \ No newline at end of file diff --git a/plugins/imail/install.sh b/plugins/imail/install.sh new file mode 100755 index 000000000..631a4a26f --- /dev/null +++ b/plugins/imail/install.sh @@ -0,0 +1,23 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +action=$1 +type=$2 + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + +sh -x $curPath/versions/$2/install.sh $1 diff --git a/plugins/imail/js/imail.js b/plugins/imail/js/imail.js new file mode 100755 index 000000000..56fc09190 --- /dev/null +++ b/plugins/imail/js/imail.js @@ -0,0 +1,95 @@ +var mail = { + plugin_name: 'imail', + init: function () { + var _this = this; + }, + + str2Obj:function(str){ + var data = {}; + kv = str.split('&'); + for(i in kv){ + v = kv[i].split('='); + data[v[0]] = v[1]; + } + return data; + }, + + send:function(info){ + var tips = info['tips']; + var method = info['method']; + var args = info['data']; + var callback = info['success']; + + var loadT = layer.msg(tips, { icon: 16, time: 0, shade: 0.3 }); + + var data = {}; + data['name'] = 'mail'; + data['func'] = method; + data['version'] = $('.plugin_version').attr('version'); + + if (typeof(args) == 'string'){ + data['args'] = JSON.stringify(this.str2Obj(args)); + } else { + data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', data, function(res) { + layer.close(loadT); + if (!res.status){ + layer.msg(res.msg,{icon:2,time:10000}); + return; + } + + var ret_data = $.parseJSON(res.data); + console.log("send:",ret_data); + // if (!ret_data.status){ + // layer.msg(ret_data.msg,{icon:2,time:2000}); + // return; + // } + + // console.log("send2:",ret_data); + + if(typeof(callback) == 'function'){ + callback(ret_data); + } + },'json'); + }, + postCallback:function(info){ + var tips = info['tips']; + var method = info['method']; + var args = info['data']; + var callback = info['success']; + + var loadT = layer.msg(tips, { icon: 16, time: 0, shade: 0.3 }); + + var data = {}; + data['name'] = 'mail'; + data['func'] = method; + data['version'] = $('.plugin_version').attr('version'); + + if (typeof(args) == 'string'){ + data['args'] = JSON.stringify(this.str2Obj(args)); + } else { + data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', data, function(res) { + + layer.close(loadT); + if (!res.status){ + layer.msg(res.msg,{icon:2,time:10000}); + return; + } + + var ret_data = $.parseJSON(res.data); + if (!ret_data.status){ + layer.msg(ret_data.msg,{icon:2,time:2000}); + return; + } + + if(typeof(callback) == 'function'){ + callback(res); + } + },'json'); + } +} diff --git a/plugins/imail/versions/0.0.19/install.sh b/plugins/imail/versions/0.0.19/install.sh new file mode 100755 index 000000000..dbfe92501 --- /dev/null +++ b/plugins/imail/versions/0.0.19/install.sh @@ -0,0 +1,111 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=0.0.19 + +# bash install.sh install 0.0.19 +## cd /www/server/mdserver-web/plugins/imail && bash install.sh install 0.0.19 + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +OSNAME_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +ARCH="amd64" + +get_arch() { + TMP_ARCH=`arch` + if [ "$TMP_ARCH" == "x86_64" ];then + ARCH="amd64" + elif [ "$TMP_ARCH" == "aarch64" ];then + ARCH="arm64" + else + echo $ARCH + fi +} + +load_vars() { + OS=$(uname | tr '[:upper:]' '[:lower:]') + TARGET_DIR="$serverPath/imail" +} + +get_download_url() { + DOWNLOAD_URL="https://github.com/midoks/imail/releases/download/$VERSION/imail_${VERSION}_${OS}_${ARCH}.tar.gz" +} + +# download file +download_file() { + url="${1}" + destination="${2}" + + printf "Fetching ${url} \n\n" + + if test -x "$(command -v curl)"; then + code=$(curl --connect-timeout 15 -w '%{http_code}' -L "${url}" -o "${destination}") + elif test -x "$(command -v wget)"; then + code=$(wget -t2 -T15 -O "${destination}" --server-response "${url}" 2>&1 | awk '/^ HTTP/{print $2}' | tail -1) + else + printf "\e[1;31mNeither curl nor wget was available to perform http requests.\e[0m\n" + exit 1 + fi + + if [ "${code}" != 200 ]; then + printf "\e[1;31mRequest failed with code %s\e[0m\n" $code + exit 1 + else + printf "\n\e[1;33mDownload succeeded\e[0m\n" + fi +} + + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + + load_vars + get_arch + get_download_url + + DOWNLOAD_FILE="$(mktemp).tar.gz" + download_file $DOWNLOAD_URL $DOWNLOAD_FILE + + if [ ! -d "$TARGET_DIR" ]; then + mkdir -p "$TARGET_DIR" + fi + + tar -C "$TARGET_DIR" -zxf $DOWNLOAD_FILE + rm -rf $DOWNLOAD_FILE + + pushd "$TARGET_DIR/scripts" >/dev/null 2>&1 + bash make.sh + + if [ -d $serverPath/imail ];then + echo "$VERSION" > $serverPath/imail/version.pl + + cd ${rootPath} && python3 ${rootPath}/plugins/imail/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/imail/index.py initd_install + fi + echo 'install successful' +} + +Uninstall_App() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/imail/index.py initd_uninstall + cd ${rootPath} && python3 ${rootPath}/plugins/imail/index.py stop + rm -rf $serverPath/imail + echo "install fail" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/keepalived/config/keepalived.conf b/plugins/keepalived/config/keepalived.conf new file mode 100644 index 000000000..3df6826fd --- /dev/null +++ b/plugins/keepalived/config/keepalived.conf @@ -0,0 +1,32 @@ +! Configuration File for keepalived + +global_defs { + router_id MYSQL_MHA +} + +vrrp_script chk_mysql_port { + script "{$SERVER_PATH}/keepalived/scripts/chk.sh mysql" + interval 2 + weight –5 + fall 2 + rise 1 +} + +vrrp_instance VI_MYSQL { + state MASTER + interface {$ETH_XX} + virtual_router_id 51 + priority 100 + advert_int 1 + authentication { + auth_type PASS + auth_pass 1111 + } + virtual_ipaddress { + 192.168.10.10 + } + + track_script { + chk_mysql_port + } +} \ No newline at end of file diff --git a/plugins/keepalived/ico.png b/plugins/keepalived/ico.png new file mode 100644 index 000000000..383c6b9ad Binary files /dev/null and b/plugins/keepalived/ico.png differ diff --git a/plugins/keepalived/index.html b/plugins/keepalived/index.html new file mode 100755 index 000000000..088db1348 --- /dev/null +++ b/plugins/keepalived/index.html @@ -0,0 +1,22 @@ +
              +
              +
              +
              +

              服务

              +

              自启动

              + +

              配置修改

              +

              脚本模板

              +

              日志

              +
              +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/plugins/keepalived/index.py b/plugins/keepalived/index.py new file mode 100755 index 000000000..3ee34712d --- /dev/null +++ b/plugins/keepalived/index.py @@ -0,0 +1,356 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'keepalived' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/etc/keepalived/keepalived.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + "/config/keepalived.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + + +def defaultScriptsTpl(): + path = getServerDir() + "/scripts/chk.sh" + return path + +def configScriptsTpl(): + path = getServerDir() + '/scripts' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def status(): + data = mw.execShell( + "ps aux|grep keepalived |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + + if data[0] == '': + return 'stop' + return 'start' + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PLUGIN_PATH}', getPluginDir()) + + # 网络接口 + ethx = mw.execShell("route -n | grep ^0.0.0.0 | awk '{print $8}'") + if ethx[1]!='': + # 未找到 + content = content.replace('{$ETH_XX}', 'eth1') + else: + # 已找到 + content = content.replace('{$ETH_XX}', ethx[0]) + + + return content + + +def copyScripts(): + # 复制检查脚本 + src_scripts_path = getPluginDir() + '/scripts' + dst_scripts_path = getServerDir() + '/scripts' + if not os.path.exists(dst_scripts_path): + mw.execShell('mkdir -p ' + dst_scripts_path) + olist = os.listdir(src_scripts_path) + for o in range(len(olist)): + src_file = src_scripts_path+'/'+olist[o] + dst_file = dst_scripts_path+'/'+olist[o] + + content = mw.readFile(src_file) + content = contentReplace(content) + mw.writeFile(dst_file, content) + + cmd = 'chmod +x ' + dst_file + mw.execShell(cmd) + return True + return False + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # log + dataLog = getServerDir() + '/data' + if not os.path.exists(dataLog): + mw.execShell('chmod +x ' + file_bin) + + # config replace + dst_conf = getServerDir() + '/etc/keepalived/keepalived.conf' + dst_conf_init = getServerDir() + '/init.pl' + if not os.path.exists(dst_conf_init): + content = mw.readFile(getConfTpl()) + content = contentReplace(content) + mw.writeFile(dst_conf, content) + mw.writeFile(dst_conf_init, 'ok') + + # 复制检查脚本 + copyScripts() + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + content = mw.readFile(systemServiceTpl) + content = contentReplace(content) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def kpOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return kpOp('start') + + +def stop(): + return kpOp('stop') + + +def restart(): + status = kpOp('restart') + + log_file = runLog() + mw.execShell("echo '' > " + log_file) + return status + + +def reload(): + return kpOp('reload') + + +def getPort(): + conf = getServerDir() + '/keepalived.conf' + content = mw.readFile(conf) + + rep = r"^(" + 'port' + r')\s*([.0-9A-Za-z_& ~]+)' + tmp = re.search(rep, content, re.M) + if tmp: + return tmp.groups()[1] + + return '6379' + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + return getServerDir() + '/' + getPluginName() + '.log' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'default_scripts_tpl': + print(defaultScriptsTpl()) + elif func == 'config_scripts_tpl': + print(configScriptsTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + else: + print('error') diff --git a/plugins/keepalived/info.json b/plugins/keepalived/info.json new file mode 100755 index 000000000..c024e497c --- /dev/null +++ b/plugins/keepalived/info.json @@ -0,0 +1,17 @@ +{ + "sort": 7, + "ps": "[内网]优秀的高可用软件", + "name": "keepalived", + "title": "keepalived", + "shell": "install.sh", + "versions":["2.2.8"], + "tip": "soft", + "checks": "server/keepalived", + "path": "server/keepalived", + "display": 1, + "author": "keepalived", + "date": "2023-10-22", + "home": "https://keepalived.org/download.html", + "type": "soft", + "pid": "4" +} diff --git a/plugins/keepalived/init.d/keepalived.service.tpl b/plugins/keepalived/init.d/keepalived.service.tpl new file mode 100644 index 000000000..0f4c5b365 --- /dev/null +++ b/plugins/keepalived/init.d/keepalived.service.tpl @@ -0,0 +1,13 @@ +[Unit] +Description=Redis In-Memory Data Store +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/keepalived/sbin/keepalived -D +ExecReload=/bin/kill -USR1 $MAINPID +Restart=on-failure +StandardOutput={$SERVER_PATH}/keepalived/keepalived.log + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/keepalived/init.d/keepalived.tpl b/plugins/keepalived/init.d/keepalived.tpl new file mode 100644 index 000000000..35a49d208 --- /dev/null +++ b/plugins/keepalived/init.d/keepalived.tpl @@ -0,0 +1,78 @@ +#!/bin/sh +# +# Startup script for the Keepalived daemon +# +# processname: keepalived +# pidfile: /var/run/keepalived.pid +# config: /etc/keepalived/keepalived.conf +# chkconfig: - 21 79 +# description: Start and stop Keepalived + +# Source function library +if [ -f /etc/rc.d/init.d/functions ];then + . /etc/rc.d/init.d/functions +fi + +SYS_KP_FILE={$SERVER_PATH}/keepalived/etc/sysconfig/keepalived +# Source configuration file (we set KEEPALIVED_OPTIONS there) +if [ -f $SYS_KP_FILE ];then + . $SYS_KP_FILE +fi + +RETVAL=0 +prog="keepalived" + +start() { + echo -n $"Starting $prog: " + {$SERVER_PATH}/keepalived/sbin/keepalived ${KEEPALIVED_OPTIONS} + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog +} + +stop() { + echo -n $"Stopping $prog: " + pkill keepalived + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog +} + +reload() { + echo -n $"Reloading $prog: " + pkill keepalived -1 + RETVAL=$? + echo +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + reload) + reload + ;; + restart) + stop + start + ;; + condrestart) + if [ -f /var/lock/subsys/$prog ]; then + stop + start + fi + ;; + status) + status keepalived + RETVAL=$? + ;; + *) + echo "Usage: $0 {start|stop|reload|restart|condrestart|status}" + RETVAL=1 +esac + +exit $RETVAL diff --git a/plugins/keepalived/install.sh b/plugins/keepalived/install.sh new file mode 100755 index 000000000..d3a869a2d --- /dev/null +++ b/plugins/keepalived/install.sh @@ -0,0 +1,98 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + +# for mac +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/keepalived && bash install.sh install 2.2.8 +# cd /www/server/mdserver-web/plugins/keepalived && bash install.sh install 2.2.8 + +# /www/server/keepalived/init.d/keepalived start + +# systemctl status keepalived +# systemctl restart keepalived +# ifconfig + +VERSION=$2 + +Install_App() +{ + echo '正在安装keepalived脚本文件...' + mkdir -p $serverPath/source/keepalived + + if [ ! -f $serverPath/source/keepalived/keepalived-${VERSION}.tar.gz ];then + wget -O $serverPath/source/keepalived/keepalived-${VERSION}.tar.gz https://keepalived.org/software/keepalived-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_file_ok=8c26f75a8767e5341d82696e1e717115 + if [ -f $serverPath/source/keepalived/keepalived-${VERSION}.tar.gz ];then + md5_file=`md5sum $serverPath/source/keepalived/keepalived-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "keepalived-${version} 下载文件不完整,重新安装" + rm -rf $serverPath/source/keepalived/keepalived-${VERSION}.tar.gz + fi + fi + + echo $serverPath/keepalived/keepalived-${VERSION} + if [ -d $serverPath/keepalived/keepalived-${VERSION} ];then + cd $serverPath/keepalived/keepalived-${VERSION} + else + cd $serverPath/source/keepalived && tar -zxvf keepalived-${VERSION}.tar.gz + cd $serverPath/keepalived/keepalived-${VERSION} + fi + + cd $serverPath/source/keepalived/keepalived-${VERSION} + + ./configure --prefix=$serverPath/keepalived && make && make install + + # for test + # mkdir -p $serverPath/keepalived + if [ -d $serverPath/keepalived ];then + echo "${VERSION}" > $serverPath/keepalived/version.pl + echo 'keepalived安装完成' + + cd ${rootPath} && python3 ${rootPath}/plugins/keepalived/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/keepalived/index.py initd_install + fi + + if [ -d $serverPath/source/keepalived/keepalived-${VERSION} ];then + rm -rf $serverPath/source/keepalived/keepalived-${VERSION} + fi +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/keepalived.service ];then + systemctl stop keepalived + systemctl disable keepalived + rm -rf /usr/lib/systemd/system/keepalived.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/keepalived.service ];then + systemctl stop keepalived + systemctl disable keepalived + rm -rf /lib/systemd/system/keepalived.service + systemctl daemon-reload + fi + + if [ -f $serverPath/keepalived/initd/keepalived ];then + $serverPath/keepalived/initd/keepalived stop + fi + + rm -rf $serverPath/keepalived + echo "keepalived卸载完成" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/keepalived/js/keepalived.js b/plugins/keepalived/js/keepalived.js new file mode 100755 index 000000000..c828527e8 --- /dev/null +++ b/plugins/keepalived/js/keepalived.js @@ -0,0 +1,143 @@ +function kpPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'keepalived'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function kpPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'keepalived'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +//redis负载状态 start +function redisStatus(version) { + + redisPost('run_info',version, {},function(data){ + var rdata = $.parseJSON(data.data); + // if (!rdata.status){ + // layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + // return; + // } + + hit = (parseInt(rdata.keyspace_hits) / (parseInt(rdata.keyspace_hits) + parseInt(rdata.keyspace_misses)) * 100).toFixed(2); + var con = '
              \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
              字段当前值说明
              uptime_in_days' + rdata.uptime_in_days + '已运行天数
              tcp_port' + rdata.tcp_port + '当前监听端口
              connected_clients' + rdata.connected_clients + '连接的客户端数量
              used_memory_rss' + toSize(rdata.used_memory_rss) + 'Redis当前占用的系统内存总量
              used_memory' + toSize(rdata.used_memory) + 'Redis当前已分配的内存总量
              used_memory_peak' + toSize(rdata.used_memory_peak) + 'Redis历史分配内存的峰值
              mem_fragmentation_ratio' + rdata.mem_fragmentation_ratio + '%内存碎片比率
              total_connections_received' + rdata.total_connections_received + '运行以来连接过的客户端的总数量
              total_commands_processed' + rdata.total_commands_processed + '运行以来执行过的命令的总数量
              instantaneous_ops_per_sec' + rdata.instantaneous_ops_per_sec + '服务器每秒钟执行的命令数量
              keyspace_hits' + rdata.keyspace_hits + '查找数据库键成功的次数
              keyspace_misses' + rdata.keyspace_misses + '查找数据库键失败的次数
              hit' + hit + '%查找数据库键命中率
              latest_fork_usec' + rdata.latest_fork_usec + '最近一次 fork() 操作耗费的微秒数
              '; + $(".soft-man-con").html(con); + }); +} +//redis负载状态 end + +//配置修改 +function getRedisConfig(version) { + redisPost('get_redis_conf', version,'',function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '70' + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = '' + break; + case 1: + var selected_1 = (rdata[i].value == 'On') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'Off') ? 'selected' : ''; + ibody = '' + break; + } + mlist += '

              ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

              ' + } + var con = '
              ' + mlist + '\ +
              \ +
              \ +
              ' + $(".soft-man-con").html(con); + }); +} + +//提交配置 +function submitConf(version) { + var data = { + version: version, + bind: $("input[name='bind']").val(), + 'port': $("input[name='port']").val(), + 'timeout': $("input[name='timeout']").val(), + maxclients: $("input[name='maxclients']").val(), + databases: $("input[name='databases']").val(), + requirepass: $("input[name='requirepass']").val(), + maxmemory: $("input[name='maxmemory']").val(), + }; + + redisPost('submit_redis_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + diff --git a/plugins/keepalived/scripts/chk.sh b/plugins/keepalived/scripts/chk.sh new file mode 100644 index 000000000..432b5a1ef --- /dev/null +++ b/plugins/keepalived/scripts/chk.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# 计划任务,恢复后,可自动拉起keepalived +# bash {$SERVER_PATH}/keepalived/scripts/chk.sh mysql + +cd {$SERVER_PATH}/keepalived + +# check script bash +curPath=`pwd` +rootPath=$(dirname "$curPath") + +SOFT=$1 + +if [ "$SOFT" == "mysql" ];then + bash ${rootPath}/keepalived/scripts/chk_mysql.sh > ${rootPath}/keepalived/keepalived.log +else + echo "you should use [chk.sh mysql] exp ." +fi \ No newline at end of file diff --git a/plugins/keepalived/scripts/chk_mysql.sh b/plugins/keepalived/scripts/chk_mysql.sh new file mode 100644 index 000000000..65151b9f9 --- /dev/null +++ b/plugins/keepalived/scripts/chk_mysql.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# 计划任务,恢复后,可自动拉起keepalived +# bash {$SERVER_PATH}/keepalived/scripts/chk_mysql.sh + +counter=$(netstat -na|grep "LISTEN"|grep "3306"|wc -l) +data_time=`date +'%Y-%m-%d %H:%M:%S'` + +if [ "${counter}" -eq "0" ]; then + echo "${data_time}: start check mysql status, mysql is down, stop keepalive ..." + systemctl stop keepalived + echo "${data_time}: start check mysql end !!!" +fi + + +# 恢复后,自动拉起 +# systemctl start keepalived +if [ "${counter}" -gt "0" ]; then + echo "${data_time}: start check mysql status, mysql is up, start keepalive" + + keepalived_status=`which systemctl && systemctl status keepalived | grep Active | grep inactive` + if [ "$keepalived_status" != "" ];then + systemctl start keepalived + fi + + echo "${data_time}: start check mysql end !!!" +fi diff --git a/plugins/keepalived/tpl/keepalived.lvs.nat.master.conf b/plugins/keepalived/tpl/keepalived.lvs.nat.master.conf new file mode 100644 index 000000000..6cc436aa5 --- /dev/null +++ b/plugins/keepalived/tpl/keepalived.lvs.nat.master.conf @@ -0,0 +1,46 @@ +# keepalived for lvs-nat +# 未测试通过 + +global_defs { + router_id HA_LVS +} + +vrrp_sync_group HA_LVS_NAT { + group { + HA_LVS_NAT + } +} + +vrrp_instance HA_LVS_NAT { + state MASTER + interface bond1 + virtual_router_id 222 + priority 100 + advert_int 1 + nopreempt + authentication { + auth_type PASS + auth_pass 1111 + } + virtual_ipaddress { + 192.168.212.100/25 dev bond1 + } +} + +virtual_server 192.168.212.100 80 { + delay_loop 6 + lb_algo rr + lb_kind NAT + protocol TCP + + real_server 192.168.212.129 80 { + weight 100 + TCP_CHECK { + connect_timeout 3 + nb_get_retry 3 + delay_before_retry 3 + connect_port 15000 + } + } + +} diff --git a/plugins/keepalived/tpl/keepalived.mysql.backup.conf b/plugins/keepalived/tpl/keepalived.mysql.backup.conf new file mode 100644 index 000000000..e09292753 --- /dev/null +++ b/plugins/keepalived/tpl/keepalived.mysql.backup.conf @@ -0,0 +1,32 @@ +! Configuration File for keepalived + +global_defs { + router_id MYSQL_MHA +} + +vrrp_script chk_mysql_port { + script "{$SERVER_PATH}/scripts/chk.sh mysql" + interval 2 + weight –5 + fall 2 + rise 1 +} + +vrrp_instance VI_MYSQL { + state 这里所有节点定义为BACKUP + interface {$ETH_XX} + virtual_router_id 51 + priority 100 + advert_int 1 + authentication { + auth_type PASS + auth_pass 1111 + } + virtual_ipaddress { + 192.168.10.10 + } + + track_script { + chk_mysql_port + } +} \ No newline at end of file diff --git a/plugins/keepalived/tpl/keepalived.mysql.master.conf b/plugins/keepalived/tpl/keepalived.mysql.master.conf new file mode 100644 index 000000000..1d5eb5962 --- /dev/null +++ b/plugins/keepalived/tpl/keepalived.mysql.master.conf @@ -0,0 +1,32 @@ +! Configuration File for keepalived + +global_defs { + router_id MYSQL_MHA +} + +vrrp_script chk_mysql_port { + script "{$SERVER_PATH}/scripts/chk.sh mysql" + interval 2 + weight –5 + fall 2 + rise 1 +} + +vrrp_instance VI_MYSQL { + state MASTER + interface {$ETH_XX} + virtual_router_id 51 + priority 101 + advert_int 1 + authentication { + auth_type PASS + auth_pass 1111 + } + virtual_ipaddress { + 192.168.10.10 + } + + track_script { + chk_mysql_port + } +} \ No newline at end of file diff --git a/plugins/keepalived/tpl/keepalived.mysql.note.conf b/plugins/keepalived/tpl/keepalived.mysql.note.conf new file mode 100644 index 000000000..855e9b54f --- /dev/null +++ b/plugins/keepalived/tpl/keepalived.mysql.note.conf @@ -0,0 +1,49 @@ +! Configuration File for keepalived + +global_defs { + # 路由标识,主从保持一致 + router_id MYSQL_MHA +} + +#检测mysql服务是否在运行。有很多方式,比如进程,用脚本检测等等 +vrrp_script chk_mysql_port { + # 这里通过脚本监测 + script "{$SERVER_PATH}/scripts/chk.sh mysql" + # 脚本执行间隔,每2s检测一次 + interval 2 + # 脚本结果导致的优先级变更,检测失败(脚本返回非0)则优先级 -5 + weight –5 + # 检测连续2次失败才算确定是真失败。会用weight减少优先级(1-255之间) + fall 2 + # 检测1次成功就算成功。但不修改优先级 + rise 1 +} + +vrrp_instance VI_1 { + # 这里所有节点定义为BACKUP/MASTER + state BACKUP + # 指定虚拟ip的网卡(可能需要手动查看) + # route -n | grep ^0.0.0.0 | awk '{print $8}' + interface {$ETH_XX} + # 路由器标识,MASTER和BACKUP必须是一致的 + virtual_router_id 51 + # 定义优先级,数字越大,优先级越高,在同一个vrrp_instance下,MASTER的优先级必须大于BACKUP的优先级 + priority 99 + # 通知间隔秒数(心跳频率) + advert_int 1 + # 不抢占模式,在优先级高的机器上设置即可,优先级低的机器可不设置 + nopreempt + # 认证信息配置,主从服务器保持一致 + authentication { + auth_type PASS + auth_pass 1111 + } + virtual_ipaddress { + # 虚拟IP(VIP) | 根据情况设置 + 192.168.10.10 + } + + track_script { + chk_mysql_port + } +} \ No newline at end of file diff --git a/plugins/lam/conf/config.cfg b/plugins/lam/conf/config.cfg new file mode 100644 index 000000000..046734c5e --- /dev/null +++ b/plugins/lam/conf/config.cfg @@ -0,0 +1,39 @@ +{ + "password": "{CRYPT-SHA512}$6$WheNHdlVwDoL4s.x$DrZ10TpIGQa5wd0jbvtm8eaTleJCf1nec3ihOaNwMdPUKVFCphXwtnTSmFFXjhGa45RlrSEWhDVyjLCMiV\/.c. V2hlTkhkbFZ3RG9MNHMueA==", + "default": "lam", + "sessionTimeout": "30", + "hideLoginErrorDetails": "false", + "logLevel": "4", + "logDestination": "SYSLOG", + "allowedHosts": "", + "passwordMinLength": "10", + "passwordMinUpper": "0", + "passwordMinLower": "0", + "passwordMinNumeric": "0", + "passwordMinClasses": "0", + "passwordMinSymbol": "0", + "checkedRulesCount": "-1", + "passwordMustNotContainUser": "false", + "passwordMustNotContain3Chars": "false", + "externalPwdCheckUrl": "", + "errorReporting": "default", + "allowedHostsSelfService": "", + "license": "", + "licenseEmailFrom": "", + "licenseEmailTo": "", + "licenseWarningType": "all", + "licenseEmailDateSent": "", + "mailServer": "", + "mailUser": "", + "mailPassword": "", + "mailEncryption": "TLS", + "mailAttribute": "mail", + "mailBackupAttribute": "passwordselfresetbackupmail", + "configDatabaseType": "files", + "configDatabaseServer": "", + "configDatabasePort": "", + "configDatabaseName": "", + "configDatabaseUser": "", + "configDatabasePassword": "", + "moduleSettings": "eyJyZXF1ZXN0QWNjZXNzIjp7Imhpc3RvcnlSZXRlbnRpb25QZXJpb2QiOiIzNjUwIiwiZXhwaXJhdGlvblBlcmlvZCI6IjMwIn19" +} diff --git a/plugins/lam/conf/lam.conf b/plugins/lam/conf/lam.conf new file mode 100755 index 000000000..da5c9a3b2 --- /dev/null +++ b/plugins/lam/conf/lam.conf @@ -0,0 +1,38 @@ +server +{ + listen 888; + server_name 127.0.0.1; + index index.html index.htm index.php; + root {$SERVER_PATH}/lam; + + #error_page 404 /404.html; + include {$PHP_CONF_PATH}/enable-php-{$PHP_VER}.conf; + + #AUTH_START + auth_basic "Authorization"; + auth_basic_user_file {$SERVER_PATH}/lam/pma.pass; + #AUTH_END + + location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ + { + expires 30d; + } + + location ~ .*\.(js|css)?$ + { + expires 12h; + } + + location ~ /.*\.(log|pass|json|pl)$ { + deny all; + } + + + location ~ /\. + { + deny all; + } + + access_log {$SERVER_PATH}/lam/access.log; + error_log {$SERVER_PATH}/lam/error.log; +} \ No newline at end of file diff --git a/plugins/lam/ico.png b/plugins/lam/ico.png new file mode 100644 index 000000000..656b43efb Binary files /dev/null and b/plugins/lam/ico.png differ diff --git a/plugins/lam/index.html b/plugins/lam/index.html new file mode 100755 index 000000000..712f00e77 --- /dev/null +++ b/plugins/lam/index.html @@ -0,0 +1,24 @@ +
              +
              +
              +

              服务

              +

              重写模版

              +

              主页

              +

              PHP版本

              +

              安全设置

              +

              访问日志

              +

              错误日志

              +

              配置

              +
              +
              +
              +
              +
              + +
              + \ No newline at end of file diff --git a/plugins/lam/index.py b/plugins/lam/index.py new file mode 100755 index 000000000..021456a3f --- /dev/null +++ b/plugins/lam/index.py @@ -0,0 +1,476 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import thisdb +from utils.site import sites as MwSites + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'lam' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + return mw.getServerDir() + '/web_conf/nginx/vhost/lam.conf' + + +def getConfInc(): + return getServerDir() + "/" + getCfg()['path'] + '/config/config.cfg' + + +def getPort(): + file = getConf() + content = mw.readFile(file) + rep = r'listen\s*(.*);' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getHomePage(): + try: + port = getPort() + ip = '127.0.0.1' + if not mw.isAppleSystem(): + ip = mw.getLocalIp() + + cfg = getCfg() + auth = cfg['username']+':'+cfg['password'] + rand_path = cfg['path'] + url = 'http://' + auth + '@' + ip + ':' + port + '/' + rand_path + '/templates/login.php' + return mw.returnJson(True, 'OK', url) + except Exception as e: + return mw.returnJson(False, '插件未启动!') + + +def getPhpVer(expect=83): + php_vers = MwSites.instance().getPhpVersion() + v = php_vers['data'] + is_find = False + for i in range(len(v)): + t = str(v[i]['version']) + if (t == expect): + is_find = True + return str(t) + expect_str = str(expect) + new_ex = expect_str[0:1]+"."+expect_str[1:2] + if t.find(new_ex) > -1: + is_find = True + return str(t) + if not is_find: + if len(v) > 1: + return v[1]['version'] + return v[0]['version'] + return str(expect) + + +def getCachePhpVer(): + cacheFile = getServerDir() + '/php.pl' + v = '' + if os.path.exists(cacheFile): + v = mw.readFile(cacheFile) + else: + v = getPhpVer() + mw.writeFile(cacheFile, v) + return v + + +def contentReplace(content): + service_path = mw.getServerDir() + php_ver = getCachePhpVer() + tmp = mw.execShell('cat /dev/urandom | head -n 32 | md5sum | head -c 16') + blowfish_secret = tmp[0].strip() + # print php_ver + php_conf_dir = mw.getServerDir() + '/web_conf/php/conf' + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PHP_CONF_PATH}', php_conf_dir) + content = content.replace('{$PHP_VER}', php_ver) + content = content.replace('{$BLOWFISH_SECRET}', blowfish_secret) + + cfg = getCfg() + content = content.replace('{$PMA_PATH}', cfg['path']) + + port = cfg["port"] + rep = r'listen\s*(.*);' + content = re.sub(rep, "listen " + port + ';', content) + return content + + +def initCfg(): + cfg = getServerDir() + "/cfg.json" + if not os.path.exists(cfg): + data = {} + data['port'] = '987' + data['path'] = '' + data['username'] = 'admin' + data['password'] = 'admin' + mw.writeFile(cfg, json.dumps(data)) + + +def setCfg(key, val): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + data = json.loads(data) + data[key] = val + mw.writeFile(cfg, json.dumps(data)) + + +def getCfg(): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + data = json.loads(data) + return data + + +def returnCfg(): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + return data + + +def status(): + conf = getConf() + conf_inc = getServerDir() + "/" + getCfg()["path"] + '/config/config.cfg' + # 两个文件都在,才算启动成功 + if os.path.exists(conf) and os.path.exists(conf_inc): + return 'start' + return 'stop' + + +def __release_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'LAM默认端口', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def __delete_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().delAcceptPortCmd(port, 'tcp') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def openPort(): + conf = getCfg() + port = conf['port'] + for i in [port]: + __release_port(i) + return True + + +def delPort(): + conf = getCfg() + port = conf['port'] + for i in [port]: + __delete_port(i) + return True + + +def start(): + initCfg() + openPort() + + pma_dir = getServerDir() + "/lam" + if os.path.exists(pma_dir): + rand_str = mw.getRandomString(6) + rand_str = rand_str.lower() + pma_dir_dst = pma_dir + "_" + rand_str + mw.execShell("mv " + pma_dir + " " + pma_dir_dst) + mw.execShell("chown -R www:www " + pma_dir_dst) + mw.execShell("chmod -R 777 " + pma_dir_dst+'/sess') + mw.execShell("chmod -R 777 " + pma_dir_dst+'/tmp') + setCfg('path', 'lam_' + rand_str) + + file_tpl = getPluginDir() + '/conf/lam.conf' + file_run = getConf() + if not os.path.exists(file_run): + centent = mw.readFile(file_tpl) + centent = contentReplace(centent) + mw.writeFile(file_run, centent) + + pma_path = getServerDir() + '/pma.pass' + if not os.path.exists(pma_path): + username = mw.getRandomString(8) + password = mw.getRandomString(10) + pass_cmd = username + ':' + mw.hasPwd(password) + setCfg('username', username) + setCfg('password', password) + mw.writeFile(pma_path, pass_cmd) + + tmp = getServerDir() + "/" + getCfg()["path"] + '/tmp' + if not os.path.exists(tmp): + os.mkdir(tmp) + mw.execShell("chown -R www:www " + tmp) + + conf_run = getServerDir() + "/" + getCfg()["path"] + '/config/config.cfg' + if not os.path.exists(conf_run): + conf_tpl = getPluginDir() + '/conf/config.cfg' + centent = mw.readFile(conf_tpl) + centent = contentReplace(centent) + mw.writeFile(conf_run, centent) + + log_a = accessLog() + log_e = errorLog() + + for i in [log_a, log_e]: + if os.path.exists(i): + cmd = "echo '' > " + i + mw.execShell(cmd) + + mw.restartWeb() + return 'ok' + + +def stop(): + conf = getConf() + if os.path.exists(conf): + os.remove(conf) + delPort() + mw.restartWeb() + return 'ok' + + +def restart(): + return start() + + +def reload(): + file_tpl = getPluginDir() + '/conf/lam.conf' + file_run = getConf() + if os.path.exists(file_run): + centent = mw.readFile(file_tpl) + centent = contentReplace(centent) + mw.writeFile(file_run, centent) + return start() + + +def setPhpVer(): + args = getArgs() + + if not 'phpver' in args: + return 'phpver missing' + + cacheFile = getServerDir() + '/php.pl' + mw.writeFile(cacheFile, args['phpver']) + + file_tpl = getPluginDir() + '/conf/lam.conf' + file_run = getConf() + + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_run, content) + + mw.restartWeb() + return 'ok' + + +def getSetPhpVer(): + cacheFile = getServerDir() + '/php.pl' + if os.path.exists(cacheFile): + return mw.readFile(cacheFile).strip() + return '' + + +def getPmaOption(): + data = getCfg() + return mw.returnJson(True, 'ok', data) + + +def getPmaPort(): + try: + port = getPort() + return mw.returnJson(True, 'OK', port) + except Exception as e: + # print(e) + return mw.returnJson(False, '插件未启动!') + + +def setPmaPort(): + args = getArgs() + data = checkArgs(args, ['port']) + if not data[0]: + return data[1] + + port = args['port'] + if port == '80': + return mw.returnJson(False, '80端不能使用!') + + file = getConf() + if not os.path.exists(file): + return mw.returnJson(False, '插件未启动!') + content = mw.readFile(file) + rep = r'listen\s*(.*);' + content = re.sub(rep, "listen " + port + ';', content) + mw.writeFile(file, content) + + setCfg("port", port) + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaUsername(): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + + username = args['username'] + setCfg('username', username) + + cfg = getCfg() + pma_path = getServerDir() + '/pma.pass' + username = mw.getRandomString(10) + pass_cmd = cfg['username'] + ':' + mw.hasPwd(cfg['password']) + mw.writeFile(pma_path, pass_cmd) + + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaPassword(): + args = getArgs() + data = checkArgs(args, ['password']) + if not data[0]: + return data[1] + + password = args['password'] + setCfg('password', password) + + cfg = getCfg() + pma_path = getServerDir() + '/pma.pass' + username = mw.getRandomString(10) + pass_cmd = cfg['username'] + ':' + mw.hasPwd(cfg['password']) + mw.writeFile(pma_path, pass_cmd) + + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaPath(): + args = getArgs() + data = checkArgs(args, ['path']) + if not data[0]: + return data[1] + + path = args['path'] + + if len(path) < 5: + return mw.returnJson(False, '不能小于5位!') + + old_path = getServerDir() + "/" + getCfg()['path'] + new_path = getServerDir() + "/" + path + + mw.execShell("mv " + old_path + " " + new_path) + setCfg('path', path) + return mw.returnJson(True, '修改成功!') + + +def accessLog(): + return getServerDir() + '/access.log' + + +def errorLog(): + return getServerDir() + '/error.log' + + +def installVersion(): + return mw.readFile(getServerDir() + '/version.pl') + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'conf': + print(getConf()) + elif func == 'version': + print(installVersion()) + elif func == 'get_cfg': + print(returnCfg()) + elif func == 'config_inc': + print(getConfInc()) + elif func == 'get_home_page': + print(getHomePage()) + elif func == 'set_php_ver': + print(setPhpVer()) + elif func == 'get_set_php_ver': + print(getSetPhpVer()) + elif func == 'get_pma_port': + print(getPmaPort()) + elif func == 'set_pma_port': + print(setPmaPort()) + elif func == 'get_pma_option': + print(getPmaOption()) + elif func == 'set_pma_username': + print(setPmaUsername()) + elif func == 'set_pma_password': + print(setPmaPassword()) + elif func == 'set_pma_path': + print(setPmaPath()) + elif func == 'access_log': + print(accessLog()) + elif func == 'error_log': + print(errorLog()) + else: + print('error') diff --git a/plugins/lam/info.json b/plugins/lam/info.json new file mode 100755 index 000000000..88a70b4b5 --- /dev/null +++ b/plugins/lam/info.json @@ -0,0 +1,15 @@ +{ + "title":"LAM", + "tip":"soft", + "name":"lam", + "type":"运行环境", + "ps":"LDAP管理工具(LAM)", + "versions":["9.0"], + "shell":"install.sh", + "checks":"server/lam", + "path": "server/lam", + "author":"gruberroland", + "home":"https://github.com/LDAPAccountManager", + "date":"2025-1-28", + "pid": "2" +} \ No newline at end of file diff --git a/plugins/lam/install.sh b/plugins/lam/install.sh new file mode 100755 index 000000000..7de143836 --- /dev/null +++ b/plugins/lam/install.sh @@ -0,0 +1,90 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/lam && bash install.sh install 9.0 +# cd /www/server/mdserver-web && python3 plugins/lam/index.py start + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +if [ "$sys_os" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +sysName=`uname` +echo "use system: ${sysName}" + +if [ "${sysName}" == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' +elif grep -Eqi "Raspbian" /etc/issue || grep -Eq "Raspbian" /etc/*-release; then + OSNAME='raspbian' +else + OSNAME='unknow' +fi + +Install_App() +{ + if [ -d $serverPath/lam ];then + exit 0 + fi + + mkdir -p ${serverPath}/lam + mkdir -p ${serverPath}/source/lam + echo "${1}" > ${serverPath}/lam/version.pl + + VER=$1 + + # https://github.com/LDAPAccountManager/lam/releases/download/9.0/ldap-account-manager-9.0.tar.bz2 + FDIR=ldap-account-manager-${VER} + FILE=ldap-account-manager-${VER}.tar.bz2 + DOWNLOAD=https://github.com/LDAPAccountManager/lam/releases/download/9.0/${FILE} + + + if [ ! -f $serverPath/source/phpmyadmin/$FILE ];then + wget --no-check-certificate -O $serverPath/source/lam/$FILE $DOWNLOAD + fi + + if [ ! -d $serverPath/source/lam/$FDIR ];then + cd $serverPath/source/lam && tar jxvf $FILE + fi + + cp -r $serverPath/source/lam/$FDIR $serverPath/lam/ + cd $serverPath/lam/ && mv $FDIR lam + # rm -rf $serverPath/source/lam/$FDIR + + cd ${rootPath} && python3 ${rootPath}/plugins/lam/index.py start + echo '安装完成' + +} + +Uninstall_App() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/lam/index.py stop + + rm -rf ${serverPath}/lam + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App $2 +else + Uninstall_App $2 +fi diff --git a/plugins/lam/js/lam.js b/plugins/lam/js/lam.js new file mode 100755 index 000000000..4660aba03 --- /dev/null +++ b/plugins/lam/js/lam.js @@ -0,0 +1,164 @@ +function str2Obj(str){ + var data = {}; + kv = str.split('&'); + for(i in kv){ + v = kv[i].split('='); + data[v[0]] = v[1]; + } + return data; +} + +function pmaPost(method,args,callback){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'lam', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function pmaAsyncPost(method,args){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'lam', func:method, args:_args}); +} + +function homePage(){ + pmaPost('get_home_page', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + var con = ''; + $(".soft-man-con").html(con); + }); +} + +//phpmyadmin切换php版本 +function phpVer(version) { + + var _version = pmaAsyncPost('get_set_php_ver','') + if (_version['data'] != ''){ + version = _version['data']; + } + + $.post('/site/get_php_version', function(data) { + var rdata = data['data']; + // console.log(rdata); + var body = "
              PHP版本
              '; + $(".soft-man-con").html(body); + },'json'); +} + +function phpVerChange(type, msg) { + var phpver = $("#phpver").val(); + pmaPost('set_php_ver', 'phpver='+phpver, function(data){ + if ( data.data == 'ok' ){ + layer.msg('设置成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg('设置失败!',{icon:2,time:2000,shade: [0.3, '#000']}); + } + }); +} + + +//phpmyadmin安全设置 +function safeConf() { + pmaPost('get_pma_option', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + var cfg = rdata.data; + var con = '
              \ + 访问端口\ + \ + \ +
              \ +
              \ + 用户名\ + \ + \ +
              \ +
              \ + 密码\ + \ + \ +
              \ +
              \ +
              \ + 路径名\ + \ + \ +
              '; + $(".soft-man-con").html(con); + }); +} + +function setPmaUsername(){ + var username = $("input[name=username]").val(); + pmaPost('set_pma_username',{'username':username}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function setPmaPassword(){ + var password = $("input[name=password]").val(); + pmaPost('set_pma_password',{'password':password}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function setPmaPath(){ + var path = $("input[name=path]").val(); + pmaPost('set_pma_path',{'path':path}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +//修改phpmyadmin端口 +function setPamPort() { + var pmport = $("#pmport").val(); + if (pmport < 80 || pmport > 65535) { + layer.msg('端口范围不合法!', { icon: 2 }); + return; + } + var data = 'port=' + pmport; + + pmaPost('set_pma_port',data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} \ No newline at end of file diff --git a/plugins/ldap/config/ldap.conf b/plugins/ldap/config/ldap.conf new file mode 100644 index 000000000..e83ed0e58 --- /dev/null +++ b/plugins/ldap/config/ldap.conf @@ -0,0 +1,17 @@ +# +# LDAP Defaults +# + +# See ldap.conf(5) for details +# This file should be world readable but not world writable. + +BASE dc=mw,dc=com +URI ldap://{$IP} ldap://{$IP}:666 + +#SIZELIMIT 12 +#TIMELIMIT 15 +#DEREF never + +# TLS certificates (needed for GnuTLS) +TLS_CACERT /etc/ssl/certs/ca-certificates.crt + diff --git a/plugins/ldap/ico.png b/plugins/ldap/ico.png new file mode 100644 index 000000000..561a2ab57 Binary files /dev/null and b/plugins/ldap/ico.png differ diff --git a/plugins/ldap/index.html b/plugins/ldap/index.html new file mode 100755 index 000000000..df3e9942f --- /dev/null +++ b/plugins/ldap/index.html @@ -0,0 +1,30 @@ + + +
              +
              +
              +
              +

              服务

              +

              自启动

              +

              配置修改

              +

              运行日志

              +

              相关说明

              + +
              +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/plugins/ldap/index.py b/plugins/ldap/index.py new file mode 100755 index 000000000..64e267b1e --- /dev/null +++ b/plugins/ldap/index.py @@ -0,0 +1,354 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'ldap' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + # path = getServerDir() + "/redis.conf" + path = "/etc/ldap/ldap.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + "/config/ldap.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def configTpl(): + path = "/etc/ldap/schema" + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + pid_file = "/var/run/slapd/slapd.pid" + if not os.path.exists(pid_file): + return 'stop' + + # data = mw.execShell( + # "ps aux|grep redis |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + + # if data[0] == '': + # return 'stop' + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + return content + +def initDreplace(): + service_path = mw.getServerDir() + + conf = getConf() + conf_tpl = getConfTpl() + if not os.path.exists(conf): + content = mw.readFile(conf_tpl) + content = contentReplace(content) + mw.writeFile(conf, content) + return True + + +def ladpOp(method): + initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + return 'ok' + + if current_os.startswith("freebsd"): + data = mw.execShell('service slapd ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' slapd') + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return ladpOp('start') + + +def stop(): + return ladpOp('stop') + + +def restart(): + status = ladpOp('restart') + + log_file = runLog() + mw.execShell("echo '' > " + log_file) + return status + + +def reload(): + return ladpOp('reload') + + +def getPort(): + conf = getConf() + content = mw.readFile(conf) + + rep = r"^(port)\s*([.0-9A-Za-z_& ~]+)" + tmp = re.search(rep, content, re.M) + if tmp: + return tmp.groups()[1] + return '6379' + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status slapd | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc slapd_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable slapd') + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc slapd_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable slapd') + return 'ok' + + +def runLog(): + return getServerDir() + '/data/redis.log' + + +def getRedisConfInfo(): + conf = getConf() + + gets = [ + {'name': 'bind', 'type': 2, 'ps': '绑定IP(修改绑定IP可能会存在安全隐患)','must_show':1}, + {'name': 'port', 'type': 2, 'ps': '绑定端口','must_show':1}, + {'name': 'timeout', 'type': 2, 'ps': '空闲链接超时时间,0表示不断开','must_show':1}, + {'name': 'maxclients', 'type': 2, 'ps': '最大连接数','must_show':1}, + {'name': 'databases', 'type': 2, 'ps': '数据库数量','must_show':1}, + {'name': 'requirepass', 'type': 2, 'ps': 'redis密码,留空代表没有设置密码','must_show':1}, + {'name': 'maxmemory', 'type': 2, 'ps': 'MB,最大使用内存,0表示不限制','must_show':1}, + {'name': 'slaveof', 'type': 2, 'ps': '同步主库地址','must_show':0}, + {'name': 'masterauth', 'type': 2, 'ps': '同步主库密码', 'must_show':0} + ] + content = mw.readFile(conf) + + result = [] + for g in gets: + rep = r"^(" + g['name'] + r'\)\s*([.0-9A-Za-z_& ~]+)' + tmp = re.search(rep, content, re.M) + if not tmp: + if g['must_show'] == 0: + continue + + g['value'] = '' + result.append(g) + continue + g['value'] = tmp.groups()[1] + if g['name'] == 'maxmemory': + g['value'] = g['value'].strip("mb") + result.append(g) + + return result + + +def getRedisConf(): + data = getRedisConfInfo() + return mw.getJson(data) + + +def submitRedisConf(): + gets = ['bind', 'port', 'timeout', 'maxclients', + 'databases', 'requirepass', 'maxmemory','slaveof','masterauth'] + args = getArgs() + conf = getConf() + content = mw.readFile(conf) + for g in gets: + if g in args: + rep = g + r'\s*([.0-9A-Za-z_& ~]+)' + val = g + ' ' + args[g] + + if g == 'maxmemory': + val = g + ' ' + args[g] + "mb" + + if g == 'requirepass' and args[g] == '': + content = re.sub('requirepass', '#requirepass', content) + if g == 'requirepass' and args[g] != '': + content = re.sub('#requirepass', 'requirepass', content) + content = re.sub(rep, val, content) + + if g != 'requirepass': + content = re.sub(rep, val, content) + mw.writeFile(conf, content) + reload() + return mw.returnJson(True, '设置成功') + +def installPreInspection(): + slapd_path = '/etc/ldap/slapd.d' + if not os.path.exists(slapd_path): + return "需要手动执行:apt install -y slapd ldap-utils" + return 'ok' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'run_info': + print(runInfo()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'get_redis_conf': + print(getRedisConf()) + elif func == 'submit_redis_conf': + print(submitRedisConf()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + else: + print('error') diff --git a/plugins/ldap/info.json b/plugins/ldap/info.json new file mode 100755 index 000000000..f577d7547 --- /dev/null +++ b/plugins/ldap/info.json @@ -0,0 +1,18 @@ +{ + "sort":4, + "ps": "LDAP轻量目录服务", + "name": "ldap", + "title": "LDAP", + "shell": "install.sh", + "versions":["1.0"], + "tip": "soft", + "install_pre_inspection":true, + "checks": "server/ldap", + "path": "server/ldap", + "display": 1, + "author": "ladp", + "date": "2025-01-28", + "home": "", + "type": 0, + "pid": "4" +} diff --git a/plugins/ldap/install.sh b/plugins/ldap/install.sh new file mode 100755 index 000000000..943375c15 --- /dev/null +++ b/plugins/ldap/install.sh @@ -0,0 +1,45 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +sysArch=`arch` + +VERSION=$2 + +# https://juejin.cn/post/7309323953683480588 + +# dpkg-reconfigure slapd + +# 该命令将在本地服务器上查找并返回在 “dc=bytedance,dc=local” 这个起点(和其下的所有子目录)下,所有 cn 属性有值的条目的详细信息 +# ldapsearch -x -H ldap://localhost -b "dc=bytedance,dc=local" "(cn=*)" + +Install_App() +{ + echo '正在安装脚本文件...' + apt install -y slapd ldap-utils + + mkdir -p $serverPath/ldap + echo "${VERSION}" > $serverPath/ldap/version.pl + echo "${VERSION}安装完成" +} + +Uninstall_App() +{ + apt remove -y slapd ldap-utils + rm -rf $serverPath/ldap/version.pl + rm -rf $serverPath/ldap + echo "卸载ldap成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/ldap/install_debian.sh b/plugins/ldap/install_debian.sh new file mode 100755 index 000000000..f355ec149 --- /dev/null +++ b/plugins/ldap/install_debian.sh @@ -0,0 +1,44 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +sysArch=`arch` + +VERSION=$2 + +# https://juejin.cn/post/7309323953683480588 + +# dpkg-reconfigure slapd +# slappasswd 修改密码 + +# 该命令将在本地服务器上查找并返回在 “dc=bytedance,dc=local” 这个起点(和其下的所有子目录)下,所有 cn 属性有值的条目的详细信息 +# ldapsearch -x -H ldap://localhost -b "dc=bytedance,dc=local" "(cn=*)" + +Install_App() +{ + echo '正在安装脚本文件...' + apt install -y slapd ldap-utils + + mkdir -p $serverPath/ldap + echo "${VERSION}" > $serverPath/ldap/version.pl + echo "${VERSION}安装完成" +} + +Uninstall_App() +{ + rm -rf $serverPath/ldap/version.pl + echo "卸载ldap成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/ldap/js/ldap.js b/plugins/ldap/js/ldap.js new file mode 100755 index 000000000..1176c308e --- /dev/null +++ b/plugins/ldap/js/ldap.js @@ -0,0 +1,116 @@ +function ldapPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'ldap'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function ldapPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'ldap'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +//配置修改 +function getLadpConfig(version) { + ladpPost('get_redis_conf', version,'',function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '70' + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = '' + break; + case 1: + var selected_1 = (rdata[i].value == 'On') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'Off') ? 'selected' : ''; + ibody = '' + break; + } + mlist += '

              ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

              ' + } + var con = '
              ' + mlist + '\ +
              \ +
              \ +
              ' + $(".soft-man-con").html(con); + }); +} + +//提交配置 +function submitConf(version) { + var data = { + version: version, + bind: $("input[name='bind']").val(), + 'port': $("input[name='port']").val(), + 'timeout': $("input[name='timeout']").val(), + maxclients: $("input[name='maxclients']").val(), + databases: $("input[name='databases']").val(), + requirepass: $("input[name='requirepass']").val(), + maxmemory: $("input[name='maxmemory']").val(), + }; + + redisPost('submit_ladp_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +function ladpReadme(){ + var readme = '
                '; + readme += '
              • 集群创建1
              • '; + readme += '
              '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/loki/conf/loki-config.bak.yaml b/plugins/loki/conf/loki-config.bak.yaml new file mode 100644 index 000000000..f0dcf0baf --- /dev/null +++ b/plugins/loki/conf/loki-config.bak.yaml @@ -0,0 +1,69 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_listen_port: 9096 + log_level: debug + grpc_server_max_concurrent_streams: 1000 + +common: + instance_addr: 127.0.0.1 + path_prefix: /tmp/loki + storage: + filesystem: + chunks_directory: /tmp/loki/chunks + rules_directory: /tmp/loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +query_range: + results_cache: + cache: + embedded_cache: + enabled: true + max_size_mb: 100 + +limits_config: + metric_aggregation_enabled: true + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +pattern_ingester: + enabled: true + metric_aggregation: + loki_address: localhost:3100 + +ruler: + alertmanager_url: http://localhost:9093 + +frontend: + encoding: protobuf + +query_engine: + # 新版本有效参数 + max_concurrent: 20 + timeout: 3m + + +# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration +# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/ +# +# Statistics help us better understand how Loki is used, and they show us performance +# levels for most users. This helps us prioritize features and documentation. +# For more information on what's sent, look at +# https://github.com/grafana/loki/blob/main/pkg/analytics/stats.go +# Refer to the buildReport method to see what goes into a report. +# +# If you would like to disable reporting, uncomment the following lines: +#analytics:max_query_lookback +# reporting_enabled: false diff --git a/plugins/loki/conf/loki-config.yaml b/plugins/loki/conf/loki-config.yaml new file mode 100644 index 000000000..fb001de9d --- /dev/null +++ b/plugins/loki/conf/loki-config.yaml @@ -0,0 +1,64 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_listen_port: 9096 + log_level: debug + grpc_server_max_concurrent_streams: 1000 + +common: + instance_addr: 127.0.0.1 + path_prefix: {$APP_PATH}/loki + storage: + filesystem: + chunks_directory: {$APP_PATH}/loki/chunks + rules_directory: {$APP_PATH}/loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +query_range: + results_cache: + cache: + embedded_cache: + enabled: true + max_size_mb: 100 + +limits_config: + metric_aggregation_enabled: true + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +pattern_ingester: + enabled: true + metric_aggregation: + loki_address: localhost:3100 + +ruler: + alertmanager_url: http://localhost:9093 + +frontend: + encoding: protobuf + + +# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration +# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/ +# +# Statistics help us better understand how Loki is used, and they show us performance +# levels for most users. This helps us prioritize features and documentation. +# For more information on what's sent, look at +# https://github.com/grafana/loki/blob/main/pkg/analytics/stats.go +# Refer to the buildReport method to see what goes into a report. +# +# If you would like to disable reporting, uncomment the following lines: +#analytics: +# reporting_enabled: false diff --git a/plugins/loki/conf/promtail-config.yaml b/plugins/loki/conf/promtail-config.yaml new file mode 100644 index 000000000..3e664cfd9 --- /dev/null +++ b/plugins/loki/conf/promtail-config.yaml @@ -0,0 +1,26 @@ +server: + http_listen_port: 9080 #云服务器需开放9080端口 + grpc_listen_port: 0 + +positions: + filename: {$APP_PATH}/positions.yaml #positions存放路径在promtail工具地址下 + +clients: + - url: http://127.0.0.1:3100/loki/api/v1/push #修改为loki服务器IP + +scrape_configs: +- job_name: web + static_configs: + - targets: + - localhost + labels: + job: "web" + __path__: {$LOG_PATH}/web_nohup.out + +- job_name: portal + static_configs: + - targets: + - localhost + labels: + job: "portal" + __path__: {$LOG_PATH}/portal_nohup.out diff --git a/plugins/loki/ico.png b/plugins/loki/ico.png new file mode 100644 index 000000000..8e89cb402 Binary files /dev/null and b/plugins/loki/ico.png differ diff --git a/plugins/loki/index.html b/plugins/loki/index.html new file mode 100755 index 000000000..07a5e9066 --- /dev/null +++ b/plugins/loki/index.html @@ -0,0 +1,31 @@ + + +
              +
              +
              +
              +

              服务

              +

              自启动

              + +

              Loki配置

              +

              Promtail配置

              +

              相关说明

              + +
              +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/plugins/loki/index.py b/plugins/loki/index.py new file mode 100755 index 000000000..0fe8c8224 --- /dev/null +++ b/plugins/loki/index.py @@ -0,0 +1,287 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'loki' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/conf/loki-config.yaml" + return path + +def getPromtailConf(): + path = getServerDir() + "/conf/promtail-config.yaml" + return path + +def getConfTpl(): + path = getPluginDir() + "/conf/loki-config.yaml" + return path + +def getPromtailConfTpl(): + path = getPluginDir() + "/conf/promtail-config.yaml" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + cmd = "ps aux|grep loki |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + +def getInstallVerion(): + version_pl = getServerDir() + "/version.pl" + version = mw.readFile(version_pl).strip() + return version + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$APP_PATH}', service_path+"/loki") + content = content.replace('{$LOG_PATH}', service_path+"/loki/logs") + return content + + +# def openPort(): +# try: +# from utils.firewall import Firewall as MwFirewall +# MwFirewall.instance().addAcceptPort('3000', 'grafana', 'port') +# return port +# except Exception as e: +# return "Release failed {}".format(e) +# return True + + +def initDreplace(): + # 初始化OP配置 + init_file = getServerDir() + '/init.pl' + if not os.path.exists(init_file): + # openPort() + mw.writeFile(init_file, 'ok') + + conf_dir = getServerDir()+'/conf' + if not os.path.exists(conf_dir): + mw.execShell('mkdir -p ' + conf_dir) + + file_tpl = getConfTpl() + dst_file = getConf() + if not os.path.exists(dst_file): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(dst_file, content) + + + file_tpl = getPromtailConfTpl() + dst_file = getPromtailConf() + if not os.path.exists(dst_file): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(dst_file, content) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/promtail.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/promtail.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return True + + +def gOp(method): + initDreplace() + + data = mw.execShell('systemctl ' + method + ' '+getPluginName()) + if data[1] != '': + return data[1] + + data = mw.execShell('systemctl ' + method + ' promtail') + if data[1] != '': + return data[1] + return 'ok' + + +def start(): + return gOp('start') + +def stop(): + return gOp('stop') + +def restart(): + return gOp('restart') + +def reload(): + return gOp('reload') + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + shell_cmd = 'systemctl status loki|grep loaded|grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + + shell_cmd = 'systemctl status promtail|grep loaded|grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + data = mw.execShell('systemctl enable loki') + if data[1] != '': + return data[1] + + data = mw.execShell('systemctl enable promtail') + if data[1] != '': + return data[1] + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + data = mw.execShell('systemctl disable loki') + if data[1] != '': + return data[1] + + data = mw.execShell('systemctl enable promtail') + if data[1] != '': + return data[1] + return 'ok' + +def grafanaUrl(): + ip = mw.getLocalIp() + return 'http://'+ip+':'+"3100" + +def installPreInspection(): + return 'ok' + + +def uninstallPreInspection(): + return 'ok' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'promtail_conf': + print(getPromtailConf()) + elif func == 'grafana_url': + print(grafanaUrl()) + else: + print('error') diff --git a/plugins/loki/info.json b/plugins/loki/info.json new file mode 100755 index 000000000..4d009f79f --- /dev/null +++ b/plugins/loki/info.json @@ -0,0 +1,17 @@ +{ + "sort": 7, + "ps": "Grafana/Loki是一个日志聚合系统", + "name": "loki", + "title": "loki", + "shell": "install.sh", + "versions":["3.5.3"], + "tip": "soft", + "checks": "server/loki", + "path": "server/loki", + "display": 1, + "author": "midoks", + "date": "2025-08-02", + "home": "https://github.com/grafana/loki", + "type": 0, + "pid": "5" +} diff --git a/plugins/loki/init.d/loki.service.tpl b/plugins/loki/init.d/loki.service.tpl new file mode 100644 index 000000000..812dadb8d --- /dev/null +++ b/plugins/loki/init.d/loki.service.tpl @@ -0,0 +1,10 @@ +[Unit] +Description=Loki Log Aggregation System +After=network.target + +[Service] +ExecStart={$SERVER_PATH}/loki/bin/loki -config.file={$SERVER_PATH}/loki/conf/loki-config.yaml +Restart=always + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/loki/init.d/promtail.service.tpl b/plugins/loki/init.d/promtail.service.tpl new file mode 100644 index 000000000..8cecbae02 --- /dev/null +++ b/plugins/loki/init.d/promtail.service.tpl @@ -0,0 +1,10 @@ +[Unit] +Description=Promtail System +After=network.target + +[Service] +ExecStart={$SERVER_PATH}/loki/bin/promtail -config.file={$SERVER_PATH}/loki/conf/promtail-config.yaml +Restart=always + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/loki/install.sh b/plugins/loki/install.sh new file mode 100755 index 000000000..4aa464f3d --- /dev/null +++ b/plugins/loki/install.sh @@ -0,0 +1,84 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://www.cnblogs.com/n00dle/p/16916044.html +# cd /www/server/mdserver-web/plugins/loki && /bin/bash install.sh install 3.5.3 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/loki/index.py start + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +sysArch=`arch` +sysName=`uname` +echo "use system: ${sysName}" + +OSNAME=`bash ${rootPath}/scripts/getos.sh` +if [ "" == "$OSNAME" ];then + OSNAME=`cat ${rootPath}/data/osname.pl` +fi + +if [ "macos" == "$OSNAME" ];then + echo "不支持Macox" + exit +fi + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +# if id grafana &> /dev/null ;then +# echo "grafana uid is `id -u grafana`" +# echo "grafana shell is `grep "^grafana:" /etc/passwd |cut -d':' -f7 `" +# else +# groupadd grafana +# useradd -g grafana -s /bin/bash grafana +# fi + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/grafana + mkdir -p $serverPath/loki + echo "${VERSION}" > $serverPath/loki/version.pl + + shell_file=${curPath}/versions/${VERSION}/linux.sh + + if [ -f $shell_file ];then + bash -x $shell_file install ${VERSION} + else + echo '不支持...' + exit 1 + fi + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/loki/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/loki/index.py initd_install + + echo 'Grafana/Loki安装完成' +} + +Uninstall_App() +{ + shell_file=${curPath}/versions/${VERSION}/linux.sh + if [ -f $shell_file ];then + bash -x $shell_file uninstall ${VERSION} + fi + + cd ${rootPath} && python3 ${rootPath}/plugins/loki/index.py stop + cd ${rootPath} && python3 ${rootPath}/plugins/loki/index.py initd_uninstall + + rm -rf $serverPath/loki + echo 'Grafana/Loki卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/loki/js/loki.js b/plugins/loki/js/loki.js new file mode 100755 index 000000000..adc278c82 --- /dev/null +++ b/plugins/loki/js/loki.js @@ -0,0 +1,96 @@ +function gPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'loki'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function gPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'loki'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function gCommonFunc(){ + var con = '

              \ + \ +

              '; + + $(".soft-man-con").html(con); + + $('#grafana_url').click(function(){ + gPost('grafana_url', '', {}, function(rdata){ + layer.open({ + title: "Grafana连接", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
              \ +
              \ +
              '+rdata.data+'
              \ +
              \ +
              ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); + }); +} + + +function gReadme(){ + var readme = '
                '; + readme += '
              • https://github.com/grafana/loki
              • '; + readme += '
              '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/loki/versions/3.5.3/linux.sh b/plugins/loki/versions/3.5.3/linux.sh new file mode 100644 index 000000000..281a9b547 --- /dev/null +++ b/plugins/loki/versions/3.5.3/linux.sh @@ -0,0 +1,84 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +VERSION=$2 + +sysArch=`arch` +sysName=`uname` + +ARCH_NAME=amd64 +if [ "$sysArch" == "arm64" ];then + ARCH_NAME=arm64 +elif [ "$sysArch" == "x86_64" ]; then + ARCH_NAME=amd64 +elif [ "$sysArch" == "aarch64" ]; then + ARCH_NAME=aarch64 +fi + +FILE_TGZ=loki-linux-${ARCH_NAME}.zip +PT_FILE_TGZ=promtail-linux-${ARCH_NAME}.zip + +# 检查是否通 +Install_App() +{ + SourceDir=$serverPath/source/grafana + InstallDir=$serverPath/loki + mkdir -p ${SourceDir} + mkdir -p ${InstallDir} + mkdir -p ${InstallDir}/bin + mkdir -p ${InstallDir}/logs + mkdir -p ${InstallDir}/data/{chunks,rules,boltdb-shipper-active,boltdb-shipper-cache} + + if [ ! -f ${SourceDir}/${FILE_TGZ} ];then + wget --no-check-certificate -O ${SourceDir}/${FILE_TGZ} https://github.com/grafana/loki/releases/download/v${VERSION}/${FILE_TGZ} + fi + + if [ ! -f ${SourceDir}/${PT_FILE_TGZ} ];then + wget --no-check-certificate -O ${SourceDir}/${PT_FILE_TGZ} https://github.com/grafana/loki/releases/download/v${VERSION}/${PT_FILE_TGZ} + fi + + if [ ! -f $InstallDir/bin/loki ];then + cd ${SourceDir} && unzip ${FILE_TGZ} + cp -rf ./loki-linux-${ARCH_NAME} ${InstallDir}/bin/loki + rm -rf ./loki-linux-${ARCH_NAME} + fi + + if [ ! -f $InstallDir/bin/promtail ];then + cd ${SourceDir} && unzip ${PT_FILE_TGZ} + cp -rf ./promtail-linux-${ARCH_NAME} ${InstallDir}/bin/promtail + rm -rf ./promtail-linux-${ARCH_NAME} + fi +} + +Uninstall_App() +{ + echo "卸载成功" +} + +# StandardOutput=syslog +# StandardError=syslog +# SyslogIdentifier=loki + +# # 资源限制 (根据服务器配置调整) +# MemoryLimit=1G +# CPUQuota=200% + +curl -v -H "Content-Type: application/json" -XPOST "http://localhost:3100/loki/api/v1/push" \ +-d "{\"streams\": [{\"stream\": {\"test\": \"test\"}, \"values\": [[\"$timestamp\", \"test message\"]]}]}" + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/lvs/ico.png b/plugins/lvs/ico.png new file mode 100644 index 000000000..b222bacdc Binary files /dev/null and b/plugins/lvs/ico.png differ diff --git a/plugins/lvs/index.html b/plugins/lvs/index.html new file mode 100755 index 000000000..27d52374f --- /dev/null +++ b/plugins/lvs/index.html @@ -0,0 +1,19 @@ +
              +
              +
              +

              服务

              +

              自启动

              +

              配置修改

              + +
              +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/plugins/lvs/index.py b/plugins/lvs/index.py new file mode 100755 index 000000000..68683bd6c --- /dev/null +++ b/plugins/lvs/index.py @@ -0,0 +1,184 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import string +import subprocess + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'lvs' + +def getIpvsadm(): + return 'ipvsadm' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + path = '/etc/default/ipvsadm' + return path + + +def status(): + data = mw.execShell("which ipvsadm") + if data[0] == '': + return 'stop' + return 'start' + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + + return file_bin + + +def haOp(method): + return 'ok' + + +def start(): + return haOp('start') + + +def stop(): + return haOp('stop') + + +def restart(): + return haOp('restart') + + +def reload(): + return haOp('reload') + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + getIpvsadm()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + getIpvsadm()) + return 'ok' + +def runLog(): + path = getConf() + content = mw.readFile(path) + rep = r'log\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0] + + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(getConf()) + else: + print('error') diff --git a/plugins/lvs/info.json b/plugins/lvs/info.json new file mode 100755 index 000000000..829ba91ec --- /dev/null +++ b/plugins/lvs/info.json @@ -0,0 +1,18 @@ +{ + "sort": 7, + "ps": "[内网]LVS超强负载均衡,通过ipvsadm管理[暂时不可用]", + "name": "lvs", + "title": "LVS", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "checks": "server/lvs", + "path": "server/lvs", + "display": 1, + "author": "midoks", + "date": "2023-11-17", + "home": "http://www.linuxvirtualserver.org/", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/lvs/install.sh b/plugins/lvs/install.sh new file mode 100755 index 000000000..9ecaccca4 --- /dev/null +++ b/plugins/lvs/install.sh @@ -0,0 +1,82 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sys_os=`uname` +VERSION=1.6.22 + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/lvs && bash install.sh install 1.0 +# cd /www/server/mdserver-web/plugins/lvs && bash install.sh install 1.0 + + +Install_LVS(){ + mkdir -p $serverPath/source + + which ipvsadm + if [ "$?" == "0" ];then + echo '已安装LVS!!' + exit 0 + fi + + echo '正在安装LVS...' + + # 检测平台命令 + which apt + if [ "$?" == "0" ];then + apt install -y ipvsadm + fi + + which yum + if [ "$?" == "0" ];then + yum install -y ipvsadm + fi + + which ipvsadm + if [ "$?" == "0" ];then + echo '正在安装LVS成功!' + mkdir -p $serverPath/lvs + + ipv_version=`ipvsadm -v | awk '{print $2}'` + if [ "$ipv_version" != "" ];then + echo "$ipv_version" > $serverPath/lvs/version.pl + else + echo '1.0' > $serverPath/lvs/version.pl + fi + + cd ${rootPath} && python3 ${rootPath}/plugins/lvs/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/lvs/index.py initd_install + else + echo '正在安装LVS失败!' + fi +} + +Uninstall_LVS() +{ + # 检测平台命令 + which apt + if [ "$?" == "0" ];then + apt remove -y ipvsadm + fi + + which yum + if [ "$?" == "0" ];then + yum uninstall -y ipvsadm + fi + echo "卸载LVS完成" + + if [ -d $serverPath/lvs ];then + rm -rf $serverPath/lvs + fi +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_LVS +else + Uninstall_LVS +fi diff --git a/plugins/lvs/js/lvs.js b/plugins/lvs/js/lvs.js new file mode 100755 index 000000000..e5b36505c --- /dev/null +++ b/plugins/lvs/js/lvs.js @@ -0,0 +1,53 @@ +function lvsPostMin(method, args, callback){ + + var req_data = {}; + req_data['name'] = 'lvs'; + req_data['func'] = method; + + if (typeof(args) != 'undefined' && args!=''){ + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function lvsPost(method, args, callback, msg = '正在获取...'){ + var loadT = layer.msg(msg, { icon: 16, time: 0, shade: 0.3 }); + lvsPostMin(method,args,function(data){ + layer.close(loadT); + if(typeof(callback) == 'function'){ + callback(data); + } + }); +} + + +function secToTime(s) { + var t; + if(s > -1){ + var hour = Math.floor(s/3600); + var min = Math.floor(s/60) % 60; + var sec = s % 60; + if(hour < 10) { + t = '0'+ hour + ":"; + } else { + t = hour + ":"; + } + + if(min < 10){t += "0";} + t += min + ":"; + if(sec < 10){t += "0";} + t += sec.toFixed(2); + } + return t; +} + diff --git a/plugins/lvs/readme.md b/plugins/lvs/readme.md new file mode 100644 index 000000000..29ef5bd64 --- /dev/null +++ b/plugins/lvs/readme.md @@ -0,0 +1,20 @@ +debian + +# 查看网卡 +ls /sys/class/net/ + +ifconfig ens256 172.16.204.100 netmask 255.255.255.255 broadcast 192.168.212.100 up + +route add -host 172.16.204.100 dev ens256 + + +ipvsadm -A -t 172.16.204.100:80 -s rr +ipvsadm -a -t 172.16.204.100:80 -r 172.16.204.129:80 -m + +# 清空LVS规则 + +ipvsadm -C + +# 查看LVS + +ipvsadm -L -n \ No newline at end of file diff --git a/plugins/manticoresearch/class/sphinx_make.py b/plugins/manticoresearch/class/sphinx_make.py new file mode 100644 index 000000000..aa7754644 --- /dev/null +++ b/plugins/manticoresearch/class/sphinx_make.py @@ -0,0 +1,471 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +def getServerDir(): + return mw.getServerDir() + '/mysql' + +def getPluginDir(): + return mw.getPluginDir() + '/mysql' + +def getConf(): + path = getServerDir() + '/etc/my.cnf' + return path + +def getDbPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getSocketFile(): + file = getConf() + content = mw.readFile(file) + rep = r'socket\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def pSqliteDb(dbname='databases'): + file = getServerDir() + '/mysql.db' + name = 'mysql' + + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + +def pMysqlDb(): + # pymysql + db = mw.getMyORM() + + db.setPort(getDbPort()) + db.setSocket(getSocketFile()) + # db.setCharset("utf8") + db.setPwd(pSqliteDb('config').where('id=?', (1,)).getField('mysql_root')) + return db + +class sphinxMake(): + + pdb = None + psdb = None + + pkey_name_cache = {} + delta = 'sph_counter' + ver = '' + + + def __init__(self): + self.pdb = pMysqlDb() + + def setDeltaName(self, name): + self.delta = name + return True + + def setVersion(self, ver): + self.ver = ver + + def createSql(self, db): + conf = ''' +CREATE TABLE IF NOT EXISTS `{$DB_NAME}`.`{$TABLE_NAME}` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `table` varchar(200) NOT NULL, + `max_id` bigint(20) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `table_uniq` (`table`), + KEY `table` (`table`) +) ENGINE=InnoDB AUTO_INCREMENT=1 CHARSET=utf8mb4; +''' + conf = conf.replace("{$TABLE_NAME}", self.delta) + conf = conf.replace("{$DB_NAME}", db) + return conf + + def eqVerField(self, field): + + if field == 'sql_attr_timestamp': + return 'attr_bigint' + + if field == 'sql_attr_bigint': + return 'attr_bigint' + + if field == 'sql_attr_float': + return 'attr_float' + + if field == 'sql_field_string': + return 'field_string' + + return field + + def pathVerName(self): + ver = self.ver.replace(".1",'') + # if float(ver) >= 3.6: + # return 'datadir' + return 'path' + + def getTablePk(self, db, table): + key = db+'_'+table + if key in self.pkey_name_cache: + return self.pkey_name_cache[key] + + # SHOW INDEX FROM bbs.bbs_ucenter_vars WHERE Key_name = 'PRIMARY' + pkey_sql = "SHOW INDEX FROM {}.{} WHERE Key_name = 'PRIMARY';".format(db,table,); + pkey_data = self.pdb.query(pkey_sql) + + # print(db, table) + # print(pkey_data) + key = '' + if len(pkey_data) == 1: + pkey_name = pkey_data[0]['Column_name'] + sql = "select COLUMN_NAME,DATA_TYPE from information_schema.COLUMNS where `TABLE_SCHEMA`='{}' and `TABLE_NAME` = '{}' and `COLUMN_NAME`='{}';" + sql = sql.format(db,table,pkey_name,) + # print(sql) + fields = self.pdb.query(sql) + + if len(fields) == 1: + # print(fields[0]['DATA_TYPE']) + if mw.inArray(['bigint','smallint','tinyint','int','mediumint'], fields[0]['DATA_TYPE']): + key = pkey_name + return key + + + def getTableFieldStr(self, db, table): + sql = "select COLUMN_NAME,DATA_TYPE from information_schema.COLUMNS where `TABLE_SCHEMA`='{}' and `TABLE_NAME` = '{}';" + sql = sql.format(db,table,) + fields = self.pdb.query(sql) + + field_str = '' + for x in range(len(fields)): + field_str += '`'+fields[x]['COLUMN_NAME']+'`,' + + field_str = field_str.strip(',') + return field_str + + def makeSphinxHeader(self): + conf = ''' +indexer { + mem_limit = 1G +} + +searchd +{ + listen = 127.0.0.1:9322 + listen = 127.0.0.1:9326:mysql + listen = 127.0.0.1:9328:http + log = /var/log/manticore/searchd.log + query_log = /var/log/manticore/query.log + pid_file = /var/run/manticore/searchd.pid +} + ''' + conf = conf.replace("{$server_dir}", mw.getServerDir()) + return conf + + def makeSphinxDbSourceRangeSql(self, db, table): + pkey_name = self.getTablePk(db,table) + sql = "SELECT min("+pkey_name+"), max("+pkey_name+") FROM "+table + return sql + + def makeSphinxDbSourceQuerySql(self, db, table): + pkey_name = self.getTablePk(db,table) + field_str = self.getTableFieldStr(db,table) + # print(field_str) + if pkey_name == 'id': + sql = "SELECT " + field_str + " FROM " + table + " where id >= $start AND id <= $end" + else: + sql = "SELECT `"+pkey_name+'` as `id`,' + field_str + " FROM " + table + " where "+pkey_name+" >= $start AND "+pkey_name+" <= $end" + return sql + + + def makeSphinxDbSourceDeltaRange(self, db, table): + pkey_name = self.getTablePk(db,table) + conf = "SELECT (SELECT max_id FROM `{$SPH_TABLE}` where `table`='{$TABLE_NAME}') as min, (SELECT max({$PK_NAME}) FROM {$TABLE_NAME}) as max" + conf = conf.replace("{$DB_NAME}", db) + conf = conf.replace("{$TABLE_NAME}", table) + conf = conf.replace("{$SPH_TABLE}", self.delta) + conf = conf.replace("{$PK_NAME}", pkey_name) + return conf + + def makeSphinxDbSourcePost(self, db, table): + pkey_name = self.getTablePk(db,table) + conf = "sql_query_post = UPDATE {$SPH_TABLE} SET max_id=(SELECT MAX({$PK_NAME}) FROM {$TABLE_NAME}) where `table`='{$TABLE_NAME}'" + # conf = "REPLACE INTO {$SPH_TABLE} (`table`,`max_id`) VALUES ('{$TABLE_NAME}',(SELECT MAX({$PK_NAME}) FROM {$TABLE_NAME}))" + conf = conf.replace("{$DB_NAME}", db) + conf = conf.replace("{$TABLE_NAME}", table) + conf = conf.replace("{$SPH_TABLE}", self.delta) + conf = conf.replace("{$PK_NAME}", pkey_name) + return conf + + def makeSphinxDbSourceDelta(self, db, table): + conf = ''' +source {$DB_NAME}_{$TABLE_NAME}_delta:{$DB_NAME}_{$TABLE_NAME} +{ + sql_query_pre = SET NAMES utf8 + sql_query_range = {$DELTA_RANGE} + sql_query = {$DELTA_QUERY} + {$DELTA_UPDATE} + +{$SPH_FIELD} +} + +index {$DB_NAME}_{$TABLE_NAME}_delta:{$DB_NAME}_{$TABLE_NAME} +{ + source = {$DB_NAME}_{$TABLE_NAME}_delta + {$PATH_NAME} = {$server_dir}/manticoresearch/index/db/{$DB_NAME}.{$TABLE_NAME}/delta + + html_strip = 1 + ngram_len = 1 + +{$SPH_FIELD_INDEX} +} +'''; + conf = conf.replace("{$server_dir}", mw.getServerDir()) + conf = conf.replace("{$PATH_NAME}", self.pathVerName()) + + conf = conf.replace("{$DB_NAME}", db) + conf = conf.replace("{$TABLE_NAME}", table) + + delta_range = self.makeSphinxDbSourceDeltaRange(db, table) + conf = conf.replace("{$DELTA_RANGE}", delta_range) + + delta_query = self.makeSphinxDbSourceQuerySql(db, table) + conf = conf.replace("{$DELTA_QUERY}", delta_query) + + delta_update = self.makeSphinxDbSourcePost(db, table) + conf = conf.replace("{$DELTA_UPDATE}", delta_update) + + + sph_field = self.makeSqlToSphinxTable(db, table) + conf = self.makeSphinxDbFieldRepalce(conf, sph_field) + + return conf; + + def makeSphinxDbSource(self, db, table, create_sphinx_table = False): + db_info = pSqliteDb('databases').field('username,password').where('name=?', (db,)).find() + port = getDbPort() + + conf = ''' +source {$DB_NAME}_{$TABLE_NAME} +{ + type = mysql + sql_host = 127.0.0.1 + sql_user = {$DB_USER} + sql_pass = {$DB_PASS} + sql_db = {$DB_NAME} + sql_port = {$DB_PORT} + + sql_query_pre = SET NAMES utf8 + + {$UPDATE} + + sql_query_range = {$DB_RANGE_SQL} + sql_range_step = 1000 + + sql_query = {$DB_QUERY_SQL} + +{$SPH_FIELD} +} + +index {$DB_NAME}_{$TABLE_NAME} +{ + source = {$DB_NAME}_{$TABLE_NAME} + {$PATH_NAME} = {$server_dir}/manticoresearch/index/db/{$DB_NAME}.{$TABLE_NAME}/index + + ngram_len = 1 + +{$SPH_FIELD_INDEX} +} + ''' + conf = conf.replace("{$server_dir}", mw.getServerDir()) + conf = conf.replace("{$PATH_NAME}", self.pathVerName()) + + conf = conf.replace("{$DB_NAME}", db) + conf = conf.replace("{$TABLE_NAME}", table) + conf = conf.replace("{$DB_USER}", db_info['username']) + conf = conf.replace("{$DB_PASS}", db_info['password']) + conf = conf.replace("{$DB_PORT}", port) + + range_sql = self.makeSphinxDbSourceRangeSql(db, table) + conf = conf.replace("{$DB_RANGE_SQL}", range_sql) + + query_sql = self.makeSphinxDbSourceQuerySql(db, table) + conf = conf.replace("{$DB_QUERY_SQL}", query_sql) + + sph_field = self.makeSqlToSphinxTable(db, table) + # conf = conf.replace("{$SPH_FIELD}", sph_field) + + + conf = self.makeSphinxDbFieldRepalce(conf, sph_field) + + if create_sphinx_table: + update = self.makeSphinxDbSourcePost(db, table) + conf = conf.replace("{$UPDATE}", update) + else: + conf = conf.replace("{$UPDATE}", '') + + if create_sphinx_table: + sph_sql = self.createSql(db) + self.pdb.query(sph_sql) + sql_find = "select * from {}.{} where `table`='{}'".format(db,self.delta,table) + find_data = self.pdb.query(sql_find) + if len(find_data) == 0: + insert_sql = "insert into `{}`.`{}`(`table`,`max_id`) values ('{}',{}) ".format(db,self.delta,table,0) + # print(insert_sql) + self.pdb.execute(insert_sql) + conf += self.makeSphinxDbSourceDelta(db,table) + + # print(ver) + # print(conf) + + return conf + + def makeSphinxDbFieldRepalce(self, content, sph_field): + + content = content.replace("{$SPH_FIELD}", '') + content = content.replace("{$SPH_FIELD_INDEX}", '') + + return content + + + def makeSqlToSphinxDb(self, db, table = [], is_delta = False): + conf = '' + + + for tn in table: + pkey_name = self.getTablePk(db,tn) + if pkey_name == '': + continue + conf += self.makeSphinxDbSource(db, tn,is_delta) + + if len(table) == 0: + tables = self.pdb.query("show tables in "+ db) + for x in range(len(tables)): + key = 'Tables_in_'+db + table_name = tables[x][key] + pkey_name = self.getTablePk(db, table_name, is_delta) + if pkey_name == '': + continue + + if self.makeSqlToSphinxTableIsHaveFulltext(db, table_name): + conf += self.makeSphinxDbSource(db, table_name) + return conf + + def makeSqlToSphinxTableIsHaveFulltext(self, db, table): + sql = "select COLUMN_NAME,DATA_TYPE from information_schema.COLUMNS where `TABLE_SCHEMA`='{}' and `TABLE_NAME` = '{}';" + sql = sql.format(db,table,) + cols = self.pdb.query(sql) + cols_len = len(cols) + + for x in range(cols_len): + data_type = cols[x]['DATA_TYPE'] + column_name = cols[x]['COLUMN_NAME'] + + if mw.inArray(['varchar'], data_type): + return True + if mw.inArray(['text','mediumtext','tinytext','longtext'], data_type): + return True + return False + + def makeSqlToSphinxTable(self,db,table): + pkey_name = self.getTablePk(db,table) + sql = "select COLUMN_NAME,DATA_TYPE from information_schema.COLUMNS where `TABLE_SCHEMA`='{}' and `TABLE_NAME` = '{}';" + sql = sql.format(db,table,) + cols = self.pdb.query(sql) + cols_len = len(cols) + conf = '' + run_pos = 0 + for x in range(cols_len): + data_type = cols[x]['DATA_TYPE'] + column_name = cols[x]['COLUMN_NAME'] + # print(column_name+":"+data_type) + + # if mw.inArray(['tinyint'], data_type): + # conf += 'sql_attr_bool = '+ column_name + "\n" + + if pkey_name == column_name: + # run_pos += 1 + # conf += '\tsql_attr_bigint = '+column_name+"\n" + continue + + if mw.inArray(['enum'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_string')+' = '+ column_name + "\n" + continue + + if mw.inArray(['decimal'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_float')+' = '+ column_name + "\n" + continue + + if mw.inArray(['bigint','smallint','tinyint','int','mediumint'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_bigint')+' = '+ column_name + "\n" + continue + + + if mw.inArray(['float'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_float')+' = '+ column_name + "\n" + continue + + if mw.inArray(['char'], data_type): + conf += '\t'+self.eqVerField('sql_attr_string')+' = '+ column_name + "\n" + continue + + if mw.inArray(['varchar'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_field_string')+' = '+ column_name + "\n" + continue + + if mw.inArray(['text','mediumtext','tinytext','longtext'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_field_string')+' = '+ column_name + "\n" + continue + + if mw.inArray(['datetime','date'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_timestamp')+' = '+ column_name + "\n" + continue + + return conf + + def checkDbName(self, db): + filter_db = ['information_schema','performance_schema','sys','mysql'] + if db in filter_db: + return False + return True + + def makeSqlToSphinx(self, db, tables = [], is_delta = False): + conf = '' + conf += self.makeSphinxHeader() + conf += self.makeSqlToSphinxDb(db, tables, is_delta) + return conf + + def makeSqlToSphinxAll(self): + filter_db = ['information_schema','performance_schema','sys','mysql'] + + dblist = self.pdb.query('show databases') + + conf = '' + conf += self.makeSphinxHeader() + + # conf += makeSqlToSphinxDb(pdb, 'bbs') + for x in range(len(dblist)): + dbname = dblist[x]['Database'] + if mw.inArray(filter_db, dbname): + continue + conf += self.makeSqlToSphinxDb(dbname) + return conf + + diff --git a/plugins/manticoresearch/class/sphinxapi.py b/plugins/manticoresearch/class/sphinxapi.py new file mode 100644 index 000000000..88f5afde4 --- /dev/null +++ b/plugins/manticoresearch/class/sphinxapi.py @@ -0,0 +1,1256 @@ +# +# $Id$ +# +# Python version of Sphinx searchd client (Python API) +# +# Copyright (c) 2006, Mike Osadnik +# Copyright (c) 2006-2016, Andrew Aksyonoff +# Copyright (c) 2008-2016, Sphinx Technologies Inc +# All rights reserved +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Library General Public License. You should +# have received a copy of the LGPL license along with this program; if you +# did not, you can find it at http://www.gnu.org/ +# +# WARNING!!! +# +# As of 2015, we strongly recommend to use either SphinxQL or REST APIs +# rather than the native SphinxAPI. +# +# While both the native SphinxAPI protocol and the existing APIs will +# continue to exist, and perhaps should not even break (too much), exposing +# all the new features via multiple different native API implementations +# is too much of a support complication for us. +# +# That said, you're welcome to overtake the maintenance of any given +# official API, and remove this warning ;) +# + +from __future__ import print_function +import sys +import select +import socket +import re +from struct import * + +if sys.version_info > (3,): + long = int + text_type = str +else: + text_type = unicode + +# known searchd commands +SEARCHD_COMMAND_SEARCH = 0 +SEARCHD_COMMAND_EXCERPT = 1 +SEARCHD_COMMAND_UPDATE = 2 +SEARCHD_COMMAND_KEYWORDS = 3 +SEARCHD_COMMAND_PERSIST = 4 +SEARCHD_COMMAND_STATUS = 5 +SEARCHD_COMMAND_FLUSHATTRS = 7 + +# current client-side command implementation versions +VER_COMMAND_SEARCH = 0x120 +VER_COMMAND_EXCERPT = 0x104 +VER_COMMAND_UPDATE = 0x103 +VER_COMMAND_KEYWORDS = 0x100 +VER_COMMAND_STATUS = 0x101 +VER_COMMAND_FLUSHATTRS = 0x100 + +# known searchd status codes +SEARCHD_OK = 0 +SEARCHD_ERROR = 1 +SEARCHD_RETRY = 2 +SEARCHD_WARNING = 3 + +# known ranking modes (extended2 mode only) +SPH_RANK_PROXIMITY_BM15 = 0 # default mode, phrase proximity major factor and BM15 minor one +SPH_RANK_BM15 = 1 # statistical mode, BM15 ranking only (faster but worse quality) +SPH_RANK_NONE = 2 # no ranking, all matches get a weight of 1 +SPH_RANK_WORDCOUNT = 3 # simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts +SPH_RANK_PROXIMITY = 4 +SPH_RANK_MATCHANY = 5 +SPH_RANK_FIELDMASK = 6 +SPH_RANK_SPH04 = 7 +SPH_RANK_EXPR = 8 +SPH_RANK_TOTAL = 9 + +# aliases; to be retired +SPH_RANK_PROXIMITY_BM25 = 0 +SPH_RANK_BM25 = 1 + +# known sort modes +SPH_SORT_RELEVANCE = 0 +SPH_SORT_ATTR_DESC = 1 +SPH_SORT_ATTR_ASC = 2 +SPH_SORT_TIME_SEGMENTS = 3 +SPH_SORT_EXTENDED = 4 + +# known filter types +SPH_FILTER_VALUES = 0 +SPH_FILTER_RANGE = 1 +SPH_FILTER_FLOATRANGE = 2 +SPH_FILTER_STRING = 3 +SPH_FILTER_STRING_LIST = 6 + +# known attribute types +SPH_ATTR_NONE = 0 +SPH_ATTR_INTEGER = 1 +SPH_ATTR_TIMESTAMP = 2 +SPH_ATTR_ORDINAL = 3 +SPH_ATTR_BOOL = 4 +SPH_ATTR_FLOAT = 5 +SPH_ATTR_BIGINT = 6 +SPH_ATTR_STRING = 7 +SPH_ATTR_FACTORS = 1001 +SPH_ATTR_MULTI = long(0X40000001) +SPH_ATTR_MULTI64 = long(0X40000002) + +SPH_ATTR_TYPES = (SPH_ATTR_NONE, + SPH_ATTR_INTEGER, + SPH_ATTR_TIMESTAMP, + SPH_ATTR_ORDINAL, + SPH_ATTR_BOOL, + SPH_ATTR_FLOAT, + SPH_ATTR_BIGINT, + SPH_ATTR_STRING, + SPH_ATTR_MULTI, + SPH_ATTR_MULTI64) + +# known grouping functions +SPH_GROUPBY_DAY = 0 +SPH_GROUPBY_WEEK = 1 +SPH_GROUPBY_MONTH = 2 +SPH_GROUPBY_YEAR = 3 +SPH_GROUPBY_ATTR = 4 +SPH_GROUPBY_ATTRPAIR = 5 + + +class SphinxClient: + def __init__ (self): + """ + Create a new client object, and fill defaults. + """ + self._host = 'localhost' # searchd host (default is "localhost") + self._port = 9312 # searchd port (default is 9312) + self._path = None # searchd unix-domain socket path + self._socket = None + self._offset = 0 # how much records to seek from result-set start (default is 0) + self._limit = 20 # how much records to return from result-set starting at offset (default is 20) + self._weights = [] # per-field weights (default is 1 for all fields) + self._sort = SPH_SORT_RELEVANCE # match sorting mode (default is SPH_SORT_RELEVANCE) + self._sortby = bytearray() # attribute to sort by (defualt is "") + self._min_id = 0 # min ID to match (default is 0) + self._max_id = 0 # max ID to match (default is UINT_MAX) + self._filters = [] # search filters + self._groupby = bytearray() # group-by attribute name + self._groupfunc = SPH_GROUPBY_DAY # group-by function (to pre-process group-by attribute value with) + self._groupsort = str_bytes('@group desc') # group-by sorting clause (to sort groups in result set with) + self._groupdistinct = bytearray() # group-by count-distinct attribute + self._maxmatches = 1000 # max matches to retrieve + self._cutoff = 0 # cutoff to stop searching at + self._retrycount = 0 # distributed retry count + self._retrydelay = 0 # distributed retry delay + self._indexweights = {} # per-index weights + self._ranker = SPH_RANK_PROXIMITY_BM15 # ranking mode + self._rankexpr = bytearray() # ranking expression for SPH_RANK_EXPR + self._maxquerytime = 0 # max query time, milliseconds (default is 0, do not limit) + self._timeout = 1.0 # connection timeout + self._fieldweights = {} # per-field-name weights + self._select = str_bytes('*') # select-list (attributes or expressions, with optional aliases) + self._query_flags = SetBit ( 0, 6, True ) # default idf=tfidf_normalized + self._predictedtime = 0 # per-query max_predicted_time + self._outerorderby = bytearray() # outer match sort by + self._outeroffset = 0 # outer offset + self._outerlimit = 0 # outer limit + self._hasouter = False # sub-select enabled + self._tokenfilterlibrary = bytearray() # token_filter plugin library name + self._tokenfiltername = bytearray() # token_filter plugin name + self._tokenfilteropts = bytearray() # token_filter plugin options + + self._error = '' # last error message + self._warning = '' # last warning message + self._reqs = [] # requests array for multi-query + + def __del__ (self): + if self._socket: + self._socket.close() + + + def GetLastError (self): + """ + Get last error message (string). + """ + return self._error + + + def GetLastWarning (self): + """ + Get last warning message (string). + """ + return self._warning + + + def SetServer (self, host, port = None): + """ + Set searchd server host and port. + """ + assert(isinstance(host, str)) + if host.startswith('/'): + self._path = host + return + elif host.startswith('unix://'): + self._path = host[7:] + return + self._host = host + if isinstance(port, int): + assert(port>0 and port<65536) + self._port = port + self._path = None + + def SetConnectTimeout ( self, timeout ): + """ + Set connection timeout ( float second ) + """ + assert (isinstance(timeout, float)) + # set timeout to 0 make connaection non-blocking that is wrong so timeout got clipped to reasonable minimum + self._timeout = max ( 0.001, timeout ) + + def _Connect (self): + """ + INTERNAL METHOD, DO NOT CALL. Connects to searchd server. + """ + if self._socket: + # we have a socket, but is it still alive? + sr, sw, _ = select.select ( [self._socket], [self._socket], [], 0 ) + + # this is how alive socket should look + if len(sr)==0 and len(sw)==1: + return self._socket + + # oops, looks like it was closed, lets reopen + self._socket.close() + self._socket = None + + try: + if self._path: + af = socket.AF_UNIX + addr = self._path + desc = self._path + else: + af = socket.AF_INET + addr = ( self._host, self._port ) + desc = '%s;%s' % addr + sock = socket.socket ( af, socket.SOCK_STREAM ) + sock.settimeout ( self._timeout ) + sock.connect ( addr ) + except socket.error as msg: + if sock: + sock.close() + self._error = 'connection to %s failed (%s)' % ( desc, msg ) + return + + v = unpack('>L', sock.recv(4))[0] + if v<1: + sock.close() + self._error = 'expected searchd protocol version, got %s' % v + return + + # all ok, send my version + sock.send(pack('>L', 1)) + return sock + + + def _GetResponse (self, sock, client_ver): + """ + INTERNAL METHOD, DO NOT CALL. Gets and checks response packet from searchd server. + """ + (status, ver, length) = unpack('>2HL', sock.recv(8)) + response = bytearray() + left = length + while left>0: + chunk = sock.recv(left) + if chunk: + response += chunk + left -= len(chunk) + else: + break + + if not self._socket: + sock.close() + + # check response + read = len(response) + if not response or read!=length: + if length: + self._error = 'failed to read searchd response (status=%s, ver=%s, len=%s, read=%s)' \ + % (status, ver, length, read) + else: + self._error = 'received zero-sized searchd response' + return None + + # check status + if status==SEARCHD_WARNING: + wend = 4 + unpack ( '>L', response[0:4] )[0] + self._warning = bytes_str(response[4:wend]) + return response[wend:] + + if status==SEARCHD_ERROR: + self._error = 'searchd error: ' + bytes_str(response[4:]) + return None + + if status==SEARCHD_RETRY: + self._error = 'temporary searchd error: ' + bytes_str(response[4:]) + return None + + if status!=SEARCHD_OK: + self._error = 'unknown status code %d' % status + return None + + # check version + if ver>8, ver&0xff, client_ver>>8, client_ver&0xff) + + return response + + + def _Send ( self, sock, req ): + """ + INTERNAL METHOD, DO NOT CALL. send request to searchd server. + """ + total = 0 + while True: + sent = sock.send ( req[total:] ) + if sent<=0: + break + + total = total + sent + + return total + + + def SetLimits (self, offset, limit, maxmatches=0, cutoff=0): + """ + Set offset and count into result set, and optionally set max-matches and cutoff limits. + """ + assert ( type(offset) in [int,long] and 0<=offset<16777216 ) + assert ( type(limit) in [int,long] and 0=0) + self._offset = offset + self._limit = limit + if maxmatches>0: + self._maxmatches = maxmatches + if cutoff>=0: + self._cutoff = cutoff + + + def SetMaxQueryTime (self, maxquerytime): + """ + Set maximum query time, in milliseconds, per-index. 0 means 'do not limit'. + """ + assert(isinstance(maxquerytime,int) and maxquerytime>0) + self._maxquerytime = maxquerytime + + + def SetRankingMode ( self, ranker, rankexpr='' ): + """ + Set ranking mode. + """ + assert(ranker>=0 and ranker=0) + assert(isinstance(delay,int) and delay>=0) + self._retrycount = count + self._retrydelay = delay + + + def SetSelect (self, select): + assert(isinstance(select, (str,text_type))) + self._select = str_bytes(select) + + + def SetQueryFlag ( self, name, value ): + known_names = [ "reverse_scan", "sort_method", "max_predicted_time", "boolean_simplify", "idf", "global_idf" ] + flags = { "reverse_scan":[0, 1], "sort_method":["pq", "kbuffer"],"max_predicted_time":[0], "boolean_simplify":[True, False], "idf":["normalized", "plain", "tfidf_normalized", "tfidf_unnormalized"], "global_idf":[True, False] } + assert ( name in known_names ) + assert ( value in flags[name] or ( name=="max_predicted_time" and isinstance(value, (int, long)) and value>=0)) + + if name=="reverse_scan": + self._query_flags = SetBit ( self._query_flags, 0, value==1 ) + if name=="sort_method": + self._query_flags = SetBit ( self._query_flags, 1, value=="kbuffer" ) + if name=="max_predicted_time": + self._query_flags = SetBit ( self._query_flags, 2, value>0 ) + self._predictedtime = int(value) + if name=="boolean_simplify": + self._query_flags= SetBit ( self._query_flags, 3, value ) + if name=="idf" and ( value=="plain" or value=="normalized" ) : + self._query_flags = SetBit ( self._query_flags, 4, value=="plain" ) + if name=="global_idf": + self._query_flags= SetBit ( self._query_flags, 5, value ) + if name=="idf" and ( value=="tfidf_normalized" or value=="tfidf_unnormalized" ) : + self._query_flags = SetBit ( self._query_flags, 6, value=="tfidf_normalized" ) + + def SetOuterSelect ( self, orderby, offset, limit ): + assert(isinstance(orderby, (str,text_type))) + assert(isinstance(offset, (int, long))) + assert(isinstance(limit, (int, long))) + assert ( offset>=0 ) + assert ( limit>0 ) + + self._outerorderby = str_bytes(orderby) + self._outeroffset = offset + self._outerlimit = limit + self._hasouter = True + + def SetTokenFilter ( self, library, name, opts='' ): + assert(isinstance(library, str)) + assert(isinstance(name, str)) + assert(isinstance(opts, str)) + + self._tokenfilterlibrary = str_bytes(library) + self._tokenfiltername = str_bytes(name) + self._tokenfilteropts = str_bytes(opts) + + + def ResetFilters (self): + """ + Clear all filters (for multi-queries). + """ + self._filters = [] + + + def ResetGroupBy (self): + """ + Clear groupby settings (for multi-queries). + """ + self._groupby = bytearray() + self._groupfunc = SPH_GROUPBY_DAY + self._groupsort = str_bytes('@group desc') + self._groupdistinct = bytearray() + + def ResetQueryFlag (self): + self._query_flags = SetBit ( 0, 6, True ) # default idf=tfidf_normalized + self._predictedtime = 0 + + def ResetOuterSelect (self): + self._outerorderby = bytearray() + self._outeroffset = 0 + self._outerlimit = 0 + self._hasouter = False + + def Query (self, query, index='*', comment=''): + """ + Connect to searchd server and run given search query. + Returns None on failure; result set hash on success (see documentation for details). + """ + assert(len(self._reqs)==0) + self.AddQuery(query,index,comment) + results = self.RunQueries() + self._reqs = [] # we won't re-run erroneous batch + + if not results or len(results)==0: + return None + self._error = results[0]['error'] + self._warning = results[0]['warning'] + if results[0]['status'] == SEARCHD_ERROR: + return None + return results[0] + + + def AddQuery (self, query, index='*', comment=''): + """ + Add query to batch. + """ + # build request + # 6 == match_mode extended2 + req = bytearray() + req.extend(pack('>5L', self._query_flags, self._offset, self._limit, 6, self._ranker)) + if self._ranker==SPH_RANK_EXPR: + req.extend(pack('>L', len(self._rankexpr))) + req.extend(self._rankexpr) + req.extend(pack('>L', self._sort)) + req.extend(pack('>L', len(self._sortby))) + req.extend(self._sortby) + + query = str_bytes(query) + assert(isinstance(query,bytearray)) + + req.extend(pack('>L', len(query))) + req.extend(query) + + req.extend(pack('>L', len(self._weights))) + for w in self._weights: + req.extend(pack('>L', w)) + index = str_bytes(index) + assert(isinstance(index,bytearray)) + req.extend(pack('>L', len(index))) + req.extend(index) + req.extend(pack('>L',1)) # id64 range marker + req.extend(pack('>Q', self._min_id)) + req.extend(pack('>Q', self._max_id)) + + # filters + req.extend ( pack ( '>L', len(self._filters) ) ) + for f in self._filters: + attr = str_bytes(f['attr']) + req.extend ( pack ( '>L', len(f['attr'])) + attr) + filtertype = f['type'] + req.extend ( pack ( '>L', filtertype)) + if filtertype == SPH_FILTER_VALUES: + req.extend ( pack ('>L', len(f['values']))) + for val in f['values']: + req.extend ( pack ('>q', val)) + elif filtertype == SPH_FILTER_RANGE: + req.extend ( pack ('>2q', f['min'], f['max'])) + elif filtertype == SPH_FILTER_FLOATRANGE: + req.extend ( pack ('>2f', f['min'], f['max'])) + elif filtertype == SPH_FILTER_STRING: + val = str_bytes(f['value']) + req.extend ( pack ( '>L', len(val) ) ) + req.extend ( val ) + elif filtertype == SPH_FILTER_STRING_LIST: + req.extend ( pack ('>L', len(f['values']))) + for sval in f['values']: + val = str_bytes( sval ) + req.extend ( pack ( '>L', len(val) ) ) + req.extend(val) + req.extend ( pack ( '>L', f['exclude'] ) ) + + # group-by, max-matches, group-sort + req.extend ( pack ( '>2L', self._groupfunc, len(self._groupby) ) ) + req.extend ( self._groupby ) + req.extend ( pack ( '>2L', self._maxmatches, len(self._groupsort) ) ) + req.extend ( self._groupsort ) + req.extend ( pack ( '>LLL', self._cutoff, self._retrycount, self._retrydelay)) + req.extend ( pack ( '>L', len(self._groupdistinct))) + req.extend ( self._groupdistinct) + + # geoanchor point + req.extend ( pack ('>L', 0) ) + + # per-index weights + req.extend ( pack ('>L',len(self._indexweights))) + for indx,weight in list(self._indexweights.items()): + indx = str_bytes(indx) + req.extend ( pack ('>L',len(indx)) + indx + pack ('>L',weight)) + + # max query time + req.extend ( pack ('>L', self._maxquerytime) ) + + # per-field weights + req.extend ( pack ('>L',len(self._fieldweights) ) ) + for field,weight in list(self._fieldweights.items()): + field = str_bytes(field) + req.extend ( pack ('>L',len(field)) + field + pack ('>L',weight) ) + + # comment + comment = str_bytes(comment) + req.extend ( pack('>L',len(comment)) + comment ) + + # attribute overrides + req.extend ( pack('>L', 0) ) + + # select-list + req.extend ( pack('>L', len(self._select)) ) + req.extend ( self._select ) + if self._predictedtime>0: + req.extend ( pack('>L', self._predictedtime ) ) + + # outer + req.extend ( pack('>L',len(self._outerorderby)) + self._outerorderby ) + req.extend ( pack ( '>2L', self._outeroffset, self._outerlimit ) ) + if self._hasouter: + req.extend ( pack('>L', 1) ) + else: + req.extend ( pack('>L', 0) ) + + # token_filter + req.extend ( pack('>L',len(self._tokenfilterlibrary)) + self._tokenfilterlibrary ) + req.extend ( pack('>L',len(self._tokenfiltername)) + self._tokenfiltername ) + req.extend ( pack('>L',len(self._tokenfilteropts)) + self._tokenfilteropts ) + + # send query, get response + + self._reqs.append(req) + return + + + def RunQueries (self): + """ + Run queries batch. + Returns None on network IO failure; or an array of result set hashes on success. + """ + if len(self._reqs)==0: + self._error = 'no queries defined, issue AddQuery() first' + return None + + sock = self._Connect() + if not sock: + return None + + req = bytearray() + for r in self._reqs: + req.extend(r) + length = len(req)+8 + req_all = bytearray() + req_all.extend(pack('>HHLLL', SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, length, 0, len(self._reqs))) + req_all.extend(req) + self._Send ( sock, req_all ) + + response = self._GetResponse(sock, VER_COMMAND_SEARCH) + if not response: + return None + + nreqs = len(self._reqs) + + # parse response + max_ = len(response) + p = 0 + + results = [] + for i in range(0,nreqs,1): + result = {} + results.append(result) + + result['error'] = '' + result['warning'] = '' + status = unpack('>L', response[p:p+4])[0] + p += 4 + result['status'] = status + if status != SEARCHD_OK: + length = unpack('>L', response[p:p+4])[0] + p += 4 + message = bytes_str(response[p:p+length]) + p += length + + if status == SEARCHD_WARNING: + result['warning'] = message + else: + result['error'] = message + continue + + # read schema + fields = [] + attrs = [] + + nfields = unpack('>L', response[p:p+4])[0] + p += 4 + while nfields>0 and pL', response[p:p+4])[0] + p += 4 + fields.append(bytes_str(response[p:p+length])) + p += length + + result['fields'] = fields + + nattrs = unpack('>L', response[p:p+4])[0] + p += 4 + while nattrs>0 and pL', response[p:p+4])[0] + p += 4 + attr = bytes_str(response[p:p+length]) + p += length + type_ = unpack('>L', response[p:p+4])[0] + p += 4 + attrs.append([attr,type_]) + + result['attrs'] = attrs + + # read match count + count = unpack('>L', response[p:p+4])[0] + p += 4 + id64 = unpack('>L', response[p:p+4])[0] + p += 4 + + # read matches + result['matches'] = [] + while count>0 and pQL', response[p:p+12]) + p += 12 + else: + doc, weight = unpack('>2L', response[p:p+8]) + p += 8 + + match = { 'id':doc, 'weight':weight, 'attrs':{} } + for i in range(len(attrs)): + if attrs[i][1] == SPH_ATTR_FLOAT: + match['attrs'][attrs[i][0]] = unpack('>f', response[p:p+4])[0] + elif attrs[i][1] == SPH_ATTR_BIGINT: + match['attrs'][attrs[i][0]] = unpack('>q', response[p:p+8])[0] + p += 4 + elif attrs[i][1] == SPH_ATTR_STRING: + slen = unpack('>L', response[p:p+4])[0] + p += 4 + match['attrs'][attrs[i][0]] = '' + if slen>0: + match['attrs'][attrs[i][0]] = bytes_str(response[p:p+slen]) + p += slen-4 + elif attrs[i][1] == SPH_ATTR_FACTORS: + slen = unpack('>L', response[p:p+4])[0] + p += 4 + match['attrs'][attrs[i][0]] = '' + if slen>0: + match['attrs'][attrs[i][0]] = response[p:p+slen-4] + p += slen-4 + p -= 4 + elif attrs[i][1] == SPH_ATTR_MULTI: + match['attrs'][attrs[i][0]] = [] + nvals = unpack('>L', response[p:p+4])[0] + p += 4 + for n in range(0,nvals,1): + match['attrs'][attrs[i][0]].append(unpack('>L', response[p:p+4])[0]) + p += 4 + p -= 4 + elif attrs[i][1] == SPH_ATTR_MULTI64: + match['attrs'][attrs[i][0]] = [] + nvals = unpack('>L', response[p:p+4])[0] + nvals = nvals/2 + p += 4 + for n in range(0,nvals,1): + match['attrs'][attrs[i][0]].append(unpack('>q', response[p:p+8])[0]) + p += 8 + p -= 4 + else: + match['attrs'][attrs[i][0]] = unpack('>L', response[p:p+4])[0] + p += 4 + + result['matches'].append ( match ) + + result['total'], result['total_found'], result['time'], words = unpack('>4L', response[p:p+16]) + + result['time'] = '%.3f' % (result['time']/1000.0) + p += 16 + + result['words'] = [] + while words>0: + words -= 1 + length = unpack('>L', response[p:p+4])[0] + p += 4 + word = bytes_str(response[p:p+length]) + p += length + docs, hits = unpack('>2L', response[p:p+8]) + p += 8 + + result['words'].append({'word':word, 'docs':docs, 'hits':hits}) + + self._reqs = [] + return results + + + def BuildExcerpts (self, docs, index, words, opts=None): + """ + Connect to searchd server and generate exceprts from given documents. + """ + if not opts: + opts = {} + + assert(isinstance(docs, list)) + assert(isinstance(index, (str,text_type))) + assert(isinstance(words, (str,text_type))) + assert(isinstance(opts, dict)) + + sock = self._Connect() + + if not sock: + return None + + # fixup options + opts.setdefault('before_match', '') + opts.setdefault('after_match', '') + opts.setdefault('chunk_separator', ' ... ') + opts.setdefault('html_strip_mode', 'index') + opts.setdefault('limit', 256) + opts.setdefault('limit_passages', 0) + opts.setdefault('limit_words', 0) + opts.setdefault('around', 5) + opts.setdefault('start_passage_id', 1) + opts.setdefault('passage_boundary', 'none') + + # build request + # v.1.0 req + + flags = 1 # (remove spaces) + if opts.get('exact_phrase'): flags |= 2 + if opts.get('single_passage'): flags |= 4 + if opts.get('use_boundaries'): flags |= 8 + if opts.get('weight_order'): flags |= 16 + if opts.get('query_mode'): flags |= 32 + if opts.get('force_all_words'): flags |= 64 + if opts.get('load_files'): flags |= 128 + if opts.get('allow_empty'): flags |= 256 + if opts.get('emit_zones'): flags |= 512 + if opts.get('load_files_scattered'): flags |= 1024 + + # mode=0, flags + req = bytearray() + req.extend(pack('>2L', 0, flags)) + + # req index + index = str_bytes(index) + req.extend(pack('>L', len(index))) + req.extend(index) + + # req words + words = str_bytes(words) + req.extend(pack('>L', len(words))) + req.extend(words) + + # options + opts_before_match = str_bytes(opts['before_match']) + req.extend(pack('>L', len(opts_before_match))) + req.extend(opts_before_match) + + opts_after_match = str_bytes(opts['after_match']) + req.extend(pack('>L', len(opts_after_match))) + req.extend(opts_after_match) + + opts_chunk_separator = str_bytes(opts['chunk_separator']) + req.extend(pack('>L', len(opts_chunk_separator))) + req.extend(opts_chunk_separator) + + req.extend(pack('>L', int(opts['limit']))) + req.extend(pack('>L', int(opts['around']))) + + req.extend(pack('>L', int(opts['limit_passages']))) + req.extend(pack('>L', int(opts['limit_words']))) + req.extend(pack('>L', int(opts['start_passage_id']))) + opts_html_strip_mode = str_bytes(opts['html_strip_mode']) + req.extend(pack('>L', len(opts_html_strip_mode))) + req.extend(opts_html_strip_mode) + opts_passage_boundary = str_bytes(opts['passage_boundary']) + req.extend(pack('>L', len(opts_passage_boundary))) + req.extend(opts_passage_boundary) + + # documents + req.extend(pack('>L', len(docs))) + for doc in docs: + doc = str_bytes(doc) + req.extend(pack('>L', len(doc))) + req.extend(doc) + + # send query, get response + length = len(req) + + # add header + req_head = bytearray() + req_head.extend(pack('>2HL', SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, length)) + req_all = req_head + req + self._Send ( sock, req_all ) + + response = self._GetResponse(sock, VER_COMMAND_EXCERPT ) + if not response: + return [] + + # parse response + pos = 0 + res = [] + rlen = len(response) + + for i in range(len(docs)): + length = unpack('>L', response[pos:pos+4])[0] + pos += 4 + + if pos+length > rlen: + self._error = 'incomplete reply' + return [] + + res.append(bytes_str(response[pos:pos+length])) + pos += length + + return res + + + def UpdateAttributes ( self, index, attrs, values, mva=False, ignorenonexistent=False ): + """ + Update given attribute values on given documents in given indexes. + Returns amount of updated documents (0 or more) on success, or -1 on failure. + + 'attrs' must be a list of strings. + 'values' must be a dict with int key (document ID) and list of int values (new attribute values). + optional boolean parameter 'mva' points that there is update of MVA attributes. + In this case the 'values' must be a dict with int key (document ID) and list of lists of int values + (new MVA attribute values). + Optional boolean parameter 'ignorenonexistent' points that the update will silently ignore any warnings about + trying to update a column which is not exists in current index schema. + + Example: + res = cl.UpdateAttributes ( 'test1', [ 'group_id', 'date_added' ], { 2:[123,1000000000], 4:[456,1234567890] } ) + """ + assert ( isinstance ( index, str ) ) + assert ( isinstance ( attrs, list ) ) + assert ( isinstance ( values, dict ) ) + for attr in attrs: + assert ( isinstance ( attr, str ) ) + for docid, entry in list(values.items()): + AssertUInt32(docid) + assert ( isinstance ( entry, list ) ) + assert ( len(attrs)==len(entry) ) + for val in entry: + if mva: + assert ( isinstance ( val, list ) ) + for vals in val: + AssertInt32(vals) + else: + AssertInt32(val) + + # build request + req = bytearray() + index = str_bytes(index) + req.extend( pack('>L',len(index)) + index ) + + req.extend ( pack('>L',len(attrs)) ) + ignore_absent = 0 + if ignorenonexistent: ignore_absent = 1 + req.extend ( pack('>L', ignore_absent ) ) + mva_attr = 0 + if mva: mva_attr = 1 + for attr in attrs: + attr = str_bytes(attr) + req.extend ( pack('>L',len(attr)) + attr ) + req.extend ( pack('>L', mva_attr ) ) + + req.extend ( pack('>L',len(values)) ) + for docid, entry in list(values.items()): + req.extend ( pack('>Q',docid) ) + for val in entry: + val_len = val + if mva: val_len = len ( val ) + req.extend ( pack('>L',val_len ) ) + if mva: + for vals in val: + req.extend ( pack ('>L',vals) ) + + # connect, send query, get response + sock = self._Connect() + if not sock: + return None + + length = len(req) + req_all = bytearray() + req_all.extend( pack ( '>2HL', SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, length ) ) + req_all.extend( req ) + self._Send ( sock, req_all ) + + response = self._GetResponse ( sock, VER_COMMAND_UPDATE ) + if not response: + return -1 + + # parse response + updated = unpack ( '>L', response[0:4] )[0] + return updated + + + def BuildKeywords ( self, query, index, hits ): + """ + Connect to searchd server, and generate keywords list for a given query. + Returns None on failure, or a list of keywords on success. + """ + assert ( isinstance ( query, str ) ) + assert ( isinstance ( index, str ) ) + assert ( isinstance ( hits, int ) ) + + # build request + req = bytearray() + query = str_bytes(query) + req.extend(pack ( '>L', len(query) ) + query) + index = str_bytes(index) + req.extend ( pack ( '>L', len(index) ) + index ) + req.extend ( pack ( '>L', hits ) ) + + # connect, send query, get response + sock = self._Connect() + if not sock: + return None + + length = len(req) + req_all = bytearray() + req_all.extend(pack ( '>2HL', SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, length )) + req_all.extend(req) + self._Send ( sock, req_all ) + + response = self._GetResponse ( sock, VER_COMMAND_KEYWORDS ) + if not response: + return None + + # parse response + res = [] + + nwords = unpack ( '>L', response[0:4] )[0] + p = 4 + max_ = len(response) + + while nwords>0 and pL', response[p:p+4] )[0] + p += 4 + tokenized = response[p:p+length] + p += length + + length = unpack ( '>L', response[p:p+4] )[0] + p += 4 + normalized = response[p:p+length] + p += length + + entry = { 'tokenized':bytes_str(tokenized), 'normalized':bytes_str(normalized) } + if hits: + entry['docs'], entry['hits'] = unpack ( '>2L', response[p:p+8] ) + p += 8 + + res.append ( entry ) + + if nwords>0 or p>max_: + self._error = 'incomplete reply' + return None + + return res + + def Status ( self, session=False ): + """ + Get the status + """ + + # connect, send query, get response + sock = self._Connect() + if not sock: + return None + + sess = 1 + if session: + sess = 0 + + req = pack ( '>2HLL', SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, sess ) + self._Send ( sock, req ) + + response = self._GetResponse ( sock, VER_COMMAND_STATUS ) + if not response: + return None + + # parse response + res = [] + + p = 8 + max_ = len(response) + + while pL', response[p:p+4] )[0] + k = response[p+4:p+length+4] + p += 4+length + length = unpack ( '>L', response[p:p+4] )[0] + v = response[p+4:p+length+4] + p += 4+length + res += [[bytes_str(k), bytes_str(v)]] + + return res + + ### persistent connections + + def Open(self): + if self._socket: + self._error = 'already connected' + return None + + server = self._Connect() + if not server: + return None + + # command, command version = 0, body length = 4, body = 1 + request = pack ( '>hhII', SEARCHD_COMMAND_PERSIST, 0, 4, 1 ) + self._Send ( server, request ) + + self._socket = server + return True + + def Close(self): + if not self._socket: + self._error = 'not connected' + return + self._socket.close() + self._socket = None + + def EscapeString(self, string): + return re.sub(r"([=\(\)|\-!@~\"&/\\\^\$\=\<])", r"\\\1", string) + + + def FlushAttributes(self): + sock = self._Connect() + if not sock: + return -1 + + request = pack ( '>hhI', SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ) # cmd, ver, bodylen + self._Send ( sock, request ) + + response = self._GetResponse ( sock, VER_COMMAND_FLUSHATTRS ) + if not response or len(response)!=4: + self._error = 'unexpected response length' + return -1 + + tag = unpack ( '>L', response[0:4] )[0] + return tag + +def AssertInt32 ( value ): + assert(isinstance(value, (int, long))) + assert(value>=-2**32-1 and value<=2**32-1) + +def AssertUInt32 ( value ): + assert(isinstance(value, (int, long))) + assert(value>=0 and value<=2**32-1) + +def SetBit ( flag, bit, on ): + if on: + flag += ( 1< (3,): + def str_bytes(x): + return bytearray(x, 'utf-8') +else: + def str_bytes(x): + if isinstance(x,unicode): + return bytearray(x, 'utf-8') + else: + return bytearray(x) + +def bytes_str(x): + assert (isinstance(x, bytearray)) + return x.decode('utf-8') + +# +# $Id$ +# diff --git a/plugins/manticoresearch/conf/manticore.conf b/plugins/manticoresearch/conf/manticore.conf new file mode 100755 index 000000000..f2a2f08b9 --- /dev/null +++ b/plugins/manticoresearch/conf/manticore.conf @@ -0,0 +1,27 @@ +indexer { + mem_limit = 1G +} + +searchd { + listen = 127.0.0.1:9322 + listen = 127.0.0.1:9326:mysql + listen = 127.0.0.1:9328:http + log = /var/log/manticore/searchd.log + query_log = /var/log/manticore/query.log + pid_file = /var/run/manticore/searchd.pid + data_dir = /var/lib/manticore + workers = threads + binlog_path = /var/lib/manticore + max_children = 1000 + max_matches = 1000000 +} + + +index mydocs +{ + type = rt + path = /usr/share/doc/manticore/README.Debian + rt_field = title + rt_attr_json = j +} + diff --git a/plugins/manticoresearch/ico.png b/plugins/manticoresearch/ico.png new file mode 100644 index 000000000..f8f4edeb9 Binary files /dev/null and b/plugins/manticoresearch/ico.png differ diff --git a/plugins/manticoresearch/index.html b/plugins/manticoresearch/index.html new file mode 100755 index 000000000..b625d8f91 --- /dev/null +++ b/plugins/manticoresearch/index.html @@ -0,0 +1,36 @@ + + +
              +
              +
              +

              服务

              +

              自启动

              +

              配置修改

              +

              运行日志

              +

              查询日志

              +

              运行状态

              +

              常用功能

              +

              说明

              +
              +
              +
              +
              +
              +
              + \ No newline at end of file diff --git a/plugins/manticoresearch/index.py b/plugins/manticoresearch/index.py new file mode 100755 index 000000000..0414cd93f --- /dev/null +++ b/plugins/manticoresearch/index.py @@ -0,0 +1,507 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import string +import subprocess + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + +def getPluginName(): + return 'manticoresearch' + +def getSeName(): + return 'manticore' + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + +sys.path.append(getPluginDir() +"/class") + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConfTpl(): + path = getPluginDir() + "/conf/manticore.conf" + return path + + +def getConf(): + path = "/etc/manticoresearch/manticore.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/'+getPluginName()) + return content + + +def status(): + cmd = "ps -ef|grep manticore |grep -v grep | grep -v mdserver-web | awk '{print $2}'" + data = mw.execShell(cmd) + # print(data) + if data[0] == '': + return 'stop' + return 'start' + + +def mkdirAll(): + content = mw.readFile(getConf()) + rep = r'path\s*=\s*(.*)' + p = re.compile(rep) + tmp = p.findall(content) + + for x in tmp: + if x.find('binlog') != -1: + mw.execShell('mkdir -p ' + x) + else: + mw.execShell('mkdir -p ' + os.path.dirname(x)) + mw.execShell("chown -R manticore:manticore "+x) + +def isInitFile(): + path = getServerDir() + '/init.pl' + if os.path.exists(path): + return True + mw.writeFile(path, 'ok') + return False + +def initDreplace(): + + dirs_list = [ + "/var/log/manticore", + "/var/run/manticore", + "/var/lib/manticore" + ] + + for d in dirs_list: + if not os.path.exists(d): + mw.execShell('mkdir -p ' + d) + + mw.execShell("chown -R manticore:manticore /var/run/manticore") + + + # config replace + conf_bin = getConf() + if not os.path.exists(conf_bin) or not isInitFile(): + conf_content = mw.readFile(getConfTpl()) + conf_content = contentReplace(conf_content) + mw.writeFile(getConf(), conf_content) + mkdirAll() + + return "ok" + + +def checkIndexSph(): + content = mw.readFile(getConf()) + rep = r'path\s*=\s*(.*)' + p = re.compile(rep) + tmp = p.findall(content) + for x in tmp: + if x.find('binlog') != -1: + continue + else: + p = x + '.sph' + if os.path.exists(p): + return False + return True + +def mcsOp(method): + initDreplace() + data = mw.execShell('systemctl ' + method + ' ' + getSeName()) + if data[1] == '': + return 'ok' + return 'fail' + + +def start(): + import tool_cron + tool_cron.createBgTask() + return mcsOp('start') + + +def stop(): + import tool_cron + tool_cron.removeBgTask() + return mcsOp('stop') + + +def restart(): + return mcsOp('restart') + + +def reload(): + return mcsOp('restart') + + +def rebuild(): + cmd = 'indexer --all --rotate' + data = mw.execShell(cmd) + if data[0].find('successfully')<0: + return data[0].replace("\n","
              ") + return 'ok' + + +def initdStatus(): + service_name = getSeName() + shell_cmd = 'systemctl status '+service_name+' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + service_name = getSeName() + mw.execShell('systemctl enable '+service_name) + return 'ok' + + +def initdUinstall(): + service_name = getSeName() + mw.execShell('systemctl disable '+service_name) + return 'ok' + + +def runLog(): + path = getConf() + content = mw.readFile(path) + rep = r'log\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0] + + +def getMainPort(): + path = getConf() + content = mw.readFile(path) + rep = r'listen\s*=\s*(.*)' + conf = re.search(rep, content) + port_line = conf.groups()[0] + return port_line.split(":")[1] + +def getMysqlPort(): + path = getConf() + content = mw.readFile(path) + rep = r'listen\s*=\s*(.*):mysql' + conf = re.search(rep, content) + port_line = conf.groups()[0] + return port_line.split(":")[1] + +def getHttpPort(): + path = getConf() + content = mw.readFile(path) + rep = r'listen\s*=\s*(.*):http' + conf = re.search(rep, content) + port_line = conf.groups()[0] + return port_line.split(":")[1] + + +def queryLog(): + path = getConf() + content = mw.readFile(path) + rep = r'query_log\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0] + + +def runStatus(): + s = status() + if s != 'start': + return mw.returnJson(False, '没有启动程序') + + sys.path.append(getPluginDir() + "/class") + import sphinxapi + + sh = sphinxapi.SphinxClient() + port = getMainPort() + sh.SetServer('127.0.0.1', port) + info_status = sh.Status() + + if info_status is None: + return mw.returnJson(False,'无法获取运行状态!') + + rData = {} + for x in range(len(info_status)): + rData[info_status[x][0]] = info_status[x][1] + + return mw.returnJson(True, 'ok', rData) + +def runStatusTest(): + s = status() + if s != 'start': + return mw.returnJson(False, '没有启动程序') + + sys.path.append(getPluginDir() + "/class") + import sphinxapi + + sh = sphinxapi.SphinxClient() + port = getMainPort() + sh.SetServer('127.0.0.1', port) + info_status = sh.Status() + + rData = {} + for x in range(len(info_status)): + rData[info_status[x][0]] = info_status[x][1] + + return mw.returnJson(True, 'ok', rData) + + +def sphinxConfParse(): + file = getConf() + bin_dir = getServerDir() + content = mw.readFile(file) + rep = r'index\s(.*)' + sindex = re.findall(rep, content) + indexlen = len(sindex) + cmd = {} + cmd['cmd'] = "indexer -c " + getConf() + + cmd['index'] = [] + cmd_index = [] + cmd_delta = [] + if indexlen > 0: + for x in range(indexlen): + name = sindex[x].strip() + if name == '': + continue + if name.find(':') != -1: + cmd_delta.append(name.strip()) + else: + cmd_index.append(name.strip()) + + # print(cmd_index) + # print(cmd_delta) + + for ci in cmd_index: + val = {} + val['index'] = ci + + for cd in cmd_delta: + cd = cd.replace(" ", '') + if cd.find(":"+ci) > -1: + val['delta'] = cd.split(":")[0].strip() + break + + cmd['index'].append(val) + return cmd + + +def sphinxCmd(): + data = sphinxConfParse() + if 'index' in data: + return mw.returnJson(True, 'ok', data) + else: + return mw.returnJson(False, 'no index') + +def makeDbToSphinxTest(): + conf_file = getConf() + import sphinx_make + sph_make = sphinx_make.sphinxMake() + conf = sph_make.makeSqlToSphinxAll() + + mw.writeFile(conf_file,conf) + print(conf) + # makeSqlToSphinxTable() + return True + +def makeDbToSphinx(): + args = getArgs() + check = checkArgs(args, ['db','tables','is_delta','is_cover']) + if not check[0]: + return check[1] + + db = args['db'] + tables = args['tables'] + is_delta = args['is_delta'] + is_cover = args['is_cover'] + + if is_cover != 'yes': + return mw.returnJson(False,'暂时仅支持覆盖!') + + sph_file = getConf() + + import sphinx_make + sph_make = sphinx_make.sphinxMake() + + version_pl = getServerDir() + "/version.pl" + if os.path.exists(version_pl): + version = mw.readFile(version_pl).strip() + sph_make.setVersion(version) + + if not sph_make.checkDbName(db): + return mw.returnJson(False,'保留数据库名称,不可用!') + is_delta_bool = False + if is_delta == 'yes': + is_delta_bool = True + if is_cover == 'yes': + tables = tables.split(',') + content = sph_make.makeSqlToSphinx(db, tables, is_delta_bool) + mw.writeFile(sph_file,content) + mkdirAll() + return mw.returnJson(True,'设置成功!') + + return mw.returnJson(True,'测试中') + + +# 全量更新 +def updateAll(): + data = sphinxConfParse() + cmd = data['cmd'] + if not 'index' in data: + return '无更新' + index = data['index'] + + for x in range(len(index)): + cmd_index = cmd + ' ' + index[x]['index'] + ' --rotate' + print(cmd_index) + os.system(cmd_index) + return '' + +#增量更新 +def updateDelta(): + data = sphinxConfParse() + cmd = data['cmd'] + if not 'index' in data: + return '无更新' + index = data['index'] + + for x in range(len(index)): + if 'delta' in index[x]: + cmd_index = cmd + ' ' + index[x]['delta'] + ' --rotate' + print(cmd_index) + os.system(cmd_index) + + cmd_index_merge = cmd + ' --merge ' + index[x]['index'] + ' ' + index[x]['delta'] + ' --rotate' + print(cmd_index_merge) + os.system(cmd_index_merge) + else: + print(index[x]['index'],'no delta') + + return '' + +def installPreInspection(version): + if mw.isAppleSystem(): + return '不支持mac系统' + return 'ok' + +if __name__ == "__main__": + version = "3.1.1" + version_pl = getServerDir() + "/version.pl" + if os.path.exists(version_pl): + version = mw.readFile(version_pl).strip() + + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'rebuild': + print(rebuild()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'conf': + print(getConf()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'run_log': + print(runLog()) + elif func == 'query_log': + print(queryLog()) + elif func == 'run_status': + print(runStatus()) + elif func == 'run_status_test': + print(runStatusTest()) + elif func == 'sphinx_cmd': + print(sphinxCmd()) + elif func == 'db_to_sphinx': + print(makeDbToSphinx()) + elif func == 'update_all': + print(updateAll()) + elif func == 'update_delta': + print(updateDelta()) + else: + print('error') diff --git a/plugins/manticoresearch/info.json b/plugins/manticoresearch/info.json new file mode 100755 index 000000000..1c7fecf72 --- /dev/null +++ b/plugins/manticoresearch/info.json @@ -0,0 +1,19 @@ +{ + "sort": 7, + "ps": "SphinxSearch项目演变而来,专注于提升搜索效率与扩展性", + "name": "manticoresearch", + "title": "manticoresearch", + "shell": "install.sh", + "versions":["7.4.6"], + "tip": "soft", + "install_pre_inspection":true, + "checks": "server/manticoresearch", + "path": "server/manticoresearch", + "display": 1, + "author": "manticoresoftware", + "date": "2025-07-21", + "home": "https://github.com/manticoresoftware/manticoresearch/", + "doc1": "https://manual.manticoresearch.com/Introduction", + "type": 0, + "pid": "2" +} \ No newline at end of file diff --git a/plugins/manticoresearch/install.sh b/plugins/manticoresearch/install.sh new file mode 100755 index 000000000..804ec448e --- /dev/null +++ b/plugins/manticoresearch/install.sh @@ -0,0 +1,33 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` +sysArch=`arch` + +# cd /www/server/mdserver-web/plugins/manticoresearch && bash install.sh install 7.4.6 +# cd /www/server/mdserver-web && python3 plugins/manticoresearch/index.py run_status_test +# cd /www/server/mdserver-web && python3 plugins/manticoresearch/index.py start +# systemctl status manticore +# systemctl restart manticore + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +ACTION=$1 +VERSION=$2 + +which apt +if [ "$?" == "0" ];then + sh -x $curPath/versions/apt/install.sh $1 $2 +fi + +which yum +if [ "$?" == "0" ];then + sh -x $curPath/versions/yum/install.sh $1 $2 +fi diff --git a/plugins/manticoresearch/js/manticoresearch.js b/plugins/manticoresearch/js/manticoresearch.js new file mode 100755 index 000000000..1058ba2b1 --- /dev/null +++ b/plugins/manticoresearch/js/manticoresearch.js @@ -0,0 +1,310 @@ +function spPostMin(method, args, callback){ + + var req_data = {}; + req_data['name'] = 'manticoresearch'; + req_data['func'] = method; + + if (typeof(args) != 'undefined' && args!=''){ + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myPost(method, args, callback, title){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + $.post('/plugins/run', {name:'mysql', func:method, args:_args}, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function spPost(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + spPostMin(method,args,function(data){ + layer.close(loadT); + if(typeof(callback) == 'function'){ + callback(data); + } + }); +} + +function mcsCommonFunc(){ + var con = ''; + con += '   '; + $(".soft-man-con").html(con); +} + +function autoMakeConf(){ + var xm_db_list; + + var con = '
                '; + con += '
              • 如果数据量比较大,第一次启动会失败!(可通过手动建立索引)
              • '; + con += '
              • 以下内容,需手动加入计划任务。
              • '; + layer.open({ + type: 1, + area: ['380px','350px'], + title: '自动创建配置', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                \ +
                \ + 选择数据库\ +
                \ + \ +
                \ +
                \ +
                \ + 选择表\ +
                \ +
                \ +
                \ +
                \ +
                \ + 是否增量\ +
                \ + \ +
                \ +
                \ +
                \ + 是否覆盖配置\ +
                \ + \ +
                \ +
                \ +
                  \ +
                • 具体配置,仍须手动修改!!!
                • \ +
                • 增量索引,需要有更新权限,主从分离时,需要主库配置
                • \ +
                \ +
                \ + ", + + success:function(l,i){ + $(l).find('.layui-layer-content').css('overflow','visible'); + + xm_db_list = xmSelect.render({ + el: '#table', + repeat: false, + toolbar: {show: true}, + data: [], + }); + + myPost('get_db_list', {"page":1,"page_size":20}, function(data){ + var rdata = $.parseJSON(data.data); + var dblist = rdata.data; + + var db_html = ''; + for (var i = 0; i < dblist.length; i++) { + db_html += ""; + } + + if (dblist.length > 0){ + initDbSelect(dblist[0]['name']); + } + $('select[name="dbname"]').html(db_html); + }); + + $('select[name="dbname"]').change(function(){ + var db = $('select[name="dbname"]').val(); + initDbSelect(db); + }); + + }, + yes:function(index){ + var args = {} + args['db'] = $('select[name="dbname"]').val(); + args['is_delta'] = $('select[name="is_delta"]').val(); + args['is_cover'] = $('select[name="is_cover"]').val(); + args['tables'] = xm_db_list.getValue('value').join(','); + // console.log(args); + spPost('db_to_sphinx', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + // console.log(rdata); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + confirmRebuildIndex(); + } + },{icon: rdata.status ? 1 : 2}, 2000); + }); + } + + }); + + function initDbSelect(db){ + if (db == ''){ + return; + } + getDbInfo(db, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var tables = rdata.tables; + + var idx_db = []; + for (var i = 0; i < tables.length; i++) { + var t = {}; + t['name'] = tables[i]['table_name']; + t['value'] = tables[i]['table_name']; + idx_db.push(t); + } + xm_db_list = xmSelect.render({el: '#table', filterable: true,repeat: false,toolbar: {show: true},data: idx_db,}); + }); + } + + function getDbInfo(db_name, callback){ + myPost('get_db_info', {name:db_name}, function(data){ + callback(data); + }); + } +} + +function rebuildIndex(){ + spPost('rebuild', '', function(data){ + if (data.data == 'ok'){ + layer.msg('重建成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg(data.data,{icon:2,time:10000,shade: [0.3, '#000']}); + } + }); +} + +function confirmRebuildIndex(){ + layer.confirm("是否重建索引?", {icon:3,closeBtn: 1} , function(){ + rebuildIndex(); + }); +} + + +function tryRebuildIndex(){ + layer.confirm("修改配置后,是否尝试重建索引!", {icon:3,closeBtn: 1} , function(){ + rebuildIndex(); + }); +} + + +function secToTime(s) { + var t; + if(s > -1){ + var hour = Math.floor(s/3600); + var min = Math.floor(s/60) % 60; + var sec = s % 60; + if(hour < 10) { + t = '0'+ hour + ":"; + } else { + t = hour + ":"; + } + + if(min < 10){t += "0";} + t += min + ":"; + if(sec < 10){t += "0";} + t += sec.toFixed(2); + } + return t; +} + + +function mcsRunStatus(){ + spPost('run_status', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata['status']){ + layer.msg(rdata['msg'],{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + var idata = rdata.data; + + var tbody = ''; + for (var i in idata) { + tbody += ''+i+'' + idata[i] + ''+i+''; + } + + var con = '
                \ + \ + \ + \ + \ + \ + \ +
                运行时间' + secToTime(idata.uptime) + '每秒查询' + parseInt(parseInt(idata.queries) / parseInt(idata.uptime)) + '
                总连接次数' + idata.connections + 'work_queue_length' +idata.work_queue_length + '
                agent_connect' + idata.agent_connect+ 'workers_active' + idata.workers_active + '
                agent_retry' + idata.agent_retry + 'workers_total' + idata.workers_total + '
                \ + \ + \ + \ + '+tbody+'\ + \ +
                \ +
                '; + + $(".soft-man-con").html(con); + }); +} + +function mcsReadme(){ + spPost('sphinx_cmd', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata['status']){ + layer.msg(rdata['msg'],{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + // console.log(rdata['data']); + var con = '
                  '; + + con += '
                • 如果数据量比较大,第一次启动会失败!(可通过手动建立索引)
                • '; + con += '
                • 以下内容,需手动加入计划任务。
                • '; + + con += '
                • 全量:' + rdata['data']['cmd'] + ' --all --rotate
                • '; + + //主索引 + for (var i = 0; i < rdata['data']['index'].length; i++) { + var index_kv = rdata['data']['index'][i]; + var index = index_kv['index']; + // console.log(index); + con += '
                • 主索引 :' + rdata['data']['cmd'] + ' '+ index +' --rotate
                • '; + if (typeof(index_kv['delta']) != 'undefined'){ + var delta = index_kv['delta']; + con += '
                • 增量索引 :' + rdata['data']['cmd'] + ' '+ delta +' --rotate
                • '; + con += '
                • 合并索引 :' + rdata['data']['cmd'] + ' --merge '+ index + ' ' + delta +' --rotate
                • '; + } + } + con += '
                '; + + $(".soft-man-con").html(con); + }); + +} + diff --git a/plugins/manticoresearch/tool_cron.py b/plugins/manticoresearch/tool_cron.py new file mode 100644 index 000000000..46a012559 --- /dev/null +++ b/plugins/manticoresearch/tool_cron.py @@ -0,0 +1,211 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'manticoresearch' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getTaskConf(): + conf = getServerDir() + "/cron_config.json" + return conf + +def getTaskDeltaConf(): + conf = getServerDir() + "/cron_delta_config.json" + return conf + +def getConfigData(): + conf = getTaskConf() + if os.path.exists(conf): + return json.loads(mw.readFile(getTaskConf())) + return { + "task_id": -1, + "period": "day-n", + "where1": "1", + "hour": "0", + "minute": "15", + } + +def getConfigDeltaData(): + conf = getTaskDeltaConf() + if os.path.exists(conf): + return json.loads(mw.readFile(getTaskDeltaConf())) + return { + "task_id": -1, + "period": "minute-n", + "where1": "3", + "hour": "0", + "minute": "0", + } + + +def createBgTask(): + removeBgTask() + removeDeltaBgTask() + + createBgTaskByName(getPluginName()) + createBgTaskDeltaByName(getPluginName()) + return True + + +def createBgTaskByName(name): + args = getConfigData() + _name = "[勿删]全量更新[" + name + "]" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in args and args["task_id"] > 0: + res = mw.M("crontab").field("id, name").where("id=?", (args["task_id"],)).find() + if res and res["id"] == args["task_id"]: + print("计划任务已经存在!") + return True + + mw_dir = mw.getPanelDir() + cmd = ''' +mw_dir=%s +rname=%s +plugin_path=%s +script_path=%s +logs_file=$plugin_path/${rname}.log +''' % (mw_dir, name, getServerDir(), getPluginDir()) + cmd += 'echo "★【`date +"%Y-%m-%d %H:%M:%S"`】 STSRT★" >> $logs_file' + "\n" + cmd += 'echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" >> $logs_file' + "\n" + cmd += 'echo "python3 $script_path/index.py update_all"' + "\n" + cmd += 'cd $mw_dir && python3 $script_path/index.py update_all' + "\n" + cmd += 'echo "【`date +"%Y-%m-%d %H:%M:%S"`】 END★" >> $logs_file' + "\n" + cmd += 'echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" >> $logs_file' + "\n" + + params = { + 'name': _name, + 'type': args['period'], + 'week': "", + 'where1': args['where1'], + 'hour': args['hour'], + 'minute': args['minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + args["task_id"] = task_id + args["name"] = name + mw.writeFile(getTaskConf(), json.dumps(args)) + +def createBgTaskDeltaByName(name): + args = getConfigDeltaData() + _name = "[勿删]增量更新[" + name + "]" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in args and args["task_id"] > 0: + res = mw.M("crontab").field("id, name").where("id=?", (args["task_id"],)).find() + if res and res["id"] == args["task_id"]: + print("计划任务已经存在!") + return True + + mw_dir = mw.getPanelDir() + cmd = ''' +mw_dir=%s +rname=%s +plugin_path=%s +script_path=%s +logs_file=$plugin_path/${rname}.log +''' % (mw_dir, name, getServerDir(), getPluginDir()) + cmd += 'echo "★【`date +"%Y-%m-%d %H:%M:%S"`】 STSRT★" >> $logs_file' + "\n" + cmd += 'echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" >> $logs_file' + "\n" + cmd += 'echo "python3 $script_path/index.py update_delta"' + "\n" + cmd += 'cd $mw_dir && python3 $script_path/index.py update_delta' + "\n" + cmd += 'echo "【`date +"%Y-%m-%d %H:%M:%S"`】 END★" >> $logs_file' + "\n" + cmd += 'echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" >> $logs_file' + "\n" + + params = { + 'name': _name, + 'type': args['period'], + 'week': "", + 'where1': args['where1'], + 'hour': args['hour'], + 'minute': args['minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + args["task_id"] = task_id + args["name"] = name + mw.writeFile(getTaskConf(), json.dumps(args)) + + +def removeBgTask(): + cfg = getConfigData() + if "task_id" in cfg and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data['status']: + cfg["task_id"] = -1 + mw.writeFile(getTaskConf(), json.dumps(cfg)) + return True + return False + +def removeDeltaBgTask(): + cfg = getConfigDeltaData() + if "task_id" in cfg and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data['status']: + cfg["task_id"] = -1 + mw.writeFile(getTaskDeltaConf(), json.dumps(cfg)) + return True + return False + + +if __name__ == "__main__": + if len(sys.argv) > 1: + action = sys.argv[1] + if action == "remove": + removeBgTask() + removeDeltaBgTask() + elif action == "add": + createBgTask() diff --git a/plugins/manticoresearch/tpl/discuz.conf b/plugins/manticoresearch/tpl/discuz.conf new file mode 100644 index 000000000..15ddcda3d --- /dev/null +++ b/plugins/manticoresearch/tpl/discuz.conf @@ -0,0 +1,107 @@ +# +# Discuz manticore Config File +# + +searchd { + listen = 127.0.0.1:9322 + listen = 127.0.0.1:9326:mysql + listen = 127.0.0.1:9328:http + log = /var/log/manticore/searchd.log + query_log = /var/log/manticore/query.log + pid_file = /var/run/manticore/searchd.pid + #data_dir = /var/lib/manticore +} + +source pre_forum_thread +{ + type = mysql + sql_host = 127.0.0.1 + sql_user = bbs + sql_pass = bbs + sql_db = bbs + sql_port = 3306 + sql_query_pre = SET NAMES UTF8 + sql_query_pre = REPLACE INTO bbs_common_sphinxcounter SELECT 1, MAX(tid) FROM bbs_forum_thread + sql_query = SELECT t.tid as id,t.tid,t.subject,t.digest,t.displayorder,t.authorid,t.lastpost,t.special \ + FROM bbs_forum_thread AS t WHERE t.tid>=$start AND t.tid<=$end + sql_query_range = SELECT (SELECT MIN(tid) FROM bbs_forum_thread),maxid FROM bbs_common_sphinxcounter WHERE indexid=1 + + sql_range_step = 5000 + sql_attr_uint = tid + + sql_attr_uint = digest + sql_attr_uint = displayorder + sql_attr_uint = authorid + sql_attr_uint = special + sql_attr_timestamp =lastpost +} + +index pre_forum_thread +{ + source = pre_forum_thread + path = {$SERVER_APP}/index/db/pre_forum_thread/index + + min_word_len = 2 + html_strip = 1 + ngram_len = 1 +} + + +# threads_minute +source pre_forum_thread_minute : pre_forum_thread +{ + sql_query_pre = SET NAMES UTF8 + sql_query_range = SELECT maxid-1,(SELECT MAX(tid) FROM bbs_forum_thread) FROM bbs_common_sphinxcounter WHERE indexid=1 +} + +# threads_minute +index pre_forum_thread_minute : pre_forum_thread +{ + source = pre_forum_thread_minute + path = {$SERVER_APP}/index/db/pre_forum_thread/pre_forum_thread_minute +} + +#posts +source pre_forum_post : pre_forum_thread +{ + type = mysql + sql_query_pre = SET NAMES UTF8 + sql_query_pre = REPLACE INTO bbs_common_sphinxcounter SELECT 2, MAX(pid) FROM bbs_forum_post + sql_query = SELECT p.pid AS id,p.tid,p.subject,p.message,t.digest,t.displayorder,t.authorid,t.lastpost,t.special \ + FROM bbs_forum_post AS p LEFT JOIN bbs_forum_thread AS t USING(tid) where p.pid >=$start and p.pid <=$end \ + AND p.first=1 + sql_query_range = SELECT (SELECT MIN(pid) FROM bbs_forum_post),maxid FROM bbs_common_sphinxcounter WHERE indexid=2 + sql_range_step = 5000 + sql_attr_uint = tid + sql_attr_uint = digest + sql_attr_uint= displayorder + sql_attr_uint = authorid + sql_attr_uint = special + sql_attr_timestamp = lastpost +} + +#posts +index pre_forum_post +{ + source = pre_forum_post + path = {$SERVER_APP}/index/db/pre_forum_thread/pre_forum_post + + min_word_len = 2 + html_strip = 1 + ngram_len = 1 +} + +#pre_forum_post_minute +source pre_forum_post_minute : pre_forum_post +{ + sql_query_pre = SET NAMES UTF8 + sql_query_range = SELECT maxid-1,(SELECT MAX(pid) FROM bbs_forum_post) FROM bbs_common_sphinxcounter WHERE indexid=2 +} + +#pre_forum_post_minute +index pre_forum_post_minute : pre_forum_post +{ + source = pre_forum_thread + path = {$SERVER_APP}/index/db/pre_forum_thread/pre_forum_post_minute +} + diff --git a/plugins/manticoresearch/tpl/none.conf b/plugins/manticoresearch/tpl/none.conf new file mode 100755 index 000000000..de93f5f85 --- /dev/null +++ b/plugins/manticoresearch/tpl/none.conf @@ -0,0 +1,9 @@ +searchd { + listen = 127.0.0.1:9322 + listen = 127.0.0.1:9326:mysql + listen = 127.0.0.1:9328:http + log = /var/log/manticore/searchd.log + query_log = /var/log/manticore/query.log + pid_file = /var/run/manticore/searchd.pid + data_dir = /var/lib/manticore +} diff --git a/plugins/manticoresearch/versions/apt/install.sh b/plugins/manticoresearch/versions/apt/install.sh new file mode 100755 index 000000000..cacf73657 --- /dev/null +++ b/plugins/manticoresearch/versions/apt/install.sh @@ -0,0 +1,71 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` +sysArch=`arch` + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +VERSION=$2 + +Install_App() +{ + echo '正在安装manticoresearch...' + mkdir -p $serverPath/manticoresearch + + MC_DIR=${serverPath}/source/manticoresearch + mkdir -p $MC_DIR + + wget --no-check-certificate -O $MC_DIR/manticore-repo.noarch.deb https://repo.manticoresearch.com/manticore-repo.noarch.deb + dpkg -i $MC_DIR/manticore-repo.noarch.deb + apt update -y + apt -y install manticore manticore-extra + + + echo "${VERSION}" > $serverPath/manticoresearch/version.pl + + + cd ${rootPath} && python3 plugins/manticoresearch/index.py start ${VERSION} + cd ${rootPath} && python3 plugins/manticoresearch/index.py initd_install ${VERSION} + + if [ -d ${MC_DIR} ];then + rm -rf ${MC_DIR} + fi +} + +Uninstall_App() +{ + cd ${rootPath} && python3 plugins/manticoresearch/index.py stop ${VERSION} + cd ${rootPath} && python3 plugins/manticoresearch/index.py initd_uninstall ${VERSION} + + apt -y remove manticore manticore-extra + apt -y autoremove + + if [ -d $serverPath/manticoresearch ];then + rm -rf $serverPath/manticoresearch + fi + + if [ -f /usr/lib/systemd/system/manticore.service ] || [ -f /lib/systemd/system/manticore.service ];then + systemctl stop manticore + systemctl disable manticore + rm -rf /usr/lib/systemd/system/manticore.service + rm -rf /lib/systemd/system/manticore.service + systemctl daemon-reload + fi + + echo "卸载manticoresearch成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/manticoresearch/versions/yum/install.sh b/plugins/manticoresearch/versions/yum/install.sh new file mode 100755 index 000000000..750be69c6 --- /dev/null +++ b/plugins/manticoresearch/versions/yum/install.sh @@ -0,0 +1,67 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` +sysArch=`arch` + + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +VERSION=$2 + +Install_App() +{ + echo '正在安装manticoresearch...' + mkdir -p $serverPath/manticoresearch + + MC_DIR=${serverPath}/source/manticoresearch + mkdir -p $MC_DIR + + yum install -y https://repo.manticoresearch.com/manticore-repo.noarch.rpm + yum install -y manticore manticore-extra + + echo "${VERSION}" > $serverPath/manticoresearch/version.pl + + if [ -d ${MC_DIR} ];then + rm -rf ${MC_DIR} + fi + + cd ${rootPath} && python3 plugins/manticoresearch/index.py start ${VERSION} + cd ${rootPath} && python3 plugins/manticoresearch/index.py initd_install ${VERSION} +} + +Uninstall_App() +{ + yum -y remove manticore manticore-extra + + if [ -d $serverPath/manticoresearch ];then + rm -rf $serverPath/manticoresearch + fi + + if [ -f /usr/lib/systemd/system/manticore.service ] || [ -f /lib/systemd/system/manticore.service ];then + systemctl stop manticore + systemctl disable manticore + rm -rf /usr/lib/systemd/system/manticore.service + rm -rf /lib/systemd/system/manticore.service + systemctl daemon-reload + fi + + cd ${rootPath} && python3 plugins/manticoresearch/index.py stop ${VERSION} + cd ${rootPath} && python3 plugins/manticoresearch/index.py initd_uninstall ${VERSION} + + echo "卸载manticoresearch成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/mariadb/conf/classic.cnf b/plugins/mariadb/conf/classic.cnf new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/mariadb/conf/gtid.cnf b/plugins/mariadb/conf/gtid.cnf new file mode 100644 index 000000000..5076776a6 --- /dev/null +++ b/plugins/mariadb/conf/gtid.cnf @@ -0,0 +1,4 @@ +[mysqld] +# SHOW GLOBAL VARIABLES LIKE '%gtid%' +gtid_mode=ON +enforce_gtid_consistency=ON \ No newline at end of file diff --git a/plugins/mariadb/conf/mariadb.sql b/plugins/mariadb/conf/mariadb.sql new file mode 100755 index 000000000..e091e3b21 --- /dev/null +++ b/plugins/mariadb/conf/mariadb.sql @@ -0,0 +1,58 @@ +CREATE TABLE IF NOT EXISTS `config` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `mysql_root` TEXT +); + +INSERT INTO `config` (`id`, `mysql_root`) VALUES (1, 'admin'); + +CREATE TABLE IF NOT EXISTS `databases` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pid` INTEGER, + `name` TEXT, + `username` TEXT, + `password` TEXT, + `accept` TEXT, + `rw` TEXT DEFAULT 'rw', + `ps` TEXT, + `addtime` TEXT +); +-- ALTER TABLE `databases` ADD COLUMN `rw` TEXT DEFAULT 'rw'; + +CREATE TABLE IF NOT EXISTS `master_replication_user` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` TEXT, + `password` TEXT, + `accept` TEXT, + `ps` TEXT, + `addtime` TEXT +); + +-- 从库配置主库的[ssh private key] +-- drop table `slave_id_rsa`; +CREATE TABLE IF NOT EXISTS `slave_id_rsa` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `ip` TEXT, + `port` TEXT, + `user` TEXT, + `db_user` TEXT, + `id_rsa` TEXT, + `ps` TEXT, + `addtime` TEXT +); + +-- 从库配置主库的[user] +-- drop table `slave_user`; +CREATE TABLE IF NOT EXISTS `slave_sync_user` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `ip` TEXT, + `port` TEXT, + `user` TEXT, + `pass` TEXT, + `mode` TEXT, + `cmd` TEXT, + `db` TEXT, + `addtime` TEXT +); + + + diff --git a/plugins/mariadb/conf/my.cnf b/plugins/mariadb/conf/my.cnf new file mode 100644 index 000000000..c1b7a9047 --- /dev/null +++ b/plugins/mariadb/conf/my.cnf @@ -0,0 +1,107 @@ +[client] +user = root +#password = your_password +port = 33106 +socket = {$SERVER_APP_PATH}/mysql.sock + +[mysqld] + +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33106 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +max_allowed_packet = 100M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=1 +tmp_table_size = 8M + +query_cache_type = 1 +query_cache_size = 4M + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 65535 + +skip-name-resolve +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + + +log-bin=mysql-bin +binlog_format=mixed +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +#log_queries_not_using_indexes=on +binlog-expire-logs-seconds = 2592000 + +relay-log=mdserver +relay-log-index=mdserver + +#master +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log-slave-updates +slave_skip_errors=1062 +#replicate-do-db +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test +replicate-wild-ignore-table=mysql.% + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_log_file_size = 5M +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 2 +innodb_file_per_table=1 + + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick +max_allowed_packet = 16M + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mariadb/ico.png b/plugins/mariadb/ico.png new file mode 100644 index 000000000..8a9f3b314 Binary files /dev/null and b/plugins/mariadb/ico.png differ diff --git a/plugins/mariadb/index.html b/plugins/mariadb/index.html new file mode 100755 index 000000000..17aeace3f --- /dev/null +++ b/plugins/mariadb/index.html @@ -0,0 +1,57 @@ +
                +
                +
                +
                + +

                服务

                +

                自启动

                +

                配置文件

                +

                存储位置

                +

                端口

                +

                当前状态

                +

                性能优化

                +

                日志

                +

                慢日志

                +

                BINLOG

                +

                管理列表

                +

                主从配置

                +
                +
                +
                +
                +
                +
                + + + \ No newline at end of file diff --git a/plugins/mariadb/index.py b/plugins/mariadb/index.py new file mode 100755 index 000000000..dad563e5b --- /dev/null +++ b/plugins/mariadb/index.py @@ -0,0 +1,3526 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +if mw.isAppleSystem(): + cmd = 'ls /usr/local/lib/ | grep python | cut -d \\ -f 1 | awk \'END {print}\'' + info = mw.execShell(cmd) + p = "/usr/local/lib/" + info[0].strip() + "/site-packages" + sys.path.append(p) + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'mariadb' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}').strip() + if (t == ''): + return tmp + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getBackupDir(): + bk_path = mw.getBackupDir() + "/database/mariadb" + if not os.path.isdir(bk_path): + mw.execShell("mkdir -p {}".format(bk_path)) + return bk_path + + +def getConf(): + path = getServerDir() + '/etc/my.cnf' + return path + + +def getDataDir(): + file = getConf() + content = mw.readFile(file) + rep = r'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getLogBinName(): + file = getConf() + content = mw.readFile(file) + rep = r'log-bin\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pid-file\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getDbPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getDbServerId(): + file = getConf() + content = mw.readFile(file) + rep = r'server-id\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getSocketFile(): + file = getConf() + content = mw.readFile(file) + rep = r'socket\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getInitdTpl(version=''): + path = getPluginDir() + '/init.d/mariadb' + version + '.tpl' + if not os.path.exists(path): + path = getPluginDir() + '/init.d/mariadb.tpl' + return path + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP_PATH}', service_path + '/mariadb') + server_id = int(time.time()) + content = content.replace('{$SERVER_ID}', str(server_id)) + + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + content = content.replace('user = mysql', 'user = ' + user) + return content + + +def pSqliteDb(dbname='databases'): + file = getServerDir() + '/mariadb.db' + name = 'mysql' + + import_sql = mw.readFile(getPluginDir() + '/conf/mariadb.sql') + md5_sql = mw.md5(import_sql) + + import_sign = False + save_md5_file = getServerDir() + '/import_sql.md5' + if os.path.exists(save_md5_file): + save_md5_sql = mw.readFile(save_md5_file) + if save_md5_sql != md5_sql: + import_sign = True + mw.writeFile(save_md5_file, md5_sql) + else: + mw.writeFile(save_md5_file, md5_sql) + + if not os.path.exists(file) or import_sql: + conn = mw.M(dbname).dbPos(getServerDir(), name) + csql_list = import_sql.split(';') + for index in range(len(csql_list)): + conn.execute(csql_list[index], ()) + + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + + +def pMysqlDb(name=''): + # pymysql + db = mw.getMyORM() + # MySQLdb | + # db = mw.getMyORMDb() + + db.setDbConf(getConf()) + db.setPort(getDbPort()) + db.setSocket(getSocketFile()) + db.setDbName(name) + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + db.setPwd(pwd) + return db + + +def makeInitRsaKey(version=''): + datadir = getServerDir() + "/data" + + mysql_pem = datadir + "/mysql.pem" + if not os.path.exists(mysql_pem): + rdata = mw.execShell( + 'cd ' + datadir + ' && openssl genrsa -out mysql.pem 1024') + # print(data) + rdata = mw.execShell( + 'cd ' + datadir + ' && openssl rsa -in mysql.pem -pubout -out mysql.pub') + # print(rdata) + + if not mw.isAppleSystem(): + mw.execShell('cd ' + datadir + ' && chmod 400 mysql.pem') + mw.execShell('cd ' + datadir + ' && chmod 444 mysql.pub') + mw.execShell('cd ' + datadir + ' && chown mysql:mysql mysql.pem') + mw.execShell('cd ' + datadir + ' && chown mysql:mysql mysql.pub') + + +def initDreplace(version=''): + initd_tpl = getInitdTpl(version) + + mysql_conf_dir = getServerDir() + '/etc' + if not os.path.exists(mysql_conf_dir): + os.mkdir(mysql_conf_dir) + + mysql_conf = mysql_conf_dir + '/my.cnf' + if not os.path.exists(mysql_conf): + mysql_conf_tpl = getPluginDir() + '/conf/my.cnf' + content = mw.readFile(mysql_conf_tpl) + content = contentReplace(content) + mw.writeFile(mysql_conf, content) + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + + file_bin = initD_path + '/' + getPluginName() + if not os.path.exists(file_bin): + content = mw.readFile(initd_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + mysql_tmp = getServerDir() + '/tmp' + if not os.path.exists(mysql_tmp): + os.mkdir(mysql_tmp) + mw.execShell("chown -R mysql:mysql " + mysql_tmp) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/mariadb.service' + systemServiceTpl = getPluginDir() + '/init.d/mariadb.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + if mw.getOs() != 'darwin': + mw.execShell('chown -R mysql mysql ' + getServerDir()) + return file_bin + + +def status(version=''): + try: + pid = getPidFile() + if os.path.exists(pid): + return 'start' + except Exception as e: + return 'stop' + return 'stop' + + +def binLog(): + args = getArgs() + conf = getConf() + con = mw.readFile(conf) + + if con.find('#log-bin=mysql-bin') != -1: + if 'status' in args: + return mw.returnJson(False, '0') + con = con.replace('#log-bin=mysql-bin', 'log-bin=mysql-bin') + con = con.replace('#binlog_format=mixed', 'binlog_format=mixed') + mw.writeFile(conf, con) + mw.execShell('sync') + restart() + else: + path = getDataDir() + if 'status' in args: + dsize = 0 + for n in os.listdir(path): + if len(n) < 9: + continue + if n[0:9] == 'mysql-bin': + dsize += os.path.getsize(path + '/' + n) + return mw.returnJson(True, dsize) + con = con.replace('log-bin=mysql-bin', '#log-bin=mysql-bin') + con = con.replace('binlog_format=mixed', '#binlog_format=mixed') + mw.writeFile(conf, con) + mw.execShell('sync') + restart() + mw.execShell('rm -f ' + path + '/mysql-bin.*') + + + return mw.returnJson(True, '设置成功!') + +def binLogList(): + args = getArgs() + data = checkArgs(args, ['page', 'page_size', 'tojs']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + data_dir = getDataDir() + log_bin_name = getLogBinName() + + alist = os.listdir(data_dir) + log_bin_l = [] + for x in range(len(alist)): + f = alist[x] + t = {} + if f.startswith(log_bin_name) and not f.endswith('.index'): + abspath = data_dir + '/' + f + t['name'] = f + t['size'] = os.path.getsize(abspath) + t['time'] = mw.getDataFromInt(os.path.getctime(abspath)) + log_bin_l.append(t) + + log_bin_l = sorted(log_bin_l, key=lambda x: x['time'], reverse=True) + + # print(log_bin_l) + # print(data_dir, log_bin_name) + + count = len(log_bin_l) + + page_start = (page - 1) * page_size + page_end = page_start + page_size + if page_end > count: + page_end = count + + data = {} + page_args = {} + page_args['count'] = count + page_args['p'] = page + page_args['row'] = page_size + page_args['tojs'] = args['tojs'] + data['page'] = mw.getPage(page_args) + data['data'] = log_bin_l[page_start:page_end] + + return mw.getJson(data) + + +def cleanBinLog(): + db = pMysqlDb() + cleanTime = time.strftime('%Y-%m-%d %H:%i:%s', time.localtime()) + db.execute("PURGE MASTER LOGS BEFORE '" + cleanTime + "';") + return mw.returnJson(True, '清理BINLOG成功!') + + +def getErrorLog(): + args = getArgs() + path = getDataDir() + filename = '' + for n in os.listdir(path): + if len(n) < 5: + continue + if n == 'error.log': + filename = path + '/' + n + break + # print filename + if not os.path.exists(filename): + return mw.returnJson(False, '指定文件不存在!') + if 'close' in args: + mw.writeFile(filename, '') + return mw.returnJson(False, '日志已清空') + info = mw.getLastLine(filename, 18) + return mw.returnJson(True, 'OK', info) + + +def getShowLogFile(): + file = getConf() + content = mw.readFile(file) + rep = r'slow-query-log-file\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def pGetDbUser(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + return 'mysql' + + +def initMysqlData(): + datadir = getDataDir() + if not os.path.exists(datadir + '/mysql'): + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + user = pGetDbUser() + cmd = 'cd ' + serverdir + ' && ./scripts/mariadb-install-db ' + \ + ' --defaults-file=' + myconf + data = mw.execShell(cmd) + # print(data[0]) + # print(data[1]) + + if not mw.isAppleSystem(): + mw.execShell('chown -R mysql:mysql ' + serverdir + '/data') + mw.execShell('chmod -R 755 ' + serverdir + '/data') + return False + return True + + +def initMariaDbPwd(): + time.sleep(5) + + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + pwd = mw.getRandomString(16) + + db_option = "-S " + serverdir + "/mysql.sock" + cmd_pass = serverdir + '/bin/mysql ' + db_option + ' -uroot -e' + cmd_pass = cmd_pass + \ + "\"flush privileges;use mysql;grant all privileges on *.* to 'root'@'localhost' identified by '" + pwd + "';" + cmd_pass = cmd_pass + "flush privileges;\"" + + # print(cmd_pass) + data = mw.execShell(cmd_pass) + # print(data) + if data[1].find("ERROR") != -1: + print("init mariadb password fail:" + data[1]) + exit(1) + + # 删除空账户 + drop_empty_user = serverdir + '/bin/mysql ' + db_option + '-uroot -p' + \ + pwd + ' -e "use mysql;delete from user where USER=\'\'"' + mw.execShell(drop_empty_user) + + # 删除测试数据库 + drop_test_db = serverdir + '/bin/mysql ' + db_option + ' -uroot -p' + \ + pwd + ' -e "drop database test";' + mw.execShell(drop_test_db) + + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (pwd,)) + + # 删除冗余账户 + hostname = mw.execShell('hostname')[0].strip() + if hostname != 'localhost': + drop_hostname = serverdir + '/bin/mysql --defaults-file=' + \ + myconf + ' -uroot -p' + pwd + ' -e "drop user \'\'@\'' + hostname + '\'";' + mw.execShell(drop_hostname) + + drop_root_hostname = serverdir + '/bin/mysql --defaults-file=' + \ + myconf + ' -uroot -p' + pwd + ' -e "drop user \'root\'@\'' + hostname + '\'";' + mw.execShell(drop_root_hostname) + return True + + +def myOp(version, method): + # import commands + init_file = initDreplace() + cmd = init_file + ' ' + method + try: + isInited = initMysqlData() + if not isInited: + if mw.isAppleSystem(): + setSkipGrantTables(True) + cmd_init_start = init_file + ' start' + subprocess.Popen(cmd_init_start, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + + time.sleep(6) + else: + mw.execShell('systemctl start mariadb') + + initMariaDbPwd() + + if mw.isAppleSystem(): + setSkipGrantTables(False) + cmd_init_stop = init_file + ' stop' + subprocess.Popen(cmd_init_stop, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + time.sleep(3) + else: + mw.execShell('systemctl stop mariadb') + + if mw.isAppleSystem(): + print + sub = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + sub.wait(5) + else: + mw.execShell('systemctl ' + method + ' mariadb') + + return 'ok' + except Exception as e: + return str(e) + + +def appCMD(version, action): + makeInitRsaKey(version) + return myOp(version, action) + + +def start(version=''): + return appCMD(version, 'start') + + +def stop(version=''): + return appCMD(version, 'stop') + + +def restart(version=''): + return appCMD(version, 'restart') + + +def reload(version=''): + return appCMD(version, 'reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status mariadb | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable mariadb') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable mariadb') + return 'ok' + + +def getMyDbPos(): + file = getConf() + content = mw.readFile(file) + rep = r'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def setMyDbPos(): + args = getArgs() + data = checkArgs(args, ['datadir']) + if not data[0]: + return data[1] + + s_datadir = getMyDbPos() + t_datadir = args['datadir'] + if t_datadir == s_datadir: + return mw.returnJson(False, '与当前存储目录相同,无法迁移文件!') + + if not os.path.exists(t_datadir): + mw.execShell('mkdir -p ' + t_datadir) + + stop() + mw.execShell('cp -rf ' + s_datadir + '/* ' + t_datadir + '/') + mw.execShell('chown -R mysql mysql ' + t_datadir) + mw.execShell('chmod -R 755 ' + t_datadir) + mw.execShell('rm -f ' + t_datadir + '/*.pid') + mw.execShell('rm -f ' + t_datadir + '/*.err') + + path = getServerDir() + myfile = path + '/etc/my.cnf' + mycnf = mw.readFile(myfile) + mw.writeFile(path + '/etc/my_backup.cnf', mycnf) + + mycnf = mycnf.replace(s_datadir, t_datadir) + mw.writeFile(myfile, mycnf) + start() + + result = mw.execShell( + 'ps aux|grep mysqld| grep -v grep|grep -v python') + if len(result[0]) > 10: + mw.writeFile('data/datadir.pl', t_datadir) + return mw.returnJson(True, '存储目录迁移成功!') + else: + mw.execShell('pkill -9 mysqld') + mw.writeFile(myfile, mw.readFile(path + '/etc/my_backup.cnf')) + start() + return mw.returnJson(False, '文件迁移失败!') + + +def getMyPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def setMyPort(): + args = getArgs() + data = checkArgs(args, ['port']) + if not data[0]: + return data[1] + + port = args['port'] + file = getConf() + content = mw.readFile(file) + rep = r"port\s*=\s*([0-9]+)\s*\n" + content = re.sub(rep, 'port = ' + port + '\n', content) + mw.writeFile(file, content) + restart() + return mw.returnJson(True, '编辑成功!') + +# python3 plugins/mariadb/index.py run_info {} +def runInfo(version): + + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + db = pMysqlDb() + data = db.query('show global status') + isError = isSqlError(data) + if isError != None: + return isError + + gets = ['Max_used_connections', 'Com_commit', 'Com_rollback', 'Questions', 'Innodb_buffer_pool_reads', 'Innodb_buffer_pool_read_requests', 'Key_reads', 'Key_read_requests', 'Key_writes', + 'Key_write_requests', 'Qcache_hits', 'Qcache_inserts', 'Bytes_received', 'Bytes_sent', 'Aborted_clients', 'Aborted_connects', + 'Created_tmp_disk_tables', 'Created_tmp_tables', 'Innodb_buffer_pool_pages_dirty', 'Opened_files', 'Open_tables', 'Opened_tables', 'Select_full_join', + 'Select_range_check', 'Sort_merge_passes', 'Table_locks_waited', 'Threads_cached', 'Threads_connected', 'Threads_created', 'Threads_running', 'Connections', 'Uptime'] + + result = {} + # print(data) + for d in data: + vname = d["Variable_name"] + for g in gets: + if vname == g: + result[g] = d["Value"] + + # print(result, int(result['Uptime'])) + result['Run'] = int(time.time()) - int(result['Uptime']) + tmp = db.query('show master status') + try: + result['File'] = tmp[0]["File"] + result['Position'] = tmp[0]["Position"] + except: + result['File'] = 'OFF' + result['Position'] = 'OFF' + return mw.getJson(result) + + +def myDbStatus(): + result = {} + db = pMysqlDb() + data = db.query('show variables') + isError = isSqlError(data) + if isError != None: + return isError + + gets = ['table_open_cache', 'thread_cache_size', 'key_buffer_size', 'tmp_table_size', 'max_heap_table_size', 'innodb_buffer_pool_size', + 'innodb_additional_mem_pool_size', 'innodb_log_buffer_size', 'max_connections', 'sort_buffer_size', 'read_buffer_size', 'read_rnd_buffer_size', 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + result['mem'] = {} + for d in data: + vname = d['Variable_name'] + for g in gets: + # print(g) + if vname == g: + result['mem'][g] = d["Value"] + return mw.getJson(result) + + +def setDbStatus(): + gets = ['key_buffer_size', 'tmp_table_size', 'max_heap_table_size', 'innodb_buffer_pool_size', 'innodb_log_buffer_size', 'max_connections', + 'table_open_cache', 'thread_cache_size', 'sort_buffer_size', 'read_buffer_size', 'read_rnd_buffer_size', 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + emptys = ['max_connections', 'thread_cache_size', 'table_open_cache'] + args = getArgs() + conFile = getConf() + content = mw.readFile(conFile) + n = 0 + for g in gets: + s = 'M' + if n > 5: + s = 'K' + if g in emptys: + s = '' + rep = r'\s*' + g + r'\s*=\s*\d+(M|K|k|m|G)?\n' + c = g + ' = ' + args[g] + s + '\n' + if content.find(g) != -1: + content = re.sub(rep, '\n' + c, content, 1) + else: + content = content.replace('[mysqld]\n', '[mysqld]\n' + c) + n += 1 + mw.writeFile(conFile, content) + return mw.returnJson(True, '设置成功!') + + +def isSqlError(mysqlMsg): + # 检测数据库执行错误 + mysqlMsg = str(mysqlMsg) + if "MySQLdb" in mysqlMsg: + return mw.returnJson(False, 'MySQLdb组件缺失!
                进入SSH命令行输入: pip install mysql-python | pip install mysqlclient==2.0.3') + if "2002," in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "2003," in mysqlMsg: + return mw.returnJson(False, "Can't connect to MySQL server on '127.0.0.1' (61)") + if "using password:" in mysqlMsg: + return mw.returnJson(False, '数据库密码错误,在管理列表-点击【修复】!') + if "1045," in mysqlMsg: + return mw.returnJson(False, '连接错误!') + if "SQL syntax" in mysqlMsg: + return mw.returnJson(False, 'SQL语法错误!') + if "Connection refused" in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "1133," in mysqlMsg: + return mw.returnJson(False, '数据库用户不存在!') + if "1007," in mysqlMsg: + return mw.returnJson(False, '数据库已经存在!') + return None + + +def __createUser(dbname, username, password, address): + pdb = pMysqlDb('mysql') + + if username == 'root': + dbname = '*' + + pdb.execute( + "CREATE USER `%s`@`localhost` IDENTIFIED BY '%s'" % (username, password)) + pdb.execute( + "grant all privileges on %s.* to `%s`@`localhost`" % (dbname, username)) + for a in address.split(','): + pdb.execute( + "CREATE USER `%s`@`%s` IDENTIFIED BY '%s'" % (username, a, password)) + pdb.execute( + "grant all privileges on %s.* to `%s`@`%s`" % (dbname, username, a)) + pdb.execute("flush privileges") + + +def getDbBackupListFunc(dbname=''): + bkDir = getBackupDir() + blist = os.listdir(bkDir) + r = [] + + bname = 'db_' + dbname + blen = len(bname) + for x in blist: + fbstr = x[0:blen] + if fbstr == bname: + r.append(x) + return r + + +def setDbBackup(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + scDir = getPluginDir() + '/scripts/backup.py' + cmd = 'python3 ' + scDir + ' database ' + args['name'] + ' 3' + os.system(cmd) + return mw.returnJson(True, 'ok') + + +def importDbBackup(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + file_path = getBackupDir() + '/' + file + file_path_sql = getBackupDir() + '/' + file.replace('.gz', '') + + if not os.path.exists(file_path_sql): + cmd = 'cd ' + mw.getBackupDir() + '/database/mariadb && gzip -d ' + file + mw.execShell(cmd) + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + mysql_cmd = getServerDir() + '/bin/mariadb -S ' + sock + ' -uroot -p' + pwd + \ + ' ' + name + ' < ' + file_path_sql + + # print(mysql_cmd) + os.system(mysql_cmd) + return mw.returnJson(True, 'ok') + + +def rootPwd(): + return pSqliteDb('config').where( + 'id=?', (1,)).getField('mysql_root') + + +# python3 plugins/mariadb/index.py import_db_external {"file":"xx.sql","name":"demo1"} +# python3 plugins/mariadb/index.py import_db_external {"file":"db_demo1_20231221_203614 2.sql","name":"demo1"} +def importDbExternal(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + import_dir = mw.getBackupDir() + '/import/' + + file_path = import_dir + file + if not os.path.exists(file_path): + return mw.returnJson(False, '文件突然消失?') + + exts = ['sql', 'gz', 'zip'] + tmp = file.split('.') + ext = tmp[len(tmp) - 1] + if ext not in exts: + return mw.returnJson(False, '导入数据库格式不对!') + + tmp = file.split('/') + tmpFile = tmp[len(tmp) - 1] + tmpFile = tmpFile.replace('.sql.' + ext, '.sql') + tmpFile = tmpFile.replace('.' + ext, '.sql') + tmpFile = tmpFile.replace('tar.', '') + + # print(tmpFile) + import_sql = "" + if file.find("sql.gz") > -1: + cmd = 'cd ' + import_dir + ' && gzip -dc ' + \ + file + " > " + import_dir + tmpFile + info = mw.execShell(cmd) + if info[1] == "": + import_sql = import_dir + tmpFile + + if file.find(".zip") > -1: + cmd = 'cd ' + import_dir + ' && unzip -o ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find("tar.gz") > -1: + cmd = 'cd ' + import_dir + ' && tar -zxvf ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find(".sql") > -1 and file.find(".sql.gz") == -1: + import_sql = import_dir + file + + if import_sql == "": + return mw.returnJson(False, '未找SQL文件') + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + os.environ["MYSQL_PWD"] = pwd + mysql_cmd = getServerDir() + '/bin/mariadb -S ' + sock + ' -uroot -p"' + \ + pwd + '" ' + name + ' < "' + import_sql+'"' + + # print(mysql_cmd) + os.system(mysql_cmd) + + if ext != 'sql': + os.remove(import_sql) + + return mw.returnJson(True, 'ok') + +def importDbExternalProgress(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && ' + cmd += 'python3 '+mw.getServerDir()+'/mdserver-web/plugins/mariadb/index.py import_db_external_progress_bar {"file":"'+file+'","name":"'+name+'"}' + return mw.returnJson(True, 'ok',cmd) + +def importDbExternalProgressBar(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + import_dir = mw.getFatherDir() + '/backup/import/' + + file_path = import_dir + file + if not os.path.exists(file_path): + return mw.returnJson(False, '文件突然消失?') + + exts = ['sql', 'gz', 'zip'] + ext = mw.getFileSuffix(file) + if ext not in exts: + return mw.returnJson(False, '导入数据库格式不对!') + + tmp = file.split('/') + tmpFile = tmp[len(tmp) - 1] + tmpFile = tmpFile.replace('.sql.' + ext, '.sql') + tmpFile = tmpFile.replace('.' + ext, '.sql') + tmpFile = tmpFile.replace('tar.', '') + + # print(tmpFile) + import_sql = "" + if file.find("sql.gz") > -1: + cmd = 'cd ' + import_dir + ' && gzip -dc ' + \ + file + " > " + import_dir + tmpFile + info = mw.execShell(cmd) + if info[1] == "": + import_sql = import_dir + tmpFile + + if file.find(".zip") > -1: + cmd = 'cd ' + import_dir + ' && unzip -o ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find("tar.gz") > -1: + cmd = 'cd ' + import_dir + ' && tar -zxvf ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find(".sql") > -1 and file.find(".sql.gz") == -1: + import_sql = import_dir + file + + if import_sql == "": + return mw.returnJson(False, '未找SQL文件') + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + my_cnf = getConf() + mysql_cmd = getServerDir() + '/bin/mariadb --defaults-file=' + my_cnf + \ + ' -uroot -p"' + pwd + '" -f ' + name + mysql_cmd_progress_bar = "pv -t -p " + import_sql + '|'+ mysql_cmd + print(mysql_cmd_progress_bar) + rdata = os.system(mysql_cmd_progress_bar) + return "" + + +def deleteDbBackup(): + args = getArgs() + data = checkArgs(args, ['filename', 'path']) + if not data[0]: + return data[1] + + path = args['path'] + full_file = "" + bkDir = getBackupDir() + full_file = bkDir + '/' + args['filename'] + if path != "": + full_file = path + "/" + args['filename'] + os.remove(full_file) + return mw.returnJson(True, 'ok') + + +def getDbBackupList(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + r = getDbBackupListFunc(args['name']) + bkDir = getBackupDir() + if not os.path.exists(bkDir): + os.mkdir(bkDir) + + rr = [] + for x in range(0, len(r)): + p = bkDir + '/' + r[x] + data = {} + data['name'] = r[x] + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + return mw.returnJson(True, 'ok', rr) + + +def getDbBackupImportList(): + + bkImportDir = mw.getBackupDir() + '/import' + if not os.path.exists(bkImportDir): + os.mkdir(bkImportDir) + + blist = os.listdir(bkImportDir) + + rr = [] + for x in range(0, len(blist)): + name = blist[x] + p = bkImportDir + '/' + name + data = {} + data['name'] = name + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + rdata = { + "list": rr, + "upload_dir": bkImportDir, + } + return mw.returnJson(True, 'ok', rdata) + + +def getDbList(): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('databases') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,pid,name,username,password,accept,rw,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + + for x in range(0, len(clist)): + dbname = clist[x]['name'] + blist = getDbBackupListFunc(dbname) + # print(blist) + clist[x]['is_backup'] = False + if len(blist) > 0: + clist[x]['is_backup'] = True + + count = conn.where(condition, ()).count() + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + info = {} + info['root_pwd'] = pSqliteDb('config').where( + 'id=?', (1,)).getField('mysql_root') + data['info'] = info + + return mw.getJson(data) + + +def syncGetDatabases(): + pdb = pMysqlDb('mysql') + psdb = pSqliteDb('databases') + data = pdb.query('show databases') + isError = isSqlError(data) + if isError != None: + return isError + users = pdb.query( + "select User,Host from user where User!='root' AND Host!='localhost' AND Host!=''") + nameArr = ['information_schema', 'performance_schema', 'mysql', 'sys'] + n = 0 + + # print(users) + for value in data: + vdb_name = value["Database"] + b = False + for key in nameArr: + if vdb_name == key: + b = True + break + if b: + continue + if psdb.where("name=?", (vdb_name,)).count() > 0: + continue + host = '127.0.0.1' + for user in users: + if vdb_name == user["User"]: + host = user["Host"] + break + + ps = vdb_name + if vdb_name == 'test': + ps = mw.getMsg('DATABASE_TEST') + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + if psdb.add('name,username,password,accept,ps,addtime', (vdb_name, vdb_name, '', host, ps, addTime)): + n += 1 + + msg = mw.getInfo('本次共从服务器获取了{1}个数据库!', (str(n),)) + return mw.returnJson(True, msg) + + +def toDbBase(find): + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + if len(find['password']) < 3: + find['username'] = find['name'] + find['password'] = mw.md5(str(time.time()) + find['name'])[0:10] + psdb.where("id=?", (find['id'],)).save( + 'password,username', (find['password'], find['username'])) + + result = pdb.execute("create database `" + find['name'] + "`") + if "using password:" in str(result): + return -1 + if "Connection refused" in str(result): + return -1 + + password = find['password'] + __createUser(find['name'], find['username'], password, find['accept']) + return 1 + + +def syncToDatabases(): + args = getArgs() + data = checkArgs(args, ['type', 'ids']) + if not data[0]: + return data[1] + + pdb = pMysqlDb() + result = pdb.execute("show databases") + isError = isSqlError(result) + if isError: + return isError + + stype = int(args['type']) + psdb = pSqliteDb('databases') + n = 0 + + if stype == 0: + data = psdb.field('id,name,username,password,accept').select() + for value in data: + result = toDbBase(value) + if result == 1: + n += 1 + else: + data = json.loads(args['ids']) + for value in data: + find = psdb.where("id=?", (value,)).field( + 'id,name,username,password,accept').find() + # print find + result = toDbBase(find) + if result == 1: + n += 1 + msg = mw.getInfo('本次共同步了{1}个数据库!', (str(n),)) + return mw.returnJson(True, msg) + + +def setRootPwd(version=''): + args = getArgs() + data = checkArgs(args, ['password']) + if not data[0]: + return data[1] + + #强制修改 + force = 0 + if 'force' in args and args['force'] == '1': + force = 1 + + + password = args['password'] + try: + pdb = pMysqlDb('mysql') + result = pdb.query("show databases") + isError = isSqlError(result) + if isError != None: + if force == 1: + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (password,)) + return mw.returnJson(True, '【强制修改】数据库root密码修改成功(不意为成功连接数据)!') + return isError + + cmd = "ALTER USER 'root'@'localhost' IDENTIFIED BY '" + password + "';" + r = pdb.execute(cmd) + # print(r) + + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (password,)) + orm = pMysqlDb() + orm.execute("flush privileges") + + msg = '' + if force == 1: + msg = ',无须强制!' + + return mw.returnJson(True, '数据库root密码修改成功!' + msg) + except Exception as ex: + return mw.returnJson(False, '修改错误:' + str(ex)) + + +def setUserPwd(version=''): + args = getArgs() + data = checkArgs(args, ['password', 'name', 'id']) + if not data[0]: + return data[1] + + newpassword = args['password'] + username = args['name'] + uid = args['id'] + try: + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + data = psdb.field('id,name,accept').where('id=?', (uid,)).find() + + cmd = "SET PASSWORD FOR '" + username + \ + "'@'localhost' = PASSWORD('" + newpassword + "')" + r = pdb.execute(cmd) + # print(cmd, r) + + accept = data['accept'] + alist = accept.split(',') + for x in alist: + cmd = "SET PASSWORD FOR '" + username + \ + "'@'" + x + "' = PASSWORD('" + newpassword + "')" + r = pdb.execute(cmd) + # print(cmd, r) + + psdb.where("id=?", (uid,)).setField('password', newpassword) + + orm = pMysqlDb() + orm.execute("flush privileges") + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]密码成功!', (data['name'],))) + except Exception as ex: + return mw.returnJson(False, mw.getInfo('修改数据库[{1}]密码失败[{2}]!', (data['name'], str(ex),))) + + +def setDbPs(): + args = getArgs() + data = checkArgs(args, ['id', 'name', 'ps']) + if not data[0]: + return data[1] + + ps = args['ps'] + sid = args['id'] + name = args['name'] + try: + psdb = pSqliteDb('databases') + psdb.where("id=?", (sid,)).setField('ps', ps) + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]备注成功!', (name,))) + except Exception as e: + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]备注失败!', (name,))) + + +def addDb(): + args = getArgs() + data = checkArgs(args, + ['password', 'name', 'codeing', 'db_user', 'dataAccess', 'ps']) + if not data[0]: + return data[1] + + if not 'address' in args: + address = '' + else: + address = args['address'].strip() + + dbname = args['name'].strip() + dbuser = args['db_user'].strip() + codeing = args['codeing'].strip() + password = args['password'].strip() + dataAccess = args['dataAccess'].strip() + ps = args['ps'].strip() + + reg = r"^[\w\.-]+$" + if not re.match(reg, args['name']): + return mw.returnJson(False, '数据库名称不能带有特殊符号!') + checks = ['root', 'mysql', 'test', 'sys', 'panel_logs'] + if dbuser in checks or len(dbuser) < 1: + return mw.returnJson(False, '数据库用户名不合法!') + if dbname in checks or len(dbname) < 1: + return mw.returnJson(False, '数据库名称不合法!') + + if len(password) < 1: + password = mw.md5(time.time())[0:8] + + wheres = { + 'utf8': 'utf8_general_ci', + 'utf8mb4': 'utf8mb4_general_ci', + 'gbk': 'gbk_chinese_ci', + 'big5': 'big5_chinese_ci' + } + codeStr = wheres[codeing] + + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + + if psdb.where("name=? or username=?", (dbname, dbuser)).count(): + return mw.returnJson(False, '数据库已存在!') + + result = pdb.execute("create database `" + dbname + + "` DEFAULT CHARACTER SET " + codeing + " COLLATE " + codeStr) + # print result + isError = isSqlError(result) + if isError != None: + return isError + + pdb.execute("drop user '" + dbuser + "'@'localhost'") + for a in address.split(','): + pdb.execute("drop user '" + dbuser + "'@'" + a + "'") + + __createUser(dbname, dbuser, password, address) + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + psdb.add('pid,name,username,password,accept,ps,addtime', + (0, dbname, dbuser, password, address, ps, addTime)) + return mw.returnJson(True, '添加成功!') + + +def delDb(): + args = getArgs() + data = checkArgs(args, ['id', 'name']) + if not data[0]: + return data[1] + try: + id = args['id'] + name = args['name'] + psdb = pSqliteDb('databases') + pdb = pMysqlDb('mysql') + find = psdb.where("id=?", (id,)).field( + 'id,pid,name,username,password,accept,ps,addtime').find() + accept = find['accept'] + username = find['username'] + + # 删除MYSQL + result = pdb.execute("drop database `" + name + "`") + + users = pdb.query("select Host from user where User='" + + username + "' AND Host!='localhost'") + pdb.execute("drop user '" + username + "'@'localhost'") + for us in users: + pdb.execute("drop user '" + username + "'@'" + us["Host"] + "'") + pdb.execute("flush privileges") + + # 删除SQLITE + psdb.where("id=?", (id,)).delete() + return mw.returnJson(True, '删除成功!') + except Exception as ex: + return mw.returnJson(False, '删除失败!' + str(ex)) + + +def getDbAccess(): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + username = args['username'] + pdb = pMysqlDb('mysql') + + users = pdb.query("select Host from user where User='" + + username + "' AND Host!='localhost'") + + isError = isSqlError(users) + if isError != None: + return isError + + if len(users) < 1: + return mw.returnJson(True, "127.0.0.1") + accs = [] + for c in users: + accs.append(c["Host"]) + userStr = ','.join(accs) + return mw.returnJson(True, userStr) + + +def setDbAccess(): + args = getArgs() + data = checkArgs(args, ['username', 'access']) + if not data[0]: + return data[1] + name = args['username'] + access = args['access'] + pdb = pMysqlDb('mysql') + psdb = pSqliteDb('databases') + + dbname = psdb.where('username=?', (name,)).getField('name') + + if name == 'root': + password = pSqliteDb('config').where( + 'id=?', (1,)).getField('mysql_root') + else: + password = psdb.where("username=?", (name,)).getField('password') + + users = pdb.query("select Host from user where User='" + + name + "' AND Host!='localhost'") + + for us in users: + pdb.execute("drop user '" + name + "'@'" + us["Host"] + "'") + + __createUser(dbname, name, password, access) + + psdb.where('username=?', (name,)).save('accept,rw', (access, 'rw',)) + return mw.returnJson(True, '设置成功!') + +def openSkipGrantTables(): + mycnf = getConf() + content = mw.readFile(mycnf) + content = content.replace('#skip-grant-tables','skip-grant-tables') + mw.writeFile(mycnf, content) + return True + +def closeSkipGrantTables(): + mycnf = getConf() + content = mw.readFile(mycnf) + content = content.replace('skip-grant-tables','#skip-grant-tables') + mw.writeFile(mycnf, content) + return True + + +def resetDbRootPwd(version): + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + pwd = mw.getRandomString(16) + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (pwd,)) + + db_option = "-S " + serverdir + "/mysql.sock" + cmd_pass = serverdir + '/bin/mariadb ' + db_option + ' -uroot -e' + cmd_pass = cmd_pass + \ + "\"flush privileges;use mysql;grant all privileges on *.* to 'root'@'localhost' identified by '" + pwd + "';" + cmd_pass = cmd_pass + "flush privileges;\"" + + data = mw.execShell(cmd_pass) + # print(data) + return True + +def fixDbAccess(version): + + pdb = pMysqlDb() + mdb_ddir = getDataDir() + if not os.path.exists(mdb_ddir): + return mw.returnJson(False, '数据目录不存在,尝试重启重建!') + + try: + psdb = pSqliteDb('databases') + data = pdb.query('show databases') + isError = isSqlError(data) + if isError != None: + # 重置密码 + appCMD(version, 'stop') + openSkipGrantTables() + appCMD(version, 'start') + time.sleep(3) + resetDbRootPwd(version) + + appCMD(version, 'stop') + closeSkipGrantTables() + appCMD(version, 'start') + return mw.returnJson(True, '修复成功!') + return mw.returnJson(True, '正常无需修复!') + except Exception as e: + return mw.returnJson(False, '修复失败请重试!') + + +def setDbRw(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'id', 'rw']) + if not data[0]: + return data[1] + + username = args['username'] + uid = args['id'] + rw = args['rw'] + + pdb = pMysqlDb('mysql') + psdb = pSqliteDb('databases') + dbname = psdb.where("id=?", (uid,)).getField('name') + users = pdb.query( + "select Host from user where User='" + username + "'") + + # show grants for demo@"127.0.0.1"; + for x in users: + # REVOKE ALL PRIVILEGES ON `imail`.* FROM 'imail'@'127.0.0.1'; + + sql = "REVOKE ALL PRIVILEGES ON `" + dbname + \ + "`.* FROM '" + username + "'@'" + x["Host"] + "';" + r = pdb.query(sql) + # print(sql, r) + + if rw == 'rw': + sql = "GRANT SELECT, INSERT, UPDATE, DELETE ON " + dbname + ".* TO " + \ + username + "@'" + x["Host"] + "'" + elif rw == 'r': + sql = "GRANT SELECT ON " + dbname + ".* TO " + \ + username + "@'" + x["Host"] + "'" + else: + sql = "GRANT all privileges ON " + dbname + ".* TO " + \ + username + "@'" + x["Host"] + "'" + pdb.execute(sql) + pdb.execute("flush privileges") + r = psdb.where("id=?", (uid,)).setField('rw', rw) + # print(r) + return mw.returnJson(True, '切换成功!') + + +def getDbInfo(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + db_name = args['name'] + pdb = pMysqlDb() + # print 'show tables from `%s`' % db_name + tables = pdb.query('show tables from `%s`' % db_name) + + ret = {} + sql = "select sum(DATA_LENGTH)+sum(INDEX_LENGTH) as sum_size from information_schema.tables where table_schema='%s'" % db_name + data_sum = pdb.query(sql) + + data = 0 + if data_sum[0]['sum_size'] != None: + data = data_sum[0]['sum_size'] + + ret['data_size'] = mw.toSize(data) + ret['database'] = db_name + + ret3 = [] + table_key = "Tables_in_" + db_name + for i in tables: + tb_sql = "show table status from `%s` where name = '%s'" % (db_name, i[ + table_key]) + table = pdb.query(tb_sql) + + tmp = {} + tmp['type'] = table[0]["Engine"] + tmp['rows_count'] = table[0]["Rows"] + tmp['collation'] = table[0]["Collation"] + + data_size = 0 + if table[0]['Avg_row_length'] != None: + data_size = table[0]['Avg_row_length'] + + if table[0]['Data_length'] != None: + data_size = table[0]['Data_length'] + + tmp['data_byte'] = data_size + tmp['data_size'] = mw.toSize(data_size) + tmp['table_name'] = table[0]["Name"] + ret3.append(tmp) + + ret['tables'] = (ret3) + + return mw.getJson(ret) + + +def repairTable(): + args = getArgs() + data = checkArgs(args, ['db_name', 'tables']) + if not data[0]: + return data[1] + + db_name = args['db_name'] + tables = json.loads(args['tables']) + pdb = pMysqlDb() + mtable = pdb.query('show tables from `%s`' % db_name) + + ret = [] + key = "Tables_in_" + db_name + for i in mtable: + for tn in tables: + if tn == i[key]: + ret.append(tn) + + if len(ret) > 0: + for i in ret: + pdb.execute('REPAIR TABLE `%s`.`%s`' % (db_name, i)) + return mw.returnJson(True, "修复完成!") + return mw.returnJson(False, "修复失败!") + + +def optTable(): + args = getArgs() + data = checkArgs(args, ['db_name', 'tables']) + if not data[0]: + return data[1] + + db_name = args['db_name'] + tables = json.loads(args['tables']) + pdb = pMysqlDb() + mtable = pdb.query('show tables from `%s`' % db_name) + ret = [] + key = "Tables_in_" + db_name + for i in mtable: + for tn in tables: + if tn == i[key]: + ret.append(tn) + + if len(ret) > 0: + for i in ret: + pdb.execute('OPTIMIZE TABLE `%s`.`%s`' % (db_name, i)) + return mw.returnJson(True, "优化成功!") + return mw.returnJson(False, "优化失败或者已经优化过了!") + + +def alterTable(): + args = getArgs() + data = checkArgs(args, ['db_name', 'tables']) + if not data[0]: + return data[1] + + db_name = args['db_name'] + tables = json.loads(args['tables']) + table_type = args['table_type'] + pdb = pMysqlDb() + mtable = pdb.query('show tables from `%s`' % db_name) + + ret = [] + key = "Tables_in_" + db_name + for i in mtable: + for tn in tables: + if tn == i[key]: + ret.append(tn) + + if len(ret) > 0: + for i in ret: + pdb.execute('alter table `%s`.`%s` ENGINE=`%s`' % + (db_name, i, table_type)) + return mw.returnJson(True, "更改成功!") + return mw.returnJson(False, "更改失败!") + + +def getTotalStatistics(): + st = status() + data = {} + + isInstall = os.path.exists(getServerDir() + '/version.pl') + + if st == 'start' and isInstall: + data['status'] = True + data['count'] = pSqliteDb('databases').count() + data['ver'] = mw.readFile(getServerDir() + '/version.pl').strip() + return mw.returnJson(True, 'ok', data) + else: + data['status'] = False + data['count'] = 0 + return mw.returnJson(False, 'fail', data) + + +def recognizeDbMode(): + conf = getConf() + con = mw.readFile(conf) + rep = r"!include %s/(.*)?\.cnf" % (getServerDir() + "/etc/mode",) + mode = 'none' + try: + data = re.findall(rep, con, re.M) + mode = data[0] + except Exception as e: + pass + return mode + + +def getDbrunMode(version=''): + mode = recognizeDbMode() + return mw.returnJson(True, "ok", {'mode': mode}) + + +def setDbrunMode(version=''): + if version == '5.5': + return mw.returnJson(False, "不支持切换") + + args = getArgs() + data = checkArgs(args, ['mode', 'reload']) + if not data[0]: + return data[1] + + mode = args['mode'] + dbreload = args['reload'] + + if not mode in ['classic', 'gtid']: + return mw.returnJson(False, "mode的值无效:" + mode) + + origin_mode = recognizeDbMode() + path = getConf() + con = mw.readFile(path) + rep = r"!include %s/%s\.cnf" % (getServerDir() + "/etc/mode", origin_mode) + rep_after = "!include %s/%s.cnf" % (getServerDir() + "/etc/mode", mode) + con = re.sub(rep, rep_after, con) + mw.writeFile(path, con) + + if version == '5.6': + dbreload = 'yes' + else: + db = pMysqlDb() + # The value of @@GLOBAL.GTID_MODE can only be changed one step at a + # time: OFF <-> OFF_PERMISSIVE <-> ON_PERMISSIVE <-> ON. Also note that + # this value must be stepped up or down simultaneously on all servers. + # See the Manual for instructions. + if mode == 'classic': + db.query('set global enforce_gtid_consistency=off') + db.query('set global gtid_mode=on') + db.query('set global gtid_mode=on_permissive') + db.query('set global gtid_mode=off_permissive') + db.query('set global gtid_mode=off') + elif mode == 'gtid': + db.query('set global enforce_gtid_consistency=on') + db.query('set global gtid_mode=off') + db.query('set global gtid_mode=off_permissive') + db.query('set global gtid_mode=on_permissive') + db.query('set global gtid_mode=on') + + if dbreload == "yes": + restart(version) + + return mw.returnJson(True, "切换成功!") + + +def findBinlogDoDb(): + conf = getConf() + con = mw.readFile(conf) + rep = r"binlog-do-db\s*?=\s*?(.*)" + dodb = re.findall(rep, con, re.M) + return dodb + + +def findBinlogSlaveDoDb(): + conf = getConf() + con = mw.readFile(conf) + rep = r"replicate-do-db\s*?=\s*?(.*)" + dodb = re.findall(rep, con, re.M) + return dodb + + +def setDbMasterAccess(): + args = getArgs() + data = checkArgs(args, ['username', 'access']) + if not data[0]: + return data[1] + username = args['username'] + access = args['access'] + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + password = psdb.where("username=?", (username,)).getField('password') + users = pdb.query("select Host from user where User='" + + username + "' AND Host!='localhost'") + for us in users: + pdb.execute("drop user '" + username + "'@'" + us["Host"] + "'") + + dbname = '*' + for a in access.split(','): + pdb.execute( + "CREATE USER `%s`@`%s` IDENTIFIED BY '%s'" % (username, a, password)) + pdb.execute( + "grant all privileges on %s.* to `%s`@`%s`" % (dbname, username, a)) + + pdb.execute("flush privileges") + psdb.where('username=?', (username,)).save('accept', (access,)) + return mw.returnJson(True, '设置成功!') + +def resetMaster(version=''): + pdb = pMysqlDb() + r = pdb.execute('reset master') + isError = isSqlError(r) + if isError != None: + return isError + return mw.returnJson(True, '重置成功!') + +def getMasterDbList(version=''): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('databases') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + dodb = findBinlogDoDb() + data['dodb'] = dodb + + slave_dodb = findBinlogSlaveDoDb() + + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,pid,name,username,password,accept,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + count = conn.where(condition, ()).count() + + for x in range(0, len(clist)): + if clist[x]['name'] in dodb: + clist[x]['master'] = 1 + else: + clist[x]['master'] = 0 + + if clist[x]['name'] in slave_dodb: + clist[x]['slave'] = 1 + else: + clist[x]['slave'] = 0 + + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def setDbMaster(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + conf = getConf() + con = mw.readFile(conf) + rep = r"(binlog-do-db\s*?=\s*?(.*))" + dodb = re.findall(rep, con, re.M) + + isHas = False + for x in range(0, len(dodb)): + + if dodb[x][1] == args['name']: + isHas = True + + con = con.replace(dodb[x][0] + "\n", '') + mw.writeFile(conf, con) + + if not isHas: + prefix = '#binlog-do-db' + con = con.replace(prefix, prefix + "\nbinlog-do-db=" + args['name']) + mw.writeFile(conf, con) + + restart(version) + time.sleep(4) + return mw.returnJson(True, '设置成功', [args, dodb]) + + +def setDbSlave(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + conf = getConf() + con = mw.readFile(conf) + rep = r"(replicate-do-db\s*?=\s*?(.*))" + dodb = re.findall(rep, con, re.M) + + isHas = False + for x in range(0, len(dodb)): + if dodb[x][1] == args['name']: + isHas = True + + con = con.replace(dodb[x][0] + "\n", '') + mw.writeFile(conf, con) + + if not isHas: + prefix = '#replicate-do-db' + con = con.replace(prefix, prefix + "\nreplicate-do-db=" + args['name']) + mw.writeFile(conf, con) + + restart(version) + time.sleep(4) + return mw.returnJson(True, '设置成功', [args, dodb]) + + +def getMasterStatus(version=''): + try: + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动,或正在启动中...!', []) + + conf = getConf() + content = mw.readFile(conf) + master_status = False + if content.find('#log-bin') == -1 and content.find('log-bin') > 1: + dodb = findBinlogDoDb() + if len(dodb) > 0: + master_status = True + + data = {} + data['mode'] = recognizeDbMode() + data['status'] = master_status + + pdb = pMysqlDb('mysql') + dlist = pdb.query('show slave status') + if len(dlist) < 1: + dlist = pdb.query("show all slaves status") + + for v in dlist: + if v["Slave_IO_Running"] == 'Yes' or v["Slave_SQL_Running"] == 'Yes': + data['slave_status'] = True + + return mw.returnJson(master_status, '设置成功', data) + except Exception as e: + return mw.returnJson(False, "数据库密码错误,在管理列表-点击【修复】!", 'pwd') + + +def setMasterStatus(version=''): + + conf = getConf() + con = mw.readFile(conf) + + if con.find('#log-bin') != -1: + return mw.returnJson(False, '必须开启二进制日志') + + sign = 'mdserver_ms_open' + + dodb = findBinlogDoDb() + if not sign in dodb: + prefix = '#binlog-do-db' + con = con.replace(prefix, prefix + "\nbinlog-do-db=" + sign) + mw.writeFile(conf, con) + else: + con = con.replace("binlog-do-db=" + sign + "\n", '') + rep = r"(binlog-do-db\s*?=\s*?(.*))" + dodb = re.findall(rep, con, re.M) + for x in range(0, len(dodb)): + con = con.replace(dodb[x][0] + "\n", '') + mw.writeFile(conf, con) + + restart(version) + return mw.returnJson(True, '设置成功') + + +def getMasterRepSlaveList(version=''): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('master_replication_user') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,username,password,accept,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + count = conn.where(condition, ()).count() + + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'getMasterRepSlaveList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def addMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'password']) + if not data[0]: + return data[1] + + if not 'address' in args: + address = '' + else: + address = args['address'].strip() + + username = args['username'].strip() + password = args['password'].strip() + # ps = args['ps'].strip() + # address = args['address'].strip() + # dataAccess = args['dataAccess'].strip() + + reg = r"^[\w-]+$" + if not re.match(reg, username): + return mw.returnJson(False, '用户名不能带有特殊符号!') + checks = ['root', 'mysql', 'test', 'sys', 'panel_logs'] + if username in checks or len(username) < 1: + return mw.returnJson(False, '用户名不合法!') + if password in checks or len(password) < 1: + return mw.returnJson(False, '密码不合法!') + + if len(password) < 1: + password = mw.md5(time.time())[0:8] + + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + + if psdb.where("username=?", (username)).count() > 0: + return mw.returnJson(False, '用户已存在!') + + sql = "GRANT REPLICATION SLAVE ON *.* TO '" + username + \ + "'@'%' identified by '" + password + "';" + result = pdb.execute(sql) + + isError = isSqlError(result) + if isError != None: + return isError + + sql_select = "grant select,reload,REPLICATION CLIENT,PROCESS on *.* to " + username + "@'%';" + pdb.execute(sql_select) + pdb.execute('FLUSH PRIVILEGES;') + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + psdb.add('username,password,accept,ps,addtime', + (username, password, '%', '', addTime)) + return mw.returnJson(True, '添加成功!') + + +def getMasterRepSlaveUserCmdSsh(version): + + args = getArgs() + data = checkArgs(args, ['username', 'db']) + if not data[0]: + return data[1] + + psdb = pSqliteDb('master_replication_user') + f = 'username,password' + username = args['username'] + if username == '': + count = psdb.count() + if count == 0: + return mw.returnJson(False, '请添加同步账户!') + + clist = psdb.field(f).limit('1').order('id desc').select() + else: + clist = psdb.field(f).where("username=?", (username,)).limit( + '1').order('id desc').select() + + if len(clist) == 0: + return mw.returnJson(False, '错误同步账户!') + + ip = mw.getLocalIp() + port = getMyPort() + db = pMysqlDb() + + mstatus = db.query('show master status') + if len(mstatus) == 0: + return mw.returnJson(False, '未开启!') + + mode = recognizeDbMode() + + # 查找同步点 + # SELECT BINLOG_GTID_POS('master1-bin.000002', 561866201); + + sid = getDbServerId() + connection_name = "" + if sid != '': + connection_name = "'r{}' ".format(sid) + + # MASTER_USE_GTID={current_pos|slave_pos|no} + # current_pos 依赖-> select @@global.gtid_current_pos; + # slave_pos 依赖-> select @@global.gtid_slave_pos; + # no -> 啥都不依赖,保证多主同步成功。同步出现问题,根据日志查找问题。 + + base_sql = "CHANGE MASTER " + connection_name + "TO MASTER_HOST='" + ip + "', MASTER_PORT=" + port + ", MASTER_USER='" + \ + clist[0]['username'] + "', MASTER_PASSWORD='" + \ + clist[0]['password']; + sql = '' + sql += base_sql + "', MASTER_LOG_FILE='" + mstatus[0]["File"] + \ + "',MASTER_LOG_POS=" + str(mstatus[0]["Position"]) + data = {} + data['cmd'] = sql + data["info"] = clist[0] + data['mode'] = mode + return mw.returnJson(True, 'ok!', data) + +def getMasterRepSlaveUserCmd(version): + + args = getArgs() + data = checkArgs(args, ['username', 'db']) + if not data[0]: + return data[1] + + psdb = pSqliteDb('master_replication_user') + f = 'username,password' + username = args['username'] + if username == '': + count = psdb.count() + if count == 0: + return mw.returnJson(False, '请添加同步账户!') + + clist = psdb.field(f).limit('1').order('id desc').select() + else: + clist = psdb.field(f).where("username=?", (username,)).limit( + '1').order('id desc').select() + + if len(clist) == 0: + return mw.returnJson(False, '错误同步账户!') + + ip = mw.getLocalIp() + port = getMyPort() + db = pMysqlDb() + + mstatus = db.query('show master status') + if len(mstatus) == 0: + return mw.returnJson(False, '未开启!') + + mode = recognizeDbMode() + + # 查找同步点 + # SELECT BINLOG_GTID_POS('master1-bin.000002', 561866201); + + sid = getDbServerId() + connection_name = "" + if sid != '': + connection_name = "'r{}' ".format(sid) + + # MASTER_USE_GTID={current_pos|slave_pos|no} + # current_pos 依赖-> select @@global.gtid_current_pos; + # slave_pos 依赖-> select @@global.gtid_slave_pos; + # no -> 啥都不依赖,保证多主同步成功。同步出现问题,根据日志查找问题。 + + base_sql = "CHANGE MASTER " + connection_name + "TO MASTER_HOST='" + ip + "', MASTER_PORT=" + port + ", MASTER_USER='" + \ + clist[0]['username'] + "', MASTER_PASSWORD='" + \ + clist[0]['password']; + sql = '' + sql += base_sql + "', MASTER_LOG_FILE='" + mstatus[0]["File"] + \ + "',MASTER_LOG_POS=" + str(mstatus[0]["Position"]) + sql += "

                "; + sql += base_sql + "',MASTER_USE_GTID=slave_pos,MASTER_CONNECT_RETRY=10;"; + sql += "
                "; + + data = {} + data['cmd'] = sql + data["info"] = clist[0] + data['mode'] = mode + + return mw.returnJson(True, 'ok!', data) + + +def delMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + + name = args['username'] + + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + pdb.execute("drop user '" + name + "'@'%'") + pdb.execute("drop user '" + name + "'@'localhost'") + + users = pdb.query("select Host from user where User='" + + name + "' AND Host!='localhost'") + for us in users: + pdb.execute("drop user '" + name + "'@'" + us["Host"] + "'") + + psdb.where("username=?", (args['username'],)).delete() + + return mw.returnJson(True, '删除成功!') + + +def updateMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'password']) + if not data[0]: + return data[1] + + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + pdb.execute("drop user '" + args['username'] + "'@'%'") + + pdb.execute("GRANT REPLICATION SLAVE ON *.* TO '" + + args['username'] + "'@'%' identified by '" + args['password'] + "'") + + psdb.where("username=?", (args['username'],)).save( + 'password', args['password']) + + return mw.returnJson(True, '更新成功!') + + +def getSlaveSSHList(version=''): + args = getArgs() + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + conn = pSqliteDb('slave_id_rsa') + limit = str((page - 1) * page_size) + ',' + str(page_size) + + field = 'id,ip,port,db_user,id_rsa,ps,addtime' + clist = conn.field(field).limit(limit).order('id desc').select() + count = conn.count() + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = args['tojs'] + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def getSlaveSyncUserByIp(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_sync_user') + data = conn.field('ip,port,user,pass,mode,cmd').where( + "ip=?", (ip,)).select() + return mw.returnJson(True, 'ok', data) + + +def addSlaveSyncUser(version=''): + import base64 + + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + if ip == "": + return mw.returnJson(True, 'ok') + + data = checkArgs(args, ['port', 'user', 'pass', 'mode']) + if not data[0]: + return data[1] + + cmd = args['cmd'] + port = args['port'] + user = args['user'] + apass = args['pass'] + mode = args['mode'] + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + conn = pSqliteDb('slave_sync_user') + data = conn.field('ip').where("ip=?", (ip,)).select() + if len(data) > 0: + res = conn.where("ip=?", (ip,)).save( + 'port,user,pass,mode,cmd', (port, user, apass, mode, cmd)) + else: + conn.add('ip,port,user,cmd,user,pass,mode,addtime', + (ip, port, user, cmd, user, apass, mode, addTime)) + + return mw.returnJson(True, '设置成功!') + + +def delSlaveSyncUser(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_sync_user') + conn.where("ip=?", (ip,)).delete() + return mw.returnJson(True, '删除成功!') + + +def getSlaveSyncUserList(version=''): + args = getArgs() + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + conn = pSqliteDb('slave_sync_user') + limit = str((page - 1) * page_size) + ',' + str(page_size) + + field = 'id,ip,port,user,pass,cmd,addtime' + clist = conn.field(field).limit(limit).order('id desc').select() + count = conn.count() + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = args['tojs'] + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def getSyncModeFile(): + return getServerDir() + "/sync.mode" + + +def getSlaveSyncMode(version): + sync_mode = getSyncModeFile() + if os.path.exists(sync_mode): + mode = mw.readFile(sync_mode).strip() + return mw.returnJson(True, 'ok', mode) + return mw.returnJson(False, 'fail') + + +def setSlaveSyncMode(version): + args = getArgs() + data = checkArgs(args, ['mode']) + if not data[0]: + return data[1] + mode = args['mode'] + sync_mode = getSyncModeFile() + + if mode == 'none': + os.remove(sync_mode) + else: + mw.writeFile(sync_mode, mode) + return mw.returnJson(True, '设置成功', mode) + + +def getSlaveSSHByIp(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_id_rsa') + data = conn.field('ip,port,db_user,id_rsa').where("ip=?", (ip,)).select() + return mw.returnJson(True, 'ok', data) + + +def addSlaveSSH(version=''): + import base64 + + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + if ip == "": + return mw.returnJson(True, 'ok') + + data = checkArgs(args, ['port', 'id_rsa', 'db_user']) + if not data[0]: + return data[1] + + id_rsa = args['id_rsa'] + port = args['port'] + db_user = args['db_user'] + user = 'root' + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + conn = pSqliteDb('slave_id_rsa') + data = conn.field('ip,id_rsa').where("ip=?", (ip,)).select() + if len(data) > 0: + res = conn.where("ip=?", (ip,)).save( + 'port,id_rsa,db_user', (port, id_rsa, db_user)) + else: + conn.add('ip,port,user,id_rsa,db_user,ps,addtime', + (ip, port, user, id_rsa, db_user, '', addTime)) + + return mw.returnJson(True, '设置成功!') + + +def delSlaveSSH(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_id_rsa') + conn.where("ip=?", (ip,)).delete() + return mw.returnJson(True, 'ok') + + +def updateSlaveSSH(version=''): + args = getArgs() + data = checkArgs(args, ['ip', 'id_rsa']) + if not data[0]: + return data[1] + + ip = args['ip'] + id_rsa = args['id_rsa'] + conn = pSqliteDb('slave_id_rsa') + conn.where("ip=?", (ip,)).save('id_rsa', (id_rsa,)) + return mw.returnJson(True, 'ok') + + +def getSlaveList(version=''): + db = pMysqlDb() + dlist = db.query('show slave status') + if len(dlist) == 0: + dlist = db.query('show all slaves status') + + data = {} + data['data'] = dlist + return mw.getJson(data) + +def trySlaveSyncBugfix(version=''): + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + if mode != 'sync-user': + return mw.returnJson(False, '仅支持【同步账户】模式') + + conn = pSqliteDb('slave_sync_user') + slave_sync_data = conn.field('ip,port,user,pass,mode,cmd').select() + if len(slave_sync_data) < 1: + return mw.returnJson(False, '需要先添加【同步用户】配置!') + + # print(slave_sync_data) + # 本地从库 + sdb = pMysqlDb() + + gtid_purged = '' + + for i in range(len(slave_sync_data)): + port = slave_sync_data[i]['port'] + password = slave_sync_data[i]['pass'] + host = slave_sync_data[i]['ip'] + user = slave_sync_data[i]['user'] + + # print(port, password, host) + + mdb = mw.getMyORM() + mdb.setHost(host) + mdb.setPort(port) + mdb.setUser(user) + mdb.setPwd(password) + mdb.setSocket('') + + # var_gtid = mdb.query('show VARIABLES like "%gtid_purged%"') + var_gtid = mdb.query('select @@global.gtid_current_pos as Value') + #print(var_gtid) + if len(var_gtid) > 0: + gtid_purged += var_gtid[0]['Value'] + ',' + + gtid_purged = gtid_purged.strip(',') + sql = "set @@global.gtid_slave_pos='" + gtid_purged + "'" + + sdb.query('stop all slaves') + # print(sql) + sdb.query(sql) + sdb.query('start all slaves') + return mw.returnJson(True, '修复成功!') + +def getSlaveSyncCmd(version=''): + root = mw.getPanelDir() + cmd = 'cd ' + root + ' && python3 ' + root + \ + '/plugins/mariadb/index.py do_full_sync {"db":"all","sign":""}' + return mw.returnJson(True, 'ok', cmd) + + +def initSlaveStatus(version=''): + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + if mode == 'ssh': + return initSlaveStatusSSH(version) + if mode == 'sync-user': + return initSlaveStatusSyncUser(version) + + +def parseSlaveSyncCmd(cmd): + a = {} + vlist = cmd.split(',') + + has_connection_name = vlist[0] + + pattern_c = r"CHANGE MASTER \'(.*)\' TO MASTER_HOST" + match_val = re.match(pattern_c, has_connection_name, re.I) + if match_val: + m_groups = match_val.groups() + a['Connection_name'] = m_groups[0] + + for i in vlist: + tmp = i.strip() + tmp_a = tmp.split(" ") + real_tmp = tmp_a[len(tmp_a) - 1] + kv = real_tmp.split("=") + a[kv[0]] = kv[1].replace("'", '').replace("'", '').replace(";", '') + return a + +# python3 plugins/mariadb/index.py init_slave_status {} +def initSlaveStatusSyncUser(version=''): + conn = pSqliteDb('slave_sync_user') + + slave_data = conn.field('id,ip,port,user,pass,mode,cmd').select() + if len(slave_data) < 1: + return mw.returnJson(False, '需要先添加同步用户配置!') + + pdb = pMysqlDb() + if len(slave_data) == 1: + dlist = pdb.query('show slave status') + if len(dlist) > 0: + return mw.returnJson(False, '已经初始化好了zz...') + + msg = '' + + pdb.query("stop slave") + pdb.query("stop all slaves") + + local_mode = recognizeDbMode() + for x in range(len(slave_data)): + slave_t = slave_data[x] + base_t = 'IP:' + slave_t['ip'] + ",PORT:" + \ + slave_t['port'] + ",USER:" + slave_t['user'] + + mode_name = 'classic' + if slave_data[x]['mode'] == '1': + mode_name = 'gtid' + + if local_mode != mode_name: + msg += base_t + '->同步模式不一致' + continue + + cmd_sql = slave_t['cmd'] + if cmd_sql == '': + msg += base_t + '->同步命令不能为空' + continue + + try: + pinfo = parseSlaveSyncCmd(cmd_sql) + except Exception as e: + return mw.returnJson(False, base_t + '->CMD同步命令不合规范:'+str(e)) + + pdb.query(cmd_sql) + + pdb.query("start slave") + pdb.query("start all slaves ") + + if msg == '': + msg = '初始化成功!' + return mw.returnJson(True, msg) + + +def initSlaveStatusSSH(version=''): + db = pMysqlDb() + dlist = db.query('show slave status') + if len(dlist) == 0: + dlist = db.query('show all slaves status') + + conn = pSqliteDb('slave_id_rsa') + ssh_list = conn.field('ip,port,id_rsa,db_user').select() + + if len(ssh_list) < 1: + return mw.returnJson(False, '需要先配置【[主]SSH配置】!') + + import paramiko + paramiko.util.log_to_file('paramiko.log') + ssh = paramiko.SSHClient() + + for data in ssh_list: + ip = data['ip'] + master_port = data['port'] + SSH_PRIVATE_KEY = "/tmp/t_ssh_" + ip + ".txt" + + mw.writeFile(SSH_PRIVATE_KEY, data['id_rsa'].replace('\\n', '\n')) + mw.execShell("chmod 600 " + SSH_PRIVATE_KEY) + + try: + key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(hostname=ip, port=int(master_port), + username='root', pkey=key) + + db_user = data['db_user'] + cmd = 'cd /www/server/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py get_master_rep_slave_user_cmd_ssh {"username":"' + db_user + '","db":""}' + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + if result.strip() == "": + return mw.returnJson(False, '[主][' + ip + ']:获取同步命令失败!') + cmd_data = json.loads(result) + time.sleep(1) + ssh.close() + if not cmd_data['status']: + return mw.returnJson(False, '[主][' + ip + ']:' + cmd_data['msg']) + + local_mode = recognizeDbMode() + if local_mode != cmd_data['data']['mode']: + return mw.returnJson(False, '[主][' + ip + ']【{}】从【{}】,运行模式不一致!'.format(cmd_data['data']['mode'], local_mode)) + + u = cmd_data['data']['info'] + ps = u['username'] + "|" + u['password'] + conn.where('ip=?', (ip,)).setField('ps', ps) + db.query('stop slave') + db.query('stop all slaves') + + # 保证同步IP一致 + cmd = cmd_data['data']['cmd'] + if cmd.find('SOURCE_HOST') > -1: + cmd = re.sub(r"SOURCE_HOST='(.*?)'", + "SOURCE_HOST='" + ip + "'", cmd, 1) + + if cmd.find('MASTER_HOST') > -1: + cmd = re.sub(r"MASTER_HOST='(.*?)'", + "MASTER_HOST='" + ip + "'", cmd, 1) + + # print(cmd) + db.query(cmd) + db.query("start slave") + db.query("start all slaves") + if os.path.exists(SSH_PRIVATE_KEY): + os.system("rm -rf " + SSH_PRIVATE_KEY) + except Exception as e: + return mw.returnJson(False, '[主][' + ip + ']:SSH认证配置连接失败!' + str(e)) + + return mw.returnJson(True, '初始化成功!') + + +def setSlaveStatus(version=''): + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + pdb = pMysqlDb() + dlist = pdb.query('show slave status') + if len(dlist) == 0: + dlist = pdb.query('show all slaves status') + + if len(dlist) == 0: + return mw.returnJson(False, '需要手动添加同步账户或者执行初始化!') + + for v in dlist: + connection_name = '' + cmd = "slave" + if 'Connection_name' in v: + connection_name = v['Connection_name'] + cmd = "slave '{}'".format(connection_name) + + if (v["Slave_IO_Running"] == 'Yes' or v["Slave_SQL_Running"] == 'Yes'): + pdb.query("stop {}".format(cmd)) + else: + pdb.query("start {}".format(cmd)) + + return mw.returnJson(True, '设置成功!') + + +def deleteSlave(version=''): + args = getArgs() + db = pMysqlDb() + if 'sign' in args: + sign = args['sign'] + db.query("stop slave '{}'".format(sign)) + db.query("reset slave '{}' all".format(sign)) + else: + db.query('stop slave') + db.query('reset slave all') + return mw.returnJson(True, '删除成功!') + + +def dumpMysqlData(version=''): + args = getArgs() + data = checkArgs(args, ['db']) + if not data[0]: + return data[1] + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + mysql_dir = getServerDir() + myconf = mysql_dir + "/etc/my.cnf" + + option = '' + mode = recognizeDbMode() + if mode == 'gtid': + option = ' --set-gtid-purged=off ' + + if args['db'].lower() == 'all': + dlist = findBinlogDoDb() + cmd = mysql_dir + "/bin/mysqldump --defaults-file=" + myconf + " " + option + " -uroot -p" + \ + pwd + " --databases " + \ + ' '.join(dlist) + " | gzip > /tmp/dump.sql.gz" + else: + cmd = mysql_dir + "/bin/mysqldump --defaults-file=" + myconf + " " + option + " -uroot -p" + \ + pwd + " --databases " + args['db'] + " | gzip > /tmp/dump.sql.gz" + + ret = mw.execShell(cmd) + if ret[0] == '': + return 'ok' + return 'fail' + +############### --- 重要 数据补足同步 ---- ########### + +def getSyncMysqlDB(dbname,sign = ''): + conn = pSqliteDb('slave_sync_user') + if sign != '': + data = conn.field('ip,port,user,pass,mode,cmd').where('ip=?', (sign,)).find() + else: + data = conn.field('ip,port,user,pass,mode,cmd').find() + user = data['user'] + apass = data['pass'] + port = data['port'] + ip = data['ip'] + # 远程数据 + sync_db = mw.getMyORM() + # MySQLdb | + sync_db.setPort(port) + sync_db.setHost(ip) + sync_db.setUser(user) + sync_db.setPwd(apass) + sync_db.setDbName(dbname) + sync_db.setTimeout(60) + return sync_db + +def syncDatabaseRepairTempFile(): + tmp_log = mw.getMWLogs()+ '/mariadb-check.log' + return tmp_log + +def syncDatabaseRepairLog(version=''): + import subprocess + args = getArgs() + data = checkArgs(args, ['db','sign','op']) + if not data[0]: + return data[1] + + sync_args_db = args['db'] + sync_args_sign = args['sign'] + op = args['op'] + tmp_log = syncDatabaseRepairTempFile() + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py sync_database_repair {"db":"'+sync_args_db+'","sign":"'+sync_args_sign+'"}' + # print(cmd) + + if op == 'get': + log = mw.getLastLine(tmp_log, 15) + return mw.returnJson(True, log) + + if op == 'cmd': + return mw.returnJson(True, 'ok', cmd) + + if op == 'do': + os.system(' echo "开始执行" > '+ tmp_log) + os.system(cmd +' >> '+ tmp_log +' &') + # time.sleep(10) + # mw.execShell('rm -rf '+tmp_log) + return mw.returnJson(True, 'ok') + + return mw.returnJson(False, '无效请求!') + + +def syncDatabaseRepair(version=''): + time_stats_s = time.time() + tmp_log = syncDatabaseRepairTempFile() + + from pymysql.converters import escape_string + args = getArgs() + data = checkArgs(args, ['db','sign']) + if not data[0]: + return data[1] + + sync_args_db = args['db'] + sync_args_sign = args['sign'] + + # 本地数据 + local_db = pMysqlDb() + # 远程数据 + sync_db = getSyncMysqlDB(sync_args_db,sync_args_sign) + + tables = local_db.query('show tables from `%s`' % sync_args_db) + table_key = "Tables_in_" + sync_args_db + inconsistent_table = [] + + tmp_dir = '/tmp/sync_db_repair' + mw.execShell('mkdir -p '+tmp_dir) + + for tb in tables: + + table_name = sync_args_db+'.'+tb[table_key] + table_check_file = tmp_dir+'/'+table_name+'.txt' + + if os.path.exists(table_check_file): + # print(table_name+', 已检查OK') + continue + + primary_key_sql = "SHOW INDEX FROM "+table_name+" WHERE Key_name = 'PRIMARY';"; + primary_key_data = local_db.query(primary_key_sql) + # print(primary_key_sql,primary_key_data) + pkey_name = '*' + if len(primary_key_data) == 1: + pkey_name = primary_key_data[0]['Column_name'] + # print(pkey_name) + if pkey_name != '*' : + # 智能校验(由于服务器同步可能会慢,比较总数总是对不上) + cmd_local_newpk_sql = 'select ' + pkey_name + ' from ' + table_name + " order by " + pkey_name + " desc limit 1" + cmd_local_newpk_data = local_db.query(cmd_local_newpk_sql) + # print(cmd_local_newpk_data) + if len(cmd_local_newpk_data) == 1: + # 比较总数 + cmd_count_sql = 'select count('+pkey_name+') as num from '+table_name + ' where '+pkey_name + ' <= '+ str(cmd_local_newpk_data[0][pkey_name]) + local_count_data = local_db.query(cmd_count_sql) + sync_count_data = sync_db.query(cmd_count_sql) + + if local_count_data != sync_count_data: + print(cmd_count_sql) + print("all data compare: ",local_count_data, sync_count_data) + else: + print(table_name+' smart compare check ok.') + mw.writeFile(tmp_log, table_name+' smart compare check ok.\n','a+') + mw.execShell("echo 'ok' > "+table_check_file) + continue + + + + # 比较总数 + cmd_count_sql = 'select count('+pkey_name+') as num from '+table_name + local_count_data = local_db.query(cmd_count_sql) + sync_count_data = sync_db.query(cmd_count_sql) + + if local_count_data != sync_count_data: + print("all data compare: ",local_count_data, sync_count_data) + inconsistent_table.append(table_name) + diff = sync_count_data[0]['num'] - local_count_data[0]['num'] + print(table_name+', need sync. diff,'+str(diff)) + mw.writeFile(tmp_log, table_name+', need sync. diff,'+str(diff)+'\n','a+') + else: + print(table_name+' check ok.') + mw.writeFile(tmp_log, table_name+' check ok.\n','a+') + mw.execShell("echo 'ok' > "+table_check_file) + + + # inconsistent_table = ['xx.xx'] + # 数据对齐 + for table_name in inconsistent_table: + is_break = False + while not is_break: + local_db.ping() + # 远程数据 + sync_db.ping() + + print("check table:"+table_name) + mw.writeFile(tmp_log, "check table:"+table_name+'\n','a+') + table_name_pos = 0 + table_name_pos_file = tmp_dir+'/'+table_name+'.pos.txt' + primary_key_sql = "SHOW INDEX FROM "+table_name+" WHERE Key_name = 'PRIMARY';"; + primary_key_data = local_db.query(primary_key_sql) + pkey_name = primary_key_data[0]['Column_name'] + + if os.path.exists(table_name_pos_file): + table_name_pos = mw.readFile(table_name_pos_file) + + + data_select_sql = 'select * from '+table_name + ' where '+pkey_name+' > '+str(table_name_pos)+' limit 10000' + print(data_select_sql) + local_select_data = local_db.query(data_select_sql) + + time_s = time.time() + sync_select_data = sync_db.query(data_select_sql) + print(f'sync query cos:{time.time() - time_s:.4f}s') + mw.writeFile(tmp_log, f'sync query cos:{time.time() - time_s:.4f}s\n','a+') + + # print(local_select_data) + # print(sync_select_data) + + # print(len(local_select_data)) + # print(len(sync_select_data)) + print('pos:',str(table_name_pos),'local compare sync,',local_select_data == sync_select_data) + + + cmd_count_sql = 'select count('+pkey_name+') as num from '+table_name + local_count_data = local_db.query(cmd_count_sql) + time_s = time.time() + sync_count_data = sync_db.query(cmd_count_sql) + print(f'sync count data cos:{time.time() - time_s:.4f}s') + print(local_count_data,sync_count_data) + # 数据同步有延迟,相等即任务数据补足完成 + if local_count_data[0]['num'] == sync_count_data[0]['num']: + is_break = True + break + + diff = sync_count_data[0]['num'] - local_count_data[0]['num'] + print("diff," + str(diff)+' line data!') + + if local_select_data == sync_select_data: + data_count = len(local_select_data) + if data_count == 0: + # mw.writeFile(table_name_pos_file, '0') + print(table_name+",data is equal ok..") + is_break = True + break + + # print(table_name,data_count) + pos = local_select_data[data_count-1][pkey_name] + print('pos',pos) + progress = pos/sync_count_data[0]['num'] + print('progress,%.2f' % progress+'%') + mw.writeFile(table_name_pos_file, str(pos)) + else: + sync_select_data_len = len(sync_select_data) + skip_idx = 0 + # 主库PK -> 查询本地 | 保证一致 + if sync_select_data_len > 0: + for idx in range(sync_select_data_len): + sync_idx_data = sync_select_data[idx] + local_idx_data = None + if idx in local_select_data: + local_idx_data = local_select_data[idx] + if sync_select_data[idx] == local_idx_data: + skip_idx = idx + pos = local_select_data[idx][pkey_name] + mw.writeFile(table_name_pos_file, str(pos)) + + # print(insert_data) + local_inquery_sql = 'select * from ' + table_name+ ' where ' +pkey_name+' = '+ str(sync_idx_data[pkey_name]) + # print(local_inquery_sql) + ldata = local_db.query(local_inquery_sql) + # print('ldata:',ldata) + if len(ldata) == 0: + print("id:"+ str(sync_idx_data[pkey_name])+ " no exists, insert") + insert_sql = 'insert into ' + table_name + field_str = '' + value_str = '' + for field in sync_idx_data: + field_str += '`'+field+'`,' + value_str += '\''+escape_string(str(sync_idx_data[field]))+'\',' + field_str = '(' +field_str.strip(',')+')' + value_str = '(' +value_str.strip(',')+')' + insert_sql = insert_sql+' '+field_str+' values'+value_str+';' + print(insert_sql) + r = local_db.execute(insert_sql) + print(r) + else: + # print('compare sync->local:',sync_idx_data == ldata[0] ) + if ldata[0] == sync_idx_data: + continue + + print("id:"+ str(sync_idx_data[pkey_name])+ " data is not equal, update") + update_sql = 'update ' + table_name + field_str = '' + value_str = '' + for field in sync_idx_data: + if field == pkey_name: + continue + field_str += '`'+field+'`=\''+escape_string(str(sync_idx_data[field]))+'\',' + field_str = field_str.strip(',') + update_sql = update_sql+' set '+field_str+' where '+pkey_name+'=\''+str(sync_idx_data[pkey_name])+'\';' + print(update_sql) + r = local_db.execute(update_sql) + print(r) + + # 本地PK -> 查询主库 | 保证一致 + # local_select_data_len = len(local_select_data) + # if local_select_data_len > 0: + # for idx in range(local_select_data_len): + # if idx < skip_idx: + # continue + # local_idx_data = local_select_data[idx] + # print('local idx check', idx, skip_idx) + # local_inquery_sql = 'select * from ' + table_name+ ' where ' +pkey_name+' = '+ str(local_idx_data[pkey_name]) + # print(local_inquery_sql) + # sdata = sync_db.query(local_inquery_sql) + # sdata_len = len(sdata) + # print('sdata:',sdata,sdata_len) + # if sdata_len == 0: + # delete_sql = 'delete from ' + table_name + ' where ' +pkey_name+' = '+ str(local_idx_data[pkey_name]) + # print(delete_sql) + # r = local_db.execute(delete_sql) + # print(r) + # break + + + if is_break: + print("break all") + break + time.sleep(3) + print(f'data check cos:{time.time() - time_stats_s:.4f}s') + print("data supplementation completed") + mw.execShell('rm -rf '+tmp_dir) + return 'ok' + + +############### --- 重要 同步---- ########### + + +def asyncTmpfile(): + path = '/tmp/mariadb_async_status.txt' + return path + + +def writeDbSyncStatus(data): + path = asyncTmpfile() + mw.writeFile(path, json.dumps(data)) + + +def fullSync(version=''): + args = getArgs() + data = checkArgs(args, ['db', 'begin']) + if not data[0]: + return data[1] + + sign = '' + if 'sign' in args: + sign = args['sign'] + + status_file = asyncTmpfile() + if args['begin'] == '1': + cmd = 'cd ' + mw.getPanelDir() + ' && python3 ' + \ + getPluginDir() + \ + '/index.py do_full_sync {"db":"' + args['db'] + '","sign":"' + sign + '"} &' + # print(cmd) + mw.execShell(cmd) + return json.dumps({'code': 0, 'msg': '同步数据中!', 'progress': 0}) + + if os.path.exists(status_file): + c = mw.readFile(status_file) + tmp = json.loads(c) + if tmp['code'] == 1: + sys_dump_sql = "/tmp/dump.sql" + if os.path.exists(sys_dump_sql): + dump_size = os.path.getsize(sys_dump_sql) + tmp['msg'] = tmp['msg'] + ":" + "同步文件:" + mw.toSize(dump_size) + c = json.dumps(tmp) + + # if tmp['code'] == 6: + # os.remove(status_file) + return c + + return json.dumps({'code': 0, 'msg': '点击开始,开始同步!', 'progress': 0}) + + +def fullSyncCmd(): + time_all_s = time.time() + args = getArgs() + data = checkArgs(args, ['db', 'sign']) + if not data[0]: + return data[1] + + db = args['db'] + sign = args['sign'] + + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py do_full_sync {"db":"'+db+'","sign":"'+sign+'"}' + return mw.returnJson(True,'ok',cmd) + +# python3 plugins/mariadb/index.py do_full_sync {"db":"demo1","sign":"","beigin":"1"} +def doFullSync(version=''): + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + if mode == 'ssh': + return doFullSyncSSH(version) + if mode == 'sync-user': + return doFullSyncUser(version) + + +def doFullSyncUser(version=''): + which_pv = mw.execShell('which pv') + is_exist_pv = False + if os.path.exists(which_pv[0]): + is_exist_pv = True + + + args = getArgs() + data = checkArgs(args, ['db', 'sign']) + if not data[0]: + return data[1] + + time_all_s = time.time() + sync_db = args['db'] + sync_sign = args['sign'] + + # print(sync_sign, sync_db) + db = pMysqlDb() + + conn = pSqliteDb('slave_sync_user') + if sync_sign != '': + data = conn.field('ip,port,user,pass,mode,cmd').where( + 'ip=?', (sync_sign,)).find() + else: + data = conn.field('ip,port,user,pass,mode,cmd').find() + + user = data['user'] + apass = data['pass'] + port = data['port'] + ip = data['ip'] + + bak_file = '/tmp/tmp.sql' + if os.path.exists(bak_file): + os.system("rm -rf " + bak_file) + + writeDbSyncStatus({'code': 0, 'msg': '开始同步...', 'progress': 0}) + + dmp_option = '' + mode = recognizeDbMode() + # if mode == 'gtid': + # dmp_option = ' --set-gtid-purged=off ' + + writeDbSyncStatus({'code': 1, 'msg': '远程导出数据...', 'progress': 10}) + + find_run_dump = mw.execShell('ps -ef | grep mariadb-dump | grep -v grep') + if find_run_dump[0] != "": + print("正在远程导出数据中,别着急...") + writeDbSyncStatus({'code': 3.1, 'msg': '正在远程导出数据中,别着急...', 'progress': 39}) + return False + + time_s = time.time() + if not os.path.exists(bak_file): + dmp_option += " --master-data=1 --apply-slave-statements --include-master-host-port " + + # https://mariadb.com/kb/zh-cn/mariadb-dump/ + dump_sql_data = getServerDir() + "/bin/mariadb-dump " + dmp_option + " -f --default-character-set=utf8 --single-transaction --compress -q -h" + ip + " -P" + \ + port + " -u" + user + " -p'" + apass + "' " + sync_db + ">" + bak_file + print(dump_sql_data) + mw.execShell(dump_sql_data) + + time_e = time.time() + export_cos = time_e - time_s + print("export cos:", export_cos) + writeDbSyncStatus({'code': 3, 'msg': '导出耗时:'+str(int(export_cos))+'秒,正在到本地导入数据中...', 'progress': 40}) + + + find_run_import = mw.execShell('ps -ef | grep mariadb| grep '+ bak_file +' | grep -v grep') + if find_run_import[0] != "": + print("正在导入数据中,别着急...") + writeDbSyncStatus({'code': 4.1, 'msg': '正在导入数据中,别着急...', 'progress': 59}) + return False + + # if os.path.exists(bak_file): + # db.execute('reset master') + + time_s = time.time() + if os.path.exists(bak_file): + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + if is_exist_pv: + my_import_cmd = getServerDir() + '/bin/mariadb -S ' + sock + " -uroot -p'" + pwd + "' " + sync_db + my_import_cmd = "pv -t -p " + bak_file + '|' + my_import_cmd + print(my_import_cmd) + os.system(my_import_cmd) + else: + my_import_cmd = getServerDir() + '/bin/mariadb -S ' + sock + " -uroot -p'" + pwd + \ + "' " + sync_db + '<' + bak_file + print(my_import_cmd) + mw.execShell(my_import_cmd) + + time_e = time.time() + import_cos = time_e - time_s + print("import cos:", import_cos) + writeDbSyncStatus({'code': 4, 'msg': '导入耗时:'+str(int(import_cos))+'秒', 'progress': 60}) + + time.sleep(3) + + pinfo = parseSlaveSyncCmd(data['cmd']) + # print(pinfo) + if 'Connection_name' in pinfo: + db.query("start slave '{}'".format(pinfo['Connection_name'])) + else: + db.query("start slave") + + writeDbSyncStatus({'code': 6, 'msg': '从库重启完成...', 'progress': 100}) + + if os.path.exists(bak_file): + os.system("rm -rf " + bak_file) + return True + + +def doFullSyncSSH(version=''): + + args = getArgs() + data = checkArgs(args, ['db', 'sign']) + if not data[0]: + return data[1] + + db = pMysqlDb() + + sync_db = args['db'] + sync_sign = args['sign'] + + id_rsa_conn = pSqliteDb('slave_id_rsa') + if sync_sign != '': + data = id_rsa_conn.field('ip,port,db_user,id_rsa').where( + 'ip=?', (sync_sign,)).find() + else: + data = id_rsa_conn.field('ip,port,db_user,id_rsa').find() + + SSH_PRIVATE_KEY = "/tmp/mysql_sync_id_rsa.txt" + id_rsa = data['id_rsa'].replace('\\n', '\n') + mw.writeFile(SSH_PRIVATE_KEY, id_rsa) + + ip = data["ip"] + master_port = data['port'] + db_user = data['db_user'] + print("master ip:", ip) + + writeDbSyncStatus({'code': 0, 'msg': '开始同步...', 'progress': 0}) + + import paramiko + paramiko.util.log_to_file('paramiko.log') + ssh = paramiko.SSHClient() + + print(SSH_PRIVATE_KEY) + if not os.path.exists(SSH_PRIVATE_KEY): + writeDbSyncStatus({'code': 0, 'msg': '需要配置SSH......', 'progress': 0}) + return 'fail' + + try: + mw.execShell("chmod 600 " + SSH_PRIVATE_KEY) + key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + print(ip, master_port) + + # pkey=key + # key_filename=SSH_PRIVATE_KEY + ssh.connect(hostname=ip, port=int(master_port), + username='root', pkey=key) + except Exception as e: + print(str(e)) + writeDbSyncStatus( + {'code': 0, 'msg': 'SSH配置错误:' + str(e), 'progress': 0}) + return 'fail' + + writeDbSyncStatus({'code': 0, 'msg': '登录Master成功...', 'progress': 5}) + dbname = args['db'] + cmd = "cd /www/server/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py dump_mysql_data {\"db\":'" + dbname + "'}" + print(cmd) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + if result.strip() == 'ok': + writeDbSyncStatus({'code': 1, 'msg': '主服务器备份完成...', 'progress': 30}) + else: + writeDbSyncStatus( + {'code': 1, 'msg': '主服务器备份失败...:' + str(result), 'progress': 100}) + return 'fail' + + print("同步文件", "start") + # cmd = 'scp -P' + str(master_port) + ' -i ' + SSH_PRIVATE_KEY + \ + # ' root@' + ip + ':/tmp/dump.sql.gz /tmp' + t = ssh.get_transport() + sftp = paramiko.SFTPClient.from_transport(t) + copy_status = sftp.get("/tmp/dump.sql.gz", "/tmp/dump.sql.gz") + print("同步信息:", copy_status) + print("同步文件", "end") + if copy_status == None: + writeDbSyncStatus({'code': 2, 'msg': '数据同步本地完成...', 'progress': 40}) + + cmd = 'cd /www/server/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py get_master_rep_slave_user_cmd {"username":"' + db_user + '","db":""}' + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + + if result == '': + writeDbSyncStatus({'code': 1, 'msg': '同步命令获取失败!', 'progress': 100}) + return 'fail' + + cmd_data = json.loads(result) + + db.query('stop slave') + db.query('stop all slaves') + writeDbSyncStatus({'code': 3, 'msg': '停止从库完成...', 'progress': 45}) + + cmd = cmd_data['data']['cmd'] + # 保证同步IP一致 + if cmd.find('SOURCE_HOST') > -1: + cmd = re.sub(r"SOURCE_HOST='(.*)'", "SOURCE_HOST='" + ip + "'", cmd, 1) + + if cmd.find('MASTER_HOST') > -1: + cmd = re.sub(r"MASTER_HOST='(.*)'", "SOURCE_HOST='" + ip + "'", cmd, 1) + + db.query(cmd) + uinfo = cmd_data['data']['info'] + ps = uinfo['username'] + "|" + uinfo['password'] + id_rsa_conn.where('ip=?', (ip,)).setField('ps', ps) + writeDbSyncStatus({'code': 4, 'msg': '刷新从库同步信息完成...', 'progress': 50}) + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + root_dir = getServerDir() + msock = root_dir + "/mysql.sock" + mw.execShell("cd /tmp && gzip -d dump.sql.gz") + cmd = root_dir + "/bin/mysql -S " + msock + \ + " -uroot -p" + pwd + " < /tmp/dump.sql" + import_data = mw.execShell(cmd) + if import_data[0] == '': + print(import_data[1]) + writeDbSyncStatus({'code': 5, 'msg': '导入数据完成...', 'progress': 90}) + else: + print(import_data[0]) + writeDbSyncStatus({'code': 5, 'msg': '导入数据失败...', 'progress': 100}) + return 'fail' + + db.query("start slave") + db.query("start all slaves") + writeDbSyncStatus({'code': 6, 'msg': '从库重启完成...', 'progress': 100}) + + os.system("rm -rf " + SSH_PRIVATE_KEY) + os.system("rm -rf /tmp/dump.sql") + return True + + +def installPreInspection(version): + swap_path = mw.getServerDir() + "/swap" + if not os.path.exists(swap_path): + return "为了稳定安装MariaDB,先安装swap插件!" + return 'ok' + + +def uninstallPreInspection(version): + stop(version) + if mw.isDebugMode(): + return 'ok' + + return "请手动删除MariaDB[{}]
                rm -rf {}".format(version, getServerDir()) + +if __name__ == "__main__": + func = sys.argv[1] + + version = "10.6" + version_pl = getServerDir() + "/version.pl" + if os.path.exists(version_pl): + version = mw.readFile(version_pl).strip() + + if func == 'status': + print(status(version)) + elif func == 'start': + print(start(version)) + elif func == 'stop': + print(stop(version)) + elif func == 'restart': + print(restart(version)) + elif func == 'reload': + print(reload(version)) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection(version)) + elif func == 'run_info': + print(runInfo(version)) + elif func == 'db_status': + print(myDbStatus()) + elif func == 'set_db_status': + print(setDbStatus()) + elif func == 'conf': + print(getConf()) + elif func == 'bin_log': + print(binLog()) + elif func == 'binlog_list': + print(binLogList()) + elif func == 'clean_bin_log': + print(cleanBinLog()) + elif func == 'error_log': + print(getErrorLog()) + elif func == 'show_log': + print(getShowLogFile()) + elif func == 'my_db_pos': + print(getMyDbPos()) + elif func == 'set_db_pos': + print(setMyDbPos()) + elif func == 'my_port': + print(getMyPort()) + elif func == 'set_my_port': + print(setMyPort()) + elif func == 'init_pwd': + print(initMysqlPwd()) + elif func == 'root_pwd': + print(rootPwd()) + elif func == 'get_db_list': + print(getDbList()) + elif func == 'set_db_backup': + print(setDbBackup()) + elif func == 'import_db_backup': + print(importDbBackup()) + elif func == 'import_db_external': + print(importDbExternal()) + elif func == 'import_db_external_progress': + print(importDbExternalProgress()) + elif func == 'import_db_external_progress_bar': + print(importDbExternalProgressBar()) + elif func == 'delete_db_backup': + print(deleteDbBackup()) + elif func == 'get_db_backup_list': + print(getDbBackupList()) + elif func == 'get_db_backup_import_list': + print(getDbBackupImportList()) + elif func == 'add_db': + print(addDb()) + elif func == 'del_db': + print(delDb()) + elif func == 'sync_get_databases': + print(syncGetDatabases()) + elif func == 'sync_to_databases': + print(syncToDatabases()) + elif func == 'set_root_pwd': + print(setRootPwd(version)) + elif func == 'set_user_pwd': + print(setUserPwd(version)) + elif func == 'get_db_access': + print(getDbAccess()) + elif func == 'set_db_access': + print(setDbAccess()) + elif func == 'fix_db_access': + print(fixDbAccess(version)) + elif func == 'get_db_rw': + print(setDbRw(version)) + elif func == 'set_db_ps': + print(setDbPs()) + elif func == 'get_db_info': + print(getDbInfo()) + elif func == 'repair_table': + print(repairTable()) + elif func == 'opt_table': + print(optTable()) + elif func == 'alter_table': + print(alterTable()) + elif func == 'get_total_statistics': + print(getTotalStatistics()) + elif func == 'get_dbrun_mode': + print(getDbrunMode(version)) + elif func == 'set_dbrun_mode': + print(setDbrunMode(version)) + elif func == 'reset_master': + print(resetMaster(version)) + elif func == 'get_masterdb_list': + print(getMasterDbList(version)) + elif func == 'get_master_status': + print(getMasterStatus(version)) + elif func == 'set_master_status': + print(setMasterStatus(version)) + elif func == 'set_db_master': + print(setDbMaster(version)) + elif func == 'set_db_slave': + print(setDbSlave(version)) + elif func == 'set_dbmaster_access': + print(setDbMasterAccess()) + elif func == 'get_master_rep_slave_list': + print(getMasterRepSlaveList(version)) + elif func == 'add_master_rep_slave_user': + print(addMasterRepSlaveUser(version)) + elif func == 'del_master_rep_slave_user': + print(delMasterRepSlaveUser(version)) + elif func == 'update_master_rep_slave_user': + print(updateMasterRepSlaveUser(version)) + elif func == 'get_master_rep_slave_user_cmd_ssh': + print(getMasterRepSlaveUserCmdSsh(version)) + elif func == 'get_master_rep_slave_user_cmd': + print(getMasterRepSlaveUserCmd(version)) + elif func == 'get_slave_list': + print(getSlaveList(version)) + elif func == 'try_slave_sync_bugfix': + print(trySlaveSyncBugfix(version)) + elif func == 'get_slave_sync_cmd': + print(getSlaveSyncCmd(version)) + elif func == 'get_slave_ssh_list': + print(getSlaveSSHList(version)) + elif func == 'get_slave_ssh_by_ip': + print(getSlaveSSHByIp(version)) + elif func == 'add_slave_ssh': + print(addSlaveSSH(version)) + elif func == 'del_slave_ssh': + print(delSlaveSSH(version)) + elif func == 'update_slave_ssh': + print(updateSlaveSSH(version)) + elif func == 'get_slave_sync_user_list': + print(getSlaveSyncUserList(version)) + elif func == 'get_slave_sync_user_by_ip': + print(getSlaveSyncUserByIp(version)) + elif func == 'add_slave_sync_user': + print(addSlaveSyncUser(version)) + elif func == 'del_slave_sync_user': + print(delSlaveSyncUser(version)) + elif func == 'get_slave_sync_mode': + print(getSlaveSyncMode(version)) + elif func == 'set_slave_sync_mode': + print(setSlaveSyncMode(version)) + elif func == 'init_slave_status': + print(initSlaveStatus(version)) + elif func == 'set_slave_status': + print(setSlaveStatus(version)) + elif func == 'delete_slave': + print(deleteSlave(version)) + elif func == 'full_sync': + print(fullSync(version)) + elif func == 'do_full_sync': + print(doFullSync(version)) + elif func == 'full_sync_cmd': + print(fullSyncCmd()) + elif func == 'dump_mysql_data': + print(dumpMysqlData(version)) + elif func == 'sync_database_repair': + print(syncDatabaseRepair()) + elif func == 'sync_database_repair_log': + print(syncDatabaseRepairLog()) + else: + print('error') diff --git a/plugins/mariadb/index_mariadb.py b/plugins/mariadb/index_mariadb.py new file mode 100644 index 000000000..644f32576 --- /dev/null +++ b/plugins/mariadb/index_mariadb.py @@ -0,0 +1,190 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +if mw.isAppleSystem(): + cmd = 'ls /usr/local/lib/ | grep python | cut -d \\ -f 1 | awk \'END {print}\'' + info = mw.execShell(cmd) + p = "/usr/local/lib/" + info[0].strip() + "/site-packages" + sys.path.append(p) + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'mariadb' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getSPluginDir(): + return '/www/server/mdserver-web/plugins/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConf(): + path = getServerDir() + '/etc/my.cnf' + return path + + +def getDataDir(): + file = getConf() + content = mw.readFile(file) + rep = 'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getRelayLogName(): + file = getConf() + content = mw.readFile(file) + rep = 'relay-log\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getLogBinName(): + file = getConf() + content = mw.readFile(file) + rep = 'log-bin\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def binLogListLook(args): + + file = args['file'] + line = args['line'] + + data_dir = getDataDir() + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata = {} + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata + + +def binLogListLookDecode(args): + + file = args['file'] + line = args['line'] + + data_dir = getDataDir() + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults --base64-output=decode-rows -vvvv ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata = {} + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata + + +def binLogListTraceRelay(args): + rdata = {} + file = args['file'] + line = args['line'] + + relay_name = getRelayLogName() + data_dir = getDataDir() + alist = os.listdir(data_dir) + relay_list = [] + for x in range(len(alist)): + f = alist[x] + t = {} + if f.startswith(relay_name) and not f.endswith('.index'): + relay_list.append(f) + + relay_list = sorted(relay_list, reverse=True) + if len(relay_list) == 0: + rdata['cmd'] = '' + rdata['data'] = '无Relay日志' + return rdata + + file = relay_list[0] + + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults --base64-output=decode-rows -vvvv ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata + + +def binLogListTraceBinLog(args): + rdata = {} + file = args['file'] + line = args['line'] + + data_dir = getDataDir() + log_bin_name = getLogBinName() + + alist = os.listdir(data_dir) + log_bin_l = [] + for x in range(len(alist)): + f = alist[x] + t = {} + if f.startswith(log_bin_name) and not f.endswith('.index'): + log_bin_l.append(f) + + if len(log_bin_l) == 0: + rdata['cmd'] = '' + rdata['data'] = '无BINLOG' + return rdata + + log_bin_l = sorted(log_bin_l, reverse=True) + file = log_bin_l[0] + + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults --base64-output=decode-rows -vvvv ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata diff --git a/plugins/mariadb/info.json b/plugins/mariadb/info.json new file mode 100755 index 000000000..6951e77a8 --- /dev/null +++ b/plugins/mariadb/info.json @@ -0,0 +1,20 @@ +{ + "sort":2, + "hook":["database"], + "title":"MariaDB", + "tip":"soft", + "name":"mariadb", + "type":"运行环境", + "ps":"一种关系数据库管理系统!", + "uninstall_pre_inspection":true, + "checks": "server/mariadb", + "path": "server/mariadb", + "versions":["10.6","10.7","10.8","10.9","10.11","11.0","11.1","11.2","11.3","11.4","11.5","11.6","11.7","11.8","12.0"], + "shell":"install.sh", + "checks":"server/mariadb", + "path":"server/mariadb", + "author":"mariadb", + "home":"https://mariadb.org/", + "date":"2022-07-12", + "pid": "2" +} \ No newline at end of file diff --git a/plugins/mariadb/init.d/mariadb.service.tpl b/plugins/mariadb/init.d/mariadb.service.tpl new file mode 100644 index 000000000..c27e9596c --- /dev/null +++ b/plugins/mariadb/init.d/mariadb.service.tpl @@ -0,0 +1,22 @@ +[Unit] +Description=MariaDB Server: The open source relational database +Documentation=https://mariadb.org/download/?t=mariadb +After=network.service +After=syslog.target + +[Service] +User=mysql +Group=mysql +Type=simple +ExecStart={$SERVER_PATH}/mariadb/bin/mysqld --defaults-file={$SERVER_PATH}/mariadb/etc/my.cnf +ExecReload=/bin/kill -USR2 $MAINPID +PermissionsStartOnly=true +LimitNOFILE=5000 +Restart=on-failure +RestartSec=10 +RestartPreventExitStatus=1 +PrivateTmp=false + + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/mariadb/init.d/mariadb.tpl b/plugins/mariadb/init.d/mariadb.tpl new file mode 100644 index 000000000..6a0a54174 --- /dev/null +++ b/plugins/mariadb/init.d/mariadb.tpl @@ -0,0 +1,384 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# Description: mysql service +# distro. For CentOS/Redhat run: 'chkconfig --add mysql' + +# Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB +# This file is public domain and comes with NO WARRANTY of any kind + +# MySQL daemon start/stop script. + +# Usually this is put in /etc/init.d (at least on machines SYSV R4 based +# systems) and linked to /etc/rc3.d/S99mysql and /etc/rc0.d/K01mysql. +# When this is done the mysql server will be started when the machine is +# started and shut down when the systems goes down. + +# Comments to support chkconfig on RedHat Linux +# chkconfig: 2345 64 36 +# description: A very fast and reliable SQL database engine. + +# Comments to support LSB init script conventions +### BEGIN INIT INFO +# Provides: mysql +# Required-Start: $local_fs $network $remote_fs +# Should-Start: ypbind nscd ldap ntpd xntpd +# Required-Stop: $local_fs $network $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop MySQL +# Description: MySQL is a very fast and reliable SQL database engine. +### END INIT INFO + +# If you install MySQL on some other places than /www/server/mysql, then you +# have to do one of the following things for this script to work: +# +# - Run this script from within the MySQL installation directory +# - Create a /etc/my.cnf file with the following information: +# [mysqld] +# basedir= +# - Add the above to any other configuration file (for example ~/.my.ini) +# and copy my_print_defaults to /usr/bin +# - Add the path to the mysql-installation-directory to the basedir variable +# below. +# +# If you want to affect other MySQL variables, you should make your changes +# in the /etc/my.cnf, ~/.my.cnf or other MySQL configuration files. + +# If you change base dir, you must also change datadir. These may get +# overwritten by settings in the MySQL configuration files. + +basedir= +datadir= + +# Default value, in seconds, afterwhich the script should timeout waiting +# for server start. +# Value here is overriden by value in my.cnf. +# 0 means don't wait at all +# Negative numbers mean to wait indefinitely +service_startup_timeout=900 + +# Lock directory for RedHat / SuSE. +lockdir='/var/lock/subsys' +lock_file_path="$lockdir/mysql" + +# The following variables are only set for letting mysql.server find things. + +# Set some defaults +mysqld_pid_file_path= +if test -z "$basedir" +then + basedir={$SERVER_APP_PATH} + bindir={$SERVER_APP_PATH}/bin + if test -z "$datadir" + then + datadir={$SERVER_APP_PATH}/data + fi + sbindir={$SERVER_APP_PATH}/bin + libexecdir={$SERVER_APP_PATH}/bin +else + bindir="$basedir/bin" + if test -z "$datadir" + then + datadir="$basedir/data" + fi + sbindir="$basedir/sbin" + libexecdir="$basedir/libexec" +fi + +# datadir_set is used to determine if datadir was set (and so should be +# *not* set inside of the --basedir= handler.) +datadir_set= + +# +# Use LSB init script functions for printing messages, if possible +# +lsb_functions="/lib/lsb/init-functions" +if test -f $lsb_functions ; then + . $lsb_functions +else + log_success_msg() + { + echo " SUCCESS! $@" + } + log_failure_msg() + { + echo " ERROR! $@" + } +fi + +PATH="/sbin:/usr/sbin:/bin:/usr/bin:$basedir/bin" +export PATH + +mode=$1 # start or stop + +[ $# -ge 1 ] && shift + + +other_args=--sql-mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" # uncommon, but needed when called from an RPM upgrade action + # Expected: "--skip-networking --skip-grant-tables" + # They are not checked here, intentionally, as it is the resposibility + # of the "spec" file author to give correct arguments only. + +case `echo "testing\c"`,`echo -n testing` in + *c*,-n*) echo_n= echo_c= ;; + *c*,*) echo_n=-n echo_c= ;; + *) echo_n= echo_c='\c' ;; +esac + +parse_server_arguments() { + for arg do + case "$arg" in + --basedir=*) basedir=`echo "$arg" | sed -e 's/^[^=]*=//'` + bindir="$basedir/bin" + if test -z "$datadir_set"; then + datadir="$basedir/data" + fi + sbindir="$basedir/sbin" + libexecdir="$basedir/libexec" + ;; + --datadir=*) datadir=`echo "$arg" | sed -e 's/^[^=]*=//'` + datadir_set=1 + ;; + --pid-file=*) mysqld_pid_file_path=`echo "$arg" | sed -e 's/^[^=]*=//'` ;; + --service-startup-timeout=*) service_startup_timeout=`echo "$arg" | sed -e 's/^[^=]*=//'` ;; + esac + done +} + +wait_for_pid () { + verb="$1" # created | removed + pid="$2" # process ID of the program operating on the pid-file + pid_file_path="$3" # path to the PID file. + + i=0 + avoid_race_condition="by checking again" + + while test $i -ne $service_startup_timeout ; do + + case "$verb" in + 'created') + # wait for a PID-file to pop into existence. + test -s "$pid_file_path" && i='' && break + ;; + 'removed') + # wait for this PID-file to disappear + test ! -s "$pid_file_path" && i='' && break + ;; + *) + echo "wait_for_pid () usage: wait_for_pid created|removed pid pid_file_path" + exit 1 + ;; + esac + + # if server isn't running, then pid-file will never be updated + if test -n "$pid"; then + if kill -0 "$pid" 2>/dev/null; then + : # the server still runs + else + # The server may have exited between the last pid-file check and now. + if test -n "$avoid_race_condition"; then + avoid_race_condition="" + continue # Check again. + fi + + # there's nothing that will affect the file. + log_failure_msg "The server quit without updating PID file ($pid_file_path)." + return 1 # not waiting any more. + fi + fi + + echo $echo_n ".$echo_c" + i=`expr $i + 1` + sleep 1 + + done + + if test -z "$i" ; then + log_success_msg + return 0 + else + log_failure_msg + return 1 + fi +} + +# Get arguments from the my.cnf file, +# the only group, which is read from now on is [mysqld] +if test -x "$bindir/my_print_defaults"; then + print_defaults="$bindir/my_print_defaults" +else + # Try to find basedir in /etc/my.cnf + conf=/etc/my.cnf + print_defaults= + if test -r $conf + then + subpat='^[^=]*basedir[^=]*=\(.*\)$' + dirs=`sed -e "/$subpat/!d" -e 's//\1/' $conf` + for d in $dirs + do + d=`echo $d | sed -e 's/[ ]//g'` + if test -x "$d/bin/my_print_defaults" + then + print_defaults="$d/bin/my_print_defaults" + break + fi + done + fi + + # Hope it's in the PATH ... but I doubt it + test -z "$print_defaults" && print_defaults="my_print_defaults" +fi + +# +# Read defaults file from 'basedir'. If there is no defaults file there +# check if it's in the old (depricated) place (datadir) and read it from there +# + +extra_args="" +if test -r "$basedir/my.cnf" +then + extra_args="-e $basedir/my.cnf" +else + if test -r "$datadir/my.cnf" + then + extra_args="-e $datadir/my.cnf" + fi +fi + +parse_server_arguments `$print_defaults $extra_args mysqld server mysql_server mysql.server` + +# +# Set pid file if not given +# +found_pid=`cd $datadir && ls |grep '.pid'` +if test -z "$mysqld_pid_file_path" +then + mysqld_pid_file_path=$datadir/$found_pid +else + case "$mysqld_pid_file_path" in + /* ) ;; + * ) mysqld_pid_file_path="$datadir/$mysqld_pid_file_path" ;; + esac +fi + +#ulimit -s unlimited +case "$mode" in + 'start') + # Start daemon + + # Safeguard (relative paths, core dumps..) + cd $basedir + + echo $echo_n "Starting MySQL" + if test -x $bindir/mysqld_safe + then + # Give extra arguments to mysqld with the my.cnf file. This script + # may be overwritten at next upgrade. + $bindir/mysqld_safe --defaults-file=$basedir/etc/my.cnf --datadir="$datadir" $other_args >/dev/null & + wait_for_pid created "$!" "$mysqld_pid_file_path"; return_value=$? + + # Make lock for RedHat / SuSE + if test -w "$lockdir" + then + touch "$lock_file_path" + fi + + exit $return_value + else + log_failure_msg "Couldn't find MySQL server ($bindir/mysqld_safe)" + fi + ;; + + 'stop') + # Stop daemon. We use a signal here to avoid having to know the + # root password. + + if test -s "$mysqld_pid_file_path" + then + mysqld_pid=`cat "$mysqld_pid_file_path"` + + if (kill -0 $mysqld_pid 2>/dev/null) + then + echo $echo_n "Shutting down MySQL" + kill $mysqld_pid + # mysqld should remove the pid file when it exits, so wait for it. + wait_for_pid removed "$mysqld_pid" "$mysqld_pid_file_path"; return_value=$? + else + log_failure_msg "MySQL server process #$mysqld_pid is not running!" + rm "$mysqld_pid_file_path" + fi + + # Delete lock for RedHat / SuSE + if test -f "$lock_file_path" + then + rm -f "$lock_file_path" + fi + exit $return_value + else + log_failure_msg "MySQL server PID file could not be found!" + fi + ;; + + 'restart') + # Stop the service and regardless of whether it was + # running or not, start it again. + if $0 stop $other_args; then + $0 start $other_args + else + log_failure_msg "Failed to stop running server, so refusing to try to start." + exit 1 + fi + ;; + + 'reload'|'force-reload') + if test -s "$mysqld_pid_file_path" ; then + read mysqld_pid < "$mysqld_pid_file_path" + kill -HUP $mysqld_pid && log_success_msg "Reloading service MySQL" + touch "$mysqld_pid_file_path" + else + log_failure_msg "MySQL PID file could not be found!" + exit 1 + fi + ;; + 'status') + # First, check to see if pid file exists + if test -s "$mysqld_pid_file_path" ; then + read mysqld_pid < "$mysqld_pid_file_path" + if kill -0 $mysqld_pid 2>/dev/null ; then + log_success_msg "MySQL running ($mysqld_pid)" + exit 0 + else + log_failure_msg "MySQL is not running, but PID file exists" + exit 1 + fi + else + # Try to find appropriate mysqld process + mysqld_pid=`pidof $libexecdir/mysqld` + + # test if multiple pids exist + pid_count=`echo $mysqld_pid | wc -w` + if test $pid_count -gt 1 ; then + log_failure_msg "Multiple MySQL running but PID file could not be found ($mysqld_pid)" + exit 5 + elif test -z $mysqld_pid ; then + if test -f "$lock_file_path" ; then + log_failure_msg "MySQL is not running, but lock file ($lock_file_path) exists" + exit 2 + fi + log_failure_msg "MySQL is not running" + exit 3 + else + log_failure_msg "MySQL is running but PID file could not be found" + exit 4 + fi + fi + ;; + *) + # usage + basename=`basename "$0"` + echo "Usage: $basename {start|stop|restart|reload|force-reload|status} [ MySQL server options ]" + exit 1 + ;; +esac + +exit 0 diff --git a/plugins/mariadb/install.sh b/plugins/mariadb/install.sh new file mode 100755 index 000000000..ff3f0ac0f --- /dev/null +++ b/plugins/mariadb/install.sh @@ -0,0 +1,56 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/mariadb && bash install.sh install 8.2 +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py try_slave_sync_bugfix {} +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py do_full_sync {"db":"xxx","sign":"","begin":1} +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py sync_database_repair {"db":"xxx","sign":""} +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py init_slave_status +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mariadb/index.py install_pre_inspection + + +action=$1 +type=$2 + +if id mysql &> /dev/null ;then + echo "mysql UID is `id -u mysql`" + echo "mysql Shell is `grep "^mysql:" /etc/passwd |cut -d':' -f7 `" +else + groupadd mysql + useradd -g mysql -s /usr/sbin/nologin mysql +fi + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + +if [ "${action}" == "uninstall" ];then + + if [ -f /usr/lib/systemd/system/mariadb.service ] || [ -f /lib/systemd/system/mariadb.service ];then + systemctl stop mariadb + systemctl disable mariadb + rm -rf /usr/lib/systemd/system/mariadb.service + rm -rf /lib/systemd/system/mariadb.service + systemctl daemon-reload + fi +fi + +sh -x $curPath/versions/$2/install.sh $1 + +if [ "${action}" == "install" ] && [ -d $serverPath/mariadb ];then + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/mariadb/index.py start ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/mariadb/index.py initd_install ${type} +fi diff --git a/plugins/mariadb/js/mariadb.js b/plugins/mariadb/js/mariadb.js new file mode 100755 index 000000000..01bdc1fe5 --- /dev/null +++ b/plugins/mariadb/js/mariadb.js @@ -0,0 +1,2907 @@ +function myPost(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'mariadb', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myPostN(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + $.post('/plugins/run', {name:'mariadb', func:method, args:_args}, function(data) { + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + return syncPost('/plugins/run', {name:'mysql', func:method, args:_args}); +} + +function myPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'mariadb'; + req_data['func'] = method; + req_data['script']='index_mariadb'; + args['version'] = version; + + + if (typeof(args) == 'string' && args == ''){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myPostCallbakN(method, version, args,callback){ + + var req_data = {}; + req_data['name'] = 'mariadb'; + req_data['func'] = method; + req_data['script']='index_mariadb'; + args['version'] = version; + + + if (typeof(args) == 'string' && args == ''){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function runInfo(){ + myPost('run_info','',function(data){ + + var rdata = $.parseJSON(data.data); + if (typeof(rdata['status']) != 'undefined'){ + layer.msg(rdata['msg'],{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var cache_size = ((parseInt(rdata.Qcache_hits) / (parseInt(rdata.Qcache_hits) + parseInt(rdata.Qcache_inserts))) * 100).toFixed(2) + '%'; + if (cache_size == 'NaN%') cache_size = 'OFF'; + var Con = '
                \ + \ + \ + \ + \ + \ + \ +
                启动时间' + getLocalTime(rdata.Run) + '每秒查询' + parseInt(rdata.Questions / rdata.Uptime) + '
                总连接次数' + rdata.Connections + '每秒事务' + parseInt((parseInt(rdata.Com_commit) + parseInt(rdata.Com_rollback)) / rdata.Uptime) + '
                发送' + toSize(rdata.Bytes_sent) + 'File' + rdata.File + '
                接收' + toSize(rdata.Bytes_received) + 'Position' + rdata.Position + '
                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                活动/峰值连接数' + rdata.Threads_running + '/' + rdata.Max_used_connections + '若值过大,增加max_connections
                线程缓存命中率' + ((1 - rdata.Threads_created / rdata.Connections) * 100).toFixed(2) + '%若过低,增加thread_cache_size
                索引命中率' + ((1 - rdata.Key_reads / rdata.Key_read_requests) * 100).toFixed(2) + '%若过低,增加key_buffer_size
                Innodb索引命中率' + (rdata.Innodb_buffer_pool_read_requests / (rdata.Innodb_buffer_pool_read_requests+rdata.Innodb_buffer_pool_reads)).toFixed(2) + '%若过低,增加innodb_buffer_pool_size
                查询缓存命中率' + cache_size + '' + lan.soft.mysql_status_ps5 + '
                创建临时表到磁盘' + ((rdata.Created_tmp_disk_tables / rdata.Created_tmp_tables) * 100).toFixed(2) + '%若过大,尝试增加tmp_table_size
                已打开的表' + rdata.Open_tables + '若过大,增加table_cache_size
                没有使用索引的量' + rdata.Select_full_join + '若不为0,请检查数据表的索引是否合理
                没有索引的JOIN量' + rdata.Select_range_check + '若不为0,请检查数据表的索引是否合理
                排序后的合并次数' + rdata.Sort_merge_passes + '若值过大,增加sort_buffer_size
                锁表次数' + rdata.Table_locks_waited + '若值过大,请考虑增加您的数据库性能
                '; + $(".soft-man-con").html(Con); + }); +} + + +function myDbPos(){ + myPost('my_db_pos','',function(data){ + var con = '
                \ +
                \ + \ + \ + \ +
                '; + $(".soft-man-con").html(con); + + $('#btn_change_path').click(function(){ + var datadir = $("input[name='datadir']").val(); + myPost('set_db_pos','datadir='+datadir,function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:rdata.status ? 1 : 5,time:2000,shade: [0.3, '#000']}); + }); + }); + }); +} + +function myPort(){ + myPost('my_port','',function(data){ + var con = '
                \ +
                \ + \ + \ +
                '; + $(".soft-man-con").html(con); + + $('#btn_change_port').click(function(){ + var port = $("input[name='port']").val(); + myPost('set_my_port','port='+port,function(data){ + var rdata = $.parseJSON(data.data); + if (rdata.status){ + layer.msg('修改成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + }); +} + + +//数据库存储信置 +function changeMySQLDataPath(act) { + if (act != undefined) { + layer.confirm(lan.soft.mysql_to_msg, { closeBtn: 2, icon: 3 }, function() { + var datadir = $("#datadir").val(); + var data = 'datadir=' + datadir; + var loadT = layer.msg(lan.soft.mysql_to_msg1, { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/database?action=SetDataDir', data, function(rdata) { + layer.close(loadT) + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + }); + }); + return; + } + + $.post('/database?action=GetMySQLInfo', '', function(rdata) { + var LimitCon = '

                \ + \ + \ +

                '; + $(".soft-man-con").html(LimitCon); + }); +} + + + + +//数据库配置状态 +function myPerfOpt() { + //获取MySQL配置 + myPost('db_status','',function(data){ + var rdata = $.parseJSON(data.data); + if ( typeof(rdata.status) != 'undefined' && !rdata.status){ + layer.msg(rdata.msg, {icon:2}); + return; + } + + // console.log(rdata); + var key_buffer_size = toSizeM(rdata.mem.key_buffer_size); + var query_cache_size = toSizeM(rdata.mem.query_cache_size); + var tmp_table_size = toSizeM(rdata.mem.tmp_table_size); + var innodb_buffer_pool_size = toSizeM(rdata.mem.innodb_buffer_pool_size); + var innodb_additional_mem_pool_size = toSizeM(rdata.mem.innodb_additional_mem_pool_size); + var innodb_log_buffer_size = toSizeM(rdata.mem.innodb_log_buffer_size); + + var sort_buffer_size = toSizeM(rdata.mem.sort_buffer_size); + var read_buffer_size = toSizeM(rdata.mem.read_buffer_size); + var read_rnd_buffer_size = toSizeM(rdata.mem.read_rnd_buffer_size); + var join_buffer_size = toSizeM(rdata.mem.join_buffer_size); + var thread_stack = toSizeM(rdata.mem.thread_stack); + var binlog_cache_size = toSizeM(rdata.mem.binlog_cache_size); + + var a = key_buffer_size + query_cache_size + tmp_table_size + innodb_buffer_pool_size + innodb_additional_mem_pool_size + innodb_log_buffer_size; + var b = sort_buffer_size + read_buffer_size + read_rnd_buffer_size + join_buffer_size + thread_stack + binlog_cache_size; + var memSize = a + rdata.mem.max_connections * b; + + + var memCon = '
                \ +
                最大使用内存: \ + \ + ' + lan.soft.mysql_set_maxmem + ': MB\ +
                \ +

                key_buffer_sizeMB, ' + lan.soft.mysql_set_key_buffer_size + '

                \ +

                query_cache_sizeMB, ' + lan.soft.mysql_set_query_cache_size + '

                \ +

                tmp_table_sizeMB, ' + lan.soft.mysql_set_tmp_table_size + '

                \ +

                innodb_buffer_pool_sizeMB, ' + lan.soft.mysql_set_innodb_buffer_pool_size + '

                \ +

                innodb_log_buffer_sizeMB, ' + lan.soft.mysql_set_innodb_log_buffer_size + '

                \ +

                innodb_additional_mem_pool_sizeMB

                \ +

                sort_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_sort_buffer_size + '

                \ +

                read_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_read_buffer_size + '

                \ +

                read_rnd_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_read_rnd_buffer_size + '

                \ +

                join_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_join_buffer_size + '

                \ +

                thread_stackKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_thread_stack + '

                \ +

                binlog_cache_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_binlog_cache_size + '

                \ +

                thread_cache_size ' + lan.soft.mysql_set_thread_cache_size + '

                \ +

                table_open_cache ' + lan.soft.mysql_set_table_open_cache + '

                \ +

                max_connections ' + lan.soft.mysql_set_max_connections + '

                \ +
                \ +
                ' + + $(".soft-man-con").html(memCon); + + $(".conf_p input[name*='size'],.conf_p input[name='max_connections'],.conf_p input[name='thread_stack']").change(function() { + comMySqlMem(); + }); + + $(".conf_p select[name='mysql_set']").change(function() { + mySQLMemOpt($(this).val()); + comMySqlMem(); + }); + }); +} + +function reBootMySqld(){ + pluginOpService('mysql','restart',''); +} + + +//设置MySQL配置参数 +function setMySQLConf() { + $.post('/system/system_total', '', function(memInfo) { + var memSize = memInfo['memTotal']; + var setSize = parseInt($("input[name='memSize']").val()); + + if(memSize < setSize){ + var errMsg = "错误,内存分配过高!

                物理内存: {1}MB
                最大使用内存: {2}MB
                可能造成的后果: 导致数据库不稳定,甚至无法启动MySQLd服务!"; + var msg = errMsg.replace('{1}',memSize).replace('{2}',setSize); + layer.msg(msg,{icon:2,time:5000}); + return; + } + + var query_cache_size = parseInt($("input[name='query_cache_size']").val()); + var query_cache_type = 0; + if (query_cache_size > 0) { + query_cache_type = 1; + } + var data = { + key_buffer_size: parseInt($("input[name='key_buffer_size']").val()), + query_cache_size: query_cache_size, + query_cache_type: query_cache_type, + tmp_table_size: parseInt($("input[name='tmp_table_size']").val()), + max_heap_table_size: parseInt($("input[name='tmp_table_size']").val()), + innodb_buffer_pool_size: parseInt($("input[name='innodb_buffer_pool_size']").val()), + innodb_log_buffer_size: parseInt($("input[name='innodb_log_buffer_size']").val()), + sort_buffer_size: parseInt($("input[name='sort_buffer_size']").val()), + read_buffer_size: parseInt($("input[name='read_buffer_size']").val()), + read_rnd_buffer_size: parseInt($("input[name='read_rnd_buffer_size']").val()), + join_buffer_size: parseInt($("input[name='join_buffer_size']").val()), + thread_stack: parseInt($("input[name='thread_stack']").val()), + binlog_cache_size: parseInt($("input[name='binlog_cache_size']").val()), + thread_cache_size: parseInt($("input[name='thread_cache_size']").val()), + table_open_cache: parseInt($("input[name='table_open_cache']").val()), + max_connections: parseInt($("input[name='max_connections']").val()) + }; + + myPost('set_db_status', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + reBootMySqld(); + },{ icon: rdata.status ? 1 : 2 }); + }); + },'json'); +} + + +//MySQL内存优化方案 +function mySQLMemOpt(opt) { + var query_size = parseInt($("input[name='query_cache_size']").val()); + switch (opt) { + case '0': + $("input[name='key_buffer_size']").val(8); + if (query_size) $("input[name='query_cache_size']").val(4); + $("input[name='tmp_table_size']").val(8); + $("input[name='innodb_buffer_pool_size']").val(16); + $("input[name='sort_buffer_size']").val(256); + $("input[name='read_buffer_size']").val(256); + $("input[name='read_rnd_buffer_size']").val(128); + $("input[name='join_buffer_size']").val(128); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(32); + $("input[name='thread_cache_size']").val(4); + $("input[name='table_open_cache']").val(32); + $("input[name='max_connections']").val(500); + break; + case '1': + $("input[name='key_buffer_size']").val(128); + if (query_size) $("input[name='query_cache_size']").val(64); + $("input[name='tmp_table_size']").val(64); + $("input[name='innodb_buffer_pool_size']").val(256); + $("input[name='sort_buffer_size']").val(768); + $("input[name='read_buffer_size']").val(768); + $("input[name='read_rnd_buffer_size']").val(512); + $("input[name='join_buffer_size']").val(1024); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(64); + $("input[name='thread_cache_size']").val(64); + $("input[name='table_open_cache']").val(128); + $("input[name='max_connections']").val(100); + break; + case '2': + $("input[name='key_buffer_size']").val(256); + if (query_size) $("input[name='query_cache_size']").val(128); + $("input[name='tmp_table_size']").val(384); + $("input[name='innodb_buffer_pool_size']").val(384); + $("input[name='sort_buffer_size']").val(768); + $("input[name='read_buffer_size']").val(768); + $("input[name='read_rnd_buffer_size']").val(512); + $("input[name='join_buffer_size']").val(2048); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(64); + $("input[name='thread_cache_size']").val(96); + $("input[name='table_open_cache']").val(192); + $("input[name='max_connections']").val(200); + break; + case '3': + $("input[name='key_buffer_size']").val(384); + if (query_size) $("input[name='query_cache_size']").val(192); + $("input[name='tmp_table_size']").val(512); + $("input[name='innodb_buffer_pool_size']").val(512); + $("input[name='sort_buffer_size']").val(1024); + $("input[name='read_buffer_size']").val(1024); + $("input[name='read_rnd_buffer_size']").val(768); + $("input[name='join_buffer_size']").val(2048); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(128); + $("input[name='thread_cache_size']").val(128); + $("input[name='table_open_cache']").val(384); + $("input[name='max_connections']").val(300); + break; + case '4': + $("input[name='key_buffer_size']").val(512); + if (query_size) $("input[name='query_cache_size']").val(256); + $("input[name='tmp_table_size']").val(1024); + $("input[name='innodb_buffer_pool_size']").val(1024); + $("input[name='sort_buffer_size']").val(2048); + $("input[name='read_buffer_size']").val(2048); + $("input[name='read_rnd_buffer_size']").val(1024); + $("input[name='join_buffer_size']").val(4096); + $("input[name='thread_stack']").val(384); + $("input[name='binlog_cache_size']").val(192); + $("input[name='thread_cache_size']").val(192); + $("input[name='table_open_cache']").val(1024); + $("input[name='max_connections']").val(400); + break; + case '5': + $("input[name='key_buffer_size']").val(1024); + if (query_size) $("input[name='query_cache_size']").val(384); + $("input[name='tmp_table_size']").val(2048); + $("input[name='innodb_buffer_pool_size']").val(4096); + $("input[name='sort_buffer_size']").val(4096); + $("input[name='read_buffer_size']").val(4096); + $("input[name='read_rnd_buffer_size']").val(2048); + $("input[name='join_buffer_size']").val(8192); + $("input[name='thread_stack']").val(512); + $("input[name='binlog_cache_size']").val(256); + $("input[name='thread_cache_size']").val(256); + $("input[name='table_open_cache']").val(2048); + $("input[name='max_connections']").val(500); + break; + } +} + +//计算MySQL内存开销 +function comMySqlMem() { + var key_buffer_size = parseInt($("input[name='key_buffer_size']").val()); + var query_cache_size = parseInt($("input[name='query_cache_size']").val()); + var tmp_table_size = parseInt($("input[name='tmp_table_size']").val()); + var innodb_buffer_pool_size = parseInt($("input[name='innodb_buffer_pool_size']").val()); + var innodb_additional_mem_pool_size = parseInt($("input[name='innodb_additional_mem_pool_size']").val()); + var innodb_log_buffer_size = parseInt($("input[name='innodb_log_buffer_size']").val()); + + var sort_buffer_size = $("input[name='sort_buffer_size']").val() / 1024; + var read_buffer_size = $("input[name='read_buffer_size']").val() / 1024; + var read_rnd_buffer_size = $("input[name='read_rnd_buffer_size']").val() / 1024; + var join_buffer_size = $("input[name='join_buffer_size']").val() / 1024; + var thread_stack = $("input[name='thread_stack']").val() / 1024; + var binlog_cache_size = $("input[name='binlog_cache_size']").val() / 1024; + var max_connections = $("input[name='max_connections']").val(); + + var a = key_buffer_size + query_cache_size + tmp_table_size + innodb_buffer_pool_size + innodb_additional_mem_pool_size + innodb_log_buffer_size + var b = sort_buffer_size + read_buffer_size + read_rnd_buffer_size + join_buffer_size + thread_stack + binlog_cache_size + var memSize = a + max_connections * b + $("input[name='memSize']").val(memSize.toFixed(2)); +} + +function syncGetDatabase(){ + myPost('sync_get_databases', null, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function syncToDatabase(type){ + var data = []; + $('input[type="checkbox"].check:checked').each(function () { + if (!isNaN($(this).val())) data.push($(this).val()); + }); + var postData = 'type='+type+'&ids='+JSON.stringify(data); + myPost('sync_to_databases', postData, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function setRootPwd(type, pwd){ + if (type==1){ + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + btn:["提交", "关闭", "复制ROOT密码", "强制修改"], + shadeClose: true, + content: "

                \ +
                \ + root密码\ +
                \ + \ +
                \ +
                \ +
                ", + yes:function(layerIndex){ + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layerIndex); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + }, + btn3:function(){ + var password = $("#MyPassword").val(); + copyText(password); + return false; + }, + btn4:function(layerIndex){ + layer.confirm('强制修改,是为了在重建时使用,确定强制?', { + btn: ['确定', '取消'] + }, function(index, layero){ + layer.close(index); + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password,force:'1'}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layerIndex); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + }); + return false; + } + }); +} + +function showHidePass(obj){ + var a = "glyphicon-eye-open"; + var b = "glyphicon-eye-close"; + + if($(obj).hasClass(a)){ + $(obj).removeClass(a).addClass(b); + $(obj).prev().text($(obj).prev().attr('data-pw')) + } + else{ + $(obj).removeClass(b).addClass(a); + $(obj).prev().text('***'); + } +} + +function checkSelect(){ + setTimeout(function () { + var num = $('input[type="checkbox"].check:checked').length; + if (num == 1) { + $('button[batch="true"]').hide(); + $('button[batch="false"]').show(); + }else if (num>1){ + $('button[batch="true"]').show(); + $('button[batch="false"]').show(); + }else{ + $('button[batch="true"]').hide(); + $('button[batch="false"]').hide(); + } + },5) +} + +function setDbRw(id,username,val){ + myPost('get_db_rw',{id:id,username:username,rw:val}, function(data){ + var rdata = $.parseJSON(data.data); + // layer.msg(rdata.msg,{icon:rdata.status ? 1 : 5,shade: [0.3, '#000']}); + showMsg(rdata.msg, function(){ + dbList(); + },{icon:rdata.status ? 1 : 5,shade: [0.3, '#000']}, 2000); + + }); +} + +function setDbAccess(username){ + myPost('get_db_access','username='+username, function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,shade: [0.3, '#000']}); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '设置数据库权限', + closeBtn: 1, + shift: 5, + btn:["提交","取消"], + shadeClose: true, + content: "
                \ +
                \ + 访问权限\ +
                \ + \ +
                \ +
                \ +
                ", + success:function(){ + if (rdata.msg == '127.0.0.1'){ + $('select[name="dataAccess"]').find("option[value='127.0.0.1']").attr("selected",true); + } else if (rdata.msg == '%'){ + $('select[name="dataAccess"]').find('option[value="%"]').attr("selected",true); + } else if ( rdata.msg == 'ip' ){ + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } else { + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#set_db_access").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['access']){ + dataObj['access'] = dataObj['dataAccess']; + if ( dataObj['dataAccess'] == 'ip'){ + if (dataObj['address']==''){ + layer.msg('IP地址不能空!',{icon:2,shade: [0.3, '#000']}); + return; + } + dataObj['access'] = dataObj['address']; + } + } + dataObj['username'] = username; + myPost('set_db_access', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); + + }); +} + +function fixDbAccess(username){ + myPost('fix_db_access', '', function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); +} + +function setDbPass(id, username, password){ + + var index = layer.open({ + type: 1, + area: '500px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                \ +
                \ + 用户名\ +
                \ +
                \ +
                \ + 密码\ +
                \ + \ +
                \ +
                \ + \ +
                ", + yes:function(index){ + var data = {}; + data['name'] = $('input[name=name]').val(); + data['password'] = $('#MyPassword').val(); + data['id'] = $('input[name=id]').val(); + myPost('set_user_pwd', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); +} + +function addDatabase(type){ + layer.open({ + type: 1, + area: '500px', + title: '添加数据库', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                \ +
                \ + 数据库名\ +
                \ + \ +
                \ +
                \ +
                用户名
                \ +
                \ + 密码\ +
                \ +
                \ +
                \ + 访问权限\ +
                \ + \ +
                \ +
                \ + \ +
                ", + success:function(){ + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='db_user']").val(v); + $("input[name='ps']").val(v); + }); + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index) { + var data = $("#add_db").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['address']){ + dataObj['address'] = dataObj['dataAccess']; + } + myPost('add_db', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + dbList(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); +} + +function delDb(id, name){ + safeMessage('删除['+name+']','您真的要删除['+name+']吗?',function(){ + var data='id='+id+'&name='+name + myPost('del_db', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); +} + +function delDbBatch(){ + var arr = []; + $('input[type="checkbox"].check:checked').each(function () { + var _val = $(this).val(); + var _name = $(this).parent().next().text(); + if (!isNaN(_val)) { + arr.push({'id':_val,'name':_name}); + } + }); + + safeMessage('批量删除数据库','您共选择了[2]个数据库,删除后将无法恢复,真的要删除吗?',function(){ + var i = 0; + $(arr).each(function(){ + var data = myAsyncPost('del_db', this); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + } + i++; + }); + + var msg = '成功删除['+i+']个数据库!'; + showMsg(msg,function(){ + dbList(); + },{icon: 1}, 600); + }); +} + + +function setDbPs(id, name, obj) { + var _span = $(obj); + var _input = $(""); + _span.hide().after(_input); + _input.focus(); + _input.blur(function(){ + $(this).remove(); + var ps = _input.val(); + _span.text(ps).show(); + var data = {name:name,id:id,ps:ps}; + myPost('set_db_ps', data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + }); + _input.keyup(function(){ + if(event.keyCode == 13){ + _input.trigger('blur'); + } + }); +} + +function openPhpmyadmin(name,username,password){ + $.post('/plugins/run', {'name':'phpmyadmin','func':'plugins_db_support'}, function(data){ + var rdata = $.parseJSON(data.data); + + if (rdata.data['installed'] != 'ok'){ + layer.msg('phpMyAdmin未安装!',{icon:2,shade: [0.3, '#000']}); + return; + } + + if (rdata.data['status'] != 'start'){ + layer.msg('phpMyAdmin未启动',{icon:2,shade: [0.3, '#000']}); + return; + } + + if (rdata.data['cfg']['choose'] != 'mariadb'){ + layer.msg('当前为['+rdata.data['cfg']['choose']+']模式,若要使用请修改phpMyAdmin访问切换.',{icon:2,shade: [0.3, '#000']}); + return; + } + var home_page = rdata.data['home_page']; + $("#toPHPMyAdmin").attr('action',home_page); + if($("#toPHPMyAdmin").attr('action').indexOf('phpmyadmin') == -1){ + layer.msg('请先安装phpMyAdmin',{icon:2,shade: [0.3, '#000']}); + setTimeout(function(){ window.location.href = '/soft'; },3000); + return; + } + //检查版本 + bigVer = rdata.data['version']; + if (bigVer>=4.5){ + + setTimeout(function(){ + $("#toPHPMyAdmin").submit(); + },2000); + layer.msg('phpMyAdmin['+data.data+']需要手动登录😭',{icon:16,shade: [0.3, '#000'],time:4000}); + + } else{ + var murl = $("#toPHPMyAdmin").attr('action'); + $("#pma_username").val(username); + $("#pma_password").val(password); + $("#db").val(name); + + layer.msg('正在打开phpMyAdmin',{icon:16,shade: [0.3, '#000'],time:2000}); + + setTimeout(function(){ + $("#toPHPMyAdmin").submit(); + },2000); + } + + },'json'); +} + +function delBackup(filename, name, path){ + if(typeof(path) == "undefined"){ + path = ""; + } + myPost('delete_db_backup',{filename:filename,path:path},function(){ + layer.msg('执行成功!'); + setTimeout(function(){ + setBackupReq(name); + },2000); + }); +} + +function downloadBackup(file){ + window.open('/files/download?filename='+encodeURIComponent(file)); +} + +function importBackup(file,name){ + myPost('import_db_backup',{file:file,name:name}, function(data){ + layer.msg('执行成功!'); + }); +} + + +function importDbExternal(file,name){ + myPost('import_db_external',{file:file,name:name}, function(data){ + layer.msg('执行成功!'); + }); +} + +function importDbExternalProgress(file,name){ + myPost('import_db_external_progress',{file:file,name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + title: "手动导入命令CMD【显示进度】", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                \ +
                \ +
                '+rdata.data+'
                \ +
                \ +
                ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + +function setLocalImport(db_name){ + + //上传文件 + function uploadDbFiles(upload_dir){ + var up_db = layer.open({ + type:1, + closeBtn: 1, + title:"上传导入文件["+upload_dir+']', + area: ['500px','300px'], + shadeClose:false, + content:'
                \ + \ + \ + \ + \ + \ + \ + 文件编码:\ + \ + \ + \ +
                  \ +
                  ', + success:function(){ + $('#filesClose').click(function(){ + layer.close(up_db); + }); + } + + }); + uploadStart(function(){ + getList(); + layer.close(up_db); + }); + } + + function getList(){ + myPost('get_db_backup_import_list',{}, function(data){ + var rdata = $.parseJSON(data.data); + + var file_list = rdata.data.list; + var upload_dir = rdata.data.upload_dir; + + var tbody = ''; + for (var i = 0; i < file_list.length; i++) { + tbody += '\ + ' + file_list[i]['name'] + '\ + ' + file_list[i]['size'] + '\ + ' + file_list[i]['time'] + '\ + \ + 导入 | \ + 导入进度 | \ + 删除\ + \ + '; + } + + $('#import_db_file_list').html(tbody); + $('input[name="upload_dir"]').val(upload_dir); + + $("#import_db_file_list .del").on('click',function(){ + var index = $(this).attr('index'); + var filename = file_list[index]["name"]; + myPost('delete_db_backup',{filename:filename,path:upload_dir},function(){ + showMsg('执行成功!', function(){ + getList(); + },{icon:1},2000); + }); + }); + }); + } + + var layerIndex = layer.open({ + type: 1, + title: "从文件导入数据", + area: ['700px', '380px'], + closeBtn: 1, + shadeClose: false, + content: '
                  \ +
                  \ + \ +
                  \ +
                  \ + \ +
                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                  文件名称文件大小备份时间操作
                  \ +
                  \ +
                    \ +
                  • 仅支持sql、zip、sql.gz、(tar.gz|gz|tgz)
                  • \ +
                  • zip、tar.gz压缩包结构:test.zip或test.tar.gz压缩包内,必需包含test.sql
                  • \ +
                  • 若文件过大,您还可以使用SFTP工具,将数据库文件上传到/www/backup/import
                  • \ +
                  \ +
                  \ +
                  ', + success:function(index){ + $('#btn_file_upload').click(function(){ + var upload_dir = $('input[name="upload_dir"]').val(); + uploadDbFiles(upload_dir); + }); + getList(); + }, + }); +} + + +function setBackup(db_name){ + var layerIndex = layer.open({ + type: 1, + title: "数据库备份详情", + area: ['600px', '280px'], + closeBtn: 1, + shadeClose: false, + content: '
                  \ +
                  \ + \ + \ +
                  \ +
                  \ +
                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                  文件名称文件大小备份时间操作
                  \ +
                  \ +
                  \ +
                  ', + success:function(index){ + $('#btn_backup').click(function(){ + myPost('set_db_backup',{name:db_name}, function(data){ + showMsg('执行成功!', function(){ + setBackupReq(db_name); + }, {icon:1}, 2000); + }); + }); + + $('#btn_local_import').click(function(){ + setLocalImport(db_name); + }); + + setBackupReq(db_name); + }, + }); +} + + +function setBackupReq(db_name, obj){ + myPost('get_db_backup_list', {name:db_name}, function(data){ + var rdata = $.parseJSON(data.data); + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + tbody += '\ + ' + rdata.data[i]['name'] + '\ + ' + rdata.data[i]['size'] + '\ + ' + rdata.data[i]['time'] + '\ + \ + 导入 | \ + 下载 | \ + 删除\ + \ + '; + } + $('#database_table tbody').html(tbody); + }); +} + + +function dbList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + myPost('get_db_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list +=''; + list += '' + rdata.data[i]['name'] +''; + list += '' + rdata.data[i]['username'] +''; + list += '' + + '***' + + ''+ + ''+ + ''; + + + list += ''+rdata.data[i]['ps']+''; + list += ''; + + list += ''+(rdata.data[i]['is_backup']?'已备份':'未备份') +' | '; + + var rw = ''; + var rw_change = 'all'; + if (typeof(rdata.data[i]['rw'])!='undefined'){ + var rw_val = '读写'; + if (rdata.data[i]['rw'] == 'all'){ + rw_val = "所有"; + rw_change = 'rw'; + } else if (rdata.data[i]['rw'] == 'rw'){ + rw_val = "读写"; + rw_change = 'r'; + } else if (rdata.data[i]['rw'] == 'r'){ + rw_val = "只读"; + rw_change = 'all'; + } + rw = ''+rw_val+' | '; + } + + + list += '管理 | ' + + '工具 | ' + + '权限 | ' + + rw + + '改密 | ' + + '删除' + + ''; + list += ''; + } + + // + var con = '
                  \ + \ + \ + \ + \ + \ + \ + \ + \ +
                  \ +
                  \ + \ + \ + \ + \ + \ + '+ + // ''+ + '\ + \ + \ + '+ list +'\ +
                  数据库名用户名密码备份备注操作
                  \ +
                  \ +
                  \ +
                  \ + 同步选中\ + 同步所有\ + 从服务器获取\ +
                  \ +
                  \ +
                  '; + + con += ''; + + $(".soft-man-con").html(con); + $('#databasePage').html(rdata.page); + + readerTableChecked(); + }); +} + +function myBinRollingLogs(_name, func, _args, line){ + + var file_line = 100; + if ( typeof(line) != 'undefined' ){ + file_line = line; + } + + var reqTimer = null; + + function requestLogs(func,file,line){ + myPostCallbakN(func,'',{'file':file,"line":line}, function(rdata){ + var data = rdata.data.data; + var cmd = rdata.data.cmd; + if(data == '') { + data = '当前没有日志!'; + } + + $('#my_rolling_cmd').html(cmd); + + $('#my_rolling_copy').click(function(){ + copyText(cmd); + }); + + var ebody = ''; + $("#my_rolling_logs").html(ebody); + var ob = document.getElementById('roll_info_log'); + ob.scrollTop = ob.scrollHeight; + }); + } + + + layer.open({ + type: 1, + title: _name + '日志', + area: ['800px','700px'], + end: function(){ + if (reqTimer){ + clearInterval(reqTimer); + } + }, + content:'
                  \ +
                  \ + \ + \ + \ + \ +
                  cmd
                  \ +
                  \ +
                  \ +
                  \ + \ +
                  ', + success:function(){ + var fileName = _args['file']; + requestLogs(func,fileName,file_line); + reqTimer = setInterval(function(){ + requestLogs(func,fileName,file_line); + },1000); + } + }); +} + +function myBinLogsRender(page){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + _data['tojs'] = 'myBinLogsRender'; + myPost('binlog_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var list = ''; + for(i in rdata.data){ + list += ''; + + list += '' + rdata.data[i]['name'] +''; + list += '' + toSize(rdata.data[i]['size'])+''; + list += '' + rdata.data[i]['time'] +''; + + + list += ''; + list += '查看 | '; + list += '解码查看'; + list += ''; + } + + if (rdata.data.length ==0){ + list = '无数据'; + } + + $("#binlog_list tbody").html(list); + $('#binlog_page').html(rdata.page); + + + $('#binlog_list .look').click(function(){ + var i = $(this).data('index'); + var file = rdata.data[i]['name']; + myBinRollingLogs('查看BINLOG','binLogListLook',{'file':file },100); + }); + + $('#binlog_list .look_decode').click(function(){ + var i = $(this).data('index'); + var file = rdata.data[i]['name']; + myBinRollingLogs('查看解码BINLOG','binLogListLookDecode',{'file':file },100); + }); + }); +} + +function myBinLogs(){ + var con = '
                  \ + \ + \ +
                  \ +
                  \ + \ + \ + \ + \ + \ + \ + \ +
                  文件名称大小时间操作
                  \ +
                  \ +
                  \ +
                  \ +
                  '; + $(".soft-man-con").html(con); + myBinLogsRender(1); + + $('.soft-man-con .relay_trace').click(function(){ + myBinRollingLogs('中继日志跟踪','binLogListTraceRelay',{'file':''},100); + }); + + $('.soft-man-con .binlog_trace').click(function(){ + myBinRollingLogs('最新BINLOG日志跟踪','binLogListTraceBinLog',{'file':''},100); + }); +} + +function myLogs(){ + + myPost('bin_log', {status:1}, function(data){ + var rdata = $.parseJSON(data.data); + + var line_status = "" + if (rdata.status){ + line_status = '\ + '; + } else { + line_status = ''; + } + + var limitCon = '

                  \ + 二进制日志 ' + toSize(rdata.msg) + '\ + '+line_status+'\ +

                  错误日志

                  \ + \ +

                  '; + $(".soft-man-con").html(limitCon); + + //设置二进制日志 + $(".btn-bin").click(function () { + myPost('bin_log', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){myLogs();}, 2000); + }); + }); + + $(".clean-btn-bin").click(function () { + myPost('clean_bin_log', '', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){myLogs();}, 2000); + }); + }); + + //清空日志 + $(".btn-clear").click(function () { + myPost('error_log', 'close=1', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){myLogs();}, 2000); + }); + }) + + myPost('error_log', 'p=1', function(data){ + var rdata = $.parseJSON(data.data); + var error_body = ''; + if (rdata.status){ + error_body = rdata.data; + } else { + error_body = rdata.msg; + } + $("#error_log").html(error_body); + var ob = document.getElementById('error_log'); + ob.scrollTop = ob.scrollHeight; + }); + }); +} + + +function repCheckeds(tables) { + var dbs = [] + if (tables) { + dbs.push(tables) + } else { + var db_tools = $("input[value^='dbtools_']"); + for (var i = 0; i < db_tools.length; i++) { + if (db_tools[i].checked) dbs.push(db_tools[i].value.replace('dbtools_', '')); + } + } + + if (dbs.length < 1) { + layer.msg('请至少选择一张表!', { icon: 2 }); + return false; + } + return dbs; +} + +function repDatabase(db_name, tables) { + dbs = repCheckeds(tables); + + myPost('repair_table', { db_name: db_name, tables: JSON.stringify(dbs) }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + repTools(db_name, true); + },'已送修复指令,请稍候...'); +} + + +function optDatabase(db_name, tables) { + dbs = repCheckeds(tables); + + myPost('opt_table', { db_name: db_name, tables: JSON.stringify(dbs) }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + repTools(db_name, true); + },'已送优化指令,请稍候...'); +} + +function toDatabaseType(db_name, tables, type){ + dbs = repCheckeds(tables); + myPost('alter_table', { db_name: db_name, tables: JSON.stringify(dbs),table_type: type }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + repTools(db_name, true); + }, '已送引擎转换指令,请稍候...'); +} + + +function selectedTools(my_obj, db_name) { + var is_checked = false + + if (my_obj) is_checked = my_obj.checked; + var db_tools = $("input[value^='dbtools_']"); + var n = 0; + for (var i = 0; i < db_tools.length; i++) { + if (my_obj) db_tools[i].checked = is_checked; + if (db_tools[i].checked) n++; + } + if (n > 0) { + var my_btns = '\ + \ + \ + ' + $("#db_tools").html(my_btns); + } else { + $("#db_tools").html(''); + } +} + +function repTools(db_name, res){ + myPost('get_db_info', {name:db_name}, function(data){ + var rdata = $.parseJSON(data.data); + var types = { InnoDB: "MyISAM", MyISAM: "InnoDB" }; + var tbody = ''; + for (var i = 0; i < rdata.tables.length; i++) { + if (!types[rdata.tables[i].type]) continue; + tbody += '\ + \ + ' + rdata.tables[i].table_name + '\ + ' + rdata.tables[i].type + '\ + ' + rdata.tables[i].collation + '\ + ' + rdata.tables[i].rows_count + '\ + ' + rdata.tables[i].data_size + '\ + \ + 修复 |\ + 优化 |\ + 转为' + types[rdata.tables[i].type] + '\ + \ + ' + } + + if (res) { + $(".gztr").html(tbody); + $("#db_tools").html(''); + $("input[type='checkbox']").attr("checked", false); + $(".tools_size").html('大小:' + rdata.data_size); + return; + } + + layer.open({ + type: 1, + title: "MariaDB工具箱【" + db_name + "】", + area: ['780px', '580px'], + closeBtn: 2, + shadeClose: false, + content: '
                  \ + \ +
                  \ +
                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + ' + tbody + '\ +
                  表名引擎字符集行数大小操作
                  \ +
                  \ +
                  \ +
                    \ +
                  • 【修复】尝试使用REPAIR命令修复损坏的表,仅能做简单修复,若修复不成功请考虑使用myisamchk工具
                  • \ +
                  • 【优化】执行OPTIMIZE命令,可回收未释放的磁盘空间,建议每月执行一次
                  • \ +
                  • 【转为InnoDB/MyISAM】转换数据表引擎,建议将所有表转为InnoDB
                  • \ +
                  ' + }); + tableFixed('database_fix'); + }); +} + + +function setDbMaster(name){ + myPost('set_db_master', {name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + masterOrSlaveConf(); + }, 2000); + }); +} + + +function setDbSlave(name){ + myPost('set_db_slave', {name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + masterOrSlaveConf(); + }, 2000); + }); +} + + +function addMasterRepSlaveUser(){ + layer.open({ + type: 1, + area: '500px', + title: '添加同步账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","取消"], + content: "
                  \ +
                  用户名
                  \ +
                  \ + 密码\ +
                  \ +
                  \ + \ +
                  ", + success:function(){ + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='db_user']").val(v); + $("input[name='ps']").val(v); + }); + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#add_master").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['address']){ + dataObj['address'] = dataObj['dataAccess']; + } + + myPost('add_master_rep_slave_user', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + if (rdata.status){ + getMasterRepSlaveList(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); +} + + + +function updateMasterRepSlaveUser(username, password){ + + var index = layer.open({ + type: 1, + area: '500px', + title: '更新账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + content: "
                  \ +
                  用户名
                  \ +
                  \ + 密码\ +
                  \ +
                  \ + \ +
                  \ + \ +
                  \ +
                  ", + }); + + $('#submit_update_master').click(function(){ + var data = $("#update_master").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + myPost('update_master_rep_slave_user', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getMasterRepSlaveList(); + } + $('.layui-layer-close1').click(); + },{icon: rdata.status ? 1 : 2},600); + }); + }); +} + +function getMasterRepSlaveUserCmd(username, db=''){ + myPost('get_master_rep_slave_user_cmd', {username:username,db:db}, function(data){ + var rdata = $.parseJSON(data.data); + + if (!rdata['status']){ + layer.msg(rdata['msg']); + return; + } + + var cmd = rdata.data['cmd']; + + var loadOpen = layer.open({ + type: 1, + title: '同步命令', + area: '500px', + content:"
                  \ +
                  "+cmd+"
                  \ +
                  \ + \ +
                  \ +
                  ", + }); + + }); +} + +function delMasterRepSlaveUser(username){ + myPost('del_master_rep_slave_user', {username:username}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg); + + $('.layui-layer-close1').click(); + + setTimeout(function(){ + getMasterRepSlaveList(); + },1000); + }); +} + + +function setDbMasterAccess(username){ + myPost('get_db_access','username='+username, function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,shade: [0.3, '#000']}); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '设置数据库权限', + closeBtn: 1, + shift: 5, + btn:["提交","取消"], + shadeClose: true, + content: "
                  \ +
                  \ + 访问权限\ +
                  \ + \ +
                  \ +
                  \ +
                  ", + success:function(){ + if (rdata.msg == '127.0.0.1'){ + $('select[name="dataAccess"]').find("option[value='127.0.0.1']").attr("selected",true); + } else if (rdata.msg == '%'){ + $('select[name="dataAccess"]').find('option[value="%"]').attr("selected",true); + } else if ( rdata.msg == 'ip' ){ + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } else { + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#set_db_access").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['access']){ + dataObj['access'] = dataObj['dataAccess']; + if ( dataObj['dataAccess'] == 'ip'){ + if (dataObj['address']==''){ + layer.msg('IP地址不能空!',{icon:2,shade: [0.3, '#000']}); + return; + } + dataObj['access'] = dataObj['address']; + } + } + dataObj['username'] = username; + myPost('set_dbmaster_access', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); + + }); +} + +function resetMaster(){ + myPost('reset_master', '', function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + },{icon: rdata.status ? 1 : 2}); + },'正在执行重置master命令[reset master]'); +} + +function getMasterRepSlaveList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + myPost('get_master_rep_slave_list', _data, function(data){ + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e){ + console.log(e); + } + var list = ''; + // console.log(rdata['data']); + var user_list = rdata['data']; + for (i in user_list) { + // console.log(i); + var name = user_list[i]['username']; + var password = user_list[i]['password']; + list += ''+name+'\ + '+password+'\ + \ + 修改 | \ + 删除 | \ + 权限 | \ + 从库同步命令\ + \ + '; + } + + $('#get_master_rep_slave_list_page tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + +function getMasterRepSlaveListPage(){ + var page = '
                  '; + page += '
                  添加同步账户
                  '; + + var loadOpen = layer.open({ + type: 1, + title: '同步账户列表', + area: '500px', + content:"
                  \ +
                  \ +
                  \ + \ + \ +
                  用户名密码操作
                  \ + "+page +"\ +
                  \ +
                  ", + success:function(){ + getMasterRepSlaveList(); + } + }); +} + + +function deleteSlave(sign = ''){ + myPost('delete_slave', {sign:sign}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata['msg'], function(){ + masterOrSlaveConf(); + },{icon:rdata.status?1:2,time:3000},3000); + }); +} + + +function getFullSyncStatus(db){ + var timeId = null; + + myPost('get_slave_list', {page:1,page_size:100}, function(data){ + var rdata = $.parseJSON(data.data); + var rsource = rdata.data; + + if (db == 'ALL' && rsource.length>1){ + layer.msg("多主不支持该模式!",{icon:2}); + return; + } + + var dataSource = ''; + if (rsource.length>1){ + var sourceList = ''; + for (var i = 0; i < rsource.length; i++) { + if ('Connection_name' in rsource[i]){ + sourceList += ''; + } + } + + dataSource = "

                  \ + 同步数据源:\ + \ +

                  "; + } + + layer.open({ + type: 1, + title: '全量同步['+db+']', + area: '500px', + content:"
                  \ +
                  \ + "+dataSource+"\ + \ +
                  \ +
                  0%
                  \ +
                  \ +
                  \ +
                  \ + 开始\ + 手动命令\ +
                  \ +
                  ", + cancel: function(){ + clearInterval(timeId); + }, + success:function(){ + $('#begin_full_sync').click(function(){ + var val = $(this).data('status'); + var sign = ''; + if (dataSource !=''){ + sign = $('select[name="data_source"]').val(); + } + if (val == 'init'){ + fullSync(db, sign, 1); + timeId = setInterval(function(){ + fullSync(db,sign,0); + }, 1000); + $(this).data('status','starting'); + } else { + layer.msg("正在同步中..",{icon:0}); + } + }); + + $('#full_sync_cmd').click(function(){ + myPostN('full_sync_cmd', {'db':db,'sign':''}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.open({ + title: "手动执行命令CMD", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                  \ +
                  \ +
                  '+rdata.data+'
                  \ +
                  \ +
                  ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); + }); + } + }); + }); + + function fullSync(db, sign, begin){ + myPostN('full_sync', {db:db,sign:sign,begin:begin}, function(data){ + var rdata = $.parseJSON(data.data); + $('#full_msg').text(rdata['msg']); + $('.progress-bar').css('width',rdata['progress']+'%'); + $('.progress-bar').text(rdata['progress']+'%'); + + if (rdata['code']==6 ||rdata['code']<0){ + layer.msg(rdata['msg']); + clearInterval(timeId); + $("#begin_full_sync").data('status','init'); + } + }); + } + +} + +function dataSyncVerify(db){ + var reqTimer = null; + + function requestLogs(layerIndex){ + myPostN('sync_database_repair_log', {db:db, sign:'',op:'get'}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + + if(!rdata.status) { + layer.close(layerIndex); + layer.msg(rdata.msg,{icon:2, time:2000}); + clearInterval(reqTimer); + return; + }; + + if (rdata.msg == ''){ + rdata.msg = '暂无数据!'; + } + + $("#data_verify_log").html(rdata.msg); + //滚动到最低 + var ob = document.getElementById('data_verify_log'); + ob.scrollTop = ob.scrollHeight; + }); + } + + layer.open({ + type: 1, + title: '同步数据库['+db+']数据校验', + area: '500px', + btn:[ "开始","取消","手动"], + content:"
                  \ + "+'
                  '+"\
                  +            
                  ", + cancel: function(){ + if (reqTimer){ + clearInterval(reqTimer); + } + }, + yes:function(index,layer_index){ + myPostN('sync_database_repair_log', {db:db, sign:'',op:'do'}, function(data){}); + layer.msg("执行成功"); + + requestLogs(layer_index); + reqTimer = setInterval(function(){ + requestLogs(layer_index); + },3000); + }, + success:function(){ + }, + btn3: function(){ + myPostN('sync_database_repair_log', {db:db, sign:'',op:'cmd'}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.open({ + title: "手动执行命令CMD", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                  \ +
                  \ +
                  '+rdata.data+'
                  \ +
                  \ +
                  ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); + return false; + } + + }); +} + +function addSlaveSSH(ip=''){ + + myPost('get_slave_ssh_by_ip', {ip:ip}, function(rdata){ + + var rdata = $.parseJSON(rdata.data); + + var ip = '127.0.0.1'; + var port = "22"; + var id_rsa = ''; + var db_user =''; + + if (rdata.data.length>0){ + ip = rdata.data[0]['ip']; + port = rdata.data[0]['port']; + id_rsa = rdata.data[0]['id_rsa']; + db_user = rdata.data[0]['db_user']; + } + + var index = layer.open({ + type: 1, + area: ['500px','480px'], + title: '添加SSH', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["确认","取消"], + content: "
                  \ +
                  IP
                  \ +
                  端口
                  \ +
                  同步账户[DB]
                  \ +
                  \ + ID_RSA\ +
                  \ +
                  \ + \ +
                  ", + success:function(){ + $('textarea[name="id_rsa"]').html(id_rsa); + }, + yes:function(index){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var db_user = $('input[name="db_user"]').val(); + var id_rsa = $('textarea[name="id_rsa"]').val(); + + var data = {ip:ip,port:port,id_rsa:id_rsa,db_user:db_user}; + myPost('add_slave_ssh', data, function(data){ + layer.close(index); + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSSHPage(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); + }); +} + + + +function delSlaveSSH(ip){ + myPost('del_slave_ssh', {ip:ip}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSSHPage(); + } + },{icon: rdata.status ? 1 : 2}, 600); + }); +} + + +function delSlaveSyncUser(ip){ + myPost('del_slave_sync_user', {ip:ip}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSyncUserPage(); + } + },{icon: rdata.status ? 1 : 2}, 600); + }); +} + +function getSlaveSSHPage(page=1){ + var _data = {}; + _data['page'] = page; + _data['page_size'] = 5; + _data['tojs'] ='getSlaveSSHPage'; + myPost('get_slave_ssh_list', _data, function(data){ + var layerId = null; + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e) { + console.log(e); + } + var list = ''; + var ssh_list = rdata['data']; + for (i in ssh_list) { + var ip = ssh_list[i]['ip']; + var port = ssh_list[i]['port']; + + var id_rsa = '未设置'; + if ( ssh_list[i]['port'] != ''){ + id_rsa = '已设置'; + } + + var db_user = '未设置'; + if ( ssh_list[i]['db_user'] != ''){ + db_user = ssh_list[i]['db_user']; + } + + list += ''+ip+'\ + '+port+'\ + '+db_user+'\ + '+id_rsa+'\ + \ + 修改 | \ + 删除\ + \ + '; + } + + $('.get-slave-ssh-list tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + +function addSlaveSyncUser(ip=''){ + + myPost('get_slave_sync_user_by_ip', {ip:ip}, function(rdata){ + + var rdata = $.parseJSON(rdata.data); + + var ip = '127.0.0.1'; + var port = "22"; + var cmd = ''; + var user = 'input_sync_user'; + var pass = 'input_sync_pwd'; + var mode = '0'; + + if (rdata.data.length>0){ + ip = rdata.data[0]['ip']; + port = rdata.data[0]['port']; + cmd = rdata.data[0]['cmd']; + user = rdata.data[0]['user']; + pass = rdata.data[0]['pass']; + mode = rdata.data[0]['mode']; + } + + var index = layer.open({ + type: 1, + area: ['500px','510px'], + title: '同步账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["确认","取消"], + content: "
                  \ +
                  IP
                  \ +
                  端口
                  \ +
                  同步账户
                  \ +
                  同步密码
                  \ +
                  \ + 同步模式\ +
                  \ + \ +
                  \ +
                  \ +
                  \ + CMD[必填]\ +
                  \ +
                  \ + \ +
                  ", + success:function(){ + $('textarea[name="cmd"]').html(cmd); + + $('textarea[name="cmd"]').change(function(){ + var val = $(this).val(); + var vlist = val.split(','); + var a = {}; + for (var i in vlist) { + var tmp = toTrim(vlist[i]); + var tmp_a = tmp.split(" "); + var real_tmp = tmp_a[tmp_a.length-1]; + var kv = real_tmp.split("="); + a[kv[0]] = kv[1].replace("'",'').replace("'",''); + } + + $('input[name="ip"]').val(a['MASTER_HOST']); + $('input[name="port"]').val(a['MASTER_PORT']); + $('input[name="user"]').val(a['MASTER_USER']); + $('input[name="pass"]').val(a['MASTER_PASSWORD']); + + console.log(a['MASTER_USE_GTID'],typeof(a['MASTER_USE_GTID'])); + if (typeof(a['MASTER_USE_GTID']) != 'undefined' ){ + $('input[name="mode"]').val('1'); + } + }); + }, + yes:function(index){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var user = $('input[name="user"]').val(); + var pass = $('input[name="pass"]').val(); + var cmd = $('textarea[name="cmd"]').val(); + var mode = $('input[name="mode"]').val(); + + var data = {ip:ip,port:port,cmd:cmd,user:user,pass:pass,mode:mode}; + myPost('add_slave_sync_user', data, function(ret_data){ + layer.close(index); + var rdata = $.parseJSON(ret_data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSyncUserPage(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); + }); +} + +function getSlaveSyncUserPage(page=1){ + var _data = {}; + _data['page'] = page; + _data['page_size'] = 5; + _data['tojs'] ='getSlaveSyncUserPage'; + myPost('get_slave_sync_user_list', _data, function(data){ + var layerId = null; + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e) { + console.log(e); + } + + var list = ''; + var user_list = rdata['data']; + for (i in user_list) { + var ip = user_list[i]['ip']; + var port = user_list[i]['port']; + var user = user_list[i]['user']; + var apass = user_list[i]['pass']; + + var cmd = '未设置'; + if (user_list[i]['cmd']!=''){ + cmd = '已设置'; + } + + list += ''+ip+'\ + '+port+'\ + '+user+'\ + '+apass+'\ + '+cmd+'\ + \ + 修改 | \ + 删除\ + \ + '; + } + + $('.get-slave-ssh-list tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + +function getSlaveCfg(){ + + myPost('get_slave_sync_mode', '', function(data){ + var rdata = $.parseJSON(data.data); + var mode_none = 'success'; + var mode_ssh = 'danger'; + var mode_sync_user = 'danger'; + if(rdata.status){ + var mode_none = 'danger'; + if (rdata.data == 'ssh'){ + var mode_ssh = 'success'; + var mode_sync_user = 'danger'; + } else { + var mode_ssh = 'danger'; + var mode_sync_user = 'success'; + } + } + + layerId = layer.open({ + type: 1, + title: '同步配置', + area: ['400px','180px'], + content:"
                  \ +

                  \ + 当前从库同步模式\ + \ + \ + \ + \ +

                  \ +
                  \ +

                  \ + 配置设置\ + \ + \ + \ +

                  \ +
                  ", + success:function(){ + $('.btn-slave-ssh').click(function(){ + getSlaveSSHList(); + }); + + $('.btn-slave-user').click(function(){ + getSlaveUserList(); + }); + + $('.slave-db-mode').click(function(){ + var _this = this; + var mode = 'none'; + if ($(this).hasClass('btn-ssh')){ + mode = 'ssh'; + } + if ($(this).hasClass('btn-sync-user')){ + mode = 'sync-user'; + } + + myPost('set_slave_sync_mode', {mode:mode}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + $('.slave-db-mode').remove('btn-success').addClass('btn-danger'); + $(_this).removeClass('btn-danger').addClass('btn-success'); + },{icon:rdata.status?1:2},2000); + }); + + }); + } + }); + }); +} + +function getSlaveUserList(){ + + var page = '
                  '; + page += '
                  添加同步账户
                  '; + + layerId = layer.open({ + type: 1, + title: '同步账户列表', + area: '600px', + content:"
                  \ +
                  \ +
                  \ + \ + \ +
                  IPPORT同步账户同步密码CMD操作
                  \ + "+page +"\ +
                  \ +
                  ", + success:function(){ + getSlaveSyncUserPage(1); + } + }); +} + +function getSlaveSSHList(page=1){ + + var page = '
                  '; + page += '
                  添加SSH
                  '; + + layerId = layer.open({ + type: 1, + title: 'SSH列表', + area: '600px', + content:"
                  \ +
                  \ +
                  \ + \ + \ +
                  IPPORT同步账户SSH操作
                  \ + "+page +"\ +
                  \ +
                  ", + success:function(){ + getSlaveSSHPage(1); + } + }); +} + +function handlerRun(){ + myPostN('get_slave_sync_cmd', {}, function(data){ + var rdata = $.parseJSON(data.data); + var cmd = rdata['data']; + var loadOpen = layer.open({ + type: 1, + title: '手动执行', + area: '500px', + content:"
                  \ +
                  "+cmd+"
                  \ +
                  \ + \ +
                  \ +
                  ", + }); + copyPass(cmd); + $('.class-copy-cmd').click(function(){ + copyPass(cmd); + }); + }); +} + +function initSlaveStatus(){ + myPost('init_slave_status', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status ){ + layer.msg(rdata.msg,{icon:2,time:10000}); + return; + } + showMsg(rdata.msg,function(){ + if (rdata.status){ + masterOrSlaveConf(); + } + },{icon:1},2000); + }); +} + +function masterOrSlaveConf(version=''){ + + function getMasterDbList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + + myPost('get_masterdb_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list += '' + rdata.data[i]['name'] +''; + list += '' + (rdata.data[i]['master']?'是':'否') +''; + list += '' + + ''+(rdata.data[i]['master']?'退出':'加入')+' | ' + + '同步命令' + + ''; + list += ''; + } + + var con = '
                  \ +
                  \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                  数据库名同步操作
                  \ +
                  \ +
                  \ +
                  \ + 同步账户列表\ +
                  \ +
                  '; + + $(".table_master_list").html(con); + $('#databasePage').html(rdata.page); + }); + } + + function getAsyncMasterDbList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 100; + + myPost('get_slave_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + + var isHasSign = false; + for(i in rdata.data){ + + var v = rdata.data[i]; + if ('Connection_name' in v){ + isHasSign = true; + } + + var status = "异常"; + if (v['Slave_SQL_Running'] == 'Yes' && v['Slave_IO_Running'] == 'Yes'){ + status = "正常"; + } + + list += ''; + list += '' + v['Master_Host'] +''; + list += '' + v['Master_Port'] +''; + list += '' + v['Master_User'] +''; + list += '' + v['Master_Log_File'] +''; + list += '' + v['Slave_IO_Running'] +''; + list += '' + v['Slave_SQL_Running'] +''; + if (isHasSign){ + list += '' + v['Connection_name'] +''; + } + + list += '' + status +''; + list += '\ + 删除\ + '; + list += ''; + } + + var signThead_th = ''; + if (isHasSign){ + var signThead_th = '标识'; + } + + var con = '
                  \ +
                  \ + \ + \ + \ + \ + \ + \ + \ + \ + '+signThead_th+'\ + \ + \ + \ + '+ list +'\ +
                  主[服务]端口用户日志IOSQL状态操作
                  \ +
                  \ +
                  '; + + //
                  \ + //
                  \ + // 添加\ + //
                  + $(".table_slave_status_list").html(con); + + + $(".btn_delete_slave").click(function(){ + var id = $(this).data('id'); + var v = rdata.data[id]; + if ('Connection_name' in v){ + deleteSlave(v['Connection_name']); + } else{ + deleteSlave(); + } + }); + + $('.db_error').click(function(){ + var id = $(this).data('id'); + var info = rdata.data[id]; + + var err_line = ""; + err_line +="\ + IO错误\ + "+ (info['Last_IO_Error'] == '' ? '无':info['Last_IO_Error'])+"\ + "; + err_line +="\ + SQL错误\ + "+(info['Last_SQL_Error'] == '' ? '无':info['Last_SQL_Error'])+"\ + "; + + err_line +="\ + 状态\ + "+(info['Slave_SQL_Running_State'] == '' ? '无':info['Slave_SQL_Running_State']) +"\ + "; + + var btn_list = ['复制错误',"取消"]; + if (info['Last_IO_Error'].search(/1236/i)>0){ + btn_list = ['复制错误',"取消","尝试修复"]; + } + + layer.open({ + type: 1, + title: '同步异常信息', + area: ['600px','300px'], + btn:btn_list, + content:"
                  \ +
                  \ +
                  \ + \ + \ + \ + \ + \ + "+ err_line +"\ +
                  类型内容
                  \ +
                  \ +
                  \ +
                  ", + success:function(){ + if (info['Last_IO_Error'] != ''){ + copyText(info['Last_IO_Error']); + return; + } + + if (info['Last_SQL_Error'] != ''){ + copyText(info['Last_SQL_Error']); + return; + } + + if (info['Slave_SQL_Running_State'] != ''){ + copyText(info['Slave_SQL_Running_State']); + return; + } + }, + yes:function(){ + if (info['Last_IO_Error'] != ''){ + copyText(info['Last_IO_Error']); + return; + } + + if (info['Last_SQL_Error'] != ''){ + copyText(info['Last_SQL_Error']); + return; + } + + if (info['Slave_SQL_Running_State'] != ''){ + copyText(info['Slave_SQL_Running_State']); + return; + } + }, + btn3:function(){ + myPost('try_slave_sync_bugfix', {}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function(){ + masterOrSlaveConf(); + },{ icon: rdata.status ? 1 : 5 },2000); + }); + } + }); + }); + }); + } + + function getAsyncDataList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + myPost('get_masterdb_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list += '' + rdata.data[i]['name'] +''; + list += '' + + ''+(rdata.data[i]['slave']?'退出':'加入')+' | ' + + '同步 | ' + + '数据校验' + + ''; + list += ''; + } + + var con = '
                  \ +
                  \ + \ + \ + \ + \ + \ + '+ list +'\ +
                  本地库名操作
                  \ +
                  \ +
                  \ +
                  \ + 手动命令\ + 全量同步\ +
                  \ +
                  '; + + $(".table_slave_list").html(con); + $('#databasePage').html(rdata.page); + }); + } + + function getMasterStatus(){ + myPost('get_master_status', '', function(rdata){ + var rdata = $.parseJSON(rdata.data); + // console.log('mode:',rdata.data); + if ( typeof(rdata.status) != 'undefined' && !rdata.status && rdata.data == 'pwd'){ + layer.msg(rdata.msg, {icon:2,time:2000}); + return; + } + + var rdata = rdata.data; + var limitCon = '\ +

                  \ + 主从同步模式\ + \ + \ +

                  \ +
                  \ +

                  \ + Master[主]配置\ + \ + \ +

                  \ +
                  \ + \ +
                  \ +
                  \ + \ +

                  \ + Slave[从]配置\ + \ + \ + \ +

                  \ +
                  \ + \ +
                  \ + \ +
                  \ + '; + $(".soft-man-con").html(limitCon); + + //设置主服务器配置 + $(".btn-master").click(function () { + myPost('set_master_status', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + getMasterStatus(); + }, 3000); + }); + }); + + $(".btn-slave").click(function () { + myPost('set_slave_status', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + getMasterStatus(); + }, 3000); + }); + }); + + $('.db-mode').click(function(){ + if ($(this).hasClass('btn-success')){ + //no action + return; + } + + var mode = 'classic'; + if ($(this).hasClass('btn-gtid')){ + mode = 'gtid'; + } + + layer.open({ + type:1, + title:"MySQL主从模式切换", + shadeClose:false, + btnAlign: 'c', + btn: ['切换并重启', '切换不重启'], + yes: function(index, layero){ + this.change(index,mode,"yes"); + + }, + btn2: function(index, layero){ + this.change(index,mode,"no"); + return false; + }, + change:function(index,mode,reload){ + myPost('set_dbrun_mode',{'mode':mode,'reload':reload},function(data){ + layer.close(index); + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg ,function(){ + getMasterStatus(); + },{ icon: rdata.status ? 1 : 5 }); + }); + } + }); + }); + + if (rdata.status){ + getMasterDbList(); + } + + getAsyncMasterDbList(); + getAsyncDataList() + }); + } + getMasterStatus(); +} diff --git a/plugins/mariadb/scripts/backup.py b/plugins/mariadb/scripts/backup.py new file mode 100755 index 000000000..874eaae56 --- /dev/null +++ b/plugins/mariadb/scripts/backup.py @@ -0,0 +1,132 @@ +# coding: utf-8 +#----------------------------- +# 网站备份工具 +#----------------------------- + +import sys +import os +import re +import time + +if sys.platform != 'darwin': + os.chdir('/www/server/mdserver-web') + + +# chdir = os.getcwd() +# sys.path.append(chdir + '/class/core') + +# reload(sys) +# sys.setdefaultencoding('utf-8') + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import core.db as db + + +class backupTools: + + def backupDatabase(self, name, count): + db_path = mw.getServerDir() + '/mariadb' + db_sock = mw.getServerDir() + '/mariadb/' + db_name = 'mysql' + name = mw.M('databases').dbPos(db_path, 'mysql').where( + 'name=?', (name,)).getField('name') + startTime = time.time() + if not name: + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + log = "数据库[" + name + "]不存在!" + print("★[" + endDate + "] " + log) + print( + "----------------------------------------------------------------------------") + return + + backup_path = mw.getFatherDir() + '/backup/database/mariadb' + if not os.path.exists(backup_path): + mw.execShell("mkdir -p " + backup_path) + + filename = backup_path + "/db_" + name + "_" + \ + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + ".sql.gz" + + mysql_root = mw.M('config').dbPos(db_path, db_name).where( + "id=?", (1,)).getField('mysql_root') + + my_conf_path = db_path + '/etc/my.cnf' + content = mw.readFile(my_conf_path) + rep = r"\[mysqldump\]\nuser=root" + sea = "[mysqldump]\n" + subStr = sea + "user=root\npassword=" + mysql_root + "\n" + content = content.replace(sea, subStr) + if len(content) > 100: + mw.writeFile(my_conf_path, content) + + # mw.execShell(db_path + "/bin/mysqldump --defaults-file=" + my_conf_path + " --opt --default-character-set=utf8 " + + # name + " | gzip > " + filename) + + # mw.execShell(db_path + "/bin/mysqldump --defaults-file=" + my_conf_path + " --skip-lock-tables --default-character-set=utf8 " + + # name + " | gzip > " + filename) + + cmd = db_path + "/bin/mariadb-dump --defaults-file=" + my_conf_path + " --single-transaction --quick --default-character-set=utf8 " + \ + name + " | gzip > " + filename + mw.execShell(cmd) + + if not os.path.exists(filename): + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + log = "数据库[" + name + "]备份失败!" + print("★[" + endDate + "] " + log) + print( + "----------------------------------------------------------------------------") + return + + mycnf = mw.readFile(db_path + '/etc/my.cnf') + mycnf = mycnf.replace(subStr, sea) + if len(mycnf) > 100: + mw.writeFile(db_path + '/etc/my.cnf', mycnf) + + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + outTime = time.time() - startTime + pid = mw.M('databases').dbPos(db_path, db_name).where( + 'name=?', (name,)).getField('id') + + mw.M('backup').add('type,name,pid,filename,addtime,size', (3, os.path.basename( + filename), pid, filename, endDate, os.path.getsize(filename))) + log = "数据库[" + name + "]备份成功,用时[" + str(round(outTime, 2)) + "]秒" + mw.writeLog('计划任务', log) + print("★[" + endDate + "] " + log) + print("|---保留最新的[" + count + "]份备份") + print("|---文件名:" + filename) + + # 清理多余备份 + backups = mw.M('backup').where( + 'type=? and pid=?', ('3', pid)).field('id,filename').select() + + num = len(backups) - int(count) + if num > 0: + for backup in backups: + mw.execShell("rm -f " + backup['filename']) + mw.M('backup').where('id=?', (backup['id'],)).delete() + num -= 1 + print("|---已清理过期备份文件:" + backup['filename']) + if num < 1: + break + + def backupDatabaseAll(self, save): + db_path = mw.getServerDir() + '/mariadb' + db_name = 'mysql' + databases = mw.M('databases').dbPos( + db_path, db_name).field('name').select() + for database in databases: + self.backupDatabase(database['name'], save) + + +if __name__ == "__main__": + backup = backupTools() + type = sys.argv[1] + if type == 'database': + if sys.argv[2] == 'ALL': + backup.backupDatabaseAll(sys.argv[3]) + else: + backup.backupDatabase(sys.argv[2], sys.argv[3]) diff --git a/plugins/mariadb/scripts/test.py b/plugins/mariadb/scripts/test.py new file mode 100644 index 000000000..b4ef50fd2 --- /dev/null +++ b/plugins/mariadb/scripts/test.py @@ -0,0 +1,66 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + +cur_dir = os.getcwd() +plugin_dir = os.path.dirname(cur_dir) + +# print(plugin_dir) +root_dir = os.path.dirname(os.path.dirname(plugin_dir)) +# print(root_dir) + +sys.path.append(root_dir + "/class/core") +import mw + +os.chdir(root_dir) + + +def getPluginName(): + return 'mariadb' + + +def getPluginDir(): + return root_dir + '/plugins/' + getPluginName() + + +def getServerDir(): + return os.path.dirname(root_dir) + '/' + getPluginName() + + +def pSqliteDb(dbname='databases'): + file = getServerDir() + '/mariadb.db' + name = 'mysql' + if not os.path.exists(file): + conn = mw.M(dbname).dbPos(getServerDir(), name) + csql = mw.readFile(getPluginDir() + '/conf/mariadb.sql') + csql_list = csql.split(';') + for index in range(len(csql_list)): + conn.execute(csql_list[index], ()) + else: + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + + +def getConf(): + path = getServerDir() + '/etc/my.cnf' + return path + + +def pMysqlDb(): + db = mw.getMyORM() + db.setDbConf(getConf()) + db.__DB_SOCKET = mw.getServerDir() + '/mysql/mysql.socket' + + print(mw.getServerDir() + '/mysql/mysql.socket') + db.setPwd(pSqliteDb('config').where('id=?', (1,)).getField('mysql_root')) + return db + +if __name__ == '__main__': + p = pMysqlDb() + print(p.query("select User,Host from mysql.user where User!='root' AND Host!='localhost' AND Host!=''")) diff --git a/plugins/mariadb/scripts/tools.py b/plugins/mariadb/scripts/tools.py new file mode 100755 index 000000000..025cef105 --- /dev/null +++ b/plugins/mariadb/scripts/tools.py @@ -0,0 +1,69 @@ +# coding: utf-8 + +import sys +import os +import json +import time + +sys.path.append(os.getcwd() + "/class/core") +import mw +import db + +cmd = 'ls /usr/local/lib/ | grep python | cut -d \\ -f 1 | awk \'END {print}\'' +info = mw.execShell(cmd) +p = "/usr/local/lib/" + info[0].strip() + "/site-packages" +sys.path.append(p) + + +def set_mysql_root(password): + # 设置MySQL密码 + import db + import os + sql = db.Sql() + + root_mysql = '''#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +pwd=$1 +${server}/init.d/mysql stop +${server}/bin/mysqld_safe --skip-grant-tables& +echo '正在修改密码...'; +echo 'The set password...'; +sleep 6 +m_version=$(cat ${server}/version.pl|grep -E "(5.1.|5.5.|5.6.|mariadb)") +if [ "$m_version" != "" ];then + ${server}/bin/mysql -uroot -e "insert into mysql.user(Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Reload_priv,Shutdown_priv,Process_priv,File_priv,Grant_priv,References_priv,Index_priv,Alter_priv,Show_db_priv,Super_priv,Create_tmp_table_priv,Lock_tables_priv,Execute_priv,Repl_slave_priv,Repl_client_priv,Create_view_priv,Show_view_priv,Create_routine_priv,Alter_routine_priv,Create_user_priv,Event_priv,Trigger_priv,Create_tablespace_priv,User,Password,host)values('Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','root',password('${pwd}'),'127.0.0.1')" + ${server}/bin/mysql -uroot -e "insert into mysql.user(Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Reload_priv,Shutdown_priv,Process_priv,File_priv,Grant_priv,References_priv,Index_priv,Alter_priv,Show_db_priv,Super_priv,Create_tmp_table_priv,Lock_tables_priv,Execute_priv,Repl_slave_priv,Repl_client_priv,Create_view_priv,Show_view_priv,Create_routine_priv,Alter_routine_priv,Create_user_priv,Event_priv,Trigger_priv,Create_tablespace_priv,User,Password,host)values('Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','root',password('${pwd}'),'localhost')" + ${server}/bin/mysql -uroot -e "UPDATE mysql.user SET password=PASSWORD('${pwd}') WHERE user='root'"; +else + ${server}/bin/mysql -uroot -e "UPDATE mysql.user SET authentication_string='' WHERE user='root'"; + ${server}/bin/mysql -uroot -e "FLUSH PRIVILEGES"; + ${server}/bin/mysql -uroot -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '${pwd}';"; +fi +${server} -uroot -e "FLUSH PRIVILEGES"; +pkill -9 mysqld_safe +pkill -9 mysqld +sleep 2 +${server}/init.d/mysql start + +echo '===========================================' +echo "root密码成功修改为: ${pwd}" +echo "The root password set ${pwd} successuful"''' + + server = mw.getServerDir() + '/mariadb' + root_mysql = root_mysql.replace('${server}', server) + mw.writeFile('mysql_root.sh', root_mysql) + os.system("/bin/bash mysql_root.sh " + password) + os.system("rm -f mysql_root.sh") + + pos = mw.getServerDir() + '/mariadb' + result = sql.table('config').dbPos(pos, 'mysql').where( + 'id=?', (1,)).setField('mysql_root', password) + + +if __name__ == "__main__": + type = sys.argv[1] + if type == 'root': + set_mysql_root(sys.argv[2]) + else: + print('ERROR: Parameter error') diff --git a/plugins/mariadb/versions/10.11/install.sh b/plugins/mariadb/versions/10.11/install.sh new file mode 100755 index 000000000..1ec8f6216 --- /dev/null +++ b/plugins/mariadb/versions/10.11/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=10.11.11 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '10.11' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/10.6/install.sh b/plugins/mariadb/versions/10.6/install.sh new file mode 100755 index 000000000..38637dab1 --- /dev/null +++ b/plugins/mariadb/versions/10.6/install.sh @@ -0,0 +1,123 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://mariadb.org/download/?t=mariadb + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=10.6.21 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # https://mirrors.aliyun.com/mariadb/mariadb-10.8.3/source/mariadb-10.8.3.tar.gz + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.6.8/source/mariadb-10.6.8.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + OPTIONS='' + if [ "$sysName" == "Darwin" ];then + OPTIONS='-DPLUGIN_TOKUDB=NO' + fi + + INSTALL_CMD=cmake + CMAKE3=`which cmake3` + if [ "$?" == "0" ];then + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && $INSTALL_CMD \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + $OPTIONS \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '10.6' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/10.7/install.sh b/plugins/mariadb/versions/10.7/install.sh new file mode 100755 index 000000000..c680889a3 --- /dev/null +++ b/plugins/mariadb/versions/10.7/install.sh @@ -0,0 +1,110 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=10.7.8 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.7.4/source/mariadb-10.7.4.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && cmake \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '10.7' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/10.8/install.sh b/plugins/mariadb/versions/10.8/install.sh new file mode 100755 index 000000000..d85218384 --- /dev/null +++ b/plugins/mariadb/versions/10.8/install.sh @@ -0,0 +1,110 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=10.8.7 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && cmake \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '10.8' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/10.9/install.sh b/plugins/mariadb/versions/10.9/install.sh new file mode 100755 index 000000000..6f6fcbf1e --- /dev/null +++ b/plugins/mariadb/versions/10.9/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=10.9.8 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '10.9' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/11.0/install.sh b/plugins/mariadb/versions/11.0/install.sh new file mode 100755 index 000000000..7b590149f --- /dev/null +++ b/plugins/mariadb/versions/11.0/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=11.0.5 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '11.0' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/11.1/install.sh b/plugins/mariadb/versions/11.1/install.sh new file mode 100755 index 000000000..3617d9cf3 --- /dev/null +++ b/plugins/mariadb/versions/11.1/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=11.1.4 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '11.1' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/11.2/install.sh b/plugins/mariadb/versions/11.2/install.sh new file mode 100755 index 000000000..04690307e --- /dev/null +++ b/plugins/mariadb/versions/11.2/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=11.2.6 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '11.2' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/11.3/install.sh b/plugins/mariadb/versions/11.3/install.sh new file mode 100755 index 000000000..fa7e2eb30 --- /dev/null +++ b/plugins/mariadb/versions/11.3/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=11.3.2 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '11.3' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/11.4/install.sh b/plugins/mariadb/versions/11.4/install.sh new file mode 100755 index 000000000..cb63ee500 --- /dev/null +++ b/plugins/mariadb/versions/11.4/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=11.4.5 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '11.4' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/11.5/install.sh b/plugins/mariadb/versions/11.5/install.sh new file mode 100755 index 000000000..8b3581135 --- /dev/null +++ b/plugins/mariadb/versions/11.5/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=11.5.2 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '11.5' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/11.6/install.sh b/plugins/mariadb/versions/11.6/install.sh new file mode 100755 index 000000000..654f225e0 --- /dev/null +++ b/plugins/mariadb/versions/11.6/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=11.6.2 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '11.6' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/11.7/install.sh b/plugins/mariadb/versions/11.7/install.sh new file mode 100755 index 000000000..b3f7bfc59 --- /dev/null +++ b/plugins/mariadb/versions/11.7/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=11.7.2 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '11.7' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/11.8/install.sh b/plugins/mariadb/versions/11.8/install.sh new file mode 100755 index 000000000..c4b07ce6d --- /dev/null +++ b/plugins/mariadb/versions/11.8/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=11.8.3 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '11.8' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/12.0/install.sh b/plugins/mariadb/versions/12.0/install.sh new file mode 100755 index 000000000..e8506c674 --- /dev/null +++ b/plugins/mariadb/versions/12.0/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=12.0.2 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '12.0' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mariadb/versions/12.1/install.sh b/plugins/mariadb/versions/12.1/install.sh new file mode 100755 index 000000000..2cf9a7bc1 --- /dev/null +++ b/plugins/mariadb/versions/12.1/install.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mariadbDir=${serverPath}/source/mariadb + +MY_VER=12.1.2 + +Install_app() +{ + mkdir -p ${mariadbDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + # if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + # wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://mirrors.aliyun.com/mariadb/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + # fi + + # https://downloads.mariadb.org/interstitial/mariadb-10.9.1/source/mariadb-10.9.1.tar.gz + if [ ! -f ${mariadbDir}/mariadb-${MY_VER}.tar.gz ];then + wget --no-check-certificate -O ${mariadbDir}/mariadb-${MY_VER}.tar.gz --tries=3 https://archive.mariadb.org/mariadb-${MY_VER}/source/mariadb-${MY_VER}.tar.gz + fi + + if [ ! -d ${mariadbDir}/mariadb-${MY_VER} ];then + cd ${mariadbDir} && tar -zxvf ${mariadbDir}/mariadb-${MY_VER}.tar.gz + fi + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -d $serverPath/mariadb ];then + cd ${mariadbDir}/mariadb-${MY_VER} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mariadb \ + -DMYSQL_DATADIR=$serverPath/mariadb/data/ \ + -DMYSQL_USER=mysql \ + -DMYSQL_UNIX_ADDR=$serverPath/mariadb/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mariadb ];then + echo '12.1' > $serverPath/mariadb/version.pl + echo '安装完成' + else + echo '安装失败' + echo 'install fail'>&2 + exit 1 + fi + fi + + if [ -d ${mariadbDir}/mariadb-${MY_VER} ];then + rm -rf ${mariadbDir}/mariadb-${MY_VER} + fi + +} + +Uninstall_app() +{ + rm -rf $serverPath/mariadb + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/memcached/ico.png b/plugins/memcached/ico.png new file mode 100755 index 000000000..704baa7ce Binary files /dev/null and b/plugins/memcached/ico.png differ diff --git a/plugins/memcached/index.html b/plugins/memcached/index.html new file mode 100755 index 000000000..251c0fec5 --- /dev/null +++ b/plugins/memcached/index.html @@ -0,0 +1,19 @@ +
                  +
                  +
                  +

                  服务

                  +

                  自启动

                  +

                  配置文件

                  +

                  负载状态

                  +

                  性能调整

                  +
                  +
                  +
                  +
                  +
                  + +
                  + \ No newline at end of file diff --git a/plugins/memcached/index.py b/plugins/memcached/index.py new file mode 100755 index 000000000..617f33a2e --- /dev/null +++ b/plugins/memcached/index.py @@ -0,0 +1,322 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +import warnings +warnings.filterwarnings('ignore', category=DeprecationWarning) + + +def getPluginName(): + return 'memcached' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/init.d/memcached" + return path + + +def getConfEnv(): + path = getServerDir() + "/memcached.env" + return path + + +def getConfTpl(): + path = getPluginDir() + "/init.d/memcached.tpl" + return path + + +def getMemPort(): + path = getServerDir() + '/memcached.env' + content = mw.readFile(path) + rep = r'PORT\s*=\s*(\d*)' + tmp = re.search(rep, content) + return tmp.groups()[0] + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + cmd = "ps aux|grep " + getPluginName() + " |grep -v grep | grep -v mdserver-web | awk '{print $2}'" + # print(cmd) + data = mw.execShell(cmd) + # print(data) + if data[0] == '': + return 'stop' + return 'start' + + +def initDreplace(): + + file_tpl = getConfTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/memcached' + + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/memcached.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/memcached.service.tpl' + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + envFile = getServerDir() + '/memcached.env' + if not os.path.exists(envFile): + wbody = "IP=127.0.0.1\n" + wbody = wbody + "PORT=11211\n" + wbody = wbody + "USER=root\n" + wbody = wbody + "MAXCONN=1024\n" + wbody = wbody + "CACHESIZE=1024\n" + wbody = wbody + "OPTIONS=''\n" + mw.writeFile(envFile, wbody) + + return file_bin + + +def memOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return memOp('start') + + +def stop(): + return memOp('stop') + + +def restart(): + return memOp('restart') + + +def reload(): + return memOp('reload') + + +def runInfo(): + # 获取memcached状态 + import telnetlib + import re + port = getMemPort() + try: + tn = telnetlib.Telnet('127.0.0.1', int(port)) + tn.write(b"stats\n") + tn.write(b"quit\n") + data = tn.read_all() + if type(data) == bytes: + data = data.decode('utf-8') + data = data.replace('STAT', '').replace('END', '').split("\n") + result = {} + res = ['cmd_get', 'get_hits', 'get_misses', 'limit_maxbytes', 'curr_items', 'bytes', + 'evictions', 'limit_maxbytes', 'bytes_written', 'bytes_read', 'curr_connections'] + for d in data: + if len(d) < 3: + continue + t = d.split() + if not t[0] in res: + continue + result[t[0]] = int(t[1]) + result['hit'] = 1 + if result['get_hits'] > 0 and result['cmd_get'] > 0: + result['hit'] = float(result['get_hits']) / \ + float(result['cmd_get']) * 100 + + conf = mw.readFile(getServerDir() + '/memcached.env') + result['bind'] = re.search('IP=(.+)', conf).groups()[0] + result['port'] = int(re.search('PORT=(\\d+)', conf).groups()[0]) + result['maxconn'] = int(re.search('MAXCONN=(\\d+)', conf).groups()[0]) + result['cachesize'] = int( + re.search('CACHESIZE=(\\d+)', conf).groups()[0]) + return mw.getJson(result) + except Exception as e: + return mw.getJson({}) + + +def saveConf(): + + args = getArgs() + data = checkArgs(args, ['ip', 'port', 'maxconn', 'maxsize']) + if not data[0]: + return data[1] + + envFile = getServerDir() + '/memcached.env' + + wbody = "IP=" + args['ip'] + "\n" + wbody = wbody + "PORT=" + args['port'] + "\n" + wbody = wbody + "USER=root\n" + wbody = wbody + "MAXCONN=" + args['maxconn'] + "\n" + wbody = wbody + "CACHESIZE=" + args['maxconn'] + "\n" + wbody = wbody + "OPTIONS=''\n" + mw.writeFile(envFile, wbody) + + restart() + return 'save ok' + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'conf_env': + print(getConfEnv()) + elif func == 'save_conf': + print(saveConf()) + else: + print('error') diff --git a/plugins/memcached/info.json b/plugins/memcached/info.json new file mode 100755 index 000000000..9bfab08dc --- /dev/null +++ b/plugins/memcached/info.json @@ -0,0 +1,18 @@ +{ + "sort": 5, + "ps": "是一个高性能的分布式内存对象缓存系统", + "name": "memcached", + "title": "Memcached", + "shell": "install.sh", + "versions":["1.6"], + "updates":["1.6.38"], + "tip": "soft", + "checks": "server/memcached", + "path":"server/memcached", + "display": 1, + "author": "memcached", + "date": "2017-04-01", + "home": "http://memcached.org/", + "type": 0, + "pid": "2" +} \ No newline at end of file diff --git a/plugins/memcached/init.d/memcached.service.tpl b/plugins/memcached/init.d/memcached.service.tpl new file mode 100644 index 000000000..ba36e0321 --- /dev/null +++ b/plugins/memcached/init.d/memcached.service.tpl @@ -0,0 +1,13 @@ +[Unit] +Description=Free & open source, high-performance, distributed memory object caching system. +After=network.target + +[Service] +Type=forking +EnvironmentFile=-{$SERVER_PATH}/memcached/memcached.env +ExecStart={$SERVER_PATH}/memcached/bin/memcached -d -p $PORT -u $USER -m $CACHESIZE -c $MAXCONN $OPTIONS +ExecReload=/bin/kill -USR2 $MAINPID +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/memcached/init.d/memcached.tpl b/plugins/memcached/init.d/memcached.tpl new file mode 100755 index 000000000..440e7f275 --- /dev/null +++ b/plugins/memcached/init.d/memcached.tpl @@ -0,0 +1,81 @@ +#! /bin/bash +# +# memcached: MemCached Daemon +# +# chkconfig: - 90 25 +# description: MemCached Daemon +# +### BEGIN INIT INFO +# Provides: memcached +# Required-Start: $syslog +# Required-Stop: $syslog +# Should-Start: $local_fs +# Should-Stop: $local_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: memcached - Memory caching daemon +# Description: memcached - Memory caching daemon +### END INIT INFO + +IP=127.0.0.1 +PORT=11211 +USER=root +MAXCONN=1024 +CACHESIZE=64 +OPTIONS="" + +RETVAL=0 +prog="memcached" + +MEM_PATH={$SERVER_PATH}/memcached + +start () { + echo -n $"Starting $prog: " + $MEM_PATH/bin/memcached -d -l $IP -p $PORT -u $USER -m $CACHESIZE -c $MAXCONN -P $MEM_PATH/memcached.pid $OPTIONS + if [ "$?" != 0 ] ; then + echo " failed" + exit 1 + else + echo " done" + fi +} +stop () { + echo -n $"Stopping $prog: " + if [ ! -e $MEM_PATH/$prog.pid ]; then + echo -n $"$prog is not running." + exit 1 + fi + kill `cat $MEM_PATH/memcached.pid` + if [ "$?" != 0 ] ; then + echo " failed" + exit 1 + else + rm -f ${MEM_PATH}/memcached.pid + echo " done" + fi +} + +restart () { + $0 stop + sleep 2 + $0 start +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart|reload) + restart + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|reload}" + exit 1 + ;; +esac + +exit $? diff --git a/plugins/memcached/install.sh b/plugins/memcached/install.sh new file mode 100755 index 000000000..fef20da40 --- /dev/null +++ b/plugins/memcached/install.sh @@ -0,0 +1,75 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sys_os=`uname` +VERSION=1.6.39 + +echo $sys_os + +Install_mem(){ + mkdir -p $serverPath/source + mkdir -p $serverPath/source/memcached + echo '正在安装脚本文件...' + + if [ ! -f $serverPath/source/memcached.tar.gz ];then + wget --no-check-certificate -O $serverPath/source/memcached/memcached.tar.gz https://www.memcached.org/files/memcached-${VERSION}.tar.gz + fi + + cd $serverPath/source/memcached && tar -zxvf memcached.tar.gz + + OPTIONS='' + if [ ${sys_os} == "Darwin" ]; then + LIB_DEPEND_DIR=`brew info libevent | grep /opt/homebrew/Cellar/libevent | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="${OPTIONS} --with-libevent=${LIB_DEPEND_DIR}" + fi + + echo "./configure --prefix=${serverPath}/memcached && make && make install" + cd $serverPath/source/memcached/memcached-${VERSION} + ./configure --prefix=$serverPath/memcached \ + $OPTIONS + + make && make install + + if [ -d $serverPath/memcached ];then + echo '1.6' > $serverPath/memcached/version.pl + echo '安装memcached成功' + + cd ${rootPath} && python3 ${rootPath}/plugins/memcached/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/memcached/index.py initd_install + + rm -rf $serverPath/source/memcached/memcached-${VERSION} + fi +} + +Uninstall_mem() +{ + + if [ -f /usr/lib/systemd/system/memcached.service ];then + systemctl stop memcached + systemctl disable memcached + rm -rf /usr/lib/systemd/system/memcached.service + systemctl daemon-reload + fi + + if [ -f $serverPath/memcached/initd/memcached ];then + $serverPath/memcached/initd/memcached stop + fi + rm -rf $serverPath/memcached + echo '卸载memcached成功' +} + + +# /www/server/memcached/bin/memcached -d -p 11211 -u root -m 100 -c 100 + +action=$1 +if [ "${1}" == 'install' ];then + Install_mem +else + Uninstall_mem +fi diff --git a/plugins/memcached/js/mem.js b/plugins/memcached/js/mem.js new file mode 100755 index 000000000..c781cc8bb --- /dev/null +++ b/plugins/memcached/js/mem.js @@ -0,0 +1,106 @@ + + + +//memcached负载状态 +function memcachedStatus() { + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'memcached', func:'run_info'}, function(data) { + layer.close(loadT); + + if (!data.status){ + showMsg(data.msg, function(){}, null,13000); + return; + } + + var rdata = $.parseJSON(data.data); + if ($.isEmptyObject(rdata)){ + showMsg('memcached服务没有启动!', function(){}, undefined, 3000); + return; + } + var Con = '
                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                  字段当前值说明
                  BindIP' + rdata.bind + '监听IP
                  PORT' + rdata.port + '监听端口
                  CACHESIZE' + rdata.cachesize + ' MB最大缓存容量
                  MAXCONN' + rdata.maxconn + '最大连接数限制
                  curr_connections' + rdata.curr_connections + '当前打开的连接数
                  cmd_get' + rdata.cmd_get + 'GET请求数
                  get_hits' + rdata.get_hits + 'GET命中次数
                  get_misses' + rdata.get_misses + 'GET失败次数
                  hit' + rdata.hit.toFixed(2) + '%GET命中率
                  curr_items' + rdata.curr_items + '当前被缓存的数据行数
                  evictions' + rdata.evictions + '因内存不足而被清理的缓存行数
                  bytes' + toSize(rdata.bytes) + '当前已使用内存
                  bytes_read' + toSize(rdata.bytes_read) + '请求总大小
                  bytes_written' + toSize(rdata.bytes_written) + '发送总大小
                  ' + $(".soft-man-con").html(Con); + },'json'); +} + +//memcached性能调整 +function memcachedCache() { + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'memcached', func:'run_info'}, function(data) { + layer.close(loadT); + + if (!data.status){ + showMsg(data.msg, function(){}, null,13000); + return; + } + + var rdata = $.parseJSON(data.data); + if ($.isEmptyObject(rdata)){ + showMsg('memcached服务没有启动!', function(){}, undefined, 3000); + return; + } + + var memCon = '
                  \ +

                  BindIP监听IP,请勿随意修改

                  \ +

                  PORT监听端口,一般无需修改

                  \ +

                  CACHESIZEMB,缓存大小,建议不要大于512M

                  \ +

                  MAXCONN最大连接数,建议不要大于40960

                  \ +
                  \ +
                  ' + $(".soft-man-con").html(memCon); + },'json'); +} + +//memcached提交配置 +function setMemcachedConf() { + var data = { + ip: $("input[name='membind']").val(), + port: $("input[name='memport']").val(), + cachesize: $("input[name='memcachesize']").val(), + maxconn: $("input[name='memmaxconn']").val() + } + + if (data.ip.split('.').length < 4) { + layer.msg('IP地址格式不正确!', { icon: 2 }); + return; + } + + if (data.port < 1 || data.port > 65535) { + layer.msg('端口范围不正确!', { icon: 2 }); + return; + } + + if (data.cachesize < 8) { + layer.msg('缓存值过小', { icon: 2 }); + return; + } + + if (data.maxconn < 4) { + layer.msg('最大连接数过小', { icon: 2 }); + return; + } + + var loadT = layer.msg('正在保存...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'memcached', func:'save_conf',args:JSON.stringify(data) }, function(rdata) { + layer.close(loadT); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + },'json'); +} \ No newline at end of file diff --git a/plugins/migration_api/ico.png b/plugins/migration_api/ico.png new file mode 100644 index 000000000..f82867608 Binary files /dev/null and b/plugins/migration_api/ico.png differ diff --git a/plugins/migration_api/index.html b/plugins/migration_api/index.html new file mode 100755 index 000000000..6e170ddf1 --- /dev/null +++ b/plugins/migration_api/index.html @@ -0,0 +1,306 @@ + +
                  +
                  +
                  +
                    +
                  • 1

                    填写信息

                  • +
                  • 2

                    检测环境

                  • +
                  • 3

                    选择数据

                  • +
                  • 4

                    一键迁移

                  • +
                  +
                  + +
                  +
                  +
                  + 只需在发送数据服务器安装本软件,请填写『 接收数据服务器 』资料。 + ? +
                  +
                  + 接收数据的面板地址 + +
                  +
                  + 应用ID + + 应用ID +
                  +
                  + 应用密钥 + + 应用密钥 +
                  +
                  + IP白名单 + 必须将本机器IP加入接收数据服务器API的IP白名单,如何添加白名单 +
                  +
                  + +
                  +
                  +
                  +
                  +
                  +
                  +
                  + +
                    +
                    +
                    +
                    +
                    + +
                      +
                      +
                      +
                      +
                      + +
                        +
                        +
                        +
                        +
                        + + +
                        +
                        +
                        +
                        +
                        + +
                        +
                        + + + \ No newline at end of file diff --git a/plugins/migration_api/index.py b/plugins/migration_api/index.py new file mode 100755 index 000000000..5932d3caa --- /dev/null +++ b/plugins/migration_api/index.py @@ -0,0 +1,1133 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import hashlib +import json +import subprocess +import requests + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +class classApi: + __MW_PANEL = 'http://127.0.0.1:7200' + __MW_APP_ID = '' + __MW_APP_SECRET = '' + __VHOST_PATH = '' + + _buff_size = 1024 * 1024 * 2 + + _SPEED_FILE = None + _INFO_FILE = None + _SYNC_INFO = None + + # 如果希望多台面板,可以在实例化对象时,将面板地址与密钥传入 + def __init__(self, mw_panel=None, app_id=None, app_secret=None): + if mw_panel: + self.__MW_PANEL = mw_panel + self.__MW_APP_ID = app_id + self.__MW_APP_SECRET = app_secret + + self._SPEED_FILE = getServerDir() + '/config/speed.json' + self._INFO_FILE = getServerDir() + '/config/sync_info.json' + self._SYNC_INFO = self.get_sync_info(None) + self.__VHOST_PATH = mw.getServerDir() + '/web_conf' + + def post(self, endpoint, request_data, timeout=60): + url = self.__MW_PANEL + endpoint + post_data = requests.post(url, data=request_data, headers={ + 'app-id':self.__MW_APP_ID, + 'app-secret':self.__MW_APP_SECRET + }) + try: + return post_data.json() + except Exception as e: + return post_data.text + + def makeSyncInfo(self, args): + data = {} + sites = [] + for i in args['sites']: + # print('ss', i) + if i == '': + continue + t = {'name': i} + sites.append(t) + data['sites'] = sites + + databases = [] + for i in args['databases']: + # print('db:', i) + if i == '': + continue + t = {'name': i} + databases.append(t) + data['databases'] = databases + + + plugin = [] + for i in args['plugin']: + # print('db:', i) + if i == '': + continue + t = {'name': i} + plugin.append(t) + data['plugin'] = plugin + return data + + def fock_process(self, args): + log_file = getServerDir() + '/sync.log' + log_file_error = getServerDir() + '/sync_error.log' + + if os.path.exists(log_file_error): + os.remove(log_file_error) + if os.path.exists(log_file): + os.remove(log_file) + + plugins_dir = mw.getServerDir() + '/mdserver-web' + exe = "cd {0} && source bin/activate && python3 plugins/migration_api/index.py bg_process &>{1} &".format( + plugins_dir, log_file_error) + # mw.execShell(exe) + # os.system(exe) + subprocess.Popen(exe, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + time.sleep(1) + # 检查是否执行成功 + if not getPid(): + return mw.returnJson(False, '创建进程失败!
                        {}'.format(mw.readFile(log_file_error))) + return mw.returnJson(True, "迁移进程创建成功!") + + def set_sync_info(self, args): + # 设置要被迁移的网站、数据库 + sync_info = self.makeSyncInfo(args) + sync_info['total'] = 0 + sync_info['speed'] = 0 + for i in range(len(sync_info['sites'])): + sync_info['sites'][i]['error'] = '' + sync_info['sites'][i]['state'] = 0 + sync_info['total'] += 1 + for i in range(len(sync_info['databases'])): + sync_info['databases'][i]['error'] = '' + sync_info['databases'][i]['state'] = 0 + sync_info['total'] += 1 + mw.writeFile(self._INFO_FILE, json.dumps(sync_info)) + self.fock_process(None) + return mw.returnJson(True, '设置成功!') + + def get_sync_info(self, args): + # 获取要被迁移的网站、数据库 + if not os.path.exists(self._INFO_FILE): + return mw.returnJson(False, '迁移信息不存在!') + sync_info = json.loads(mw.readFile(self._INFO_FILE)) + if not args: + return sync_info + result = [] + for i in sync_info['sites']: + i['type'] = "网站" + result.append(i) + for i in sync_info['databases']: + i['type'] = "数据库" + result.append(i) + return result + + def write_speed(self, key, value): + # 写进度 + if os.path.exists(self._SPEED_FILE): + speed_info = json.loads(mw.readFile(self._SPEED_FILE)) + else: + speed_info = {"time": int(time.time()), "size": 0, "used": 0, "total_size": 0, + "speed": 0, "action": "等待中", "done": "等待中", "end_time": int(time.time())} + if not key in speed_info: + speed_info[key] = 0 + if key == 'total_size': + speed_info[key] += value + else: + speed_info[key] = value + mw.writeFile(self._SPEED_FILE, json.dumps(speed_info)) + + # 设置文件权限 + def set_mode(self, filename, mode): + if not os.path.exists(filename): + return False + mode = int(str(mode), 8) + os.chmod(filename, mode) + return True + + def send(self, url, args, timeout=600): + try: + result = self.post(url, args, timeout) + return result + except Exception as e: + return str(e) + + def sendPlugins(self, name, func, args, timeout=36000): + url = '/plugins/run' + + data = {} + data['name'] = name + data['func'] = func + data['args'] = json.dumps(args).replace(": ", ":").replace(", ", ",") + r = self.send(url, data, timeout) + if r['status']: + return json.loads(r['data']) + return r + + def get_mode_and_user(self, path): + '''取文件或目录权限信息''' + data = {} + if not os.path.exists(path): + return None + stat = os.stat(path) + data['mode'] = str(oct(stat.st_mode)[-3:]) + try: + data['user'] = pwd.getpwuid(stat.st_uid).pw_name + except: + data['user'] = str(stat.st_uid) + return data + + def error(self, error_msg, is_exit=False): + # 发生错误 + write_log("=" * 50) + write_log("|-发生时间: {}".format(mw.formatDate())) + write_log("|-错误信息: {}".format(error_msg)) + if is_exit: + write_log("|-处理结果: 终止迁移任务") + sys.exit(0) + write_log("|-处理结果: 忽略错误, 继续执行") + + def upload_file(self, sfile, dfile, chmod=None): + # 上传文件 + if not os.path.exists(sfile): + write_log("|-指定目录不存在{}".format(sfile)) + return False + pdata = {} + pdata['name'] = os.path.basename(dfile) + pdata['path'] = os.path.dirname(dfile) + pdata['size'] = os.path.getsize(sfile) + pdata['start'] = 0 + if chmod: + mode_user = self.get_mode_and_user(os.path.dirname(sfile)) + pdata['dir_mode'] = mode_user['mode'] + ',' + mode_user['user'] + mode_user = self.get_mode_and_user(sfile) + pdata['file_mode'] = mode_user['mode'] + ',' + mode_user['user'] + f = open(sfile, 'rb') + + return self.send_file(pdata, f) + + def close_sync(self, args): + # 取消迁移 + mw.execShell("kill -9 {}".format(self.get_pid())) + mw.execShell("kill -9 $(ps aux|grep index.py|grep -v grep|awk '{print $2}')") + # 删除迁移配置 + time.sleep(1) + if os.path.exists(self._INFO_FILE): + os.remove(self._INFO_FILE) + if os.path.exists(self._SPEED_FILE): + os.remove(self._SPEED_FILE) + return mw.returnJson(True, '已取消迁移任务!') + + def send_file(self, pdata, f): + success_num = 0 # 连续发送成功次数 + max_buff_size = int(1024 * 1024 * 2) # 最大分片大小 + min_buff_size = int(1024 * 32) # 最小分片大小 + err_num = 0 # 连接错误计数 + max_err_num = 10 # 最大连接错误重试次数 + up_buff_num = 5 # 调整分片的触发次数 + timeout = 60 # 每次发送分片的超时时间 + split_num = 0 + split_done = 0 + total_time = 0 + self.write_speed('done', "正在传输文件") + self.write_speed('size', pdata['size']) + self.write_speed('used', 0) + self.write_speed('speed', 0) + write_log("|-上传文件[{}], 总大小:{}, 当前分片大小为:{}".format(pdata['name'], toSize(pdata['size']), toSize(self._buff_size))) + while True: + buff_size = self._buff_size + max_buff = int(pdata['size'] - pdata['start']) + if max_buff < buff_size: + buff_size = max_buff + files = {"blob": f.read(buff_size)} + start_time = time.time() + + try: + url = self.__MW_PANEL + '/files/upload_segment' + res = requests.post(url, data=pdata, files=files, headers={ + 'app-id':self.__MW_APP_ID, + 'app-secret':self.__MW_APP_SECRET + },timeout=30000) + + success_num += 1 + err_num = 0 + # 连续5次分片发送成功的情况下尝试调整分片大小, 以提升上传效率 + if success_num > up_buff_num and self._buff_size < max_buff_size: + self._buff_size = int(self._buff_size * 2) + success_num = up_buff_num - 3 # 如再顺利发送3次则继续提升分片大小 + if self._buff_size > max_buff_size: + self._buff_size = max_buff_size + write_log("|-发送顺利, 尝试调整分片大小为: {}".format(toSize(self._buff_size))) + except Exception as e: + times = time.time() - start_time + total_time += times + ex = str(e) + if ex.find('Read timed out') != -1 or ex.find('Connection aborted') != -1: + # 发生超时的时候尝试调整分片大小, 以确保网络情况不好的时候能继续上传 + self._buff_size = int(self._buff_size / 2) + if self._buff_size < min_buff_size: + self._buff_size = min_buff_size + success_num = 0 + write_log( + "|-发送超时, 尝试调整分片大小为: {}".format(toSize(self._buff_size))) + continue + + # 如果连接超时 + if ex.find('Max retries exceeded with') != -1 and err_num <= max_err_num: + err_num += 1 + write_log("|-连接超时, 第{}次重试".format(err_num)) + time.sleep(1) + continue + + # 超过重试次数 + write_log("|-上传失败, 跳过本次上传任务") + write_log(mw.getTracebackInfo()) + return False + + result = res.json() + times = time.time() - start_time + total_time += times + + if result['status'] and result['msg'] == 'size': + if result == split_done: + split_num += 1 + else: + split_num = 0 + split_done = result + if split_num > 10: + write_log("|-上传失败, 跳过本次上传任务") + return False + if result['data'] > pdata['size']: + write_log("|-上传失败, 跳过本次上传任务") + return False + self.write_speed('used', result) + self.write_speed('speed', int(buff_size / times)) + write_log("|-已上传 {},上传速度 {}/s, 共用时 {}分{:.2f}秒, {:.2f}%".format(toSize(result['data']), toSize( + buff_size / times), int(total_time // 60), total_time % 60, (float(result['data']) / float(pdata['size']) * 100))) + pdata['start'] = result['data'] # 设置断点 + else: + if not result['status']: # 如果服务器响应上传失败 + write_log(result['msg']) + return False + + if pdata['size']: + self.write_speed('used', pdata['size']) + self.write_speed('speed', int(buff_size / times)) + write_log("|-已上传 {},上传速度 {}/s, 共用时 {}分{:.2f}秒, {:.2f}%".format(toSize(float(pdata['size'])), toSize( + buff_size / times), int(total_time // 60), total_time % 60, (float(pdata['size']) / float(pdata['size']) * 100))) + break + + self.write_speed('total_size', pdata['size']) + self.write_speed('end_time', int(time.time())) + write_log("|-总耗时:{} 分钟, {:.2f} 秒, 平均速度:{}/s".format(int(total_time // 60), total_time % 60, toSize(pdata['size'] / total_time))) + return True + + def send_list(self, s_files): + # 发送文件列表 + for f in s_files: + if not os.path.exists(f[0]): + continue + self.send_file_list(f[0], f[0]) + + def send_file_list(self, spath, dpath): + + if os.path.islink(spath): + dpath = os.readlink(spath) + # mw.buildSoftLink(spath, dpath, True) + self.send('/files/exec_shell', + {"shell": 'ln -sf "' + spath + '" "' + dpath + '"', "path": "/www"}, 30) + return True + + if not os.path.isdir(spath): + return self.upload_file(spath, dpath, True) + + # 创建目录 + self.send('/files/create_dir', {"path": dpath}) + + backup_path = mw.getFatherDir() + '/backup' + if not os.path.exists(backup_path): + os.makedirs(backup_path, 384) + + zip_file = backup_path + \ + "/psync_tmp_{}.tar.gz".format(os.path.basename(spath)) + zip_dst = mw.getPanelDir() + '/tmp/psync_tmp_{}.tar.gz'.format( + os.path.basename(dpath)) + write_log("|-正在压缩目录[{}]...".format(spath)) + self.write_speed('done', '正在压缩') + + mw.execShell("cd {} && tar zcvf {} ./ > /dev/null".format(spath, zip_file)) + if not os.path.exists(zip_file): + self.error("目录[{}]打包失败!".format(spath)) + return False + + self.set_mode(zip_file, 600) + if not self.upload_file(zip_file, zip_dst, True): + self.error("目录[{}]上传失败!".format(spath)) + if os.path.exists(zip_file): + os.remove(zip_file) + return False + + if os.path.exists(zip_file): + os.remove(zip_file) + write_log("|-正在解压文件到目录[{}]...".format(dpath)) + self.write_speed('done', '正在解压') + + self.send('/files/uncompress',{"sfile": zip_dst, "dfile": dpath, 'path': dpath}) + self.send('/files/exec_shell',{"shell": "rm -f " + zip_dst, "path": "/www"}, 30) + return True + + def state(self, stype, index, state, error=''): + # 设置状态 + self._SYNC_INFO[stype][index]['state'] = state + self._SYNC_INFO[stype][index]['error'] = error + if self._SYNC_INFO[stype][index]['state'] == 1: + self._SYNC_INFO['speed'] += 1 + self.save() + + def save(self): + # 保存迁移配置 + mw.writeFile(self._INFO_FILE, json.dumps(self._SYNC_INFO)) + + def format_domain(self, domain): + # 格式化域名 + domains = [] + for d in domain: + domains.append("{}:{}".format(d['name'], d['port'])) + return domains + + def create_site(self, siteInfo, index): + pdata = {} + domains = self.format_domain(siteInfo['domain']) + + pdata['webinfo'] = json.dumps( + {"domain": siteInfo['name'], "domainlist": domains, "count": len(domains)}) + pdata['ps'] = siteInfo['ps'] + pdata['path'] = siteInfo['path'] + pdata['type'] = 'PHP' + pdata['version'] = '00' + pdata['type_id'] = '0' + pdata['port'] = siteInfo['port'] + if not pdata['port']: + pdata['port'] = 80 + + result = self.send('/site/add', pdata) + if not result['status']: + err_msg = '站点[{}]创建失败, {}'.format(siteInfo['name'], result['msg']) + self.state('sites', index, -1, err_msg) + self.error(err_msg) + return False + return True + + def send_site(self, siteInfo, index): + if not os.path.exists(siteInfo['path']): + err_msg = "网站根目录[{}]不存在,跳过!".format(siteInfo['path']) + self.state('sites', index, -1, err_msg) + self.error(err_msg) + return False + + self.create_site(siteInfo, index) + # if not self.create_site(siteInfo, index): + # return False + + s_files = [ + [self.__VHOST_PATH + + '/nginx/vhost/{}.conf'.format(siteInfo['name']), "网站配置文件"], + [self.__VHOST_PATH + + '/nginx/pass/{}.conf'.format(siteInfo['name']), "PASS"], + [self.__VHOST_PATH + + '/nginx/rewrite/{}.conf'.format(siteInfo['name']), "伪静态配置"], + [self.__VHOST_PATH + + '/nginx/redirect/{}'.format(siteInfo['name']), "重定向配置"], + [self.__VHOST_PATH + + '/nginx/proxy/{}'.format(siteInfo['name']), "反向代理配置"], + [self.__VHOST_PATH + + "/letsencrypt/{}".format(siteInfo['name']), "网站[LETS]SSL证书"], + [self.__VHOST_PATH + "/ssl/" + siteInfo['name'], "网站SSL目录"], + ] + + if not mw.isAppleSystem(): + acme_dir = mw.getAcmeDomainDir(siteInfo['name']) + s_files.append([acme_dir, "网站[ACME]SSL证书"]) + + s_files.append( + [self.__VHOST_PATH + "/ssl/{}/fullchain.pem".format(siteInfo['name']), "网站SSL[fullchain]证书"]) + s_files.append( + [self.__VHOST_PATH + "/ssl/{}/privkey.pem".format(siteInfo['name']), "网站SSL[privkey]证书"]) + + self.send_list(s_files) + + if not self.send_file_list(siteInfo['path'], siteInfo['path']): + self.state('sites', index, -1, '数据传输失败!') + return False + + def sync_site(self): + data = getCfgData() + sites = self._SYNC_INFO['sites'] + for i in range(len(sites)): + + site_name = sites[i]['name'] + try: + self.state('sites', i, 1) + siteInfo = mw.M('sites').where('name=?', (site_name,)).field( + 'id,name,path,ps,status,edate,add_time').find() + + if not siteInfo: + err_msg = "指定站点[{}]不存在!".format(site_name) + self.state('sites', i, -1, err_msg) + self.error(err_msg) + continue + site_id = siteInfo['id'] + + siteInfo['port'] = mw.M('domain').where( + 'pid=? and name=?', (site_id, site_name,)).getField('port') + + siteInfo['domain'] = mw.M('domain').where( + 'pid=? and name!=?', (site_id, site_name)).field('name,port').select() + + siteInfo['binding'] = mw.M('binding').where( + 'pid=?', (id,)).field('domain,path,port').select() + + if self.send_site(siteInfo, i): + self.state('sites', i, 2) + write_log("=" * 50) + except Exception as e: + self.error(mw.getTracebackInfo()) + + def getConf(self, mtype='mysql'): + path = mw.getServerDir() + '/' + mtype + '/etc/my.cnf' + return path + + def getSocketFile(self, mtype='mysql'): + file = self.getConf(mtype) + content = mw.readFile(file) + rep = r'socket\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + def getDbPort(self, mtype='mysql'): + file = self.getConf(mtype) + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + def getDbConn(self, mtype='mysql', db='databases'): + my_db_pos = mw.getServerDir() + '/' + mtype + conn = mw.M(db).dbPos(my_db_pos, 'mysql') + return conn + + def getMyConn(self, mtype='mysql'): + # pymysql + db = mw.getMyORM() + + db.setPort(self.getDbPort(mtype)) + db.setSocket(self.getSocketFile(mtype)) + pwd = self.getDbConn(mtype, 'config').where('id=?', (1,)).getField('mysql_root') + db.setPwd(pwd) + return db + + def getDbList(self): + conn = self.getDbConn() + alist = conn.field('id,name,username,password,ps').order("id desc").select() + return alist + + def getDbInfo(self, name): + conn = self.getDbConn() + info = conn.field('id,name,username,password,ps').where('name=?', (name,)).find() + return info + + def mapToList(self, map_obj): + # map to list + try: + if type(map_obj) != list and type(map_obj) != str: + map_obj = list(map_obj) + return map_obj + except: + return [] + + # 取数据库权限 + def getDatabaseAccess(self, name): + return '127.0.0.1' + try: + conn = self.getMyConn() + users = conn.query( + "select Host from mysql.user where User='" + name + "' AND Host!='localhost'") + users = self.mapToList(users) + if len(users) < 1: + return "127.0.0.1" + accs = [] + for c in users: + accs.append(c[0]) + userStr = ','.join(accs) + return userStr + except: + return '127.0.0.1' + + def isSqlError(self, mysqlMsg): + # 检测数据库执行错误 + mysqlMsg = str(mysqlMsg) + if "MySQLdb" in mysqlMsg: + return mw.returnData(False, 'DATABASE_ERR_MYSQLDB') + if "2002," in mysqlMsg or '2003,' in mysqlMsg: + return mw.returnData(False, 'DATABASE_ERR_CONNECT') + if "using password:" in mysqlMsg: + return mw.returnData(False, 'DATABASE_ERR_PASS') + if "Connection refused" in mysqlMsg: + return mw.returnData(False, 'DATABASE_ERR_CONNECT') + if "1133" in mysqlMsg: + return mw.returnData(False, 'DATABASE_ERR_NOT_EXISTS') + return None + + def getDatabaseCharacter(self, db_name): + try: + conn = self.getMyConn() + tmp = conn.query("show create database `%s`" % db_name.strip(), ()) + # print(tmp) + c_type = str(re.findall(r"SET\s+([\w\d-]+)\s", tmp[0][1])[0]) + c_types = ['utf8', 'utf-8', 'gbk', 'big5', 'utf8mb4'] + if not c_type.lower() in c_types: + return 'utf8' + return c_type + except Exception as e: + # print(str(e)) + return 'utf8' + + # 创建远程数据库 + def create_database(self, dbInfo, index): + pdata = {} + pdata['name'] = dbInfo['name'] + pdata['db_user'] = dbInfo['username'] + pdata['password'] = dbInfo['password'] + pdata['dataAccess'] = dbInfo['accept'] + if dbInfo['accept'] != '%' and dbInfo['accept'] != '127.0.0.1': + pdata['dataAccess'] = '127.0.0.1' + pdata['address'] = dbInfo['accept'] + pdata['ps'] = dbInfo['ps'] + pdata['codeing'] = dbInfo['character'] + + result = self.sendPlugins('mysql', 'add_db', pdata) + if result['status']: + return True + err_msg = '数据库[{}]创建失败,{}'.format(dbInfo['name'], result['msg']) + self.state('databases', index, -1, err_msg) + self.error(err_msg) + return False + + # 数据库密码处理 + def myPass(self, act, root): + # conf_file = '/etc/my.cnf' + conf_file = self.getConf('mysql') + mw.execShell("sed -i '/user=root/d' {}".format(conf_file)) + mw.execShell("sed -i '/password=/d' {}".format(conf_file)) + if act: + mycnf = mw.readFile(conf_file) + src_dump = "[mysqldump]\n" + sub_dump = src_dump + "user=root\npassword=\"{}\"\n".format(root) + if not mycnf: + return False + mycnf = mycnf.replace(src_dump, sub_dump) + if len(mycnf) > 100: + mw.writeFile(conf_file, mycnf) + return True + return True + + def recognizeDbMode(self): + conf = self.getConf('mysql') + con = mw.readFile(conf) + + path = mw.getServerDir() + '/mysql' + rep = r"!include %s/(.*)?\.cnf" % (path + "/etc/mode",) + mode = 'none' + try: + data = re.findall(rep, con, re.M) + mode = data[0] + except Exception as e: + pass + return mode + + def export_database(self, name, index): + self.write_speed('done', '正在导出数据库') + write_log("|-正在导出数据库[{}]...".format(name)) + conn = self.getMyConn() + result = conn.execute("show databases") + isError = self.isSqlError(result) + if isError: + err_msg = '数据库[{}]导出失败,{}!'.format(name, isError['msg']) + self.state('databases', index, -1, err_msg) + self.error(err_msg) + return None + + root = self.getDbConn('mysql', 'config').where( + 'id=?', (1,)).getField('mysql_root') + + backup_path = mw.getFatherDir() + '/backup' + if not os.path.exists(backup_path): + os.makedirs(backup_path, 384) + + backup_name = backup_path + '/psync_import.sql.gz' + if os.path.exists(backup_name): + os.remove(backup_name) + + root_dir = mw.getServerDir() + '/mysql' + my_cnf = self.getConf('mysql') + + mode = self.recognizeDbMode() + gtid_option = '' + if mode == 'gtid': + gtid_option = ' --set-gtid-purged=off ' + + self.myPass(True, root) + cmd = root_dir + "/bin/mysqldump --defaults-file=" + my_cnf + " --default-character-set=" + \ + self.getDatabaseCharacter( + name) + gtid_option + " --force --opt \"" + name + "\" | gzip > " + backup_name + # print(cmd) + mw.execShell(cmd) + + self.myPass(False, root) + if not os.path.exists(backup_name) or os.path.getsize(backup_name) < 30: + if os.path.exists(backup_name): + os.remove(backup_name) + err_msg = '数据库[{}]导出失败!'.format(name) + self.state('databases', index, -1, err_msg) + self.error(err_msg) + write_log("失败") + return None + write_log("成功") + return backup_name + + def send_database(self, dbInfo, index): + # print(dbInfo) + # 创建远程库 + # if not self.create_database(dbInfo, index): + # return False + + self.create_database(dbInfo, index) + filename = self.export_database(dbInfo['name'], index) + if not filename: + return False + + db_dir = '/www/backup/import' + new_db_name = 'psync_import_{}.sql.gz'.format(dbInfo['name']) + upload_file = db_dir + '/' + new_db_name + self.send('/files/exec_shell', {"shell": "rm -f " + upload_file, "path": "/www"}, 30) + + if self.upload_file(filename, upload_file): + self.write_speed('done', '正在导入数据库') + write_log("|-正在导入数据库{}...".format(dbInfo['name'])) + + self.sendPlugins('mysql', 'import_db_external', {"file": new_db_name, "name": dbInfo['name']}) + self.send('/files/exec_shell',{"shell": "rm -f " + upload_file, "path": "/www"}, 30) + return True + self.state('databases', index, -1, "数据传输失败") + return False + + def sync_database(self): + data = getCfgData() + + databases = self._SYNC_INFO['databases'] + for i in range(len(databases)): + db = databases[i]['name'] + try: + self.state('databases', i, 1) + sp_msg = "|-迁移数据库: [{}]".format(db) + self.write_speed('action', sp_msg) + write_log(sp_msg) + dbInfo = self.getDbInfo(db) + dbInfo['accept'] = self.getDatabaseAccess(db) + dbInfo['character'] = self.getDatabaseCharacter(db) + # print(dbInfo) + if self.send_database(dbInfo, i): + self.state('databases', i, 2) + write_log("=" * 50) + except: + self.error(mw.getTracebackInfo()) + + + def print_directory_tree(self, directory, prefix=""): + """递归打印目录树结构""" + items = sorted(os.listdir(directory)) + for i, item in enumerate(items): + item_path = os.path.join(directory, item) + is_last = i == len(items) - 1 + + if os.path.isdir(item_path): + print(f"{prefix}{'└── ' if is_last else '├── '}{item}/") + extension = " " if is_last else "│ " + self.print_directory_tree(item_path, prefix + extension) + else: + print(f"{prefix}{'└── ' if is_last else '├── '}{item}") + + def upload_dir(self, src, dst): + """递归打印目录树结构""" + items = sorted(os.listdir(src)) + for i, item in enumerate(items): + item_path = os.path.join(src, item) + is_last = i == len(items) - 1 + + if os.path.isdir(item_path): + # print("dir:",item_path, dst+'/'+item) + self.upload_dir(item_path, dst+'/'+item) + else: + # print("file:",item_path, dst+'/'+item) + self.upload_file(item_path, dst+'/'+item) + + def sync_plugin_webssh(self): + name = "webssh" + webssh_dir = mw.getServerDir()+'/'+name + + # 递归遍历所有文件和目录并打印 + print(f"\n目录树结构: {webssh_dir}") + print("=" * 50) + if os.path.exists(webssh_dir): + # self.print_directory_tree(webssh_dir) + self.upload_dir(webssh_dir, "/www/server/"+name) + else: + print(f"目录不存在: {webssh_dir}") + print("=" * 50) + + + def sync_plugin(self): + data = getCfgData() + plugin = self._SYNC_INFO['plugin'] + for x in plugin: + if x['name'] == 'webssh': + self.sync_plugin_webssh() + return True + + + def run(self): + # 开始迁移 + # self.upload_file("/tmp/mysql-boost-5.7.39.tar.gz", "/tmp/mysql-boost-5.7.39.tar.gz") + + # self.sync_other() + self.sync_site() + self.sync_database() + # self.sync_path() + self.sync_plugin() + self.write_speed('action', 'True') + write_log('|-所有项目迁移完成!') + + +# 字节单位转换 +def toSize(size): + d = ('b', 'KB', 'MB', 'GB', 'TB') + s = d[0] + for b in d: + if size < 1024: + return ("%.2f" % size) + ' ' + b + size = size / 1024 + s = b + return ("%.2f" % size) + ' ' + b + + +def getPluginName(): + return 'migration_api' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/ma.cfg" + return path + + +def getCfgData(): + path = getConf() + if not os.path.exists(path): + mw.writeFile(path, "{}") + + t = mw.readFile(path) + return json.loads(t) + + +def writeConf(data): + path = getConf() + mw.writeFile(path, json.dumps(data)) + return True + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + # print(args) + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + + for i in range(len(args)): + # print(args[i]) + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + path = getServerDir() + '/config' + if not os.path.exists(path): + os.makedirs(path) + return 'start' + + +def initDreplace(): + return 'ok' + + +def getStepOneData(): + data = getCfgData() + return mw.returnJson(True, 'ok', data) + + +def stepOne(): + args = getArgs() + data = checkArgs(args, ['url', 'app_id','app_secret']) + if not data[0]: + return data[1] + + url = args['url'] + app_id = args['app_id'] + app_secret = args['app_secret'] + + speed_file = getServerDir() + '/config/speed.json' + if os.path.exists(speed_file): + os.remove(speed_file) + + sync_file = getServerDir() + '/config/sync_info.json' + if os.path.exists(sync_file): + os.remove(sync_file) + + api = classApi(url, app_id, app_secret) + # api = + # classApi('http://127.0.0.1:7200','HfJNKGP5RPqGvhIOyrwpXG4A2fTjSh9B') + rdata = api.send('/task/count', {}) + if not rdata['status']: + return mw.returnJson(False, rdata['msg']) + data = getCfgData() + data['url'] = url + data['app_id'] = app_id + data['app_secret'] = app_secret + writeConf(data) + + return mw.returnJson(True, '验证成功') + + +# 获取本地服务器和环境配置 +def get_src_config(args): + data = {} + data['status'] = True + sdir = mw.getServerDir() + + data['webserver'] = '未安装' + if os.path.exists(sdir + '/openresty/nginx/sbin/nginx'): + data['webserver'] = 'OpenResty' + data['php'] = [] + phpversions = ['52', '53', '54', '55', '56', '70', '71', + '72', '73', '74', '80', '81', '82', '83', '84', '85'] + phpPath = sdir + '/php/' + for pv in phpversions: + if not os.path.exists(phpPath + pv + '/bin/php'): + continue + data['php'].append(pv) + data['mysql'] = False + if os.path.exists(sdir + '/mysql/bin/mysql'): + data['mysql'] = True + import psutil + try: + diskInfo = psutil.disk_usage('/www') + except: + diskInfo = psutil.disk_usage('/') + + data['disk'] = mw.toSize(diskInfo[2]) + return data + + +def get_dst_config(args): + + data = getCfgData() + api = classApi(data['url'], data['app_id'], data['app_secret']) + disk = api.send('/system/disk_info', {}) + info = api.send('/system/get_env_info', {}) + + # print(disk) + # print(info) + result = info['data'] + result['disk'] = disk['data'] + return result + + +def stepTwo(): + data = {} + data['local'] = get_src_config(None) + data['remote'] = get_dst_config(None) + return mw.returnJson(True, '获取成功!', data) + + +def get_src_info(args): + # 获取本地服务器网站、数据库. + data = {} + data['sites'] = mw.M('sites').field("id,name,path,ps,status,add_time").order("id desc").select() + + my_db_pos = mw.getServerDir() + '/mysql' + conn = mw.M('databases').dbPos(my_db_pos, 'mysql') + data['databases'] = conn.field('id,name,ps').order("id desc").select() + data['plugin'] = [] + + plugin_webssh_name = "webssh" + webssh_dir = mw.getServerDir() + '/'+plugin_webssh_name + if os.path.exists(webssh_dir): + data['plugin'].append(plugin_webssh_name) + + return data + + +def stepThree(): + data = get_src_info(None) + return mw.returnJson(True, '同步数据,获取成功!', data) + + +def getPid(): + cmd = "ps aux|grep 'plugins/migration_api/index.py bg_process' |grep -v grep|awk '{print $2}'|xargs" + result = mw.execShell(cmd)[0].strip() + if not result: + return None + return result + + +def write_log(log_str): + log_file = getServerDir() + '/sync.log' + f = open(log_file, 'ab+') + log_str += "\n" + if __name__ == '__main__': + print(log_str) + f.write(log_str.encode('utf-8')) + f.close() + return True + + +def bgProcessRun(): + data = getCfgData() + + # demo_url = 'http://127.0.0.1:7200' + # demo_key = 'HfJNKGP5RPqGvhIOyrwpXG4A2fTjSh9B' + # api = classApi(demo_url, demo_key) + api = classApi(data['url'], data['app_id'], data['app_secret']) + api.run() + return '' + + +def stepFour(): + if getPid(): + return mw.returnJson(True, '正在运行中..') + args = getArgs() + data = checkArgs(args, ['sites', 'databases']) + if not data[0]: + return data[1] + + sites = args['sites'] + databases = args['databases'] + plugin = args['plugin'] + + data = getCfgData() + args = { + 'sites': sites.strip(',').split(','), + 'databases': databases.strip(',').split(','), + 'plugin': plugin.strip(',').split(',') + } + api = classApi(data['url'], data['app_id'],data['app_secret']) + return api.set_sync_info(args) + + +def get_speed_data(): + path = getServerDir() + '/config/speed.json' + data = mw.readFile(path) + return json.loads(data) + + +def getSpeed(): + # 取迁移进度 + path = getServerDir() + '/config/speed.json' + if not os.path.exists(path): + return mw.returnJson(False, '正在准备..') + + try: + speed_info = json.loads(mw.readFile(path)) + except Exception as e: + return mw.returnJson(True, str(e)+'...') + data = getCfgData() + api = classApi(data['url'], data['app_id'], data['app_secret']) + sync_info = api.get_sync_info(None) + speed_info['all_total'] = sync_info['total'] + speed_info['all_speed'] = sync_info['speed'] + speed_info['total_time'] = speed_info['end_time'] - speed_info['time'] + speed_info['total_time'] = str(int(speed_info['total_time'] // 60)) + "分" + str(int(speed_info['total_time'] % 60)) + "秒" + log_file = getServerDir() + '/sync.log' + speed_info['log'] = mw.execShell("tail -n 10 {}".format(log_file))[0] + speed_info['log_file'] = log_file + return mw.returnJson(True, 'ok', speed_info) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'get_conf': + print(getStepOneData()) + elif func == 'step_one': + print(stepOne()) + elif func == 'step_two': + print(stepTwo()) + elif func == 'step_three': + print(stepThree()) + elif func == 'step_four': + print(stepFour()) + elif func == 'bg_process': + print(bgProcessRun()) + elif func == 'get_speed': + print(getSpeed()) + else: + print('error') diff --git a/plugins/migration_api/info.json b/plugins/migration_api/info.json new file mode 100755 index 000000000..8d115c02c --- /dev/null +++ b/plugins/migration_api/info.json @@ -0,0 +1,18 @@ +{ + "sort": 7, + "ps": "一键迁移,仅网站数据和MySQL数据(仅支持源码安装软件)[暂时不可用]", + "name": "migration_api", + "title": "一键迁移API", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "checks": "server/migration_api", + "path":"server/migration_api", + "display": 1, + "author": "midoks", + "date": "2022-01-17", + "home": "https://github.com/midoks/mdserver-web", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/migration_api/install.sh b/plugins/migration_api/install.sh new file mode 100755 index 000000000..3be897541 --- /dev/null +++ b/plugins/migration_api/install.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + + +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/migration_api/index.py bg_process + +VERSION=1.0 + +Install_App(){ + mkdir -p $serverPath/migration_api + echo "${VERSION}" > $serverPath/migration_api/version.pl + echo '正在安装脚本文件...' +} + +Uninstall_App() +{ + rm -rf $serverPath/migration_api +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/migration_api/js/app.js b/plugins/migration_api/js/app.js new file mode 100755 index 000000000..485a96f84 --- /dev/null +++ b/plugins/migration_api/js/app.js @@ -0,0 +1,358 @@ + +function maPostNoMsg(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + $.post('/plugins/run', {name:'migration_api', func:method, args:_args}, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function maPost(method,args,callback, msg = '正在获取...'){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg(msg, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'migration_api', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function maAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'migration_api', func:method, args:_args}); +} + + +function maPostCallbak(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'migration_api'; + req_data['func'] = method; + args['version'] = '1.0'; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function selectProgress(val){ + $('.step_head li').removeClass('active'); + $('.step_head li').each(function(){ + var v = $(this).find('span').text(); + if (val == v){ + $(this).addClass('active'); + } + }); +} + +function initStep1(){ + var url = $('input[name="sync_url"]').val(); + var app_id = $('input[name="sync_app_id"]').val(); + var app_secret = $('input[name="sync_app_secret"]').val(); + maPost('step_one',{'url':url,'app_id':app_id,'app_secret':app_secret}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + initStep2(); + } + },{ icon: rdata.status ? 1 : 2 }); + },'API校验中...'); +} + +function initStep2(){ + maPost('step_two',{}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + console.log(rdata); + showMsg(rdata.msg,function(){ + if (rdata.status){ + selectProgress(2); + $('.psync_info').hide(); + + var info = rdata.data; + + var body = '
                        \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                        服务当前服务器远程服务器
                        网站服务'+info['local']['webserver']+''+info['remote']['webserver']+'
                        安装MySQL'+(info['local']['mysql']?'是':'否')+''+(info['remote']['mysql']?'是':'否')+'
                        安装PHP'+(info['local']['php'].join('/'))+''+(info['remote']['php'].join('/')) +'
                        可用磁盘'+info['local']['disk']+''+info['remote']['disk'][0]['size'][0]+'
                        \ +
                        '; + body += '
                        \ + \ + \ + \ +
                        '; + + $('.psync_path').html(body); + $('.psync_path').show(); + } + },{ icon: rdata.status ? 1 : 2 }); + },'检测环境中...'); +} + +function initStep3(){ + maPost('step_three',{}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + selectProgress(3); + var pdata = rdata.data; + var site_li = ''; + for (var i = 0; i < pdata.sites.length; i++) { + site_li+='
                      • \ + \ +
                      • '; + } + + $('#sites_li').html(site_li); + + + var db_li = ''; + for (var i = 0; i < pdata.databases.length; i++) { + db_li+='
                      • \ + \ +
                      • '; + } + $('#db_li').html(db_li); + + + var plugin_li = ''; + for (var i = 0; i < pdata.plugin.length; i++) { + plugin_li+='
                      • \ + \ +
                      • '; + } + $('#plugin_li').html(plugin_li); + + $('.psync_path').hide(); + $('.psync_data').show(); + } + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function renderMigrationProgress(){ + maPostNoMsg('get_speed',{}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + // console.log('speed:',rdata.data); + if (rdata.status){ + if (rdata['data']['action'] == 'True'){ + var end = '
                        \ +
                        \ +

                        数据迁移完成,请务必检查数据完整性!

                        \ +

                        传输大小: '+toSize(rdata['data']['total_size'])+',耗时: '+rdata['data']['total_time']+',平均速度: '+toSize(rdata['data']['speed'])+'/s

                        \ +

                        \ + \ + 迁移日志\ +

                        \ +
                        \ +
                        '; + $('.psync_migrate').html(end); + } else{ + $('.psync_migrate .action').text(rdata['data']['action']); + $('.psync_migrate .done').text(rdata['data']['done']); + $('.psync_migrate pre').text(rdata['data']['log']); + + var p = (rdata['data']['all_speed']/rdata['data']['all_total'])*100; + if (p>100){ + p = 100; + } + $('.psync_migrate .progress_info_bar').width(p+'%'); + $('.psync_migrate .progress_info').text(p+'%'); + + renderMigrationProgress(); + } + } else{ + layer.msg(rdata.msg,{icon:1}); + } + }); +} + +function initStep4(){ + var site_checked = ''; + $('input[name="sites"]:checked').each(function(){ + site_checked += $(this).val()+','; + }); + + var databases_checked = ''; + $('input[name="databases"]:checked').each(function(){ + databases_checked+=$(this).val()+','; + }); + + var plugin_checked = ''; + $('input[name="plugin"]:checked').each(function(){ + plugin_checked+=$(this).val()+','; + }); + + maPost('step_four',{sites:site_checked,databases:databases_checked,plugin:plugin_checked}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + selectProgress(4); + + var progress = '
                        \ +
                        \ +
                        --\ + 当前: --[取消]
                        \ +
                        \ +
                        \ +
                        \ +
                        \ +
                        \ +
                        \ +
                        \
                        +        ';
                        +
                        +
                        +        $('.psync_data').hide();
                        +        $('.psync_migrate').html(progress);
                        +        $('.psync_migrate').show();
                        +
                        +        renderMigrationProgress();
                        +    });
                        +}
                        +
                        +
                        +function initStep(){
                        +    maPost('get_conf',{}, function(rdata){
                        +        var rdata = $.parseJSON(rdata.data);
                        +        $('input[name="sync_url"]').val(rdata.data['url']);
                        +        $('input[name="sync_app_id"]').val(rdata.data['app_id']);
                        +        $('input[name="sync_app_secret"]').val(rdata.data['app_secret']);
                        +    });
                        +
                        +    $('.infoNext').click(function(){
                        +        initStep1();
                        +    });
                        +
                        +    // 重新检测按钮
                        +    $('.psync_path').on('click', '.pathTestting', function () {
                        +        initStep2();
                        +    });
                        +
                        +    $('.psync_path').on('click', '.pathBcak', function(){
                        +        $('.psync_path').hide();
                        +        $('.psync_info').show();
                        +        selectProgress(1);
                        +    });
                        +
                        +    $('.psync_path').on('click', '.pathNext', function(){
                        +        initStep3();
                        +    });
                        +
                        +
                        +    $('.psync_data').on('click', '.dataBack', function(){
                        +        $('.psync_data').hide();
                        +        $('.psync_path').show();
                        +        selectProgress(2);
                        +    });
                        +
                        +    
                        +    $('.psync_data').on('click', '.dataMigrate', function(){ 
                        +        initStep4();
                        +    });
                        +
                        +    $('#sites_All').on('click',function(){ 
                        +        var ch = $(this).prop('checked');
                        +        $('#sites_li input').prop('checked',ch);
                        +    });
                        +
                        +    $('#db_All').on('click',function(){ 
                        +        var ch = $(this).prop('checked');
                        +        $('#db_li input').prop('checked',ch);
                        +    });
                        +
                        +    $('#plugin_All').on('click',function(){ 
                        +        var ch = $(this).prop('checked');
                        +        $('#plugin_li input').prop('checked',ch);
                        +    });
                        +
                        +    $('.psync_migrate').on('click', '.okBtn', function(){ 
                        +        $('.psync_migrate').hide();
                        +        $('.psync_info').show();
                        +        selectProgress(1);
                        +    });
                        +}
                        +
                        +
                        +
                        +
                        +
                        +
                        +
                        +
                        +
                        +
                        diff --git a/plugins/mongodb/check.sh b/plugins/mongodb/check.sh
                        new file mode 100644
                        index 000000000..e94441c2a
                        --- /dev/null
                        +++ b/plugins/mongodb/check.sh
                        @@ -0,0 +1,42 @@
                        +#!/bin/bash
                        +
                        +# MongoDB 监控脚本
                        +# 功能:检查 MongoDB 是否运行,如果停止则自动重启
                        +
                        +# MongoDB 服务名称(根据你的系统配置调整)
                        +SERVICE_NAME="mongod"
                        +
                        +# 检查 MongoDB 状态函数
                        +check_mongodb() {
                        +    # 检查 MongoDB 进程是否在运行
                        +    if pgrep -x "$SERVICE_NAME" > /dev/null; then
                        +        echo "$(date): MongoDB 正在运行"
                        +        return 0
                        +    else
                        +        echo "$(date): MongoDB 已停止"
                        +        return 1
                        +    fi
                        +}
                        +
                        +# 重启 MongoDB 函数
                        +restart_mongodb() {
                        +    echo "$(date): 尝试重启 MongoDB..."
                        +    
                        +    # 根据你的系统选择适当的命令
                        +    # 对于使用 systemd 的系统(如 Ubuntu 16.04+, CentOS 7+)
                        +    if systemctl restart "mongodb"; then
                        +        echo "$(date): MongoDB 重启成功"
                        +        return 0
                        +    else
                        +        echo "$(date): MongoDB 重启失败"
                        +        return 1
                        +    fi
                        +    
                        +    # 对于使用 init.d 的系统
                        +    # service mongod restart
                        +}
                        +
                        +if ! check_mongodb; then
                        +    echo "$(date): 检测到 MongoDB 停止运行"
                        +    restart_mongodb
                        +fi
                        diff --git a/plugins/mongodb/config/mongodb.bak.conf b/plugins/mongodb/config/mongodb.bak.conf
                        new file mode 100644
                        index 000000000..2e1ecfdf6
                        --- /dev/null
                        +++ b/plugins/mongodb/config/mongodb.bak.conf
                        @@ -0,0 +1,18 @@
                        +directoryperdb = true
                        +dbpath = {$SERVER_PATH}/mongodb/data
                        +logpath = {$SERVER_PATH}/mongodb/logs/mongodb.log
                        +logappend = true
                        +bind_ip = 127.0.0.1
                        +port = 27017
                        +fork = true
                        +auth = false
                        +#smallfiles = true
                        +
                        +oplogSize=100
                        +
                        +# Master/slave replication is no longer supported
                        +#master = true
                        +
                        +#replSet = test
                        +
                        +pidfilepath = {$SERVER_PATH}/mongodb/mongodb.pid
                        diff --git a/plugins/mongodb/config/mongodb.conf b/plugins/mongodb/config/mongodb.conf
                        new file mode 100644
                        index 000000000..025138a43
                        --- /dev/null
                        +++ b/plugins/mongodb/config/mongodb.conf
                        @@ -0,0 +1,19 @@
                        +net:
                        +  bindIp: 127.0.0.1
                        +  port: 27017
                        +processManagement:
                        +  fork: true
                        +  pidFilePath: {$SERVER_PATH}/mongodb/mongodb.pid
                        +security:
                        +  authorization: disabled
                        +  javascriptEnabled: false
                        +storage:
                        +  dbPath: {$SERVER_PATH}/mongodb/data
                        +  directoryPerDB: true
                        +systemLog:
                        +  destination: file
                        +  logAppend: true
                        +  logRotate: reopen
                        +  path: {$SERVER_PATH}/mongodb/logs/mongodb.log
                        +replication:
                        +  oplogSizeMB: 2048
                        \ No newline at end of file
                        diff --git a/plugins/mongodb/config/mongodb.sql b/plugins/mongodb/config/mongodb.sql
                        new file mode 100644
                        index 000000000..37c875509
                        --- /dev/null
                        +++ b/plugins/mongodb/config/mongodb.sql
                        @@ -0,0 +1,19 @@
                        +CREATE TABLE IF NOT EXISTS `config` (
                        +  `id` INTEGER PRIMARY KEY AUTOINCREMENT,
                        +  `mg_root` TEXT
                        +);
                        +
                        +INSERT INTO `config` (`id`, `mg_root`) VALUES (1, 'mg_root');
                        +
                        +
                        +CREATE TABLE IF NOT EXISTS `databases` (
                        +  `id` INTEGER PRIMARY KEY AUTOINCREMENT,
                        +  `name` TEXT,
                        +  `username` TEXT,
                        +  `password` TEXT,
                        +  `accept` TEXT,
                        +  `rw` TEXT DEFAULT 'rw',
                        +  `ps` TEXT,
                        +  `addtime` TEXT
                        +);
                        +
                        diff --git a/plugins/mongodb/ico.png b/plugins/mongodb/ico.png
                        new file mode 100644
                        index 000000000..2b8a9f6ac
                        Binary files /dev/null and b/plugins/mongodb/ico.png differ
                        diff --git a/plugins/mongodb/index.html b/plugins/mongodb/index.html
                        new file mode 100755
                        index 000000000..3d930fab4
                        --- /dev/null
                        +++ b/plugins/mongodb/index.html
                        @@ -0,0 +1,84 @@
                        +
                        +
                        +
                        +
                        +
                        +
                        +

                        服务

                        +

                        自启动

                        +

                        配置修改

                        +

                        配置文件

                        +

                        配置[KEY]

                        +

                        数据列表

                        +

                        文档状态

                        +

                        负载状态

                        +

                        复制状态

                        +

                        日志

                        +

                        维护功能

                        +

                        相关说明

                        +
                        +
                        +
                        +
                        +
                        +
                        + + + \ No newline at end of file diff --git a/plugins/mongodb/index.py b/plugins/mongodb/index.py new file mode 100755 index 000000000..c6b74c6f8 --- /dev/null +++ b/plugins/mongodb/index.py @@ -0,0 +1,1690 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import datetime +import yaml + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +# /usr/lib/systemd/system/mongod.service + +# python3 /www/server/mdserver-web/plugins/mongodb/index.py repl_init +# python3 /www/server/mdserver-web/plugins/mongodb/index.py run_repl_info +# python3 /www/server/mdserver-web/plugins/mongodb/index.py test_data +# python3 /www/server/mdserver-web/plugins/mongodb/index.py run_info + +def getPluginName(): + return 'mongodb' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/mongodb.conf" + return path + +def getConfKey(): + key = getServerDir() + "/mongodb.key" + return key + +def getConfTpl(): + path = getPluginDir() + "/config/mongodb.conf" + return path + +def getConfigData(): + cfg = getConf() + config_data = mw.readFile(cfg) + try: + config = yaml.safe_load(config_data) + except: + config = { + "systemLog": { + "destination": "file", + "logAppend": True, + "path": mw.getServerDir()+"/mongodb/log/mongodb.log" + }, + "storage": { + "dbPath": mw.getServerDir()+"/mongodb/data", + "directoryPerDB": True, + "journal": { + "enabled": True + } + }, + "processManagement": { + "fork": True, + "pidFilePath": mw.getServerDir()+"/mongodb/log/mongodb.pid" + }, + "net": { + "port": 27017, + "bindIp": "0.0.0.0" + }, + "security": { + "authorization": "enabled", + "javascriptEnabled": False + } + } + return config + +def setConfig(config_data): + # t = status() + cfg = getConf() + try: + mw.writeFile(cfg, yaml.safe_dump(config_data)) + except: + return False + return True + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getConfIp(): + data = getConfigData() + return data['net']['bindIp'] + +def getConfLocalIp(): + return '127.0.0.1' + +def getConfPort(): + data = getConfigData() + return data['net']['port'] + # file = getConf() + # content = mw.readFile(file) + # rep = 'port\s*=\s*(.*)' + # tmp = re.search(rep, content) + # return tmp.groups()[0].strip() + +def getConfAuth(): + data = getConfigData() + return data['security']['authorization'] + # file = getConf() + # content = mw.readFile(file) + # rep = 'auth\s*=\s*(.*)' + # tmp = re.search(rep, content) + # return tmp.groups()[0].strip() + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':', 1) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def status(): + data = mw.execShell( + "ps -ef|grep mongod |grep -v mongosh|grep -v grep | grep -v /Applications | grep -v python | grep -v mdserver-web | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + +def pSqliteDb(dbname='users'): + file = getServerDir() + '/mongodb.db' + name = 'mongodb' + + sql_file = getPluginDir() + '/config/mongodb.sql' + import_sql = mw.readFile(sql_file) + # print(sql_file,import_sql) + md5_sql = mw.md5(import_sql) + + import_sign = False + save_md5_file = getServerDir() + '/import_mongodb.md5' + if os.path.exists(save_md5_file): + save_md5_sql = mw.readFile(save_md5_file) + if save_md5_sql != md5_sql: + import_sign = True + mw.writeFile(save_md5_file, md5_sql) + else: + mw.writeFile(save_md5_file, md5_sql) + + if not os.path.exists(file) or import_sql: + conn = mw.M(dbname).dbPos(getServerDir(), name) + csql_list = import_sql.split(';') + for index in range(len(csql_list)): + conn.execute(csql_list[index], ()) + + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + +def mongdbClientS(): + import pymongo + port = getConfPort() + auth = getConfAuth() + ip = getConfLocalIp() + mg_root = pSqliteDb('config').where('id=?', (1,)).getField('mg_root') + + if auth == 'disabled': + client = pymongo.MongoClient(host=ip, port=int(port), directConnection=True) + else: + # print(auth,mg_root) + client = pymongo.MongoClient(host=ip, port=int(port), directConnection=True, username='root',password=mg_root) + return client + +def mongdbClient(): + import pymongo + port = getConfPort() + auth = getConfAuth() + ip = getConfLocalIp() + mg_root = pSqliteDb('config').where('id=?', (1,)).getField('mg_root') + # print(ip,port,auth,mg_root) + if auth == 'disabled': + client = pymongo.MongoClient(host=ip, port=int(port), directConnection=True) + else: + # uri = "mongodb://root:"+mg_root+"@127.0.0.1:"+str(port) + # client = pymongo.MongoClient(uri) + client = pymongo.MongoClient(host=ip, port=int(port), directConnection=True, username='root',password=mg_root) + return client + + +def initDreplace(): + + mg_key = getServerDir() + "/mongodb.key" + if not os.path.exists(mg_key): + mw.execShell("openssl rand -base64 756 >> "+mg_key) + mw.execShell("chmod 400 "+mg_key) + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + logs_dir = getServerDir() + '/logs' + if not os.path.exists(logs_dir): + os.mkdir(logs_dir) + + data_dir = getServerDir() + '/data' + if not os.path.exists(data_dir): + os.mkdir(data_dir) + + install_ok = getServerDir() + "/install.lock" + if os.path.exists(install_ok): + return file_bin + mw.writeFile(install_ok, 'ok') + + # initd replace + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # config replace + conf_content = mw.readFile(getConfTpl()) + conf_content = conf_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(getServerDir() + '/mongodb.conf', conf_content) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/mongodb.service' + systemServiceTpl = getPluginDir() + '/init.d/mongodb.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def mgOp(method): + file = initDreplace() + if mw.isAppleSystem(): + data = mw.execShell(file + ' ' + method) + # print(data) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return 'fail' + + +def start(): + mw.execShell( + 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/www/server/lib/openssl11/lib') + return mgOp('start') + + +def stop(): + return mgOp('stop') + + +def reload(): + return mgOp('reload') + + +def restart(): + if os.path.exists("/tmp/mongodb-27017.sock"): + mw.execShell('rm -rf ' + "/tmp/mongodb-27017.sock") + + return mgOp('restart') + + +def getConfig(): + t = status() + if t == 'stop': + return mw.returnJson(False,'未启动!') + d = getConfigData() + return mw.returnJson(True,'ok',d) + +def saveConfig(): + d = getConfigData() + args = getArgs() + data = checkArgs(args, ['bind_ip','port','data_path','log','pid_file_path']) + if not data[0]: + return data[1] + + d['net']['bindIp'] = args['bind_ip'] + d['net']['port'] = int(args['port']) + + d['storage']['dbPath'] = args['data_path'] + d['systemLog']['path'] = args['log'] + d['processManagement']['pidFilePath'] = args['pid_file_path'] + setConfig(d) + restart() + return mw.returnJson(True,'设置成功') + +def initMgRoot(password='',force=0): + if force == 1: + d = getConfigData() + auth_t = d['security']['authorization'] + d['security']['authorization'] = 'disabled' + setConfig(d) + restart() + + client = mongdbClient() + db = client.admin + + db_all_rules = [ + {'role': 'root', 'db': 'admin'}, + {'role': 'clusterAdmin', 'db': 'admin'}, + {'role': 'readAnyDatabase', 'db': 'admin'}, + {'role': 'readWriteAnyDatabase', 'db': 'admin'}, + {'role': 'userAdminAnyDatabase', 'db': 'admin'}, + {'role': 'dbAdminAnyDatabase', 'db': 'admin'}, + {'role': 'userAdmin', 'db': 'admin'}, + {'role': 'dbAdmin', 'db': 'admin'} + ] + + if password =='': + mg_pass = mw.getRandomString(8) + else: + mg_pass = password + + try: + db.command("createUser", "root", pwd=mg_pass, roles=db_all_rules) + except Exception as e: + if force == 0: + db.command("updateUser", "root", pwd=mg_pass, roles=db_all_rules) + else: + db.command('dropUser','root') + db.command("createUser", "root", pwd=mg_pass, roles=db_all_rules) + r = pSqliteDb('config').where('id=?', (1,)).save('mg_root',(mg_pass,)) + + if force == 1: + d['security']['authorization'] = auth_t + setConfig(d) + restart() + return True + +def initUserRoot(): + d = getConfigData() + auth_t = d['security']['authorization'] + d['security']['authorization'] = 'disabled' + setConfig(d) + restart() + time.sleep(1) + + client = mongdbClient() + db = client.admin + + db_all_rules = [ + {'role': 'root', 'db': 'admin'}, + {'role': 'clusterAdmin', 'db': 'admin'}, + {'role': 'readAnyDatabase', 'db': 'admin'}, + {'role': 'readWriteAnyDatabase', 'db': 'admin'}, + {'role': 'userAdminAnyDatabase', 'db': 'admin'}, + {'role': 'dbAdminAnyDatabase', 'db': 'admin'}, + {'role': 'userAdmin', 'db': 'admin'}, + {'role': 'dbAdmin', 'db': 'admin'} + ] + # db.command("updateUser", "root", pwd=mg_pass, roles=db_all_rules) + mg_pass = mw.getRandomString(8) + try: + r1 = db.command("createUser", "root", pwd=mg_pass, roles=db_all_rules) + # print(r1) + except Exception as e: + # print(e) + r1 = db.command('dropUser','root') + r2 = db.command("createUser", "root", pwd=mg_pass, roles=db_all_rules) + # print(r1, r2) + + r = pSqliteDb('config').where('id=?', (1,)).save('mg_root',(mg_pass,)) + + d['security']['authorization'] = auth_t + setConfig(d) + restart() + return True + +def setConfigAuth(): + init_db_root = getServerDir() + '/init_db_root.lock' + if not os.path.exists(init_db_root): + initUserRoot() + mw.writeFile(init_db_root,'ok') + + d = getConfigData() + if d['security']['authorization'] == 'enabled': + d['security']['authorization'] = 'disabled' + del d['security']['keyFile'] + setConfig(d) + restart() + return mw.returnJson(True,'关闭成功') + else: + d['security']['authorization'] = 'enabled' + d['security']['keyFile'] = getServerDir()+'/mongodb.key' + setConfig(d) + restart() + return mw.returnJson(True,'开启成功') + +def runInfo(): + ''' + cd /www/server/mdserver-web && source bin/activate && python3 /www/server/mdserver-web/plugins/mongodb/index.py run_info + ''' + client = mongdbClient() + db = client.admin + + try: + serverStatus = db.command('serverStatus') + except Exception as e: + return mw.returnJson(False, str(e)) + + + listDbs = client.list_database_names() + + result = {} + result["host"] = serverStatus['host'] + result["version"] = serverStatus['version'] + result["uptime"] = serverStatus['uptime'] + result['db_path'] = getServerDir() + "/data" + result["connections"] = serverStatus['connections']['current'] + result["collections"] = len(listDbs) + + pf = serverStatus['opcounters'] + result['pf'] = pf + + return mw.getJson(result) + + +def runDocInfo(): + client = mongdbClient() + db = client.admin + # print(db) + + try: + serverStatus = db.command('serverStatus') + except Exception as e: + return mw.returnJson(False, str(e)) + + + serverStatus = db.command('serverStatus') + + listDbs = client.list_database_names() + showDbList = [] + result = {} + for x in range(len(listDbs)): + mongd = client[listDbs[x]] + stats = mongd.command({"dbstats": 1}) + if 'operationTime' in stats: + del stats['operationTime'] + + if '$clusterTime' in stats: + del stats['$clusterTime'] + showDbList.append(stats) + + result["dbs"] = showDbList + return mw.getJson(result) + +def runReplInfo(): + client = mongdbClient() + db = client.admin + result = {} + try: + serverStatus = db.command('serverStatus') + except Exception as e: + return mw.returnJson(False, str(e)) + + d = getConfigData() + if 'replication' in d and 'replSetName' in d['replication']: + result['repl_name'] = d['replication']['replSetName'] + + result['status'] = '无' + result['doc_name'] = '无' + if 'repl' in serverStatus: + repl = serverStatus['repl'] + # print(repl) + result['status'] = '从' + if 'ismaster' in repl and repl['ismaster']: + result['status'] = '主' + + if 'secondary' in repl and not repl['secondary']: + result['status'] = '主' + + result['setName'] = mw.getDefault(repl,'setName', '') + result['primary'] = mw.getDefault(repl,'primary', '') + result['me'] = mw.getDefault(repl,'me', '') + + hosts = mw.getDefault(repl,'hosts', '') + result['hosts'] = ','.join(hosts) + + result['members'] = [] + try: + members_list = [] + replStatus = db.command('replSetGetStatus') + if 'members' in replStatus: + members = replStatus['members'] + for m in members: + t = {} + t['name'] = m['name'] + t['stateStr'] = m['stateStr'] + t['uptime'] = m['uptime'] + members_list.append(t) + result['members'] = members_list + except Exception as e: + pass + + return mw.returnJson(True, 'OK', result) + +def getDbList(): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('databases') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,name,username,password,accept,rw,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + + for x in range(0, len(clist)): + dbname = clist[x]['name'] + blist = getDbBackupListFunc(dbname) + clist[x]['is_backup'] = False + if len(blist) > 0: + clist[x]['is_backup'] = True + + count = conn.where(condition, ()).count() + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + info = {} + info['root_pwd'] = pSqliteDb('config').where('id=?', (1,)).getField('mg_root') + data['info'] = info + return mw.getJson(data) + # return mw.returnJson(True,'ok',data) + +def addDb(): + t = status() + if t == 'stop': + return mw.returnJson(False,'未启动!') + + client = mongdbClient() + db = client.admin + + args = getArgs() + data = checkArgs(args, ['ps','name','db_user','password']) + if not data[0]: + return data[1] + + data_name = args['name'].strip() + if not data_name: + return mw.returnJson(False, "数据库名不能为空!") + + nameArr = ['admin', 'config', 'local'] + if data_name in nameArr: + return mw.returnJson(False, "数据库名是保留名称!") + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + username = '' + password = '' + # auth为true时如果__DB_USER为空则将它赋值为 root,用于开启本地认证后数据库用户为空的情况 + auth_status = getConfAuth() == "enabled" + + if auth_status: + data_name = args['name'] + username = args['db_user'] + password = args['password'] + else: + username = data_name + + + client[data_name].zchat.insert_one({}) + user_roles = [{'role': 'dbOwner', 'db': data_name}, {'role': 'userAdmin', 'db': data_name}] + if auth_status: + # db.command("dropUser", username) + db.command("createUser", username, pwd=password, roles=user_roles) + + ps = args['ps'] + if ps == '': + ps = data_name + + # 添加入SQLITE + pSqliteDb('databases').add('name,username,password,accept,ps,addtime', (data_name, username, password, '127.0.0.1', ps, addTime)) + return mw.returnJson(True, '添加成功') + + +def delDb(): + client = mongdbClient() + db = client.admin + sqlite_db = pSqliteDb('databases') + + args = getArgs() + data = checkArgs(args, ['id', 'name']) + if not data[0]: + return data[1] + try: + sid = args['id'] + name = args['name'] + find = sqlite_db.where("id=?", (sid,)).field('id,name,username,password,accept,ps,addtime').find() + accept = find['accept'] + username = find['username'] + + client.drop_database(name) + + try: + db.command('dropUser',username) + except Exception as e: + pass + + # 删除SQLITE + sqlite_db.where("id=?", (sid,)).delete() + return mw.returnJson(True, '删除成功!') + except Exception as ex: + return mw.returnJson(False, '删除失败!' + str(ex)) + + +def delDbTable(): + client = mongdbClient() + db = client.admin + sqlite_db = pSqliteDb('databases') + + args = getArgs() + data = checkArgs(args, ['table_name', 'name']) + if not data[0]: + return data[1] + + name = args['name'] + table_name = args['table_name'] + + try: + cur_db = client[name] + cur_db[table_name].drop() + return mw.returnJson(True, '删除成功!') + except Exception as ex: + return mw.returnJson(False, '删除失败!' + str(ex)) + +def setRootPwd(version=''): + args = getArgs() + data = checkArgs(args, ['password']) + if not data[0]: + return data[1] + + #强制修改 + force = 0 + if 'force' in args and args['force'] == '1': + force = 1 + + password = args['password'] + try: + msg = '' + if force == 1: + msg = ',无须强制!' + initMgRoot(password, force) + return mw.returnJson(True, '数据库root密码修改成功!'+msg) + except Exception as ex: + return mw.returnJson(False, '修改错误:' + str(ex)) + +def setUserPwd(version=''): + + client = mongdbClient() + db = client.admin + sqlite_db = pSqliteDb('databases') + + args = getArgs() + data = checkArgs(args, ['password', 'name']) + if not data[0]: + return data[1] + + newpassword = args['password'] + username = args['name'] + uid = args['id'] + try: + name = sqlite_db.where('id=?', (uid,)).getField('name') + user_roles = [{'role': 'dbOwner', 'db': name}, {'role': 'userAdmin', 'db': name}] + + try: + db.command("updateUser", username, pwd=newpassword, roles=user_roles) + except Exception as e: + db.command("createUser", username, pwd=newpassword, roles=user_roles) + + sqlite_db.where("id=?", (uid,)).setField('password', newpassword) + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]密码成功!', (name,))) + except Exception as ex: + return mw.returnJson(False, mw.getInfo('修改数据库[{1}]密码失败[{2}]!', (name, str(ex),))) + + +def syncGetDatabases(): + client = mongdbClient() + sqlite_db = pSqliteDb('databases') + db = client.admin + data = client.admin.command({"listDatabases": 1}) + nameArr = ['admin', 'config', 'local'] + n = 0 + + for value in data['databases']: + vdb_name = value["name"] + b = False + for key in nameArr: + if vdb_name == key: + b = True + break + if b: + continue + if sqlite_db.where("name=?", (vdb_name,)).count() > 0: + continue + + host = '127.0.0.1' + ps = vdb_name + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + if sqlite_db.add('name,username,password,accept,ps,addtime', (vdb_name, vdb_name, '', host, ps, addTime)): + n += 1 + + msg = mw.getInfo('本次共从服务器获取了{1}个数据库!', (str(n),)) + return mw.returnJson(True, msg) + +def setDbPs(): + args = getArgs() + data = checkArgs(args, ['id', 'name', 'ps']) + if not data[0]: + return data[1] + + ps = args['ps'] + sid = args['id'] + name = args['name'] + try: + psdb = pSqliteDb('databases') + psdb.where("id=?", (sid,)).setField('ps', ps) + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]备注成功!', (name,))) + except Exception as e: + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]备注失败!', (name,))) + + +def getDbInfo(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + ret = {} + + client = mongdbClient() + + db_name = args['name'] + db = client[db_name] + + result = {} + t = db.command("dbStats") + # print(result) + result['collections'] = t['collections'] + result['avgObjSize'] = t['avgObjSize'] + result['dataSize'] = t['dataSize'] + result['storageSize'] = t['storageSize'] + result['indexSize'] = t['indexSize'] + + result["collection_list"] = [] + for collection_name in db.list_collection_names(): + collection = db.command("collStats", collection_name) + data = { + "collection_name": collection_name, + "count": collection.get("count"), # 文档数 + "size": collection.get("size"), # 内存中的大小 + "avg_obj_size": collection.get("avgObjSize"), # 对象平均大小 + "storage_size": collection.get("storageSize"), # 存储大小 + "capped": collection.get("capped"), + "nindexes": collection.get("nindexes"), # 索引数 + "total_index_size": collection.get("totalIndexSize"), # 索引大小 + } + result["collection_list"].append(data) + + return mw.returnJson(True,'ok', result) + +def toDbBase(find): + client = mongdbClient() + db_admin = client.admin + data_name = find['name'] + db = client[data_name] + + db.zchat.insert_one({}) + user_roles = [{'role': 'dbOwner', 'db': data_name}, {'role': 'userAdmin', 'db': data_name}] + try: + db_admin.command("createUser", find['username'], pwd=find['password'], roles=user_roles) + except Exception as e: + db_admin.command("updateUser", find['username'], pwd=find['password'], roles=user_roles) + return 1 + +def syncToDatabases(): + args = getArgs() + data = checkArgs(args, ['type', 'ids']) + if not data[0]: + return data[1] + + stype = int(args['type']) + sqlite_db = pSqliteDb('databases') + n = 0 + + if stype == 0: + data = sqlite_db.field('id,name,username,password,accept').select() + for value in data: + result = toDbBase(value) + if result == 1: + n += 1 + else: + data = json.loads(args['ids']) + for value in data: + find = sqlite_db.where("id=?", (value,)).field( + 'id,name,username,password,accept').find() + # print find + result = toDbBase(find) + if result == 1: + n += 1 + msg = mw.getInfo('本次共同步了{1}个数据库!', (str(n),)) + return mw.returnJson(True, msg) + + +def getAllRole(): + mongo_role = { + # 数据库用户角色 + "read": "读取数据(read)", + "readWrite": "读取和写入数据(readWrite)", + # 数据库管理角色 + # "dbAdmin": "数据库管理员", + "dbOwner": "数据库所有者(dbOwner)", + "userAdmin": "用户管理员(userAdmin)", + # 集群管理角色 + # "clusterAdmin": "集群管理员", + # "clusterManager": "集群管理器", + # "clusterMonitor": "集群监视器", + # "hostManager": "主机管理员", + # 备份和恢复角色 + # "backup": "备份数据", + # "restore": "还原数据", + # 所有数据库角色 + # "readAnyDatabase": "任意数据库读取", + # "readWriteAnyDatabase": "任意数据库读取和写入", + # "userAdminAnyDatabase": "任意数据库用户管理员", + # "dbAdminAnyDatabase": "任意数据库管理员", + # 超级用户角色 + # "root": "超级管理员", + # 内部角色 + # "__queryableBackup": "可查询备份", + # "__system": "系统角色", + # "enableSharding": "启用分片", + } + + client = mongdbClient() + db = client.admin + + # 获取所有角色 + role_data = db.command('rolesInfo', showBuiltinRoles=True) + result = [] + for role in role_data["roles"]: + if mongo_role.get(role["role"]) is not None: + role["name"] = mongo_role.get(role["role"]) + result.append(role) + return mw.returnJson(True, 'ok', result) + +def getDbAccess(): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + + client = mongdbClient() + db = client.admin + username = args['username'] + + mongo_role = { + # 数据库用户角色 + "read": "读取数据(read)", + "readWrite": "读取和写入数据(readWrite)", + # 数据库管理角色 + # "dbAdmin": "数据库管理员", + "dbOwner": "数据库所有者(dbOwner)", + "userAdmin": "用户管理员(userAdmin)", + # 集群管理角色 + # "clusterAdmin": "集群管理员", + # "clusterManager": "集群管理器", + # "clusterMonitor": "集群监视器", + # "hostManager": "主机管理员", + # 备份和恢复角色 + # "backup": "备份数据", + # "restore": "还原数据", + # 所有数据库角色 + # "readAnyDatabase": "任意数据库读取", + # "readWriteAnyDatabase": "任意数据库读取和写入", + # "userAdminAnyDatabase": "任意数据库用户管理员", + # "dbAdminAnyDatabase": "任意数据库管理员", + # 超级用户角色 + # "root": "超级管理员", + # 内部角色 + # "__queryableBackup": "可查询备份", + # "__system": "系统角色", + # "enableSharding": "启用分片", + } + + role_data = db.command('rolesInfo', showBuiltinRoles=True) + all_role_list = [] + for role in role_data["roles"]: + if mongo_role.get(role["role"]) is not None: + role["name"] = mongo_role.get(role["role"]) + all_role_list.append(role) + + result = { + "user": username, + "db": username, + "roles": [], + "all_roles":all_role_list, + } + + user_data = db.command('usersInfo', username) + if user_data: + if len(user_data["users"]) != 0: + user = user_data["users"][0] + result["user"] = user.get("user", username) + result["db"] = user.get("db", username) + result["roles"] = user.get("roles", []) + + return mw.returnJson(True, 'ok', result) + +def setDbAccess(): + args = getArgs() + data = checkArgs(args, ['username', 'select','name']) + if not data[0]: + return data[1] + username = args['username'] + select = args['select'] + name = args['name'] + + mg_pass = pSqliteDb('config').where('id=?', (1,)).getField('mg_root') + + user_roles = [] + select_role = select.split(',') + for role in select_role: + t = {} + t['role'] = role + t['db'] = name + user_roles.append(t) + + client = mongdbClient() + db = client.admin + + try: + db.command("updateUser", username, pwd=mg_pass, roles=user_roles) + except Exception as e: + db.command('dropUser',username) + db.command("createUser", username, pwd=mg_pass, roles=user_roles) + + return mw.returnJson(True, '设置成功!') + +def getReplConfigData(): + import json + f = getServerDir()+'/repl.json' + if os.path.exists(f): + c = mw.readFile(f) + return json.loads(c) + else: + t = {} + t['name'] = '' + t['nodes'] = [] + mw.writeFile(f, mw.getJson(t)) + return t + +def setReplConfigData(c): + import json + f = getServerDir()+'/repl.json' + mw.writeFile(f, mw.getJson(c)) + return c + +def getReplConfig(): + c = getReplConfigData() + return mw.returnJson(True, 'ok!', c) + +def replSetName(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + c = getReplConfigData() + c['name'] = args['name'] + setReplConfigData(c) + + + d = getConfigData() + d['replication']['replSetName'] = args['name'] + setConfig(d) + restart() + + return mw.returnJson(True, '设置成功!') + +def replSetNode(): + args = getArgs() + data = checkArgs(args, ['node','priority','arbiterOnly','votes','idx']) + if not data[0]: + return data[1] + + c = getReplConfigData() + nodes = c['nodes'] + add_node = args['node'].strip() + idx = int(args['idx']) + + priority = -1 + if 'priority' in args: + priority = args['priority'].strip() + + priority = int(priority) + if priority<0 or priority>100: + return mw.returnJson(False, 'priority应该在[0-100]之间!') + + arbiterOnly = 0 + if 'arbiterOnly' in args: + arbiterOnly = args['arbiterOnly'].strip() + arbiterOnly = int(arbiterOnly) + + votes = 1 + if 'votes' in args: + votes = args['votes'] + votes = int(votes) + + # 编辑状态 + if idx>-1: + for i in range(len(nodes)): + if i == idx: + nodes[i]['host'] = add_node + nodes[i]['priority'] = priority + nodes[i]['votes'] = votes + nodes[i]['arbiterOnly'] = arbiterOnly + c['nodes'] = nodes + setReplConfigData(c) + return mw.returnJson(True, '编辑成功!') + + is_have = False + for x in nodes: + if x['host'] == add_node: + is_have = True + + if is_have: + return mw.returnJson(False, add_node+',节点已经存在!') + + t = {} + t['host'] = add_node + t['priority'] = priority + t['votes'] = votes + t['arbiterOnly'] = arbiterOnly + + nodes.append(t) + c['nodes'] = nodes + setReplConfigData(c) + return mw.returnJson(True, '添加成功!') + + +def delReplNode(): + args = getArgs() + data = checkArgs(args, ['node']) + if not data[0]: + return data[1] + + c = getReplConfigData() + nodes = c['nodes'] + del_node = args['node'].strip() + + filter_nodes = []; + for x in nodes: + if x['host'] != del_node: + filter_nodes.append(x) + + c['nodes'] = filter_nodes + setReplConfigData(c) + + return mw.returnJson(True, '删除节点'+args['node']+'成功!') + + +def replInit(): + c = getReplConfigData() + + name = c['name'] + nodes = c['nodes'] + + if name == '': + return mw.returnJson(False, '副本名不能为空!') + + # d = getConfigData() + # d['replication']['replSetName'] = name + # setConfig(d) + # restart() + + if len(nodes) == 0: + return mw.returnJson(False, '节点不能为空!') + + cfg_node = [] + + now_time_t = int(time.time()) + + for x in range(len(nodes)): + n = nodes[x] + t = {} + t['_id'] = x + t['host'] = n['host'] + if 'priority' in n: + t['priority'] = int(n['priority']) + + if 'votes' in n: + t['votes'] = int(n['votes']) + + if 'arbiterOnly' in n and n['arbiterOnly'] == 1: + t['arbiterOnly'] = True + + cfg_node.append(t) + + # print(cfg_node) + # return mw.returnJson(False, '设置副本成功!') + + config = { + '_id': name, + 'members': cfg_node + } + + client = mongdbClient() + try: + client.admin.command('replSetInitiate',config) + except Exception as e: + info = str(e).split(',') + # print(info) + if info[0] == 'already initialized': + config['version'] = int(now_time_t) + try: + client.admin.command('replSetReconfig',config,force=True,maxTimeMS=10) + except Exception as e: + return mw.returnJson(False, str(e)) + + return mw.returnJson(True, '重置副本同步成功!') + return mw.returnJson(False, str(e)) + + return mw.returnJson(True, '设置副本初始化成功!') + +def replClose(): + + d = getConfigData() + if 'replSetName' in d['replication']: + del d['replication']['replSetName'] + setConfig(d) + restart() + + client = mongdbClient() + db = client.admin + try: + restart() + except Exception as e: + return mw.returnJson(False, str(e)) + + return mw.returnJson(True, '关闭副本同步成功!') + +def getDbBackupListFunc(dbname=''): + bkDir = mw.getBackupDir() + '/database' + blist = os.listdir(bkDir) + r = [] + + bname = 'mongodb_' + dbname + blen = len(bname) + for x in blist: + fbstr = x[0:blen] + if fbstr == bname: + r.append(x) + return r + +def getDbBackupList(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + r = getDbBackupListFunc(args['name']) + bkDir = mw.getBackupDir() + '/database' + rr = [] + for x in range(0, len(r)): + p = bkDir + '/' + r[x] + data = {} + data['name'] = r[x] + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + return mw.returnJson(True, 'ok', rr) + +def getDbBackupImportList(): + + bkImportDir = mw.getBackupDir() + '/mongodb_import' + if not os.path.exists(bkImportDir): + os.mkdir(bkImportDir) + + blist = os.listdir(bkImportDir) + + rr = [] + for x in range(0, len(blist)): + name = blist[x] + p = bkImportDir + '/' + name + data = {} + data['name'] = name + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + rdata = { + "list": rr, + "upload_dir": bkImportDir, + } + return mw.returnJson(True, 'ok', rdata) + +def deleteDbBackup(): + args = getArgs() + data = checkArgs(args, ['filename', 'path']) + if not data[0]: + return data[1] + + path = args['path'] + full_file = "" + bkDir = mw.getBackupDir() + '/database' + full_file = bkDir + '/' + args['filename'] + if path != "": + full_file = path + "/" + args['filename'] + os.remove(full_file) + return mw.returnJson(True, 'ok') + +def setDbBackup(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + scDir = getPluginDir() + '/scripts/backup.py' + cmd = 'python3 ' + scDir + ' database ' + args['name'] + ' 3' + os.system(cmd) + return mw.returnJson(True, 'ok') + + +def getListBson(dbname=''): + bkDir = mw.getBackupDir() + '/mongodb_import/'+dbname + blist = os.listdir(bkDir) + r = [] + + bname = 'bson' + blen = len(bname) + for x in blist: + if x.endswith(bname): + r.append(x) + return r + +def rootPwd(): + return pSqliteDb('config').where( + 'id=?', (1,)).getField('mg_root') + +def importDbExternal(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + import_dir = mw.getBackupDir() + '/mongodb_import/' + mg_root = pSqliteDb('config').where('id=?', (1,)).getField('mg_root') + port = getConfPort() + + file_path = import_dir + file + if not os.path.exists(file_path): + return mw.returnJson(False, '文件突然消失?') + + exts = ['gz', 'zip'] + ext = mw.getFileSuffix(file) + if ext not in exts: + return mw.returnJson(False, '导入数据库格式不对!') + + # print(file,name) + # print(import_dir,name) + auth = getConfAuth() + mg_root = pSqliteDb('config').where('id=?', (1,)).getField('mg_root') + uoption = '' + if auth != 'disabled': + uoption =' --authenticationDatabase admin -u root -p '+mg_root + + file_dir = import_dir+name + if not os.path.exists(file_dir): + mw.execShell("mkdir -p "+file_dir) + + file_tgz = import_dir+file + if os.path.exists(file_tgz): + cmd = 'cd ' + file_dir + ' && tar -xzvf ' + file_tgz + " -C "+file_dir + # print(cmd) + r = mw.execShell(cmd) + # print(r) + bson_list = getListBson(name) + # print(bson_list) + for x in bson_list: + cmd = getServerDir() + "/bin/mongorestore "+uoption+" --port "+str(port)+" --dir "+file_dir+'/'+x + # print(cmd) + rdata = mw.execShell(cmd) + # print(data) + if rdata[1].lower().find('error') > -1: + return mw.returnJson(False, rdata[1]) + + # 删除文件 + if os.path.exists(file_dir): + del_cmd = "rm -rf "+file_dir + mw.execShell(del_cmd) + + return mw.returnJson(True, 'ok') + + +def importDbBackup(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + port = getConfPort() + + file_tgz = mw.getBackupDir() + '/database/' + file + file_dir = mw.getBackupDir() + '/database/' + file.replace('.tar.gz','') + + if not os.path.exists(file_dir): + mw.execShell("mkdir -p "+file_dir) + + # print(os.path.exists(file_tgz)) + if os.path.exists(file_tgz): + cmd = 'cd ' + mw.getBackupDir() + '/database && tar -xzvf ' + file + " -C "+file_dir + # print(cmd) + mw.execShell(cmd) + + auth = getConfAuth() + mg_root = pSqliteDb('config').where('id=?', (1,)).getField('mg_root') + uoption = '' + if auth != 'disabled': + uoption =' -u root -p '+mg_root + + cmd = getServerDir() + "/bin/mongorestore "+uoption+" --port "+str(port)+" --dir "+file_dir + # print(cmd) + mw.execShell(cmd) + + + # 删除文件 + if os.path.exists(file_dir): + del_cmd = "rm -rf "+file_dir + mw.execShell(del_cmd) + + return mw.returnJson(True, 'ok') + +def testData(): + ''' + cd /www/server/mdserver-web && source bin/activate && python3 /www/server/mdserver-web/plugins/mongodb/index.py test_data + ''' + import pymongo + from pymongo import ReadPreference + + client = mongdbClient() + + db = client.test + col = db["demo"] + + rndStr = mw.getRandomString(10) + insert_dict = { "name": "v1", "value": rndStr} + x = col.insert_one(insert_dict) + print(x) + + +def test(): + ''' + python3 /www/server/mdserver-web/plugins/mongodb/index.py set_config_auth {} + cd /www/server/mdserver-web && source bin/activate && python3 /www/server/mdserver-web/plugins/mongodb/index.py test + python3 plugins/mongodb/index.py test + ''' + # https://pymongo.readthedocs.io/en/stable/examples/high_availability.html + # import pymongo + # from pymongo import ReadPreference + + client = mongdbClient() + db = client.admin + + mg_pass = mw.getRandomString(10) + config = { + '_id': 'test', + 'members': [ + {'_id': 0, 'host': '127.0.0.1:27019'}, + {'_id': 1, 'host': '127.0.0.1:27017'}, + ] + } + + rsStatus = client.admin.command('replSetInitiate',config) + print(rsStatus) + + # 需要通过命令行操作 + # rs.initiate({ + # _id: 'test', + # members: [ + # { + # _id: 1, + # host: '127.0.0.1:27019', + # priority: 2 + # }, + # { + # _id: 2, + # host: '127.0.0.1:27017', + # priority: 1 + # } + + # ] + # }); + + # > rs.status(); // 查询状态 + # // "stateStr" : "PRIMARY", 主节点 + # // "stateStr" : "SECONDARY", 副本节点 + + # > rs.add({"_id":3, "host":"127.0.0.1:27318","priority":0,"votes":0}); + + + # serverStatus = db.command('serverStatus') + # print(serverStatus) + + return mw.returnJson(True, 'OK') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status mongodb | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable mongodb') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable mongodb') + return 'ok' + + +def runLog(): + f = getServerDir() + '/logs/mongodb.log' + if os.path.exists(f): + return f + return getServerDir() + '/logs.pl' + +def cronAddCheck(): + try: + import tool_task + tool_task.createBgTask() + return mw.returnJson(True, '添加检查任务成功') + except Exception as e: + return mw.returnJson(False, '添加检查任务失败:'+str(e)) + +def cronDelCheck(): + try: + import tool_task + tool_task.removeBgTask() + return mw.returnJson(True, '删除检查任务成功') + except Exception as e: + return mw.returnJson(False, '删除检查任务失败:'+str(e)) + + +def installPreInspectionDebainCheck(sysId,version): + if version == '8.0': + if int(sysId) < 12: + return "[%s]需要至少debain[12]" % (version,) + return '' + +def installPreInspection(version): + if mw.isAppleSystem(): + return 'ok' + + cmd = "cat /etc/*-release | grep PRETTY_NAME |awk -F = '{print $2}' | awk -F '\"' '{print $2}'| awk '{print $1}'" + sys = mw.execShell(cmd) + + if sys[1] != '': + return '暂时不支持该系统' + + sys_id = mw.execShell("cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F '\"' '{print $2}'") + + sysName = sys[0].strip().lower() + sysId = sys_id[0].strip() + + supportOs = ['centos', 'ubuntu', 'debian', 'opensuse'] + if not sysName in supportOs: + return '暂时仅支持{}'.format(','.join(supportOs)) + + if sysName == 'debian': + check = installPreInspectionDebainCheck(sysId, version) + if check != '': + return check + + return 'ok' + +def uninstallPreInspection(version): + stop() + + from utils.plugin import plugin as MwPlugin + MwPlugin.instance().removeIndex(getPluginName(), version) + + return "请手动删除MongoDB[{}]
                        rm -rf {}".format(version, getServerDir()) + +if __name__ == "__main__": + func = sys.argv[1] + + version = '4.4' + if (len(sys.argv) > 2): + version = sys.argv[2] + + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection(version)) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'run_doc_info': + print(runDocInfo()) + elif func == 'run_repl_info': + print(runReplInfo()) + elif func == 'conf': + print(getConf()) + elif func == 'config_key': + print(getConfKey()) + elif func == 'get_config': + print(getConfig()) + elif func == 'set_config': + print(saveConfig()) + elif func == 'set_config_auth': + print(setConfigAuth()) + elif func == 'root_pwd': + print(rootPwd()) + elif func == 'get_db_list': + print(getDbList()) + elif func == 'add_db': + print(addDb()) + elif func == 'del_db': + print(delDb()) + elif func == 'del_db_table': + print(delDbTable()) + elif func == 'set_root_pwd': + print(setRootPwd()) + elif func == 'set_user_pwd': + print(setUserPwd()) + elif func == 'sync_get_databases': + print(syncGetDatabases()) + elif func == 'sync_to_databases': + print(syncToDatabases()) + elif func == 'set_db_ps': + print(setDbPs()) + elif func == 'get_db_info': + print(getDbInfo()) + elif func == 'get_all_role': + print(getAllRole()) + elif func == 'get_db_access': + print(getDbAccess()) + elif func == 'set_db_access': + print(setDbAccess()) + elif func == 'repl_set_name': + print(replSetName()) + elif func == 'repl_set_node': + print(replSetNode()) + elif func == 'get_repl_config': + print(getReplConfig()) + elif func == 'del_repl_node': + print(delReplNode()) + elif func == 'repl_init': + print(replInit()) + elif func == 'repl_close': + print(replClose()) + elif func == 'get_db_backup_list': + print(getDbBackupList()) + elif func == 'get_db_backup_import_list': + print(getDbBackupImportList()) + elif func == 'delete_db_backup': + print(deleteDbBackup()) + elif func == 'set_db_backup': + print(setDbBackup()) + elif func == 'import_db_external': + print(importDbExternal()) + elif func == 'import_db_backup': + print(importDbBackup()) + elif func == 'run_log': + print(runLog()) + elif func == 'test': + print(test()) + elif func == 'test_data': + print(testData()) + elif func == 'cron_add_check': + print(cronAddCheck()) + elif func == 'cron_del_check': + print(cronDelCheck()) + else: + print('error') diff --git a/plugins/mongodb/info.json b/plugins/mongodb/info.json new file mode 100755 index 000000000..f42160c5e --- /dev/null +++ b/plugins/mongodb/info.json @@ -0,0 +1,20 @@ +{ + "sort":3, + "hook":["database"], + "ps": "一个高性能的NOSQL数据库【社区版】", + "name": "mongodb", + "title": "MongoDB", + "shell": "install.sh", + "install_pre_inspection":true, + "uninstall_pre_inspection":true, + "versions":["4.4","5.0","6.0","7.0","8.0","8.2"], + "tip": "soft", + "checks": "server/mongodb", + "path": "server/mongodb", + "display": 1, + "author": "mongodb", + "date": "2021-11-23", + "home": "https://www.mongodb.com/docs/v4.4/installation/", + "type": 0, + "pid": "2" +} \ No newline at end of file diff --git a/plugins/mongodb/init.d/mongodb.service.tpl b/plugins/mongodb/init.d/mongodb.service.tpl new file mode 100644 index 000000000..ceb19302f --- /dev/null +++ b/plugins/mongodb/init.d/mongodb.service.tpl @@ -0,0 +1,37 @@ +[Unit] +Description=MongoDB Database Server +Documentation=https://docs.mongodb.org/manual +After=network-online.target +Wants=network-online.target + +[Service] +User=root +Group=root +#EnvironmentFile=-/etc/default/mongod +Environment="MONGODB_CONFIG_OVERRIDE_NOFORK=1" +Environment="LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib64:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/www/server/lib/openssl11/lib" +PIDFile={$SERVER_PATH}/mongodb/mongodb.pid +ExecStart={$SERVER_PATH}/mongodb/bin/mongod -f {$SERVER_PATH}/mongodb/mongodb.conf +ExecReload=/bin/kill -HUP $MAINPID +#RuntimeDirectory=mongodb +# file size +LimitFSIZE=infinity +# cpu time +LimitCPU=infinity +# virtual memory size +LimitAS=infinity +# open files +LimitNOFILE=64000 +# processes/threads +LimitNPROC=64000 +# locked memory +LimitMEMLOCK=infinity +# total threads (user+kernel) +TasksMax=infinity +TasksAccounting=false + +# Recommended limits for mongod as specified in +# https://docs.mongodb.com/manual/reference/ulimit/#recommended-ulimit-settings + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/mongodb/init.d/mongodb.tpl b/plugins/mongodb/init.d/mongodb.tpl new file mode 100644 index 000000000..a2d7e5b72 --- /dev/null +++ b/plugins/mongodb/init.d/mongodb.tpl @@ -0,0 +1,71 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: Mongodb Service + +### BEGIN INIT INFO +# Provides: Mongodb +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts Mongodb +# Description: starts the MDW-Web +### END INIT INFO + +# Simple Mongodb init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +CONF="{$SERVER_PATH}/mongodb/mongodb.conf" +EXEC={$SERVER_PATH}/mongodb/bin/mongod + +PIDFILE={$SERVER_PATH}/mongodb/mongodb.pid + +mkdir -p {$SERVER_PATH}/mongodb/data +mkdir -p {$SERVER_PATH}/mongodb/logs + +app_start(){ + if [ -f $PIDFILE ];then + kill -9 `cat $PIDFILE` + fi + + echo "Starting mongodb server..." + echo $EXEC -f $CONF + $EXEC -f $CONF >> {$SERVER_PATH}/mongodb/logs.pl 1>&1 2>&1 & +} + +app_stop(){ + if [ ! -f $PIDFILE ] + then + echo "$PIDFILE does not exist, process is not running" + else + PID=$(cat $PIDFILE) + echo "Stopping ..." + kill -9 $PID + while [ -x /proc/${PID} ] + do + echo "Waiting for mongodb to shutdown ..." + sleep 1 + done + echo "mongodb stopped" + rm -rf $PIDFILE + fi +} + + +case "$1" in + start) + app_start + ;; + stop) + app_stop + ;; + restart|reload) + app_stop + sleep 0.3 + app_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/mongodb/install.bk1.sh b/plugins/mongodb/install.bk1.sh new file mode 100755 index 000000000..e0c7a57e9 --- /dev/null +++ b/plugins/mongodb/install.bk1.sh @@ -0,0 +1,286 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/mongodb && /bin/bash install.sh install 5.0.4 +# cd /www/server/mdserver-web/plugins/mongodb && /bin/bash install.sh install 5.0.4 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +sysName=`uname` +echo "use system: ${sysName}" + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +Install_app_mac() +{ + mkdir -p $serverPath/mongodb + if [ "$VERSION" == "5.0" ];then + G_VERSION="5.0.11" + elif [[ "$VERSION" == "4.4" ]]; then + G_VERSION="4.4.11" + fi + + if [ ! -f $serverPath/source/mongodb-macos-x86_64-${G_VERSION}.tgz ];then + wget -O $serverPath/source/mongodb-macos-x86_64-${G_VERSION}.tgz https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-${G_VERSION}.tgz + fi + + cd $serverPath/source && tar -zxvf mongodb-macos-x86_64-${G_VERSION}.tgz + cd mongodb-macos-x86_64-${G_VERSION} && mv ./* $serverPath/mongodb +} + + +Install_Linux_Ubuntu() +{ +##################### Ubuntu start ##################### +if [ "$SYS_VERSION_ID" == "22" ]; then + echo "Not yet supported" + exit 1 +fi + + + + +echo $SYS_VERSION_ID + +SOURCE_NAME=bionic +if [ "$SYS_VERSION_ID" == "18.04" ];then + SOURCE_NAME=bionic +elif [ "$SYS_VERSION_ID" == "16.04" ];then + SOURCE_NAME=xenial +elif [ "$SYS_VERSION_ID" == "20.04" ];then + SOURCE_NAME=focal +fi + +# wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add - +# apt install -y gnupg +# touch /etc/apt/sources.list.d/mongodb-org-4.4.list +# echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list + + +wget -qO - https://www.mongodb.org/static/pgp/server-${VERSION}.asc | sudo apt-key add - +apt install -y gnupg +touch /etc/apt/sources.list.d/mongodb-org-${VERSION}.list +lsb_release -dc + +echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu ${SOURCE_NAME}/mongodb-org/${VERSION} multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-${VERSION}.list + +apt update -y +apt install -y mongodb-org +##################### Ubuntu end ##################### +} + + +Uninstall_Linux_Ubuntu() +{ + +if [ -f /lib/systemd/system/mongod.service ] || [ -f /usr/lib/systemd/system/mongod.service ] ;then + systemctl stop mongod +fi + + + +apt purge -y mongodb-org* +apt autoremove -y +rm -r /var/log/mongodb +rm -r /var/lib/mongodb +} + + +Install_Linux_Debian() +{ +##################### debian start ##################### +if [ "$SYS_VERSION_ID" -eq "11" ] && [ "$VERSION" -eq "4.4" ]; then + echo "Not yet supported" + exit 1 +fi + + +if [ -f /usr/lib/systemd/system/mongod.service ];then + echo 'alreay install exist!' + exit 0 +fi + +wget -qO - https://www.mongodb.org/static/pgp/server-${VERSION}.asc | sudo apt-key add - +apt install -y gnupg +wget -qO - https://www.mongodb.org/static/pgp/server-${VERSION}.asc | sudo apt-key add - + +echo "deb http://repo.mongodb.org/apt/debian buster/mongodb-org/${VERSION} main" | sudo tee /etc/apt/sources.list.d/mongodb-org-${VERSION}.list + +apt update -y +apt install -y mongodb-org + + +echo "mongodb-org hold" | sudo dpkg --set-selections +echo "mongodb-org-server hold" | sudo dpkg --set-selections +echo "mongodb-org-shell hold" | sudo dpkg --set-selections +echo "mongodb-org-mongos hold" | sudo dpkg --set-selections +echo "mongodb-org-tools hold" | sudo dpkg --set-selections + +##################### debian end ##################### +} + + + +Uninstall_Linux_Debian() +{ +systemctl stop mongod +apt purge -y mongodb-org* +apt autoremove -y +rm -r /var/log/mongodb +rm -r /var/lib/mongodb +} + + +Install_Linux_Opensuse() +{ +##################### opensuse start ##################### +if [ "$SYS_VERSION_ID" -gt "15" ]; then + echo "Not yet supported" + exit 1 +fi + + +if [ -f /usr/lib/systemd/system/mongod.service ];then + echo 'alreay exist!' + exit 0 +fi + +rpm --import https://www.mongodb.org/static/pgp/server-${VERSION}.asc +zypper addrepo --gpgcheck "https://repo.mongodb.org/zypper/suse/15/mongodb-org/${VERSION}/x86_64/" mongodb +zypper -n install mongodb-org +# zypper install mongodb-org-4.4.15 mongodb-org-server-4.4.15 mongodb-org-shell-4.4.15 mongodb-org-mongos-4.4.15 mongodb-org-tools-4.4.15 + + +##################### opensuse end ##################### +} + + +Uninstall_Linux_Opensuse() +{ +systemctl stop mongod +zypper remove -y $(rpm -qa | grep mongodb-org) +rm -r /var/log/mongodb +rm -r /var/lib/mongo +} + + + + +# https://repo.mongodb.org/yum/redhat/7/mongodb-org/5.0/x86_64/RPMS/mongodb-org-server-5.0.4-1.el7.x86_64.rpm +Install_Linux_CentOS() +{ +if [ -f /usr/lib/systemd/system/mongod.service ];then + echo 'alreay exist!' + exit 0 +fi +#清空mongo的repo源 +rm -rf /etc/yum.repos.d/mongodb-org-* +##################### centos start ##################### +echo " +[mongodb-org-${VERSION}] +name=MongoDB Repository +baseurl=https://repo.mongodb.org/yum/redhat/\$releasever/mongodb-org/${VERSION}/x86_64/ +gpgcheck=1 +enabled=1 +gpgkey=https://www.mongodb.org/static/pgp/server-${VERSION}.asc +" > /etc/yum.repos.d/mongodb-org-${VERSION}.repo + +yum install -y mongodb-org +##################### centos end ##################### +} + +Uninstall_Linux_CentOS() +{ +systemctl stop mongod +yum erase -y $(rpm -qa | grep mongodb-org) +rm -r /var/log/mongodb +rm -r /var/lib/mongo +rm -rf /etc/yum.repos.d/mongodb-org-${VERSION}.repo +} + + +Install_app_linux() +{ + if [ "$OSNAME" == "ubuntu" ];then + Install_Linux_Ubuntu + elif [ "$OSNAME" == "debian" ];then + Install_Linux_Debian + elif [ "$OSNAME" == "centos" ];then + Install_Linux_CentOS + elif [ "$OSNAME" == "opensuse" ];then + Install_Linux_Opensuse + else + echo "Not yet supported" + exit 1 + fi +} + + +Install_app() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + + if [ "macos" == "$OSNAME" ];then + Install_app_mac + else + Install_app_linux + fi + + if [ "$?" == "0" ];then + mkdir -p $serverPath/mongodb + echo "${VERSION}" > $serverPath/mongodb/version.pl + echo '安装完成' + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/mongodb/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/mongodb/index.py initd_install + fi +} + + + +Uninstall_app_linux() +{ +################## +if [ "$OSNAME" == "ubuntu" ];then + Uninstall_Linux_Ubuntu +elif [ "$OSNAME" == "debian" ];then + Uninstall_Linux_Debian +elif [ "$OSNAME" == "centos" ];then + Uninstall_Linux_CentOS +elif [ "$OSNAME" == "opensuse" ];then + Uninstall_Linux_Opensuse +else + echo "ok" +fi +################## +} + +Uninstall_app() +{ + if [ "macos" == "$OSNAME" ];then + echo 'mac' + else + Uninstall_app_linux + fi + + rm -rf $serverPath/mongodb + echo "Uninstall_mongodb" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mongodb/install.bk2.sh b/plugins/mongodb/install.bk2.sh new file mode 100755 index 000000000..3c45158da --- /dev/null +++ b/plugins/mongodb/install.bk2.sh @@ -0,0 +1,106 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/mongodb && /bin/bash install.sh install 5.0.4 +# cd /www/server/mdserver-web/plugins/mongodb && /bin/bash install.sh install 5.0.4 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +sysName=`uname` +echo "use system: ${sysName}" + +# bash ${rootPath}/scripts/getos.sh +# OSNAME=`cat ${rootPath}/data/osname.pl` +# SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +Install_app() +{ + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + echo '正在安装脚本文件...' + MG_DIR=$serverPath/source/mongodb + mkdir -p $MG_DIR + + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + echo "cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh" + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + + if [ ! -f $MG_DIR/mongodb-src-r${VERSION}.tar.gz ]; then + wget --no-check-certificate -O $MG_DIR/mongodb-src-r${VERSION}.tar.gz https://fastdl.mongodb.org/src/mongodb-src-r${VERSION}.tar.gz + echo "wget --no-check-certificate -O $MG_DIR/mongodb-src-r${VERSION}.tar.gz https://fastdl.mongodb.org/src/mongodb-src-r${VERSION}.tar.gz" + fi + + if [ ! -d $MG_DIR/mongodb-src-r${VERSION} ];then + cd $MG_DIR && tar -zxvf $MG_DIR/mongodb-src-r${VERSION}.tar.gz + fi + + cd $MG_DIR/mongodb-src-r${VERSION} && python3 -m pip install requirements_parser + cd $MG_DIR/mongodb-src-r${VERSION} && python3 -m pip install -r etc/pip/compile-requirements.txt + + # cd $MG_DIR/mongodb-src-r${VERSION} && python3 buildscripts/scons.py all -j 2 + # echo "cd $MG_DIR/mongodb-src-r${VERSION} && python3 buildscripts/scons.py all -j 2" + # cd $MG_DIR/mongodb-src-r${VERSION} && python3 buildscripts/scons.py all MONGO_VERSION=${VERSION} -j 4 + + + cd $MG_DIR/mongodb-src-r${VERSION} && python3 buildscripts/scons.py install-mongod MONGO_VERSION=${VERSION} -j ${cpuCore} + cd $MG_DIR/mongodb-src-r${VERSION} && python3 buildscripts/scons.py DESTDIR=$serverPath/mongodb install-mongod MONGO_VERSION=${VERSION} \ + --ssl=off + echo "cd $MG_DIR/mongodb-src-r${VERSION} && python3 buildscripts/scons.py DESTDIR=$serverPath/mongodb install MONGO_VERSION=${VERSION} \ + --ssl=off CPPPATH=$serverPath/lib/openssl/include \ + LIBPATH=$serverPath/lib/openssl/lib" + + if [ "$?" == "0" ];then + mkdir -p $serverPath/mongodb + echo "${VERSION}" > $serverPath/mongodb/version.pl + echo '安装完成' + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/mongodb/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/mongodb/index.py initd_install + fi +} + + +Uninstall_app() +{ + + rm -rf $serverPath/mongodb + echo "Uninstall_mongodb" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mongodb/install.sh b/plugins/mongodb/install.sh new file mode 100755 index 000000000..a59ce4e29 --- /dev/null +++ b/plugins/mongodb/install.sh @@ -0,0 +1,105 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://www.mongodb.com/try/download/community + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/mongodb && /bin/bash install.sh install 7.0 +# cd /www/server/mdserver-web/plugins/mongodb && /bin/bash install.sh install 7.0 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mongodb/index.py start + + + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +sysName=`uname` +echo "use system: ${sysName}" + +OSNAME=`bash ${rootPath}/scripts/getos.sh` + +if [ "" == "$OSNAME" ];then + OSNAME=`cat ${rootPath}/data/osname.pl` +fi + +if [ "macos" != "$OSNAME" ];then + SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +fi + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi +Install_app() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + + # if id mongodb &> /dev/null ;then + # echo "mongodb uid is `id -u mongodb`" + # echo "mongodb shell is `grep "^mongodb:" /etc/passwd |cut -d':' -f7 `" + # else + # groupadd mongodb + # useradd -g mongodb mongodb + # fi + + # if [ "centos" == "$OSNAME" ];then + # OSNAME=rhel + # fi + + # if [ "fedora" == "$OSNAME" ];then + # OSNAME=rhel + # fi + + # if [ "rocky" == "$OSNAME" ];then + # OSNAME=rhel + # fi + + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_11.sh + + shell_file=${curPath}/versions/${VERSION}/${OSNAME}.sh + + if [ -f $shell_file ];then + bash -x $shell_file + else + echo '不支持...' + exit 1 + fi + + if [ "$?" == "0" ];then + mkdir -p $serverPath/mongodb + echo "${VERSION}" > $serverPath/mongodb/version.pl + echo 'mongodb安装完成' + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/mongodb/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/mongodb/index.py initd_install + fi +} + +Uninstall_app() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/mongodb/index.py stop + rm -rf $serverPath/mongodb + + + if [ -f /usr/lib/systemd/system/mongodb.service ] || [ -f /lib/systemd/system/mongodb.service ];then + systemctl stop mongodb + systemctl disable mongodb + rm -rf /usr/lib/systemd/system/mongodb.service + rm -rf /lib/systemd/system/mongodb.service + systemctl daemon-reload + fi + + echo 'mongodb卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/mongodb/js/mongodb.js b/plugins/mongodb/js/mongodb.js new file mode 100644 index 000000000..526372bd4 --- /dev/null +++ b/plugins/mongodb/js/mongodb.js @@ -0,0 +1,1274 @@ +function mgPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'mongodb'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function mgPostN(method, version, args,callback){ + + var req_data = {}; + req_data['name'] = 'mongodb'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function mgAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + return syncPost('/plugins/run', {name:'mongodb', func:method, args:_args}); +} + + +function mongoStatus() { + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'mongodb', func:'run_info'}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var rdata = $.parseJSON(data.data); + var con = '
                        \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                        字段当前值说明
                        host' + rdata.host + '服务器
                        version' + rdata.version + '版本
                        db_path' + rdata.db_path + '数据路径
                        uptime' + rdata.uptime + '已运行秒
                        connections' + rdata.connections + '当前链接数
                        collections' + rdata.collections + '文档数
                        insert' + rdata.pf['insert'] + '插入命令数
                        query' + rdata.pf['query'] + '查询命令数
                        update' + rdata.pf['update'] + '更新命令数
                        delete' + rdata.pf['delete'] + '删除命令数
                        getmore' + rdata.pf['getmore'] + 'getmore命令数
                        command' + rdata.pf['command'] + '执行命令数
                        '; + + $(".soft-man-con").html(con); + },'json'); +} + + +function mongoDocStatus() { + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'mongodb', func:'run_doc_info'}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var rdata = $.parseJSON(data.data); + + var t = ''; + for(var i=0; i'; + t += ''+toSize(rdata.dbs[i]["totalSize"])+''; + t += ''+toSize(rdata.dbs[i]["storageSize"])+''; + t += ''+toSize(rdata.dbs[i]["dataSize"])+''; + t += ''+toSize(rdata.dbs[i]["indexSize"])+''; + t += ''+rdata.dbs[i]["indexes"]+''; + t += ''+rdata.dbs[i]["objects"]+''; + t += ''; + } + // console.log(t); + + var con = '
                        \ + \ + \ + '+t+'\ +
                        库名大小存储大小数据索引文档数据对象
                        '; + // console.log(rdata.dbs); + + $(".soft-man-con").html(con); + },'json'); +} + +function mongoReplStatus() { + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'mongodb', func:'run_repl_info'}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var rdata = $.parseJSON(data.data); + var rdata = rdata.data; + + var tbody = ''; + if (rdata.status == '无'){ + tbody += '无数据'; + } else{ + tbody += '状态' + rdata.status + '主/从\ + 同步文档' + rdata.setName + '文档名\ + hosts' + rdata.hosts + '服务器所有节点\ + primary' + rdata.primary + '主节点\ + me' + rdata.me + '本机'; + } + + var tbody_members = ''; + var member_list = rdata['members']; + for (var i = 0; i < member_list.length; i++) { + tbody_members += ''+member_list[i]['name']+'' + member_list[i]['stateStr'] + ''+member_list[i]['uptime']+''; + } + + // console.log(rdata); + var repl_on = 'btn-danger'; + var repl_on_title = '未开启'; + if ('repl_name' in rdata && rdata['repl_name'] != ''){ + repl_on = ''; + repl_on_title = '已开启'; + } + + var con = "

                        \ + Mongodb副本配置\ + \ + \ +


                        "; + + con += '
                        \ + \ + \ + '+tbody+'\ +
                        字段当前值说明
                        \ +
                        '; + + con += '
                        \ + \ + \ + '+tbody_members+'\ +
                        IP状态在线
                        \ +
                        '; + + $(".soft-man-con").html(con); + },'json'); +} + +//设置副本名称 +function mongoReplCfgReplSetName(){ + // + layer.open({ + type: 1, + area: '300px', + title: '设置副本名称', + closeBtn: 1, + shift: 5, + shadeClose: false, + btn:["提交","关闭"], + content: "
                        \ +
                        \ + 同步副本名称\ +
                        \ + \ +
                        \ +
                        \ +
                        ", + + success: function(){ + // // console.log(rdata); + // var rlist = rdata['dbs']; + // var dbs = []; + // var selectHtml = ''; + // for (var i = 0; i < rlist.length; i++) { + // // console.log(rlist[i]['db']); + // var dbname = rlist[i]['db']; + + // if (['admin','local','config'].includes(dbname)){ + // } else { + // dbs.push(dbname); + // } + // } + + // if (dbs.length == 0 ){ + // selectHtml += ""; + // } + + // for (index in dbs) { + // selectHtml += ""; + // } + + // $('select[name="replSetName"]').html(selectHtml); + }, + yes:function(index){ + var data = {}; + data['name'] = $('input[name=replSetName]').val(); + if (data['name'] == ''){ + layer.msg("副本名称不能为空"); + return; + } + mgPost('repl_set_name', '',data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata['status']){ + layer.close(index); + mongoReplCfgInit(); + } + },{icon: rdata.status ? 1 : 2}); + }); + } + }); +} + +function mongoReplCfgNodes(idx,host, priority, votes, arbiterOnly){ + + if (typeof(host) == 'undefined'){ + host = '127.0.0.1:27017'; + } + + if (typeof(priority) == 'undefined'){ + priority = '1'; + } + + if (typeof(votes) == 'undefined'){ + votes = '1'; + } + + if (typeof(arbiterOnly) == 'undefined'){ + arbiterOnly = '1'; + } + + var title_name = '添加节点'; + if (idx>-1){ + title_name = '编辑节点'; + } + + layer.open({ + type: 1, + area: '500px', + title: '添加节点', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                        \ +
                        \ + 节点服务:\ +
                        \ + \ +
                        \ +
                        \ +
                        \ + priority:\ +
                        \ + \ + 值越大,优先权越高\ +
                        \ +
                        \ +
                        \ + votes:\ +
                        \ + \ + 一般是0或者1\ +
                        \ +
                        \ +
                        \ + 仲裁员:\ +
                        \ + \ +
                        \ +
                        \ +
                        ", + yes:function(index){ + // var data = $("#mod_pwd").serialize(); + var data = {}; + data['node'] = $('input[name=node]').val(); + data['priority'] = $('input[name=priority]').val(); + data['votes'] = $('input[name=votes]').val(); + data['arbiterOnly'] = $('select[name=arbiterOnly]').val(); + data['idx'] = idx; + mgPost('repl_set_node', '',data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata['status']){ + layer.close(index); + mongoReplCfgInit(); + } + },{icon: rdata.status ? 1 : 2},rdata['status']?2000:10000); + }); + } + }); +} + +function mongoReplCfgDelNode(host){ + mgPost('del_repl_node', '', {"node":host}, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata['status']); + showMsg(rdata.msg,function(){ + if (rdata['status']){ + mongoReplCfgInit(); + } + },{icon: rdata.status ? 1 : 2}); + }); +} + +function mongoReplCfgInit(){ + mgPostN('get_repl_config', '', '', function(data){ + var rdata = $.parseJSON(data.data); + $('#repl_name').html("同步副本:"+rdata.data['name']); + + var node = ''; + for (var i = 0; i < rdata.data['nodes'].length; i++) { + var t = rdata.data['nodes'][i]; + + var arbiterOnly = '否'; + if(t['arbiterOnly']==1){ + arbiterOnly = '是'; + } + + var op = '删除'; + op += ' | 编辑'; + node += ''+t['host']+''+t['priority']+''+t['votes']+''+arbiterOnly+''+op+''; + } + $('#repl_node tbody').html(node); + }); +} + +function mongoReplCfg(){ + layer.open({ + type: 1, + title: "副本设置", + area: ['580px', '380px'], + closeBtn: 1, + shadeClose: false, + btn: ["初始化","取消","添加节点","设置同步副本","关闭副本同步"], + content: '
                        \ +
                        \ + \ + 同步副本:\ + \ +
                        \ +
                        \ +
                        \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                        节点优先级投票仲裁者操作
                        \ +
                        \ +
                        \ +
                        ', + success:function(){ + mongoReplCfgInit(); + }, + yes:function(){ + mgPost('repl_init', '', '', function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + mongoReplStatus(); + },{icon: rdata.status ? 1 : 2}); + }); + return false; + }, + btn3:function(){ + mongoReplCfgNodes(-1); + return false; + }, + btn4:function(){ + mongoReplCfgReplSetName(); + return false; + }, + btn5:function(){ + mgPost('repl_close', '', '', function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata['status']){ + mongoReplStatus(); + } + },{icon: rdata.status ? 1 : 2}); + }); + return false; + } + }); +} + +//配置修改 +function mongoSetConfig() { + mgPost('get_config', '','',function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata['status']){ + layer.msg(rdata['msg']); + return; + } + rdata = rdata.data; + if (rdata['security']['authorization'] == 'enabled'){ + var body_auth = ''; + } else { + var body_auth = ''; + } + + var body = "
                        " + + "

                        IP:监听IP请勿随意修改

                        " + + "

                        port: 监听端口,一般无需修改

                        " + + "

                        dbPath:数据存储位置

                        " + + "

                        path:日志文件位置

                        " + + "

                        pidFilePath:PID保存路径

                        " + + "

                        安全认证:"+body_auth+"

                        " + + "
                        \ + \ + " + + "
                        "; + + // console.log(body); + $(".soft-man-con").html(body); + }); +} + +function mongoConfigAuth(){ + mgPost('set_config_auth', '','',function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function mongoConfigSave(){ + var data = {}; + data['bind_ip'] = $('input[name="bind_ip"]').val(); + data['port'] = $('input[name="port"]').val(); + data['data_path'] = $('input[name="data_path"]').val(); + data['log'] = $('input[name="log"]').val(); + data['pid_file_path'] = $('input[name="pid_file_path"]').val(); + + mgPost('set_config', '',data,function(rdata){ + // console.log(rdata); + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function dbList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + _data['page'] = page; + _data['page_size'] = 10; + // console.log(_data); + mgPost('get_db_list', '',_data, function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var list = ''; + for(i in rdata.data){ + list += ''; + list +=''; + list += '' + rdata.data[i]['name'] +''; + list += '' + rdata.data[i]['username'] +''; + list += '' + + '***' + + ''+ + ''+ + ''; + + + list += ''+rdata.data[i]['ps']+''; + list += ''; + + list += ''+(rdata.data[i]['is_backup']?'已备份':'未备份') +' | '; + + list += '工具 | ' + + '权限 | ' + + '改密 | ' + + '删除' + + ''; + list += ''; + } + + // + // \ + var con = '
                        \ + \ + \ + \ + \ + \ +
                        \ +
                        \ + \ + \ + \ + \ + \ + '+ + // ''+ + '\ + \ + \ + '+ list +'\ +
                        数据库名用户名密码备份备注操作
                        \ +
                        \ +
                        \ +
                        \ + 同步选中\ + 同步所有\ + 从服务器获取\ +
                        \ +
                        \ +
                        '; + + $(".soft-man-con").html(con); + $('#databasePage').html(rdata.page); + + readerTableChecked(); + }); +} + +function addDatabase(type){ + layer.open({ + type: 1, + area: '500px', + title: '添加数据库', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                        \ +
                        \ + 数据库名\ +
                        \ +
                        \ +
                        \ +
                        用户名
                        \ +
                        \ + 密码\ +
                        \ +
                        \ +
                        \ + 访问权限\ +
                        \ + \ +
                        \ +
                        \ + \ +
                        ", + success:function(){ + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='db_user']").val(v); + $("input[name='ps']").val(v); + }); + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index) { + var data = $("#add_db").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['address']){ + dataObj['address'] = dataObj['dataAccess']; + } + mgPost('add_db', '',dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + dbList(); + } + },{icon: rdata.status ? 1 : 2}, 2000); + }); + } + }); +} + +function setRootPwd(type, pwd){ + if (type==1){ + var password = $("#MyPassword").val(); + mgPost('set_root_pwd', '',{password:password}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + btn:["提交", "关闭", "复制ROOT密码", "强制修改"], + shadeClose: true, + content: "
                        \ +
                        \ + root密码\ +
                        \ + \ +
                        \ +
                        \ +
                        ", + yes:function(layerIndex){ + var password = $("#MyPassword").val(); + mgPost('set_root_pwd', '',{password:password}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layerIndex); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + }, + btn3:function(){ + var password = $("#MyPassword").val(); + copyText(password); + return false; + }, + btn4:function(layerIndex){ + layer.confirm('强制修改,是为了在重建时使用,确定强制?', { + btn: ['确定', '取消'] + }, function(index, layero){ + layer.close(index); + var password = $("#MyPassword").val(); + mgPost('set_root_pwd', '',{password:password,force:'1'}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layerIndex); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + }); + return false; + } + }); +} + +function syncGetDatabase(){ + mgPost('sync_get_databases', '', '', function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function showHidePass(obj){ + var a = "glyphicon-eye-open"; + var b = "glyphicon-eye-close"; + + if($(obj).hasClass(a)){ + $(obj).removeClass(a).addClass(b); + $(obj).prev().text($(obj).prev().attr('data-pw')) + } + else{ + $(obj).removeClass(b).addClass(a); + $(obj).prev().text('***'); + } +} + +function setDbPs(id, name, obj) { + var _span = $(obj); + var _input = $(""); + _span.hide().after(_input); + _input.focus(); + _input.blur(function(){ + $(this).remove(); + var ps = _input.val(); + _span.text(ps).show(); + var data = {name:name,id:id,ps:ps}; + mgPost('set_db_ps', '',data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + }); + _input.keyup(function(){ + if(event.keyCode == 13){ + _input.trigger('blur'); + } + }); +} + +function delDb(id, name){ + safeMessage('删除['+name+']','您真的要删除['+name+']吗?',function(){ + var data='id='+id+'&name='+name; + mgPost('del_db', '', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); +} + +function delDbTable( name, table_name){ + safeMessage('删除['+name+']','您真的要删除['+table_name+']吗?',function(){ + var data='name='+name+'&table_name='+table_name; + mgPost('del_db_table', '', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + repTools(name); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); +} + +function delDbBatch(){ + var arr = []; + $('input[type="checkbox"].check:checked').each(function () { + var _val = $(this).val(); + var _name = $(this).parent().next().text(); + if (!isNaN(_val)) { + arr.push({'id':_val,'name':_name}); + } + }); + + safeMessage('批量删除数据库','您共选择了[2]个数据库,删除后将无法恢复,真的要删除吗?',function(){ + var i = 0; + $(arr).each(function(){ + var data = mgAsyncPost('del_db', this); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + } + i++; + }); + + var msg = '成功删除['+i+']个数据库!'; + showMsg(msg,function(){ + dbList(); + },{icon: 1}, 600); + }); +} + +function setDbPass(id, username, password){ + layer.open({ + type: 1, + area: '500px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                        \ +
                        \ + 用户名\ +
                        \ +
                        \ +
                        \ + 密码\ +
                        \ + \ +
                        \ +
                        \ + \ +
                        ", + yes:function(index){ + // var data = $("#mod_pwd").serialize(); + var data = {}; + data['name'] = $('input[name=name]').val(); + data['password'] = $('#MyPassword').val(); + data['id'] = $('input[name=id]').val(); + mgPost('set_user_pwd', '',data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); +} + +function repTools(db_name, res){ + + mgPost('get_db_info', '', {name:db_name}, function(data){ + var rdata = $.parseJSON(data.data); + var rdata = rdata.data; + + layer.open({ + type: 1, + title: "MongoDB工具箱【" + db_name + "】", + area: ['780px', '480px'], + closeBtn: 1, + shadeClose: false, + content: '
                        \ + \ +
                        \ +
                        \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                        集合名称文档数量内存中的大小对象平均大小存储大小索引数量索引大小操作
                        \ +
                        \ +
                        \ +
                        ', + success:function(layer_id, layer_index){ + + var tbody = ''; + for (var i = 0; i < rdata.collection_list.length; i++) { + tbody += '\ + ' + rdata.collection_list[i].collection_name + '\ + ' + rdata.collection_list[i].count + '\ + ' + toSize(rdata.collection_list[i].size) + '\ + ' + toSize(rdata.collection_list[i].avg_obj_size) + '\ + ' + toSize(rdata.collection_list[i].storage_size) + '\ + ' + rdata.collection_list[i].nindexes + '\ + ' + toSize(rdata.collection_list[i].total_index_size) + '\ + ' + '删除'+'\ + '; + } + + $('#mongodb_list tbody').html(tbody); + + $('#mongodb_list .delete').click(function(){ + var index = $(this).data('index'); + + var name = db_name; + var table_name = rdata.collection_list[index].collection_name; + + safeMessage('删除['+name+']','您真的要删除['+table_name+']吗?',function(){ + var data='name='+name+'&table_name='+table_name; + mgPost('del_db_table', '', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layer_index); + repTools(name); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); + + }); + } + }); + tableFixed('database_fix'); + }); +} + +function syncToDatabase(type){ + var data = []; + $('input[type="checkbox"].check:checked').each(function () { + if (!isNaN($(this).val())) data.push($(this).val()); + }); + var postData = 'type='+type+'&ids='+JSON.stringify(data); + mgPost('sync_to_databases', '',postData, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function setDbAccess(username,name){ + mgPost('get_db_access','','username='+username, function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,shade: [0.3, '#000']}); + return; + } + var db_roles = rdata.data.roles; + var all_roles = rdata.data.all_roles; + // console.log(all_roles); + var role_list; + var index = layer.open({ + type: 1, + area: '500px', + title: '设置数据库权限', + closeBtn: 1, + shift: 5, + btn:["提交","取消"], + shadeClose: true, + content: "
                        \ +
                        \ + 访问权限\ +
                        \ +
                        \ +
                        \ +
                        \ +
                        ", + success:function(layers, index){ + document.getElementById('layui-layer' + index).getElementsByClassName('layui-layer-content')[0].style.overflow = 'unset'; + + var role_data = []; + for (var i = 0; i < db_roles.length; i++) { + var t = {}; + t['name'] = db_roles[i]['role']; + t['value'] = db_roles[i]['role']; + role_data.push(t); + } + + role_list = xmSelect.render({ + el: '#role_list', + language: 'zn', + toolbar: {show: true,}, + paging: false, + data: role_data, + }); + + var pdata = []; + for (var i = 0; i < all_roles.length; i++) { + var tval = all_roles[i]; + var isSelected = false; + for (var db_i = 0; db_i < db_roles.length; db_i++) { + var db_name = db_roles[db_i]['role']; + if (db_name == tval['role']){ + isSelected = true; + } + } + + var t = {name:tval['name'],value:tval['role']}; + if (isSelected){ + t = {name:tval['name'],value:tval['role'], selected: true}; + } + pdata.push(t); + } + role_list.update({data:pdata}); + }, + yes:function(index){ + var data = $("#set_db_access").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + dataObj['username'] = username; + dataObj['name'] = name; + mgPost('set_db_access', '',dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); + + }); +} + + +function setBackup(db_name){ + var layerIndex = layer.open({ + type: 1, + title: "数据库[MongoDB]备份详情", + area: ['600px', '280px'], + closeBtn: 1, + shadeClose: false, + content: '
                        \ +
                        \ + \ + \ +
                        \ +
                        \ +
                        \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                        文件名称文件大小备份时间操作
                        \ +
                        \ +
                        \ +
                        ', + success:function(index){ + $('#btn_backup').click(function(){ + mgPost('set_db_backup', '',{name:db_name}, function(data){ + showMsg('执行成功!', function(){ + setBackupReq(db_name); + }, {icon:1}, 2000); + }); + }); + + $('#btn_local_import').click(function(){ + setLocalImport(db_name); + }); + + setBackupReq(db_name); + }, + }); +} + +function setBackupReq(db_name, obj){ + mgPost('get_db_backup_list', '', {name:db_name}, function(data){ + var rdata = $.parseJSON(data.data); + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + tbody += '\ + ' + rdata.data[i]['name'] + '\ + ' + rdata.data[i]['size'] + '\ + ' + rdata.data[i]['time'] + '\ + \ + 导入 | \ + 下载 | \ + 删除\ + \ + '; + } + $('#database_table tbody').html(tbody); + }); +} + +function delBackup(filename, name, path){ + if(typeof(path) == "undefined"){ + path = ""; + } + mgPost('delete_db_backup','',{filename:filename,path:path},function(){ + layer.msg('执行成功!'); + setTimeout(function(){ + setBackupReq(name); + },2000); + }); +} + +function downloadBackup(file){ + window.open('/files/download?filename='+encodeURIComponent(file)); +} + +function importBackup(file,name){ + mgPost('import_db_backup','',{file:file,name:name}, function(data){ + layer.msg('执行成功!'); + }); +} + +function setLocalImport(db_name){ + + //上传文件 + function uploadDbFiles(upload_dir){ + var up_db = layer.open({ + type:1, + closeBtn: 1, + title:"上传导入文件["+upload_dir+']', + area: ['500px','300px'], + shadeClose:false, + content:'
                        \ + \ + \ + \ + \ + \ + \ + 文件编码:\ + \ + \ + \ +
                          \ +
                          ', + success:function(){ + $('#filesClose').click(function(){ + layer.close(up_db); + }); + } + + }); + uploadStart(function(){ + getList(); + layer.close(up_db); + }); + } + + function getList(){ + mgPost('get_db_backup_import_list','',{}, function(data){ + var rdata = $.parseJSON(data.data); + + var file_list = rdata.data.list; + var upload_dir = rdata.data.upload_dir; + + var tbody = ''; + for (var i = 0; i < file_list.length; i++) { + tbody += '\ + ' + file_list[i]['name'] + '\ + ' + file_list[i]['size'] + '\ + ' + file_list[i]['time'] + '\ + \ + 导入 | \ + 删除\ + \ + '; + } + + $('#import_db_file_list').html(tbody); + $('input[name="upload_dir"]').val(upload_dir); + + $("#import_db_file_list .del").on('click',function(){ + var index = $(this).attr('index'); + var filename = file_list[index]["name"]; + mgPost('delete_db_backup','',{filename:filename,path:upload_dir},function(){ + showMsg('执行成功!', function(){ + getList(); + },{icon:1},2000); + }); + }); + }); + } + + var layerIndex = layer.open({ + type: 1, + title: "从文件导入数据", + area: ['600px', '380px'], + closeBtn: 1, + shadeClose: false, + content: '
                          \ +
                          \ + \ +
                          \ +
                          \ + \ +
                          \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                          文件名称文件大小备份时间操作
                          \ +
                          \ +
                            \ +
                          • 仅支持sql、zip、sql.gz、(tar.gz|gz|tgz)
                          • \ +
                          • zip、tar.gz压缩包结构:test.zip或test.tar.gz压缩包内,必需包含test.sql
                          • \ +
                          • 若文件过大,您还可以使用SFTP工具,将数据库文件上传到/www/backup/import
                          • \ +
                          \ +
                          \ +
                          ', + success:function(index){ + $('#btn_file_upload').click(function(){ + var upload_dir = $('input[name="upload_dir"]').val(); + uploadDbFiles(upload_dir); + }); + + getList(); + }, + }); +} + +function importDbExternal(file,name){ + mgPost('import_db_external','',{file:file,name:name}, function(data){ + layer.msg('执行成功!'); + }); +} + +function otherFunc(){ + var con = '

                          \ + \ + \ +

                          '; + $(".soft-man-con").html(con); +} + +function cronAddCheck(){ + mgPost('cron_add_check', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function cronDelCheck(){ + mgPost('cron_del_check', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function mgdbReadme(){ + var readme = '
                            '; + readme += '
                          • 认证同步说明
                          • '; + readme += '
                          • root/用户,配置Key完全一致才能同步。
                          • '; + readme += '
                          '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/mongodb/scripts/backup.py b/plugins/mongodb/scripts/backup.py new file mode 100644 index 000000000..bc3db6bf1 --- /dev/null +++ b/plugins/mongodb/scripts/backup.py @@ -0,0 +1,235 @@ +# coding: utf-8 +#----------------------------- +# 网站备份工具 +#----------------------------- + +import sys +import os +import re +import time +import yaml + +if sys.platform != 'darwin': + os.chdir('/www/server/mdserver-web') + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import core.db as db + + + +def getPluginName(): + return 'mongodb' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/mongodb.conf" + return path + +def getConfigData(): + cfg = getConf() + config_data = mw.readFile(cfg) + try: + config = yaml.safe_load(config_data) + except: + config = { + "systemLog": { + "destination": "file", + "logAppend": True, + "path": mw.getServerDir()+"/mongodb/log/mongodb.log" + }, + "storage": { + "dbPath": mw.getServerDir()+"/mongodb/data", + "directoryPerDB": True, + "journal": { + "enabled": True + } + }, + "processManagement": { + "fork": True, + "pidFilePath": mw.getServerDir()+"/mongodb/log/mongodb.pid" + }, + "net": { + "port": 27017, + "bindIp": "0.0.0.0" + }, + "security": { + "authorization": "enabled", + "javascriptEnabled": False + } + } + return config + + +def getConfIp(): + data = getConfigData() + return data['net']['bindIp'] + +def getConfPort(): + data = getConfigData() + return data['net']['port'] + +def getConfAuth(): + data = getConfigData() + return data['security']['authorization'] + +def pSqliteDb(dbname='users'): + file = getServerDir() + '/mongodb.db' + name = 'mongodb' + + sql_file = getPluginDir() + '/config/mongodb.sql' + import_sql = mw.readFile(sql_file) + # print(sql_file,import_sql) + md5_sql = mw.md5(import_sql) + + import_sign = False + save_md5_file = getServerDir() + '/import_mongodb.md5' + if os.path.exists(save_md5_file): + save_md5_sql = mw.readFile(save_md5_file) + if save_md5_sql != md5_sql: + import_sign = True + mw.writeFile(save_md5_file, md5_sql) + else: + mw.writeFile(save_md5_file, md5_sql) + + if not os.path.exists(file) or import_sql: + conn = mw.M(dbname).dbPos(getServerDir(), name) + csql_list = import_sql.split(';') + for index in range(len(csql_list)): + conn.execute(csql_list[index], ()) + + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + +def mongdbClient(): + import pymongo + port = getConfPort() + auth = getConfAuth() + ip = getConfIp() + mg_root = pSqliteDb('config').where('id=?', (1,)).getField('mg_root') + # print(ip,port,auth,mg_root) + if auth == 'disabled': + client = pymongo.MongoClient(host=ip, port=int(port), directConnection=True) + else: + # uri = "mongodb://root:"+mg_root+"@127.0.0.1:"+str(port) + # client = pymongo.MongoClient(uri) + client = pymongo.MongoClient(host=ip, port=int(port), directConnection=True, username='root',password=mg_root) + return client + +class backupTools: + + def getDbBackupList(self,dbname=''): + bkDir = mw.getFatherDir() + '/backup/database' + blist = os.listdir(bkDir) + r = [] + + bname = 'mongodb_' + dbname + blen = len(bname) + for x in blist: + fbstr = x[0:blen] + if fbstr == bname: + r.append(x) + return r + + def backupDatabase(self, name, count): + db_path = mw.getServerDir() + '/mongodb' + db_name = 'mongodb' + name = mw.M('databases').dbPos(db_path, db_name).where('name=?', (name,)).getField('name') + + startTime = time.time() + if not name: + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + log = "数据库[" + name + "]不存在!" + print("★[" + endDate + "] " + log) + print( + "----------------------------------------------------------------------------") + return + + backup_path = mw.getBackupDir() + '/database' + if not os.path.exists(backup_path): + mw.execShell("mkdir -p " + backup_path) + + time_now = time.strftime('%Y%m%d_%H%M%S', time.localtime()) + backup_name = "mongodb_" + name + "_" + time_now + ".tar.gz" + filename = backup_path + "/"+backup_name + + port = getConfPort() + auth = getConfAuth() + mg_root = pSqliteDb('config').where('id=?', (1,)).getField('mg_root') + uoption = '' + if auth != 'disabled': + uoption =' --authenticationDatabase admin -u root -p '+mg_root + + cmd = db_path + "/bin/mongodump "+uoption+" --port "+str(port)+" -d "+name+" -o "+backup_path + # print(cmd) + mw.execShell(cmd) + cmd_gz = "cd "+backup_path+"/"+name+" && tar -zcvf "+filename + " ./" + mw.execShell(cmd_gz) + mw.execShell("rm -rf "+ backup_path+"/"+name) + + if not os.path.exists(filename): + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + log = "数据库[" + name + "]备份失败!" + print("★[" + endDate + "] " + log) + print("----------------------------------------------------------------------------") + return + + + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + outTime = time.time() - startTime + + # print(outTime) + log = "数据库MongoDB[" + name + "]备份成功,用时[" + str(round(outTime, 2)) + "]秒" + mw.writeLog('计划任务', log) + print("★[" + endDate + "] " + log) + print("|---保留最新的[" + count + "]份备份") + print("|---文件名:" + filename) + + backups = self.getDbBackupList(name) + + # 清理多余备份 + num = len(backups) - int(count) + if num > 0: + for backup in backups: + mw.execShell("rm -f " + backup_path + "/"+backup) + num -= 1 + print("|---已清理过期备份文件:" + backup) + if num < 1: + break + + def backupDatabaseAll(self, save): + db_path = mw.getServerDir() + '/mongodb' + db_name = 'mongodb' + databases = mw.M('databases').dbPos( + db_path, db_name).field('name').select() + for db in databases: + self.backupDatabase(db['name'], save) + + def findPathName(self, path, filename): + f = os.scandir(path) + l = [] + for ff in f: + if ff.name.find(filename) > -1: + l.append(ff.name) + return l + +if __name__ == "__main__": + backup = backupTools() + stype = sys.argv[1] + if stype == 'all': + backup.backupDatabaseAll(sys.argv[2]) + if stype == 'database': + backup.backupDatabase(sys.argv[2], sys.argv[3]) diff --git a/plugins/mongodb/tool_task.py b/plugins/mongodb/tool_task.py new file mode 100644 index 000000000..d7a769640 --- /dev/null +++ b/plugins/mongodb/tool_task.py @@ -0,0 +1,124 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'mongodb' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getTaskConf(): + conf = getServerDir() + "/task_config.json" + return conf + + +def getConfigData(): + conf = getTaskConf() + if os.path.exists(conf): + return json.loads(mw.readFile(getTaskConf())) + return { + "task_id": -1, + "period": "minute-n", + "where1": "1", + "hour": "0", + "minute": "0", + } + + +def createBgTask(): + removeBgTask() + createBgTaskByName(getPluginName()) + + +def createBgTaskByName(name): + args = getConfigData() + _name = "[MongoDB]检查任务" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in args and args["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (args["task_id"],)).find() + if res and res["id"] == args["task_id"]: + print("计划任务已经存在!") + return True + + mw_dir = mw.getPanelDir() + cmd = ''' +mw_dir=%s +rname=%s +plugin_path=%s +script_path=%s +''' % (mw_dir, name, getServerDir(), getPluginDir()) + cmd += 'echo "bash $script_path/check.sh"' + "\n" + cmd += 'cd $mw_dir && bash $script_path/check.sh' + "\n" + + params = { + 'name': _name, + 'type': args['period'], + 'week': "", + 'where1': args['where1'], + 'hour': args['hour'], + 'minute': args['minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + args["task_id"] = task_id + args["name"] = name + mw.writeFile(getTaskConf(), json.dumps(args)) + + +def removeBgTask(): + cfg = getConfigData() + if "task_id" in cfg and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data["status"]: + cfg["task_id"] = -1 + mw.writeFile(getTaskConf(), json.dumps(cfg)) + return True + return False + + +if __name__ == "__main__": + if len(sys.argv) > 1: + action = sys.argv[1] + if action == "remove": + removeBgTask() + elif action == "add": + createBgTask() diff --git a/plugins/mongodb/versions/4.4/centos.sh b/plugins/mongodb/versions/4.4/centos.sh new file mode 100644 index 000000000..7d79c8eeb --- /dev/null +++ b/plugins/mongodb/versions/4.4/centos.sh @@ -0,0 +1,110 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=4.4.23 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +if [ "$SYS_ARCH" == "aarch64" ];then + if [ "$SYS_NAME" -gt "82" ];then + SYS_NAME="82" + fi + + if [ "$SYS_NAME" -lt "62" ];then + SYS_NAME="62" + fi +else + + if [ "$SYS_NAME" -gt "80" ];then + SYS_NAME="80" + fi + + if [ "$SYS_NAME" -lt "70" ];then + SYS_NAME="70" + fi +fi + + +# https://fastdl.mongodb.org/linux/mongodb-linux-aarch64-rhel82-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-aarch64-rhel82-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-4.4.23.tgz + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-rhel${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-aarch64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} diff --git a/plugins/mongodb/versions/4.4/debian.sh b/plugins/mongodb/versions/4.4/debian.sh new file mode 100644 index 000000000..8647f8c05 --- /dev/null +++ b/plugins/mongodb/versions/4.4/debian.sh @@ -0,0 +1,88 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=4.4.23 +SYS_ARCH=`arch` +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} + +if [ "$SYS_NAME" -gt "10" ];then + SYS_NAME="10" +fi + +if [ "$SYS_NAME" -lt "10" ];then + SYS_NAME="10" +fi + +FILE_NAME=mongodb-linux-${SYS_ARCH}-debian${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} diff --git a/plugins/mongodb/versions/4.4/macos.sh b/plugins/mongodb/versions/4.4/macos.sh new file mode 100644 index 000000000..1c2ff9a70 --- /dev/null +++ b/plugins/mongodb/versions/4.4/macos.sh @@ -0,0 +1,76 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=4.4.23 + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz ]; then + wget --no-check-certificate -O $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-${VERSION}.tgz + echo "wget --no-check-certificate -O $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-${VERSION}.tgz" +fi + +if [ ! -d $MG_DIR/mongodb-macos-x86_64-${VERSION} ];then + cd $MG_DIR && tar -zxvf mongodb-macos-x86_64-${VERSION}.tgz +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/mongodb-macos-x86_64-${VERSION} && cp -rf ./bin $serverPath/mongodb +fi + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +#--------------- mongodb-database-tools ------------------ # +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-macos-arm64-100.9.4.zip +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-macos-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/4.4/opensuse.sh b/plugins/mongodb/versions/4.4/opensuse.sh new file mode 100644 index 000000000..016ba6cbb --- /dev/null +++ b/plugins/mongodb/versions/4.4/opensuse.sh @@ -0,0 +1,92 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=4.4.23 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}' | awk -F . '{print $1}'` + +SYS_NAME="15" +if [ "$SYS_VERSION_ID" -gt "15" ];then + SYS_NAME="15" +fi + +if [ "$SYS_NAME" -lt "12" ];then + SYS_NAME="12" +fi + +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse15-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse12-4.4.23.tgz + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-suse${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-suse12-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/4.4/rhel.sh b/plugins/mongodb/versions/4.4/rhel.sh new file mode 100644 index 000000000..6413d0639 --- /dev/null +++ b/plugins/mongodb/versions/4.4/rhel.sh @@ -0,0 +1,63 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=4.4.23 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.4.23.tgz + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-ubuntu${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/4.4/ubuntu.sh b/plugins/mongodb/versions/4.4/ubuntu.sh new file mode 100644 index 000000000..4dd2b3423 --- /dev/null +++ b/plugins/mongodb/versions/4.4/ubuntu.sh @@ -0,0 +1,89 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=4.4.23 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2004-4.4.23.tgz + +if [ "$SYS_NAME" -gt "2004" ];then + SYS_NAME="2004" +fi + +if [ "$SYS_NAME" -lt "1604" ];then + SYS_NAME="1604" +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-ubuntu${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/5.0/centos.sh b/plugins/mongodb/versions/5.0/centos.sh new file mode 100644 index 000000000..4ecaf059f --- /dev/null +++ b/plugins/mongodb/versions/5.0/centos.sh @@ -0,0 +1,97 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=5.0.30 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +if [ "$SYS_ARCH" == "aarch64" ];then + SYS_NAME="82" +else + + if [ "$SYS_NAME" -gt "80" ];then + SYS_NAME="80" + fi + + if [ "$SYS_NAME" -lt "70" ];then + SYS_NAME="70" + fi +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-rhel${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-aarch64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} diff --git a/plugins/mongodb/versions/5.0/debian.sh b/plugins/mongodb/versions/5.0/debian.sh new file mode 100644 index 000000000..bfc72cf2a --- /dev/null +++ b/plugins/mongodb/versions/5.0/debian.sh @@ -0,0 +1,87 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=5.0.30 +SYS_ARCH=`arch` +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} + +if [ "$SYS_NAME" -gt "11" ];then + SYS_NAME="11" +fi + +if [ "$SYS_NAME" -lt "10" ];then + SYS_NAME="10" +fi + +FILE_NAME=mongodb-linux-${SYS_ARCH}-debian${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f ${MG_DIR}/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} diff --git a/plugins/mongodb/versions/5.0/macos.sh b/plugins/mongodb/versions/5.0/macos.sh new file mode 100644 index 000000000..8f811860d --- /dev/null +++ b/plugins/mongodb/versions/5.0/macos.sh @@ -0,0 +1,76 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=5.0.30 + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz ]; then + wget --no-check-certificate -O $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-${VERSION}.tgz + echo "wget --no-check-certificate -O $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-${VERSION}.tgz" +fi + +if [ ! -d $MG_DIR/mongodb-macos-x86_64-${VERSION} ];then + cd $MG_DIR && tar -zxvf mongodb-macos-x86_64-${VERSION}.tgz +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/mongodb-macos-x86_64-${VERSION} && cp -rf ./bin $serverPath/mongodb +fi + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +#--------------- mongodb-database-tools ------------------ # +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-macos-arm64-100.9.4.zip +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-macos-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/5.0/opensuse.sh b/plugins/mongodb/versions/5.0/opensuse.sh new file mode 100644 index 000000000..af3e43044 --- /dev/null +++ b/plugins/mongodb/versions/5.0/opensuse.sh @@ -0,0 +1,91 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=5.0.30 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}' | awk -F . '{print $1}'` + +SYS_NAME="15" +if [ "$SYS_VERSION_ID" -gt "15" ];then + SYS_NAME="15" +fi + +if [ "$SYS_NAME" -lt "12" ];then + SYS_NAME="12" +fi + +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse15-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse12-4.4.23.tgz + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-suse${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-suse12-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} diff --git a/plugins/mongodb/versions/5.0/ubuntu.sh b/plugins/mongodb/versions/5.0/ubuntu.sh new file mode 100644 index 000000000..1b5ef0315 --- /dev/null +++ b/plugins/mongodb/versions/5.0/ubuntu.sh @@ -0,0 +1,90 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=5.0.30 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2004-4.4.23.tgz + +if [ "$SYS_NAME" -gt "2004" ];then + SYS_NAME="2004" +fi + +if [ "$SYS_NAME" -lt "1804" ];then + SYS_NAME="1804" +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-ubuntu${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/6.0/centos.sh b/plugins/mongodb/versions/6.0/centos.sh new file mode 100644 index 000000000..8c836bb6c --- /dev/null +++ b/plugins/mongodb/versions/6.0/centos.sh @@ -0,0 +1,105 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=6.0.19 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +if [ "$SYS_ARCH" == "aarch64" ];then + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "82" ];then + SYS_NAME="82" + fi +else + + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "70" ];then + SYS_NAME="70" + fi +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-rhel${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-aarch64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/6.0/debian.sh b/plugins/mongodb/versions/6.0/debian.sh new file mode 100644 index 000000000..b0590596b --- /dev/null +++ b/plugins/mongodb/versions/6.0/debian.sh @@ -0,0 +1,89 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=6.0.19 +SYS_ARCH=`arch` +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} + +if [ "$SYS_NAME" -gt "11" ];then + SYS_NAME="11" +fi + +if [ "$SYS_NAME" -lt "10" ];then + SYS_NAME="10" +fi + +FILE_NAME=mongodb-linux-${SYS_ARCH}-debian${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/6.0/macos.sh b/plugins/mongodb/versions/6.0/macos.sh new file mode 100644 index 000000000..ed763d4ce --- /dev/null +++ b/plugins/mongodb/versions/6.0/macos.sh @@ -0,0 +1,76 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=6.0.19 + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz ]; then + wget --no-check-certificate -O $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-${VERSION}.tgz + echo "wget --no-check-certificate -O $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-${VERSION}.tgz" +fi + +if [ ! -d $MG_DIR/mongodb-macos-x86_64-${VERSION} ];then + cd $MG_DIR && tar -zxvf mongodb-macos-x86_64-${VERSION}.tgz +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/mongodb-macos-x86_64-${VERSION} && cp -rf ./bin $serverPath/mongodb +fi + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +#--------------- mongodb-database-tools ------------------ # +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-macos-arm64-100.9.4.zip +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-macos-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/6.0/opensuse.sh b/plugins/mongodb/versions/6.0/opensuse.sh new file mode 100644 index 000000000..5262433b3 --- /dev/null +++ b/plugins/mongodb/versions/6.0/opensuse.sh @@ -0,0 +1,92 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=6.0.19 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}' | awk -F . '{print $1}'` + +SYS_NAME="15" +if [ "$SYS_VERSION_ID" -gt "15" ];then + SYS_NAME="15" +fi + +if [ "$SYS_NAME" -lt "12" ];then + SYS_NAME="12" +fi + +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse15-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse12-4.4.23.tgz + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-suse${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-suse12-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/6.0/ubuntu.sh b/plugins/mongodb/versions/6.0/ubuntu.sh new file mode 100644 index 000000000..b754a49b8 --- /dev/null +++ b/plugins/mongodb/versions/6.0/ubuntu.sh @@ -0,0 +1,89 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=6.0.19 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2004-4.4.23.tgz + +if [ "$SYS_NAME" -gt "2204" ];then + SYS_NAME="2204" +fi + +if [ "$SYS_NAME" -lt "1804" ];then + SYS_NAME="1804" +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-ubuntu${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} diff --git a/plugins/mongodb/versions/7.0/centos.sh b/plugins/mongodb/versions/7.0/centos.sh new file mode 100644 index 000000000..ff808f549 --- /dev/null +++ b/plugins/mongodb/versions/7.0/centos.sh @@ -0,0 +1,107 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=7.0.15 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +if [ "$SYS_ARCH" == "aarch64" ];then + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "82" ];then + SYS_NAME="82" + fi +else + + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "70" ];then + SYS_NAME="70" + fi +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-rhel${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-aarch64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-x86_64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel83-s390x-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-aarch64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/7.0/debian.sh b/plugins/mongodb/versions/7.0/debian.sh new file mode 100644 index 000000000..d66088db6 --- /dev/null +++ b/plugins/mongodb/versions/7.0/debian.sh @@ -0,0 +1,95 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=7.0.15 +SYS_ARCH=`arch` +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} + + +if [ "$SYS_NAME" -gt "11" ];then + SYS_NAME="11" +fi + +if [ "$SYS_NAME" -lt "11" ];then + SYS_NAME="11" +fi + +if [ "$SYS_NAME" == "" ];then + SYS_NAME="11" +fi + +FILE_NAME=mongodb-linux-${SYS_ARCH}-debian${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/7.0/euler.sh b/plugins/mongodb/versions/7.0/euler.sh new file mode 100644 index 000000000..ff808f549 --- /dev/null +++ b/plugins/mongodb/versions/7.0/euler.sh @@ -0,0 +1,107 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=7.0.15 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +if [ "$SYS_ARCH" == "aarch64" ];then + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "82" ];then + SYS_NAME="82" + fi +else + + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "70" ];then + SYS_NAME="70" + fi +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-rhel${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-aarch64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-x86_64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel83-s390x-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-aarch64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/7.0/macos.sh b/plugins/mongodb/versions/7.0/macos.sh new file mode 100644 index 000000000..5ae23731b --- /dev/null +++ b/plugins/mongodb/versions/7.0/macos.sh @@ -0,0 +1,80 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +SYS_ARCH=`arch` + +VERSION=7.0.15 + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz ]; then + wget --no-check-certificate -O $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-${VERSION}.tgz + echo "wget --no-check-certificate -O $MG_DIR/mongodb-macos-x86_64-${VERSION}.tgz https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-${VERSION}.tgz" +fi + +if [ ! -d $MG_DIR/mongodb-macos-x86_64-${VERSION} ];then + cd $MG_DIR && tar -zxvf mongodb-macos-x86_64-${VERSION}.tgz +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/mongodb-macos-x86_64-${VERSION} && cp -rf ./bin $serverPath/mongodb +fi + +# https://downloads.mongodb.com/compass/mongosh-2.2.5-darwin-x64.zip +# https://downloads.mongodb.com/compass/mongosh-2.2.5-darwin-arm64.zip +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-macos-arm64-100.9.4.zip +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-macos-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/7.0/opensuse.sh b/plugins/mongodb/versions/7.0/opensuse.sh new file mode 100644 index 000000000..22d1db08a --- /dev/null +++ b/plugins/mongodb/versions/7.0/opensuse.sh @@ -0,0 +1,94 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=7.0.15 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}' | awk -F . '{print $1}'` + +SYS_NAME="15" +if [ "$SYS_VERSION_ID" -gt "15" ];then + SYS_NAME="15" +fi + +if [ "$SYS_NAME" -lt "12" ];then + SYS_NAME="12" +fi + +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse15-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse12-4.4.23.tgz + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-suse${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-suse12-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/7.0/ubuntu.sh b/plugins/mongodb/versions/7.0/ubuntu.sh new file mode 100644 index 000000000..9ea088118 --- /dev/null +++ b/plugins/mongodb/versions/7.0/ubuntu.sh @@ -0,0 +1,90 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=7.0.15 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2004-4.4.23.tgz + +if [ "$SYS_NAME" -gt "2204" ];then + SYS_NAME="2204" +fi + +if [ "$SYS_NAME" -lt "2004" ];then + SYS_NAME="2004" +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-ubuntu${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/8.0/centos.sh b/plugins/mongodb/versions/8.0/centos.sh new file mode 100644 index 000000000..c66fa6c31 --- /dev/null +++ b/plugins/mongodb/versions/8.0/centos.sh @@ -0,0 +1,107 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.0.3 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +if [ "$SYS_ARCH" == "aarch64" ];then + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "82" ];then + SYS_NAME="82" + fi +else + + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "70" ];then + SYS_NAME="70" + fi +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-rhel${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-aarch64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-x86_64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel83-s390x-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-aarch64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/8.0/debian.sh b/plugins/mongodb/versions/8.0/debian.sh new file mode 100644 index 000000000..556dcb41b --- /dev/null +++ b/plugins/mongodb/versions/8.0/debian.sh @@ -0,0 +1,84 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.0.3 +SYS_ARCH=`arch` +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} + + +SYS_NAME="12" +FILE_NAME=mongodb-linux-${SYS_ARCH}-debian${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/8.0/euler.sh b/plugins/mongodb/versions/8.0/euler.sh new file mode 100644 index 000000000..c66fa6c31 --- /dev/null +++ b/plugins/mongodb/versions/8.0/euler.sh @@ -0,0 +1,107 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.0.3 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +if [ "$SYS_ARCH" == "aarch64" ];then + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "82" ];then + SYS_NAME="82" + fi +else + + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "70" ];then + SYS_NAME="70" + fi +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-rhel${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-aarch64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-x86_64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel83-s390x-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-aarch64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/8.0/macos.sh b/plugins/mongodb/versions/8.0/macos.sh new file mode 100644 index 000000000..43b4ecbb4 --- /dev/null +++ b/plugins/mongodb/versions/8.0/macos.sh @@ -0,0 +1,87 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +SYS_ARCH=`arch` + +VERSION=8.0.3 + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +# https://fastdl.mongodb.org/osx/mongodb-macos-arm64-8.0.3.tgz +FILE_NAME=mongodb-macos-x86_64-${VERSION} +if [ "arm64" == "${SYS_ARCH}" ];then + FILE_NAME=mongodb-macos-arm64-${VERSION} +fi +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/osx/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/osx/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +# https://downloads.mongodb.com/compass/mongosh-2.2.5-darwin-x64.zip +# https://downloads.mongodb.com/compass/mongosh-2.2.5-darwin-arm64.zip +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-macos-arm64-100.9.4.zip +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-macos-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/8.0/opensuse.sh b/plugins/mongodb/versions/8.0/opensuse.sh new file mode 100644 index 000000000..6926dd1bc --- /dev/null +++ b/plugins/mongodb/versions/8.0/opensuse.sh @@ -0,0 +1,94 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.0.3 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}' | awk -F . '{print $1}'` + +SYS_NAME="15" +if [ "$SYS_VERSION_ID" -gt "15" ];then + SYS_NAME="15" +fi + +if [ "$SYS_NAME" -lt "12" ];then + SYS_NAME="12" +fi + +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse15-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse12-4.4.23.tgz + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-suse${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-suse12-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/8.0/ubuntu.sh b/plugins/mongodb/versions/8.0/ubuntu.sh new file mode 100644 index 000000000..2a9230b05 --- /dev/null +++ b/plugins/mongodb/versions/8.0/ubuntu.sh @@ -0,0 +1,94 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.0.3 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2004-4.4.23.tgz + +if [ "$SYS_NAME" -lt "2004" ];then + SYS_NAME="2004" +fi + +if [ "$SYS_NAME" == "2204" ];then + SYS_NAME="2204" +fi + +if [ "$SYS_NAME" == "2404" ];then + SYS_NAME="2404" +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-ubuntu${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/8.2/centos.sh b/plugins/mongodb/versions/8.2/centos.sh new file mode 100644 index 000000000..cc99c0833 --- /dev/null +++ b/plugins/mongodb/versions/8.2/centos.sh @@ -0,0 +1,107 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.2.3 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +if [ "$SYS_ARCH" == "aarch64" ];then + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "82" ];then + SYS_NAME="82" + fi +else + + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "70" ];then + SYS_NAME="70" + fi +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-rhel${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-aarch64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-x86_64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel83-s390x-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-aarch64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/8.2/debian.sh b/plugins/mongodb/versions/8.2/debian.sh new file mode 100644 index 000000000..55f1c0c6d --- /dev/null +++ b/plugins/mongodb/versions/8.2/debian.sh @@ -0,0 +1,84 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.2.3 +SYS_ARCH=`arch` +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} + + +SYS_NAME="12" +FILE_NAME=mongodb-linux-${SYS_ARCH}-debian${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-debian${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/8.2/euler.sh b/plugins/mongodb/versions/8.2/euler.sh new file mode 100644 index 000000000..cc99c0833 --- /dev/null +++ b/plugins/mongodb/versions/8.2/euler.sh @@ -0,0 +1,107 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.2.3 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +SYS_NAME_LEN=`echo "$SYS_NAME" | wc -L` + +if [ "$SYS_NAME_LEN" == "1" ];then + SYS_NAME=${SYS_NAME}0 +fi + +if [ "$SYS_ARCH" == "aarch64" ];then + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "82" ];then + SYS_NAME="82" + fi +else + + if [ "$SYS_NAME" -gt "90" ];then + SYS_NAME="90" + fi + + if [ "$SYS_NAME" -lt "70" ];then + SYS_NAME="70" + fi +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-rhel${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-aarch64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel90-x86_64-100.9.4.tgz +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel83-s390x-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-aarch64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-rhel${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/8.2/macos.sh b/plugins/mongodb/versions/8.2/macos.sh new file mode 100644 index 000000000..461ece36c --- /dev/null +++ b/plugins/mongodb/versions/8.2/macos.sh @@ -0,0 +1,87 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +SYS_ARCH=`arch` + +VERSION=8.2.3 + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +# https://fastdl.mongodb.org/osx/mongodb-macos-arm64-8.0.3.tgz +FILE_NAME=mongodb-macos-x86_64-${VERSION} +if [ "arm64" == "${SYS_ARCH}" ];then + FILE_NAME=mongodb-macos-arm64-${VERSION} +fi +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/osx/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/osx/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +# https://downloads.mongodb.com/compass/mongosh-2.2.5-darwin-x64.zip +# https://downloads.mongodb.com/compass/mongosh-2.2.5-darwin-arm64.zip +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-darwin-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-macos-arm64-100.9.4.zip +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-macos-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-macos-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.zip +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && unzip ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mongodb/versions/8.2/opensuse.sh b/plugins/mongodb/versions/8.2/opensuse.sh new file mode 100644 index 000000000..93e74b24f --- /dev/null +++ b/plugins/mongodb/versions/8.2/opensuse.sh @@ -0,0 +1,94 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.2.3 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}' | awk -F . '{print $1}'` + +SYS_NAME="15" +if [ "$SYS_VERSION_ID" -gt "15" ];then + SYS_NAME="15" +fi + +if [ "$SYS_NAME" -lt "12" ];then + SYS_NAME="12" +fi + +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse15-4.4.23.tgz +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-suse12-4.4.23.tgz + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-suse${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-suse12-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-suse${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + diff --git a/plugins/mongodb/versions/8.2/ubuntu.sh b/plugins/mongodb/versions/8.2/ubuntu.sh new file mode 100644 index 000000000..af47fc832 --- /dev/null +++ b/plugins/mongodb/versions/8.2/ubuntu.sh @@ -0,0 +1,94 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=8.2.3 +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +SYS_NAME=${SYS_VERSION_ID/./} +# https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2004-4.4.23.tgz + +if [ "$SYS_NAME" -lt "2004" ];then + SYS_NAME="2004" +fi + +if [ "$SYS_NAME" == "2204" ];then + SYS_NAME="2204" +fi + +if [ "$SYS_NAME" == "2404" ];then + SYS_NAME="2404" +fi + +MG_DIR=$serverPath/source/mongodb +mkdir -p $MG_DIR + +FILE_NAME=mongodb-linux-${SYS_ARCH}-ubuntu${SYS_NAME}-${VERSION} +FILE_NAME_TGZ=${FILE_NAME}.tgz + +if [ ! -f $MG_DIR/${FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${FILE_NAME_TGZ} https://fastdl.mongodb.org/linux/${FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${FILE_NAME} ];then + cd $MG_DIR && tar -zxvf ${FILE_NAME_TGZ} +fi + +if [ ! -d $serverPath/mongodb/bin ];then + mkdir -p $serverPath/mongodb + cd $MG_DIR/${FILE_NAME} && cp -rf ./bin $serverPath/mongodb +fi + +cd ${MG_DIR} && rm -rf ${MG_DIR}/${FILE_NAME} + + +#--------------- mongosh tool install ------------------ # +TOOL_VERSION=2.2.5 +TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-x64 +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongosh-${TOOL_VERSION}-linux-arm64 +fi +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ} + echo "wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://downloads.mongodb.com/compass/${TOOL_FILE_NAME_TGZ}" +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} + +# https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.9.4.tgz +#--------------- mongodb database install ------------------ # +TOOL_VERSION=100.9.4 +TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-x86_64-${TOOL_VERSION} +if [ "aarch64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +if [ "arm64" == ${SYS_ARCH} ];then + TOOL_FILE_NAME=mongodb-database-tools-ubuntu${SYS_NAME}-arm64-${TOOL_VERSION} +fi + +TOOL_FILE_NAME_TGZ=${TOOL_FILE_NAME}.tgz +if [ ! -f $MG_DIR/${TOOL_FILE_NAME_TGZ} ]; then + wget --no-check-certificate -O $MG_DIR/${TOOL_FILE_NAME_TGZ} https://fastdl.mongodb.org/tools/db/${TOOL_FILE_NAME_TGZ} +fi + +if [ ! -d $MG_DIR/${TOOL_FILE_NAME_TGZ} ];then + cd $MG_DIR && tar -zxvf ${TOOL_FILE_NAME_TGZ} +fi + +cd ${MG_DIR}/${TOOL_FILE_NAME} && cp -rf ./bin $serverPath/mongodb +cd ${MG_DIR} && rm -rf ${MG_DIR}/${TOOL_FILE_NAME} \ No newline at end of file diff --git a/plugins/mosquitto/config/mosquitto.conf b/plugins/mosquitto/config/mosquitto.conf new file mode 100644 index 000000000..566c13a43 --- /dev/null +++ b/plugins/mosquitto/config/mosquitto.conf @@ -0,0 +1,3 @@ + +allow_anonymous true +listener 1883 \ No newline at end of file diff --git a/plugins/mosquitto/ico.png b/plugins/mosquitto/ico.png new file mode 100644 index 000000000..d39fde819 Binary files /dev/null and b/plugins/mosquitto/ico.png differ diff --git a/plugins/mosquitto/index.html b/plugins/mosquitto/index.html new file mode 100755 index 000000000..3dc4222a4 --- /dev/null +++ b/plugins/mosquitto/index.html @@ -0,0 +1,18 @@ +
                          +
                          +
                          +
                          +

                          服务

                          +

                          自启动

                          +

                          配置修改

                          +
                          +
                          +
                          +
                          +
                          +
                          + \ No newline at end of file diff --git a/plugins/mosquitto/index.py b/plugins/mosquitto/index.py new file mode 100755 index 000000000..5739b1a14 --- /dev/null +++ b/plugins/mosquitto/index.py @@ -0,0 +1,249 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'mosquitto' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/etc/mosquitto/mosquitto.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + "/config/mosquitto.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def status(): + data = mw.execShell( + "ps aux|grep mosquitto |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + + if data[0] == '': + return 'stop' + return 'start' + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # log + dataLog = getServerDir() + '/data' + if not os.path.exists(dataLog): + mw.execShell('chmod +x ' + file_bin) + + # config replace + dst_conf = getServerDir() + '/etc/mosquitto/' + getPluginName() + '.conf' + dst_conf_init = getServerDir() + '/init.pl' + if not os.path.exists(dst_conf_init): + conf_content = mw.readFile(getConfTpl()) + conf_content = conf_content.replace('{$SERVER_PATH}', service_path) + + mw.writeFile(dst_conf, conf_content) + mw.writeFile(dst_conf_init, 'ok') + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def mqttOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return mqttOp('start') + + +def stop(): + return mqttOp('stop') + + +def restart(): + status = mqttOp('restart') + + log_file = runLog() + mw.execShell("echo '' > " + log_file) + return status + + +def reload(): + return mqttOp('reload') + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(getConf()) + else: + print('error') diff --git a/plugins/mosquitto/info.json b/plugins/mosquitto/info.json new file mode 100755 index 000000000..fbe0320d6 --- /dev/null +++ b/plugins/mosquitto/info.json @@ -0,0 +1,18 @@ +{ + "sort": 4, + "ps": "MQTT是一个消息队列遥测传输软件", + "name": "mosquitto", + "title": "mosquitto", + "shell": "install.sh", + "versions":["2.0.18"], + "updates":["2.0.18"], + "tip": "soft", + "checks": "server/mosquitto", + "path": "server/mosquitto", + "display": 1, + "author": "midoks", + "date": "2023-09-28", + "home": "https://mosquitto.org", + "type": "soft", + "pid": "4" +} diff --git a/plugins/mosquitto/init.d/mosquitto.service.tpl b/plugins/mosquitto/init.d/mosquitto.service.tpl new file mode 100644 index 000000000..7a5d2cdc0 --- /dev/null +++ b/plugins/mosquitto/init.d/mosquitto.service.tpl @@ -0,0 +1,12 @@ +[Unit] +Description=Mosquitto MQTT Broker +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/mosquitto/sbin/mosquitto -c {$SERVER_PATH}/mosquitto/etc/mosquitto/mosquitto.conf +ExecReload=/bin/kill -USR2 $MAINPID +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/mosquitto/init.d/mosquitto.tpl b/plugins/mosquitto/init.d/mosquitto.tpl new file mode 100644 index 000000000..9ff8fd5a0 --- /dev/null +++ b/plugins/mosquitto/init.d/mosquitto.tpl @@ -0,0 +1,84 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: Mosquitto MQTT Broker Service + +### BEGIN INIT INFO +# Provides: MQTT +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts MQTT +# Description: starts the MDW-Web +### END INIT INFO + +# Simple MQTT init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +PATH=/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export LANG=en_US.UTF-8 + + +mw_path={$SERVER_PATH} +PATH=$PATH:$mw_path/bin + +if [ -f $mw_path/bin/activate ];then + source $mw_path/bin/activate +fi + +app_start(){ + isStart=`ps -ef|grep 'mosquitto' |grep -v grep | awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "starting mosquitto... \c" + cd $mw_path + ${APP_PATH}/mosquitto/sbin/mosquitto -c ${APP_PATH}/mosquitto/etc/mosquitto/mosquitto.conf >> {$APP_PATH}/mosquitto.log & + isStart="" + while [[ "$isStart" == "" ]]; + do + echo -e ".\c" + sleep 0.5 + isStart=`ps -ef|grep 'mosquitto' |grep -v grep | awk '{print $2}'` + let n+=1 + if [ $n -gt 20 ];then + break; + fi + done + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo -e "\033[31mError: mosquitto service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "starting mosquitto...(pid $(echo $isStart)) already running" + fi +} + + +app_stop(){ + echo -e "stopping mosquitto ... \c"; + arr=`ps aux | grep 'mosquitto' | grep -v grep | awk '{print $2}'` + for p in ${arr[@]} + do + kill -9 $p > /dev/null 2>&1 + done + echo -e "\033[32mdone\033[0m" + +} + +case "$1" in + start) + app_start + ;; + stop) + app_stop + ;; + restart|reload) + app_stop + sleep 0.3 + app_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac diff --git a/plugins/mosquitto/install.sh b/plugins/mosquitto/install.sh new file mode 100755 index 000000000..37a795254 --- /dev/null +++ b/plugins/mosquitto/install.sh @@ -0,0 +1,86 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +echo "use system: ${sysName}" + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/mosquitto && bash install.sh install 2.0.18 +# cd /www/mdserver-web/plugins/mosquitto && bash install.sh install 2.0.18 + +VERSION=$2 + +Install_App() +{ + if id mosquitto &> /dev/null ;then + echo "mosquitto UID is `id -u mosquitto`" + echo "mosquitto Shell is `grep "^mosquitto:" /etc/passwd |cut -d':' -f7 `" + else + groupadd mosquitto + useradd -g mosquitto mosquitto + fi + + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + + if [ ! -f $serverPath/source/mosquitto-${VERSION}.tar.gz ];then + wget --no-check-certificate -O $serverPath/source/mosquitto-${VERSION}.tar.gz https://mosquitto.org/files/source/mosquitto-${VERSION}.tar.gz + fi + + if [ ! -d mosquitto-${VERSION} ];then + cd $serverPath/source && tar -zxvf mosquitto-${VERSION}.tar.gz + fi + + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + mkdir -p $serverPath/mosquitto + if [ ! -d $serverPath/mosquitto/bin ];then + cd mosquitto-${VERSION} && ${INSTALL_CMD} CMakeLists.txt -DCMAKE_INSTALL_PREFIX=$serverPath/mosquitto && make install + fi + + if [ -d $serverPath/mosquitto ];then + echo "${VERSION}" > $serverPath/mosquitto/version.pl + echo '安装mosquitto完成' + + + cd ${rootPath} && python3 ${rootPath}/plugins/mosquitto/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/mosquitto/index.py initd_install + fi +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/mosquitto.service ];then + systemctl stop mosquitto + systemctl disable mosquitto + rm -rf /usr/lib/systemd/system/mosquitto.service + systemctl daemon-reload + fi + + if [ -f $serverPath/mosquitto/initd/mosquitto ];then + $serverPath/mosquitto/initd/mosquitto stop + fi + + rm -rf $serverPath/mosquitto + echo "卸载mosquitto成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/mosquitto/js/mosquitto.js b/plugins/mosquitto/js/mosquitto.js new file mode 100755 index 000000000..e2b4dbcd8 --- /dev/null +++ b/plugins/mosquitto/js/mosquitto.js @@ -0,0 +1,55 @@ +function mqPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'mosquitto'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function mqPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'mosquitto'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + diff --git a/plugins/msonedrive/class/msodclient.py b/plugins/msonedrive/class/msodclient.py new file mode 100644 index 000000000..f21a13aac --- /dev/null +++ b/plugins/msonedrive/class/msodclient.py @@ -0,0 +1,830 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import io + +import oauthlib +import requests +import datetime +from requests_oauthlib import OAuth2Session + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +DEBUG = False + + +def setDebug(d=False): + DEBUG = d + + +class UnauthorizedError(Exception): + pass + + +class ObjectNotFoundError(Exception): + pass + + +class msodclient: + + plugin_dir = '' + server_dir = '' + credential_file = 'credentials.json' + user_conf = "user.conf" + token_file = 'token.pickle' + + def __init__(self, plugin_dir, server_dir): + self.plugin_dir = plugin_dir + self.server_dir = server_dir + self.load() + + def setDebug(self, d=False): + DEBUG = d + + def load(self): + credential_path = os.path.join(self.plugin_dir, self.credential_file) + credential = json.loads(mw.readFile(credential_path)) + # print(credential) + self.credential = credential["onedrive-international"] + + self.authorize_url = '{0}{1}'.format( + self.credential['authority'], + self.credential['authorize_endpoint']) + self.token_url = '{0}{1}'.format( + self.credential['authority'], + self.credential['token_endpoint']) + + self.token_path = os.path.join(self.server_dir, self.token_file) + self.root_uri = self.credential["api_uri"] + "/me/drive/root" + + self.backup_path = 'backup' + + def store_token(self, token): + """存储token""" + enstr = mw.enDoubleCrypt('msodc', json.dumps(token)) + mw.writeFile(self.token_path, enstr) + return True + + def get_store_token(self): + rdata = mw.readFile(self.token_path) + destr = mw.deDoubleCrypt('msodc', rdata) + return json.loads(destr) + + def clear_token(self): + """清除token记录""" + try: + if os.path.isfile(self.token_path): + os.remove(self.token_path) + except: + if DEBUG: + print("清除token失败。") + + def refresh_token(self, origin_token): + """刷新token""" + + os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1' + os.environ['OAUTHLIB_IGNORE_SCOPE_CHANGE'] = '1' + refresh_token = origin_token["refresh_token"] + aad_auth = OAuth2Session( + self.credential["client_id"], + scope=self.credential["scopes"], + redirect_uri=self.credential["redirect_uri"]) + + new_token = aad_auth.refresh_token( + self.token_url, + refresh_token=refresh_token, + client_id=self.credential["client_id"], + client_secret=self.credential["client_secret"]) + return new_token + + def get_token_from_authorized_url(self, authorized_url, expected_state=None): + """通过授权编码获取访问token""" + + # 忽略token scope与已请求的scope不一致 + os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1' + os.environ['OAUTHLIB_IGNORE_SCOPE_CHANGE'] = '1' + aad_auth = OAuth2Session(self.credential["client_id"], + state=expected_state, + scope=self.credential['scopes'], + redirect_uri=self.credential['redirect_uri']) + + token = aad_auth.fetch_token( + self.token_url, + client_secret=self.credential["client_secret"], + authorization_response=authorized_url) + + return token + + def get_token(self): + token = self.get_store_token() + now = time.time() + + expire_time = token["expires_at"] - 300 + if now >= expire_time: + new_token = self.refresh_token(token) + self.store_token(new_token) + return new_token + + return token + + def get_sign_in_url(self): + """生成签名地址""" + + # Initialize the OAuth client + aad_auth = OAuth2Session(self.credential["client_id"], + scope=self.credential['scopes'], + redirect_uri=self.credential['redirect_uri']) + + sign_in_url, state = aad_auth.authorization_url(self.authorize_url, + prompt='login') + + return sign_in_url, state + + def get_authorized_header(self): + token_obj = self.get_token() + token = token_obj["access_token"] + header = { + "Authorization": "Bearer " + token, + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/67.0.3396.99 Safari/537.36' + } + return header + + def get_user_from_ms(self): + """查询用户信息""" + try: + headers = self.get_authorized_header() + user_api_base = self.credential["api_uri"] + "/me" + # select_user_info_uri = self.build_uri(base=user_api_base) + response = requests.get(user_api_base, headers=headers) + if DEBUG: + print("Debug get user:") + print(response.status_code) + print(response.text) + if response.status_code == 200: + response_data = response.json() + user_principal_name = response_data["userPrincipalName"] + return user_principal_name + except oauthlib.oauth2.rfc6749.errors.InvalidGrantError: + self.clear_auth() + if DEBUG: + print("用户授权已过期。") + return None + + def clear_auth(self): + self.clear_token() + self.clear_user() + + def clear_user(self): + try: + # 清空user + path = os.path.join(self.server_dir, self.user_conf) + if os.path.isfile(path): + os.remove(path) + except: + if DEBUG: + print("清除user失败。") + + def store_user(self): + """更新并存储用户信息""" + user = self.get_user_from_ms() + if user: + path = os.path.join(self.server_dir, self.user_conf) + mw.writeFile(path, user) + else: + raise RuntimeError("无法获取用户信息。") + + # --------------------- 文件操作功能 ---------------------- + + # 取目录路径 + def get_path(self, path): + sep = ":" + if path == '/': + path = '' + if path[-1:] == '/': + path = path[:-1] + if path[:1] != "/" and path[:1] != sep: + path = "/" + path + if path == '/': + path = '' + # if path[:1] != sep: + # path = sep + path + try: + from urllib.parse import quote + except: + from urllib import quote + # path = quote(path) + + return path.replace('//', '/') + + def build_uri(self, path="", operate=None, base=None): + """构建请求URL + + API请求URI格式参考: + https://graph.microsoft.com/v1.0/me/drive/root:/bt_backup/:content + --------------------------------------------- ---------- -------- + base path operate + 各部分之间用“:”连接。 + :param path 子资源路径 + :param operate 对文件进行的操作,比如content,children + :return 请求url + """ + + if base is None: + base = self.root_uri + path = self.get_path(path) + sep = ":" + if operate: + if operate[:1] != "/": + operate = "/" + operate + + if path: + uri = base + sep + path + if operate: + uri += sep + operate + else: + uri = base + if operate: + uri += operate + + return uri + + def get_list(self, path="/"): + """获取存储空间中的所有文件对象""" + + list_uri = self.build_uri(path, operate="/children") + if DEBUG: + print("List uri:") + print(list_uri) + + data = [] + response = requests.get(list_uri, headers=self.get_authorized_header()) + status_code = response.status_code + if status_code == 200: + if DEBUG: + print("DEBUG:") + print(response.json()) + response_data = response.json() + drive_items = response_data["value"] + + for item in drive_items: + tmp = {} + tmp['name'] = item["name"] + tmp['size'] = item["size"] + if "folder" in item: + # print("{} is folder:".format(item["name"])) + # print(item["folder"]) + tmp["type"] = None + tmp['download'] = "" + if "file" in item: + tmp["type"] = "File" + tmp['download'] = item["@microsoft.graph.downloadUrl"] + # print("{} is file:".format(item["name"])) + # print(item["file"]) + + formats = ["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"] + t = None + for time_format in formats: + try: + t = datetime.datetime.strptime( + item["lastModifiedDateTime"], time_format) + break + except: + continue + t += datetime.timedelta(hours=8) + ts = int( + (time.mktime(t.timetuple()) + t.microsecond / 1000000.0)) + tmp['time'] = ts + data.append(tmp) + + mlist = {'path': path, 'list': data} + return mlist + + def get_object(self, object_name): + """查询对象信息""" + try: + get_uri = self.build_uri(path=object_name) + if DEBUG: + print("Get uri:") + print(get_uri) + response = requests.get(get_uri, + headers=self.get_authorized_header()) + if response.status_code in [200]: + response_data = response.json() + if DEBUG: + print("Object info:") + print(response_data) + return response_data + if response.status_code == 404: + if DEBUG: + print("对象不存在。") + if DEBUG: + print("Get Object debug:") + print(response.status_code) + print(response.text) + except Exception as e: + if DEBUG: + print("Get object has excepiton:") + print(e) + return None + + def is_folder(self, obj): + if "folder" in obj: + return True + return False + + def delete_object_by_os(self, object_name): + """删除对象 + + :param object_name: + :return: True 删除成功 + 其他 删除失败 + """ + obj = self.get_object(object_name) + if obj is None: + if DEBUG: + print("对象不存在,删除操作未执行。") + return True + if self.is_folder(obj): + child_count = obj["folder"]["childCount"] + if child_count > 0: + if DEBUG: + print("文件夹不是空文件夹无法删除。") + return False + + headers = self.get_authorized_header() + delete_uri = self.build_uri(object_name) + response = requests.delete(delete_uri, headers=headers) + if response.status_code == 204: + if DEBUG: + print("对象: {} 已被删除。".format(object_name)) + return True + return False + + def delete_object(self, object_name, retries=2): + """删除对象 + + :param object_name: + :param retries: 重试次数,默认2次 + :return: True 删除成功 + 其他 删除失败 + """ + + try: + return self.delete_object_by_os(object_name) + except Exception as e: + print("删除文件异常:") + print(e) + + # 重试 + if retries > 0: + print("重新尝试删除文件{}...".format(object_name)) + return self.delete_object( + object_name, + retries=retries - 1) + return False + + def build_object_name(self, data_type, file_name): + """根据数据类型构建对象存储名称 + + :param data_type: + :param file_name: + :return: + """ + + import re + + prefix_dict = { + "site": "web", + "database": "db", + "path": "path", + } + + if not prefix_dict.get(data_type): + print("data_type 类型错误!!!") + exit(1) + + file_regx = prefix_dict.get(data_type) + "_(.+)_20\d+_\d+(?:\.|_)" + sub_search = re.search(file_regx, file_name) + sub_path_name = "" + if sub_search: + sub_path_name = sub_search.groups()[0] + sub_path_name += '/' + + # 构建OS存储路径 + object_name = self.backup_path + '/' + \ + data_type + '/' + \ + sub_path_name + \ + file_name + + if object_name[:1] == "/": + object_name = object_name[1:] + + return object_name + + def delete_file(self, file_name, data_type=None): + """删除文件 + + 根据传入的文件名称和文件数据类型构建对象名称,再删除 + :param file_name: + :param data_type: 数据类型 site/database/path + :return: True 删除成功 + 其他 删除失败 + """ + + object_name = self.build_object_name(data_type, file_name) + return self.delete_object(object_name) + + def create_dir_by_step(self, parent_folder, sub_folder): + create_uri = self.build_uri(path=parent_folder, operate="/children") + + if DEBUG: + print("Create dir uri:") + print(create_uri) + post_data = { + "name": sub_folder, + "folder": {"@odata.type": "microsoft.graph.folder"}, + "@microsoft.graph.conflictBehavior": "fail" + } + + headers = self.get_authorized_header() + headers.update({"Content-type": "application/json"}) + response = requests.post(create_uri, headers=headers, json=post_data) + if response.status_code in [201, 409]: + if DEBUG: + if response.status_code == 409: + print("目录:{} 已经存在。".format(sub_folder)) + return True + else: + if DEBUG: + print("目录:{} 创建失败:".format(sub_folder)) + print(response.status_code) + print(response.text) + return False + + def create_dir(self, dir_name): + """创建远程目录 + + # API 请求结构 + # POST /me/drive/root/children + # or + # POST /me/drive/root:/bt_backup/:/children + # Content - Type: application / json + + # { + # "name": "New Folder", + # "folder": {}, + # "@microsoft.graph.conflictBehavior": "rename" + # } + + # Response: status code == 201 新创建/ 409 已存在 + # @microsoft.graph.conflictBehavior: fail/rename/replace + + :param dir_name: 目录名称 + :param parent_id: 父目录ID + :return: True/False + """ + + dir_name = self.get_path(dir_name.strip()) + onedrive_business_reserved = r"[\*<>?:|#%]" + if re.search(onedrive_business_reserved, dir_name) \ + or dir_name[-1] == "." or dir_name[:1] == "~": + if DEBUG: + print("文件夹名称包含非法字符。") + return False + + parent_folder = self.get_path(os.path.split(dir_name)[0]) + sub_folder = os.path.split(dir_name)[1] + + # print("create_dir:", dir_name) + obj = self.get_object(dir_name) + # 判断对象是否存在 + if obj is None: + if not self.create_dir_by_step(parent_folder, sub_folder): + + # 兼容OneDrive 商业版文件夹创建 + folder_array = dir_name.split("/") + parent_folder = self.get_path(folder_array[0]) + for i in range(1, len(folder_array)): + sub_folder = folder_array[i] + if DEBUG: + print("Parent folder: {}".format(parent_folder)) + print("Sub folder: {}".format(sub_folder)) + if self.create_dir_by_step(parent_folder, sub_folder): + parent_folder += "/" + folder_array[i] + else: + return False + return True + else: + if self.is_folder(obj): + if DEBUG: + print("文件夹已存在。") + return True + + def resumable_upload(self, + local_file_name, + object_name=None, + progress_callback=None, + progress_file_name=None, + multipart_threshold=1024 * 1024 * 2, + part_size=1024 * 1024 * 5, + store_dir="/tmp", + auto_cancel=True, + retries=5, + ): + """断点续传 + + :param local_file_name: 本地文件名称 + :param object_name: 指定OS中存储的对象名称 + :param part_size: 指定分片上传的每个分片的大小。必须是320*1024的整数倍。 + :param multipart_threshold: 文件长度大于该值时,则用分片上传。 + :param progress_callback: 进度回调函数,默认是把进度信息输出到标准输出。 + :param progress_file_name: 进度信息保存文件,进度格式参见[report_progress] + :param store_dir: 上传分片存储目录, 默认/tmp。 + :param auto_cancel: 当备份失败是否自动取消上传记录 + :param retries: 上传重试次数 + :return: True上传成功/False or None上传失败 + """ + + try: + file_size_separation_value = 4 * 1024 * 1024 + if part_size % 320 != 0: + if DEBUG: + print("Part size 必须是320的整数倍。") + return False + + if object_name is None: + temp_file_name = os.path.split(local_file_name)[1] + object_name = os.path.join(self.backup_path, temp_file_name) + + # if progress_file_name: + # os.environ[PROGRESS_FILE_NAME] = progress_file_name + # progress_callback = report_progress + + print("|-正在上传到 {}...".format(object_name)) + dir_name = os.path.split(object_name)[0] + if not self.create_dir(dir_name): + if DEBUG: + print("目录创建失败!") + return False + + local_file_size = os.path.getsize(local_file_name) + # if local_file_size < file_size_separation_value: + if False: + # 小文件上传 + upload_uri = self.build_uri(path=object_name, + operate="/content") + if DEBUG: + print("Upload uri:") + print(upload_uri) + headers = self.get_authorized_header() + # headers.update({ + # "Content-Type": "application/octet-stream" + # }) + # files = {"file": (object_name, open(local_file_name, "rb"))} + file_data = open(local_file_name, "rb") + response = requests.put(upload_uri, + headers=headers, + data=file_data) + if DEBUG: + print("status code:") + print(response.status_code) + # print(response.text) + if response.status_code in [201, 200]: + if DEBUG: + print("文件上传成功!") + return True + else: + # 大文件上传 + + # 1. 创建上传session + create_session_uri = self.build_uri( + path=object_name, + operate="createUploadSession") + headers = self.get_authorized_header() + response = requests.post(create_session_uri, headers=headers) + if response.status_code == 200: + response_data = response.json() + upload_url = response_data["uploadUrl"] + expiration_date_time = response_data["expirationDateTime"] + + if DEBUG: + print("上传session已建立。") + print("Upload url: {}".format(upload_url)) + print("Expiration datetime: {}".format( + expiration_date_time)) + + # 2. 分片上传文件 + requests.adapters.DEFAULT_RETRIES = 1 + session = requests.session() + session.keep_alive = False + + # 开始分片上传 + import math + parts = int(math.ceil(local_file_size / part_size)) + for i in range(parts): + if DEBUG: + if i == parts - 1: + num = "最后" + else: + num = "第{}".format(i + 1) + print("正在上传{}部分...".format(num)) + + upload_range_start = i * part_size + upload_range_end = min(upload_range_start + part_size, + local_file_size) + content_length = upload_range_end - upload_range_start + + headers = { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/67.0.3396.99 Safari/537.36' + } + # 开发记录 + # Content-Range和标准的http请求头中的Range作用有所不同 + # Content-Range是OneDrive自定义的分片上传标识,格式也不一样 + headers.update({ + "Content-Length": repr(content_length), + "Content-Range": "bytes {}-{}/{}".format( + upload_range_start, + upload_range_end - 1, + local_file_size), + "Content-Type": "application/octet-stream" + }) + + if DEBUG: + print("Headers:") + print(headers) + + '''# TODO 优化read的读取占用内存''' + f = io.open(local_file_name, "rb") + f.seek(upload_range_start) + upload_data = f.read(content_length) + sub_response = session.put(upload_url, + headers=headers, + data=upload_data) + + expected_status_code = [200, 201, 202] + if sub_response.status_code in expected_status_code: + if DEBUG: + print("Response status code: {}, " + "bytes {}-{} 已上传成功。".format( + sub_response.status_code, + upload_range_start, + upload_range_end - 1) + ) + print(sub_response.text) + if sub_response.status_code in [200, 201]: + if DEBUG: + print("文件 {} 上传成功。".format(object_name)) + return True + else: + print(sub_response.status_code) + print(sub_response.text) + _error_msg = "Bytes {}-{} 分片上传失败。".format( + upload_range_start, + upload_range_end + ) + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += _error_msg + raise RuntimeError(_error_msg) + + time.sleep(0.5) + else: + raise RuntimeError("session创建失败。") + + except UnauthorizedError as e: + _error_msg = str(e) + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += _error_msg + print(_error_msg) + return False + except Exception as e: + print("文件上传出现错误:") + print(e) + + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += "文件{}上传出现错误:{}".format(object_name, str(e)) + + try: + if upload_url: + if DEBUG: + print("正在清理上传session.") + session.delete(upload_url) + except: + pass + finally: + try: + f.close() + except: + pass + try: + session.close() + except: + pass + + # 重试断点续传 + if retries > 0: + print("重试上传文件....") + return self.resumable_upload( + local_file_name, + object_name=object_name, + store_dir=store_dir, + part_size=part_size, + multipart_threshold=multipart_threshold, + progress_callback=progress_callback, + progress_file_name=progress_file_name, + retries=retries - 1, + ) + else: + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += "文件{}上传失败。".format(object_name) + return False + + def upload_abs_file(self, file_name, remote_dir, *args, **kwargs): + """按照数据类型上传文件 + + :param file_name: 上传文件名称 + :param data_type: 数据类型 site/database/path + :return: True/False + """ + try: + import re + # 根据数据类型提取子分类名称 + # 比如data_type=database,子分类名称是数据库的名称。 + # 提取方式是从file_name中利用正则规则去提取。 + self.error_msg = "" + + file_name = os.path.abspath(file_name) + temp_name = os.path.split(file_name)[1] + object_name = 'backup/' + temp_name + + print(file_name) + print(object_name) + + return self.resumable_upload(file_name, + object_name=object_name, + *args, + **kwargs) + except Exception as e: + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += "文件上传出现错误:{}".format(str(e)) + return False + + def upload_file(self, file_name, data_type, *args, **kwargs): + """按照数据类型上传文件 + + :param file_name: 上传文件名称 + :param data_type: 数据类型 site/database/path + :return: True/False + """ + try: + import re + # 根据数据类型提取子分类名称 + # 比如data_type=database,子分类名称是数据库的名称。 + # 提取方式是从file_name中利用正则规则去提取。 + self.error_msg = "" + + if not file_name or not data_type: + _error_msg = "文件参数错误。" + print(_error_msg) + self.error_msg = _error_msg + return False + + file_name = os.path.abspath(file_name) + temp_name = os.path.split(file_name)[1] + object_name = self.build_object_name(data_type, temp_name) + + # dir_name = os.path.dirname(object_name) + # self.create_dir(dir_name) + if DEBUG: + print(file_name) + print(object_name) + print(dir_name) + + return self.resumable_upload(file_name, + object_name=object_name, + *args, + **kwargs) + except Exception as e: + if self.error_msg: + self.error_msg += r"\n" + self.error_msg += "文件上传出现错误:{}".format(str(e)) + return False diff --git a/plugins/msonedrive/credentials.json b/plugins/msonedrive/credentials.json new file mode 100644 index 000000000..123443172 --- /dev/null +++ b/plugins/msonedrive/credentials.json @@ -0,0 +1,12 @@ +{ + "onedrive-international": { + "client_id": "08125e6b-6502-4ac9-9548-ad682f00848d", + "client_secret": "0WA8Q~sZkZFZKv50ryP4ux~.fpVtbHw7BuTZmbQB", + "authority": "https://login.microsoftonline.com/common", + "token_endpoint": "/oauth2/v2.0/token", + "authorize_endpoint": "/oauth2/v2.0/authorize", + "scopes": "offline_access Files.ReadWrite.All User.Read", + "redirect_uri": "http://localhost", + "api_uri": "https://graph.microsoft.com/v1.0" + } +} \ No newline at end of file diff --git a/plugins/msonedrive/ico.png b/plugins/msonedrive/ico.png new file mode 100644 index 000000000..fb837e43f Binary files /dev/null and b/plugins/msonedrive/ico.png differ diff --git a/plugins/msonedrive/index.html b/plugins/msonedrive/index.html new file mode 100644 index 000000000..fa7ecd85c --- /dev/null +++ b/plugins/msonedrive/index.html @@ -0,0 +1,310 @@ + +
                          +
                          + + +
                          +
                            +
                            + + + +
                            + +
                            +
                            + + + +
                            名称大小更新时间操作
                            +
                            +
                            +
                            + + + + + + \ No newline at end of file diff --git a/plugins/msonedrive/index.py b/plugins/msonedrive/index.py new file mode 100644 index 000000000..71abaeb68 --- /dev/null +++ b/plugins/msonedrive/index.py @@ -0,0 +1,354 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + + +# print(sys.platform) +if sys.platform != "darwin": + os.chdir("/www/server/mdserver-web") + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import core.db as db + +_ver = sys.version_info +is_py2 = (_ver[0] == 2) +is_py3 = (_ver[0] == 3) + +DEBUG = False + +if is_py2: + reload(sys) + sys.setdefaultencoding('utf-8') + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'msonedrive' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def in_array(name, arr=[]): + for x in arr: + if name == x: + return True + return False + + +sys.path.append(getPluginDir() + "/class") +from msodclient import msodclient + + +msodc = msodclient(getPluginDir(), getServerDir()) +msodc.setDebug(False) + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + return 'start' + + +def isAuthApi(): + cfg = getServerDir() + "/user.conf" + if os.path.exists(cfg): + return True + return False + + +def getConf(): + if not isAuthApi(): + sign_in_url, state = msodc.get_sign_in_url() + return mw.returnJson(False, "未授权!", {'auth_url': sign_in_url}) + return mw.returnJson(True, "OK") + + +def setAuthUrl(): + args = getArgs() + data = checkArgs(args, ['url']) + if not data[0]: + return data[1] + + url = args['url'] + + try: + if url.startswith("http://"): + url = url.replace("http://", "https://") + token = msodc.get_token_from_authorized_url(authorized_url=url) + msodc.store_token(token) + msodc.store_user() + return mw.returnJson(True, "授权成功!") + except Exception as e: + return mw.returnJson(False, "授权失败2!:" + str(e)) + return mw.returnJson(False, "授权失败!:" + str(e)) + + +def clearAuth(): + cfg = getServerDir() + "/user.conf" + if os.path.exists(cfg): + os.remove(cfg) + + token = getServerDir() + "/token.pickle" + if os.path.exists(token): + os.remove(token) + + return mw.returnJson(True, "清空授权成功!") + + +def getList(): + cfg = getServerDir() + "/user.conf" + if not os.path.exists(cfg): + return mw.returnJson(False, "未配置,请点击`授权`", []) + + args = getArgs() + data = checkArgs(args, ['path']) + if not data[0]: + return data[1] + + try: + flist = msodc.get_list(args['path']) + return mw.returnJson(True, "ok", flist) + except Exception as e: + return mw.returnJson(False, str(e), []) + + +def createDir(): + cfg = getServerDir() + "/user.conf" + if not os.path.exists(cfg): + return mw.returnJson(False, "未配置OneDrive,请点击`授权`", []) + + args = getArgs() + data = checkArgs(args, ['path', 'name']) + if not data[0]: + return data[1] + + file = args['path'] + "/" + args['name'] + isok = msodc.create_dir(file) + if isok: + return mw.returnJson(True, "创建成功") + return mw.returnJson(False, "创建失败") + + +def deleteDir(): + args = getArgs() + data = checkArgs(args, ['dir_name', 'path']) + if not data[0]: + return data[1] + + file = args['path'] + "/" + args['dir_name'] + file = file.strip('/') + isok = msodc.delete_object(file) + if isok: + return mw.returnJson(True, "删除成功") + return mw.returnJson(False, "文件不为空,删除失败!") + + +def deleteFile(): + args = getArgs() + data = checkArgs(args, ['path', 'filename']) + if not data[0]: + return data[1] + + file = args['path'] + "/" + args['filename'] + file = file.strip('/') + isok = msodc.delete_object(file) + if isok: + return mw.returnJson(True, "删除成功") + return mw.returnJson(False, "删除失败") + + +def findPathName(path, filename): + f = os.scandir(path) + l = [] + for ff in f: + t = {} + if ff.name.find(filename) > -1: + t['filename'] = path + '/' + ff.name + l.append(t) + return l + + +def backupAllFunc(stype): + if not isAuthApi(): + mw.echoInfo("未授权API,无法使用!!!") + return '' + + os.chdir(mw.getPanelDir()) + backup_dir = mw.getBackupDir() + run_dir = mw.getPanelDir() + + stype = sys.argv[1] + name = sys.argv[2] + num = sys.argv[3] + + prefix_dict = { + "site": "web", + "database": "db", + "path": "path", + } + + backups = [] + sql = db.Sql() + + # print("stype:", stype) + # 提前获取-清理多余备份 + if stype == 'site': + pid = sql.table('sites').where('name=?', (name,)).getField('id') + backups = sql.table('backup').where( + 'type=? and pid=?', ('0', pid)).field('id,filename').select() + if stype == 'database': + db_path = mw.getServerDir() + '/mysql' + pid = mw.M('databases').dbPos(db_path, 'mysql').where( + 'name=?', (name,)).getField('id') + backups = sql.table('backup').where( + 'type=? and pid=?', ('1', pid)).field('id,filename').select() + if stype == 'path': + backup_path = backup_dir + '/path' + _name = 'path_{}'.format(os.path.basename(name)) + backups = findPathName(backup_path, _name) + + # 其他类型关系性数据库(mysql类的) + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + db_path = mw.getServerDir() + '/' + plugin_name + pid = mw.M('databases').dbPos(db_path, 'mysql').where( + 'name=?', (name,)).getField('id') + backups = sql.table('backup').where( + 'type=? and pid=?', ('1', pid)).field('id,filename').select() + + args = stype + " " + name + " " + num + cmd = 'python3 ' + run_dir + '/scripts/backup.py ' + args + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + args = "database " + name + " " + num + cmd = 'python3 ' + run_dir + '/plugins/' + \ + plugin_name + '/scripts/backup.py ' + args + + if stype == 'path': + name = os.path.basename(name) + + # print("cmd:", cmd) + os.system(cmd) + + # 开始执行上传信息. + if stype.find('database_') > -1: + bk_name = 'database' + plugin_name = stype.replace('database_', '') + bk_prefix = plugin_name + '/db' + stype = 'database' + else: + bk_prefix = prefix_dict[stype] + bk_name = stype + + find_path = backup_dir + '/' + bk_name + '/' + bk_prefix + '_' + name + find_new_file = "ls " + find_path + "_* | grep '.gz' | cut -d \\ -f 1 | awk 'END {print}'" + + # print(find_new_file) + + filename = mw.execShell(find_new_file)[0].strip() + if filename == "": + mw.echoInfo("not find upload file!") + return False + + mw.echoInfo("准备上传文件 {}".format(filename)) + mw.echoStart('开始上传') + msodc.upload_file(filename, stype) + mw.echoEnd('上传成功') + + # print(backups) + backups = sorted(backups, key=lambda x: x['filename'], reverse=False) + mw.echoStart('开始删除远程备份') + num = int(num) + sep = len(backups) - num + if sep > -1: + for backup in backups: + fn = os.path.basename(backup['filename']) + msodc.delete_file(fn, stype) + mw.echoInfo("---已清理远程过期备份文件:" + fn) + sep -= 1 + if sep < 0: + break + mw.echoEnd('结束删除远程备份') + + return '' + + +def installPreInspection(): + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'set_auth_url': + print(setAuthUrl()) + elif func == 'clear_auth': + print(clearAuth()) + elif func == "get_list": + print(getList()) + elif func == "create_dir": + print(createDir()) + elif func == "delete_dir": + print(deleteDir()) + elif func == 'delete_file': + print(deleteFile()) + elif in_array(func, ['site', 'database', 'path']) or func.find('database_') > -1: + print(backupAllFunc(func)) + else: + print('error') diff --git a/plugins/msonedrive/info.json b/plugins/msonedrive/info.json new file mode 100644 index 000000000..da645c943 --- /dev/null +++ b/plugins/msonedrive/info.json @@ -0,0 +1,15 @@ +{ + "sort":5, + "hook":["backup"], + "title": "OneDrive", + "tip": "lib", + "name": "msonedrive", + "type": "sort", + "ps": "微软家的云网盘服务", + "versions": "1.0", + "shell": "install.sh", + "checks": "server/msonedrive", + "author": "midoks", + "date": "2023-8-18", + "pid": "5" +} diff --git a/plugins/msonedrive/install.sh b/plugins/msonedrive/install.sh new file mode 100644 index 000000000..c1e72bd28 --- /dev/null +++ b/plugins/msonedrive/install.sh @@ -0,0 +1,42 @@ +#!/bin/bash +PATH=/www/server/panel/pyenv/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +# cd /www/server/mdserver-web/plugins/msonedrive && bash install.sh install 1.0 + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +Install_App() +{ + pip install requests-oauthlib==1.3.0 + mkdir -p $serverPath/msonedrive + echo '正在安装脚本文件...' + + echo "${VERSION}" > $serverPath/msonedrive/version.pl + echo '安装完成' + + echo "Successify" +} + + +Uninstall_App() +{ + rm -rf $serverPath/msonedrive +} + +if [ "${1}" == 'install' ];then + Install_App +elif [ "${1}" == 'uninstall' ];then + Uninstall_App +else + echo 'Error!'; +fi diff --git a/plugins/msonedrive/js/msonedrive.js b/plugins/msonedrive/js/msonedrive.js new file mode 100644 index 000000000..de9288ae2 --- /dev/null +++ b/plugins/msonedrive/js/msonedrive.js @@ -0,0 +1,258 @@ + +function msodPost(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'msonedrive', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function createDir(){ + layer.open({ + type: 1, + area: "400px", + title: "创建目录", + closeBtn: 1, + shift: 5, + shadeClose: false, + btn: ['确定','取消'], + content:'
                            \ +

                            \ + 目录名称:\ + \ +

                            \ +
                            ', + success:function(){ + $("input[name='newPath']").focus().keyup(function(e){ + if(e.keyCode == 13) $(".layui-layer-btn0").click(); + }); + }, + yes:function(index,layero){ + var name = $("input[name='newPath']").val(); + if(name == ''){ + layer.msg('目录名称不能为空!',{icon:2}); + return; + } + var path = $("#myPath").val(); + var dirname = name; + var loadT = layer.msg('正在创建目录['+dirname+']...',{icon:16,time:0,shade: [0.3, '#000']}); + msodPost('create_dir', {path:path,name:dirname}, function(data){ + layer.close(loadT); + var rdata = $.parseJSON(data.data); + if(rdata.status) { + showMsg(rdata.msg, function(){ + layer.close(index); + odList(path); + } ,{icon:1}, 2000); + } else{ + layer.msg(rdata.msg,{icon:2}); + } + }); + } + }); +} + + +//设置API +function authApi(){ + + msodPost('conf', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + + // console.log(rdata); + // console.log(rdata.data.auth_url); + var apicon = ''; + if (rdata.status){ + + var html = ''; + html += ''; + + var loadOpen = layer.open({ + type: 1, + title: '已授权', + area: '240px', + content:'
                            '+html+'
                            ', + success: function(){ + $('#clear_auth').click(function(){ + msodPost('clear_auth', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + layer.close(loadOpen); + odList('/'); + },{icon:rdata.status?1:2},2000); + }); + }); + } + }); + return true; + + } else{ + apicon = '
                            '+$("#check_api").html()+'
                            '; + } + + var layer_auth = layer.open({ + type: 1, + area: "620px", + title: "OneDrive授权", + closeBtn: 1, + shift: 5, + shadeClose: false, + content:apicon, + success:function(layero,index){ + // console.log(layero,index); + if (!rdata.status){ + $('.check_api .step_two_url').val(rdata.data['auth_url']); + $('.check_api .open_btlink').attr('href',rdata.data['auth_url']); + + $('.check_api .ico-copy').click(function(){ + copyPass(rdata.data['auth_url']); + }); + + $('.check_api .set_auth_btn').click(function(){ + + var url = $('.check_api .OneDrive').val(); + if ( url == ''){ + layer.msg("验证URL不能为空",{icon:2}); + return; + } + // console.log(url); + msodPost('set_auth_url', {url:url}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var show_time = 2000; + if (!rdata.status){ + show_time = 10000; + } + + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(layer_auth); + odList('/'); + } + },{icon:rdata.status?1:2},show_time); + }); + }); + + + } + + } + }); + }); +} + +//计算当前目录偏移 +function upPathLeft(){ + var UlWidth = $(".place-input ul").width(); + var SpanPathWidth = $(".place-input").width() - 20; + var Ml = UlWidth - SpanPathWidth; + if(UlWidth > SpanPathWidth ){ + $(".place-input ul").css("left",-Ml) + } + else{ + $(".place-input ul").css("left",0) + } +} + +function odList(path){ + msodPost('get_list', {path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if(rdata.status === false){ + showMsg(rdata.msg,function(){ + authApi(); + },{icon:2},2000); + return; + } + + var mlist = rdata.data; + var listBody = '' + var listFiles = '' + for(var i=0;i\'+mlist.list[i].name+'\ + -\ + -\ + 删除' + }else{ + listFiles += '\'+mlist.list[i].name+'\ + '+toSize(mlist.list[i].size)+'\ + '+getLocalTime(mlist.list[i].time)+'\ + 下载 | 删除' + } + } + listBody += listFiles; + + var pathLi=''; + var tmp = path.split('/') + var pathname = ''; + var n = 0; + for(var i=0;i 0 && tmp[i] == '') continue; + var dirname = tmp[i]; + if(dirname == '') { + dirname = '根目录'; + n++; + } + pathname += '/' + tmp[i]; + pathname = pathname.replace('//','/'); + pathLi += '
                          • '+dirname+'
                          • '; + } + var um = 1; + if(tmp[tmp.length-1] == '') um = 2; + var backPath = tmp.slice(0,tmp.length-um).join('/') || '/'; + $('#myPath').val(path); + $(".upyunCon .place-input ul").html(pathLi); + $(".upyunlist .list-list").html(listBody); + + upPathLeft(); + + $('#backBtn').unbind().click(function() { + odList(backPath); + }); + + $('.upyunCon .refreshBtn').unbind().click(function(){ + odList(path); + }); + }); +} + + +//删除文件 +function deleteFile(name, is_dir){ + if (is_dir === false){ + safeMessage('删除文件','删除后将无法恢复,真的要删除['+name+']吗?',function(){ + var path = $("#myPath").val(); + var filename = name; + msodPost('delete_file', {filename:filename,path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + odList(path); + },{icon:rdata.status?1:2},2000); + }); + }); + } else { + safeMessage('删除文件夹','删除后将无法恢复,真的要删除['+name+']吗?',function(){ + var path = $("#myPath").val(); + msodPost('delete_dir', {dir_name:name,path:path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + odList(path); + },{icon:rdata.status?1:2},2000); + }); + }); + } +} \ No newline at end of file diff --git a/plugins/msonedrive/t/test.py b/plugins/msonedrive/t/test.py new file mode 100644 index 000000000..565f507ac --- /dev/null +++ b/plugins/msonedrive/t/test.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# coding: utf-8 + +# python3 plugins/msonedrive/t/test.py + + +# https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/08125e6b-6502-4ac9-9548-ad682f00848d/objectId/62b1d655-9828-47ed-be99-65eb18c3a929/isMSAApp~/false/defaultBlade/Overview/appSignInAudience/AzureADandPersonalMicrosoftAccount/servicePrincipalCreated~/true + +# 0WA8Q~sZkZFZKv50ryP4ux~.fpVtbHw7BuTZmbQB +# client_id:d9878fac-8526-4ff6-8036-e1c92dd9dd80 + +# 08125e6b-6502-4ac9-9548-ad682f00848d + + +# https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&client_id=d9878fac-8526-4ff6-8036-e1c92dd9dd80&redirect_uri=http://localhost&scope=offline_access+Files.ReadWrite.All+User.Read&state=cwopMdHiPIkze4MvgFL6WfSl8LdYAl&prompt=login + + +# http://localhost/?code=M.C106_BAY.2.3e12c859-6107-0c5b-9ef4-14b3fb8269ba&state=JzHdzHXmA7x6zl7Be6cJ6uOlf9Bg69 + + +# python3 /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/msonedrive/index.py site t1.cn 3 +# python3 /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/msonedrive/index.py database t1 3 +# python3 +# /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/msonedrive/index.py +# path /Users/midoks/Desktop/dev/python 3 + + +import sys +import io +import os +import time +import re +import json + + +from requests_oauthlib import OAuth2Session + +sys.path.append(os.getcwd() + "/class/core") +import mw + + +def getPluginName(): + return 'msonedrive' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +sys.path.append(getPluginDir() + "/class") +from msodclient import msodclient + +msodclient.setDebug(True) +msodc = msodclient(getPluginDir(), getServerDir()) + + +# sign_in_url, state = msodc.get_sign_in_url() +# print(sign_in_url) + + +def set_auth_url(url): + try: + if url.startswith("http://"): + url = url.replace("http://", "https://") + token = msodc.get_token_from_authorized_url( + authorized_url=url) + msodc.store_token(token) + msodc.store_user() + return mw.returnJson(True, "授权成功!") + except Exception as e: + print(e) + return mw.returnJson(False, "授权失败2!:" + str(e)) + return mw.returnJson(False, "授权失败!:" + str(e)) + +# url = 'http://localhost/?code=M.C106_BAY.2.310112f3-a158-c400-9667-d158cbd1de6c&state=jEJz0ucR9bpZYD9PGxp2GgRDotrzO6' +# token = set_auth_url(url) +# print(token) + +# token = msodc.get_token() +# print("token:", token) + +# t = msodc.get_list('/backup') +# print(t) + +# t = msodc.create_dir('backup') +# print(t) + +# t = msodc.delete_object('backup') +# print(t) + + +t = msodc.upload_file('web_t1.cn_20230830_134549.tar.gz', 'site') +print(t) +# print(msodc.error_msg) + +# /Users/midoks/Desktop/mwdev/server/mdserver-web/paramiko.log +# backup/site/paramiko.log +# |-正在上传到 backup/site/paramiko.log... +# True + + +# t = msodc.upload_abs_file( +# 'web_t1.cn_20230830_134549.tar.gz', 'site') +# print(t) +# print(msodc.error_msg) diff --git a/plugins/mtproxy/conf/mt.toml b/plugins/mtproxy/conf/mt.toml new file mode 100644 index 000000000..495d45a3e --- /dev/null +++ b/plugins/mtproxy/conf/mt.toml @@ -0,0 +1,23 @@ +debug = false + +secret = "{$SECRET}" +bind-to = "0.0.0.0:{$PORT}" + +concurrency = 256 +prefer-ip = "prefer-ipv4" +domain-fronting-port = 443 +tolerate-time-skewness = "10s" + +[network] +doh-ip = "9.9.9.9" + +[network.timeout] +tcp = "5s" +http = "10s" +idle = "1m" + +[defense.anti-replay] +enabled = true +max-size = "1mib" +error-rate = 0.001 + diff --git a/plugins/mtproxy/ico.png b/plugins/mtproxy/ico.png new file mode 100644 index 000000000..efaab9dfb Binary files /dev/null and b/plugins/mtproxy/ico.png differ diff --git a/plugins/mtproxy/index.html b/plugins/mtproxy/index.html new file mode 100755 index 000000000..f4a0d936f --- /dev/null +++ b/plugins/mtproxy/index.html @@ -0,0 +1,23 @@ +
                            +
                            +
                            +

                            服务

                            +

                            自启动

                            +

                            常用功能

                            +

                            配置修改

                            +

                            配置文件

                            +
                            +
                            +
                            +
                            +
                            +
                            + \ No newline at end of file diff --git a/plugins/mtproxy/index.py b/plugins/mtproxy/index.py new file mode 100755 index 000000000..c15208253 --- /dev/null +++ b/plugins/mtproxy/index.py @@ -0,0 +1,257 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'mtproxy' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getServiceTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".service.tpl" + return path + + +def getConfEnvTpl(): + path = getPluginDir() + "/conf/mt.toml" + return path + + +def getConfEnv(): + path = getServerDir() + "/mt.toml" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + +def status(): + cmd = "ps -ef|grep mtproxy| grep mtg |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def getServiceFile(): + systemDir = mw.systemdCfgDir() + return systemDir + '/mtproxy.service' + + +def getMtproxyPort(): + return '8349' + + +def __release_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'mtproxy', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + +def __delete_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().delAcceptPortCmd(port, 'tcp') + return port + except Exception as e: + return "Delete failed {}".format(e) + +def openPort(): + port = getMtproxyPort() + for i in [port]: + __release_port(i) + return True + +def delPort(): + port = getMtproxyPort() + for i in [port]: + __delete_port(i) + return True + + +def initDreplace(): + + envTpl = getConfEnvTpl() + dstEnv = getConfEnv() + cmd = getServerDir() + '/mtg/mtg generate-secret `head -c 16 /dev/urandom | xxd -ps`' + secret = mw.execShell(cmd) + if not os.path.exists(dstEnv): + env_content = mw.readFile(envTpl) + env_content = env_content.replace('{$PORT}', getMtproxyPort()) + env_content = env_content.replace('{$SECRET}', secret[0].strip()) + mw.writeFile(dstEnv, env_content) + openPort() + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/mtproxy.service' + systemServiceTpl = getServiceTpl() + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return 'ok' + + +def mtOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + mw.execShell('systemctl daemon-reload') + data = mw.execShell('systemctl ' + method + ' mtproxy') + if data[1] == '': + return 'ok' + return data[1] + + return 'fail' + + +def start(): + return mtOp('start') + + +def stop(): + return mtOp('stop') + + +def restart(): + return mtOp('restart') + + +def reload(): + return mtOp('reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status mtproxy | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + mw.execShell('systemctl enable mtproxy') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + mw.execShell('systemctl disable mtproxy') + return 'ok' + +def getMtproxyUrl(): + conf = getConfEnv() + content = mw.readFile(conf) + + + rep = r'bind-to\s*=\s*(.*)' + tmp = re.search(rep, content) + bind_to = tmp.groups()[0].strip() + bind_to = bind_to.strip('"') + + rep = r'secret\s*=\s*(.*)' + tmp = re.search(rep, content) + secret = tmp.groups()[0].strip() + secret = secret.strip('"') + + info = bind_to.split(":") + + ip = mw.getLocalIp() + + url = 'tg://proxy?server={0}&port={1}&secret={2}'.format(ip, info[1], secret) + return mw.returnJson(True, 'ok', url) + +def installPreInspection(): + sys = mw.execShell("cat /etc/*-release | grep PRETTY_NAME |awk -F = '{print $2}' | awk -F '\"' '{print $2}'| awk '{print $1}'") + + if sys[1] != '': + return '不支持该系统' + + sys_id = mw.execShell("cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F '\"' '{print $2}'") + + sysName = sys[0].strip().lower() + sysId = sys_id[0].strip() + + if sysName in ('opensuse'): + return '不支持该系统' + + return 'ok' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getServiceFile()) + elif func == 'conf_env': + print(getConfEnv()) + elif func == 'url': + print(getMtproxyUrl()) + else: + print('error') diff --git a/plugins/mtproxy/info.json b/plugins/mtproxy/info.json new file mode 100755 index 000000000..a68c2a59b --- /dev/null +++ b/plugins/mtproxy/info.json @@ -0,0 +1,19 @@ +{ + "sort": 7, + "ps": "你的纸飞机", + "install_pre_inspection":true, + "name": "mtproxy", + "title": "mtproxy", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "checks": "server/mtproxy", + "path": "server/mtproxy", + "display": 1, + "author": "TelegramMessenger", + "date": "2022-07-09", + "home": "https://github.com/9seconds/mtg", + "type": 0, + "pid": "5" +} \ No newline at end of file diff --git a/plugins/mtproxy/init.d/mtproxy.service.tpl b/plugins/mtproxy/init.d/mtproxy.service.tpl new file mode 100644 index 000000000..03ad10760 --- /dev/null +++ b/plugins/mtproxy/init.d/mtproxy.service.tpl @@ -0,0 +1,14 @@ +[Unit] +Description=MTProxy +After=network.target + +[Service] +Type=simple +WorkingDirectory={$SERVER_PATH}/mtproxy +ExecStart={$SERVER_PATH}/mtproxy/mtg/mtg run {$SERVER_PATH}/mtproxy/mt.toml +RestartSec=3 +Restart=on-failure +AmbientCapabilities=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/mtproxy/install.sh b/plugins/mtproxy/install.sh new file mode 100755 index 000000000..494fa03b5 --- /dev/null +++ b/plugins/mtproxy/install.sh @@ -0,0 +1,141 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysArch=`arch` +sysName=`uname` + +# systemctl status mtg +# cd /www/server/mdserver-web && python3 plugins/mtproxy/index.py url +# cd /www/server/mdserver-web/plugins/mtproxy && /bin/bash install.sh install 1.0 + +bash ${rootPath}/scripts/getos.sh +echo "bash ${rootPath}/scripts/getos.sh" +OSNAME=`cat ${rootPath}/data/osname.pl` + +VERSION_MIN=2.1.7 +VERSION=v${VERSION_MIN} + +sysName=$(uname | tr '[:upper:]' '[:lower:]') + +ARCH=amd64 +get_arch() { + echo "package main +import ( + \"fmt\" + \"runtime\" +) +func main() { fmt.Println(runtime.GOARCH) }" > /tmp/go_arch.go + + ARCH=$(go run /tmp/go_arch.go) + echo "ARCH:${ARCH}" +} + +TARGET_DIR="${serverPath}/mtproxy" + +get_download_url() { + DOWNLOAD_URL="https://github.com/9seconds/mtg/releases/download/$VERSION/mtg-${VERSION_MIN}-${sysName}-${ARCH}.tar.gz" +} + +# download file +download_file() { + url="${1}" + destination="${2}" + + printf "Fetching ${url} \n\n" + + if test -x "$(command -v curl)"; then + code=$(curl --connect-timeout 15 -w '%{http_code}' -L "${url}" -o "${destination}") + elif test -x "$(command -v wget)"; then + code=$(wget -t2 -T15 -O "${destination}" --server-response "${url}" 2>&1 | awk '/^ HTTP/{print $2}' | tail -1) + else + printf "\e[1;31mNeither curl nor wget was available to perform http requests.\e[0m\n" + exit 1 + fi + + if [ "${code}" != 200 ]; then + printf "\e[1;31mRequest failed with code %s\e[0m\n" $code + exit 1 + else + printf "\n\e[1;33mDownload succeeded\e[0m\n" + fi +} + +# /www/server/mtproxy/mtg/mtg run /www/server/mtproxy/mt.toml +Install_app() +{ + mkdir -p ${serverPath}/mtproxy + mkdir -p ${serverPath}/source/mtproxy + echo "${1}" > ${serverPath}/mtproxy/version.pl + + if [ "$OSNAME" == "centos" ]; then + yum install -y golang golang-src + elif [ "$OSNAME" == "amazon" ]; then + yum install -y golang golang-src + elif [ "$OSNAME" == "rocky" ]; then + yum install -y golang golang-src + elif [ "$OSNAME" == "rhel" ]; then + yum install -y golang golang-src + elif [ "$OSNAME" == "opensuse" ]; then + zypper install -y golang golang-src + elif [ "$sysName" == "macos" ]; then + echo "macos" + else + apt install -y golang golang-src + fi + + if [ "$sysName" == "darwin" ]; then + ARCH=arm64 + DOWNLOAD_URL="https://github.com/9seconds/mtg/releases/download/$VERSION/mtg-${VERSION_MIN}-${sysName}-arm64.tar.gz" + elif [ "$sysName" != "macos" ]; then + get_arch + get_download_url + else + echo "else" + fi + + DOWNLOAD_FILE="$(mktemp).tar.gz" + download_file $DOWNLOAD_URL $DOWNLOAD_FILE + + tar -C "$TARGET_DIR" -zxf $DOWNLOAD_FILE + rm -rf $DOWNLOAD_FILE + + if [ -d ${serverPath}/mtproxy/mtg ];then + rm -rf ${serverPath}/mtproxy/mtg + fi + + # cd ${serverPath}/mtproxy + # curl -s https://core.telegram.org/getProxySecret -o proxy-secret + # curl -s https://core.telegram.org/getProxyConfig -o proxy-multi.conf + + + mv ${serverPath}/mtproxy/mtg-${VERSION_MIN}-${sysName}-${ARCH} ${serverPath}/mtproxy/mtg + echo '安装完成' + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/mtproxy/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/mtproxy/index.py initd_install +} + +Uninstall_app() +{ + if [ -f /usr/lib/systemd/system/mtproxy.service ];then + systemctl stop mtproxy + rm -rf /usr/lib/systemd/system/mtproxy.service + systemctl daemon-reload + fi + rm -rf ${serverPath}/mtproxy + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app $2 +else + Uninstall_app $2 +fi diff --git a/plugins/mtproxy/js/mtproxy.js b/plugins/mtproxy/js/mtproxy.js new file mode 100644 index 000000000..748e2db60 --- /dev/null +++ b/plugins/mtproxy/js/mtproxy.js @@ -0,0 +1,61 @@ +function mtPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'mtproxy'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function mtCommonFunc(){ + var con = '

                            \ + \ +

                            '; + + $(".soft-man-con").html(con); + + $('#mtproxy_url').click(function(){ + mtPost('url', '', {}, function(rdata){ + var data = $.parseJSON(rdata.data); + + layer.open({ + title: "mtproxy代理信息", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                            \ +
                            \ +
                            '+data.data+'
                            \ +
                            \ +
                            ', + success:function(){ + copyText(data.data); + }, + yes:function(){ + copyText(data.data); + } + }); + }); + }); +} \ No newline at end of file diff --git a/plugins/mysql-community/conf/classic.cnf b/plugins/mysql-community/conf/classic.cnf new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/mysql-community/conf/gtid.cnf b/plugins/mysql-community/conf/gtid.cnf new file mode 100644 index 000000000..5076776a6 --- /dev/null +++ b/plugins/mysql-community/conf/gtid.cnf @@ -0,0 +1,4 @@ +[mysqld] +# SHOW GLOBAL VARIABLES LIKE '%gtid%' +gtid_mode=ON +enforce_gtid_consistency=ON \ No newline at end of file diff --git a/plugins/mysql-community/conf/my5.7.cnf b/plugins/mysql-community/conf/my5.7.cnf new file mode 100644 index 000000000..f97a2bfb7 --- /dev/null +++ b/plugins/mysql-community/conf/my5.7.cnf @@ -0,0 +1,110 @@ +[client] +user = root +#password = your_password +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB +language={$SERVER_APP_PATH}/share/english + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +character-set-server = UTF8MB4 + +query_cache_type = 1 +query_cache_size = 64M + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve = 1 +skip-ssl +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +log-bin=mysql-bin +binlog_format=mixed +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +expire_logs_days=30 +#log_queries_not_using_indexes=on + +relay-log=mdserver +relay-log-index=mdserver + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log-slave-updates +#replicate-do-db +slave_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +master_info_repository = table +relay_log_info_repository = table + +default_storage_engine = InnoDB +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_log_file_size = 5M +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql-community/conf/my8.0.cnf b/plugins/mysql-community/conf/my8.0.cnf new file mode 100644 index 000000000..0f5f4872a --- /dev/null +++ b/plugins/mysql-community/conf/my8.0.cnf @@ -0,0 +1,107 @@ +[client] +user = root +#password = your_password +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password + +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33206 +mysqlx_port = 33260 +socket = {$SERVER_APP_PATH}/mysql.sock +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=1 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +#skip-external-locking +#skip-grant-tables +#loose-skip-innodb +#skip-networking +#skip-name-resolve + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +#log_queries_not_using_indexes=on + +relay-log=mdserver +relay-log-index=mdserver + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +default_storage_engine = InnoDB +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 +binlog_expire_logs_seconds=604800 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql-community/conf/my8.2.cnf b/plugins/mysql-community/conf/my8.2.cnf new file mode 100644 index 000000000..2ab5d59dd --- /dev/null +++ b/plugins/mysql-community/conf/my8.2.cnf @@ -0,0 +1,106 @@ +[client] +user = root +#password = your_password +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33206 +mysqlx_port = 33260 +socket = {$SERVER_APP_PATH}/mysql.sock +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=1 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +#skip-external-locking +#skip-grant-tables +#loose-skip-innodb +#skip-networking +#skip-name-resolve + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +#log_queries_not_using_indexes=on + +relay-log=mdserver +relay-log-index=mdserver + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +default_storage_engine = InnoDB +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 +binlog_expire_logs_seconds=604800 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql-community/conf/my8.3.cnf b/plugins/mysql-community/conf/my8.3.cnf new file mode 100644 index 000000000..2ab5d59dd --- /dev/null +++ b/plugins/mysql-community/conf/my8.3.cnf @@ -0,0 +1,106 @@ +[client] +user = root +#password = your_password +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33206 +mysqlx_port = 33260 +socket = {$SERVER_APP_PATH}/mysql.sock +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=1 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +#skip-external-locking +#skip-grant-tables +#loose-skip-innodb +#skip-networking +#skip-name-resolve + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +#log_queries_not_using_indexes=on + +relay-log=mdserver +relay-log-index=mdserver + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +default_storage_engine = InnoDB +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 +binlog_expire_logs_seconds=604800 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql-community/conf/my8.4.cnf b/plugins/mysql-community/conf/my8.4.cnf new file mode 100644 index 000000000..2ab5d59dd --- /dev/null +++ b/plugins/mysql-community/conf/my8.4.cnf @@ -0,0 +1,106 @@ +[client] +user = root +#password = your_password +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33206 +mysqlx_port = 33260 +socket = {$SERVER_APP_PATH}/mysql.sock +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=1 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +#skip-external-locking +#skip-grant-tables +#loose-skip-innodb +#skip-networking +#skip-name-resolve + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +#log_queries_not_using_indexes=on + +relay-log=mdserver +relay-log-index=mdserver + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +default_storage_engine = InnoDB +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 +binlog_expire_logs_seconds=604800 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql-community/conf/my9.0.cnf b/plugins/mysql-community/conf/my9.0.cnf new file mode 100644 index 000000000..4ec52424a --- /dev/null +++ b/plugins/mysql-community/conf/my9.0.cnf @@ -0,0 +1,106 @@ +[client] +user = root +#password = your_password +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33206 +mysqlx_port = 33260 +socket = {$SERVER_APP_PATH}/mysql.sock +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=1 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +#skip-external-locking +#skip-grant-tables +#loose-skip-innodb +#skip-networking +#skip-name-resolve + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +#log_queries_not_using_indexes=on + +relay-log=mdserver +relay-log-index=mdserver + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +default_storage_engine = InnoDB +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table = 1 +binlog_expire_logs_seconds=604800 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql-community/conf/my9.1.cnf b/plugins/mysql-community/conf/my9.1.cnf new file mode 100644 index 000000000..4ec52424a --- /dev/null +++ b/plugins/mysql-community/conf/my9.1.cnf @@ -0,0 +1,106 @@ +[client] +user = root +#password = your_password +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33206 +mysqlx_port = 33260 +socket = {$SERVER_APP_PATH}/mysql.sock +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=1 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +#skip-external-locking +#skip-grant-tables +#loose-skip-innodb +#skip-networking +#skip-name-resolve + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +#log_queries_not_using_indexes=on + +relay-log=mdserver +relay-log-index=mdserver + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +default_storage_engine = InnoDB +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table = 1 +binlog_expire_logs_seconds=604800 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql-community/conf/my9.2.cnf b/plugins/mysql-community/conf/my9.2.cnf new file mode 100644 index 000000000..4ec52424a --- /dev/null +++ b/plugins/mysql-community/conf/my9.2.cnf @@ -0,0 +1,106 @@ +[client] +user = root +#password = your_password +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33206 +mysqlx_port = 33260 +socket = {$SERVER_APP_PATH}/mysql.sock +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=1 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +#skip-external-locking +#skip-grant-tables +#loose-skip-innodb +#skip-networking +#skip-name-resolve + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +#log_queries_not_using_indexes=on + +relay-log=mdserver +relay-log-index=mdserver + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +default_storage_engine = InnoDB +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table = 1 +binlog_expire_logs_seconds=604800 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql-community/conf/my9.3.cnf b/plugins/mysql-community/conf/my9.3.cnf new file mode 100644 index 000000000..4ec52424a --- /dev/null +++ b/plugins/mysql-community/conf/my9.3.cnf @@ -0,0 +1,106 @@ +[client] +user = root +#password = your_password +port = 33206 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 33206 +mysqlx_port = 33260 +socket = {$SERVER_APP_PATH}/mysql.sock +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=1 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +#skip-external-locking +#skip-grant-tables +#loose-skip-innodb +#skip-networking +#skip-name-resolve + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=3 +#log_queries_not_using_indexes=on + +relay-log=mdserver +relay-log-index=mdserver + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +default_storage_engine = InnoDB +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table = 1 +binlog_expire_logs_seconds=604800 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql-community/conf/mysql.sql b/plugins/mysql-community/conf/mysql.sql new file mode 100755 index 000000000..f98ddf79b --- /dev/null +++ b/plugins/mysql-community/conf/mysql.sql @@ -0,0 +1,58 @@ +CREATE TABLE IF NOT EXISTS `config` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `mysql_root` TEXT +); + +INSERT INTO `config` (`id`, `mysql_root`) VALUES (1, 'admin'); + +CREATE TABLE IF NOT EXISTS `databases` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pid` INTEGER, + `name` TEXT, + `username` TEXT, + `password` TEXT, + `accept` TEXT, + `rw` TEXT DEFAULT 'rw', + `ps` TEXT, + `addtime` TEXT +); +-- ALTER TABLE `databases` ADD COLUMN `rw` TEXT DEFAULT 'rw'; + +CREATE TABLE IF NOT EXISTS `master_replication_user` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` TEXT, + `password` TEXT, + `accept` TEXT, + `ps` TEXT, + `addtime` TEXT +); + +-- 从库配置主库的[ssh private key] +-- drop table `slave_id_rsa`; +CREATE TABLE IF NOT EXISTS `slave_id_rsa` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `ip` TEXT, + `port` TEXT, + `user` TEXT, + `db_user` TEXT, + `id_rsa` TEXT, + `ps` TEXT, + `addtime` TEXT +); + +-- 从库配置主库的[user] +-- drop table `slave_user`; +CREATE TABLE IF NOT EXISTS `slave_sync_user` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `ip` TEXT, + `port` TEXT, + `user` TEXT, + `pass` TEXT, + `mode` TEXT, + `cmd` TEXT, + `db` TEXT, + `addtime` TEXT +); +ALTER TABLE `slave_sync_user` ADD COLUMN `db` TEXT DEFAULT ''; + + diff --git a/plugins/mysql-community/ico.png b/plugins/mysql-community/ico.png new file mode 100644 index 000000000..ead815fc2 Binary files /dev/null and b/plugins/mysql-community/ico.png differ diff --git a/plugins/mysql-community/index.html b/plugins/mysql-community/index.html new file mode 100755 index 000000000..cb109b0db --- /dev/null +++ b/plugins/mysql-community/index.html @@ -0,0 +1,59 @@ +
                            +
                            +
                            +
                            + +

                            服务

                            +

                            自启动

                            +

                            配置文件

                            +

                            存储位置

                            +

                            端口

                            +

                            当前状态

                            +

                            性能优化

                            +

                            日志

                            +

                            慢日志

                            +

                            BINLOG

                            +

                            管理列表

                            +

                            主从配置

                            +
                            +
                            +
                            +
                            +
                            + +
                            + + + + \ No newline at end of file diff --git a/plugins/mysql-community/index.py b/plugins/mysql-community/index.py new file mode 100755 index 000000000..7e414682e --- /dev/null +++ b/plugins/mysql-community/index.py @@ -0,0 +1,3716 @@ +# coding=utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + +from packaging import version as pk_version + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'mysql-community' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getSPluginDir(): + return '/www/server/mdserver-web/plugins/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def is_number(s): + try: + float(s) + return True + except ValueError: + pass + + try: + import unicodedata + unicodedata.numeric(s) + return True + except (TypeError, ValueError): + pass + + return False + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + return tmp + + +def getBackupDir(): + bk_path = mw.getBackupDir() + "/database/mysql-community" + if not os.path.isdir(bk_path): + mw.execShell("mkdir -p {}".format(bk_path)) + return bk_path + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + path = getServerDir() + '/etc/my.cnf' + return path + + +def getDbPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getDbServerId(): + file = getConf() + content = mw.readFile(file) + rep = r'server-id\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getSocketFile(): + file = getConf() + content = mw.readFile(file) + rep = r'socket\s*=\s*(.*)' + tmp = re.search(rep, content) + + socket = tmp.groups()[0].strip() + return socket + + +def getErrorLogsFile(): + file = getConf() + content = mw.readFile(file) + rep = r'log-error\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getAuthPolicy(): + file = getConf() + content = mw.readFile(file) + rep = r'authentication_policy\s*=\s*(.*)' + tmp = re.search(rep, content) + if tmp: + return tmp.groups()[0].strip() + + rep2 = r'default-authentication-plugin\s*=\s*(.*)' + tmp2 = re.search(rep, content) + if tmp2: + return tmp2.groups()[0].strip() + # caching_sha2_password + return 'mysql_native_password' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP_PATH}',service_path + '/' + getPluginName()) + + server_id = int(time.time()) + content = content.replace('{$SERVER_ID}', str(server_id)) + return content + + +def pSqliteDb(dbname='databases'): + file = getServerDir() + '/mysql.db' + name = 'mysql' + + import_sql = mw.readFile(getPluginDir() + '/conf/mysql.sql') + md5_sql = mw.md5(import_sql) + + import_sign = False + save_md5_file = getServerDir() + '/import_sql.md5' + if os.path.exists(save_md5_file): + save_md5_sql = mw.readFile(save_md5_file) + if save_md5_sql != md5_sql: + import_sign = True + mw.writeFile(save_md5_file, md5_sql) + else: + mw.writeFile(save_md5_file, md5_sql) + + if not os.path.exists(file) or import_sql: + conn = mw.M(dbname).dbPos(getServerDir(), name) + csql_list = import_sql.split(';') + for index in range(len(csql_list)): + conn.execute(csql_list[index], ()) + + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + + +def pMysqlDb(): + # pymysql + db = mw.getMyORM() + # MySQLdb | + # db = mw.getMyORMDb() + + db.setPort(getDbPort()) + db.setSocket(getSocketFile()) + # db.setCharset("utf8") + db.setPwd(pSqliteDb('config').where('id=?', (1,)).getField('mysql_root')) + return db + + +def initDreplace(version=''): + + t = getServerDir() + if not os.path.exists(t): + return + + conf_dir = getServerDir() + '/etc' + mode_dir = conf_dir + '/mode' + + conf_list = [ + conf_dir, + mode_dir, + ] + for conf in conf_list: + if not os.path.exists(conf): + os.mkdir(conf) + + tmp_dir = getServerDir() + '/tmp' + if not os.path.exists(tmp_dir): + os.mkdir(tmp_dir) + mw.execShell("chown -R mysql:mysql " + tmp_dir) + mw.execShell("chmod 750 " + tmp_dir) + + my_conf = conf_dir + '/my.cnf' + if not os.path.exists(my_conf): + tpl = getPluginDir() + '/conf/my' + version + '.cnf' + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(my_conf, content) + + classic_conf = mode_dir + '/classic.cnf' + if not os.path.exists(classic_conf): + tpl = getPluginDir() + '/conf/classic.cnf' + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(classic_conf, content) + + gtid_conf = mode_dir + '/gtid.cnf' + if not os.path.exists(gtid_conf): + tpl = getPluginDir() + '/conf/gtid.cnf' + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(gtid_conf, content) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/mysql-community.service' + systemServiceTpl = getPluginDir() + '/init.d/mysql' + version + '.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + if mw.getOs() != 'darwin': + mw.execShell('chown -R mysql mysql ' + getServerDir()) + return 'ok' + +def process_status(): + cmd = "ps -ef|grep mysql-community|grep mysql |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + +def status(version=''): + pid = getPidFile() + if not os.path.exists(pid): + return 'stop' + return 'start' + + +def getDataDir(): + file = getConf() + content = mw.readFile(file) + rep = r'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getLogBinName(): + file = getConf() + content = mw.readFile(file) + rep = r'log-bin\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pid-file\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def binLog(version=''): + args = getArgs() + conf = getConf() + con = mw.readFile(conf) + + if con.find('#log-bin=mysql-bin') != -1: + if 'status' in args: + return mw.returnJson(False, '0') + con = con.replace('#log-bin=mysql-bin', 'log-bin=mysql-bin') + con = con.replace('#binlog_format=mixed', 'binlog_format=mixed') + + con = con.replace('skip-log-bin', '#skip-log-bin') + con = con.replace('disable-log-bin', '#disable-log-bin') + con = con.replace('skip-slave-start', '#skip-slave-start') + + mw.writeFile(conf, con) + mw.execShell('sync') + restart(version) + else: + path = getDataDir() + if 'status' in args: + dsize = 0 + for n in os.listdir(path): + if len(n) < 9: + continue + if n[0:9] == 'mysql-bin': + dsize += os.path.getsize(path + '/' + n) + return mw.returnJson(True, dsize) + con = con.replace('log-bin=mysql-bin', '#log-bin=mysql-bin') + con = con.replace('binlog_format=mixed', '#binlog_format=mixed') + + con = con.replace('#skip-log-bin', 'skip-log-bin') + con = con.replace('#disable-log-bin', 'disable-log-bin') + con = con.replace('#skip-slave-start', 'skip-slave-start') + + mw.writeFile(conf, con) + mw.execShell('sync') + restart(version) + mw.execShell('rm -f ' + path + '/mysql-bin.*') + mw.execShell('rm -f ' + path + '/binlog.*') + return mw.returnJson(True, '设置成功!') + + +def setSkipGrantTables(v): + ''' + 设置是否密码验证 + ''' + conf = getConf() + con = mw.readFile(conf) + if v: + if con.find('#skip-grant-tables') != -1: + con = con.replace('#skip-grant-tables', 'skip-grant-tables') + else: + con = con.replace('skip-grant-tables', '#skip-grant-tables') + mw.writeFile(conf, con) + return True + +def binLogList(): + args = getArgs() + data = checkArgs(args, ['page', 'page_size', 'tojs']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + data_dir = getDataDir() + log_bin_name = getLogBinName() + + alist = os.listdir(data_dir) + log_bin_l = [] + for x in range(len(alist)): + f = alist[x] + t = {} + if f.startswith(log_bin_name) and not f.endswith('.index'): + abspath = data_dir + '/' + f + t['name'] = f + t['size'] = os.path.getsize(abspath) + t['time'] = mw.getDataFromInt(os.path.getctime(abspath)) + log_bin_l.append(t) + + log_bin_l = sorted(log_bin_l, key=lambda x: x['time'], reverse=True) + + # print(log_bin_l) + # print(data_dir, log_bin_name) + + count = len(log_bin_l) + + page_start = (page - 1) * page_size + page_end = page_start + page_size + if page_end > count: + page_end = count + + data = {} + page_args = {} + page_args['count'] = count + page_args['p'] = page + page_args['row'] = page_size + page_args['tojs'] = args['tojs'] + data['page'] = mw.getPage(page_args) + data['data'] = log_bin_l[page_start:page_end] + + return mw.getJson(data) + +def cleanBinLog(): + db = pMysqlDb() + cleanTime = time.strftime('%Y-%m-%d %H:%i:%s', time.localtime()) + db.execute("PURGE MASTER LOGS BEFORE '" + cleanTime + "';") + return mw.returnJson(True, '清理BINLOG成功!') + +def getErrorLog(): + args = getArgs() + filename = getErrorLogsFile() + if not os.path.exists(filename): + return mw.returnJson(False, '指定文件不存在!') + if 'close' in args: + mw.writeFile(filename, '') + return mw.returnJson(False, '日志已清空') + info = mw.getLastLine(filename, 18) + return mw.returnJson(True, 'OK', info) + + +def getShowLogFile(): + file = getConf() + content = mw.readFile(file) + rep = r'slow-query-log-file\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getMdb8Ver(): + return ['8.0','8.1','8.2','8.3','8.4','9.0','9.1', '9.2', '9.3', '9.4'] + +def getSlaveName(): + mdb8 = getMdb8Ver() + if mw.inArray(mdb8, version): + return 'replica' + return 'slave' + +def pGetDbUser(): + if mw.isAppleSystem(): + user = mw.execShell("who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + return 'mysql' + + +def initMysql57Data(): + datadir = getDataDir() + if not os.path.exists(datadir + '/mysql'): + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + user = pGetDbUser() + + cmd = serverdir + '/bin/mysqld --basedir=' + serverdir + ' --datadir=' + \ + datadir + ' --initialize-insecure --explicit_defaults_for_timestamp' + # print(cmd) + data = mw.execShell(cmd) + # print(data) + if data[1].lower().find('error') != -1: + exit("Init MySQL5.7 Data Error:"+data[1]) + + if not mw.isAppleSystem(): + mw.execShell('chown -R mysql:mysql ' + datadir) + mw.execShell('chmod -R 755 ' + datadir) + return False + return True + + +def initMysql8Data(): + ''' + chmod 644 /www/server/mysql-community/etc/my.cnf + try: + mysqld --basedir=/usr --datadir=/www/server/mysql-community/data --initialize-insecure + mysqld --defaults-file=/www/server/mysql-community/etc/my.cnf --initialize-insecure + mysqld --initialize-insecure + select user, plugin from user; + update user set authentication_string=password("123123"),plugin='mysql_native_password' where user='root'; + ''' + datadir = getDataDir() + if not os.path.exists(datadir + '/mysql'): + serverdir = getServerDir() + user = pGetDbUser() + + cmd = serverdir + '/bin/mysqld --basedir=' + serverdir + ' --datadir=' + datadir + \ + ' --initialize-insecure --lower-case-table-names=1' + # print(cmd) + data = mw.execShell(cmd) + # print(data) + if data[1].lower().find('error') != -1: + exit("Init MySQL8+ Data Error:"+data[1]) + if data[1].lower().find('not found') != -1: + exit("Init MySQL8+ Data Error:"+data[1]) + + if not mw.isAppleSystem(): + mw.execShell('chown -R mysql:mysql ' + datadir) + mw.execShell('chmod -R 755 ' + datadir) + return False + return True + + +def initMysql8Pwd(): + ''' + /usr/bin/mysql --defaults-file=/www/server/mysql-apt/etc/my.cnf -uroot -e"UPDATE mysql.user SET password=PASSWORD('BhIroUczczNVaKvw') WHERE user='root';flush privileges;" + /usr/bin/mysql --defaults-file=/www/server/mysql-apt/etc/my.cnf -uroot -e"alter user 'root'@'localhost' identified by '123456';" + ''' + time.sleep(5) + + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + pwd = mw.getRandomString(16) + + cmd_my = serverdir + '/bin/mysql' + + cmd_pass = cmd_my + ' --defaults-file=' + myconf + ' -uroot -e' + cmd_pass = cmd_pass + '"alter user \'root\'@\'localhost\' identified by \'' + pwd + '\';' + cmd_pass = cmd_pass + 'flush privileges;"' + # print(cmd_pass) + data = mw.execShell(cmd_pass) + # print(data) + + # 删除空账户 + drop_empty_user = cmd_my + ' --defaults-file=' + myconf + ' -uroot -p' + \ + pwd + ' -e "use mysql;delete from user where USER=\'\'"' + mw.execShell(drop_empty_user) + + # 删除测试数据库 + drop_test_db = cmd_my + ' --defaults-file=' + myconf + ' -uroot -p' + \ + pwd + ' -e "drop database test";' + mw.execShell(drop_test_db) + + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (pwd,)) + + # 删除冗余账户 + hostname = mw.execShell('hostname')[0].strip() + if hostname != 'localhost': + drop_hostname = cmd_my + ' --defaults-file=' + myconf + ' -uroot -p' + pwd + ' -e "drop user \'\'@\'' + hostname + '\'";' + mw.execShell(drop_hostname) + + drop_root_hostname = cmd_my + ' --defaults-file=' + myconf + ' -uroot -p' + pwd + ' -e "drop user \'root\'@\'' + hostname + '\'";' + mw.execShell(drop_root_hostname) + + return True + + +def my8cmd(version, method): + initDreplace(version) + # mysql 8.0 and 5.7 + mdb8 = getMdb8Ver() + try: + isInited = True + if mw.inArray(mdb8, version): + isInited = initMysql8Data() + else: + isInited = initMysql57Data() + + if not isInited: + if not mw.isSupportSystemctl(): + cmd_init_start = init_file + ' start' + subprocess.Popen(cmd_init_start, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + + time.sleep(6) + else: + mw.execShell('systemctl start '+getPluginName()) + + for x in range(10): + mydb_status = process_status() + if mydb_status == 'start': + initMysql8Pwd() + break + time.sleep(1) + + if not mw.isSupportSystemctl(): + cmd_init_stop = init_file + ' stop' + subprocess.Popen(cmd_init_stop, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + time.sleep(3) + else: + mw.execShell('systemctl stop ' + getPluginName()) + + if not mw.isSupportSystemctl(): + sub = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + sub.wait(5) + else: + mw.execShell('systemctl ' + method + ' '+getPluginName()) + return 'ok' + except Exception as e: + return str(e) + + +def appCMD(version, action): + return my8cmd(version, action) + + +def start(version=''): + return appCMD(version, 'start') + + +def stop(version=''): + return appCMD(version, 'stop') + + +def restart(version=''): + return appCMD(version, 'restart') + + +def reload(version=''): + return appCMD(version, 'reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def getMyDbPos(): + file = getConf() + content = mw.readFile(file) + rep = r'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def setMyDbPos(version=''): + args = getArgs() + data = checkArgs(args, ['datadir']) + if not data[0]: + return data[1] + + s_datadir = getMyDbPos() + t_datadir = args['datadir'] + if t_datadir == s_datadir: + return mw.returnJson(False, '与当前存储目录相同,无法迁移文件!') + + if not os.path.exists(t_datadir): + mw.execShell('mkdir -p ' + t_datadir) + + # mw.execShell('/etc/init.d/mysqld stop') + stop(version) + mw.execShell('cp -rf ' + s_datadir + '/* ' + t_datadir + '/') + mw.execShell('chown -R mysql mysql ' + t_datadir) + mw.execShell('chmod -R 755 ' + t_datadir) + mw.execShell('rm -f ' + t_datadir + '/*.pid') + mw.execShell('rm -f ' + t_datadir + '/*.err') + + path = getServerDir() + myfile = path + '/etc/my.cnf' + mycnf = mw.readFile(myfile) + mw.writeFile(path + '/etc/my_backup.cnf', mycnf) + + mycnf = mycnf.replace(s_datadir, t_datadir) + mw.writeFile(myfile, mycnf) + restart(version) + + result = mw.execShell('ps aux|grep mysqld| grep -v grep|grep -v python') + if len(result[0]) > 10: + mw.writeFile('data/datadir.pl', t_datadir) + return mw.returnJson(True, '存储目录迁移成功!') + else: + mw.execShell('pkill -9 mysqld') + mw.writeFile(myfile, mw.readFile(path + '/etc/my_backup.cnf')) + start() + return mw.returnJson(False, '文件迁移失败!') + + +def getMyPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def setMyPort(): + args = getArgs() + data = checkArgs(args, ['port']) + if not data[0]: + return data[1] + + port = args['port'] + file = getConf() + content = mw.readFile(file) + rep = r"port\s*=\s*([0-9]+)\s*\n" + content = re.sub(rep, 'port = ' + port + '\n', content) + mw.writeFile(file, content) + restart() + return mw.returnJson(True, '编辑成功!') + + +def runInfo(version): + + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + db = pMysqlDb() + data = db.query('show global status') + isError = isSqlError(data) + if isError != None: + return isError + + gets = ['Max_used_connections', 'Com_commit', 'Com_select', 'Com_rollback', 'Questions', 'Innodb_buffer_pool_reads', 'Innodb_buffer_pool_read_requests', 'Key_reads', 'Key_read_requests', 'Key_writes', + 'Key_write_requests', 'Qcache_hits', 'Qcache_inserts', 'Bytes_received', 'Bytes_sent', 'Aborted_clients', 'Aborted_connects', + 'Created_tmp_disk_tables', 'Created_tmp_tables', 'Innodb_buffer_pool_pages_dirty', 'Opened_files', 'Open_tables', 'Opened_tables', 'Select_full_join', + 'Select_range_check', 'Sort_merge_passes', 'Table_locks_waited', 'Threads_cached', 'Threads_connected', 'Threads_created', 'Threads_running', 'Connections', 'Uptime'] + + result = {} + # print(data) + for d in data: + vname = d["Variable_name"] + for g in gets: + if vname == g: + result[g] = d["Value"] + + # print(result, int(result['Uptime'])) + result['Run'] = int(time.time()) - int(result['Uptime']) + tmp = db.query('show master status') + try: + result['File'] = tmp[0]["File"] + result['Position'] = tmp[0]["Position"] + except: + result['File'] = 'OFF' + result['Position'] = 'OFF' + return mw.getJson(result) + + +def myDbStatus(version): + result = {} + db = pMysqlDb() + data = db.query('show variables') + isError = isSqlError(data) + if isError != None: + return isError + + gets = ['table_open_cache', 'thread_cache_size', 'key_buffer_size', 'tmp_table_size', 'max_heap_table_size', 'innodb_buffer_pool_size', + 'innodb_additional_mem_pool_size', 'innodb_log_buffer_size', 'max_connections', 'sort_buffer_size', 'read_buffer_size', 'read_rnd_buffer_size', 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + + if pk_version.parse(version) < pk_version.parse("8.0"): + gets.append('query_cache_size') + + result['mem'] = {} + for d in data: + vname = d['Variable_name'] + for g in gets: + # print(g) + if vname == g: + result['mem'][g] = d["Value"] + return mw.getJson(result) + + +def setDbStatus(version): + gets = ['key_buffer_size', 'tmp_table_size', 'max_heap_table_size', 'innodb_buffer_pool_size', 'innodb_log_buffer_size', 'max_connections', + 'table_open_cache', 'thread_cache_size', 'sort_buffer_size', 'read_buffer_size', 'read_rnd_buffer_size', 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + + if pk_version.parse(version) < pk_version.parse("8.0"): + gets = ['key_buffer_size', 'query_cache_size', 'tmp_table_size', 'max_heap_table_size', 'innodb_buffer_pool_size', 'innodb_log_buffer_size', 'max_connections', + 'table_open_cache', 'thread_cache_size', 'sort_buffer_size', 'read_buffer_size', 'read_rnd_buffer_size', 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + + # print(gets) + emptys = ['max_connections', 'thread_cache_size', 'table_open_cache'] + args = getArgs() + conFile = getConf() + content = mw.readFile(conFile) + n = 0 + for g in gets: + s = 'M' + if n > 5: + s = 'K' + if g in emptys: + s = '' + rep = r'\s*' + g + r'\s*=\s*\d+(M|K|k|m|G)?\n' + c = g + ' = ' + args[g] + s + '\n' + if content.find(g) != -1: + content = re.sub(rep, '\n' + c, content, 1) + else: + content = content.replace('[mysqld]\n', '[mysqld]\n' + c) + n += 1 + mw.writeFile(conFile, content) + return mw.returnJson(True, '设置成功!') + + +def isSqlError(mysqlMsg): + # 检测数据库执行错误 + mysqlMsg = str(mysqlMsg) + if "MySQLdb" in mysqlMsg: + return mw.returnJson(False, 'err:' + str(mysqlMsg) + "\n" + 'MySQLdb组件缺失!
                            进入SSH命令行输入: pip install mysql-python | pip install mysqlclient==2.0.3') + if "2002," in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "2003," in mysqlMsg: + return mw.returnJson(False, "Can't connect to MySQL server on '127.0.0.1' (61)") + if "using password:" in mysqlMsg: + return mw.returnJson(False, '数据库密码错误,在管理列表-点击【修复】!') + if "1045," in mysqlMsg: + return mw.returnJson(False, '连接错误!') + if "SQL syntax" in mysqlMsg: + return mw.returnJson(False, 'SQL语法错误!') + if "Connection refused" in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "1133," in mysqlMsg: + return mw.returnJson(False, '数据库用户不存在!') + if "1007," in mysqlMsg: + return mw.returnJson(False, '数据库已经存在!') + return None + + +def __createUser(dbname, username, password, address): + pdb = pMysqlDb() + + if username == 'root': + dbname = '*' + + pdb.execute( + "CREATE USER `%s`@`localhost` IDENTIFIED BY '%s'" % (username, password)) + pdb.execute( + "grant all privileges on %s.* to `%s`@`localhost`" % (dbname, username)) + for a in address.split(','): + pdb.execute( + "CREATE USER `%s`@`%s` IDENTIFIED BY '%s'" % (username, a, password)) + pdb.execute( + "grant all privileges on %s.* to `%s`@`%s`" % (dbname, username, a)) + pdb.execute("flush privileges") + + +def getDbBackupListFunc(dbname=''): + + bkDir = getBackupDir() + if not os.path.exists(bkDir): + os.mkdir(bkDir) + + blist = os.listdir(bkDir) + r = [] + + bname = 'db_' + dbname + blen = len(bname) + for x in blist: + fbstr = x[0:blen] + if fbstr == bname: + r.append(x) + return r + + +def setDbBackup(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + scDir = getPluginDir() + '/scripts/backup.py' + cmd = 'python3 ' + scDir + ' database ' + args['name'] + ' 3' + os.system(cmd) + return mw.returnJson(True, 'ok') + + +def rootPwd(): + return pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + + +def importDbExternal(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + import_dir = mw.getBackupDir() + '/import/' + + file_path = import_dir + file + if not os.path.exists(file_path): + return mw.returnJson(False, '文件突然消失?') + + exts = ['sql', 'gz', 'zip'] + ext = mw.getFileSuffix(file) + if ext not in exts: + return mw.returnJson(False, '导入数据库格式不对!') + + tmp = file.split('/') + tmpFile = tmp[len(tmp) - 1] + tmpFile = tmpFile.replace('.sql.' + ext, '.sql') + tmpFile = tmpFile.replace('.' + ext, '.sql') + tmpFile = tmpFile.replace('tar.', '') + + # print(tmpFile) + import_sql = "" + if file.find("sql.gz") > -1: + cmd = 'cd ' + import_dir + ' && gzip -dc ' + \ + file + " > " + import_dir + tmpFile + info = mw.execShell(cmd) + if info[1] == "": + import_sql = import_dir + tmpFile + + if file.find(".zip") > -1: + cmd = 'cd ' + import_dir + ' && unzip -o ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find("tar.gz") > -1: + cmd = 'cd ' + import_dir + ' && tar -zxvf ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find(".sql") > -1 and file.find(".sql.gz") == -1: + import_sql = import_dir + file + + if import_sql == "": + return mw.returnJson(False, '未找SQL文件') + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + os.environ["MYSQL_PWD"] = pwd + mysql_cmd = getServerDir() + '/bin/mysql -S ' + sock + ' -uroot -p' + \ + pwd + ' ' + name + ' < ' + import_sql + + # print(mysql_cmd) + rdata = mw.execShell(mysql_cmd) + if ext != 'sql': + os.remove(import_sql) + + if rdata[1].lower().find('error') > -1: + return mw.returnJson(False, rdata[1]) + + return mw.returnJson(True, 'ok') + +def importDbExternalProgress(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && ' + cmd += 'python3 '+mw.getServerDir()+'/mdserver-web/plugins/mysql-community/index.py import_db_external_progress_bar {"file":"'+file+'","name":"'+name+'"}' + return mw.returnJson(True, 'ok',cmd) + +def importDbExternalProgressBar(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + import_dir = mw.getFatherDir() + '/backup/import/' + + file_path = import_dir + file + if not os.path.exists(file_path): + return mw.returnJson(False, '文件突然消失?') + + exts = ['sql', 'gz', 'zip'] + ext = mw.getFileSuffix(file) + if ext not in exts: + return mw.returnJson(False, '导入数据库格式不对!') + + tmp = file.split('/') + tmpFile = tmp[len(tmp) - 1] + tmpFile = tmpFile.replace('.sql.' + ext, '.sql') + tmpFile = tmpFile.replace('.' + ext, '.sql') + tmpFile = tmpFile.replace('tar.', '') + + # print(tmpFile) + import_sql = "" + if file.find("sql.gz") > -1: + cmd = 'cd ' + import_dir + ' && gzip -dc ' + \ + file + " > " + import_dir + tmpFile + info = mw.execShell(cmd) + if info[1] == "": + import_sql = import_dir + tmpFile + + if file.find(".zip") > -1: + cmd = 'cd ' + import_dir + ' && unzip -o ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find("tar.gz") > -1: + cmd = 'cd ' + import_dir + ' && tar -zxvf ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find(".sql") > -1 and file.find(".sql.gz") == -1: + import_sql = import_dir + file + + if import_sql == "": + return mw.returnJson(False, '未找SQL文件') + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + # option = '' + # mode = recognizeDbMode() + # if mode == 'gtid': + # option = ' --set-gtid-purged=off ' + + my_cnf = getConf() + mysql_cmd = getServerDir() + '/bin/mysql --defaults-file=' + my_cnf + ' -uroot -p"' + pwd + '" -f ' + name + mysql_cmd_progress_bar = "pv -t -p " + import_sql + '|'+ mysql_cmd + print(mysql_cmd_progress_bar) + rdata = os.system(mysql_cmd_progress_bar) + return "" + +def importDbBackup(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + file_path = mw.getBackupDir() + '/database/' + file + file_path_sql = mw.getBackupDir() + '/database/' + file.replace('.gz', '') + + if not os.path.exists(file_path_sql): + cmd = 'cd ' + mw.getBackupDir() + '/database && gzip -d ' + file + mw.execShell(cmd) + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + mysql_cmd = getServerDir() + '/bin/mysql -S ' + sock + ' -uroot -p' + pwd + \ + ' ' + name + ' < ' + file_path_sql + + # print(mysql_cmd) + # os.system(mysql_cmd) + + rdata = mw.execShell(mysql_cmd) + if rdata[1].lower().find('error') > -1: + return mw.returnJson(False, rdata[1]) + + return mw.returnJson(True, 'ok') + + +def deleteDbBackup(): + args = getArgs() + data = checkArgs(args, ['filename', 'path']) + if not data[0]: + return data[1] + + path = args['path'] + full_file = "" + bkDir = getBackupDir() + full_file = bkDir + '/' + args['filename'] + if path != "": + full_file = path + "/" + args['filename'] + os.remove(full_file) + return mw.returnJson(True, 'ok') + + +def getDbBackupList(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + r = getDbBackupListFunc(args['name']) + bkDir = getBackupDir() + rr = [] + for x in range(0, len(r)): + p = bkDir + '/' + r[x] + data = {} + data['name'] = r[x] + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + return mw.returnJson(True, 'ok', rr) + + +def getDbBackupImportList(): + bkImportDir = mw.getBackupDir() + '/import' + if not os.path.exists(bkImportDir): + os.mkdir(bkImportDir) + + blist = os.listdir(bkImportDir) + + rr = [] + for x in range(0, len(blist)): + name = blist[x] + p = bkImportDir + '/' + name + data = {} + data['name'] = name + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + rdata = { + "list": rr, + "upload_dir": bkImportDir, + } + return mw.returnJson(True, 'ok', rdata) + + +def getDbList(): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('databases') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,pid,name,username,password,accept,rw,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + + for x in range(0, len(clist)): + dbname = clist[x]['name'] + blist = getDbBackupListFunc(dbname) + # print(blist) + clist[x]['is_backup'] = False + if len(blist) > 0: + clist[x]['is_backup'] = True + + count = conn.where(condition, ()).count() + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + info = {} + info['root_pwd'] = pSqliteDb('config').where( + 'id=?', (1,)).getField('mysql_root') + data['info'] = info + + return mw.getJson(data) + + +def syncGetDatabases(): + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + data = pdb.query('show databases') + isError = isSqlError(data) + if isError != None: + return isError + users = pdb.query( + "select User,Host from mysql.user where User!='root' AND Host!='localhost' AND Host!=''") + nameArr = ['information_schema', 'performance_schema', 'mysql', 'sys'] + n = 0 + + # print(users) + for value in data: + vdb_name = value["Database"] + b = False + for key in nameArr: + if vdb_name == key: + b = True + break + if b: + continue + if psdb.where("name=?", (vdb_name,)).count() > 0: + continue + host = '127.0.0.1' + for user in users: + if vdb_name == user["User"]: + host = user["Host"] + break + + ps = mw.getMsg('INPUT_PS') + if vdb_name == 'test': + ps = mw.getMsg('DATABASE_TEST') + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + if psdb.add('name,username,password,accept,ps,addtime', (vdb_name, vdb_name, '', host, ps, addTime)): + n += 1 + + msg = mw.getInfo('本次共从服务器获取了{1}个数据库!', (str(n),)) + return mw.returnJson(True, msg) + + +def toDbBase(find): + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + if len(find['password']) < 3: + find['username'] = find['name'] + find['password'] = mw.md5(str(time.time()) + find['name'])[0:10] + psdb.where("id=?", (find['id'],)).save( + 'password,username', (find['password'], find['username'])) + + result = pdb.execute("create database `" + find['name'] + "`") + if "using password:" in str(result): + return -1 + if "Connection refused" in str(result): + return -1 + + password = find['password'] + __createUser(find['name'], find['username'], password, find['accept']) + return 1 + + +def syncToDatabases(): + args = getArgs() + data = checkArgs(args, ['type', 'ids']) + if not data[0]: + return data[1] + + pdb = pMysqlDb() + result = pdb.execute("show databases") + isError = isSqlError(result) + if isError: + return isError + + stype = int(args['type']) + psdb = pSqliteDb('databases') + n = 0 + + if stype == 0: + data = psdb.field('id,name,username,password,accept').select() + for value in data: + result = toDbBase(value) + if result == 1: + n += 1 + else: + data = json.loads(args['ids']) + for value in data: + find = psdb.where("id=?", (value,)).field( + 'id,name,username,password,accept').find() + # print find + result = toDbBase(find) + if result == 1: + n += 1 + msg = mw.getInfo('本次共同步了{1}个数据库!', (str(n),)) + return mw.returnJson(True, msg) + + +def setRootPwd(version=''): + args = getArgs() + data = checkArgs(args, ['password']) + if not data[0]: + return data[1] + + #强制修改 + force = 0 + if 'force' in args and args['force'] == '1': + force = 1 + + password = args['password'] + try: + pdb = pMysqlDb() + result = pdb.query("show databases") + isError = isSqlError(result) + if isError != None: + if force == 1: + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (password,)) + return mw.returnJson(True, '【强制修改】数据库root密码修改成功(不意为成功连接数据)!') + return isError + + if version.find('5.7') > -1 or version.find('8.0') > -1: + pdb.execute( + "UPDATE mysql.user SET authentication_string='' WHERE user='root'") + pdb.execute( + "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s'" % password) + pdb.execute( + "ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY '%s'" % password) + else: + result = pdb.execute( + "update mysql.user set Password=password('" + password + "') where User='root'") + pdb.execute("flush privileges") + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (password,)) + + msg = '' + if force == 1: + msg = ',无须强制!' + return mw.returnJson(True, '数据库root密码修改成功!'+msg) + except Exception as ex: + return mw.returnJson(False, '修改错误:' + str(ex)) + +def setUserPwd(version=''): + args = getArgs() + data = checkArgs(args, ['password', 'name']) + if not data[0]: + return data[1] + + newpassword = args['password'] + username = args['name'] + uid = args['id'] + try: + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + name = psdb.where('id=?', (uid,)).getField('name') + + if version.find('5.7') > -1 or version.find('8.0') > -1: + accept = pdb.query( + "select Host from mysql.user where User='" + name + "' AND Host!='localhost'") + t1 = pdb.execute( + "update mysql.user set authentication_string='' where User='" + username + "'") + # print(t1) + result = pdb.execute( + "ALTER USER `%s`@`localhost` IDENTIFIED BY '%s'" % (username, newpassword)) + # print(result) + for my_host in accept: + t2 = pdb.execute("ALTER USER `%s`@`%s` IDENTIFIED BY '%s'" % ( + username, my_host["Host"], newpassword)) + # print(t2) + else: + result = pdb.execute("update mysql.user set Password=password('" + + newpassword + "') where User='" + username + "'") + + pdb.execute("flush privileges") + psdb.where("id=?", (uid,)).setField('password', newpassword) + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]密码成功!', (name,))) + except Exception as ex: + return mw.returnJson(False, mw.getInfo('修改数据库[{1}]密码失败[{2}]!', (name, str(ex),))) + + +def setDbPs(): + args = getArgs() + data = checkArgs(args, ['id', 'name', 'ps']) + if not data[0]: + return data[1] + + ps = args['ps'] + sid = args['id'] + name = args['name'] + try: + psdb = pSqliteDb('databases') + psdb.where("id=?", (sid,)).setField('ps', ps) + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]备注成功!', (name,))) + except Exception as e: + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]备注失败!', (name,))) + + +def addDb(): + args = getArgs() + data = checkArgs(args, + ['password', 'name', 'codeing', 'db_user', 'dataAccess', 'ps']) + if not data[0]: + return data[1] + + if not 'address' in args: + address = '' + else: + address = args['address'].strip() + + dbname = args['name'].strip() + dbuser = args['db_user'].strip() + codeing = args['codeing'].strip() + password = args['password'].strip() + dataAccess = args['dataAccess'].strip() + ps = args['ps'].strip() + + reg = r"^[\w-]+$" + if not re.match(reg, args['name']): + return mw.returnJson(False, '数据库名称不能带有特殊符号!') + checks = ['root', 'mysql', 'test', 'sys', 'performance_schema','information_schema'] + if dbuser in checks or len(dbuser) < 1: + return mw.returnJson(False, '数据库用户名不合法!') + if dbname in checks or len(dbname) < 1: + return mw.returnJson(False, '数据库名称不合法!') + + if len(password) < 1: + password = mw.md5(time.time())[0:8] + + wheres = { + 'utf8': 'utf8_general_ci', + 'utf8mb4': 'utf8mb4_general_ci', + 'gbk': 'gbk_chinese_ci', + 'big5': 'big5_chinese_ci' + } + codeStr = wheres[codeing] + + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + + if psdb.where("name=? or username=?", (dbname, dbuser)).count(): + return mw.returnJson(False, '数据库已存在!') + + result = pdb.execute("create database `" + dbname + + "` DEFAULT CHARACTER SET " + codeing + " COLLATE " + codeStr) + # print result + isError = isSqlError(result) + if isError != None: + return isError + + pdb.execute("drop user '" + dbuser + "'@'localhost'") + for a in address.split(','): + pdb.execute("drop user '" + dbuser + "'@'" + a + "'") + + __createUser(dbname, dbuser, password, address) + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + psdb.add('pid,name,username,password,accept,ps,addtime', + (0, dbname, dbuser, password, address, ps, addTime)) + return mw.returnJson(True, '添加成功!') + + +def delDb(): + args = getArgs() + data = checkArgs(args, ['id', 'name']) + if not data[0]: + return data[1] + try: + sid = args['id'] + name = args['name'] + psdb = pSqliteDb('databases') + pdb = pMysqlDb() + find = psdb.where("id=?", (sid,)).field( + 'id,pid,name,username,password,accept,ps,addtime').find() + accept = find['accept'] + username = find['username'] + + # 删除MYSQL + result = pdb.execute("drop database `" + name + "`") + + users = pdb.query("select Host from mysql.user where User='" + + username + "' AND Host!='localhost'") + pdb.execute("drop user '" + username + "'@'localhost'") + for us in users: + pdb.execute("drop user '" + username + "'@'" + us["Host"] + "'") + pdb.execute("flush privileges") + + # 删除SQLITE + psdb.where("id=?", (sid,)).delete() + return mw.returnJson(True, '删除成功!') + except Exception as ex: + return mw.returnJson(False, '删除失败!' + str(ex)) + + +def getDbAccess(): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + username = args['username'] + pdb = pMysqlDb() + + users = pdb.query("select Host from mysql.user where User='" + + username + "' AND Host!='localhost'") + + isError = isSqlError(users) + if isError != None: + return isError + + if len(users) < 1: + return mw.returnJson(True, "127.0.0.1") + accs = [] + for c in users: + accs.append(c["Host"]) + userStr = ','.join(accs) + return mw.returnJson(True, userStr) + + +def setDbAccess(): + args = getArgs() + data = checkArgs(args, ['username', 'access']) + if not data[0]: + return data[1] + name = args['username'] + access = args['access'] + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + + dbname = psdb.where('username=?', (name,)).getField('name') + + if name == 'root': + password = pSqliteDb('config').where( + 'id=?', (1,)).getField('mysql_root') + else: + password = psdb.where("username=?", (name,)).getField('password') + + users = pdb.query("select Host from mysql.user where User='" + + name + "' AND Host!='localhost'") + + for us in users: + pdb.execute("drop user '" + name + "'@'" + us["Host"] + "'") + + __createUser(dbname, name, password, access) + + psdb.where('username=?', (name,)).save('accept,rw', (access, 'rw',)) + return mw.returnJson(True, '设置成功!') + + + +def openSkipGrantTables(): + mycnf = getConf() + content = mw.readFile(mycnf) + content = content.replace('#skip-grant-tables','skip-grant-tables') + mw.writeFile(mycnf, content) + return True + +def closeSkipGrantTables(): + mycnf = getConf() + content = mw.readFile(mycnf) + content = content.replace('skip-grant-tables','#skip-grant-tables') + mw.writeFile(mycnf, content) + return True + + +def resetDbRootPwd(version): + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + pwd = mw.getRandomString(16) + + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (pwd,)) + mdb8 = getMdb8Ver() + if not mw.inArray(mdb8, version): + cmd_pass = serverdir + '/bin/mysql --defaults-file=' + myconf + ' -uroot -e' + cmd_pass = cmd_pass + '"UPDATE mysql.user SET password=PASSWORD(\'' + pwd + "') WHERE user='root';" + cmd_pass = cmd_pass + 'flush privileges;"' + data = mw.execShell(cmd_pass) + # print(data) + else: + auth_policy = getAuthPolicy() + + reset_pwd = 'flush privileges;' + reset_pwd = reset_pwd + \ + "UPDATE mysql.user SET authentication_string='' WHERE user='root';" + reset_pwd = reset_pwd + "flush privileges;" + reset_pwd = reset_pwd + \ + "alter user 'root'@'localhost' IDENTIFIED by '" + pwd + "';" + reset_pwd = reset_pwd + \ + "alter user 'root'@'localhost' IDENTIFIED WITH "+auth_policy+" by '" + pwd + "';" + reset_pwd = reset_pwd + "flush privileges;" + + tmp_file = "/tmp/mysql_init_tmp.log" + mw.writeFile(tmp_file, reset_pwd) + cmd_pass = serverdir + '/bin/mysql --defaults-file=' + myconf + ' -uroot -proot < ' + tmp_file + + data = mw.execShell(cmd_pass) + # print(data) + os.remove(tmp_file) + return True + +def fixDbAccess2(version): + try: + pdb = pMysqlDb() + data = pdb.query('show databases') + isError = isSqlError(data) + if isError != None: + appCMD(version, 'stop') + mw.execShell("rm -rf " + getServerDir() + "/data") + appCMD(version, 'start') + return mw.returnJson(True, '修复成功!') + return mw.returnJson(True, '正常无需修复!') + except Exception as e: + return mw.returnJson(False, '修复失败请重试!') + +def fixDbAccess(version): + + pdb = pMysqlDb() + mdb_ddir = getDataDir() + if not os.path.exists(mdb_ddir): + return mw.returnJson(False, '数据目录不存在,尝试重启重建!') + + try: + data = pdb.query('show databases') + isError = isSqlError(data) + if isError != None: + + # 重置密码 + appCMD(version, 'stop') + openSkipGrantTables() + appCMD(version, 'start') + time.sleep(3) + resetDbRootPwd(version) + + appCMD(version, 'stop') + closeSkipGrantTables() + appCMD(version, 'start') + + return mw.returnJson(True, '修复成功!') + return mw.returnJson(True, '正常无需修复!') + except Exception as e: + return mw.returnJson(False, '修复失败请重试!') + + + +def setDbRw(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'id', 'rw']) + if not data[0]: + return data[1] + + username = args['username'] + uid = args['id'] + rw = args['rw'] + + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + dbname = psdb.where("id=?", (uid,)).getField('name') + users = pdb.query( + "select Host from mysql.user where User='" + username + "'") + + # show grants for demo@"127.0.0.1"; + for x in users: + # REVOKE ALL PRIVILEGES ON `imail`.* FROM 'imail'@'127.0.0.1'; + + sql = "REVOKE ALL PRIVILEGES ON `" + dbname + \ + "`.* FROM '" + username + "'@'" + x["Host"] + "';" + r = pdb.query(sql) + # print(sql, r) + + if rw == 'rw': + sql = "GRANT SELECT, INSERT, UPDATE, DELETE ON " + dbname + ".* TO " + \ + username + "@'" + x["Host"] + "'" + elif rw == 'r': + sql = "GRANT SELECT ON " + dbname + ".* TO " + \ + username + "@'" + x["Host"] + "'" + else: + sql = "GRANT all privileges ON " + dbname + ".* TO " + \ + username + "@'" + x["Host"] + "'" + pdb.execute(sql) + pdb.execute("flush privileges") + r = psdb.where("id=?", (uid,)).setField('rw', rw) + # print(r) + return mw.returnJson(True, '切换成功!') + + +def getDbInfo(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + db_name = args['name'] + pdb = pMysqlDb() + # print 'show tables from `%s`' % db_name + tables = pdb.query('show tables from `%s`' % db_name) + + ret = {} + sql = "select sum(DATA_LENGTH)+sum(INDEX_LENGTH) as sum_size from information_schema.tables where table_schema='%s'" % db_name + data_sum = pdb.query(sql) + + data = 0 + if data_sum[0]['sum_size'] != None: + data = data_sum[0]['sum_size'] + + ret['data_size'] = mw.toSize(data) + ret['database'] = db_name + + ret3 = [] + table_key = "Tables_in_" + db_name + for i in tables: + tb_sql = "show table status from `%s` where name = '%s'" % (db_name, i[ + table_key]) + table = pdb.query(tb_sql) + + tmp = {} + tmp['type'] = table[0]["Engine"] + tmp['rows_count'] = table[0]["Rows"] + tmp['collation'] = table[0]["Collation"] + + data_size = 0 + if table[0]['Avg_row_length'] != None: + data_size = table[0]['Avg_row_length'] + + if table[0]['Data_length'] != None: + data_size = table[0]['Data_length'] + + tmp['data_byte'] = data_size + tmp['data_size'] = mw.toSize(data_size) + tmp['table_name'] = table[0]["Name"] + ret3.append(tmp) + + ret['tables'] = (ret3) + + return mw.getJson(ret) + + +def repairTable(): + args = getArgs() + data = checkArgs(args, ['db_name', 'tables']) + if not data[0]: + return data[1] + + db_name = args['db_name'] + tables = json.loads(args['tables']) + pdb = pMysqlDb() + mtable = pdb.query('show tables from `%s`' % db_name) + + ret = [] + key = "Tables_in_" + db_name + for i in mtable: + for tn in tables: + if tn == i[key]: + ret.append(tn) + + if len(ret) > 0: + for i in ret: + pdb.execute('REPAIR TABLE `%s`.`%s`' % (db_name, i)) + return mw.returnJson(True, "修复完成!") + return mw.returnJson(False, "修复失败!") + + +def optTable(): + args = getArgs() + data = checkArgs(args, ['db_name', 'tables']) + if not data[0]: + return data[1] + + db_name = args['db_name'] + tables = json.loads(args['tables']) + pdb = pMysqlDb() + mtable = pdb.query('show tables from `%s`' % db_name) + ret = [] + key = "Tables_in_" + db_name + for i in mtable: + for tn in tables: + if tn == i[key]: + ret.append(tn) + + if len(ret) > 0: + for i in ret: + pdb.execute('OPTIMIZE TABLE `%s`.`%s`' % (db_name, i)) + return mw.returnJson(True, "优化成功!") + return mw.returnJson(False, "优化失败或者已经优化过了!") + + +def alterTable(): + args = getArgs() + data = checkArgs(args, ['db_name', 'tables']) + if not data[0]: + return data[1] + + db_name = args['db_name'] + tables = json.loads(args['tables']) + table_type = args['table_type'] + pdb = pMysqlDb() + mtable = pdb.query('show tables from `%s`' % db_name) + + ret = [] + key = "Tables_in_" + db_name + for i in mtable: + for tn in tables: + if tn == i[key]: + ret.append(tn) + + if len(ret) > 0: + for i in ret: + pdb.execute('alter table `%s`.`%s` ENGINE=`%s`' % + (db_name, i, table_type)) + return mw.returnJson(True, "更改成功!") + return mw.returnJson(False, "更改失败!") + + +def getTotalStatistics(): + st = status() + data = {} + + isInstall = os.path.exists(getServerDir() + '/version.pl') + + if st == 'start' and isInstall: + data['status'] = True + data['count'] = pSqliteDb('databases').count() + data['ver'] = mw.readFile(getServerDir() + '/version.pl').strip() + return mw.returnJson(True, 'ok', data) + else: + data['status'] = False + data['count'] = 0 + return mw.returnJson(False, 'fail', data) + + +def recognizeDbMode(): + conf = getConf() + con = mw.readFile(conf) + rep = r"!include %s/(.*)?\.cnf" % (getServerDir() + "/etc/mode",) + mode = 'none' + try: + data = re.findall(rep, con, re.M) + mode = data[0] + except Exception as e: + pass + return mode + + +def getDbrunMode(version=''): + mode = recognizeDbMode() + return mw.returnJson(True, "ok", {'mode': mode}) + + +def setDbrunMode(version=''): + if version == '5.5': + return mw.returnJson(False, "不支持切换") + + args = getArgs() + data = checkArgs(args, ['mode', 'reload']) + if not data[0]: + return data[1] + + mode = args['mode'] + dbreload = args['reload'] + + if not mode in ['classic', 'gtid']: + return mw.returnJson(False, "mode的值无效:" + mode) + + origin_mode = recognizeDbMode() + path = getConf() + con = mw.readFile(path) + rep = r"!include %s/%s\.cnf" % (getServerDir() + "/etc/mode", origin_mode) + rep_after = "!include %s/%s.cnf" % (getServerDir() + "/etc/mode", mode) + con = re.sub(rep, rep_after, con) + mw.writeFile(path, con) + + if version == '5.6': + dbreload = 'yes' + else: + db = pMysqlDb() + # The value of @@GLOBAL.GTID_MODE can only be changed one step at a + # time: OFF <-> OFF_PERMISSIVE <-> ON_PERMISSIVE <-> ON. Also note that + # this value must be stepped up or down simultaneously on all servers. + # See the Manual for instructions. + if mode == 'classic': + db.query('set global enforce_gtid_consistency=off') + db.query('set global gtid_mode=on') + db.query('set global gtid_mode=on_permissive') + db.query('set global gtid_mode=off_permissive') + db.query('set global gtid_mode=off') + elif mode == 'gtid': + db.query('set global enforce_gtid_consistency=on') + db.query('set global gtid_mode=off') + db.query('set global gtid_mode=off_permissive') + db.query('set global gtid_mode=on_permissive') + db.query('set global gtid_mode=on') + + if dbreload == "yes": + restart(version) + + return mw.returnJson(True, "切换成功!") + + +def findBinlogDoDb(): + conf = getConf() + con = mw.readFile(conf) + rep = r"binlog-do-db\s*?=\s*?(.*)" + dodb = re.findall(rep, con, re.M) + return dodb + + +def findBinlogSlaveDoDb(): + conf = getConf() + con = mw.readFile(conf) + rep = r"replicate-do-db\s*?=\s*?(.*)" + dodb = re.findall(rep, con, re.M) + return dodb + + +def setDbMasterAccess(): + args = getArgs() + data = checkArgs(args, ['username', 'access']) + if not data[0]: + return data[1] + username = args['username'] + access = args['access'] + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + password = psdb.where("username=?", (username,)).getField('password') + users = pdb.query("select Host from mysql.user where User='" + + username + "' AND Host!='localhost'") + for us in users: + pdb.execute("drop user '" + username + "'@'" + us["Host"] + "'") + + dbname = '*' + for a in access.split(','): + pdb.execute( + "CREATE USER `%s`@`%s` IDENTIFIED BY '%s'" % (username, a, password)) + pdb.execute( + "grant all privileges on %s.* to `%s`@`%s`" % (dbname, username, a)) + + pdb.execute("flush privileges") + psdb.where('username=?', (username,)).save('accept', (access,)) + return mw.returnJson(True, '设置成功!') + + +def resetMaster(version=''): + pdb = pMysqlDb() + r = pdb.execute('reset master') + isError = isSqlError(r) + if isError != None: + return isError + return mw.returnJson(True, '重置成功!') + + +def getMasterDbList(version=''): + try: + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('databases') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + dodb = findBinlogDoDb() + data['dodb'] = dodb + + slave_dodb = findBinlogSlaveDoDb() + + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,pid,name,username,password,accept,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + count = conn.where(condition, ()).count() + + for x in range(0, len(clist)): + if clist[x]['name'] in dodb: + clist[x]['master'] = 1 + else: + clist[x]['master'] = 0 + + if clist[x]['name'] in slave_dodb: + clist[x]['slave'] = 1 + else: + clist[x]['slave'] = 0 + + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + return mw.getJson(data) + except Exception as e: + return mw.returnJson(False, "数据库密码错误,在管理列表-点击【修复】!") + + +def setDbMaster(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + conf = getConf() + con = mw.readFile(conf) + rep = r"(binlog-do-db\s*?=\s*?(.*))" + dodb = re.findall(rep, con, re.M) + + isHas = False + for x in range(0, len(dodb)): + + if dodb[x][1] == args['name']: + isHas = True + + con = con.replace(dodb[x][0] + "\n", '') + mw.writeFile(conf, con) + + if not isHas: + prefix = '#binlog-do-db' + con = con.replace( + prefix, prefix + "\nbinlog-do-db=" + args['name']) + mw.writeFile(conf, con) + + restart(version) + time.sleep(4) + return mw.returnJson(True, '设置成功', [args, dodb]) + + +def setDbSlave(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + conf = getConf() + con = mw.readFile(conf) + rep = r"(replicate-do-db\s*?=\s*?(.*))" + dodb = re.findall(rep, con, re.M) + + isHas = False + for x in range(0, len(dodb)): + if dodb[x][1] == args['name']: + isHas = True + + con = con.replace(dodb[x][0] + "\n", '') + mw.writeFile(conf, con) + + if not isHas: + prefix = '#replicate-do-db' + con = con.replace( + prefix, prefix + "\nreplicate-do-db=" + args['name']) + mw.writeFile(conf, con) + + restart(version) + time.sleep(4) + return mw.returnJson(True, '设置成功', [args, dodb]) + + +def getMasterStatus(version=''): + + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动,或正在启动中...!', []) + + query_status_cmd = 'show slave status' + mdb8 = getMdb8Ver() + is_mdb8 = False + if mw.inArray(mdb8, version): + is_mdb8 = True + query_status_cmd = 'show replica status' + + try: + conf = getConf() + content = mw.readFile(conf) + master_status = False + if content.find('#log-bin') == -1 and content.find('log-bin') > 1: + dodb = findBinlogDoDb() + if len(dodb) > 0: + master_status = True + + data = {} + data['mode'] = recognizeDbMode() + data['status'] = master_status + + db = pMysqlDb() + dlist = db.query(query_status_cmd) + + # print(dlist[0]) + if is_mdb8: + if len(dlist) > 0 and (dlist[0]["Replica_IO_Running"] == 'Yes' or dlist[0]["Replica_SQL_Running"] == 'Yes'): + data['slave_status'] = True + else: + if len(dlist) > 0 and (dlist[0]["Slave_IO_Running"] == 'Yes' or dlist[0]["Slave_SQL_Running"] == 'Yes'): + data['slave_status'] = True + + return mw.returnJson(master_status, '设置成功', data) + except Exception as e: + return mw.returnJson(False, "数据库密码错误,在管理列表-点击【修复】!" + str(e) , 'pwd') + + +def setMasterStatus(version=''): + + conf = getConf() + con = mw.readFile(conf) + + if con.find('#log-bin') != -1: + return mw.returnJson(False, '必须开启二进制日志') + + sign = 'mdserver_ms_open' + + dodb = findBinlogDoDb() + if not sign in dodb: + prefix = '#binlog-do-db' + con = con.replace(prefix, prefix + "\nbinlog-do-db=" + sign) + mw.writeFile(conf, con) + else: + con = con.replace("binlog-do-db=" + sign + "\n", '') + rep = r"(binlog-do-db\s*?=\s*?(.*))" + dodb = re.findall(rep, con, re.M) + for x in range(0, len(dodb)): + con = con.replace(dodb[x][0] + "\n", '') + mw.writeFile(conf, con) + + restart(version) + return mw.returnJson(True, '设置成功') + + +def getMasterRepSlaveList(version=''): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('master_replication_user') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,username,password,accept,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + count = conn.where(condition, ()).count() + + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'getMasterRepSlaveList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def addMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'password']) + if not data[0]: + return data[1] + + address = '' + if 'address' in args: + address = args['address'].strip() + + username = args['username'].strip() + password = args['password'].strip() + # ps = args['ps'].strip() + # address = args['address'].strip() + # dataAccess = args['dataAccess'].strip() + + reg = r"^[\w-]+$" + if not re.match(reg, username): + return mw.returnJson(False, '用户名不能带有特殊符号!') + checks = ['root', 'mysql', 'test', 'sys', 'performance_schema','information_schema'] + if username in checks or len(username) < 1: + return mw.returnJson(False, '用户名不合法!') + if password in checks or len(password) < 1: + return mw.returnJson(False, '密码不合法!') + + if len(password) < 1: + password = mw.md5(time.time())[0:8] + + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + + if psdb.where("username=?", (username)).count() > 0: + return mw.returnJson(False, '用户已存在!') + + mdb8 = ['8.0','8.1','8.2','8.3','8.4'] + if mw.inArray(mdb8,version): + sql = "CREATE USER '" + username + \ + "' IDENTIFIED WITH mysql_native_password BY '" + password + "';" + pdb.execute(sql) + sql = "grant replication slave on *.* to '" + username + "'@'%';" + result = pdb.execute(sql) + isError = isSqlError(result) + if isError != None: + return isError + + else: + sql = "GRANT REPLICATION SLAVE ON *.* TO '" + username + \ + "'@'%' identified by '" + password + "';" + result = pdb.execute(sql) + isError = isSqlError(result) + if isError != None: + return isError + + sql_select = "grant select,reload,REPLICATION CLIENT,PROCESS on *.* to " + username + "@'%';" + pdb.execute(sql_select) + pdb.execute('FLUSH PRIVILEGES;') + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + psdb.add('username,password,accept,ps,addtime',(username, password, '%', '', addTime)) + return mw.returnJson(True, '添加成功!') + + +def getMasterRepSlaveUserCmd(version): + args = getArgs() + data = checkArgs(args, ['username', 'db']) + if not data[0]: + return data[1] + + psdb = pSqliteDb('master_replication_user') + f = 'username,password' + username = args['username'] + if username == '': + count = psdb.count() + if count == 0: + return mw.returnJson(False, '请添加同步账户!') + + clist = psdb.field(f).limit('1').order('id desc').select() + else: + clist = psdb.field(f).where("username=?", (username,)).limit( + '1').order('id desc').select() + + ip = mw.getLocalIp() + port = getMyPort() + db = pMysqlDb() + + cmd_status = "show master status" + if pk_version.parse(version) > pk_version.parse("8.0"): + cmd_status = "SHOW BINARY LOG STATUS" + mstatus = db.query(cmd_status) + + if len(mstatus) == 0: + return mw.returnJson(False, '未开启!') + + mode = recognizeDbMode() + + sid = getDbServerId() + channel_name = "" + if sid != '': + channel_name = " for channel 'r{}'".format(sid) + + mdb8 = getMdb8Ver() + sql = '' + if not mw.inArray(mdb8,version): + base_sql = "CHANGE MASTER TO MASTER_HOST='" + ip + "', MASTER_PORT=" + port + ", MASTER_USER='" + \ + clist[0]['username'] + "', MASTER_PASSWORD='" + clist[0]['password'] + "'" + + sql += base_sql; + sql += "

                            "; + # sql += base_sql + ", MASTER_AUTO_POSITION=1" + channel_name + sql += base_sql + channel_name + sql += "

                            "; + + sql += base_sql + ", MASTER_LOG_FILE='" + mstatus[0]["File"] + "',MASTER_LOG_POS=" + str(mstatus[0]["Position"]) + channel_name + else: + base_sql = "CHANGE REPLICATION SOURCE TO SOURCE_HOST='" + ip + "', SOURCE_PORT=" + port + ", SOURCE_USER='" + \ + clist[0]['username'] + "', SOURCE_PASSWORD='" + clist[0]['password']+"'" + sql += base_sql; + sql += "

                            "; + # sql += base_sql + ", MASTER_AUTO_POSITION=1" + channel_name + sql += base_sql + channel_name + sql += "

                            "; + sql += base_sql + ", SOURCE_LOG_FILE='" + mstatus[0]["File"] + "',SOURCE_LOG_POS=" + str(mstatus[0]["Position"]) + channel_name + + + data = {} + data['cmd'] = sql + data["info"] = clist[0] + data['mode'] = mode + + return mw.returnJson(True, 'ok!', data) + + +def delMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + + name = args['username'] + + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + pdb.execute("drop user '" + name + "'@'%'") + pdb.execute("drop user '" + name + "'@'localhost'") + + users = pdb.query("select Host from mysql.user where User='" + + name + "' AND Host!='localhost'") + for us in users: + pdb.execute("drop user '" + name + "'@'" + us["Host"] + "'") + + psdb.where("username=?", (args['username'],)).delete() + + return mw.returnJson(True, '删除成功!') + + +def updateMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'password']) + if not data[0]: + return data[1] + + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + pdb.execute("drop user '" + args['username'] + "'@'%'") + + pdb.execute("GRANT REPLICATION SLAVE ON *.* TO '" + + args['username'] + "'@'%' identified by '" + args['password'] + "'") + + psdb.where("username=?", (args['username'],)).save( + 'password', args['password']) + + return mw.returnJson(True, '更新成功!') + + +def getSlaveSSHList(version=''): + args = getArgs() + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + conn = pSqliteDb('slave_id_rsa') + limit = str((page - 1) * page_size) + ',' + str(page_size) + + field = 'id,ip,port,db_user,id_rsa,ps,addtime' + clist = conn.field(field).limit(limit).order('id desc').select() + count = conn.count() + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = args['tojs'] + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def getSlaveSyncUserByIp(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_sync_user') + data = conn.field('ip,port,user,pass,mode,cmd').where( + "ip=?", (ip,)).select() + return mw.returnJson(True, 'ok', data) + + +def addSlaveSyncUser(version=''): + import base64 + + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + if ip == "": + return mw.returnJson(True, 'ok') + + data = checkArgs(args, ['port', 'user', 'pass', 'mode']) + if not data[0]: + return data[1] + + cmd = args['cmd'] + port = args['port'] + user = args['user'] + apass = args['pass'] + mode = args['mode'] + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + conn = pSqliteDb('slave_sync_user') + data = conn.field('ip').where("ip=?", (ip,)).select() + if len(data) > 0: + res = conn.where("ip=?", (ip,)).save( + 'port,user,pass,mode,cmd', (port, user, apass, mode, cmd)) + else: + conn.add('ip,port,user,cmd,user,pass,mode,addtime', + (ip, port, user, cmd, user, apass, mode, addTime)) + + return mw.returnJson(True, '设置成功!') + + +def delSlaveSyncUser(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_sync_user') + conn.where("ip=?", (ip,)).delete() + return mw.returnJson(True, '删除成功!') + + +def getSlaveSyncUserList(version=''): + args = getArgs() + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + conn = pSqliteDb('slave_sync_user') + limit = str((page - 1) * page_size) + ',' + str(page_size) + + field = 'id,ip,port,user,pass,cmd,addtime' + clist = conn.field(field).limit(limit).order('id desc').select() + count = conn.count() + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = args['tojs'] + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def getSyncModeFile(): + return getServerDir() + "/sync.mode" + + +def getSlaveSyncMode(version): + sync_mode = getSyncModeFile() + if os.path.exists(sync_mode): + mode = mw.readFile(sync_mode).strip() + return mw.returnJson(True, 'ok', mode) + return mw.returnJson(False, 'fail') + + +def setSlaveSyncMode(version): + args = getArgs() + data = checkArgs(args, ['mode']) + if not data[0]: + return data[1] + mode = args['mode'] + sync_mode = getSyncModeFile() + + if mode == 'none': + os.remove(sync_mode) + else: + mw.writeFile(sync_mode, mode) + return mw.returnJson(True, '设置成功', mode) + + +def getSlaveSSHByIp(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_id_rsa') + data = conn.field('ip,port,db_user,id_rsa').where("ip=?", (ip,)).select() + return mw.returnJson(True, 'ok', data) + + +def addSlaveSSH(version=''): + import base64 + + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + if ip == "": + return mw.returnJson(True, 'ok') + + data = checkArgs(args, ['port', 'id_rsa', 'db_user']) + if not data[0]: + return data[1] + + id_rsa = args['id_rsa'] + port = args['port'] + db_user = args['db_user'] + user = 'root' + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + conn = pSqliteDb('slave_id_rsa') + data = conn.field('ip,id_rsa').where("ip=?", (ip,)).select() + if len(data) > 0: + res = conn.where("ip=?", (ip,)).save( + 'port,id_rsa,db_user', (port, id_rsa, db_user)) + else: + conn.add('ip,port,user,id_rsa,db_user,ps,addtime', + (ip, port, user, id_rsa, db_user, '', addTime)) + + return mw.returnJson(True, '设置成功!') + + +def delSlaveSSH(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_id_rsa') + conn.where("ip=?", (ip,)).delete() + return mw.returnJson(True, 'ok') + + +def updateSlaveSSH(version=''): + args = getArgs() + data = checkArgs(args, ['ip', 'id_rsa']) + if not data[0]: + return data[1] + + ip = args['ip'] + id_rsa = args['id_rsa'] + conn = pSqliteDb('slave_id_rsa') + conn.where("ip=?", (ip,)).save('id_rsa', (id_rsa,)) + return mw.returnJson(True, 'ok') + + +def getSlaveList(version=''): + + query_status_cmd = 'show slave status' + mdb8 = getMdb8Ver() + if mw.inArray(mdb8, version): + query_status_cmd = 'show replica status' + + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + db = pMysqlDb() + dlist = db.query(query_status_cmd) + + # print(dlist) + data = {} + data['data'] = dlist + return mw.getJson(data) + +def trySlaveSyncBugfix(version=''): + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + if mode != 'sync-user': + return mw.returnJson(False, '仅支持【同步账户】模式') + + conn = pSqliteDb('slave_sync_user') + slave_sync_data = conn.field('ip,port,user,pass,mode,cmd').select() + if len(slave_sync_data) < 1: + return mw.returnJson(False, '需要先添加【同步用户】配置!') + + # print(slave_sync_data) + # 本地从库 + sdb = pMysqlDb() + + gtid_purged = '' + + for i in range(len(slave_sync_data)): + port = slave_sync_data[i]['port'] + password = slave_sync_data[i]['pass'] + host = slave_sync_data[i]['ip'] + user = slave_sync_data[i]['user'] + + # print(port, password, host) + + mdb = mw.getMyORM() + mdb.setHost(host) + mdb.setPort(port) + mdb.setUser(user) + mdb.setPwd(password) + mdb.setSocket('') + + var_gtid = mdb.query('show VARIABLES like "%gtid_purged%"') + if len(var_gtid) > 0: + gtid_purged += var_gtid[0]['Value'] + ',' + + gtid_purged = gtid_purged.strip(',') + sql = "set @@global.gtid_purged='" + gtid_purged + "'" + + sdb.query('stop slave') + # print(sql) + sdb.query(sql) + sdb.query('start slave') + return mw.returnJson(True, '修复成功!') + +def getSlaveSyncCmd(version=''): + + root = mw.getPanelDir() + cmd = 'cd ' + root + ' && python3 ' + root + \ + '/plugins/mysql/index.py do_full_sync {"db":"all","sign":""}' + return mw.returnJson(True, 'ok', cmd) + + +def initSlaveStatus(version=''): + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + if mode == 'ssh': + return initSlaveStatusSSH(version) + if mode == 'sync-user': + return initSlaveStatusSyncUser(version) + + +def parseSlaveSyncCmd(cmd): + a = {} + if cmd.lower().find('for') > 0: + cmd_tmp = cmd.split('for') + cmd = cmd_tmp[0].strip() + + pattern_c = r"channel \'(.*)\';" + match_val = re.match(pattern_c, cmd_tmp[1].strip(), re.I) + if match_val: + m_groups = match_val.groups() + a['channel'] = m_groups[0] + vlist = cmd.split(',') + for i in vlist: + tmp = i.strip() + tmp_a = tmp.split(" ") + real_tmp = tmp_a[len(tmp_a) - 1] + kv = real_tmp.split("=") + a[kv[0]] = kv[1].replace("'", '').replace("'", '').replace(";", '') + return a + + +def initSlaveStatusSyncUser(version=''): + conn = pSqliteDb('slave_sync_user') + slave_data = conn.field('ip,port,user,pass,mode,cmd').select() + if len(slave_data) < 1: + return mw.returnJson(False, '需要先添加同步用户配置!') + + slave_name = getSlaveName() + # print(data) + pdb = pMysqlDb() + if len(slave_data) == 1: + dlist = pdb.query('show '+slave_name+' status') + if len(dlist) > 0: + return mw.returnJson(False, '已经初始化好了zz...') + + msg = '' + local_mode = recognizeDbMode() + for x in range(len(slave_data)): + slave_t = slave_data[x] + mode_name = 'classic' + base_t = 'IP:' + slave_t['ip'] + ",PORT:" + \ + slave_t['port'] + ",USER:" + slave_t['user'] + + if slave_t['mode'] == '1': + mode_name = 'gtid' + + if local_mode != mode_name: + msg += base_t + '->同步模式不一致' + continue + + cmd_sql = slave_t['cmd'] + if cmd_sql == '': + msg += base_t + '->同步命令不能为空' + continue + + try: + pinfo = parseSlaveSyncCmd(cmd_sql) + except Exception as e: + return mw.returnJson(False, base_t + '->CMD同步命令不合规范!') + t = pdb.query(cmd_sql) + isError = isSqlError(t) + if isError: + return isError + + # pdb.query("start slave user='{}' password='{}';".format( + # u['user'], u['pass'])) + + pdb.query("start "+slave_name) + pdb.query("start all "+slave_name) + + if msg == '': + msg = '初始化成功!' + return mw.returnJson(True, msg) + + +def initSlaveStatusSSH(version=''): + slave_name = getSlaveName() + db = pMysqlDb() + dlist = db.query('show '+slave_name+' status') + + conn = pSqliteDb('slave_id_rsa') + ssh_list = conn.field('ip,port,id_rsa,db_user').select() + + if len(ssh_list) < 1: + return mw.returnJson(False, '需要先配置【[主]SSH配置】!') + + local_mode = recognizeDbMode() + + import paramiko + paramiko.util.log_to_file('paramiko.log') + ssh = paramiko.SSHClient() + + db.query('stop '+slave_name) + db.query('reset '+slave_name+' all') + for data in ssh_list: + ip = data['ip'] + SSH_PRIVATE_KEY = "/tmp/t_ssh_" + ip + ".txt" + master_port = data['port'] + mw.writeFile(SSH_PRIVATE_KEY, data['id_rsa'].replace('\\n', '\n')) + mw.execShell("chmod 600 " + SSH_PRIVATE_KEY) + try: + key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(hostname=ip, port=int(master_port), + username='root', pkey=key) + + db_user = data['db_user'] + cmd = 'cd /www/server/mdserver-web && source bin/activate && python3 ' + \ + getSPluginDir() + \ + '/index.py get_master_rep_slave_user_cmd {"username":"' + \ + db_user + '","db":""}' + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + if result.strip() == "": + return mw.returnJson(False, '[主][' + ip + ']:获取同步命令失败!') + + cmd_data = json.loads(result) + if not cmd_data['status']: + return mw.returnJson(False, '[主][' + ip + ']:' + cmd_data['msg']) + + if local_mode != cmd_data['data']['mode']: + return mw.returnJson(False, '[主][' + ip + ']:【{}】从【{}】,运行模式不一致!'.format(cmd_data['data']['mode'], local_mode)) + + u = cmd_data['data']['info'] + + ps = u['username'] + "|" + u['password'] + print(ps) + conn.where('ip=?', (ip,)).setField('ps', ps) + db.query('stop slave') + + # 保证同步IP一致 + cmd = cmd_data['data']['cmd'] + if cmd.find('SOURCE_HOST') > -1: + cmd = re.sub(r"SOURCE_HOST='(.*?)'", + "SOURCE_HOST='" + ip + "'", cmd, 1) + + if cmd.find('MASTER_HOST') > -1: + cmd = re.sub(r"MASTER_HOST='(.*?)'", + "MASTER_HOST='" + ip + "'", cmd, 1) + db.query(cmd) + ssh.close() + if os.path.exists(SSH_PRIVATE_KEY): + os.system("rm -rf " + SSH_PRIVATE_KEY) + except Exception as e: + return mw.returnJson(False, '[主][' + ip + ']:SSH认证配置连接失败!' + str(e)) + db.query('start '+slave_name) + return mw.returnJson(True, '初始化成功!') + + +def setSlaveStatus(version=''): + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + pdb = pMysqlDb() + slave_name = getSlaveName() + cmd = 'show '+slave_name+' status' + + dlist = pdb.query(cmd) + if len(dlist) == 0: + return mw.returnJson(False, '需要手动添加同步账户或者执行初始化!') + + for v in dlist: + connection_name = '' + cmd = "slave" + if 'Channel_Name' in v: + ch_name = v['Channel_Name'] + cmd = slave_name + " for channel '{}'".format(ch_name) + + if (( 'Slave_IO_Running' in v and v["Slave_IO_Running"] == 'Yes') or ('Slave_SQL_Running' in v and v["Slave_SQL_Running"] == 'Yes')): + pdb.query("stop {}".format(cmd)) + elif (( 'Replica_IO_Running' in v and v["Replica_IO_Running"] == 'Yes') or ( 'Replica_SQL_Running' in v and v["Replica_SQL_Running"] == 'Yes') ): + pdb.query("stop {}".format(cmd)) + else: + pdb.query("start {}".format(cmd)) + + return mw.returnJson(True, '设置成功!') + + +def deleteSlave(version=''): + args = getArgs() + db = pMysqlDb() + slave_name = 'slave' + mdb8 = getMdb8Ver() + if mw.inArray(mdb8, version): + slave_name = 'replica' + if 'sign' in args: + sign = args['sign'] + db.query("stop {} for channel '{}'".format(slave_name,sign)) + db.query("reset {} all for channel '{}'".format(slave_name, sign)) + else: + db.query('stop '+slave_name) + db.query('reset '+slave_name+' all') + + return mw.returnJson(True, '删除成功!') + + +def dumpMysqlData(version=''): + args = getArgs() + data = checkArgs(args, ['db']) + if not data[0]: + return data[1] + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + mysql_dir = getServerDir() + myconf = mysql_dir + "/etc/my.cnf" + + option = '' + mode = recognizeDbMode() + if mode == 'gtid': + option = ' --set-gtid-purged=off ' + + if args['db'].lower() == 'all': + dlist = findBinlogDoDb() + cmd = mysql_dir + "/bin/mysqldump --defaults-file=" + myconf + " " + option + " -uroot -p" + \ + pwd + " --databases " + \ + ' '.join(dlist) + " | gzip > /tmp/dump.sql.gz" + else: + cmd = mysql_dir + "/bin/mysqldump --defaults-file=" + myconf + " " + option + " -uroot -p" + \ + pwd + " --databases " + args['db'] + " | gzip > /tmp/dump.sql.gz" + + ret = mw.execShell(cmd) + if ret[0] == '': + return 'ok' + return 'fail' + +############### --- 重要 数据补足同步 ---- ########### + +def getSyncMysqlDB(dbname,sign = ''): + conn = pSqliteDb('slave_sync_user') + if sign != '': + data = conn.field('ip,port,user,pass,mode,cmd').where('ip=?', (sign,)).find() + else: + data = conn.field('ip,port,user,pass,mode,cmd').find() + user = data['user'] + apass = data['pass'] + port = data['port'] + ip = data['ip'] + # 远程数据 + sync_db = mw.getMyORM() + # MySQLdb | + sync_db.setPort(port) + sync_db.setHost(ip) + sync_db.setUser(user) + sync_db.setPwd(apass) + sync_db.setDbName(dbname) + sync_db.setTimeout(60) + return sync_db + +def syncDatabaseRepairTempFile(): + tmp_log = mw.getMWLogs()+ '/mysql-check.log' + return tmp_log + +def syncDatabaseRepairLog(version=''): + import subprocess + args = getArgs() + data = checkArgs(args, ['db','sign','op']) + if not data[0]: + return data[1] + + sync_args_db = args['db'] + sync_args_sign = args['sign'] + op = args['op'] + tmp_log = syncDatabaseRepairTempFile() + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && python3 plugins/mysql-community/index.py sync_database_repair {"db":"'+sync_args_db+'","sign":"'+sync_args_sign+'"}' + # print(cmd) + + if op == 'get': + log = mw.getLastLine(tmp_log, 15) + return mw.returnJson(True, log) + + if op == 'cmd': + return mw.returnJson(True, 'ok', cmd) + + if op == 'do': + os.system(' echo "开始执行" > '+ tmp_log) + os.system(cmd +' >> '+ tmp_log +' &') + return mw.returnJson(True, 'ok') + + return mw.returnJson(False, '无效请求!') + + +def syncDatabaseRepair(version=''): + time_stats_s = time.time() + tmp_log = syncDatabaseRepairTempFile() + + from pymysql.converters import escape_string + args = getArgs() + data = checkArgs(args, ['db','sign']) + if not data[0]: + return data[1] + + sync_args_db = args['db'] + sync_args_sign = args['sign'] + + # 本地数据 + local_db = pMysqlDb() + # 远程数据 + sync_db = getSyncMysqlDB(sync_args_db,sync_args_sign) + + tables = local_db.query('show tables from `%s`' % sync_args_db) + table_key = "Tables_in_" + sync_args_db + inconsistent_table = [] + + tmp_dir = '/tmp/sync_db_repair' + mw.execShell('mkdir -p '+tmp_dir) + + for tb in tables: + + table_name = sync_args_db+'.'+tb[table_key] + table_check_file = tmp_dir+'/'+table_name+'.txt' + + if os.path.exists(table_check_file): + # print(table_name+', 已检查OK') + continue + + primary_key_sql = "SHOW INDEX FROM "+table_name+" WHERE Key_name = 'PRIMARY';"; + primary_key_data = local_db.query(primary_key_sql) + # print(primary_key_sql,primary_key_data) + pkey_name = '*' + if len(primary_key_data) == 1: + pkey_name = primary_key_data[0]['Column_name'] + # print(pkey_name) + if pkey_name != '*' : + # 智能校验(由于服务器同步可能会慢,比较总数总是对不上) + cmd_local_newpk_sql = 'select ' + pkey_name + ' from ' + table_name + " order by " + pkey_name + " desc limit 1" + cmd_local_newpk_data = local_db.query(cmd_local_newpk_sql) + # print(cmd_local_newpk_data) + if len(cmd_local_newpk_data) == 1: + # 比较总数 + cmd_count_sql = 'select count('+pkey_name+') as num from '+table_name + ' where '+pkey_name + ' <= '+ str(cmd_local_newpk_data[0][pkey_name]) + local_count_data = local_db.query(cmd_count_sql) + sync_count_data = sync_db.query(cmd_count_sql) + + if local_count_data != sync_count_data: + print(cmd_count_sql) + print("all data compare: ",local_count_data, sync_count_data) + else: + print(table_name+' smart compare check ok.') + mw.writeFile(tmp_log, table_name+' smart compare check ok.\n','a+') + mw.execShell("echo 'ok' > "+table_check_file) + continue + + + + # 比较总数 + cmd_count_sql = 'select count('+pkey_name+') as num from '+table_name + local_count_data = local_db.query(cmd_count_sql) + sync_count_data = sync_db.query(cmd_count_sql) + + if local_count_data != sync_count_data: + print("all data compare: ",local_count_data, sync_count_data) + inconsistent_table.append(table_name) + diff = sync_count_data[0]['num'] - local_count_data[0]['num'] + print(table_name+', need sync. diff,'+str(diff)) + mw.writeFile(tmp_log, table_name+', need sync. diff,'+str(diff)+'\n','a+') + else: + print(table_name+' check ok.') + mw.writeFile(tmp_log, table_name+' check ok.\n','a+') + mw.execShell("echo 'ok' > "+table_check_file) + + + # inconsistent_table = ['xx.xx'] + # 数据对齐 + for table_name in inconsistent_table: + is_break = False + while not is_break: + local_db.ping() + # 远程数据 + sync_db.ping() + + print("check table:"+table_name) + mw.writeFile(tmp_log, "check table:"+table_name+'\n','a+') + table_name_pos = 0 + table_name_pos_file = tmp_dir+'/'+table_name+'.pos.txt' + primary_key_sql = "SHOW INDEX FROM "+table_name+" WHERE Key_name = 'PRIMARY';"; + primary_key_data = local_db.query(primary_key_sql) + pkey_name = primary_key_data[0]['Column_name'] + + if os.path.exists(table_name_pos_file): + table_name_pos = mw.readFile(table_name_pos_file) + + + data_select_sql = 'select * from '+table_name + ' where '+pkey_name+' > '+str(table_name_pos)+' limit 10000' + print(data_select_sql) + local_select_data = local_db.query(data_select_sql) + + time_s = time.time() + sync_select_data = sync_db.query(data_select_sql) + print(f'sync query cos:{time.time() - time_s:.4f}s') + mw.writeFile(tmp_log, f'sync query cos:{time.time() - time_s:.4f}s\n','a+') + + # print(local_select_data) + # print(sync_select_data) + + # print(len(local_select_data)) + # print(len(sync_select_data)) + print('pos:',str(table_name_pos),'local compare sync,',local_select_data == sync_select_data) + + + cmd_count_sql = 'select count('+pkey_name+') as num from '+table_name + local_count_data = local_db.query(cmd_count_sql) + time_s = time.time() + sync_count_data = sync_db.query(cmd_count_sql) + print(f'sync count data cos:{time.time() - time_s:.4f}s') + print(local_count_data,sync_count_data) + # 数据同步有延迟,相等即任务数据补足完成 + if local_count_data[0]['num'] == sync_count_data[0]['num']: + is_break = True + break + + diff = sync_count_data[0]['num'] - local_count_data[0]['num'] + print("diff," + str(diff)+' line data!') + + if local_select_data == sync_select_data: + data_count = len(local_select_data) + if data_count == 0: + # mw.writeFile(table_name_pos_file, '0') + print(table_name+",data is equal ok..") + is_break = True + break + + # print(table_name,data_count) + pos = local_select_data[data_count-1][pkey_name] + print('pos',pos) + progress = pos/sync_count_data[0]['num'] + print('progress,%.2f' % progress+'%') + mw.writeFile(table_name_pos_file, str(pos)) + else: + sync_select_data_len = len(sync_select_data) + skip_idx = 0 + # 主库PK -> 查询本地 | 保证一致 + if sync_select_data_len > 0: + for idx in range(sync_select_data_len): + sync_idx_data = sync_select_data[idx] + local_idx_data = None + if idx in local_select_data: + local_idx_data = local_select_data[idx] + if sync_select_data[idx] == local_idx_data: + skip_idx = idx + pos = local_select_data[idx][pkey_name] + mw.writeFile(table_name_pos_file, str(pos)) + + # print(insert_data) + local_inquery_sql = 'select * from ' + table_name+ ' where ' +pkey_name+' = '+ str(sync_idx_data[pkey_name]) + # print(local_inquery_sql) + ldata = local_db.query(local_inquery_sql) + # print('ldata:',ldata) + if len(ldata) == 0: + print("id:"+ str(sync_idx_data[pkey_name])+ " no exists, insert") + insert_sql = 'insert into ' + table_name + field_str = '' + value_str = '' + for field in sync_idx_data: + field_str += '`'+field+'`,' + value_str += '\''+escape_string(str(sync_idx_data[field]))+'\',' + field_str = '(' +field_str.strip(',')+')' + value_str = '(' +value_str.strip(',')+')' + insert_sql = insert_sql+' '+field_str+' values'+value_str+';' + print(insert_sql) + r = local_db.execute(insert_sql) + print(r) + else: + # print('compare sync->local:',sync_idx_data == ldata[0] ) + if ldata[0] == sync_idx_data: + continue + + print("id:"+ str(sync_idx_data[pkey_name])+ " data is not equal, update") + update_sql = 'update ' + table_name + field_str = '' + value_str = '' + for field in sync_idx_data: + if field == pkey_name: + continue + field_str += '`'+field+'`=\''+escape_string(str(sync_idx_data[field]))+'\',' + field_str = field_str.strip(',') + update_sql = update_sql+' set '+field_str+' where '+pkey_name+'=\''+str(sync_idx_data[pkey_name])+'\';' + print(update_sql) + r = local_db.execute(update_sql) + print(r) + + # 本地PK -> 查询主库 | 保证一致 + # local_select_data_len = len(local_select_data) + # if local_select_data_len > 0: + # for idx in range(local_select_data_len): + # if idx < skip_idx: + # continue + # local_idx_data = local_select_data[idx] + # print('local idx check', idx, skip_idx) + # local_inquery_sql = 'select * from ' + table_name+ ' where ' +pkey_name+' = '+ str(local_idx_data[pkey_name]) + # print(local_inquery_sql) + # sdata = sync_db.query(local_inquery_sql) + # sdata_len = len(sdata) + # print('sdata:',sdata,sdata_len) + # if sdata_len == 0: + # delete_sql = 'delete from ' + table_name + ' where ' +pkey_name+' = '+ str(local_idx_data[pkey_name]) + # print(delete_sql) + # r = local_db.execute(delete_sql) + # print(r) + # break + + + if is_break: + print("break all") + break + time.sleep(3) + print(f'data check cos:{time.time() - time_stats_s:.4f}s') + print("data supplementation completed") + mw.execShell('rm -rf '+tmp_dir) + return 'ok' + +############### --- 重要 同步---- ########### + +def asyncTmpfile(): + path = '/tmp/mysql_community_async_status.txt' + return path + + +def writeDbSyncStatus(data): + path = asyncTmpfile() + mw.writeFile(path, json.dumps(data)) + +def fullSyncCmd(): + time_all_s = time.time() + args = getArgs() + data = checkArgs(args, ['db', 'sign']) + if not data[0]: + return data[1] + + db = args['db'] + sign = args['sign'] + + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && python3 plugins/mysql-community/index.py do_full_sync {"db":"'+db+'","sign":"'+sign+'"}' + return mw.returnJson(True,'ok',cmd) + +def doFullSync(version=''): + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + if mode == 'ssh': + return doFullSyncSSH(version) + if mode == 'sync-user': + return doFullSyncUser(version) + +def isSimpleSyncCmd(sql): + new_sql = sql.lower() + if new_sql.find('master_auto_position') > 0: + return False + return True + +def getChannelNameForCmd(cmd): + cmd = cmd.lower() + cmd_arr = cmd.split('channel') + if len(cmd_arr) == 2: + cmd_channel_info = cmd_arr[1] + channel_name = cmd_channel_info.strip() + channel_name = channel_name.strip(';') + channel_name = channel_name.strip("'") + return channel_name + return '' + +def doFullSyncUserImportContentForChannel(file, channel_name): + # print(file, channel_name) + content = mw.readFile(file) + + slave_name = getSlaveName() + slave_name = slave_name.upper() + + content = content.replace('STOP '+slave_name+';', "STOP {} for channel '{}';".format(slave_name,channel_name)) + content = content.replace('START '+slave_name+';', "START {} for channel '{}';".format(slave_name,channel_name)) + + find_head = "CHANGE MASTER TO " + find_re = find_head+"(.*?);" + find_r = re.search(find_re, content, re.I|re.M) + if find_r: + find_rg = find_r.groups() + if len(find_rg)>0: + find_str = find_head+find_rg[0] + if find_str.lower().find('channel')==-1: + content = content.replace(find_str+';', find_str+" for channel '{}';".format(channel_name)) + + mw.writeFile(file,content) + return True + +def doFullSyncUser(version=''): + which_pv = mw.execShell('which pv') + is_exist_pv = False + if os.path.exists(which_pv[0]): + is_exist_pv = True + + time_all_s = time.time() + + args = getArgs() + data = checkArgs(args, ['db', 'sign']) + if not data[0]: + return data[1] + + sync_db = args['db'] + sync_db_import = args['db'] + + if sync_db.lower() == 'all': + sync_db_import = '' + dbs = findBinlogSlaveDoDb() + dbs_str = '' + for x in dbs: + dbs_str += ' ' + x + sync_db = "--databases " + dbs_str.strip() + + sync_sign = args['sign'] + + db = pMysqlDb() + + conn = pSqliteDb('slave_sync_user') + if sync_sign != '': + data = conn.field('ip,port,user,pass,mode,cmd').where( + 'ip=?', (sync_sign,)).find() + else: + data = conn.field('ip,port,user,pass,mode,cmd').find() + + user = data['user'] + apass = data['pass'] + port = data['port'] + ip = data['ip'] + cmd = data['cmd'] + + channel_name = getChannelNameForCmd(cmd) + sync_mdb = getSyncMysqlDB(sync_db,sync_sign) + + bak_file = '/tmp/tmp.sql' + if os.path.exists(bak_file): + os.system("rm -rf " + bak_file) + + writeDbSyncStatus({'code': 0, 'msg': '开始同步...', 'progress': 0}) + dmp_option = '' + mode = recognizeDbMode() + if mode == 'gtid': + dmp_option = ' --set-gtid-purged=off ' + + time.sleep(1) + writeDbSyncStatus({'code': 1, 'msg': '正在停止从库...', 'progress': 15}) + + mdb8 = getMdb8Ver() + if mw.inArray(mdb8,version): + db.query("stop slave user='{}' password='{}';".format(user, apass)) + else: + db.query("stop slave") + + time.sleep(1) + writeDbSyncStatus({'code': 2, 'msg': '远程导出数据...', 'progress': 20}) + + find_run_dump = mw.execShell('ps -ef | grep mysqldump | grep -v grep') + if find_run_dump[0] != "": + print("正在远程导出数据中,别着急...") + writeDbSyncStatus({'code': 3.1, 'msg': '正在远程导出数据中,别着急...', 'progress': 19}) + return False + + time_s = time.time() + if not os.path.exists(bak_file): + dmp_option += ' ' + if isSimpleSyncCmd(cmd): + if mw.inArray(mdb8,version): + # --compression-algorithms + dmp_option += " --source-data=1 --apply-replica-statements --include-source-host-port " + else: + dmp_option += " --master-data=1 --apply-slave-statements --include-master-host-port --compress " + + + dump_sql_data = getServerDir() + "/bin/mysqldump --single-transaction --default-character-set=utf8mb4 -q " + dmp_option + " -h" + \ + ip + " -P" + port + " -u" + user + ' -p"' + apass + '" --ssl-mode=DISABLED ' + sync_db + " > " + bak_file + print(dump_sql_data) + time_s = time.time() + r = mw.execShell(dump_sql_data) + print(r) + time_e = time.time() + export_cos = time_e - time_s + print("export cos:", export_cos) + + writeDbSyncStatus({'code': 3, 'msg': '导出耗时:'+str(int(export_cos))+'秒,正在到本地导入数据中...', 'progress': 40}) + + find_run_import = mw.execShell('ps -ef | grep mysql| grep '+ bak_file +' | grep -v grep') + if find_run_import[0] != "": + print("正在导入数据中,别着急...") + writeDbSyncStatus({'code': 4.1, 'msg': '正在导入数据中,别着急...', 'progress': 39}) + return False + + + time_s = time.time() + if os.path.exists(bak_file): + + # 重置 + db.execute('reset master') + if channel_name != '': + doFullSyncUserImportContentForChannel(bak_file, channel_name) + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + if is_exist_pv: + my_import_cmd = getServerDir() + '/bin/mysql -S ' + sock + " -uroot -p'" + pwd + "' " + sync_db_import + my_import_cmd = "pv -t -p " + bak_file + '|' + my_import_cmd + print(my_import_cmd) + os.system(my_import_cmd) + else: + my_import_cmd = getServerDir() + '/bin/mysql -S ' + sock + " -uroot -p'" + pwd + "' " + sync_db_import + ' < ' + bak_file + print(my_import_cmd) + mw.execShell(my_import_cmd) + + my_import_cmd = getServerDir() + '/bin/mysql -S ' + sock + ' -uroot -p' + pwd + \ + ' ' + sync_db_import + ' < ' + bak_file + mw.execShell(my_import_cmd) + + if mw.inArray(mdb8, version): + db.query("start replica user='{}' password='{}';".format(user, apass)) + else: + db.query("start slave") + + db.query("start all slaves") + time_all_e = time.time() + cos = time_all_e - time_all_s + writeDbSyncStatus({'code': 6, 'msg': '总耗时:'+str(int(cos))+'秒,从库重启完成...', 'progress': 100}) + + if os.path.exists(bak_file): + os.system("rm -rf " + bak_file) + + return True + + +def doFullSyncSSH(version=''): + + args = getArgs() + data = checkArgs(args, ['db', 'sign']) + if not data[0]: + return data[1] + + sync_db = args['db'] + sync_sign = args['sign'] + + db = pMysqlDb() + + id_rsa_conn = pSqliteDb('slave_id_rsa') + if sync_sign != '': + data = id_rsa_conn.field('ip,port,db_user,id_rsa').where( + 'ip=?', (sync_sign,)).find() + else: + data = id_rsa_conn.field('ip,port,db_user,id_rsa').find() + + SSH_PRIVATE_KEY = "/tmp/mysql_sync_id_rsa.txt" + id_rsa = data['id_rsa'].replace('\\n', '\n') + mw.writeFile(SSH_PRIVATE_KEY, id_rsa) + + ip = data["ip"] + master_port = data['port'] + db_user = data['db_user'] + print("master ip:", ip) + + writeDbSyncStatus({'code': 0, 'msg': '开始同步...', 'progress': 0}) + + import paramiko + paramiko.util.log_to_file('paramiko.log') + ssh = paramiko.SSHClient() + + print(SSH_PRIVATE_KEY) + if not os.path.exists(SSH_PRIVATE_KEY): + writeDbSyncStatus({'code': 0, 'msg': '需要配置SSH......', 'progress': 0}) + return 'fail' + + try: + # ssh.load_system_host_keys() + mw.execShell("chmod 600 " + SSH_PRIVATE_KEY) + key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + print(ip, master_port) + + # pkey=key + # key_filename=SSH_PRIVATE_KEY + ssh.connect(hostname=ip, port=int(master_port), + username='root', pkey=key) + except Exception as e: + print(str(e)) + writeDbSyncStatus( + {'code': 0, 'msg': 'SSH配置错误:' + str(e), 'progress': 0}) + return 'fail' + + writeDbSyncStatus({'code': 0, 'msg': '登录Master成功...', 'progress': 5}) + + dbname = args['db'] + cmd = "cd /www/server/mdserver-web && source bin/activate && python3 " + \ + getSPluginDir() + "/index.py dump_mysql_data {\"db\":'" + dbname + "'}" + print(cmd) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + if result.strip() == 'ok': + writeDbSyncStatus({'code': 1, 'msg': '主服务器备份完成...', 'progress': 30}) + else: + writeDbSyncStatus( + {'code': 1, 'msg': '主服务器备份失败...:' + str(result), 'progress': 100}) + return 'fail' + + print("同步文件", "start") + # cmd = 'scp -P' + str(master_port) + ' -i ' + SSH_PRIVATE_KEY + \ + # ' root@' + ip + ':/tmp/dump.sql.gz /tmp' + t = ssh.get_transport() + sftp = paramiko.SFTPClient.from_transport(t) + copy_status = sftp.get("/tmp/dump.sql.gz", "/tmp/dump.sql.gz") + print("同步信息:", copy_status) + print("同步文件", "end") + if copy_status == None: + writeDbSyncStatus({'code': 2, 'msg': '数据同步本地完成...', 'progress': 40}) + + cmd = 'cd /www/server/mdserver-web && source bin/activate && python3 ' + \ + getSPluginDir() + \ + '/index.py get_master_rep_slave_user_cmd {"username":"' + \ + db_user + '","db":""}' + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + cmd_data = json.loads(result) + + db.query('stop slave') + writeDbSyncStatus({'code': 3, 'msg': '停止从库完成...', 'progress': 45}) + + cmd = cmd_data['data']['cmd'] + # 保证同步IP一致 + if cmd.find('SOURCE_HOST') > -1: + cmd = re.sub(r"SOURCE_HOST='(.*?)'", + "SOURCE_HOST='" + ip + "'", cmd, 1) + + if cmd.find('MASTER_HOST') > -1: + cmd = re.sub(r"MASTER_HOST='(.*?)'", + "MASTER_HOST='" + ip + "'", cmd, 1) + + db.query(cmd) + uinfo = cmd_data['data']['info'] + ps = uinfo['username'] + "|" + uinfo['password'] + id_rsa_conn.where('ip=?', (ip,)).setField('ps', ps) + writeDbSyncStatus({'code': 4, 'msg': '刷新从库同步信息完成...', 'progress': 50}) + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + root_dir = getServerDir() + msock = root_dir + "/mysql.sock" + mw.execShell("cd /tmp && gzip -d dump.sql.gz") + cmd = root_dir + "/bin/mysql -S " + msock + \ + " -uroot -p" + pwd + " < /tmp/dump.sql" + + print(cmd) + import_data = mw.execShell(cmd) + if import_data[0] == '': + print(import_data[1]) + writeDbSyncStatus({'code': 5, 'msg': '导入数据完成...', 'progress': 90}) + else: + print(import_data[0]) + writeDbSyncStatus({'code': 5, 'msg': '导入数据失败...', 'progress': 100}) + return 'fail' + + # "start slave user='{}' password='{}';".format(uinfo['username'], uinfo['password']) + + db.query("start slave") + writeDbSyncStatus({'code': 6, 'msg': '从库重启完成...', 'progress': 100}) + + os.system("rm -rf " + SSH_PRIVATE_KEY) + os.system("rm -rf /tmp/dump.sql") + return True + + +def fullSync(version=''): + args = getArgs() + data = checkArgs(args, ['db', 'begin']) + if not data[0]: + return data[1] + + status_file = asyncTmpfile() + if args['begin'] == '1': + cmd = 'cd ' + mw.getPanelDir() + ' && python3 ' + getPluginDir() + \ + '/index.py do_full_sync {"db":"' + \ + args['db'] + '","sign":"' + sign + '"} &' + # print(cmd) + mw.execShell(cmd) + return json.dumps({'code': 0, 'msg': '同步数据中!', 'progress': 0}) + + if os.path.exists(status_file): + c = mw.readFile(status_file) + tmp = json.loads(c) + if tmp['code'] == 1: + sys_dump_sql = "/tmp/dump.sql" + if os.path.exists(sys_dump_sql): + dump_size = os.path.getsize(sys_dump_sql) + tmp['msg'] = tmp['msg'] + ":" + "同步文件:" + mw.toSize(dump_size) + c = json.dumps(tmp) + + # if tmp['code'] == 6: + # os.remove(status_file) + return c + + return json.dumps({'code': 0, 'msg': '点击开始,开始同步!', 'progress': 0}) + + +def installPreInspection(version): + return 'ok' + + +def uninstallPreInspection(version): + data_dir = getDataDir() + if not os.path.exists(data_dir): + return 'ok' + stop(version) + if mw.isDebugMode(): + return 'ok' + + from utils.plugin import plugin as MwPlugin + MwPlugin.instance().removeIndex(getPluginName(), version) + + return "请手动删除MySQL[{}]
                            rm -rf {}".format(version, getServerDir()) + +if __name__ == "__main__": + func = sys.argv[1] + version = '5.6' + version_pl = getServerDir() + "/version.pl" + if os.path.exists(version_pl): + version = mw.readFile(version_pl).strip() + + if func == 'status': + print(status(version)) + elif func == 'start': + print(start(version)) + elif func == 'stop': + print(stop(version)) + elif func == 'restart': + print(restart(version)) + elif func == 'reload': + print(reload(version)) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection(version)) + elif func == 'run_info': + print(runInfo(version)) + elif func == 'db_status': + print(myDbStatus(version)) + elif func == 'set_db_status': + print(setDbStatus(version)) + elif func == 'conf': + print(getConf()) + elif func == 'bin_log': + print(binLog(version)) + elif func == 'binlog_list': + print(binLogList()) + elif func == 'clean_bin_log': + print(cleanBinLog()) + elif func == 'error_log': + print(getErrorLog()) + elif func == 'show_log': + print(getShowLogFile()) + elif func == 'my_db_pos': + print(getMyDbPos()) + elif func == 'set_db_pos': + print(setMyDbPos(version)) + elif func == 'my_port': + print(getMyPort()) + elif func == 'set_my_port': + print(setMyPort()) + elif func == 'init_pwd': + print(initMysqlPwd()) + elif func == 'root_pwd': + print(rootPwd()) + elif func == 'get_db_list': + print(getDbList()) + elif func == 'set_db_backup': + print(setDbBackup()) + elif func == 'import_db_backup': + print(importDbBackup()) + elif func == 'import_db_external': + print(importDbExternal()) + elif func == 'import_db_external_progress': + print(importDbExternalProgress()) + elif func == 'import_db_external_progress_bar': + print(importDbExternalProgressBar()) + elif func == 'delete_db_backup': + print(deleteDbBackup()) + elif func == 'get_db_backup_list': + print(getDbBackupList()) + elif func == 'get_db_backup_import_list': + print(getDbBackupImportList()) + elif func == 'add_db': + print(addDb()) + elif func == 'del_db': + print(delDb()) + elif func == 'sync_get_databases': + print(syncGetDatabases()) + elif func == 'sync_to_databases': + print(syncToDatabases()) + elif func == 'set_root_pwd': + print(setRootPwd(version)) + elif func == 'set_user_pwd': + print(setUserPwd(version)) + elif func == 'get_db_access': + print(getDbAccess()) + elif func == 'set_db_access': + print(setDbAccess()) + elif func == 'fix_db_access': + print(fixDbAccess(version)) + elif func == 'fix_db_access2': + print(fixDbAccess2(version)) + elif func == 'set_db_rw': + print(setDbRw(version)) + elif func == 'set_db_ps': + print(setDbPs()) + elif func == 'get_db_info': + print(getDbInfo()) + elif func == 'repair_table': + print(repairTable()) + elif func == 'opt_table': + print(optTable()) + elif func == 'alter_table': + print(alterTable()) + elif func == 'get_total_statistics': + print(getTotalStatistics()) + elif func == 'get_dbrun_mode': + print(getDbrunMode(version)) + elif func == 'set_dbrun_mode': + print(setDbrunMode(version)) + elif func == 'reset_master': + print(resetMaster(version)) + elif func == 'get_masterdb_list': + print(getMasterDbList(version)) + elif func == 'get_master_status': + print(getMasterStatus(version)) + elif func == 'set_master_status': + print(setMasterStatus(version)) + elif func == 'set_db_master': + print(setDbMaster(version)) + elif func == 'set_db_slave': + print(setDbSlave(version)) + elif func == 'set_dbmaster_access': + print(setDbMasterAccess()) + elif func == 'get_master_rep_slave_list': + print(getMasterRepSlaveList(version)) + elif func == 'add_master_rep_slave_user': + print(addMasterRepSlaveUser(version)) + elif func == 'del_master_rep_slave_user': + print(delMasterRepSlaveUser(version)) + elif func == 'update_master_rep_slave_user': + print(updateMasterRepSlaveUser(version)) + elif func == 'get_master_rep_slave_user_cmd': + print(getMasterRepSlaveUserCmd(version)) + elif func == 'get_slave_list': + print(getSlaveList(version)) + elif func == 'try_slave_sync_bugfix': + print(trySlaveSyncBugfix(version)) + elif func == 'get_slave_sync_cmd': + print(getSlaveSyncCmd(version)) + elif func == 'get_slave_ssh_list': + print(getSlaveSSHList(version)) + elif func == 'get_slave_ssh_by_ip': + print(getSlaveSSHByIp(version)) + elif func == 'add_slave_ssh': + print(addSlaveSSH(version)) + elif func == 'del_slave_ssh': + print(delSlaveSSH(version)) + elif func == 'update_slave_ssh': + print(updateSlaveSSH(version)) + elif func == 'get_slave_sync_user_list': + print(getSlaveSyncUserList(version)) + elif func == 'get_slave_sync_user_by_ip': + print(getSlaveSyncUserByIp(version)) + elif func == 'add_slave_sync_user': + print(addSlaveSyncUser(version)) + elif func == 'del_slave_sync_user': + print(delSlaveSyncUser(version)) + elif func == 'get_slave_sync_mode': + print(getSlaveSyncMode(version)) + elif func == 'set_slave_sync_mode': + print(setSlaveSyncMode(version)) + elif func == 'init_slave_status': + print(initSlaveStatus(version)) + elif func == 'set_slave_status': + print(setSlaveStatus(version)) + elif func == 'delete_slave': + print(deleteSlave(version)) + elif func == 'full_sync': + print(fullSync(version)) + elif func == 'do_full_sync': + print(doFullSync(version)) + elif func == 'full_sync_cmd': + print(fullSyncCmd()) + elif func == 'dump_mysql_data': + print(dumpMysqlData(version)) + elif func == 'sync_database_repair': + print(syncDatabaseRepair()) + elif func == 'sync_database_repair_log': + print(syncDatabaseRepairLog()) + else: + print('error') diff --git a/plugins/mysql-community/index_mysql_community.py b/plugins/mysql-community/index_mysql_community.py new file mode 100644 index 000000000..0c3268070 --- /dev/null +++ b/plugins/mysql-community/index_mysql_community.py @@ -0,0 +1,189 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +# if mw.isAppleSystem(): +# cmd = 'ls /usr/local/lib/ | grep python | cut -d \\ -f 1 | awk \'END {print}\'' +# info = mw.execShell(cmd) +# p = "/usr/local/lib/" + info[0].strip() + "/site-packages" +# sys.path.append(p) + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'mysql-community' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getSPluginDir(): + return '/www/server/mdserver-web/plugins/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConf(): + path = getServerDir() + '/etc/my.cnf' + return path + + +def getDataDir(): + file = getConf() + content = mw.readFile(file) + rep = r'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getRelayLogName(): + file = getConf() + content = mw.readFile(file) + rep = r'relay-log\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getLogBinName(): + file = getConf() + content = mw.readFile(file) + rep = r'log-bin\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def binLogListLook(args): + + file = args['file'] + line = args['line'] + + data_dir = getDataDir() + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata = {} + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata + + +def binLogListLookDecode(args): + + file = args['file'] + line = args['line'] + + data_dir = getDataDir() + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults --base64-output=decode-rows -vvvv ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata = {} + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata + + +def binLogListTraceRelay(args): + rdata = {} + file = args['file'] + line = args['line'] + + relay_name = getRelayLogName() + data_dir = getDataDir() + alist = os.listdir(data_dir) + relay_list = [] + for x in range(len(alist)): + f = alist[x] + t = {} + if f.startswith(relay_name) and not f.endswith('.index'): + relay_list.append(f) + + relay_list = sorted(relay_list, reverse=True) + if len(relay_list) == 0: + rdata['cmd'] = '' + rdata['data'] = '无Relay日志' + return rdata + + file = relay_list[0] + + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults --base64-output=decode-rows -vvvv ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata + + +def binLogListTraceBinLog(args): + rdata = {} + file = args['file'] + line = args['line'] + + data_dir = getDataDir() + log_bin_name = getLogBinName() + + alist = os.listdir(data_dir) + log_bin_l = [] + for x in range(len(alist)): + f = alist[x] + t = {} + if f.startswith(log_bin_name) and not f.endswith('.index'): + log_bin_l.append(f) + + if len(log_bin_l) == 0: + rdata['cmd'] = '' + rdata['data'] = '无BINLOG' + return rdata + + log_bin_l = sorted(log_bin_l, reverse=True) + file = log_bin_l[0] + + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults --base64-output=decode-rows -vvvv ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata diff --git a/plugins/mysql-community/info.json b/plugins/mysql-community/info.json new file mode 100755 index 000000000..9e94cf5ea --- /dev/null +++ b/plugins/mysql-community/info.json @@ -0,0 +1,20 @@ +{ + "sort":1, + "hook":["database"], + "title":"MySQL[Tar]", + "tip":"soft", + "name":"mysql-community", + "type":"运行环境", + "ps":"一种关系数据库管理系统(极速安装)", + "todo_versions":["5.7","8.0"], + "versions":["5.7","8.0","8.2","8.3","8.4","9.0","9.1","9.2","9.3"], + "shell":"install.sh", + "install_pre_inspection":true, + "uninstall_pre_inspection":true, + "checks":"server/mysql-community", + "path":"server/mysql-community", + "author":"mysql", + "home":"https://dev.mysql.com/downloads/mysql", + "date":"2022-06-29", + "pid": "6" +} \ No newline at end of file diff --git a/plugins/mysql-community/init.d/mysql5.7.service.tpl b/plugins/mysql-community/init.d/mysql5.7.service.tpl new file mode 100644 index 000000000..dad046923 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql5.7.service.tpl @@ -0,0 +1,43 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +After=network.target + +[Service] +User=mysql +Group=mysql +Type=simple +PermissionsStartOnly=true +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 5000 +Restart=on-failure +RestartPreventExitStatus=1 +RuntimeDirectory=mysqld +RuntimeDirectoryMode=755 + +[Install] +WantedBy=multi-user.target diff --git a/plugins/mysql-community/init.d/mysql8.0.service.tpl b/plugins/mysql-community/init.d/mysql8.0.service.tpl new file mode 100644 index 000000000..067279016 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql8.0.service.tpl @@ -0,0 +1,50 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +User=mysql +Group=mysql +Type=notify +#ExecStartPre=+/usr/share/mysql-8.0/mysql-systemd-start pre +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 10000 +Restart=on-failure +RestartPreventExitStatus=1 + +# Always restart when mysqld exits with exit code of 16. This special exit code +# is used by mysqld for RESTART SQL. +RestartForceExitStatus=16 + +# Set enviroment variable MYSQLD_PARENT_PID. This is required for restart. +Environment=MYSQLD_PARENT_PID=1 diff --git a/plugins/mysql-community/init.d/mysql8.1.service.tpl b/plugins/mysql-community/init.d/mysql8.1.service.tpl new file mode 100644 index 000000000..067279016 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql8.1.service.tpl @@ -0,0 +1,50 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +User=mysql +Group=mysql +Type=notify +#ExecStartPre=+/usr/share/mysql-8.0/mysql-systemd-start pre +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 10000 +Restart=on-failure +RestartPreventExitStatus=1 + +# Always restart when mysqld exits with exit code of 16. This special exit code +# is used by mysqld for RESTART SQL. +RestartForceExitStatus=16 + +# Set enviroment variable MYSQLD_PARENT_PID. This is required for restart. +Environment=MYSQLD_PARENT_PID=1 diff --git a/plugins/mysql-community/init.d/mysql8.2.service.tpl b/plugins/mysql-community/init.d/mysql8.2.service.tpl new file mode 100644 index 000000000..067279016 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql8.2.service.tpl @@ -0,0 +1,50 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +User=mysql +Group=mysql +Type=notify +#ExecStartPre=+/usr/share/mysql-8.0/mysql-systemd-start pre +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 10000 +Restart=on-failure +RestartPreventExitStatus=1 + +# Always restart when mysqld exits with exit code of 16. This special exit code +# is used by mysqld for RESTART SQL. +RestartForceExitStatus=16 + +# Set enviroment variable MYSQLD_PARENT_PID. This is required for restart. +Environment=MYSQLD_PARENT_PID=1 diff --git a/plugins/mysql-community/init.d/mysql8.3.service.tpl b/plugins/mysql-community/init.d/mysql8.3.service.tpl new file mode 100644 index 000000000..067279016 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql8.3.service.tpl @@ -0,0 +1,50 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +User=mysql +Group=mysql +Type=notify +#ExecStartPre=+/usr/share/mysql-8.0/mysql-systemd-start pre +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 10000 +Restart=on-failure +RestartPreventExitStatus=1 + +# Always restart when mysqld exits with exit code of 16. This special exit code +# is used by mysqld for RESTART SQL. +RestartForceExitStatus=16 + +# Set enviroment variable MYSQLD_PARENT_PID. This is required for restart. +Environment=MYSQLD_PARENT_PID=1 diff --git a/plugins/mysql-community/init.d/mysql8.4.service.tpl b/plugins/mysql-community/init.d/mysql8.4.service.tpl new file mode 100644 index 000000000..9de86ce28 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql8.4.service.tpl @@ -0,0 +1,49 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +User=mysql +Group=mysql +Type=notify +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 10000 +Restart=on-failure +RestartPreventExitStatus=1 + +# Always restart when mysqld exits with exit code of 16. This special exit code +# is used by mysqld for RESTART SQL. +RestartForceExitStatus=16 + +# Set enviroment variable MYSQLD_PARENT_PID. This is required for restart. +Environment=MYSQLD_PARENT_PID=1 diff --git a/plugins/mysql-community/init.d/mysql9.0.service.tpl b/plugins/mysql-community/init.d/mysql9.0.service.tpl new file mode 100644 index 000000000..9de86ce28 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql9.0.service.tpl @@ -0,0 +1,49 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +User=mysql +Group=mysql +Type=notify +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 10000 +Restart=on-failure +RestartPreventExitStatus=1 + +# Always restart when mysqld exits with exit code of 16. This special exit code +# is used by mysqld for RESTART SQL. +RestartForceExitStatus=16 + +# Set enviroment variable MYSQLD_PARENT_PID. This is required for restart. +Environment=MYSQLD_PARENT_PID=1 diff --git a/plugins/mysql-community/init.d/mysql9.1.service.tpl b/plugins/mysql-community/init.d/mysql9.1.service.tpl new file mode 100644 index 000000000..9de86ce28 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql9.1.service.tpl @@ -0,0 +1,49 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +User=mysql +Group=mysql +Type=notify +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 10000 +Restart=on-failure +RestartPreventExitStatus=1 + +# Always restart when mysqld exits with exit code of 16. This special exit code +# is used by mysqld for RESTART SQL. +RestartForceExitStatus=16 + +# Set enviroment variable MYSQLD_PARENT_PID. This is required for restart. +Environment=MYSQLD_PARENT_PID=1 diff --git a/plugins/mysql-community/init.d/mysql9.2.service.tpl b/plugins/mysql-community/init.d/mysql9.2.service.tpl new file mode 100644 index 000000000..9de86ce28 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql9.2.service.tpl @@ -0,0 +1,49 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +User=mysql +Group=mysql +Type=notify +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 10000 +Restart=on-failure +RestartPreventExitStatus=1 + +# Always restart when mysqld exits with exit code of 16. This special exit code +# is used by mysqld for RESTART SQL. +RestartForceExitStatus=16 + +# Set enviroment variable MYSQLD_PARENT_PID. This is required for restart. +Environment=MYSQLD_PARENT_PID=1 diff --git a/plugins/mysql-community/init.d/mysql9.3.service.tpl b/plugins/mysql-community/init.d/mysql9.3.service.tpl new file mode 100644 index 000000000..9de86ce28 --- /dev/null +++ b/plugins/mysql-community/init.d/mysql9.3.service.tpl @@ -0,0 +1,49 @@ +# Copyright (c) 2015, 2022, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# MySQL systemd service file + +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.target + +[Install] +WantedBy=multi-user.target + +[Service] +User=mysql +Group=mysql +Type=notify +ExecStart={$SERVER_PATH}/mysql-community/bin/mysqld --defaults-file={$SERVER_PATH}/mysql-community/etc/my.cnf +TimeoutSec=600 +LimitNOFILE = 10000 +Restart=on-failure +RestartPreventExitStatus=1 + +# Always restart when mysqld exits with exit code of 16. This special exit code +# is used by mysqld for RESTART SQL. +RestartForceExitStatus=16 + +# Set enviroment variable MYSQLD_PARENT_PID. This is required for restart. +Environment=MYSQLD_PARENT_PID=1 diff --git a/plugins/mysql-community/install.sh b/plugins/mysql-community/install.sh new file mode 100755 index 000000000..2bd9202d0 --- /dev/null +++ b/plugins/mysql-community/install.sh @@ -0,0 +1,131 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + + +# https://dev.mysql.com/downloads/mysql/ +# https://downloads.mysql.com/archives/community/ + +# SHOW VARIABLES LIKE 'default_authentication_plugin'; +# SELECT user, host, plugin FROM mysql.user; +# default_authentication_plugin=caching_sha2_password + +# /www/server/mysql-community/bin/mysqld --basedir=/www/server/mysql-community --datadir=/www/server/mysql-community/data --initialize-insecure --explicit_defaults_for_timestamp + +# source bin/activate +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 5.7 +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 9.3 +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh uninstall 9.0 +# cd /www/server/mdserver-web && python3 plugins/mysql-community/index.py start 8.0 +# cd /www/server/mdserver-web && python3 plugins/mysql-community/index.py fix_db_access +# cd /www/server/mdserver-web && python3 plugins/mysql/index.py do_full_sync {"db":"xxx","sign":"","begin":1} + +action=$1 +type=$2 + +if id mysql &> /dev/null ;then + echo "mysql UID is `id -u mysql`" + echo "mysql Shell is `grep "^mysql:" /etc/passwd |cut -d':' -f7 `" +else + groupadd mysql + useradd -g mysql -s /usr/sbin/nologin mysql +fi + + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep 'VERSION_ID' | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 针对ubuntu24进行优化 +if [[ "$OSNAME" == "ubuntu" ]] && [[ "$VERSION_ID" =~ "24" ]]; then + cur_dir=`pwd` + cd /usr/lib/x86_64-linux-gnu + if [ ! -f libaio.so.1 ];then + ln -s libaio.so.1t64.0.2 libaio.so.1 + fi + + if [ ! -f libncurses.so.6 ];then + ln -s libncursesw.so.6.4 libncurses.so.6 + fi + cd $cur_dir +fi + +if [[ "$OSNAME" == "debian" ]] && [[ "$VERSION_ID" =~ "13" ]]; then + cur_dir=`pwd` + cd /usr/lib/x86_64-linux-gnu + if [ ! -f libaio.so.1 ];then + ln -s libaio.so.1t64.0.2 libaio.so.1 + fi + cd $cur_dir +fi + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + +if [ "${action}" == "uninstall" ];then + + cd ${rootPath} && python3 ${rootPath}/plugins/mysql-community/index.py stop ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/mysql-community/index.py initd_uninstall ${type} + cd $curPath + + if [ -f /usr/lib/systemd/system/mysql-community.service ] || [ -f /lib/systemd/system/mysql-community.service ];then + systemctl stop mysql-community + systemctl disable mysql-community + rm -rf /usr/lib/systemd/system/mysql-community.service + rm -rf /lib/systemd/system/mysql-community.service + systemctl daemon-reload + fi +fi + + +sh -x $curPath/versions/$2/install_generic.sh $1 + +if [ "${action}" == "install" ];then + #初始化 + + if [ "$?" != "0" ];then + exit $? + fi + cd ${rootPath} && python3 ${rootPath}/plugins/mysql-community/index.py start ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/mysql-community/index.py initd_install ${type} +fi diff --git a/plugins/mysql-community/js/mysql-community.js b/plugins/mysql-community/js/mysql-community.js new file mode 100755 index 000000000..a2029d94d --- /dev/null +++ b/plugins/mysql-community/js/mysql-community.js @@ -0,0 +1,2968 @@ + +function myPost(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'mysql-community', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myPostN(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + $.post('/plugins/run', {name:'mysql-community', func:method, args:_args}, function(data) { + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + return syncPost('/plugins/run', {name:'mysql-community', func:method, args:_args}); +} + + +function myPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'mysql-community'; + req_data['func'] = method; + req_data['script']='index_mysql_community'; + args['version'] = version; + + + if (typeof(args) == 'string' && args == ''){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myPostCallbakN(method, version, args,callback){ + + var req_data = {}; + req_data['name'] = 'mysql-community'; + req_data['func'] = method; + req_data['script']='index_mysql_community'; + args['version'] = version; + + + if (typeof(args) == 'string' && args == ''){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function vaildPhpmyadmin(url,username,password){ + // console.log("Authorization: Basic " + btoa(username + ":" + password)); + $.ajax({ + type: "GET", + url: url, + dataType: 'json', + async: false, + username:username, + password:password, + headers: { + "Authorization": "Basic " + btoa(username + ":" + password) + }, + data: 'vaild', + success: function (){ + alert('Thanks for your comment!'); + } + }); +} + +function runInfo(){ + myPost('run_info','',function(data){ + + var rdata = $.parseJSON(data.data); + if (typeof(rdata['status']) != 'undefined'){ + layer.msg(rdata['msg'],{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + // Com_select , Qcache_inserts + var cache_size = ((parseInt(rdata.Qcache_hits) / (parseInt(rdata.Qcache_hits) + parseInt(rdata.Qcache_inserts))) * 100).toFixed(2) + '%'; + if (cache_size == 'NaN%') cache_size = 'OFF'; + var Con = '
                            \ + \ + \ + \ + \ + \ + \ +
                            启动时间' + getLocalTime(rdata.Run) + '每秒查询' + parseInt(rdata.Questions / rdata.Uptime) + '
                            总连接次数' + rdata.Connections + '每秒事务' + parseInt((parseInt(rdata.Com_commit) + parseInt(rdata.Com_rollback)) / rdata.Uptime) + '
                            发送' + toSize(rdata.Bytes_sent) + 'File' + rdata.File + '
                            接收' + toSize(rdata.Bytes_received) + 'Position' + rdata.Position + '
                            \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                            活动/峰值连接数' + rdata.Threads_running + '/' + rdata.Max_used_connections + '若值过大,增加max_connections
                            线程缓存命中率' + ((1 - rdata.Threads_created / rdata.Connections) * 100).toFixed(2) + '%若过低,增加thread_cache_size
                            索引命中率' + ((1 - rdata.Key_reads / rdata.Key_read_requests) * 100).toFixed(2) + '%若过低,增加key_buffer_size
                            Innodb索引命中率' + (rdata.Innodb_buffer_pool_read_requests / (rdata.Innodb_buffer_pool_read_requests+rdata.Innodb_buffer_pool_reads)).toFixed(2) + '%若过低,增加innodb_buffer_pool_size
                            查询缓存命中率' + cache_size + '' + lan.soft.mysql_status_ps5 + '
                            创建临时表到磁盘' + ((rdata.Created_tmp_disk_tables / rdata.Created_tmp_tables) * 100).toFixed(2) + '%若过大,尝试增加tmp_table_size
                            已打开的表' + rdata.Open_tables + '若过大,增加table_cache_size
                            没有使用索引的量' + rdata.Select_full_join + '若不为0,请检查数据表的索引是否合理
                            没有索引的JOIN量' + rdata.Select_range_check + '若不为0,请检查数据表的索引是否合理
                            排序后的合并次数' + rdata.Sort_merge_passes + '若值过大,增加sort_buffer_size
                            锁表次数' + rdata.Table_locks_waited + '若值过大,请考虑增加您的数据库性能
                            '; + $(".soft-man-con").html(Con); + }); +} + + +function myDbPos(){ + myPost('my_db_pos','',function(data){ + var con = '
                            \ +
                            \ + \ + \ + \ +
                            '; + $(".soft-man-con").html(con); + + $('#btn_change_path').click(function(){ + var datadir = $("input[name='datadir']").val(); + myPost('set_db_pos','datadir='+datadir,function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:rdata.status ? 1 : 5,time:2000,shade: [0.3, '#000']}); + }); + }); + }); +} + +function myPort(){ + myPost('my_port','',function(data){ + var con = '
                            \ +
                            \ + \ + \ +
                            '; + $(".soft-man-con").html(con); + + $('#btn_change_port').click(function(){ + var port = $("input[name='port']").val(); + myPost('set_my_port','port='+port,function(data){ + var rdata = $.parseJSON(data.data); + if (rdata.status){ + layer.msg('修改成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + }); +} + +//数据库配置状态 +function myPerfOpt() { + //获取MySQL配置 + myPost('db_status','',function(data){ + var rdata = $.parseJSON(data.data); + if ( typeof(rdata.status) != 'undefined' && !rdata.status){ + layer.msg(rdata.msg, {icon:2}); + return; + } + + + // console.log(rdata); + var key_buffer_size = toSizeM(rdata.mem.key_buffer_size); + var query_cache_size = toSizeM(rdata.mem.query_cache_size); + var tmp_table_size = toSizeM(rdata.mem.tmp_table_size); + var innodb_buffer_pool_size = toSizeM(rdata.mem.innodb_buffer_pool_size); + var innodb_additional_mem_pool_size = toSizeM(rdata.mem.innodb_additional_mem_pool_size); + var innodb_log_buffer_size = toSizeM(rdata.mem.innodb_log_buffer_size); + + var sort_buffer_size = toSizeM(rdata.mem.sort_buffer_size); + var read_buffer_size = toSizeM(rdata.mem.read_buffer_size); + var read_rnd_buffer_size = toSizeM(rdata.mem.read_rnd_buffer_size); + var join_buffer_size = toSizeM(rdata.mem.join_buffer_size); + var thread_stack = toSizeM(rdata.mem.thread_stack); + var binlog_cache_size = toSizeM(rdata.mem.binlog_cache_size); + + var a = key_buffer_size + query_cache_size + tmp_table_size + innodb_buffer_pool_size + innodb_additional_mem_pool_size + innodb_log_buffer_size; + var b = sort_buffer_size + read_buffer_size + read_rnd_buffer_size + join_buffer_size + thread_stack + binlog_cache_size; + var memSize = a + rdata.mem.max_connections * b; + + + var memCon = '
                            \ +
                            最大使用内存: \ + \ + ' + lan.soft.mysql_set_maxmem + ': MB\ +
                            \ +

                            key_buffer_sizeMB, ' + lan.soft.mysql_set_key_buffer_size + '

                            \ +

                            query_cache_sizeMB, ' + lan.soft.mysql_set_query_cache_size + '

                            \ +

                            tmp_table_sizeMB, ' + lan.soft.mysql_set_tmp_table_size + '

                            \ +

                            innodb_buffer_pool_sizeMB, ' + lan.soft.mysql_set_innodb_buffer_pool_size + '

                            \ +

                            innodb_log_buffer_sizeMB, ' + lan.soft.mysql_set_innodb_log_buffer_size + '

                            \ +

                            innodb_additional_mem_pool_sizeMB

                            \ +

                            sort_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_sort_buffer_size + '

                            \ +

                            read_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_read_buffer_size + '

                            \ +

                            read_rnd_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_read_rnd_buffer_size + '

                            \ +

                            join_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_join_buffer_size + '

                            \ +

                            thread_stackKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_thread_stack + '

                            \ +

                            binlog_cache_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_binlog_cache_size + '

                            \ +

                            thread_cache_size ' + lan.soft.mysql_set_thread_cache_size + '

                            \ +

                            table_open_cache ' + lan.soft.mysql_set_table_open_cache + '

                            \ +

                            max_connections ' + lan.soft.mysql_set_max_connections + '

                            \ +
                            \ +
                            ' + + $(".soft-man-con").html(memCon); + + $(".conf_p input[name*='size'],.conf_p input[name='max_connections'],.conf_p input[name='thread_stack']").change(function() { + comMySqlMem(); + }); + + $(".conf_p select[name='mysql_set']").change(function() { + mySQLMemOpt($(this).val()); + comMySqlMem(); + }); + }); +} + +function reBootMySqld(){ + pluginOpService('mysql-apt','restart',''); +} + + +//设置MySQL配置参数 +function setMySQLConf() { + $.post('/system/system_total', '', function(memInfo) { + var memSize = memInfo['memTotal']; + var setSize = parseInt($("input[name='memSize']").val()); + + if(memSize < setSize){ + var errMsg = "错误,内存分配过高!

                            物理内存: {1}MB
                            最大使用内存: {2}MB
                            可能造成的后果: 导致数据库不稳定,甚至无法启动MySQLd服务!"; + var msg = errMsg.replace('{1}',memSize).replace('{2}',setSize); + layer.msg(msg,{icon:2,time:5000}); + return; + } + + var query_cache_size = parseInt($("input[name='query_cache_size']").val()); + var query_cache_type = 0; + if (query_cache_size > 0) { + query_cache_type = 1; + } + var data = { + key_buffer_size: parseInt($("input[name='key_buffer_size']").val()), + query_cache_size: query_cache_size, + query_cache_type: query_cache_type, + tmp_table_size: parseInt($("input[name='tmp_table_size']").val()), + max_heap_table_size: parseInt($("input[name='tmp_table_size']").val()), + innodb_buffer_pool_size: parseInt($("input[name='innodb_buffer_pool_size']").val()), + innodb_log_buffer_size: parseInt($("input[name='innodb_log_buffer_size']").val()), + sort_buffer_size: parseInt($("input[name='sort_buffer_size']").val()), + read_buffer_size: parseInt($("input[name='read_buffer_size']").val()), + read_rnd_buffer_size: parseInt($("input[name='read_rnd_buffer_size']").val()), + join_buffer_size: parseInt($("input[name='join_buffer_size']").val()), + thread_stack: parseInt($("input[name='thread_stack']").val()), + binlog_cache_size: parseInt($("input[name='binlog_cache_size']").val()), + thread_cache_size: parseInt($("input[name='thread_cache_size']").val()), + table_open_cache: parseInt($("input[name='table_open_cache']").val()), + max_connections: parseInt($("input[name='max_connections']").val()) + }; + + myPost('set_db_status', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + reBootMySqld(); + },{ icon: rdata.status ? 1 : 2 }); + }); + },'json'); +} + + +//MySQL内存优化方案 +function mySQLMemOpt(opt) { + var query_size = parseInt($("input[name='query_cache_size']").val()); + switch (opt) { + case '0': + $("input[name='key_buffer_size']").val(8); + if (query_size) $("input[name='query_cache_size']").val(4); + $("input[name='tmp_table_size']").val(8); + $("input[name='innodb_buffer_pool_size']").val(16); + $("input[name='sort_buffer_size']").val(256); + $("input[name='read_buffer_size']").val(256); + $("input[name='read_rnd_buffer_size']").val(128); + $("input[name='join_buffer_size']").val(128); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(32); + $("input[name='thread_cache_size']").val(4); + $("input[name='table_open_cache']").val(32); + $("input[name='max_connections']").val(500); + break; + case '1': + $("input[name='key_buffer_size']").val(128); + if (query_size) $("input[name='query_cache_size']").val(64); + $("input[name='tmp_table_size']").val(64); + $("input[name='innodb_buffer_pool_size']").val(256); + $("input[name='sort_buffer_size']").val(768); + $("input[name='read_buffer_size']").val(768); + $("input[name='read_rnd_buffer_size']").val(512); + $("input[name='join_buffer_size']").val(1024); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(64); + $("input[name='thread_cache_size']").val(64); + $("input[name='table_open_cache']").val(128); + $("input[name='max_connections']").val(100); + break; + case '2': + $("input[name='key_buffer_size']").val(256); + if (query_size) $("input[name='query_cache_size']").val(128); + $("input[name='tmp_table_size']").val(384); + $("input[name='innodb_buffer_pool_size']").val(384); + $("input[name='sort_buffer_size']").val(768); + $("input[name='read_buffer_size']").val(768); + $("input[name='read_rnd_buffer_size']").val(512); + $("input[name='join_buffer_size']").val(2048); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(64); + $("input[name='thread_cache_size']").val(96); + $("input[name='table_open_cache']").val(192); + $("input[name='max_connections']").val(200); + break; + case '3': + $("input[name='key_buffer_size']").val(384); + if (query_size) $("input[name='query_cache_size']").val(192); + $("input[name='tmp_table_size']").val(512); + $("input[name='innodb_buffer_pool_size']").val(512); + $("input[name='sort_buffer_size']").val(1024); + $("input[name='read_buffer_size']").val(1024); + $("input[name='read_rnd_buffer_size']").val(768); + $("input[name='join_buffer_size']").val(2048); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(128); + $("input[name='thread_cache_size']").val(128); + $("input[name='table_open_cache']").val(384); + $("input[name='max_connections']").val(300); + break; + case '4': + $("input[name='key_buffer_size']").val(512); + if (query_size) $("input[name='query_cache_size']").val(256); + $("input[name='tmp_table_size']").val(1024); + $("input[name='innodb_buffer_pool_size']").val(1024); + $("input[name='sort_buffer_size']").val(2048); + $("input[name='read_buffer_size']").val(2048); + $("input[name='read_rnd_buffer_size']").val(1024); + $("input[name='join_buffer_size']").val(4096); + $("input[name='thread_stack']").val(384); + $("input[name='binlog_cache_size']").val(192); + $("input[name='thread_cache_size']").val(192); + $("input[name='table_open_cache']").val(1024); + $("input[name='max_connections']").val(400); + break; + case '5': + $("input[name='key_buffer_size']").val(1024); + if (query_size) $("input[name='query_cache_size']").val(384); + $("input[name='tmp_table_size']").val(2048); + $("input[name='innodb_buffer_pool_size']").val(4096); + $("input[name='sort_buffer_size']").val(4096); + $("input[name='read_buffer_size']").val(4096); + $("input[name='read_rnd_buffer_size']").val(2048); + $("input[name='join_buffer_size']").val(8192); + $("input[name='thread_stack']").val(512); + $("input[name='binlog_cache_size']").val(256); + $("input[name='thread_cache_size']").val(256); + $("input[name='table_open_cache']").val(2048); + $("input[name='max_connections']").val(500); + break; + } +} + +//计算MySQL内存开销 +function comMySqlMem() { + var key_buffer_size = parseInt($("input[name='key_buffer_size']").val()); + var query_cache_size = parseInt($("input[name='query_cache_size']").val()); + var tmp_table_size = parseInt($("input[name='tmp_table_size']").val()); + var innodb_buffer_pool_size = parseInt($("input[name='innodb_buffer_pool_size']").val()); + var innodb_additional_mem_pool_size = parseInt($("input[name='innodb_additional_mem_pool_size']").val()); + var innodb_log_buffer_size = parseInt($("input[name='innodb_log_buffer_size']").val()); + + var sort_buffer_size = $("input[name='sort_buffer_size']").val() / 1024; + var read_buffer_size = $("input[name='read_buffer_size']").val() / 1024; + var read_rnd_buffer_size = $("input[name='read_rnd_buffer_size']").val() / 1024; + var join_buffer_size = $("input[name='join_buffer_size']").val() / 1024; + var thread_stack = $("input[name='thread_stack']").val() / 1024; + var binlog_cache_size = $("input[name='binlog_cache_size']").val() / 1024; + var max_connections = $("input[name='max_connections']").val(); + + var a = key_buffer_size + query_cache_size + tmp_table_size + innodb_buffer_pool_size + innodb_additional_mem_pool_size + innodb_log_buffer_size + var b = sort_buffer_size + read_buffer_size + read_rnd_buffer_size + join_buffer_size + thread_stack + binlog_cache_size + var memSize = a + max_connections * b + $("input[name='memSize']").val(memSize.toFixed(2)); +} + +function syncGetDatabase(){ + myPost('sync_get_databases', null, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function syncToDatabase(type){ + var data = []; + $('input[type="checkbox"].check:checked').each(function () { + if (!isNaN($(this).val())) data.push($(this).val()); + }); + var postData = 'type='+type+'&ids='+JSON.stringify(data); + myPost('sync_to_databases', postData, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function setRootPwd(type, pwd){ + if (type==1){ + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + btn:["提交", "关闭", "复制ROOT密码", "强制修改"], + shadeClose: true, + content: "

                            \ +
                            \ + root密码\ +
                            \ + \ +
                            \ +
                            \ +
                            ", + yes:function(layerIndex){ + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layerIndex); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + }, + btn3:function(){ + var password = $("#MyPassword").val(); + copyText(password); + return false; + }, + btn4:function(layerIndex){ + layer.confirm('强制修改,是为了在重建时使用,确定强制?', { + btn: ['确定', '取消'] + }, function(index, layero){ + layer.close(index); + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password,force:'1'}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layerIndex); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + }); + return false; + } + }); +} + +function showHidePass(obj){ + var a = "glyphicon-eye-open"; + var b = "glyphicon-eye-close"; + + if($(obj).hasClass(a)){ + $(obj).removeClass(a).addClass(b); + $(obj).prev().text($(obj).prev().attr('data-pw')) + } + else{ + $(obj).removeClass(b).addClass(a); + $(obj).prev().text('***'); + } +} + +function checkSelect(){ + setTimeout(function () { + var num = $('input[type="checkbox"].check:checked').length; + // console.log(num); + if (num == 1) { + $('button[batch="true"]').hide(); + $('button[batch="false"]').show(); + }else if (num>1){ + $('button[batch="true"]').show(); + $('button[batch="false"]').show(); + }else{ + $('button[batch="true"]').hide(); + $('button[batch="false"]').hide(); + } + },5) +} + +function setDbRw(id,username,val){ + myPost('set_db_rw',{id:id,username:username,rw:val}, function(data){ + var rdata = $.parseJSON(data.data); + // layer.msg(rdata.msg,{icon:rdata.status ? 1 : 5,shade: [0.3, '#000']}); + showMsg(rdata.msg, function(){ + dbList(); + },{icon:rdata.status ? 1 : 5,shade: [0.3, '#000']}, 2000); + + }); +} + +function setDbAccess(username){ + myPost('get_db_access','username='+username, function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,shade: [0.3, '#000']}); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '设置数据库权限', + closeBtn: 1, + shift: 5, + btn:["提交","取消"], + shadeClose: true, + content: "
                            \ +
                            \ + 访问权限\ +
                            \ + \ +
                            \ +
                            \ +
                            ", + success:function(){ + if (rdata.msg == '127.0.0.1'){ + $('select[name="dataAccess"]').find("option[value='127.0.0.1']").attr("selected",true); + } else if (rdata.msg == '%'){ + $('select[name="dataAccess"]').find('option[value="%"]').attr("selected",true); + } else if ( rdata.msg == 'ip' ){ + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } else { + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#set_db_access").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['access']){ + dataObj['access'] = dataObj['dataAccess']; + if ( dataObj['dataAccess'] == 'ip'){ + if (dataObj['address']==''){ + layer.msg('IP地址不能空!',{icon:2,shade: [0.3, '#000']}); + return; + } + dataObj['access'] = dataObj['address']; + } + } + dataObj['username'] = username; + myPost('set_db_access', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); + + }); +} + +function fixDbAccess(username){ + myPost('fix_db_access', '', function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); +} + +function setDbPass(id, username, password){ + layer.open({ + type: 1, + area: '500px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                            \ +
                            \ + 用户名\ +
                            \ +
                            \ +
                            \ + 密码\ +
                            \ + \ +
                            \ +
                            \ + \ +
                            ", + yes:function(index){ + // var data = $("#mod_pwd").serialize(); + var data = {}; + data['name'] = $('input[name=name]').val(); + data['password'] = $('#MyPassword').val(); + data['id'] = $('input[name=id]').val(); + myPost('set_user_pwd', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); +} + +function addDatabase(type){ + layer.open({ + type: 1, + area: '500px', + title: '添加数据库', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                            \ +
                            \ + 数据库名\ +
                            \ + \ +
                            \ +
                            \ +
                            用户名
                            \ +
                            \ + 密码\ +
                            \ +
                            \ +
                            \ + 访问权限\ +
                            \ + \ +
                            \ +
                            \ + \ +
                            ", + success:function(){ + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='db_user']").val(v); + $("input[name='ps']").val(v); + }); + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index) { + var data = $("#add_db").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['address']){ + dataObj['address'] = dataObj['dataAccess']; + } + myPost('add_db', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + dbList(); + } + },{icon: rdata.status ? 1 : 2}, 2000); + }); + } + }); +} + +function delDb(id, name){ + safeMessage('删除['+name+']','您真的要删除['+name+']吗?',function(){ + var data='id='+id+'&name='+name; + myPost('del_db', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); +} + +function delDbBatch(){ + var arr = []; + $('input[type="checkbox"].check:checked').each(function () { + var _val = $(this).val(); + var _name = $(this).parent().next().text(); + if (!isNaN(_val)) { + arr.push({'id':_val,'name':_name}); + } + }); + + safeMessage('批量删除数据库','您共选择了[2]个数据库,删除后将无法恢复,真的要删除吗?',function(){ + var i = 0; + $(arr).each(function(){ + var data = myAsyncPost('del_db', this); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + } + i++; + }); + + var msg = '成功删除['+i+']个数据库!'; + showMsg(msg,function(){ + dbList(); + },{icon: 1}, 600); + }); +} + + +function setDbPs(id, name, obj) { + var _span = $(obj); + var _input = $(""); + _span.hide().after(_input); + _input.focus(); + _input.blur(function(){ + $(this).remove(); + var ps = _input.val(); + _span.text(ps).show(); + var data = {name:name,id:id,ps:ps}; + myPost('set_db_ps', data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + }); + _input.keyup(function(){ + if(event.keyCode == 13){ + _input.trigger('blur'); + } + }); +} + +function openPhpmyadmin(name,username,password){ + $.post('/plugins/run', {'name':'phpmyadmin','func':'plugins_db_support'}, function(data){ + var rdata = $.parseJSON(data.data); + + if (rdata.data['installed'] != 'ok'){ + layer.msg('phpMyAdmin未安装!',{icon:2,shade: [0.3, '#000']}); + return; + } + + if (rdata.data['status'] != 'start'){ + layer.msg('phpMyAdmin未启动',{icon:2,shade: [0.3, '#000']}); + return; + } + + if (rdata.data['cfg']['choose'] != 'mysql-community'){ + layer.msg('当前为['+rdata.data['cfg']['choose'] + ']模式,若要使用请修改phpMyAdmin访问切换.',{icon:2,shade: [0.3, '#000']}); + return; + } + var home_page = rdata.data['home_page']; + $("#toPHPMyAdmin").attr('action',home_page); + if($("#toPHPMyAdmin").attr('action').indexOf('phpmyadmin') == -1){ + layer.msg('请先安装phpMyAdmin',{icon:2,shade: [0.3, '#000']}); + setTimeout(function(){ window.location.href = '/soft'; },3000); + return; + } + //检查版本 + bigVer = rdata.data['version']; + if (bigVer>=4.5){ + + setTimeout(function(){ + $("#toPHPMyAdmin").submit(); + },2000); + layer.msg('phpMyAdmin['+data.data+']需要手动登录😭',{icon:16,shade: [0.3, '#000'],time:4000}); + + } else{ + var murl = $("#toPHPMyAdmin").attr('action'); + $("#pma_username").val(username); + $("#pma_password").val(password); + $("#db").val(name); + + layer.msg('正在打开phpMyAdmin',{icon:16,shade: [0.3, '#000'],time:2000}); + + setTimeout(function(){ + $("#toPHPMyAdmin").submit(); + },2000); + } + + },'json'); +} + +function delBackup(filename, name, path){ + if(typeof(path) == "undefined"){ + path = ""; + } + myPost('delete_db_backup',{filename:filename,path:path},function(){ + layer.msg('执行成功!'); + setTimeout(function(){ + setBackupReq(name); + },2000); + }); +} + +function downloadBackup(file){ + window.open('/files/download?filename='+encodeURIComponent(file)); +} + +function importBackup(file,name){ + myPost('import_db_backup',{file:file,name:name}, function(data){ + // console.log(data); + layer.msg('执行成功!'); + }); +} + +function importBackupProgress(file,name){ + myPost('import_db_backup_progress',{file:file,name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + title: "手动导入命令CMD【显示进度】", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                            \ +
                            \ +
                            '+rdata.data+'
                            \ +
                            \ +
                            ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + + +function importDbExternal(file,name){ + myPost('import_db_external',{file:file,name:name}, function(data){ + layer.msg('执行成功!'); + }); +} + +function importDbExternalProgress(file,name){ + myPost('import_db_external_progress',{file:file,name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + title: "手动导入命令CMD【显示进度】", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                            \ +
                            \ +
                            '+rdata.data+'
                            \ +
                            \ +
                            ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + +function setLocalImport(db_name){ + + //上传文件 + function uploadDbFiles(upload_dir){ + var up_db = layer.open({ + type:1, + closeBtn: 1, + title:"上传导入文件["+upload_dir+']', + area: ['500px','300px'], + shadeClose:false, + content:'
                            \ + \ + \ + \ + \ + \ + \ + 文件编码:\ + \ + \ + \ +
                              \ +
                              ', + success:function(){ + $('#filesClose').click(function(){ + layer.close(up_db); + }); + } + + }); + uploadStart(function(){ + getList(); + layer.close(up_db); + }); + } + + function getList(){ + myPost('get_db_backup_import_list',{}, function(data){ + var rdata = $.parseJSON(data.data); + + var file_list = rdata.data.list; + var upload_dir = rdata.data.upload_dir; + + var tbody = ''; + for (var i = 0; i < file_list.length; i++) { + tbody += '\ + ' + file_list[i]['name'] + '\ + ' + file_list[i]['size'] + '\ + ' + file_list[i]['time'] + '\ + \ + 导入 | \ + 导入进度 | \ + 删除\ + \ + '; + } + + $('#import_db_file_list').html(tbody); + $('input[name="upload_dir"]').val(upload_dir); + + $("#import_db_file_list .del").on('click',function(){ + var index = $(this).attr('index'); + var filename = file_list[index]["name"]; + myPost('delete_db_backup',{filename:filename,path:upload_dir},function(){ + showMsg('执行成功!', function(){ + getList(); + },{icon:1},2000); + }); + }); + }); + } + + var layerIndex = layer.open({ + type: 1, + title: "从文件导入数据", + area: ['700px', '380px'], + closeBtn: 1, + shadeClose: false, + content: '
                              \ +
                              \ + \ +
                              \ +
                              \ + \ +
                              \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                              文件名称文件大小备份时间操作
                              \ +
                              \ +
                                \ +
                              • 仅支持sql、zip、sql.gz、(tar.gz|gz|tgz)
                              • \ +
                              • zip、tar.gz压缩包结构:test.zip或test.tar.gz压缩包内,必需包含test.sql
                              • \ +
                              • 若文件过大,您还可以使用SFTP工具,将数据库文件上传到/www/backup/import
                              • \ +
                              \ +
                              \ +
                              ', + success:function(index){ + $('#btn_file_upload').click(function(){ + var upload_dir = $('input[name="upload_dir"]').val(); + uploadDbFiles(upload_dir); + }); + + getList(); + }, + }); + + +} + +function setBackup(db_name){ + var layerIndex = layer.open({ + type: 1, + title: "数据库备份详情", + area: ['700px', '280px'], + closeBtn: 1, + shadeClose: false, + content: '
                              \ +
                              \ + \ + \ +
                              \ +
                              \ +
                              \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                              文件名称文件大小备份时间操作
                              \ +
                              \ +
                              \ +
                              ', + success:function(index){ + $('#btn_backup').click(function(){ + myPost('set_db_backup',{name:db_name}, function(data){ + showMsg('执行成功!', function(){ + setBackupReq(db_name); + }, {icon:1}, 2000); + }); + }); + + $('#btn_local_import').click(function(){ + setLocalImport(db_name); + }); + + setBackupReq(db_name); + }, + }); +} + + +function setBackupReq(db_name, obj){ + myPost('get_db_backup_list', {name:db_name}, function(data){ + var rdata = $.parseJSON(data.data); + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + tbody += '\ + ' + rdata.data[i]['name'] + '\ + ' + rdata.data[i]['size'] + '\ + ' + rdata.data[i]['time'] + '\ + \ + 导入 | \ + 导入进度 | \ + 下载 | \ + 删除\ + \ + '; + } + $('#database_table tbody').html(tbody); + }); +} + +function dbList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + myPost('get_db_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list +=''; + list += '' + rdata.data[i]['name'] +''; + list += '' + rdata.data[i]['username'] +''; + list += '' + + '***' + + ''+ + ''+ + ''; + + + list += ''+rdata.data[i]['ps']+''; + list += ''; + + list += ''+(rdata.data[i]['is_backup']?'已备份':'未备份') +' | '; + + var rw = ''; + var rw_change = 'all'; + if (typeof(rdata.data[i]['rw'])!='undefined'){ + var rw_val = '读写'; + if (rdata.data[i]['rw'] == 'all'){ + rw_val = "所有"; + rw_change = 'rw'; + } else if (rdata.data[i]['rw'] == 'rw'){ + rw_val = "读写"; + rw_change = 'r'; + } else if (rdata.data[i]['rw'] == 'r'){ + rw_val = "只读"; + rw_change = 'all'; + } + rw = ''+rw_val+' | '; + } + + + list += '管理 | ' + + '工具 | ' + + '权限 | ' + + rw + + '改密 | ' + + '删除' + + ''; + list += ''; + } + + // + var con = '
                              \ + \ + \ + \ + \ + \ + \ + \ + \ +
                              \ +
                              \ + \ + \ + \ + \ + \ + '+ + // ''+ + '\ + \ + \ + '+ list +'\ +
                              数据库名用户名密码备份备注操作
                              \ +
                              \ +
                              \ +
                              \ + 同步选中\ + 同步所有\ + 从服务器获取\ +
                              \ +
                              \ +
                              '; + + con += ''; + + $(".soft-man-con").html(con); + $('#databasePage').html(rdata.page); + + readerTableChecked(); + }); +} + + +function myBinRollingLogs(_name, func, _args, line){ + + var file_line = 100; + if ( typeof(line) != 'undefined' ){ + file_line = line; + } + + var reqTimer = null; + + function requestLogs(func,file,line){ + myPostCallbakN(func,'',{'file':file,"line":line}, function(rdata){ + var data = rdata.data.data; + var cmd = rdata.data.cmd; + if(data == '') { + data = '当前没有日志!'; + } + + $('#my_rolling_cmd').html(cmd); + + $('#my_rolling_copy').click(function(){ + copyText(cmd); + }); + + var ebody = ''; + $("#my_rolling_logs").html(ebody); + var ob = document.getElementById('roll_info_log'); + ob.scrollTop = ob.scrollHeight; + }); + } + + + layer.open({ + type: 1, + title: _name + '日志', + area: ['800px','700px'], + end: function(){ + if (reqTimer){ + clearInterval(reqTimer); + } + }, + content:'
                              \ +
                              \ + \ + \ + \ + \ +
                              cmd
                              \ +
                              \ +
                              \ +
                              \ + \ +
                              ', + success:function(){ + var fileName = _args['file']; + requestLogs(func,fileName,file_line); + reqTimer = setInterval(function(){ + requestLogs(func,fileName,file_line); + },1000); + } + }); +} + +function myBinLogsRender(page){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + _data['tojs'] = 'myBinLogsRender'; + myPost('binlog_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var list = ''; + for(i in rdata.data){ + list += ''; + + list += '' + rdata.data[i]['name'] +''; + list += '' + toSize(rdata.data[i]['size'])+''; + list += '' + rdata.data[i]['time'] +''; + + + list += ''; + list += '查看 | '; + list += '解码查看'; + list += ''; + } + + if (rdata.data.length ==0){ + list = '无数据'; + } + + $("#binlog_list tbody").html(list); + $('#binlog_page').html(rdata.page); + + + $('#binlog_list .look').click(function(){ + var i = $(this).data('index'); + var file = rdata.data[i]['name']; + myBinRollingLogs('查看BINLOG','binLogListLook',{'file':file },100); + }); + + $('#binlog_list .look_decode').click(function(){ + var i = $(this).data('index'); + var file = rdata.data[i]['name']; + myBinRollingLogs('查看解码BINLOG','binLogListLookDecode',{'file':file },100); + }); + }); +} + +function myBinLogs(){ + var con = '
                              \ + \ + \ +
                              \ +
                              \ + \ + \ + \ + \ + \ + \ + \ +
                              文件名称大小时间操作
                              \ +
                              \ +
                              \ +
                              \ +
                              '; + $(".soft-man-con").html(con); + myBinLogsRender(1); + + $('.soft-man-con .relay_trace').click(function(){ + myBinRollingLogs('中继日志跟踪','binLogListTraceRelay',{'file':''},100); + }); + + $('.soft-man-con .binlog_trace').click(function(){ + myBinRollingLogs('最新BINLOG日志跟踪','binLogListTraceBinLog',{'file':''},100); + }); +} + +function myLogs(){ + + myPost('bin_log', {status:1}, function(data){ + var rdata = $.parseJSON(data.data); + + var line_status = "" + if (rdata.status){ + line_status = '\ + '; + } else { + line_status = ''; + } + + var limitCon = '

                              \ + 二进制日志 ' + toSize(rdata.msg) + '\ + '+line_status+'\ +

                              错误日志

                              \ + \ +

                              '; + $(".soft-man-con").html(limitCon); + + //设置二进制日志 + $(".btn-bin").click(function () { + myPost('bin_log', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){myLogs();}, 2000); + }); + }); + + $(".clean-btn-bin").click(function () { + myPost('clean_bin_log', '', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){myLogs();}, 2000); + }); + }); + + //清空日志 + $(".btn-clear").click(function () { + myPost('error_log', 'close=1', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){myLogs();}, 2000); + }); + }) + + myPost('error_log', 'p=1', function(data){ + var rdata = $.parseJSON(data.data); + var error_body = ''; + if (rdata.status){ + error_body = rdata.data; + } else { + error_body = rdata.msg; + } + $("#error_log").html(error_body); + var ob = document.getElementById('error_log'); + ob.scrollTop = ob.scrollHeight; + }); + }); +} + + +function repCheckeds(tables) { + var dbs = [] + if (tables) { + dbs.push(tables) + } else { + var db_tools = $("input[value^='dbtools_']"); + for (var i = 0; i < db_tools.length; i++) { + if (db_tools[i].checked) dbs.push(db_tools[i].value.replace('dbtools_', '')); + } + } + + if (dbs.length < 1) { + layer.msg('请至少选择一张表!', { icon: 2 }); + return false; + } + return dbs; +} + +function repDatabase(db_name, tables) { + dbs = repCheckeds(tables); + + myPost('repair_table', { db_name: db_name, tables: JSON.stringify(dbs) }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + repTools(db_name, true); + },'已送修复指令,请稍候...'); +} + + +function optDatabase(db_name, tables) { + dbs = repCheckeds(tables); + + myPost('opt_table', { db_name: db_name, tables: JSON.stringify(dbs) }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + repTools(db_name, true); + },'已送优化指令,请稍候...'); +} + +function toDatabaseType(db_name, tables, type){ + dbs = repCheckeds(tables); + myPost('alter_table', { db_name: db_name, tables: JSON.stringify(dbs),table_type: type }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + repTools(db_name, true); + }, '已送引擎转换指令,请稍候...'); +} + + +function selectedTools(my_obj, db_name) { + var is_checked = false + + if (my_obj) is_checked = my_obj.checked; + var db_tools = $("input[value^='dbtools_']"); + var n = 0; + for (var i = 0; i < db_tools.length; i++) { + if (my_obj) db_tools[i].checked = is_checked; + if (db_tools[i].checked) n++; + } + if (n > 0) { + var my_btns = '\ + \ + \ + ' + $("#db_tools").html(my_btns); + } else { + $("#db_tools").html(''); + } +} + +function repTools(db_name, res){ + myPost('get_db_info', {name:db_name}, function(data){ + var rdata = $.parseJSON(data.data); + var types = { InnoDB: "MyISAM", MyISAM: "InnoDB" }; + var tbody = ''; + for (var i = 0; i < rdata.tables.length; i++) { + if (!types[rdata.tables[i].type]) continue; + tbody += '\ + \ + ' + rdata.tables[i].table_name + '\ + ' + rdata.tables[i].type + '\ + ' + rdata.tables[i].collation + '\ + ' + rdata.tables[i].rows_count + '\ + ' + rdata.tables[i].data_size + '\ + \ + 修复 |\ + 优化 |\ + 转为' + types[rdata.tables[i].type] + '\ + \ + ' + } + + if (res) { + $(".gztr").html(tbody); + $("#db_tools").html(''); + $("input[type='checkbox']").attr("checked", false); + $(".tools_size").html('大小:' + rdata.data_size); + return; + } + + layer.open({ + type: 1, + title: "MySQL工具箱【" + db_name + "】", + area: ['780px', '580px'], + closeBtn: 1, + shadeClose: false, + content: '
                              \ + \ +
                              \ +
                              \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + ' + tbody + '\ +
                              表名引擎字符集行数大小操作
                              \ +
                              \ +
                              \ +
                                \ +
                              • 【修复】尝试使用REPAIR命令修复损坏的表,仅能做简单修复,若修复不成功请考虑使用myisamchk工具
                              • \ +
                              • 【优化】执行OPTIMIZE命令,可回收未释放的磁盘空间,建议每月执行一次
                              • \ +
                              • 【转为InnoDB/MyISAM】转换数据表引擎,建议将所有表转为InnoDB
                              • \ +
                              ' + }); + tableFixed('database_fix'); + }); +} + + +function setDbMaster(name){ + myPost('set_db_master', {name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + masterOrSlaveConf(); + }, 2000); + }); +} + + +function setDbSlave(name){ + myPost('set_db_slave', {name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + masterOrSlaveConf(); + }, 2000); + }); +} + + +function addMasterRepSlaveUser(){ + layer.open({ + type: 1, + area: '500px', + title: '添加同步账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","取消"], + content: "
                              \ +
                              用户名
                              \ +
                              \ + 密码\ +
                              \ +
                              \ + \ +
                              ", + success:function(){ + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='db_user']").val(v); + $("input[name='ps']").val(v); + }); + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#add_master").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['address']){ + dataObj['address'] = dataObj['dataAccess']; + } + + myPost('add_master_rep_slave_user', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + if (rdata.status){ + getMasterRepSlaveList(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); +} + + + +function updateMasterRepSlaveUser(username, password){ + + var index = layer.open({ + type: 1, + area: '500px', + title: '更新账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + content: "
                              \ +
                              用户名
                              \ +
                              \ + 密码\ +
                              \ +
                              \ + \ +
                              \ + \ +
                              \ +
                              ", + }); + + $('#submit_update_master').click(function(){ + var data = $("#update_master").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + myPost('update_master_rep_slave_user', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getMasterRepSlaveList(); + } + $('.layui-layer-close1').click(); + },{icon: rdata.status ? 1 : 2},600); + }); + }); +} + +function getMasterRepSlaveUserCmd(username, db=''){ + myPost('get_master_rep_slave_user_cmd', {username:username,db:db}, function(data){ + var rdata = $.parseJSON(data.data); + + if (!rdata['status']){ + layer.msg(rdata['msg']); + return; + } + + var cmd = rdata.data['cmd']; + + var loadOpen = layer.open({ + type: 1, + title: '同步命令', + area: '500px', + content:"
                              \ +
                              "+cmd+"
                              \ +
                              \ + \ +
                              \ +
                              ", + }); + }); +} + +function delMasterRepSlaveUser(username){ + myPost('del_master_rep_slave_user', {username:username}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function(){ + getMasterRepSlaveList(); + },{icon: rdata.status ? 1 : 2},1000) + }); +} + + +function setDbMasterAccess(username){ + myPost('get_db_access','username='+username, function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,shade: [0.3, '#000']}); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '设置数据库权限', + closeBtn: 1, + shift: 5, + btn:["提交","取消"], + shadeClose: true, + content: "
                              \ +
                              \ + 访问权限\ +
                              \ + \ +
                              \ +
                              \ +
                              ", + success:function(){ + if (rdata.msg == '127.0.0.1'){ + $('select[name="dataAccess"]').find("option[value='127.0.0.1']").attr("selected",true); + } else if (rdata.msg == '%'){ + $('select[name="dataAccess"]').find('option[value="%"]').attr("selected",true); + } else if ( rdata.msg == 'ip' ){ + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } else { + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#set_db_access").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['access']){ + dataObj['access'] = dataObj['dataAccess']; + if ( dataObj['dataAccess'] == 'ip'){ + if (dataObj['address']==''){ + layer.msg('IP地址不能空!',{icon:2,shade: [0.3, '#000']}); + return; + } + dataObj['access'] = dataObj['address']; + } + } + dataObj['username'] = username; + myPost('set_dbmaster_access', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); + + }); +} + + +function resetMaster(){ + myPost('reset_master', '', function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + },{icon: rdata.status ? 1 : 2}); + },'正在执行重置master命令[reset master]'); +} + +function getMasterRepSlaveList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + myPost('get_master_rep_slave_list', _data, function(data){ + // console.log(data); + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e){ + console.log(e); + } + var list = ''; + // console.log(rdata['data']); + var user_list = rdata['data']; + for (i in user_list) { + // console.log(i); + var name = user_list[i]['username']; + var password = user_list[i]['password']; + list += ''+name+'\ + '+password+'\ + \ + 修改 | \ + 删除 | \ + 权限 | \ + 从库同步命令\ + \ + '; + } + + $('#get_master_rep_slave_list_page tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + +function getMasterRepSlaveListPage(){ + var page = '
                              '; + page += '
                              添加同步账户
                              '; + + var loadOpen = layer.open({ + type: 1, + title: '同步账户列表', + area: '500px', + content:"
                              \ +
                              \ +
                              \ + \ + \ +
                              用户名密码操作
                              \ + "+page +"\ +
                              \ +
                              ", + success:function(){ + getMasterRepSlaveList(); + } + }); +} + + +function deleteSlave(sign){ + myPost('delete_slave', {sign:sign}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata['msg'], function(){ + masterOrSlaveConf(); + },{icon:rdata.status?1:2,time:3000},3000); + }); +} + + +function getFullSyncStatus(db){ + var timeId = null; + + myPost('get_slave_list', {page:1,page_size:100}, function(data){ + var rdata = $.parseJSON(data.data); + var rsource = rdata.data; + + if (db == 'ALL' && rsource.length>1){ + layer.msg("多主不支持该模式!",{icon:2}); + return; + } + + var dataSource = ''; + if (rsource.length>1){ + var sourceList = ''; + for (var i = 0; i < rsource.length; i++) { + if ('Channel_Name' in rsource[i]){ + sourceList += ''; + } + } + + dataSource = "

                              \ + 同步数据源:\ + \ +

                              "; + } + + layer.open({ + type: 1, + title: '全量同步['+db+']', + area: '500px', + content:"
                              \ +
                              \ + "+dataSource+"\ + \ +
                              \ +
                              0%
                              \ +
                              \ +
                              \ +
                              \ + 开始\ + 手动命令\ +
                              \ +
                              ", + cancel: function(){ + clearInterval(timeId); + }, + success:function(){ + $('#begin_full_sync').click(function(){ + var val = $(this).data('status'); + var sign = ''; + if (dataSource !=''){ + sign = $('select[name="data_source"]').val(); + } + if (val == 'init'){ + fullSync(db, sign, 1); + timeId = setInterval(function(){ + fullSync(db,sign,0); + }, 1000); + $(this).data('status','starting'); + $('#begin_full_sync').text("同步中"); + } else { + layer.msg("正在同步中..",{icon:0}); + } + }); + + $('#full_sync_cmd').click(function(){ + myPostN('full_sync_cmd', {'db':db,'sign':''}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.open({ + title: "手动执行命令CMD", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                              \ +
                              \ +
                              '+rdata.data+'
                              \ +
                              \ +
                              ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); + }); + } + }); + }); + + function fullSync(db,sign,begin){ + + myPostN('full_sync', {db:db,sign:sign,begin:begin}, function(data){ + var rdata = $.parseJSON(data.data); + $('#full_msg').text(rdata['msg']); + $('.progress-bar').css('width',rdata['progress']+'%'); + $('.progress-bar').text(rdata['progress']+'%'); + + if (rdata['code']==6 ||rdata['code']<0){ + layer.msg(rdata['msg']); + clearInterval(timeId); + $('#begin_full_sync').text("同步结束,再次同步?"); + $("#begin_full_sync").attr('data-status','init'); + } + }); + } +} + +function dataSyncVerify(db){ + var reqTimer = null; + + function requestLogs(layerIndex){ + myPostN('sync_database_repair_log', {db:db, sign:'',op:'get'}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + + if(!rdata.status) { + layer.close(layerIndex); + layer.msg(rdata.msg,{icon:2, time:2000}); + clearInterval(reqTimer); + return; + }; + + if (rdata.msg == ''){ + rdata.msg = '暂无数据!'; + } + + $("#data_verify_log").html(rdata.msg); + //滚动到最低 + var ob = document.getElementById('data_verify_log'); + ob.scrollTop = ob.scrollHeight; + }); + } + + layer.open({ + type: 1, + title: '同步数据库['+db+']数据校验', + area: '500px', + btn:[ "开始","取消","手动"], + content:"
                              \ + "+'
                              '+"\
                              +            
                              ", + cancel: function(){ + if (reqTimer){ + clearInterval(reqTimer); + } + }, + yes:function(index,layer_index){ + myPostN('sync_database_repair_log', {db:db, sign:'',op:'do'}, function(data){}); + layer.msg("执行成功"); + + requestLogs(layer_index); + reqTimer = setInterval(function(){ + requestLogs(layer_index); + },3000); + }, + success:function(){ + }, + btn3: function(){ + myPostN('sync_database_repair_log', {db:db, sign:'',op:'cmd'}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.open({ + title: "手动执行命令CMD", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                              \ +
                              \ +
                              '+rdata.data+'
                              \ +
                              \ +
                              ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); + return false; + } + + }); +} + +function addSlaveSSH(ip=''){ + + myPost('get_slave_ssh_by_ip', {ip:ip}, function(rdata){ + + var rdata = $.parseJSON(rdata.data); + + var ip = '127.0.0.1'; + var port = "22"; + var id_rsa = ''; + var db_user =''; + + if (rdata.data.length>0){ + ip = rdata.data[0]['ip']; + port = rdata.data[0]['port']; + id_rsa = rdata.data[0]['id_rsa']; + db_user = rdata.data[0]['db_user']; + } + + var index = layer.open({ + type: 1, + area: ['500px','480px'], + title: '添加SSH', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["确认","取消"], + content: "
                              \ +
                              IP
                              \ +
                              端口
                              \ +
                              同步账户[DB]
                              \ +
                              \ + ID_RSA\ +
                              \ +
                              \ + \ +
                              ", + success:function(){ + $('textarea[name="id_rsa"]').html(id_rsa); + }, + yes:function(index){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var db_user = $('input[name="db_user"]').val(); + var id_rsa = $('textarea[name="id_rsa"]').val(); + + var data = {ip:ip,port:port,id_rsa:id_rsa,db_user:db_user}; + myPost('add_slave_ssh', data, function(data){ + layer.close(index); + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSSHPage(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); + }); +} + + +function delSlaveSSH(ip){ + myPost('del_slave_ssh', {ip:ip}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSSHPage(); + } + },{icon: rdata.status ? 1 : 2}, 600); + }); +} + + +function delSlaveSyncUser(ip){ + myPost('del_slave_sync_user', {ip:ip}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSyncUserPage(); + } + },{icon: rdata.status ? 1 : 2}, 600); + }); +} + +function getSlaveSSHPage(page=1){ + var _data = {}; + _data['page'] = page; + _data['page_size'] = 5; + _data['tojs'] ='getSlaveSSHPage'; + myPost('get_slave_ssh_list', _data, function(data){ + var layerId = null; + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e) { + console.log(e); + } + var list = ''; + var ssh_list = rdata['data']; + for (i in ssh_list) { + var ip = ssh_list[i]['ip']; + var port = ssh_list[i]['port']; + + var id_rsa = '未设置'; + if ( ssh_list[i]['port'] != ''){ + id_rsa = '已设置'; + } + + var db_user = '未设置'; + if ( ssh_list[i]['db_user'] != ''){ + db_user = ssh_list[i]['db_user']; + } + + list += ''+ip+'\ + '+port+'\ + '+db_user+'\ + '+id_rsa+'\ + \ + 修改 | \ + 删除\ + \ + '; + } + + $('.get-slave-ssh-list tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + + + +function addSlaveSyncUser(ip=''){ + + myPost('get_slave_sync_user_by_ip', {ip:ip}, function(rdata){ + + var rdata = $.parseJSON(rdata.data); + + var ip = '127.0.0.1'; + var port = "22"; + var cmd = ''; + var user = 'input_sync_user'; + var pass = 'input_sync_pwd'; + var mode = '0'; + + if (rdata.data.length>0){ + ip = rdata.data[0]['ip']; + port = rdata.data[0]['port']; + cmd = rdata.data[0]['cmd']; + user = rdata.data[0]['user']; + pass = rdata.data[0]['pass']; + mode = rdata.data[0]['mode']; + } + + var index = layer.open({ + type: 1, + area: ['500px','510px'], + title: '同步账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["确认","取消"], + content: "
                              \ +
                              IP
                              \ +
                              端口
                              \ +
                              同步账户
                              \ +
                              同步密码
                              \ +
                              \ + 同步模式\ +
                              \ + \ +
                              \ +
                              \ +
                              \ + CMD[必填]\ +
                              \ +
                              \ + \ +
                              ", + success:function(){ + $('textarea[name="cmd"]').html(cmd); + $('textarea[name="cmd"]').change(function(){ + var val = $(this).val(); + val = val.replace(';',''); + var a = {}; + if (val.toLowerCase().indexOf('for')>0){ + cmd_tmp = val.split('for'); + val = cmd_tmp[0].trim(); + + const channel_str = cmd_tmp[1].trim(); + const ch_reg = /channel \'(.*)\';/; + var match_val = channel_str.match(ch_reg); + if (match_val.length>1){ + a['channel'] = match_val[1]; + } + } + + var vlist = val.split(','); + for (var i in vlist) { + var tmp = toTrim(vlist[i]); + var tmp_a = tmp.split(" "); + var real_tmp = tmp_a[tmp_a.length-1]; + var kv = real_tmp.split("="); + a[kv[0]] = kv[1].replace("'",'').replace("'",''); + } + + if ('MASTER_HOST' in a){ + $('input[name="ip"]').val(a['MASTER_HOST']); + $('input[name="port"]').val(a['MASTER_PORT']); + $('input[name="user"]').val(a['MASTER_USER']); + $('input[name="pass"]').val(a['MASTER_PASSWORD']); + } else { + $('input[name="ip"]').val(a['SOURCE_HOST']); + $('input[name="port"]').val(a['SOURCE_PORT']); + $('input[name="user"]').val(a['SOURCE_USER']); + $('input[name="pass"]').val(a['SOURCE_PASSWORD']); + } + + }); + }, + yes:function(index){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var user = $('input[name="user"]').val(); + var pass = $('input[name="pass"]').val(); + var cmd = $('textarea[name="cmd"]').val(); + var mode = $('select[name="mode"]').val(); + + var data = {ip:ip,port:port,cmd:cmd,user:user,pass:pass,mode:mode}; + myPost('add_slave_sync_user', data, function(ret_data){ + layer.close(index); + var rdata = $.parseJSON(ret_data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSyncUserPage(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); + }); +} + +function getSlaveSyncUserPage(page=1){ + var _data = {}; + _data['page'] = page; + _data['page_size'] = 5; + _data['tojs'] ='getSlaveSyncUserPage'; + myPost('get_slave_sync_user_list', _data, function(data){ + var layerId = null; + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e) { + console.log(e); + } + + var list = ''; + var user_list = rdata['data']; + for (i in user_list) { + var ip = user_list[i]['ip']; + var port = user_list[i]['port']; + var user = user_list[i]['user']; + var apass = user_list[i]['pass']; + + var cmd = '未设置'; + if (user_list[i]['cmd']!=''){ + cmd = '已设置'; + } + + list += ''+ip+'\ + '+port+'\ + '+user+'\ + '+apass+'\ + '+cmd+'\ + \ + 修改 | \ + 删除\ + \ + '; + } + + $('.get-slave-ssh-list tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + +function getSlaveCfg(){ + + myPost('get_slave_sync_mode', '', function(data){ + var rdata = $.parseJSON(data.data); + var mode_none = 'success'; + var mode_ssh = 'danger'; + var mode_sync_user = 'danger'; + if(rdata.status){ + var mode_none = 'danger'; + if (rdata.data == 'ssh'){ + var mode_ssh = 'success'; + var mode_sync_user = 'danger'; + } else { + var mode_ssh = 'danger'; + var mode_sync_user = 'success'; + } + } + + layerId = layer.open({ + type: 1, + title: '同步配置', + area: ['400px','180px'], + content:"
                              \ +

                              \ + 当前从库同步模式\ + \ + \ + \ + \ +

                              \ +
                              \ +

                              \ + 配置设置\ + \ + \ + \ +

                              \ +
                              ", + success:function(){ + $('.btn-slave-ssh').click(function(){ + getSlaveSSHList(); + }); + + $('.btn-slave-user').click(function(){ + getSlaveUserList(); + }); + + $('.slave-db-mode').click(function(){ + var _this = this; + var mode = 'none'; + if ($(this).hasClass('btn-ssh')){ + mode = 'ssh'; + } + if ($(this).hasClass('btn-sync-user')){ + mode = 'sync-user'; + } + + myPost('set_slave_sync_mode', {mode:mode}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + $('.slave-db-mode').remove('btn-success').addClass('btn-danger'); + $(_this).removeClass('btn-danger').addClass('btn-success'); + },{icon:rdata.status?1:2},2000); + }); + + }); + } + }); + }); +} + + +function getSlaveUserList(){ + + var page = '
                              '; + page += '
                              添加同步账户
                              '; + + layerId = layer.open({ + type: 1, + title: '同步账户列表', + area: '600px', + content:"
                              \ +
                              \ +
                              \ + \ + \ +
                              IPPORT同步账户同步密码CMD操作
                              \ + "+page +"\ +
                              \ +
                              ", + success:function(){ + getSlaveSyncUserPage(1); + } + }); +} + +function getSlaveSSHList(page=1){ + + var page = '
                              '; + page += '
                              添加SSH
                              '; + + layerId = layer.open({ + type: 1, + title: 'SSH列表', + area: '600px', + content:"
                              \ +
                              \ +
                              \ + \ + \ +
                              IPPORT同步账户SSH操作
                              \ + "+page +"\ +
                              \ +
                              ", + success:function(){ + getSlaveSSHPage(1); + } + }); +} + +function handlerRun(){ + myPostN('get_slave_sync_cmd', {}, function(data){ + var rdata = $.parseJSON(data.data); + var cmd = rdata['data']; + var loadOpen = layer.open({ + type: 1, + title: '手动执行', + area: '500px', + content:"
                              \ +
                              "+cmd+"
                              \ +
                              \ + \ +
                              \ +
                              ", + }); + copyPass(cmd); + $('.class-copy-cmd').click(function(){ + copyPass(cmd); + }); + }); +} + +function initSlaveStatus(){ + myPost('init_slave_status', '', function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + masterOrSlaveConf(); + } + },{icon:rdata.status?1:2},2000); + }); +} + +function masterOrSlaveConf(version=''){ + + function getMasterDbList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + + myPost('get_masterdb_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list += '' + rdata.data[i]['name'] +''; + list += '' + (rdata.data[i]['master']?'是':'否') +''; + list += '' + + ''+(rdata.data[i]['master']?'退出':'加入')+' | ' + + '同步命令' + + ''; + list += ''; + } + + var con = '
                              \ +
                              \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                              数据库名同步操作
                              \ +
                              \ +
                              \ +
                              \ + 同步账户列表\ +
                              \ +
                              '; + + $(".table_master_list").html(con); + $('#databasePage').html(rdata.page); + }); + } + + function getAsyncMasterDbList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + + var mdb_ver = $('.plugin_version').attr('version'); + + myPost('get_slave_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + + var isHasSign = false; + for(i in rdata.data){ + + var v = rdata.data[i]; + if ('Channel_Name' in v && v['Channel_Name'] !=''){ + isHasSign = true; + } + + var status = "异常"; + if (mdb_ver >= 8){ + + if (v['Replica_SQL_Running'] == 'Yes' && v['Replica_IO_Running'] == 'Yes'){ + status = "正常"; + } + + list += ''; + list += '' + rdata.data[i]['Source_Host'] +''; + list += '' + rdata.data[i]['Source_Port'] +''; + list += '' + rdata.data[i]['Source_User'] +''; + list += '' + rdata.data[i]['Relay_Source_Log_File'] +''; + list += '' + rdata.data[i]['Replica_IO_Running'] +''; + list += '' + rdata.data[i]['Replica_SQL_Running'] +''; + + } else { + if (v['Slave_SQL_Running'] == 'Yes' && v['Slave_IO_Running'] == 'Yes'){ + status = "正常"; + } + + list += ''; + list += '' + rdata.data[i]['Master_Host'] +''; + list += '' + rdata.data[i]['Master_Port'] +''; + list += '' + rdata.data[i]['Master_User'] +''; + list += '' + rdata.data[i]['Master_Log_File'] +''; + list += '' + rdata.data[i]['Slave_IO_Running'] +''; + list += '' + rdata.data[i]['Slave_SQL_Running'] +''; + } + + + if (isHasSign){ + list += '' + v['Channel_Name'] +''; + } + + list += '' + status +''; + list += '' + + '删除' + + ''; + list += ''; + } + + var signThead_th = ''; + if (isHasSign){ + var signThead_th = '标识'; + } + + var con = '
                              \ +
                              \ + \ + \ + \ + \ + \ + \ + \ + \ + '+signThead_th+'\ + \ + \ + \ + '+ list +'\ +
                              主[服务]端口用户日志IOSQL状态操作
                              \ +
                              \ +
                              '; + + //
                              \ + //
                              \ + // 添加\ + //
                              + $(".table_slave_status_list").html(con); + + $(".btn_delete_slave").click(function(){ + var id = $(this).data('id'); + var v = rdata.data[id]; + if ('Channel_Name' in v){ + deleteSlave(v['Channel_Name']); + } else{ + deleteSlave(); + } + }); + + $('.db_error').click(function(){ + var id = $(this).data('id'); + var info = rdata.data[id]; + + var err_line = ""; + err_line +="\ + IO错误\ + "+ (info['Last_IO_Error'] == '' ? '无':info['Last_IO_Error'])+"\ + "; + err_line +="\ + SQL错误\ + "+(info['Last_SQL_Error'] == '' ? '无':info['Last_SQL_Error'])+"\ + "; + + err_line +="\ + 状态\ + "+(info['Slave_SQL_Running_State'] == '' ? '无':info['Slave_SQL_Running_State']) +"\ + "; + + + var btn_list = ['复制错误',"取消"]; + if (info['Last_IO_Error'].search(/1236/i)>0){ + btn_list = ['复制错误',"取消","尝试修复"]; + } + layer.open({ + type: 1, + title: '同步异常信息', + area: ['600px','300px'], + btn:btn_list, + content:"
                              \ +
                              \ +
                              \ + \ + \ + \ + \ + \ + "+ err_line +"\ +
                              类型内容
                              \ +
                              \ +
                              \ +
                              ", + success:function(){ + if (info['Last_IO_Error'] != ''){ + copyText(info['Last_IO_Error']); + return; + } + + if (info['Last_SQL_Error'] != ''){ + copyText(info['Last_SQL_Error']); + return; + } + + if (info['Slave_SQL_Running_State'] != ''){ + copyText(info['Slave_SQL_Running_State']); + return; + } + }, + yes:function(){ + if (info['Last_IO_Error'] != ''){ + copyText(info['Last_IO_Error']); + return; + } + + if (info['Last_SQL_Error'] != ''){ + copyText(info['Last_SQL_Error']); + return; + } + + if (info['Slave_SQL_Running_State'] != ''){ + copyText(info['Slave_SQL_Running_State']); + return; + } + }, + btn3:function(){ + myPost('try_slave_sync_bugfix', {}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function(){ + masterOrSlaveConf(); + },{ icon: rdata.status ? 1 : 5 },2000); + }); + } + }); + }); + }); + } + + function getAsyncDataList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + myPost('get_masterdb_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list += '' + rdata.data[i]['name'] +''; + list += '' + + ''+(rdata.data[i]['slave']?'退出':'加入')+' | ' + + '同步 | ' + + '数据校验' + + ''; + list += ''; + } + + var con = '
                              \ +
                              \ + \ + \ + \ + \ + \ + '+ list +'\ +
                              本地库名操作
                              \ +
                              \ +
                              \ +
                              \ + 手动命令\ + 全量同步\ +
                              \ +
                              '; + + $(".table_slave_list").html(con); + $('#databasePage').html(rdata.page); + }); + } + + + function getMasterStatus(){ + myPost('get_master_status', '', function(rdata){ + var rdata = $.parseJSON(rdata.data); + // console.log('mode:',rdata.data); + if ( typeof(rdata.status) != 'undefined' && !rdata.status && rdata.data == 'pwd'){ + layer.msg(rdata.msg, {icon:2}); + return; + } + + var rdata = rdata.data; + var limitCon = '\ +

                              \ + 主从同步模式\ + \ + \ +

                              \ +
                              \ +

                              \ + Master[主]配置\ + \ + \ +

                              \ +
                              \ + \ +
                              \ +
                              \ + \ +

                              \ + Slave[从]配置\ + \ + \ + \ +

                              \ +
                              \ + \ +
                              \ + \ +
                              \ + '; + $(".soft-man-con").html(limitCon); + + //设置主服务器配置 + $(".btn-master").click(function () { + myPost('set_master_status', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + getMasterStatus(); + }, 3000); + }); + }); + + $(".btn-slave").click(function () { + myPost('set_slave_status', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + getMasterStatus(); + }, 3000); + }); + }); + + $('.db-mode').click(function(){ + if ($(this).hasClass('btn-success')){ + //no action + return; + } + + var mode = 'classic'; + if ($(this).hasClass('btn-gtid')){ + mode = 'gtid'; + } + + layer.open({ + type:1, + title:"MySQL主从模式切换", + shadeClose:false, + btnAlign: 'c', + btn: ['切换并重启', '切换不重启'], + yes: function(index, layero){ + this.change(index,mode,"yes"); + + }, + btn2: function(index, layero){ + this.change(index,mode,"no"); + return false; + }, + change:function(index,mode,reload){ + console.log(index,mode,reload); + myPost('set_dbrun_mode',{'mode':mode,'reload':reload},function(data){ + layer.close(index); + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg ,function(){ + getMasterStatus(); + },{ icon: rdata.status ? 1 : 5 }); + }); + } + }); + }); + + if (rdata.status){ + getMasterDbList(); + } + + // if (rdata.slave_status){ + getAsyncMasterDbList(); + getAsyncDataList() + // } + }); + } + getMasterStatus(); +} diff --git a/plugins/mysql-community/scripts/backup.py b/plugins/mysql-community/scripts/backup.py new file mode 100755 index 000000000..a8073aa00 --- /dev/null +++ b/plugins/mysql-community/scripts/backup.py @@ -0,0 +1,116 @@ +# coding: utf-8 +#----------------------------- +# 网站备份工具 +#----------------------------- + +import sys +import os +import time +import re + +if sys.platform != 'darwin': + os.chdir('/www/server/mdserver-web') + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import core.db as db + +''' +DEBUG: +python3 /www/server/mdserver-web/plugins/mysql-community/scripts/backup.py database admin 3 +''' +class backupTools: + + def backupDatabase(self, name, count): + db_path = mw.getServerDir() + '/mysql-community' + db_name = 'mysql' + find_name = mw.M('databases').dbPos(db_path, 'mysql').where( + 'name=?', (name,)).getField('name') + startTime = time.time() + if not find_name: + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + log = "数据库[" + name + "]不存在!" + print("★[" + endDate + "] " + log) + print( + "----------------------------------------------------------------------------") + return + + backup_path = mw.getFatherDir() + '/backup/database/mysql-community' + if not os.path.exists(backup_path): + mw.execShell("mkdir -p " + backup_path) + + filename = backup_path + "/db_" + name + "_" + \ + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + ".sql.gz" + + mysql_root = mw.M('config').dbPos(db_path, db_name).where( + "id=?", (1,)).getField('mysql_root') + + my_conf_path = db_path + '/etc/my.cnf' + mycnf = mw.readFile(my_conf_path) + rep = r"\[mysqldump\]\nuser=root" + sea = "[mysqldump]\n" + subStr = sea + "user=root\npassword=" + mysql_root + "\n" + mycnf = mycnf.replace(sea, subStr) + if len(mycnf) > 100: + mw.writeFile(db_path + '/etc/my.cnf', mycnf) + + cmd = db_path + "/bin/usr/bin/mysqldump --defaults-file=" + my_conf_path + " --single-transaction -q --default-character-set=utf8mb4 " + name + " | gzip > " + filename + mw.execShell(cmd) + + if not os.path.exists(filename): + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + log = "数据库[" + name + "]备份失败!" + print("★[" + endDate + "] " + log) + print( + "----------------------------------------------------------------------------") + return + + mycnf = mw.readFile(db_path + '/etc/my.cnf') + mycnf = mycnf.replace(subStr, sea) + if len(mycnf) > 100: + mw.writeFile(db_path + '/etc/my.cnf', mycnf) + + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + outTime = time.time() - startTime + pid = mw.M('databases').dbPos(db_path, db_name).where('name=?', (name,)).getField('id') + + mw.M('backup').add('type,name,pid,filename,add_time,size', (1, os.path.basename(filename), pid, filename, endDate, os.path.getsize(filename))) + log = "数据库[" + name + "]备份成功,用时[" + str(round(outTime, 2)) + "]秒" + mw.writeLog('计划任务', log) + print("★[" + endDate + "] " + log) + print("|---保留最新的[" + count + "]份备份") + print("|---文件名:" + filename) + + # 清理多余备份 + backups = mw.M('backup').where('type=? and pid=?', ('1', pid)).field('id,filename').select() + + num = len(backups) - int(count) + if num > 0: + for backup in backups: + mw.execShell("rm -f " + backup['filename']) + mw.M('backup').where('id=?', (backup['id'],)).delete() + num -= 1 + print("|---已清理过期备份文件:" + backup['filename']) + if num < 1: + break + + def backupDatabaseAll(self, save): + db_path = mw.getServerDir() + '/mysql-community' + db_name = 'mysql' + databases = mw.M('databases').dbPos(db_path, db_name).field('name').select() + for database in databases: + self.backupDatabase(database['name'], save) + + +if __name__ == "__main__": + backup = backupTools() + type = sys.argv[1] + if type == 'database': + if sys.argv[2] == 'ALL': + backup.backupDatabaseAll(sys.argv[3]) + else: + backup.backupDatabase(sys.argv[2], sys.argv[3]) diff --git a/plugins/mysql-community/versions/5.7/install_generic.sh b/plugins/mysql-community/versions/5.7/install_generic.sh new file mode 100755 index 000000000..9cb55ed5c --- /dev/null +++ b/plugins/mysql-community/versions/5.7/install_generic.sh @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +# https://downloads.mysql.com/archives/community/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +myDir=${serverPath}/source/mysql-community + +OS_ARCH=`arch` +MYSQL_VER=5.7.44 +SUFFIX_NAME=${MYSQL_VER}-linux-glibc2.12-${OS_ARCH} + +if [ "$OS_ARCH" == "x86_64" ] || [ "$OS_ARCH" == "i686" ];then + echo "ok" +else + echo "暂时不支持该${OS_ARCH}" + exit 0 +fi + +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 5.7 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mysql-community/index.py start 5.7 +COMMUNITY_INSTALL() +{ + +######## +mkdir -p $myDir +mkdir -p $serverPath/mysql-community + +# Linux - Generic +if [ ! -f ${myDir}/mysql-${SUFFIX_NAME}.tar.gz ];then + wget --no-check-certificate -O ${myDir}/mysql-${SUFFIX_NAME}.tar.gz https://cdn.mysql.com/archives/mysql-5.7/mysql-${SUFFIX_NAME}.tar.gz +fi + +if [ -d ${myDir} ];then + cd ${myDir} && tar -zvxf ${myDir}/mysql-${SUFFIX_NAME}.tar.gz + cp -rf ${myDir}/mysql-${SUFFIX_NAME}/* $serverPath/mysql-community +fi + +# 测试时可关闭 +rm -rf $myDir/mysql-${SUFFIX_NAME} +####### +} + +COMMUNITY_UNINSTALL() +{ +### +rm -rf $myDir/mysql-${SUFFIX_NAME} +### +} + + +Install_mysql() +{ + echo '正在安装脚本文件...' + COMMUNITY_INSTALL + if [ "$?" == "0" ];then + mkdir -p $serverPath/mysql-community + echo '5.7' > $serverPath/mysql-community/version.pl + echo '安装完成' + else + echo '5.7' > $serverPath/mysql-community/version.pl + echo "暂时不支持该系统" + fi +} + +Uninstall_mysql() +{ + COMMUNITY_UNINSTALL + rm -rf $serverPath/mysql-community + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql-community/versions/8.0/install_generic.sh b/plugins/mysql-community/versions/8.0/install_generic.sh new file mode 100755 index 000000000..f0debfa91 --- /dev/null +++ b/plugins/mysql-community/versions/8.0/install_generic.sh @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +# https://downloads.mysql.com/archives/community/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +myDir=${serverPath}/source/mysql-community + +OS_ARCH=`arch` +MYSQL_VER=8.0.39 +SUFFIX_NAME=${MYSQL_VER}-linux-glibc2.28-${OS_ARCH} + +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 8.0 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mysql-community/index.py start 8.0 +COMMUNITY_INSTALL() +{ + +######## +mkdir -p $myDir +mkdir -p $serverPath/mysql-community + +# Linux - Generic +if [ ! -f ${myDir}/mysql-${SUFFIX_NAME}.tar.xz ];then + wget --no-check-certificate -O ${myDir}/mysql-${SUFFIX_NAME}.tar.xz https://cdn.mysql.com/archives/mysql-8.0/mysql-${SUFFIX_NAME}.tar.xz +fi + +if [ -d ${myDir} ];then + cd ${myDir} && tar -Jxf ${myDir}/mysql-${SUFFIX_NAME}.tar.xz + cp -rf ${myDir}/mysql-${SUFFIX_NAME}/* $serverPath/mysql-community +fi + +# 测试时可关闭 +rm -rf $myDir/mysql-${SUFFIX_NAME} +####### +} + +COMMUNITY_UNINSTALL() +{ +### +rm -rf $myDir/mysql-${SUFFIX_NAME} +### +} + + +Install_mysql() +{ + echo '正在安装脚本文件...' + + COMMUNITY_INSTALL + + if [ "$?" == "0" ];then + mkdir -p $serverPath/mysql-community + echo '8.0' > $serverPath/mysql-community/version.pl + echo '安装完成' + else + echo '8.0' > $serverPath/mysql-community/version.pl + echo "暂时不支持该系统" + fi +} + +Uninstall_mysql() +{ + COMMUNITY_UNINSTALL + + rm -rf $serverPath/mysql-community + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql-community/versions/8.2/install_generic.sh b/plugins/mysql-community/versions/8.2/install_generic.sh new file mode 100755 index 000000000..d2cb381d3 --- /dev/null +++ b/plugins/mysql-community/versions/8.2/install_generic.sh @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +# https://downloads.mysql.com/archives/community/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +myDir=${serverPath}/source/mysql-community + +OS_ARCH=`arch` +MYSQL_VER=8.2.0 +SUFFIX_NAME=${MYSQL_VER}-linux-glibc2.28-${OS_ARCH} + +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 8.2 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mysql-community/index.py start 8.2 +COMMUNITY_INSTALL() +{ + +######## +mkdir -p $myDir +mkdir -p $serverPath/mysql-community + +# Linux - Generic +if [ ! -f ${myDir}/mysql-${SUFFIX_NAME}.tar.xz ];then + wget --no-check-certificate -O ${myDir}/mysql-${SUFFIX_NAME}.tar.xz https://cdn.mysql.com/archives/mysql-8.2/mysql-${SUFFIX_NAME}.tar.xz +fi + +if [ -d ${myDir} ];then + cd ${myDir} && tar -Jxf ${myDir}/mysql-${SUFFIX_NAME}.tar.xz + cp -rf ${myDir}/mysql-${SUFFIX_NAME}/* $serverPath/mysql-community +fi + +# 测试时可关闭 +rm -rf $myDir/mysql-${SUFFIX_NAME} +####### +} + +COMMUNITY_UNINSTALL() +{ +### +rm -rf $myDir/mysql-${SUFFIX_NAME} +### +} + + +Install_mysql() +{ + echo '正在安装脚本文件...' + COMMUNITY_INSTALL + + if [ "$?" == "0" ];then + mkdir -p $serverPath/mysql-community + echo '8.2' > $serverPath/mysql-community/version.pl + echo '安装完成' + else + echo '8.2' > $serverPath/mysql-community/version.pl + echo "暂时不支持该系统" + fi +} + +Uninstall_mysql() +{ + COMMUNITY_UNINSTALL + rm -rf $serverPath/mysql-community + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql-community/versions/8.3/install_generic.sh b/plugins/mysql-community/versions/8.3/install_generic.sh new file mode 100755 index 000000000..67debcca4 --- /dev/null +++ b/plugins/mysql-community/versions/8.3/install_generic.sh @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +# https://downloads.mysql.com/archives/community/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +myDir=${serverPath}/source/mysql-community + +OS_ARCH=`arch` +MYSQL_VER=8.3.0 +SUFFIX_NAME=${MYSQL_VER}-linux-glibc2.28-${OS_ARCH} + +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 8.3 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mysql-community/index.py start 8.3 +COMMUNITY_INSTALL() +{ + +######## +mkdir -p $myDir +mkdir -p $serverPath/mysql-community + +# Linux - Generic +if [ ! -f ${myDir}/mysql-${SUFFIX_NAME}.tar.xz ];then + wget --no-check-certificate -O ${myDir}/mysql-${SUFFIX_NAME}.tar.xz https://cdn.mysql.com/archives/mysql-8.3/mysql-${SUFFIX_NAME}.tar.xz +fi + +if [ -d ${myDir} ];then + cd ${myDir} && tar -Jxf ${myDir}/mysql-${SUFFIX_NAME}.tar.xz + cp -rf ${myDir}/mysql-${SUFFIX_NAME}/* $serverPath/mysql-community +fi + +# 测试时可关闭 +rm -rf $myDir/mysql-${SUFFIX_NAME} +####### +} + +COMMUNITY_UNINSTALL() +{ +### +rm -rf $myDir/mysql-${SUFFIX_NAME} +### +} + + +Install_mysql() +{ + echo '正在安装脚本文件...' + COMMUNITY_INSTALL + if [ "$?" == "0" ];then + mkdir -p $serverPath/mysql-community + echo '8.3' > $serverPath/mysql-community/version.pl + echo '安装完成' + else + echo '8.3' > $serverPath/mysql-community/version.pl + echo "暂时不支持该系统" + fi +} + +Uninstall_mysql() +{ + + COMMUNITY_UNINSTALL + rm -rf $serverPath/mysql-APT_INSTALL + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql-community/versions/8.4/install_generic.sh b/plugins/mysql-community/versions/8.4/install_generic.sh new file mode 100755 index 000000000..261e19a58 --- /dev/null +++ b/plugins/mysql-community/versions/8.4/install_generic.sh @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +# https://downloads.mysql.com/archives/community/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +myDir=${serverPath}/source/mysql-community + +OS_ARCH=`arch` +MYSQL_VER=8.4.2 +SUFFIX_NAME=${MYSQL_VER}-linux-glibc2.28-${OS_ARCH} + +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 8.4 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mysql-community/index.py start 8.4 +COMMUNITY_INSTALL() +{ +######## +mkdir -p $myDir +mkdir -p $serverPath/mysql-community + +# Linux - Generic +# https://cdn.mysql.com/archives/mysql-8.4/mysql-8.4.2-linux-glibc2.28-x86_64.tar.xz +if [ ! -f ${myDir}/mysql-${SUFFIX_NAME}.tar.xz ];then + wget --no-check-certificate -O ${myDir}/mysql-${SUFFIX_NAME}.tar.xz https://cdn.mysql.com/archives/mysql-8.4/mysql-${SUFFIX_NAME}.tar.xz +fi + +if [ -d ${myDir} ];then + cd ${myDir} && tar -Jxf ${myDir}/mysql-${SUFFIX_NAME}.tar.xz + cp -rf ${myDir}/mysql-${SUFFIX_NAME}/* $serverPath/mysql-community +fi + +# 测试时可关闭 +rm -rf $myDir/mysql-${SUFFIX_NAME} +####### +} + +COMMUNITY_UNINSTALL() +{ +### +rm -rf $myDir/mysql-${SUFFIX_NAME} +### +} + + +Install_mysql() +{ + echo '正在安装脚本文件...' + COMMUNITY_INSTALL + if [ "$?" == "0" ];then + mkdir -p $serverPath/mysql-community + echo '8.4' > $serverPath/mysql-community/version.pl + echo '安装完成' + else + echo '8.4' > $serverPath/mysql-community/version.pl + echo "暂时不支持该系统" + fi +} + +Uninstall_mysql() +{ + COMMUNITY_UNINSTALL + rm -rf $serverPath/mysql-community + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql-community/versions/9.0/install_generic.sh b/plugins/mysql-community/versions/9.0/install_generic.sh new file mode 100755 index 000000000..cdf5c915e --- /dev/null +++ b/plugins/mysql-community/versions/9.0/install_generic.sh @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +# https://downloads.mysql.com/archives/community/ +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +myDir=${serverPath}/source/mysql-community + +OS_ARCH=`arch` +MYSQL_VER=9.0.1 +SUFFIX_NAME=${MYSQL_VER}-linux-glibc2.28-${OS_ARCH} + +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 9.0 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mysql-community/index.py start 9.0 +COMMUNITY_INSTALL() +{ + +######## +mkdir -p $myDir +mkdir -p $serverPath/mysql-community + +# Linux - Generic +# https://cdn.mysql.com/archives/mysql-9.0/mysql-9.0.1-linux-glibc2.28-x86_64.tar.xz +if [ ! -f ${myDir}/mysql-${SUFFIX_NAME}.tar.xz ];then + wget --no-check-certificate -O ${myDir}/mysql-${SUFFIX_NAME}.tar.xz https://cdn.mysql.com/archives/mysql-9.0/mysql-${SUFFIX_NAME}.tar.xz +fi + +if [ -d ${myDir} ];then + cd ${myDir} && tar -Jxf ${myDir}/mysql-${SUFFIX_NAME}.tar.xz + cp -rf ${myDir}/mysql-${SUFFIX_NAME}/* $serverPath/mysql-community +fi + +# 测试时可关闭 +rm -rf $myDir/mysql-${SUFFIX_NAME} +####### +} + +COMMUNITY_UNINSTALL() +{ +### +rm -rf $myDir/mysql-${SUFFIX_NAME} +### +} + + +Install_mysql() +{ + echo '正在安装脚本文件...' + COMMUNITY_INSTALL + if [ "$?" == "0" ];then + mkdir -p $serverPath/mysql-community + echo '9.0' > $serverPath/mysql-community/version.pl + echo '安装完成' + else + echo '9.0' > $serverPath/mysql-community/version.pl + echo "暂时不支持该系统" + fi +} + +Uninstall_mysql() +{ + COMMUNITY_UNINSTALL + rm -rf $serverPath/mysql-community + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql-community/versions/9.1/install_generic.sh b/plugins/mysql-community/versions/9.1/install_generic.sh new file mode 100755 index 000000000..382e39040 --- /dev/null +++ b/plugins/mysql-community/versions/9.1/install_generic.sh @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +# https://downloads.mysql.com/archives/community/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +myDir=${serverPath}/source/mysql-community + +OS_ARCH=`arch` +MYSQL_VER=9.1.0 +SUFFIX_NAME=${MYSQL_VER}-linux-glibc2.28-${OS_ARCH} + +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 9.1 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mysql-community/index.py start 9.1 +COMMUNITY_INSTALL() +{ + +######## +mkdir -p $myDir +mkdir -p $serverPath/mysql-community + +# Linux - Generic +# https://cdn.mysql.com/archives/mysql-8.4/mysql-8.4.2-linux-glibc2.28-x86_64.tar.xz +# https://cdn.mysql.com/Downloads/MySQL-9.1/mysql-${SUFFIX_NAME}.tar.xz +if [ ! -f ${myDir}/mysql-${SUFFIX_NAME}.tar.xz ];then + wget --no-check-certificate -O ${myDir}/mysql-${SUFFIX_NAME}.tar.xz https://cdn.mysql.com/Downloads/MySQL-9.1/mysql-${SUFFIX_NAME}.tar.xz +fi + +if [ -d ${myDir} ];then + cd ${myDir} && tar -Jxf ${myDir}/mysql-${SUFFIX_NAME}.tar.xz + cp -rf ${myDir}/mysql-${SUFFIX_NAME}/* $serverPath/mysql-community +fi + +# 测试时可关闭 +rm -rf $myDir/mysql-${SUFFIX_NAME} +####### +} + +COMMUNITY_UNINSTALL() +{ +### +rm -rf $myDir/mysql-${SUFFIX_NAME} +# apt remove -y mysql-server +### +} + + +Install_mysql() +{ + echo '正在安装脚本文件...' + COMMUNITY_INSTALL + if [ "$?" == "0" ];then + mkdir -p $serverPath/mysql-community + echo '9.1' > $serverPath/mysql-community/version.pl + echo '安装完成' + else + echo '9.1' > $serverPath/mysql-community/version.pl + echo "暂时不支持该系统" + fi +} + +Uninstall_mysql() +{ + COMMUNITY_UNINSTALL + rm -rf $serverPath/mysql-community + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql-community/versions/9.2/install_generic.sh b/plugins/mysql-community/versions/9.2/install_generic.sh new file mode 100755 index 000000000..ccb6d88b4 --- /dev/null +++ b/plugins/mysql-community/versions/9.2/install_generic.sh @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +# https://downloads.mysql.com/archives/community/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +myDir=${serverPath}/source/mysql-community + +OS_ARCH=`arch` +MYSQL_VER=9.2.0 +SUFFIX_NAME=${MYSQL_VER}-linux-glibc2.28-${OS_ARCH} + +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 9.2 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mysql-community/index.py start 9.2 +COMMUNITY_INSTALL() +{ + +######## +mkdir -p $myDir +mkdir -p $serverPath/mysql-community + +# Linux - Generic +# https://cdn.mysql.com/archives/mysql-9.2/mysql-8.4.2-linux-glibc2.28-x86_64.tar.xz +# https://cdn.mysql.com/Downloads/MySQL-9.2/mysql-${SUFFIX_NAME}.tar.xz +if [ ! -f ${myDir}/mysql-${SUFFIX_NAME}.tar.xz ];then + wget --no-check-certificate -O ${myDir}/mysql-${SUFFIX_NAME}.tar.xz https://cdn.mysql.com/Downloads/MySQL-9.2/mysql-${SUFFIX_NAME}.tar.xz +fi + +if [ -d ${myDir} ];then + cd ${myDir} && tar -Jxf ${myDir}/mysql-${SUFFIX_NAME}.tar.xz + cp -rf ${myDir}/mysql-${SUFFIX_NAME}/* $serverPath/mysql-community +fi + +# 测试时可关闭 +rm -rf $myDir/mysql-${SUFFIX_NAME} +####### +} + +COMMUNITY_UNINSTALL() +{ +### +rm -rf $myDir/mysql-${SUFFIX_NAME} +# apt remove -y mysql-server +### +} + + +Install_mysql() +{ + echo '正在安装脚本文件...' + COMMUNITY_INSTALL + if [ "$?" == "0" ];then + mkdir -p $serverPath/mysql-community + echo '9.2' > $serverPath/mysql-community/version.pl + echo '安装完成' + else + echo '9.2' > $serverPath/mysql-community/version.pl + echo "暂时不支持该系统" + fi +} + +Uninstall_mysql() +{ + COMMUNITY_UNINSTALL + rm -rf $serverPath/mysql-community + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql-community/versions/9.3/install_generic.sh b/plugins/mysql-community/versions/9.3/install_generic.sh new file mode 100755 index 000000000..486f950d5 --- /dev/null +++ b/plugins/mysql-community/versions/9.3/install_generic.sh @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +# https://downloads.mysql.com/archives/community/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +myDir=${serverPath}/source/mysql-community + +OS_ARCH=`arch` +MYSQL_VER=9.3.0 +SUFFIX_NAME=${MYSQL_VER}-linux-glibc2.28-${OS_ARCH} + +# cd /www/server/mdserver-web/plugins/mysql-community && bash install.sh install 9.3 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/mysql-community/index.py start 9.3 +COMMUNITY_INSTALL() +{ + +######## +mkdir -p $myDir +mkdir -p $serverPath/mysql-community + +# Linux - Generic +# https://cdn.mysql.com/archives/mysql-9.3/mysql-9.3.0-linux-glibc2.28-x86_64.tar.xz +# https://cdn.mysql.com/Downloads/MySQL-9.3/mysql-${SUFFIX_NAME}.tar.xz +if [ ! -f ${myDir}/mysql-${SUFFIX_NAME}.tar.xz ];then + wget --no-check-certificate -O ${myDir}/mysql-${SUFFIX_NAME}.tar.xz https://cdn.mysql.com/Downloads/MySQL-9.3/mysql-${SUFFIX_NAME}.tar.xz +fi + +if [ -d ${myDir} ];then + cd ${myDir} && tar -Jxf ${myDir}/mysql-${SUFFIX_NAME}.tar.xz + cp -rf ${myDir}/mysql-${SUFFIX_NAME}/* $serverPath/mysql-community +fi + +# 测试时可关闭 +rm -rf $myDir/mysql-${SUFFIX_NAME} +####### +} + +COMMUNITY_UNINSTALL() +{ +### +rm -rf $myDir/mysql-${SUFFIX_NAME} +# apt remove -y mysql-server +### +} + + +Install_mysql() +{ + echo '正在安装脚本文件...' + COMMUNITY_INSTALL + if [ "$?" == "0" ];then + mkdir -p $serverPath/mysql-community + echo '9.3' > $serverPath/mysql-community/version.pl + echo '安装完成' + else + echo '9.3' > $serverPath/mysql-community/version.pl + echo "暂时不支持该系统" + fi +} + +Uninstall_mysql() +{ + COMMUNITY_UNINSTALL + rm -rf $serverPath/mysql-community + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/README.md b/plugins/mysql/README.md new file mode 100644 index 000000000..5b3b7cc89 --- /dev/null +++ b/plugins/mysql/README.md @@ -0,0 +1,27 @@ + +``` +show global variables like '%gtid%'; +show global variables like 'server_uuid'; +``` + +``` +# 不锁表,需要删除原来数据表 +# tables = db.query('show tables from `%s`' % sync_db_import) +# table_key = "Tables_in_" + sync_db_import +# for tname in tables: +# drop_db_cmd = 'drop table if exists '+sync_db_import+'.'+tname[table_key] +# # print(drop_db_cmd) +# db.query(drop_db_cmd) +``` + +``` +# 修改同步位置 +# master_info = sync_mdb.query('show master status') +# slave_info = db.query('show slave status') +# if len(master_info)>0: +# channel_name = slave_info[0]['Channel_Name'] +# change_cmd = "CHANGE MASTER TO MASTER_LOG_FILE='"+master_info[0]['File']+"', MASTER_LOG_POS="+str(master_info[0]['Position'])+" for channel '"+channel_name+"';" +# print(change_cmd) +# r = db.execute(change_cmd) +# print(r) +``` \ No newline at end of file diff --git a/plugins/mysql/bak/orm.py b/plugins/mysql/bak/orm.py new file mode 100644 index 000000000..21e29ace2 --- /dev/null +++ b/plugins/mysql/bak/orm.py @@ -0,0 +1,291 @@ +# coding: utf-8 + +import re +import os +import sys + +import pymysql.cursors + + +class ORM: + __DB_PASS = None + __DB_USER = 'root' + __DB_PORT = 3306 + __DB_NAME = '' + __DB_HOST = 'localhost' + __DB_CONN = None + __DB_CUR = None + __DB_ERR = None + __DB_CNF = '/etc/my.cnf' + __DB_SOCKET = '/www/server/mysql/mysql.sock' + __DB_CHARSET = "utf8" + + # orm + __DB_TABLE = "" # 被操作的表名称 + __OPT_WHERE = "" # where条件 + __OPT_LIMIT = "" # limit条件 + __OPT_GROUP = "" # group条件 + __OPT_ORDER = "" # order条件 + __OPT_FIELD = "*" # field条件 + __OPT_PARAM = () # where值 + + def __Conn(self): + '''连接数据库''' + try: + + if os.path.exists(self.__DB_SOCKET): + try: + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=1, + unix_socket=self.__DB_SOCKET, cursorclass=pymysql.cursors.DictCursor) + except Exception as e: + self.__DB_HOST = '127.0.0.1' + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=1, + unix_socket=self.__DB_SOCKET, cursorclass=pymysql.cursors.DictCursor) + else: + try: + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=1, + cursorclass=pymysql.cursors.DictCursor) + except Exception as e: + self.__DB_HOST = '127.0.0.1' + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=1, + cursorclass=pymysql.cursors.DictCursor) + + self.__DB_CUR = self.__DB_CONN.cursor() + return True + except Exception as e: + self.__DB_ERR = e + return False + + def setDbConf(self, conf): + self.__DB_CNF = conf + + def setSocket(self, sock): + self.__DB_SOCKET = sock + + def setCharset(self, charset): + self.__DB_CHARSET = charset + + def setHost(self, host): + self.__DB_HOST = host + + def setPort(self, port): + self.__DB_PORT = port + + def setUser(self, user): + self.__DB_USER = user + + def setPwd(self, pwd): + self.__DB_PASS = pwd + + def getPwd(self): + return self.__DB_PASS + + def setDbName(self, name): + self.__DB_NAME = name + + def execute(self, sql, param=()): + # 执行SQL语句返回受影响行 + if not self.__Conn(): + return self.__DB_ERR + try: + result = self.__DB_CUR.execute(sql,param) + self.__DB_CONN.commit() + self.__Close() + return result + except Exception as ex: + return ex + + def query(self, sql, param=()): + # 执行SQL语句返回数据集 + if not self.__Conn(): + return self.__DB_ERR + try: + self.__DB_CUR.execute(sql,param) + result = self.__DB_CUR.fetchall() + # print(result) + # 将元组转换成列表 + # data = map(list, result) + self.__Close() + return result + except Exception as ex: + return ex + + def __Close(self): + # 关闭连接 + self.__DB_CUR.close() + self.__DB_CONN.close() + + + def checkInput(self, data): + if not data: + return data + if type(data) != str: + return data + checkList = [ + {'d': '<', 'r': '<'}, + {'d': '>', 'r': '>'}, + {'d': '\'', 'r': '‘'}, + {'d': '"', 'r': '“'}, + {'d': '&', 'r': '&'}, + {'d': '#', 'r': '#'}, + {'d': '<', 'r': '<'} + ] + for v in checkList: + data = data.replace(v['d'], v['r']) + return data + + + def table(self, table): + # 设置表名 + self.__DB_TABLE = table + return self + + def where(self, where, param): + # WHERE条件 + if where: + self.__OPT_WHERE = " WHERE " + where + self.__OPT_PARAM = param + return self + + def andWhere(self, where, param): + # WHERE条件 + if where: + self.__OPT_WHERE = self.__OPT_WHERE + " and " + where + # print(param) + # print(self.__OPT_PARAM) + self.__OPT_PARAM = self.__OPT_PARAM + param + return self + + def order(self, order): + # ORDER条件 + if len(order): + self.__OPT_ORDER = " ORDER BY " + order + else: + self.__OPT_ORDER = "" + return self + + def group(self, group): + if len(group): + self.__OPT_GROUP = " GROUP BY " + group + else: + self.__OPT_GROUP = "" + return self + + def limit(self, limit): + # LIMIT条件 + if len(limit): + self.__OPT_LIMIT = " LIMIT " + limit + else: + self.__OPT_LIMIT = "" + return self + + def field(self, field): + # FIELD条件 + if len(field): + self.__OPT_FIELD = field + return self + + def find(self): + # 取一行数据 + result = self.limit("1").select() + # print(result) + if len(result) == 1: + return result[0] + return result + + def select(self): + # 查询数据集 + + sql = "SELECT " + self.__OPT_FIELD + " FROM " + self.__DB_TABLE + \ + self.__OPT_WHERE + self.__OPT_GROUP + self.__OPT_ORDER + self.__OPT_LIMIT + # print(sql) + # print(self.__OPT_PARAM) + result = self.query(sql, self.__OPT_PARAM) + return result + + def getField(self, keyName): + # 取回指定字段 + result = self.field(keyName).select() + # print(result[0][keyName]) + if len(result) == 1: + return result[0][keyName] + return result + + def setField(self, keyName, keyValue): + # 更新指定字段 + return self.save(keyName, (keyValue,)) + + # 构造数据 + def __format_pdata(self, pdata): + keys = pdata.keys() + keys_str = ','.join(keys) + param = [] + for k in keys: + param.append(pdata[k]) + return keys_str, tuple(param) + + def delete(self, id=None): + # 删除数据 + + if id: + self.__OPT_WHERE = " WHERE id=%s" + self.__OPT_PARAM = (id,) + sql = "DELETE FROM " + self.__DB_TABLE + self.__OPT_WHERE + # print(sql) + result = self.execute(sql, self.__OPT_PARAM) + return result + + + def save(self, keys, param): + # 更新数据 + + opt = "" + for key in keys.split(','): + opt += key + "=%s," + opt = opt[0:len(opt) - 1] + sql = "UPDATE " + self.__DB_TABLE + " SET " + opt + self.__OPT_WHERE + + # 处理拼接WHERE与UPDATE参数 + tmp = list(param) + for arg in self.__OPT_PARAM: + tmp.append(arg) + self.__OPT_PARAM = tuple(tmp) + + # print(sql,self.__OPT_PARAM) + result = self.execute(sql, self.__OPT_PARAM) + return result + + # 更新数据 + def update(self, pdata): + if not pdata: + return False + keys, param = self.__format_pdata(pdata) + return self.save(keys, param) + + def add(self, keys, param): + # 插入数据 + values = "" + for key in keys.split(','): + values += "%s," + values = self.checkInput(values[0:len(values) - 1]) + sql = "INSERT INTO " + self.__DB_TABLE + \ + "(" + keys + ") " + "VALUES(" + values + ")" + + # print(sql) + result = self.execute(sql, param) + return result + + + + + + + diff --git a/plugins/mysql/conf/classic.cnf b/plugins/mysql/conf/classic.cnf new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/mysql/conf/gtid.cnf b/plugins/mysql/conf/gtid.cnf new file mode 100644 index 000000000..67ce04f61 --- /dev/null +++ b/plugins/mysql/conf/gtid.cnf @@ -0,0 +1,5 @@ +[mysqld] +# SHOW GLOBAL VARIABLES LIKE '%gtid%' +gtid_mode=ON +enforce_gtid_consistency=ON +binlog_checksum=NONE \ No newline at end of file diff --git a/plugins/mysql/conf/my5.5.cnf b/plugins/mysql/conf/my5.5.cnf new file mode 100644 index 000000000..8c59c159a --- /dev/null +++ b/plugins/mysql/conf/my5.5.cnf @@ -0,0 +1,118 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} + +default_storage_engine = InnoDB + +key_buffer_size = 8M +max_allowed_packet = 100M + +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M + + +query_cache_type = 1 +query_cache_size = 64M + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 65535 + +skip_name_resolve=1 +#skip-grant-tables +#skip-networking +#skip-external-locking +#loose-skip-innodb + +log-bin=mysql-bin +binlog_format=mixed +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=on +#log_slow_admin_statements=1 +#log_slow_slave_statements=1 +expire_logs_days=7 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log-slave-updates = 1 +# Prevent replication from starting automatically with MySQL +#skip-slave-start = 1 +#replicate-do-db +slave_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_additional_mem_pool_size = 2M +innodb_log_file_size = 5M +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 +innodb_large_prefix = 1 + + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick +max_allowed_packet = 32M + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my5.6.cnf b/plugins/mysql/conf/my5.6.cnf new file mode 100644 index 000000000..54b97fa8b --- /dev/null +++ b/plugins/mysql/conf/my5.6.cnf @@ -0,0 +1,121 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +sha256_password_private_key_path=mysql.pem +sha256_password_public_key_path=mysql.pub + +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +max_allowed_packet = 100M + +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M + +query_cache_type = 1 +query_cache_size = 64M + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 65535 + +skip_name_resolve=1 +#skip-grant-tables +#skip-networking +#skip-external-locking +#loose-skip-innodb + +log-bin=mysql-bin +binlog_format=mixed +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=on +#log_slow_admin_statements=1 +#log_slow_slave_statements=1 +expire_logs_days=7 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log-slave-updates = 1 +# Prevent replication from starting automatically with MySQL +#skip-slave-start = 1 +#replicate-do-db +slave_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_additional_mem_pool_size = 2M +innodb_log_file_size = 5M +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 +innodb_large_prefix = 1 + + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick +max_allowed_packet = 32M + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my5.7.cnf b/plugins/mysql/conf/my5.7.cnf new file mode 100644 index 000000000..2fc66e783 --- /dev/null +++ b/plugins/mysql/conf/my5.7.cnf @@ -0,0 +1,123 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +explicit_defaults_for_timestamp=true +default_storage_engine = InnoDB + + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +query_cache_type = 1 +query_cache_size = 64M + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +skip-ssl +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +log-bin=mysql-bin +binlog_format=mixed +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_slave_statements=1 +expire_logs_days=7 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +# 5.7 It can not be opened later +#log-slave-updates = 1 +# Prevent replication from starting automatically with MySQL +#skip-slave-start = 1 +#replicate-do-db +slave_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + +master_info_repository = table +relay_log_info_repository = table + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +innodb_log_file_size = 5M +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table = 1 +innodb_large_prefix = 1 + +secure-file-priv={$SERVER_APP_PATH}/tmp +#secure-file-priv=disabled +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my8.0.cnf b/plugins/mysql/conf/my8.0.cnf new file mode 100644 index 000000000..5df1172e2 --- /dev/null +++ b/plugins/mysql/conf/my8.0.cnf @@ -0,0 +1,124 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +sha256_password_private_key_path={$SERVER_APP_PATH}/data/mysql.pem +sha256_password_public_key_path={$SERVER_APP_PATH}/data/mysql.pub +authentication_policy=mysql_native_password + +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +#sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +#skip-log-bin +#disable-log-bin +#skip-slave-start + +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_replica_statements=1 +binlog_expire_logs_seconds=604800 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates = 1 +# Prevent replication from starting automatically with MySQL +#skip_replica_start = 1 +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +#innodb_log_file_size = 5M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +# 对于极高写入负载的系统,可考虑调整刷新行为 +innodb_io_capacity = 2000 +innodb_io_capacity_max = 4000 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my8.2.cnf b/plugins/mysql/conf/my8.2.cnf new file mode 100644 index 000000000..c6615335a --- /dev/null +++ b/plugins/mysql/conf/my8.2.cnf @@ -0,0 +1,123 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +sha256_password_private_key_path={$SERVER_APP_PATH}/data/mysql.pem +sha256_password_public_key_path={$SERVER_APP_PATH}/data/mysql.pub +authentication_policy=caching_sha2_password + +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +#sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_replica_statements=1 +binlog_expire_logs_seconds=604800 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates = 1 +# Prevent replication from starting automatically with MySQL +#skip_replica_start = 1 +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +#innodb_log_file_size = 5M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +# 对于极高写入负载的系统,可考虑调整刷新行为 +innodb_io_capacity = 2000 +innodb_io_capacity_max = 4000 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my8.3.cnf b/plugins/mysql/conf/my8.3.cnf new file mode 100644 index 000000000..6b9dd402a --- /dev/null +++ b/plugins/mysql/conf/my8.3.cnf @@ -0,0 +1,119 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +sha256_password_private_key_path={$SERVER_APP_PATH}/data/mysql.pem +sha256_password_public_key_path={$SERVER_APP_PATH}/data/mysql.pub +authentication_policy=caching_sha2_password + +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +#sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_replica_statements=1 +binlog_expire_logs_seconds=604800 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates = 1 +# Prevent replication from starting automatically with MySQL +#skip_replica_start = 1 +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +#innodb_log_file_size = 5M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my8.4.cnf b/plugins/mysql/conf/my8.4.cnf new file mode 100644 index 000000000..87cb53435 --- /dev/null +++ b/plugins/mysql/conf/my8.4.cnf @@ -0,0 +1,116 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +#sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_replica_statements=1 +binlog_expire_logs_seconds=604800 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates = 1 +# Prevent replication from starting automatically with MySQL +#skip_replica_start = 1 +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +#innodb_log_file_size = 5M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my9.0.cnf b/plugins/mysql/conf/my9.0.cnf new file mode 100644 index 000000000..87cb53435 --- /dev/null +++ b/plugins/mysql/conf/my9.0.cnf @@ -0,0 +1,116 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +#sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_replica_statements=1 +binlog_expire_logs_seconds=604800 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates = 1 +# Prevent replication from starting automatically with MySQL +#skip_replica_start = 1 +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +#innodb_log_file_size = 5M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my9.1.cnf b/plugins/mysql/conf/my9.1.cnf new file mode 100644 index 000000000..87cb53435 --- /dev/null +++ b/plugins/mysql/conf/my9.1.cnf @@ -0,0 +1,116 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +#sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_replica_statements=1 +binlog_expire_logs_seconds=604800 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates = 1 +# Prevent replication from starting automatically with MySQL +#skip_replica_start = 1 +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +#innodb_log_file_size = 5M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my9.2.cnf b/plugins/mysql/conf/my9.2.cnf new file mode 100644 index 000000000..87cb53435 --- /dev/null +++ b/plugins/mysql/conf/my9.2.cnf @@ -0,0 +1,116 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +#sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_replica_statements=1 +binlog_expire_logs_seconds=604800 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates = 1 +# Prevent replication from starting automatically with MySQL +#skip_replica_start = 1 +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +#innodb_log_file_size = 5M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my9.3.cnf b/plugins/mysql/conf/my9.3.cnf new file mode 100644 index 000000000..87cb53435 --- /dev/null +++ b/plugins/mysql/conf/my9.3.cnf @@ -0,0 +1,116 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +#sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_replica_statements=1 +binlog_expire_logs_seconds=604800 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates = 1 +# Prevent replication from starting automatically with MySQL +#skip_replica_start = 1 +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +#innodb_log_file_size = 5M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/my9.4.cnf b/plugins/mysql/conf/my9.4.cnf new file mode 100644 index 000000000..87cb53435 --- /dev/null +++ b/plugins/mysql/conf/my9.4.cnf @@ -0,0 +1,116 @@ +[client] +user = root +#password = your_password +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +default-character-set = UTF8MB4 + +[mysqld] +!include {$SERVER_APP_PATH}/etc/mode/classic.cnf + +authentication_policy=caching_sha2_password +pid-file = {$SERVER_APP_PATH}/data/mysql.pid +user = mysql +port = 3306 +socket = {$SERVER_APP_PATH}/mysql.sock +basedir = {$SERVER_APP_PATH} +datadir = {$SERVER_APP_PATH}/data +log-error = {$SERVER_APP_PATH}/data/error.log +server-id = {$SERVER_ID} +#sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES + +default_storage_engine = InnoDB + +key_buffer_size = 8M +table_open_cache = 32 +sort_buffer_size = 256K +net_buffer_length = 4K +read_buffer_size = 128K +read_rnd_buffer_size = 256K +myisam_sort_buffer_size = 4M +thread_cache_size = 4 +lower_case_table_names=0 +tmp_table_size = 8M +character-set-server = UTF8MB4 + +max_connections = 500 +max_connect_errors = 100 +open_files_limit = 2560 +max_allowed_packet = 128M + +skip_name_resolve=1 +#skip-networking +#skip-external-locking +#loose-skip-innodb +#skip-grant-tables + +#skip-log-bin +#disable-log-bin +#skip-slave-start +log-bin=mysql-bin +slow_query_log=1 +slow-query-log-file={$SERVER_APP_PATH}/data/mysql-slow.log +long_query_time=10 +#log_queries_not_using_indexes=1 +#log_slow_admin_statements=1 +#log_slow_replica_statements=1 +binlog_expire_logs_seconds=604800 + +relay-log=mdserver +relay-log-index=mdserver + +#多主设置 +#auto_increment_offset=2 +#auto_increment_increment=2 + +#master +#sync_binlog=1 + +#binlog-do-db +binlog-ignore-db = test +binlog-ignore-db = mysql +binlog-ignore-db = information_schema +binlog-ignore-db = performance_schema + +#slave +log_replica_updates = 1 +# Prevent replication from starting automatically with MySQL +#skip_replica_start = 1 +#replicate-do-db +replica_skip_errors=1062,1396 +replicate-ignore-db = information_schema +replicate-ignore-db = performance_schema +replicate-ignore-db = mysql +replicate-ignore-db = test + + +innodb_data_home_dir = {$SERVER_APP_PATH}/data +innodb_data_file_path = ibdata1:10M:autoextend +innodb_log_group_home_dir = {$SERVER_APP_PATH}/data +innodb_buffer_pool_size = 16M +#innodb_log_file_size = 5M +innodb_redo_log_capacity = 4G +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 120 +innodb_max_dirty_pages_pct = 90 +innodb_read_io_threads = 1 +innodb_write_io_threads = 1 +innodb_file_per_table=1 + +secure-file-priv={$SERVER_APP_PATH}/tmp + +[mysqldump] +quick + +[mysql] +no-auto-rehash + +[myisamchk] +key_buffer_size = 20M +sort_buffer_size = 20M +read_buffer = 2M +write_buffer = 2M + +[mysqlhotcopy] +interactive-timeout \ No newline at end of file diff --git a/plugins/mysql/conf/mysql.sql b/plugins/mysql/conf/mysql.sql new file mode 100755 index 000000000..f98ddf79b --- /dev/null +++ b/plugins/mysql/conf/mysql.sql @@ -0,0 +1,58 @@ +CREATE TABLE IF NOT EXISTS `config` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `mysql_root` TEXT +); + +INSERT INTO `config` (`id`, `mysql_root`) VALUES (1, 'admin'); + +CREATE TABLE IF NOT EXISTS `databases` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pid` INTEGER, + `name` TEXT, + `username` TEXT, + `password` TEXT, + `accept` TEXT, + `rw` TEXT DEFAULT 'rw', + `ps` TEXT, + `addtime` TEXT +); +-- ALTER TABLE `databases` ADD COLUMN `rw` TEXT DEFAULT 'rw'; + +CREATE TABLE IF NOT EXISTS `master_replication_user` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` TEXT, + `password` TEXT, + `accept` TEXT, + `ps` TEXT, + `addtime` TEXT +); + +-- 从库配置主库的[ssh private key] +-- drop table `slave_id_rsa`; +CREATE TABLE IF NOT EXISTS `slave_id_rsa` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `ip` TEXT, + `port` TEXT, + `user` TEXT, + `db_user` TEXT, + `id_rsa` TEXT, + `ps` TEXT, + `addtime` TEXT +); + +-- 从库配置主库的[user] +-- drop table `slave_user`; +CREATE TABLE IF NOT EXISTS `slave_sync_user` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `ip` TEXT, + `port` TEXT, + `user` TEXT, + `pass` TEXT, + `mode` TEXT, + `cmd` TEXT, + `db` TEXT, + `addtime` TEXT +); +ALTER TABLE `slave_sync_user` ADD COLUMN `db` TEXT DEFAULT ''; + + diff --git a/plugins/mysql/ico.png b/plugins/mysql/ico.png new file mode 100755 index 000000000..a0911ee83 Binary files /dev/null and b/plugins/mysql/ico.png differ diff --git a/plugins/mysql/index.html b/plugins/mysql/index.html new file mode 100755 index 000000000..187869409 --- /dev/null +++ b/plugins/mysql/index.html @@ -0,0 +1,71 @@ +
                              +
                              +
                              +
                              + +

                              服务

                              +

                              自启动

                              +

                              配置文件

                              +

                              存储位置

                              +

                              端口

                              +

                              当前状态

                              +

                              性能优化

                              +

                              日志

                              +

                              慢日志

                              +

                              BINLOG

                              +

                              管理列表

                              +

                              主从配置

                              +
                              +
                              +
                              +
                              +
                              + +
                              + + + + + + \ No newline at end of file diff --git a/plugins/mysql/index.py b/plugins/mysql/index.py new file mode 100755 index 000000000..09e98f6a9 --- /dev/null +++ b/plugins/mysql/index.py @@ -0,0 +1,4081 @@ +#!/usr/bin/env python +# coding=utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + +from packaging import version as pk_version + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +if mw.isAppleSystem(): + cmd = 'ls /usr/local/lib/ | grep python | cut -d \\ -f 1 | awk \'END {print}\'' + info = mw.execShell(cmd) + p = "/usr/local/lib/" + info[0].strip() + "/site-packages" + sys.path.append(p) + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'mysql' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getSPluginDir(): + return '/www/server/mdserver-web/plugins/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':',1) + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':',1) + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + path = getServerDir() + '/etc/my.cnf' + return path + + +def getDbPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getDbServerId(): + file = getConf() + content = mw.readFile(file) + rep = r'server-id\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getSocketFile(): + file = getConf() + content = mw.readFile(file) + rep = r'socket\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getErrorLogsFile(): + file = getConf() + content = mw.readFile(file) + rep = r'log-error\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getAuthPolicy(): + file = getConf() + content = mw.readFile(file) + rep = r'authentication_policy\s*=\s*(.*)' + tmp = re.search(rep, content) + if tmp: + return tmp.groups()[0].strip() + # caching_sha2_password + return 'mysql_native_password' + + +def getInitdTpl(version=''): + path = getPluginDir() + '/init.d/mysql' + version + '.tpl' + if not os.path.exists(path): + path = getPluginDir() + '/init.d/mysql.tpl' + return path + + +def contentReplace(content): + + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP_PATH}', service_path + '/mysql') + + server_id = int(time.time()) + content = content.replace('{$SERVER_ID}', str(server_id)) + + if mw.isAppleSystem(): + content = content.replace( + 'lower_case_table_names=0', 'lower_case_table_names=2') + + return content + + +def pSqliteDb(dbname='databases'): + file = getServerDir() + '/mysql.db' + name = 'mysql' + + import_sql = mw.readFile(getPluginDir() + '/conf/mysql.sql') + md5_sql = mw.md5(import_sql) + + import_sign = False + save_md5_file = getServerDir() + '/import_sql.md5' + if os.path.exists(save_md5_file): + save_md5_sql = mw.readFile(save_md5_file) + if save_md5_sql != md5_sql: + import_sign = True + mw.writeFile(save_md5_file, md5_sql) + else: + mw.writeFile(save_md5_file, md5_sql) + + if not os.path.exists(file) or import_sql: + conn = mw.M(dbname).dbPos(getServerDir(), name) + csql_list = import_sql.split(';') + for index in range(len(csql_list)): + conn.execute(csql_list[index], ()) + + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + + +def pMysqlDb(): + # pymysql + db = mw.getMyORM() + # MySQLdb | + # db = mw.getMyORMDb() + # print(getDbPort()) + db.setPort(getDbPort()) + db.setSocket(getSocketFile()) + # db.setCharset("utf8") + db.setPwd(pSqliteDb('config').where('id=?', (1,)).getField('mysql_root')) + return db + + +def makeInitRsaKey(version=''): + try: + datadir = getDataDir() + except Exception as e: + datadir = getServerDir() + "/data" + + mysql_pem = datadir + "/mysql.pem" + if not os.path.exists(mysql_pem): + rdata = mw.execShell( + 'cd ' + datadir + ' && openssl genrsa -out mysql.pem 1024') + # print(data) + rdata = mw.execShell( + 'cd ' + datadir + ' && openssl rsa -in mysql.pem -pubout -out mysql.pub') + # print(rdata) + if not mw.isAppleSystem(): + mw.execShell('cd ' + datadir + ' && chmod 400 mysql.pem') + mw.execShell('cd ' + datadir + ' && chmod 444 mysql.pub') + mw.execShell('cd ' + datadir + ' && chown mysql:mysql mysql.pem') + mw.execShell('cd ' + datadir + ' && chown mysql:mysql mysql.pub') + + +def initDreplace(version=''): + conf_dir = getServerDir() + '/etc' + mode_dir = conf_dir + '/mode' + + conf_list = [ + conf_dir, + mode_dir, + ] + for conf in conf_list: + if not os.path.exists(conf): + os.mkdir(conf) + + tmp_dir = getServerDir() + '/tmp' + if not os.path.exists(tmp_dir): + os.mkdir(tmp_dir) + mw.execShell("chown -R mysql:mysql " + tmp_dir) + mw.execShell("chmod 750 " + tmp_dir) + + my_conf = conf_dir + '/my.cnf' + if not os.path.exists(my_conf): + tpl = getPluginDir() + '/conf/my' + version + '.cnf' + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(my_conf, content) + + classic_conf = mode_dir + '/classic.cnf' + if not os.path.exists(classic_conf): + tpl = getPluginDir() + '/conf/classic.cnf' + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(classic_conf, content) + + gtid_conf = mode_dir + '/gtid.cnf' + if not os.path.exists(gtid_conf): + tpl = getPluginDir() + '/conf/gtid.cnf' + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(gtid_conf, content) + + # systemd + system_dir = mw.systemdCfgDir() + service = system_dir + '/mysql.service' + if os.path.exists(system_dir) and not os.path.exists(service): + tpl = getPluginDir() + '/init.d/mysql.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(service, content) + mw.execShell('systemctl daemon-reload') + + if not mw.isAppleSystem(): + mw.execShell('chown -R mysql mysql ' + getServerDir()) + + initd_path = getServerDir() + '/init.d' + if not os.path.exists(initd_path): + os.mkdir(initd_path) + + file_bin = initd_path + '/' + getPluginName() + if not os.path.exists(file_bin): + initd_tpl = getInitdTpl(version) + content = mw.readFile(initd_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + return file_bin + + +def process_status(): + cmd = "ps -ef|grep mysql |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def status(version=''): + path = getConf() + if not os.path.exists(path): + return 'stop' + + pid = getPidFile() + if not os.path.exists(pid): + return 'stop' + + return 'start' + + +def getDataDir(): + file = getConf() + content = mw.readFile(file) + rep = r'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getLogBinName(): + file = getConf() + content = mw.readFile(file) + rep = r'log-bin\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pid-file\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def binLog(version = ''): + args = getArgs() + conf = getConf() + con = mw.readFile(conf) + + if con.find('#log-bin=mysql-bin') != -1: + if 'status' in args: + return mw.returnJson(False, '0') + con = con.replace('#log-bin=mysql-bin', 'log-bin=mysql-bin') + con = con.replace('#binlog_format=mixed', 'binlog_format=mixed') + + con = con.replace('skip-log-bin', '#skip-log-bin') + con = con.replace('disable-log-bin', '#disable-log-bin') + con = con.replace('skip-slave-start', '#skip-slave-start') + + mw.writeFile(conf, con) + mw.execShell('sync') + restart(version) + else: + path = getDataDir() + if 'status' in args: + dsize = 0 + for n in os.listdir(path): + if len(n) < 9: + continue + if n[0:9] == 'mysql-bin': + dsize += os.path.getsize(path + '/' + n) + return mw.returnJson(True, dsize) + con = con.replace('log-bin=mysql-bin', '#log-bin=mysql-bin') + con = con.replace('binlog_format=mixed', '#binlog_format=mixed') + + con = con.replace('#skip-log-bin', 'skip-log-bin') + con = con.replace('#disable-log-bin', 'disable-log-bin') + con = con.replace('#skip-slave-start', 'skip-slave-start') + + mw.writeFile(conf, con) + mw.execShell('sync') + restart(version) + mw.execShell('rm -f ' + path + '/mysql-bin.*') + mw.execShell('rm -f ' + path + '/binlog.*') + + + return mw.returnJson(True, '设置成功!') + + +def binLogList(): + args = getArgs() + data = checkArgs(args, ['page', 'page_size', 'tojs']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + data_dir = getDataDir() + log_bin_name = getLogBinName() + + alist = os.listdir(data_dir) + log_bin_l = [] + for x in range(len(alist)): + f = alist[x] + t = {} + if f.startswith(log_bin_name) and not f.endswith('.index'): + abspath = data_dir + '/' + f + t['name'] = f + t['size'] = os.path.getsize(abspath) + t['time'] = mw.getDataFromInt(os.path.getctime(abspath)) + log_bin_l.append(t) + + log_bin_l = sorted(log_bin_l, key=lambda x: x['time'], reverse=True) + + # print(log_bin_l) + # print(data_dir, log_bin_name) + + count = len(log_bin_l) + + page_start = (page - 1) * page_size + page_end = page_start + page_size + if page_end > count: + page_end = count + + data = {} + page_args = {} + page_args['count'] = count + page_args['p'] = page + page_args['row'] = page_size + page_args['tojs'] = args['tojs'] + data['page'] = mw.getPage(page_args) + data['data'] = log_bin_l[page_start:page_end] + + return mw.getJson(data) + + +def cleanBinLog(): + db = pMysqlDb() + cleanTime = time.strftime('%Y-%m-%d %H:%i:%s', time.localtime()) + db.execute("PURGE MASTER LOGS BEFORE '" + cleanTime + "';") + return mw.returnJson(True, '清理BINLOG成功!') + +def getErrorLog(): + args = getArgs() + filename = getErrorLogsFile() + if not os.path.exists(filename): + return mw.returnJson(False, '指定文件不存在!') + if 'close' in args: + mw.writeFile(filename, '') + return mw.returnJson(False, '日志已清空') + info = mw.getLastLine(filename, 18) + return mw.returnJson(True, 'OK', info) + + +def getShowLogFile(): + file = getConf() + content = mw.readFile(file) + rep = r'slow-query-log-file\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getMdb8Ver(): + return ['8.0','8.1','8.2','8.3','8.4','9.0','9.1', '9.2', '9.3', '9.4'] + + +def getSlaveName(): + mdb8 = getMdb8Ver() + if mw.inArray(mdb8, version): + return 'replica' + return 'slave' + +def pGetDbUser(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + return 'mysql' + + +def initMysqlData(): + datadir = getDataDir() + if not os.path.exists(datadir + '/mysql'): + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + user = pGetDbUser() + cmd = 'cd ' + serverdir + ' && ./scripts/mysql_install_db --defaults-file=' + myconf + mw.execShell(cmd) + return False + return True + + +def initMysql57Data(): + ''' + cd /www/server/mysql && /www/server/mysql/bin/mysqld --defaults-file=/www/server/mysql/etc/my.cnf --initialize-insecure --explicit_defaults_for_timestamp + ''' + datadir = getDataDir() + if not os.path.exists(datadir + '/mysql'): + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + user = pGetDbUser() + cmd = 'cd ' + serverdir + ' && ./bin/mysqld --defaults-file=' + myconf + \ + ' --initialize-insecure --explicit_defaults_for_timestamp --user=mysql' + data = mw.execShell(cmd) + # print(data) + return False + return True + + +def initMysql8Data(): + datadir = getDataDir() + if not os.path.exists(datadir + '/mysql'): + serverdir = getServerDir() + user = pGetDbUser() + # cmd = 'cd ' + serverdir + ' && ./bin/mysqld --basedir=' + serverdir + ' --datadir=' + \ + # datadir + ' --initialize' + + cmd = 'cd ' + serverdir + ' && ./bin/mysqld --basedir=' + serverdir + ' --datadir=' + \ + datadir + ' --initialize-insecure' + + # print(cmd) + data = mw.execShell(cmd) + # print(data) + return False + return True + + +def initMysqlPwd(): + time.sleep(5) + + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + pwd = mw.getRandomString(16) + + cmd_pass = serverdir + '/bin/mysql --defaults-file=' + myconf + ' -uroot -e' + cmd_pass = cmd_pass + \ + '"UPDATE mysql.user SET password=PASSWORD(\'' + \ + pwd + "') WHERE user='root';" + cmd_pass = cmd_pass + 'flush privileges;"' + data = mw.execShell(cmd_pass) + # print(cmd_pass) + # print(data) + + # 删除空账户 + drop_empty_user = serverdir + '/bin/mysql -uroot -p' + \ + pwd + ' -e "use mysql;delete from user where USER=\'\'"' + mw.execShell(drop_empty_user) + + # 删除测试数据库 + drop_test_db = serverdir + '/bin/mysql -uroot -p' + \ + pwd + ' -e "drop database test";' + mw.execShell(drop_test_db) + + # 删除冗余账户 + hostname = mw.execShell('hostname')[0].strip() + if hostname != 'localhost': + drop_hostname = serverdir + '/bin/mysql --defaults-file=' + \ + myconf + ' -uroot -p"' + pwd + '" -e "drop user \'\'@\'' + hostname + '\'";' + mw.execShell(drop_hostname) + + drop_root_hostname = serverdir + '/bin/mysql --defaults-file=' + \ + myconf + ' -uroot -p"' + pwd + '" -e "drop user \'root\'@\'' + hostname + '\'";' + mw.execShell(drop_root_hostname) + + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (pwd,)) + return True + +def initMysql8Pwd(): + time.sleep(8) + + + auth_policy = getAuthPolicy() + + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + + pwd = mw.getRandomString(16) + + alter_root_pwd = 'flush privileges;' + + alter_root_pwd = alter_root_pwd + \ + "UPDATE mysql.user SET authentication_string='' WHERE user='root';" + alter_root_pwd = alter_root_pwd + "flush privileges;" + alter_root_pwd = alter_root_pwd + \ + "alter user 'root'@'localhost' IDENTIFIED by '" + pwd + "';" + alter_root_pwd = alter_root_pwd + \ + "alter user 'root'@'localhost' IDENTIFIED WITH "+auth_policy+" by '" + pwd + "';" + alter_root_pwd = alter_root_pwd + "flush privileges;" + + cmd_pass = serverdir + '/bin/mysqladmin --defaults-file=' + \ + myconf + ' -uroot password root' + data = mw.execShell(cmd_pass) + # print(cmd_pass) + # print(data) + + tmp_file = "/tmp/mysql_init_tmp.log" + mw.writeFile(tmp_file, alter_root_pwd) + cmd_pass = serverdir + '/bin/mysql --defaults-file=' + \ + myconf + ' -uroot -proot < ' + tmp_file + + data = mw.execShell(cmd_pass) + os.remove(tmp_file) + + # 删除测试数据库 + drop_test_db = serverdir + '/bin/mysql --defaults-file=' + \ + myconf + ' -uroot -p"' + pwd + '" -e "drop database test";' + mw.execShell(drop_test_db) + + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (pwd,)) + + # 删除冗余账户 + hostname = mw.execShell('hostname')[0].strip() + if hostname != 'localhost': + drop_hostname = serverdir + '/bin/mysql --defaults-file=' + \ + myconf + ' -uroot -p"' + pwd + '" -e "drop user \'\'@\'' + hostname + '\'";' + mw.execShell(drop_hostname) + + drop_root_hostname = serverdir + '/bin/mysql --defaults-file=' + \ + myconf + ' -uroot -p"' + pwd + '" -e "drop user \'root\'@\'' + hostname + '\'";' + mw.execShell(drop_root_hostname) + + return True + + +def myOp(version, method): + # import commands + init_file = initDreplace(version) + current_os = mw.getOs() + try: + isInited = initMysqlData() + if not isInited: + + if not mw.isSupportSystemctl(): + mw.execShell('service ' + getPluginName() + ' start') + else: + mw.execShell('systemctl start mysql') + + initMysqlPwd() + + if not mw.isSupportSystemctl(): + mw.execShell('service ' + getPluginName() + ' stop') + else: + mw.execShell('systemctl stop mysql') + + if not mw.isSupportSystemctl(): + mw.execShell('service ' + getPluginName() + ' ' + method) + else: + mw.execShell('systemctl ' + method + ' mysql') + return 'ok' + except Exception as e: + return str(e) + + +def my8cmd(version, method): + # mysql 8.0 and 5.7 + init_file = initDreplace(version) + cmd = init_file + ' ' + method + mdb8 = getMdb8Ver() + try: + isInited = True + if mw.inArray(mdb8, version): + isInited = initMysql8Data() + else: + isInited = initMysql57Data() + + if not isInited: + + if not mw.isSupportSystemctl(): + cmd_init_start = init_file + ' start' + subprocess.Popen(cmd_init_start, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + + time.sleep(6) + else: + mw.execShell('systemctl start mysql') + + for x in range(10): + mydb_status = process_status() + if mydb_status == 'start': + initMysql8Pwd() + break + time.sleep(1) + + if not mw.isSupportSystemctl(): + cmd_init_stop = init_file + ' stop' + subprocess.Popen(cmd_init_stop, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + time.sleep(3) + else: + mw.execShell('systemctl stop mysql') + + if not mw.isSupportSystemctl(): + sub = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + sub.wait(5) + else: + mw.execShell('systemctl ' + method + ' mysql') + return 'ok' + except Exception as e: + return str(e) + + +def appCMD(version, action): + makeInitRsaKey(version) + mdb8 = getMdb8Ver() + mdb8.append('5.7') + # print(mdb8) + if mw.inArray(mdb8, version): + status = my8cmd(version, action) + else: + status = myOp(version, action) + return status + + +def start(version=''): + return appCMD(version, 'start') + + +def stop(version=''): + return appCMD(version, 'stop') + + +def restart(version=''): + return appCMD(version, 'restart') + + +def reload(version=''): + return appCMD(version, 'reload') + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status mysql | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable mysql') + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable mysql') + return 'ok' + + +def getMyDbPos(): + file = getConf() + content = mw.readFile(file) + rep = r'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def setMyDbPos(version): + args = getArgs() + data = checkArgs(args, ['datadir']) + if not data[0]: + return data[1] + + s_datadir = getMyDbPos() + t_datadir = args['datadir'] + if t_datadir == s_datadir: + return mw.returnJson(False, '与当前存储目录相同,无法迁移文件!') + + if not os.path.exists(t_datadir): + mw.execShell('mkdir -p ' + t_datadir) + + # mw.execShell('/etc/init.d/mysqld stop') + stop(version) + mw.execShell('cp -rf ' + s_datadir + '/* ' + t_datadir + '/') + mw.execShell('chown -R mysql mysql ' + t_datadir) + mw.execShell('chmod -R 755 ' + t_datadir) + mw.execShell('rm -f ' + t_datadir + '/*.pid') + mw.execShell('rm -f ' + t_datadir + '/*.err') + + path = getServerDir() + myfile = path + '/etc/my.cnf' + mycnf = mw.readFile(myfile) + mw.writeFile(path + '/etc/my_backup.cnf', mycnf) + + mycnf = mycnf.replace(s_datadir, t_datadir) + mw.writeFile(myfile, mycnf) + start(version) + + result = mw.execShell('ps aux|grep mysqld| grep -v grep|grep -v python') + if len(result[0]) > 10: + mw.writeFile('data/datadir.pl', t_datadir) + return mw.returnJson(True, '存储目录迁移成功!') + else: + mw.execShell('pkill -9 mysqld') + mw.writeFile(myfile, mw.readFile(path + '/etc/my_backup.cnf')) + start(version) + return mw.returnJson(False, '文件迁移失败!') + + +def getMyPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def setMyPort(version): + args = getArgs() + data = checkArgs(args, ['port']) + if not data[0]: + return data[1] + + port = args['port'] + file = getConf() + content = mw.readFile(file) + rep = r"port\s*=\s*([0-9]+)\s*\n" + content = re.sub(rep, 'port = ' + port + '\n', content) + mw.writeFile(file, content) + restart(version) + return mw.returnJson(True, '编辑成功!') + + +def runInfo(version): + + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + db = pMysqlDb() + data = db.query('show global status') + isError = isSqlError(data) + if isError != None: + return isError + + gets = ['Max_used_connections', 'Com_commit', 'Com_select', 'Com_rollback', 'Questions', 'Innodb_buffer_pool_reads', 'Innodb_buffer_pool_read_requests', 'Key_reads', 'Key_read_requests', 'Key_writes', + 'Key_write_requests', 'Qcache_hits', 'Qcache_inserts', 'Bytes_received', 'Bytes_sent', 'Aborted_clients', 'Aborted_connects', + 'Created_tmp_disk_tables', 'Created_tmp_tables', 'Innodb_buffer_pool_pages_dirty', 'Opened_files', 'Open_tables', 'Opened_tables', 'Select_full_join', + 'Select_range_check', 'Sort_merge_passes', 'Table_locks_waited', 'Threads_cached', 'Threads_connected', 'Threads_created', 'Threads_running', 'Connections', 'Uptime'] + + result = {} + # print(data) + for d in data: + vname = d["Variable_name"] + for g in gets: + if vname == g: + result[g] = d["Value"] + + # print(result, int(result['Uptime'])) + result['Run'] = int(time.time()) - int(result['Uptime']) + tmp = db.query('show master status') + try: + result['File'] = tmp[0]["File"] + result['Position'] = tmp[0]["Position"] + except: + result['File'] = 'OFF' + result['Position'] = 'OFF' + return mw.getJson(result) + + +def myDbStatus(ver): + if status(ver) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + result = {} + db = pMysqlDb() + data = db.query('show variables') + isError = isSqlError(data) + if isError != None: + return isError + + gets = ['table_open_cache', 'thread_cache_size', 'key_buffer_size', 'tmp_table_size', 'max_heap_table_size', 'innodb_buffer_pool_size', + 'innodb_additional_mem_pool_size', 'innodb_log_buffer_size', 'max_connections', 'sort_buffer_size', 'read_buffer_size', 'read_rnd_buffer_size', 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + + if pk_version.parse(ver) < pk_version.parse("8.0"): + gets.append('query_cache_size') + + result['mem'] = {} + for d in data: + vname = d['Variable_name'] + for g in gets: + # print(g) + if vname == g: + result['mem'][g] = d["Value"] + return mw.getJson(result) + + +def setDbStatus(ver): + gets = ['key_buffer_size', 'tmp_table_size', 'max_heap_table_size', 'innodb_buffer_pool_size', 'innodb_log_buffer_size', 'max_connections', + 'table_open_cache', 'thread_cache_size', 'sort_buffer_size', 'read_buffer_size', 'read_rnd_buffer_size', 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + + if pk_version.parse(ver) < pk_version.parse("8.0"): + gets = ['key_buffer_size', 'query_cache_size', 'tmp_table_size', 'max_heap_table_size', 'innodb_buffer_pool_size', 'innodb_log_buffer_size', 'max_connections', + 'table_open_cache', 'thread_cache_size', 'sort_buffer_size', 'read_buffer_size', 'read_rnd_buffer_size', 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + + # print(gets) + emptys = ['max_connections', 'thread_cache_size', 'table_open_cache'] + args = getArgs() + conFile = getConf() + content = mw.readFile(conFile) + n = 0 + for g in gets: + s = 'M' + if n > 5: + s = 'K' + if g in emptys: + s = '' + rep = r'\s*' + g + r'\s*=\s*\d+(M|K|k|m|G)?\n' + c = g + ' = ' + args[g] + s + '\n' + if content.find(g) != -1: + content = re.sub(rep, '\n' + c, content, 1) + else: + content = content.replace('[mysqld]\n', '[mysqld]\n' + c) + n += 1 + mw.writeFile(conFile, content) + return mw.returnJson(True, '设置成功!') + + +def isSqlError(mysqlMsg): + # 检测数据库执行错误 + mysqlMsg = str(mysqlMsg) + if "MySQLdb" in mysqlMsg: + return mw.returnJson(False, 'MySQLdb组件缺失!
                              进入SSH命令行输入: pip install mysql-python | pip install mysqlclient==2.0.3') + if "2002," in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "2003," in mysqlMsg: + return mw.returnJson(False, "Can't connect to MySQL server on '127.0.0.1' (61)") + if "using password:" in mysqlMsg: + return mw.returnJson(False, '数据库密码错误,在管理列表-点击【修复】!') + if "1045," in mysqlMsg: + return mw.returnJson(False, '连接错误!') + if "SQL syntax" in mysqlMsg: + return mw.returnJson(False, 'SQL语法错误!') + if "Connection refused" in mysqlMsg: + return mw.returnJson(False, '数据库连接失败,请检查数据库服务是否启动!') + if "1133," in mysqlMsg: + return mw.returnJson(False, '数据库用户不存在!') + if "1007," in mysqlMsg: + return mw.returnJson(False, '数据库已经存在!') + return None + + +def __createUser(dbname, username, password, address): + pdb = pMysqlDb() + + if username == 'root': + dbname = '*' + + pdb.execute( + "CREATE USER `%s`@`localhost` IDENTIFIED BY '%s'" % (username, password)) + pdb.execute( + "grant all privileges on %s.* to `%s`@`localhost`" % (dbname, username)) + for a in address.split(','): + pdb.execute( + "CREATE USER `%s`@`%s` IDENTIFIED BY '%s'" % (username, a, password)) + pdb.execute( + "grant all privileges on %s.* to `%s`@`%s`" % (dbname, username, a)) + pdb.execute("flush privileges") + + +def getDbBackupListFunc(dbname=''): + bkDir = mw.getBackupDir() + '/database' + if not os.path.exists(bkDir): + mw.execShell('mkdir -p ' + bkDir) + + blist = os.listdir(bkDir) + r = [] + + bname = 'db_' + dbname + blen = len(bname) + for x in blist: + fbstr = x[0:blen] + if fbstr == bname: + r.append(x) + return r + + +def setDbBackup(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + scDir = mw.getPanelDir() + '/scripts/backup.py' + cmd = 'python3 ' + scDir + ' database ' + args['name'] + ' 3' + os.system(cmd) + return mw.returnJson(True, 'ok') + + +# 数据库密码处理 +def myPass(act, root): + conf_file = getConf() + mw.execShell("sed -i '/user=root/d' {}".format(conf_file)) + mw.execShell("sed -i '/password=/d' {}".format(conf_file)) + if act: + mycnf = mw.readFile(conf_file) + src_dump = "[mysqldump]\n" + sub_dump = src_dump + "user=root\npassword=\"{}\"\n".format(root) + if not mycnf: + return False + mycnf = mycnf.replace(src_dump, sub_dump) + if len(mycnf) > 100: + mw.writeFile(conf_file, mycnf) + return True + return True + + +def rootPwd(): + return pSqliteDb('config').where( + 'id=?', (1,)).getField('mysql_root') + + +def importDbExternal(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + import_dir = mw.getFatherDir() + '/backup/import/' + + file_path = import_dir + file + if not os.path.exists(file_path): + return mw.returnJson(False, '文件突然消失?') + + exts = ['sql', 'gz', 'zip'] + ext = mw.getFileSuffix(file) + if ext not in exts: + return mw.returnJson(False, '导入数据库格式不对!') + + tmp = file.split('/') + tmpFile = tmp[len(tmp) - 1] + tmpFile = tmpFile.replace('.sql.' + ext, '.sql') + tmpFile = tmpFile.replace('.' + ext, '.sql') + tmpFile = tmpFile.replace('tar.', '') + + # print(tmpFile) + import_sql = "" + if file.find("sql.gz") > -1: + cmd = 'cd ' + import_dir + ' && gzip -dc ' + \ + file + " > " + import_dir + tmpFile + info = mw.execShell(cmd) + if info[1] == "": + import_sql = import_dir + tmpFile + + if file.find(".zip") > -1: + cmd = 'cd ' + import_dir + ' && unzip -o ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find("tar.gz") > -1: + cmd = 'cd ' + import_dir + ' && tar -zxvf ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find(".sql") > -1 and file.find(".sql.gz") == -1: + import_sql = import_dir + file + + if import_sql == "": + return mw.returnJson(False, '未找SQL文件') + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + my_cnf = getConf() + + myPass(True, pwd) + mysql_cmd = getServerDir() + '/bin/mysql --defaults-file=' + my_cnf + \ + ' -uroot -p"' + pwd + '" -f ' + name + ' < ' + import_sql + + # print(mysql_cmd) + rdata = mw.execShell(mysql_cmd) + myPass(False, pwd) + # print(rdata) + if ext != 'sql': + os.remove(import_sql) + + if rdata[1].lower().find('error') > -1: + return mw.returnJson(False, rdata[1]) + + return mw.returnJson(True, 'ok') + +def importDbExternalProgress(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && ' + cmd += 'python3 '+mw.getServerDir()+'/mdserver-web/plugins/mysql/index.py import_db_external_progress_bar {"file":"'+file+'","name":"'+name+'"}' + return mw.returnJson(True, 'ok',cmd) + +def importDbExternalProgressBar(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + import_dir = mw.getFatherDir() + '/backup/import/' + + file_path = import_dir + file + if not os.path.exists(file_path): + return mw.returnJson(False, '文件突然消失?') + + exts = ['sql', 'gz', 'zip'] + ext = mw.getFileSuffix(file) + if ext not in exts: + return mw.returnJson(False, '导入数据库格式不对!') + + tmp = file.split('/') + tmpFile = tmp[len(tmp) - 1] + tmpFile = tmpFile.replace('.sql.' + ext, '.sql') + tmpFile = tmpFile.replace('.' + ext, '.sql') + tmpFile = tmpFile.replace('tar.', '') + + # print(tmpFile) + import_sql = "" + if file.find("sql.gz") > -1: + cmd = 'cd ' + import_dir + ' && gzip -dc ' + \ + file + " > " + import_dir + tmpFile + info = mw.execShell(cmd) + if info[1] == "": + import_sql = import_dir + tmpFile + + if file.find(".zip") > -1: + cmd = 'cd ' + import_dir + ' && unzip -o ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find("tar.gz") > -1: + cmd = 'cd ' + import_dir + ' && tar -zxvf ' + file + mw.execShell(cmd) + import_sql = import_dir + tmpFile + + if file.find(".sql") > -1 and file.find(".sql.gz") == -1: + import_sql = import_dir + file + + if import_sql == "": + return mw.returnJson(False, '未找SQL文件') + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + my_cnf = getConf() + mysql_cmd = getServerDir() + '/bin/mysql --defaults-file=' + my_cnf + \ + ' -uroot -p"' + pwd + '" -f ' + name + mysql_cmd_progress_bar = "pv -t -p " + import_sql + '|'+ mysql_cmd + print(mysql_cmd_progress_bar) + rdata = os.system(mysql_cmd_progress_bar) + return "" + + +def importDbBackup(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + file_path = mw.getFatherDir() + '/backup/database/' + file + file_path_sql = mw.getFatherDir() + '/backup/database/' + file.replace('.gz', '') + + if not os.path.exists(file_path_sql): + cmd = 'cd ' + mw.getFatherDir() + '/backup/database && gzip -d ' + file + mw.execShell(cmd) + + local_mode = recognizeDbMode() + if local_mode == 'gtid': + pdb = pMysqlDb() + pdb.execute('reset master') + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + mysql_cmd = getServerDir() + '/bin/mysql -S ' + sock + ' -uroot -p"' + pwd + \ + '" ' + name + ' < ' + file_path_sql + + # print(mysql_cmd) + # os.system(mysql_cmd) + + rdata = mw.execShell(mysql_cmd) + if rdata[1].lower().find('error') > -1: + return mw.returnJson(False, rdata[1]) + + return mw.returnJson(True, 'ok') + + +def importDbBackupProgress(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && ' + cmd += 'python3 '+mw.getServerDir()+'/mdserver-web/plugins/mysql/index.py import_db_backup_progress_bar {"file":"'+file+'","name":"'+name+'"}' + return mw.returnJson(True, 'ok',cmd) + + return mw.returnJson(True, 'ok') + +def importDbBackupProgressBar(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + file_path = mw.getFatherDir() + '/backup/database/' + file + file_path_sql = mw.getFatherDir() + '/backup/database/' + file.replace('.gz', '') + + if not os.path.exists(file_path_sql): + cmd = 'cd ' + mw.getFatherDir() + '/backup/database && gzip -d ' + file + mw.execShell(cmd) + + local_mode = recognizeDbMode() + if local_mode == 'gtid': + pdb = pMysqlDb() + pdb.execute('reset master') + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + mysql_cmd = getServerDir() + '/bin/mysql -S ' + sock + ' -uroot -p"' + pwd + \ + '" ' + name + mysql_cmd_progress_bar = "pv -t -p " + file_path_sql + '|'+ mysql_cmd + print(mysql_cmd_progress_bar) + rdata = os.system(mysql_cmd_progress_bar) + return '' + + +def deleteDbBackup(): + args = getArgs() + data = checkArgs(args, ['filename', 'path']) + if not data[0]: + return data[1] + + path = args['path'] + full_file = "" + bkDir = mw.getFatherDir() + '/backup/database' + full_file = bkDir + '/' + args['filename'] + if path != "": + full_file = path + "/" + args['filename'] + os.remove(full_file) + return mw.returnJson(True, 'ok') + + +def getDbBackupList(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + r = getDbBackupListFunc(args['name']) + bkDir = mw.getFatherDir() + '/backup/database' + rr = [] + for x in range(0, len(r)): + p = bkDir + '/' + r[x] + if not os.path.exists(p): + continue + data = {} + data['name'] = r[x] + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + return mw.returnJson(True, 'ok', rr) + + +def getDbBackupImportList(): + + bkImportDir = mw.getFatherDir() + '/backup/import' + if not os.path.exists(bkImportDir): + os.mkdir(bkImportDir) + + blist = os.listdir(bkImportDir) + + rr = [] + for x in range(0, len(blist)): + name = blist[x] + p = bkImportDir + '/' + name + data = {} + data['name'] = name + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + rdata = { + "list": rr, + "upload_dir": bkImportDir, + } + return mw.returnJson(True, 'ok', rdata) + + +def getDbList(): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('databases') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,pid,name,username,password,accept,rw,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + + for x in range(0, len(clist)): + dbname = clist[x]['name'] + blist = getDbBackupListFunc(dbname) + # print(blist) + clist[x]['is_backup'] = False + if len(blist) > 0: + clist[x]['is_backup'] = True + + count = conn.where(condition, ()).count() + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + info = {} + info['root_pwd'] = pSqliteDb('config').where( + 'id=?', (1,)).getField('mysql_root') + data['info'] = info + + return mw.getJson(data) + + +def syncGetDatabases(): + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + data = pdb.query('show databases') + isError = isSqlError(data) + if isError != None: + return isError + users = pdb.query( + "select User,Host from mysql.user where User!='root' AND Host!='localhost' AND Host!=''") + nameArr = ['information_schema', 'performance_schema', 'mysql', 'sys'] + n = 0 + + # print(users) + for value in data: + vdb_name = value["Database"] + b = False + for key in nameArr: + if vdb_name == key: + b = True + break + if b: + continue + if psdb.where("name=?", (vdb_name,)).count() > 0: + continue + host = '127.0.0.1' + for user in users: + if vdb_name == user["User"]: + host = user["Host"] + break + + ps = vdb_name + if vdb_name == 'test': + ps = mw.getMsg('DATABASE_TEST') + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + if psdb.add('name,username,password,accept,ps,addtime', (vdb_name, vdb_name, '', host, ps, addTime)): + n += 1 + + msg = mw.getInfo('本次共从服务器获取了{1}个数据库!', (str(n),)) + return mw.returnJson(True, msg) + + +def toDbBase(find): + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + if len(find['password']) < 3: + find['username'] = find['name'] + find['password'] = mw.md5(str(time.time()) + find['name'])[0:10] + psdb.where("id=?", (find['id'],)).save( + 'password,username', (find['password'], find['username'])) + + result = pdb.execute("create database `" + find['name'] + "`") + if "using password:" in str(result): + return -1 + if "Connection refused" in str(result): + return -1 + + password = find['password'] + __createUser(find['name'], find['username'], password, find['accept']) + return 1 + + +def syncToDatabases(): + args = getArgs() + data = checkArgs(args, ['type', 'ids']) + if not data[0]: + return data[1] + + pdb = pMysqlDb() + result = pdb.execute("show databases") + isError = isSqlError(result) + if isError: + return isError + + stype = int(args['type']) + psdb = pSqliteDb('databases') + n = 0 + + if stype == 0: + data = psdb.field('id,name,username,password,accept').select() + for value in data: + result = toDbBase(value) + if result == 1: + n += 1 + else: + data = json.loads(args['ids']) + for value in data: + find = psdb.where("id=?", (value,)).field( + 'id,name,username,password,accept').find() + # print find + result = toDbBase(find) + if result == 1: + n += 1 + msg = mw.getInfo('本次共同步了{1}个数据库!', (str(n),)) + return mw.returnJson(True, msg) + +def adjust_password_policy(version): + if version.startswith("8."): + serverdir = getServerDir() + mw.execShell(serverdir+f"/bin/mysql -u root -e \"SET GLOBAL validate_password.policy=LOW;\"") + mw.execShell(serverdir+f"/bin/mysql -u root -e \"SET GLOBAL validate_password.length=4;\"") + +def setRootPwdForce(new_password,version=''): + # stop(version) + # | awk '{print $2}'| xargs kill + data = mw.execShell("ps -ef|grep mysql|grep -v plugins |grep -v grep | awk '{print $2}'| xargs kill") + # print(data) + time.sleep(1) + + serverdir = getServerDir() + + # 启动安全模式 + cmd_msafe = serverdir+"/bin/mysqld_safe --skip-grant-tables --skip-networking" + # print("cmd_msafe",cmd_msafe) + subprocess.Popen(cmd_msafe, stdout=subprocess.PIPE, shell=True,bufsize=4096, stderr=subprocess.PIPE) + # 等待MySQL安全模式启动... + time.sleep(5) + + if version.startswith("5.5") or version.startswith("5.6"): + cmd_mod_root = serverdir+f"/bin/mysql -u root -e \"UPDATE mysql.user SET Password=PASSWORD('{new_password}') WHERE user='root'; FLUSH PRIVILEGES;\"" + data = mw.execShell(cmd_mod_root) + # print("修改root密码", cmd_mod_root) + # print(data) + else: + cmd_clear_root = serverdir+"/bin/mysql -u root -e \"UPDATE mysql.user SET authentication_string = '' WHERE user = 'root'; FLUSH PRIVILEGES;\"" + data = mw.execShell(cmd_clear_root) + # print(cmd_clear_root,"清空root密码",data) + + data = mw.execShell("ps -ef|grep mysql|grep -v plugins |grep -v grep | awk '{print $2}'| xargs kill") + # print("停止安全模式",data) + + # 正常启动MySQL + start(version) + time.sleep(5) + + if not version.startswith("5.5") and not version.startswith("5.6"): + # 针对MySQL 8.0+调整密码策略 + if version.startswith("8."): + adjust_password_policy(version) + # 设置新密码 + cmd_newpass = serverdir+f"/bin/mysql -u root -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '{new_password}'; FLUSH PRIVILEGES;\"" + data = mw.execShell(cmd_newpass) + # print("新密码",cmd_newpass) + # print(data) + + # 验证密码 + cmd = serverdir+f"/bin/mysql -u root -p'{new_password}' -e 'SHOW DATABASES;'" + data = mw.execShell(cmd) + if data[1] == '': + return True + return False + + +def setRootPwd(version=''): + args = getArgs() + data = checkArgs(args, ['password']) + if not data[0]: + return data[1] + password = args['password'] + + #强制修改 + force = 0 + if 'force' in args and args['force'] == '1': + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (password,)) + return mw.returnJson(True, '【强制本地记录】数据库root密码修改成功(立马检查)!') + if 'force' in args and args['force'] == '2': + force = 2 + ok = setRootPwdForce(password,version) + + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (password,)) + if ok and version.startswith("8"): + pdb = pMysqlDb() + pdb.query("SET GLOBAL validate_password.policy=MEDIUM;") + return mw.returnJson(True, '【强制】数据库root密码修改成功!') + + try: + pdb = pMysqlDb() + result = pdb.query("show databases") + isError = isSqlError(result) + if isError != None: + return isError + + if version.find('5.7') > -1 or version.find('8.0') > -1: + pdb.execute( + "UPDATE mysql.user SET authentication_string='' WHERE user='root'") + pdb.execute( + "ALTER USER 'root'@'localhost' IDENTIFIED BY '%s'" % password) + pdb.execute( + "ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY '%s'" % password) + else: + result = pdb.execute( + "update mysql.user set Password=password('" + password + "') where User='root'") + pdb.execute("flush privileges") + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (password,)) + + msg = '' + if force == 1: + msg = ',无须强制!' + + return mw.returnJson(True, '数据库root密码修改成功!'+msg) + except Exception as ex: + return mw.returnJson(False, '修改错误:' + str(ex)) + + +def setUserPwd(version=''): + args = getArgs() + data = checkArgs(args, ['password', 'name']) + if not data[0]: + return data[1] + + newpassword = args['password'] + username = args['name'] + uid = args['id'] + try: + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + name = psdb.where('id=?', (uid,)).getField('name') + + if version.find('5.7') > -1 or version.find('8.0') > -1: + accept = pdb.query( + "select Host from mysql.user where User='" + name + "' AND Host!='localhost'") + t1 = pdb.execute( + "update mysql.user set authentication_string='' where User='" + username + "'") + # print(t1) + result = pdb.execute( + "ALTER USER `%s`@`localhost` IDENTIFIED BY '%s'" % (username, newpassword)) + # print(result) + for my_host in accept: + t2 = pdb.execute("ALTER USER `%s`@`%s` IDENTIFIED BY '%s'" % ( + username, my_host["Host"], newpassword)) + # print(t2) + else: + result = pdb.execute("update mysql.user set Password=password('" + + newpassword + "') where User='" + username + "'") + + pdb.execute("flush privileges") + psdb.where("id=?", (uid,)).setField('password', newpassword) + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]密码成功!', (name,))) + except Exception as ex: + return mw.returnJson(False, mw.getInfo('修改数据库[{1}]密码失败[{2}]!', (name, str(ex),))) + + +def setDbPs(): + args = getArgs() + data = checkArgs(args, ['id', 'name', 'ps']) + if not data[0]: + return data[1] + + ps = args['ps'] + sid = args['id'] + name = args['name'] + try: + psdb = pSqliteDb('databases') + psdb.where("id=?", (sid,)).setField('ps', ps) + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]备注成功!', (name,))) + except Exception as e: + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]备注失败!', (name,))) + + +def addDb(): + args = getArgs() + data = checkArgs(args, + ['password', 'name', 'codeing', 'db_user', 'dataAccess', 'ps']) + if not data[0]: + return data[1] + + if not 'address' in args: + address = '' + else: + address = args['address'].strip() + + dbname = args['name'].strip() + dbuser = args['db_user'].strip() + codeing = args['codeing'].strip() + password = args['password'].strip() + dataAccess = args['dataAccess'].strip() + ps = args['ps'].strip() + + reg = r"^[\w-]+$" + if not re.match(reg, args['name']): + return mw.returnJson(False, '数据库名称不能带有特殊符号!') + checks = ['root', 'mysql', 'test', 'sys', 'performance_schema','information_schema'] + if dbuser in checks or len(dbuser) < 1: + return mw.returnJson(False, '数据库用户名不合法!') + if dbname in checks or len(dbname) < 1: + return mw.returnJson(False, '数据库名称不合法!') + + if len(password) < 1: + password = mw.md5(time.time())[0:8] + + wheres = { + 'utf8': 'utf8_general_ci', + 'utf8mb4': 'utf8mb4_general_ci', + 'gbk': 'gbk_chinese_ci', + 'big5': 'big5_chinese_ci' + } + codeStr = wheres[codeing] + + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + + if psdb.where("name=? or username=?", (dbname, dbuser)).count(): + return mw.returnJson(False, '数据库已存在!') + + result = pdb.execute("create database `" + dbname + + "` DEFAULT CHARACTER SET " + codeing + " COLLATE " + codeStr) + # print result + isError = isSqlError(result) + if isError != None: + return isError + + pdb.execute("drop user '" + dbuser + "'@'localhost'") + for a in address.split(','): + pdb.execute("drop user '" + dbuser + "'@'" + a + "'") + + __createUser(dbname, dbuser, password, address) + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + psdb.add('pid,name,username,password,accept,ps,addtime', + (0, dbname, dbuser, password, address, ps, addTime)) + return mw.returnJson(True, '添加成功!') + + +def delDb(): + args = getArgs() + data = checkArgs(args, ['id', 'name']) + if not data[0]: + return data[1] + try: + sid = args['id'] + name = args['name'] + psdb = pSqliteDb('databases') + pdb = pMysqlDb() + find = psdb.where("id=?", (sid,)).field( + 'id,pid,name,username,password,accept,ps,addtime').find() + accept = find['accept'] + username = find['username'] + + # 删除MYSQL + result = pdb.execute("drop database `" + name + "`") + + users = pdb.query("select Host from mysql.user where User='" + + username + "' AND Host!='localhost'") + pdb.execute("drop user '" + username + "'@'localhost'") + for us in users: + pdb.execute("drop user '" + username + "'@'" + us["Host"] + "'") + pdb.execute("flush privileges") + + # 删除SQLITE + psdb.where("id=?", (sid,)).delete() + return mw.returnJson(True, '删除成功!') + except Exception as ex: + return mw.returnJson(False, '删除失败!' + str(ex)) + + +def getDbAccess(): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + username = args['username'] + pdb = pMysqlDb() + + users = pdb.query("select Host from mysql.user where User='" + + username + "' AND Host!='localhost'") + + isError = isSqlError(users) + if isError != None: + return isError + + if len(users) < 1: + return mw.returnJson(True, "127.0.0.1") + accs = [] + for c in users: + accs.append(c["Host"]) + userStr = ','.join(accs) + return mw.returnJson(True, userStr) + + +def setDbAccess(): + args = getArgs() + data = checkArgs(args, ['username', 'access']) + if not data[0]: + return data[1] + name = args['username'] + access = args['access'] + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + + dbname = psdb.where('username=?', (name,)).getField('name') + + if name == 'root': + password = pSqliteDb('config').where( + 'id=?', (1,)).getField('mysql_root') + else: + password = psdb.where("username=?", (name,)).getField('password') + + users = pdb.query("select Host from mysql.user where User='" + + name + "' AND Host!='localhost'") + + for us in users: + pdb.execute("drop user '" + name + "'@'" + us["Host"] + "'") + + __createUser(dbname, name, password, access) + + psdb.where('username=?', (name,)).save('accept,rw', (access, 'rw',)) + return mw.returnJson(True, '设置成功!') + + +def openSkipGrantTables(): + mycnf = getConf() + content = mw.readFile(mycnf) + content = content.replace('#skip-grant-tables','skip-grant-tables') + mw.writeFile(mycnf, content) + return True + +def closeSkipGrantTables(): + mycnf = getConf() + content = mw.readFile(mycnf) + content = content.replace('skip-grant-tables','#skip-grant-tables') + mw.writeFile(mycnf, content) + return True + + +def resetDbRootPwd(version): + serverdir = getServerDir() + myconf = serverdir + "/etc/my.cnf" + pwd = mw.getRandomString(16) + + pSqliteDb('config').where('id=?', (1,)).save('mysql_root', (pwd,)) + + mdb8 = getMdb8Ver() + if not mw.inArray(mdb8, version): + cmd_pass = serverdir + '/bin/mysql --defaults-file=' + myconf + ' -uroot -e' + cmd_pass = cmd_pass + '"UPDATE mysql.user SET password=PASSWORD(\'' + pwd + "') WHERE user='root';" + cmd_pass = cmd_pass + 'flush privileges;"' + data = mw.execShell(cmd_pass) + # print(data) + else: + auth_policy = getAuthPolicy() + + reset_pwd = 'flush privileges;' + reset_pwd = reset_pwd + \ + "UPDATE mysql.user SET authentication_string='' WHERE user='root';" + reset_pwd = reset_pwd + "flush privileges;" + reset_pwd = reset_pwd + \ + "alter user 'root'@'localhost' IDENTIFIED by '" + pwd + "';" + reset_pwd = reset_pwd + \ + "alter user 'root'@'localhost' IDENTIFIED WITH "+auth_policy+" by '" + pwd + "';" + reset_pwd = reset_pwd + "flush privileges;" + + tmp_file = "/tmp/mysql_init_tmp.log" + mw.writeFile(tmp_file, reset_pwd) + cmd_pass = serverdir + '/bin/mysql --defaults-file=' + myconf + ' -uroot -proot < ' + tmp_file + + data = mw.execShell(cmd_pass) + # print(data) + os.remove(tmp_file) + return True + +def fixDbAccess(version): + + pdb = pMysqlDb() + mdb_ddir = getDataDir() + if not os.path.exists(mdb_ddir): + return mw.returnJson(False, '数据目录不存在,尝试重启重建!') + + try: + data = pdb.query('show databases') + isError = isSqlError(data) + if isError != None: + + # 重置密码 + appCMD(version, 'stop') + openSkipGrantTables() + appCMD(version, 'start') + time.sleep(3) + resetDbRootPwd(version) + + appCMD(version, 'stop') + closeSkipGrantTables() + appCMD(version, 'start') + + return mw.returnJson(True, '修复成功!') + return mw.returnJson(True, '正常无需修复!') + except Exception as e: + return mw.returnJson(False, '修复失败请重试!') + + +def setDbRw(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'id', 'rw']) + if not data[0]: + return data[1] + + username = args['username'] + uid = args['id'] + rw = args['rw'] + + pdb = pMysqlDb() + psdb = pSqliteDb('databases') + dbname = psdb.where("id=?", (uid,)).getField('name') + users = pdb.query( + "select Host from mysql.user where User='" + username + "'") + + # show grants for demo@"127.0.0.1"; + for x in users: + # REVOKE ALL PRIVILEGES ON `imail`.* FROM 'imail'@'127.0.0.1'; + + sql = "REVOKE ALL PRIVILEGES ON `" + dbname + \ + "`.* FROM '" + username + "'@'" + x["Host"] + "';" + r = pdb.query(sql) + # print(sql, r) + + if rw == 'rw': + sql = "GRANT SELECT, INSERT, UPDATE, DELETE ON " + dbname + ".* TO " + \ + username + "@'" + x["Host"] + "'" + elif rw == 'r': + sql = "GRANT SELECT ON " + dbname + ".* TO " + \ + username + "@'" + x["Host"] + "'" + else: + sql = "GRANT all privileges ON " + dbname + ".* TO " + \ + username + "@'" + x["Host"] + "'" + pdb.execute(sql) + pdb.execute("flush privileges") + r = psdb.where("id=?", (uid,)).setField('rw', rw) + # print(r) + return mw.returnJson(True, '切换成功!') + + +def getDbInfo(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + db_name = args['name'] + pdb = pMysqlDb() + # print 'show tables from `%s`' % db_name + tables = pdb.query('show tables from `%s`' % db_name) + + ret = {} + sql = "select sum(DATA_LENGTH)+sum(INDEX_LENGTH) as sum_size from information_schema.tables where table_schema='%s'" % db_name + data_sum = pdb.query(sql) + + data = 0 + if data_sum[0]['sum_size'] != None: + data = data_sum[0]['sum_size'] + + ret['data_size'] = mw.toSize(data) + ret['database'] = db_name + + ret3 = [] + table_key = "Tables_in_" + db_name + for i in tables: + tb_sql = "show table status from `%s` where name = '%s'" % (db_name, i[ + table_key]) + table = pdb.query(tb_sql) + + tmp = {} + tmp['type'] = table[0]["Engine"] + tmp['rows_count'] = table[0]["Rows"] + tmp['collation'] = table[0]["Collation"] + + data_size = 0 + if table[0]['Avg_row_length'] != None: + data_size = table[0]['Avg_row_length'] + + if table[0]['Data_length'] != None: + data_size = table[0]['Data_length'] + + tmp['data_byte'] = data_size + tmp['data_size'] = mw.toSize(data_size) + tmp['table_name'] = table[0]["Name"] + ret3.append(tmp) + + ret['tables'] = (ret3) + + return mw.getJson(ret) + + +def repairTable(): + args = getArgs() + data = checkArgs(args, ['db_name', 'tables']) + if not data[0]: + return data[1] + + db_name = args['db_name'] + tables = json.loads(args['tables']) + pdb = pMysqlDb() + mtable = pdb.query('show tables from `%s`' % db_name) + + ret = [] + key = "Tables_in_" + db_name + for i in mtable: + for tn in tables: + if tn == i[key]: + ret.append(tn) + + if len(ret) > 0: + for i in ret: + pdb.execute('REPAIR TABLE `%s`.`%s`' % (db_name, i)) + return mw.returnJson(True, "修复完成!") + return mw.returnJson(False, "修复失败!") + + +def optTable(): + args = getArgs() + data = checkArgs(args, ['db_name', 'tables']) + if not data[0]: + return data[1] + + db_name = args['db_name'] + tables = json.loads(args['tables']) + pdb = pMysqlDb() + mtable = pdb.query('show tables from `%s`' % db_name) + ret = [] + key = "Tables_in_" + db_name + for i in mtable: + for tn in tables: + if tn == i[key]: + ret.append(tn) + + if len(ret) > 0: + for i in ret: + pdb.execute('OPTIMIZE TABLE `%s`.`%s`' % (db_name, i)) + return mw.returnJson(True, "优化成功!") + return mw.returnJson(False, "优化失败或者已经优化过了!") + + +def alterTable(): + args = getArgs() + data = checkArgs(args, ['db_name', 'tables']) + if not data[0]: + return data[1] + + db_name = args['db_name'] + tables = json.loads(args['tables']) + table_type = args['table_type'] + pdb = pMysqlDb() + mtable = pdb.query('show tables from `%s`' % db_name) + + ret = [] + key = "Tables_in_" + db_name + for i in mtable: + for tn in tables: + if tn == i[key]: + ret.append(tn) + + if len(ret) > 0: + for i in ret: + pdb.execute('alter table `%s`.`%s` ENGINE=`%s`' % + (db_name, i, table_type)) + return mw.returnJson(True, "更改成功!") + return mw.returnJson(False, "更改失败!") + + +def getTotalStatistics(): + st = status() + data = {} + + isInstall = os.path.exists(getServerDir() + '/version.pl') + + if st == 'start' and isInstall: + data['status'] = True + data['count'] = pSqliteDb('databases').count() + data['ver'] = mw.readFile(getServerDir() + '/version.pl').strip() + return mw.returnJson(True, 'ok', data) + else: + data['status'] = False + data['count'] = 0 + return mw.returnJson(False, 'fail', data) + + +def recognizeDbMode(): + conf = getConf() + con = mw.readFile(conf) + rep = r"!include %s/(.*)?\.cnf" % (getServerDir() + "/etc/mode",) + mode = 'none' + try: + data = re.findall(rep, con, re.M) + mode = data[0] + except Exception as e: + pass + return mode + + +def getDbrunMode(version=''): + mode = recognizeDbMode() + return mw.returnJson(True, "ok", {'mode': mode}) + + +def setDbrunMode(version=''): + if version == '5.5': + return mw.returnJson(False, "不支持切换") + + args = getArgs() + data = checkArgs(args, ['mode', 'reload']) + if not data[0]: + return data[1] + + mode = args['mode'] + dbreload = args['reload'] + + if not mode in ['classic', 'gtid']: + return mw.returnJson(False, "mode的值无效:" + mode) + + origin_mode = recognizeDbMode() + path = getConf() + con = mw.readFile(path) + rep = r"!include %s/%s\.cnf" % (getServerDir() + "/etc/mode", origin_mode) + rep_after = "!include %s/%s.cnf" % (getServerDir() + "/etc/mode", mode) + con = re.sub(rep, rep_after, con) + mw.writeFile(path, con) + + if version == '5.6': + dbreload = 'yes' + else: + db = pMysqlDb() + # The value of @@GLOBAL.GTID_MODE can only be changed one step at a + # time: OFF <-> OFF_PERMISSIVE <-> ON_PERMISSIVE <-> ON. Also note that + # this value must be stepped up or down simultaneously on all servers. + # See the Manual for instructions. + if mode == 'classic': + db.query('set global enforce_gtid_consistency=off') + db.query('set global gtid_mode=on') + db.query('set global gtid_mode=on_permissive') + db.query('set global gtid_mode=off_permissive') + db.query('set global gtid_mode=off') + elif mode == 'gtid': + db.query('set global enforce_gtid_consistency=on') + db.query('set global gtid_mode=off') + db.query('set global gtid_mode=off_permissive') + db.query('set global gtid_mode=on_permissive') + db.query('set global gtid_mode=on') + + if dbreload == "yes": + restart(version) + + return mw.returnJson(True, "切换成功!") + + +def findBinlogDoDb(): + conf = getConf() + con = mw.readFile(conf) + rep = r"binlog-do-db\s*?=\s*?(.*)" + dodb = re.findall(rep, con, re.M) + return dodb + + +def findBinlogSlaveDoDb(): + conf = getConf() + con = mw.readFile(conf) + rep = r"replicate-do-db\s*?=\s*?(.*)" + dodb = re.findall(rep, con, re.M) + return dodb + + +def setDbMasterAccess(): + args = getArgs() + data = checkArgs(args, ['username', 'access']) + if not data[0]: + return data[1] + username = args['username'] + access = args['access'] + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + password = psdb.where("username=?", (username,)).getField('password') + users = pdb.query("select Host from mysql.user where User='" + + username + "' AND Host!='localhost'") + for us in users: + pdb.execute("drop user '" + username + "'@'" + us["Host"] + "'") + + dbname = '*' + for a in access.split(','): + pdb.execute( + "CREATE USER `%s`@`%s` IDENTIFIED BY '%s'" % (username, a, password)) + pdb.execute( + "grant all privileges on %s.* to `%s`@`%s`" % (dbname, username, a)) + + pdb.execute("flush privileges") + psdb.where('username=?', (username,)).save('accept', (access,)) + return mw.returnJson(True, '设置成功!') + + +def resetMaster(version=''): + pdb = pMysqlDb() + r = pdb.execute('reset master') + isError = isSqlError(r) + if isError != None: + return isError + return mw.returnJson(True, '重置成功!') + + +def getMasterDbList(version=''): + try: + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('databases') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + dodb = findBinlogDoDb() + data['dodb'] = dodb + + slave_dodb = findBinlogSlaveDoDb() + + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,pid,name,username,password,accept,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + count = conn.where(condition, ()).count() + + for x in range(0, len(clist)): + if clist[x]['name'] in dodb: + clist[x]['master'] = 1 + else: + clist[x]['master'] = 0 + + if clist[x]['name'] in slave_dodb: + clist[x]['slave'] = 1 + else: + clist[x]['slave'] = 0 + + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + return mw.getJson(data) + except Exception as e: + return mw.returnJson(False, "数据库密码错误,在管理列表-点击【修复】!") + + +def setDbMaster(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + conf = getConf() + con = mw.readFile(conf) + rep = r"(binlog-do-db\s*?=\s*?(.*))" + dodb = re.findall(rep, con, re.M) + + isHas = False + for x in range(0, len(dodb)): + + if dodb[x][1] == args['name']: + isHas = True + + con = con.replace(dodb[x][0] + "\n", '') + mw.writeFile(conf, con) + + if not isHas: + prefix = '#binlog-do-db' + con = con.replace(prefix, prefix + "\nbinlog-do-db=" + args['name']) + mw.writeFile(conf, con) + + restart(version) + time.sleep(4) + return mw.returnJson(True, '设置成功', [args, dodb]) + + +def setDbSlave(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + conf = getConf() + con = mw.readFile(conf) + rep = r"(replicate-do-db\s*?=\s*?(.*))" + dodb = re.findall(rep, con, re.M) + + isHas = False + for x in range(0, len(dodb)): + if dodb[x][1] == args['name']: + isHas = True + + con = con.replace(dodb[x][0] + "\n", '') + mw.writeFile(conf, con) + + if not isHas: + prefix = '#replicate-do-db' + con = con.replace( + prefix, prefix + "\nreplicate-do-db=" + args['name']) + mw.writeFile(conf, con) + + restart(version) + time.sleep(4) + return mw.returnJson(True, '设置成功', [args, dodb]) + + +def getMasterStatus(version=''): + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动,或正在启动中...!', []) + + query_status_cmd = 'show slave status' + is_mdb8 = False + mdb8 = getMdb8Ver() + if mw.inArray(mdb8, version): + is_mdb8 = True + query_status_cmd = 'show replica status' + + try: + + conf = getConf() + content = mw.readFile(conf) + master_status = False + if content.find('#log-bin') == -1 and content.find('log-bin') > 1: + dodb = findBinlogDoDb() + if len(dodb) > 0: + master_status = True + + data = {} + data['mode'] = recognizeDbMode() + data['status'] = master_status + data['slave_status'] = False + + db = pMysqlDb() + dlist = db.query(query_status_cmd) + + for v in dlist: + + if is_mdb8: + if (v["Replica_IO_Running"] == 'Yes' or v["Replica_SQL_Running"] == 'Yes'): + data['slave_status'] = True + else: + if (v["Slave_IO_Running"] == 'Yes' or v["Slave_SQL_Running"] == 'Yes'): + data['slave_status'] = True + + return mw.returnJson(master_status, '设置成功', data) + except Exception as e: + return mw.returnJson(False, "数据库密码错误,在管理列表-点击【修复】,"+str(mw.getTracebackInfo()), 'pwd') + + +def setMasterStatus(version=''): + + conf = getConf() + con = mw.readFile(conf) + + if con.find('#log-bin') != -1: + return mw.returnJson(False, '必须开启二进制日志') + + sign = 'mdserver_ms_open' + + dodb = findBinlogDoDb() + if not sign in dodb: + prefix = '#binlog-do-db' + con = con.replace(prefix, prefix + "\nbinlog-do-db=" + sign) + mw.writeFile(conf, con) + else: + con = con.replace("binlog-do-db=" + sign + "\n", '') + rep = r"(binlog-do-db\s*?=\s*?(.*))" + dodb = re.findall(rep, con, re.M) + for x in range(0, len(dodb)): + con = con.replace(dodb[x][0] + "\n", '') + mw.writeFile(conf, con) + + restart(version) + return mw.returnJson(True, '设置成功') + + +def getMasterRepSlaveList(version=''): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('master_replication_user') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,username,password,accept,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + count = conn.where(condition, ()).count() + + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'getMasterRepSlaveList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def addMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'password']) + if not data[0]: + return data[1] + + if not 'address' in args: + address = '' + else: + address = args['address'].strip() + + username = args['username'].strip() + password = args['password'].strip() + # ps = args['ps'].strip() + # address = args['address'].strip() + # dataAccess = args['dataAccess'].strip() + + reg = r"^[\w-]+$" + if not re.match(reg, username): + return mw.returnJson(False, '用户名不能带有特殊符号!') + checks = ['root', 'mysql', 'test', 'sys', 'performance_schema','information_schema'] + if username in checks or len(username) < 1: + return mw.returnJson(False, '用户名不合法!') + if password in checks or len(password) < 1: + return mw.returnJson(False, '密码不合法!') + + if len(password) < 1: + password = mw.md5(time.time())[0:8] + + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + + auth_policy = getAuthPolicy() + + if psdb.where("username=?", (username)).count() > 0: + return mw.returnJson(False, '用户已存在!') + + mdb8 = getMdb8Ver() + if mw.inArray(mdb8,version): + sql = "CREATE USER '" + username + \ + "' IDENTIFIED WITH "+auth_policy+" BY '" + password + "';" + pdb.execute(sql) + sql = "grant replication slave on *.* to '" + username + "'@'%';" + result = pdb.execute(sql) + isError = isSqlError(result) + if isError != None: + return isError + else: + sql = "grant replication SLAVE ON *.* TO '" + username + \ + "'@'%' identified by '" + password + "';" + result = pdb.execute(sql) + isError = isSqlError(result) + if isError != None: + return isError + + sql_select = "grant select,reload,REPLICATION CLIENT,PROCESS on *.* to " + username + "@'%';" + pdb.execute(sql_select) + pdb.execute('FLUSH PRIVILEGES;') + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + psdb.add('username,password,accept,ps,addtime', + (username, password, '%', '', addTime)) + return mw.returnJson(True, '添加成功!') + + +def getMasterRepSlaveUserCmd(version): + args = getArgs() + data = checkArgs(args, ['username', 'db']) + if not data[0]: + return data[1] + + psdb = pSqliteDb('master_replication_user') + f = 'username,password' + username = args['username'] + if username == '': + count = psdb.count() + if count == 0: + return mw.returnJson(False, '请添加同步账户!') + + clist = psdb.field(f).limit('1').order('id desc').select() + else: + clist = psdb.field(f).where("username=?", (username,)).limit( + '1').order('id desc').select() + + ip = mw.getLocalIp() + port = getMyPort() + db = pMysqlDb() + + cmd_status = "show master status" + if pk_version.parse(version) > pk_version.parse("8.0"): + cmd_status = "SHOW BINARY LOG STATUS" + mstatus = db.query(cmd_status) + if len(mstatus) == 0: + return mw.returnJson(False, '未开启!') + + mode = recognizeDbMode() + + sid = getDbServerId() + channel_name = "" + if sid != '': + channel_name = " for channel 'r{}'".format(sid) + + mdb8 = getMdb8Ver() + sql = '' + if not mw.inArray(mdb8,version): + base_sql = "CHANGE MASTER TO MASTER_HOST='" + ip + "', MASTER_PORT=" + port + ", MASTER_USER='" + \ + clist[0]['username'] + "', MASTER_PASSWORD='" + clist[0]['password'] + "'" + + sql += base_sql; + sql += "

                              "; + # sql += base_sql + ", MASTER_AUTO_POSITION=1" + channel_name + sql += base_sql + channel_name + sql += "

                              "; + + sql += base_sql + ", MASTER_LOG_FILE='" + mstatus[0]["File"] + "',MASTER_LOG_POS=" + str(mstatus[0]["Position"]) + channel_name + else: + base_sql = "CHANGE REPLICATION SOURCE TO SOURCE_HOST='" + ip + "', SOURCE_PORT=" + port + ", SOURCE_USER='" + \ + clist[0]['username'] + "', SOURCE_PASSWORD='" + clist[0]['password']+"'" + sql += base_sql; + sql += "

                              "; + # sql += base_sql + ", MASTER_AUTO_POSITION=1" + channel_name + sql += base_sql + channel_name + sql += "

                              "; + sql += base_sql + ", SOURCE_LOG_FILE='" + mstatus[0]["File"] + "',SOURCE_LOG_POS=" + str(mstatus[0]["Position"]) + channel_name + + + data = {} + data['cmd'] = sql + data["info"] = clist[0] + data['mode'] = mode + + return mw.returnJson(True, 'ok!', data) + + +def delMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + + name = args['username'] + + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + pdb.execute("drop user '" + name + "'@'%'") + pdb.execute("drop user '" + name + "'@'localhost'") + + users = pdb.query("select Host from mysql.user where User='" + + name + "' AND Host!='localhost'") + for us in users: + pdb.execute("drop user '" + name + "'@'" + us["Host"] + "'") + + psdb.where("username=?", (args['username'],)).delete() + + return mw.returnJson(True, '删除成功!') + + +def updateMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'password']) + if not data[0]: + return data[1] + + pdb = pMysqlDb() + psdb = pSqliteDb('master_replication_user') + pdb.execute("drop user '" + args['username'] + "'@'%'") + + pdb.execute("GRANT REPLICATION SLAVE ON *.* TO '" + + args['username'] + "'@'%' identified by '" + args['password'] + "'") + + psdb.where("username=?", (args['username'],)).save( + 'password', args['password']) + + return mw.returnJson(True, '更新成功!') + + +def getSlaveSSHList(version=''): + args = getArgs() + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + conn = pSqliteDb('slave_id_rsa') + limit = str((page - 1) * page_size) + ',' + str(page_size) + + field = 'id,ip,port,db_user,id_rsa,ps,addtime' + clist = conn.field(field).limit(limit).order('id desc').select() + count = conn.count() + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = args['tojs'] + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def getSlaveSyncUserByIp(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_sync_user') + data = conn.field('ip,port,user,pass,mode,cmd').where( + "ip=?", (ip,)).select() + return mw.returnJson(True, 'ok', data) + + +def addSlaveSyncUser(version=''): + import base64 + + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + if ip == "": + return mw.returnJson(True, 'ok') + + data = checkArgs(args, ['port', 'user', 'pass', 'mode']) + if not data[0]: + return data[1] + + cmd = args['cmd'] + port = args['port'] + user = args['user'] + apass = args['pass'] + mode = args['mode'] + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + conn = pSqliteDb('slave_sync_user') + data = conn.field('ip').where("ip=?", (ip,)).select() + if len(data) > 0: + res = conn.where("ip=?", (ip,)).save( + 'port,user,pass,mode,cmd', (port, user, apass, mode, cmd)) + else: + conn.add('ip,port,user,cmd,user,pass,mode,addtime', + (ip, port, user, cmd, user, apass, mode, addTime)) + + return mw.returnJson(True, '设置成功!') + + +def delSlaveSyncUser(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_sync_user') + conn.where("ip=?", (ip,)).delete() + return mw.returnJson(True, '删除成功!') + + +def getSlaveSyncUserList(version=''): + args = getArgs() + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + conn = pSqliteDb('slave_sync_user') + limit = str((page - 1) * page_size) + ',' + str(page_size) + + field = 'id,ip,port,user,pass,cmd,addtime' + clist = conn.field(field).limit(limit).order('id desc').select() + count = conn.count() + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = args['tojs'] + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def getSyncModeFile(): + return getServerDir() + "/sync.mode" + + +def getSlaveSyncMode(version): + sync_mode = getSyncModeFile() + if os.path.exists(sync_mode): + mode = mw.readFile(sync_mode).strip() + return mw.returnJson(True, 'ok', mode) + return mw.returnJson(False, 'fail') + + +def setSlaveSyncMode(version): + args = getArgs() + data = checkArgs(args, ['mode']) + if not data[0]: + return data[1] + mode = args['mode'] + sync_mode = getSyncModeFile() + + if mode == 'none': + os.remove(sync_mode) + else: + mw.writeFile(sync_mode, mode) + return mw.returnJson(True, '设置成功', mode) + + +def getSlaveSSHByIp(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_id_rsa') + data = conn.field('ip,port,db_user,id_rsa').where("ip=?", (ip,)).select() + return mw.returnJson(True, 'ok', data) + + +def addSlaveSSH(version=''): + import base64 + + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + if ip == "": + return mw.returnJson(True, 'ok') + + data = checkArgs(args, ['port', 'id_rsa', 'db_user']) + if not data[0]: + return data[1] + + id_rsa = args['id_rsa'] + port = args['port'] + db_user = args['db_user'] + user = 'root' + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + conn = pSqliteDb('slave_id_rsa') + data = conn.field('ip,id_rsa').where("ip=?", (ip,)).select() + if len(data) > 0: + res = conn.where("ip=?", (ip,)).save( + 'port,id_rsa,db_user', (port, id_rsa, db_user)) + else: + conn.add('ip,port,user,id_rsa,db_user,ps,addtime', + (ip, port, user, id_rsa, db_user, '', addTime)) + + return mw.returnJson(True, '设置成功!') + + +def delSlaveSSH(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_id_rsa') + conn.where("ip=?", (ip,)).delete() + return mw.returnJson(True, '删除SSH成功!') + + +def updateSlaveSSH(version=''): + args = getArgs() + data = checkArgs(args, ['ip', 'id_rsa']) + if not data[0]: + return data[1] + + ip = args['ip'] + id_rsa = args['id_rsa'] + conn = pSqliteDb('slave_id_rsa') + conn.where("ip=?", (ip,)).save('id_rsa', (id_rsa,)) + return mw.returnJson(True, 'ok') + + +def getSlaveList(version=''): + + query_status_cmd = 'show slave status' + mdb8 = getMdb8Ver() + if mw.inArray(mdb8, version): + query_status_cmd = 'show replica status' + + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + db = pMysqlDb() + dlist = db.query(query_status_cmd) + + # print(dlist) + data = {} + data['data'] = dlist + return mw.getJson(data) + + +def trySlaveSyncBugfix(version=''): + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + if mode != 'sync-user': + return mw.returnJson(False, '仅支持【同步账户】模式') + + conn = pSqliteDb('slave_sync_user') + slave_sync_data = conn.field('ip,port,user,pass,mode,cmd').select() + if len(slave_sync_data) < 1: + return mw.returnJson(False, '需要先添加【同步用户】配置!') + + # print(slave_sync_data) + # 本地从库 + sdb = pMysqlDb() + + gtid_purged = '' + + var_slave_gtid = sdb.query('show VARIABLES like "%gtid_purged%"') + if len(var_slave_gtid) > 0: + gtid_purged += var_slave_gtid[0]['Value'] + ',' + + for i in range(len(slave_sync_data)): + port = slave_sync_data[i]['port'] + password = slave_sync_data[i]['pass'] + host = slave_sync_data[i]['ip'] + user = slave_sync_data[i]['user'] + + # print(port, password, host) + + mdb = mw.getMyORM() + mdb.setHost(host) + mdb.setPort(port) + mdb.setUser(user) + mdb.setPwd(password) + mdb.setSocket('') + + var_gtid = mdb.query('show VARIABLES like "%gtid_purged%"') + if len(var_gtid) > 0: + gtid_purged += var_gtid[0]['Value'] + ',' + + + + gtid_purged = gtid_purged.strip(',') + sql = "set @@global.gtid_purged='" + gtid_purged + "'" + + sdb.query('stop slave') + # print(sql) + sdb.query(sql) + sdb.query('start slave') + return mw.returnJson(True, '修复成功!') + + +def getSlaveSyncCmd(version=''): + root = mw.getPanelDir() + cmd = 'cd ' + root + ' && python3 ' + root + \ + '/plugins/mysql/index.py do_full_sync {"db":"all","sign",""}' + return mw.returnJson(True, 'ok', cmd) + + +def initSlaveStatus(version=''): + if status(version) == 'stop': + return mw.returnJson(False, 'MySQL未启动', []) + + mode_file = getSyncModeFile() + # print(mode_file) + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + if mode == 'ssh': + return initSlaveStatusSSH(version) + if mode == 'sync-user': + return initSlaveStatusSyncUser(version) + + +def makeSyncUsercmd(u, version=''): + mode = recognizeDbMode() + sql = '' + + ip = u['ip'] + port = u['port'] + username = u['user'] + password = u['pass'] + if mode == "gtid": + sql = "CHANGE MASTER TO MASTER_HOST='" + ip + "', MASTER_PORT=" + port + ", MASTER_USER='" + \ + username + "', MASTER_PASSWORD='" + \ + password + "', MASTER_AUTO_POSITION=1" + if version == '8.0': + sql = "CHANGE REPLICATION SOURCE TO SOURCE_HOST='" + ip + "', SOURCE_PORT=" + port + ", SOURCE_USER='" + \ + username + "', SOURCE_PASSWORD='" + \ + password + "', MASTER_AUTO_POSITION=1" + return sql + + +def parseSlaveSyncCmd(cmd): + a = {} + if cmd.lower().find('for') > 0: + cmd_tmp = cmd.split('for') + cmd = cmd_tmp[0].strip() + + pattern_c = r"channel \'(.*)\';" + match_val = re.match(pattern_c, cmd_tmp[1].strip(), re.I) + if match_val: + m_groups = match_val.groups() + a['channel'] = m_groups[0] + vlist = cmd.split(',') + for i in vlist: + tmp = i.strip() + tmp_a = tmp.split(" ") + real_tmp = tmp_a[len(tmp_a) - 1] + kv = real_tmp.split("=") + a[kv[0]] = kv[1].replace("'", '').replace("'", '').replace(";", '') + return a + + +def initSlaveStatusSyncUser(version=''): + conn = pSqliteDb('slave_sync_user') + slave_data = conn.field('ip,port,user,pass,mode,cmd').select() + if len(slave_data) < 1: + return mw.returnJson(False, '需要先添加同步用户配置!') + + slave_name = getSlaveName() + # print(data) + pdb = pMysqlDb() + if len(slave_data) == 1: + cmd_slave = 'show '+slave_name+' status' + dlist = pdb.query(cmd_slave) + if dlist and len(dlist) > 0: + return mw.returnJson(False, '已经初始化好了zz...') + + msg = '' + local_mode = recognizeDbMode() + for x in range(len(slave_data)): + slave_t = slave_data[x] + mode_name = 'classic' + base_t = 'IP:' + slave_t['ip'] + ",PORT:" + \ + slave_t['port'] + ",USER:" + slave_t['user'] + + if slave_t['mode'] == '1': + mode_name = 'gtid' + # print(local_mode, mode_name) + if local_mode != mode_name: + msg += base_t + '->同步模式不一致' + continue + + cmd_sql = slave_t['cmd'] + if cmd_sql == '': + msg += base_t + '->同步命令不能为空' + continue + + try: + pinfo = parseSlaveSyncCmd(cmd_sql) + except Exception as e: + return mw.returnJson(False, base_t + '->CMD同步命令不合规范!') + # print(cmd_sql) + t = pdb.query(cmd_sql) + # print(t) + isError = isSqlError(t) + if isError: + return isError + + # pdb.query("start slave user='{}' password='{}';".format( + # u['user'], u['pass'])) + + pdb.query("start "+slave_name) + pdb.query("start all "+slave_name) + + if msg == '': + msg = '初始化成功!' + return mw.returnJson(True, msg) + + +def initSlaveStatusSSH(version=''): + slave_name = getSlaveName() + db = pMysqlDb() + dlist = db.query('show '+slave_name+' status') + + conn = pSqliteDb('slave_id_rsa') + ssh_list = conn.field('ip,port,id_rsa,db_user').select() + + if len(ssh_list) < 1: + return mw.returnJson(False, '需要先配置【[主]SSH配置】!') + + local_mode = recognizeDbMode() + + import paramiko + paramiko.util.log_to_file('paramiko.log') + ssh = paramiko.SSHClient() + + db.query('stop '+slave_name) + db.query('reset '+slave_name+' all') + for data in ssh_list: + ip = data['ip'] + SSH_PRIVATE_KEY = "/tmp/t_ssh_" + ip + ".txt" + master_port = data['port'] + mw.writeFile(SSH_PRIVATE_KEY, data['id_rsa'].replace('\\n', '\n')) + mw.execShell("chmod 600 " + SSH_PRIVATE_KEY) + try: + key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(hostname=ip, port=int(master_port), + username='root', pkey=key) + + db_user = data['db_user'] + cmd = 'cd /www/server/mdserver-web && source bin/activate && python3 ' + \ + getSPluginDir() + \ + '/index.py get_master_rep_slave_user_cmd {"username":"' + \ + db_user + '","db":""}' + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + if result.strip() == "": + return mw.returnJson(False, '[主][' + ip + ']:获取同步命令失败!') + + cmd_data = json.loads(result) + if not cmd_data['status']: + return mw.returnJson(False, '[主][' + ip + ']:' + cmd_data['msg']) + + if local_mode != cmd_data['data']['mode']: + return mw.returnJson(False, '[主][' + ip + ']:【{}】从【{}】,运行模式不一致!'.format(cmd_data['data']['mode'], local_mode)) + + u = cmd_data['data']['info'] + + ps = u['username'] + "|" + u['password'] + print(ps) + conn.where('ip=?', (ip,)).setField('ps', ps) + db.query('stop slave') + + # 保证同步IP一致 + cmd = cmd_data['data']['cmd'] + if cmd.find('SOURCE_HOST') > -1: + cmd = re.sub(r"SOURCE_HOST='(.*?)'", + "SOURCE_HOST='" + ip + "'", cmd, 1) + + if cmd.find('MASTER_HOST') > -1: + cmd = re.sub(r"MASTER_HOST='(.*?)'", + "MASTER_HOST='" + ip + "'", cmd, 1) + db.query(cmd) + ssh.close() + if os.path.exists(SSH_PRIVATE_KEY): + os.system("rm -rf " + SSH_PRIVATE_KEY) + except Exception as e: + return mw.returnJson(False, '[主][' + ip + ']:SSH认证配置连接失败!' + str(e)) + db.query('start '+slave_name) + return mw.returnJson(True, '初始化成功!') + + +def setSlaveStatus(version=''): + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + pdb = pMysqlDb() + + slave_name = getSlaveName() + dlist = pdb.query(cmd) + if len(dlist) == 0: + return mw.returnJson(False, '需要手动添加同步账户或者执行初始化!') + + for v in dlist: + connection_name = '' + if 'Channel_Name' in v: + ch_name = v['Channel_Name'] + cmd = slave_name + " for channel '{}'".format(ch_name) + + if (( 'Slave_IO_Running' in v and v["Slave_IO_Running"] == 'Yes') or ('Slave_SQL_Running' in v and v["Slave_SQL_Running"] == 'Yes')): + pdb.query("stop {}".format(cmd)) + elif (( 'Replica_IO_Running' in v and v["Replica_IO_Running"] == 'Yes') or ( 'Replica_SQL_Running' in v and v["Replica_SQL_Running"] == 'Yes') ): + pdb.query("stop {}".format(cmd)) + else: + pdb.query("start {}".format(cmd)) + + return mw.returnJson(True, '设置成功!') + +def deleteSlaveFunc(sign = ''): + db = pMysqlDb() + slave_name = getSlaveName() + if sign != '': + db.query("stop {} for channel '{}'".format(slave_name,sign)) + db.query("reset {} all for channel '{}'".format(slave_name, sign)) + else: + db.query('stop '+slave_name) + db.query('reset '+slave_name+' all') + + +def deleteSlave(version=''): + args = getArgs() + sign = '' + if 'sign' in args: + sign = args['sign'] + deleteSlaveFunc(sign) + return mw.returnJson(True, '删除成功!') + + +def dumpMysqlData(version=''): + args = getArgs() + data = checkArgs(args, ['db']) + if not data[0]: + return data[1] + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + mysql_dir = getServerDir() + myconf = mysql_dir + "/etc/my.cnf" + + option = '' + mode = recognizeDbMode() + if mode == 'gtid': + option = ' --set-gtid-purged=off ' + + if args['db'].lower() == 'all': + dlist = findBinlogDoDb() + cmd = mysql_dir + "/bin/mysqldump --defaults-file=" + myconf + " " + option + " -uroot -p" + \ + pwd + " --force --databases " + \ + ' '.join(dlist) + " | gzip > /tmp/dump.sql.gz" + else: + cmd = mysql_dir + "/bin/mysqldump --defaults-file=" + myconf + " " + option + " -uroot -p" + \ + pwd + " --force --databases " + \ + args['db'] + " | gzip > /tmp/dump.sql.gz" + + ret = mw.execShell(cmd) + if ret[0] == '': + return 'ok' + return 'fail' + + +############### --- 重要 数据补足同步 ---- ########### + +def getSyncMysqlDB(dbname,sign = ''): + conn = pSqliteDb('slave_sync_user') + if sign != '': + data = conn.field('ip,port,user,pass,mode,cmd').where('ip=?', (sign,)).find() + else: + data = conn.field('ip,port,user,pass,mode,cmd').find() + user = data['user'] + apass = data['pass'] + port = data['port'] + ip = data['ip'] + # 远程数据 + sync_db = mw.getMyORM() + # MySQLdb | + sync_db.setPort(port) + sync_db.setHost(ip) + sync_db.setUser(user) + sync_db.setPwd(apass) + sync_db.setDbName(dbname) + sync_db.setTimeout(60) + return sync_db + +def syncDatabaseRepairTempFile(): + tmp_log = mw.getMWLogs()+ '/mysql-check.log' + return tmp_log + +def syncDatabaseRepairLog(version=''): + import subprocess + args = getArgs() + data = checkArgs(args, ['db','sign','op']) + if not data[0]: + return data[1] + + sync_args_db = args['db'] + sync_args_sign = args['sign'] + op = args['op'] + tmp_log = syncDatabaseRepairTempFile() + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && python3 plugins/mysql/index.py sync_database_repair {"db":"'+sync_args_db+'","sign":"'+sync_args_sign+'"}' + # print(cmd) + + if op == 'get': + log = mw.getLastLine(tmp_log, 15) + return mw.returnJson(True, log) + + if op == 'cmd': + return mw.returnJson(True, 'ok', cmd) + + if op == 'do': + os.system(' echo "开始执行" > '+ tmp_log) + os.system(cmd +' >> '+ tmp_log +' &') + return mw.returnJson(True, 'ok') + + return mw.returnJson(False, '无效请求!') + + +def syncDatabaseRepair(version=''): + time_stats_s = time.time() + tmp_log = syncDatabaseRepairTempFile() + + from pymysql.converters import escape_string + args = getArgs() + data = checkArgs(args, ['db','sign']) + if not data[0]: + return data[1] + + sync_args_db = args['db'] + sync_args_sign = args['sign'] + + # 本地数据 + local_db = pMysqlDb() + # 远程数据 + sync_db = getSyncMysqlDB(sync_args_db,sync_args_sign) + + tables = local_db.query('show tables from `%s`' % sync_args_db) + table_key = "Tables_in_" + sync_args_db + inconsistent_table = [] + + tmp_dir = '/tmp/sync_db_repair' + mw.execShell('mkdir -p '+tmp_dir) + + for tb in tables: + + table_name = sync_args_db+'.'+tb[table_key] + table_check_file = tmp_dir+'/'+table_name+'.txt' + + if os.path.exists(table_check_file): + # print(table_name+', 已检查OK') + continue + + primary_key_sql = "SHOW INDEX FROM "+table_name+" WHERE Key_name = 'PRIMARY';"; + primary_key_data = local_db.query(primary_key_sql) + # print(primary_key_sql,primary_key_data) + pkey_name = '*' + if len(primary_key_data) == 1: + pkey_name = primary_key_data[0]['Column_name'] + # print(pkey_name) + if pkey_name != '*' : + # 智能校验(由于服务器同步可能会慢,比较总数总是对不上) + cmd_local_newpk_sql = 'select ' + pkey_name + ' from ' + table_name + " order by " + pkey_name + " desc limit 1" + cmd_local_newpk_data = local_db.query(cmd_local_newpk_sql) + # print(cmd_local_newpk_data) + if len(cmd_local_newpk_data) == 1: + # 比较总数 + cmd_count_sql = 'select count('+pkey_name+') as num from '+table_name + ' where '+pkey_name + ' <= '+ str(cmd_local_newpk_data[0][pkey_name]) + local_count_data = local_db.query(cmd_count_sql) + sync_count_data = sync_db.query(cmd_count_sql) + + if local_count_data != sync_count_data: + print(cmd_count_sql) + print("all data compare: ",local_count_data, sync_count_data) + else: + print(table_name+' smart compare check ok.') + mw.writeFile(tmp_log, table_name+' smart compare check ok.\n','a+') + mw.execShell("echo 'ok' > "+table_check_file) + continue + + + + # 比较总数 + cmd_count_sql = 'select count('+pkey_name+') as num from '+table_name + local_count_data = local_db.query(cmd_count_sql) + sync_count_data = sync_db.query(cmd_count_sql) + + if local_count_data != sync_count_data: + if sync_count_data == None: + print("sync:"+table_name+" is not exists!!!") + mw.writeFile(tmp_log, "sync:"+table_name+" is not exists!!!"+'\n','a+') + else: + print("all data compare: ",local_count_data, sync_count_data) + inconsistent_table.append(table_name) + diff = sync_count_data[0]['num'] - local_count_data[0]['num'] + print(table_name+', need sync. diff,'+str(diff)) + mw.writeFile(tmp_log, table_name+', need sync. diff,'+str(diff)+'\n','a+') + else: + print(table_name+' check ok.') + mw.writeFile(tmp_log, table_name+' check ok.\n','a+') + mw.execShell("echo 'ok' > "+table_check_file) + + + # inconsistent_table = ['xx.xx'] + # 数据对齐 + for table_name in inconsistent_table: + is_break = False + while not is_break: + local_db.ping() + # 远程数据 + sync_db.ping() + + print("check table:"+table_name) + mw.writeFile(tmp_log, "check table:"+table_name+'\n','a+') + table_name_pos = 0 + table_name_pos_file = tmp_dir+'/'+table_name+'.pos.txt' + primary_key_sql = "SHOW INDEX FROM "+table_name+" WHERE Key_name = 'PRIMARY';"; + primary_key_data = local_db.query(primary_key_sql) + pkey_name = primary_key_data[0]['Column_name'] + + if os.path.exists(table_name_pos_file): + table_name_pos = mw.readFile(table_name_pos_file) + + + data_select_sql = 'select * from '+table_name + ' where '+pkey_name+' > '+str(table_name_pos)+' limit 10000' + print(data_select_sql) + local_select_data = local_db.query(data_select_sql) + + time_s = time.time() + sync_select_data = sync_db.query(data_select_sql) + print(f'sync query cos:{time.time() - time_s:.4f}s') + mw.writeFile(tmp_log, f'sync query cos:{time.time() - time_s:.4f}s\n','a+') + + # print(local_select_data) + # print(sync_select_data) + + # print(len(local_select_data)) + # print(len(sync_select_data)) + print('pos:',str(table_name_pos),'local compare sync,',local_select_data == sync_select_data) + + + cmd_count_sql = 'select count('+pkey_name+') as num from '+table_name + local_count_data = local_db.query(cmd_count_sql) + time_s = time.time() + sync_count_data = sync_db.query(cmd_count_sql) + print(f'sync count data cos:{time.time() - time_s:.4f}s') + print(local_count_data,sync_count_data) + # 数据同步有延迟,相等即任务数据补足完成 + if local_count_data[0]['num'] == sync_count_data[0]['num']: + is_break = True + break + + diff = sync_count_data[0]['num'] - local_count_data[0]['num'] + print("diff," + str(diff)+' line data!') + + if local_select_data == sync_select_data: + data_count = len(local_select_data) + if data_count == 0: + # mw.writeFile(table_name_pos_file, '0') + print(table_name+",data is equal ok..") + is_break = True + break + + # print(table_name,data_count) + pos = local_select_data[data_count-1][pkey_name] + print('pos',pos) + progress = pos/sync_count_data[0]['num'] + print('progress,%.2f' % progress+'%') + mw.writeFile(table_name_pos_file, str(pos)) + else: + sync_select_data_len = len(sync_select_data) + skip_idx = 0 + # 主库PK -> 查询本地 | 保证一致 + if sync_select_data_len > 0: + for idx in range(sync_select_data_len): + sync_idx_data = sync_select_data[idx] + local_idx_data = None + if idx in local_select_data: + local_idx_data = local_select_data[idx] + if sync_select_data[idx] == local_idx_data: + skip_idx = idx + pos = local_select_data[idx][pkey_name] + mw.writeFile(table_name_pos_file, str(pos)) + + # print(insert_data) + local_inquery_sql = 'select * from ' + table_name+ ' where ' +pkey_name+' = '+ str(sync_idx_data[pkey_name]) + # print(local_inquery_sql) + ldata = local_db.query(local_inquery_sql) + # print('ldata:',ldata) + if len(ldata) == 0: + print("id:"+ str(sync_idx_data[pkey_name])+ " no exists, insert") + insert_sql = 'insert into ' + table_name + field_str = '' + value_str = '' + for field in sync_idx_data: + field_str += '`'+field+'`,' + value_str += '\''+escape_string(str(sync_idx_data[field]))+'\',' + field_str = '(' +field_str.strip(',')+')' + value_str = '(' +value_str.strip(',')+')' + insert_sql = insert_sql+' '+field_str+' values'+value_str+';' + print(insert_sql) + r = local_db.execute(insert_sql) + print(r) + else: + # print('compare sync->local:',sync_idx_data == ldata[0] ) + if ldata[0] == sync_idx_data: + continue + + print("id:"+ str(sync_idx_data[pkey_name])+ " data is not equal, update") + update_sql = 'update ' + table_name + field_str = '' + value_str = '' + for field in sync_idx_data: + if field == pkey_name: + continue + field_str += '`'+field+'`=\''+escape_string(str(sync_idx_data[field]))+'\',' + field_str = field_str.strip(',') + update_sql = update_sql+' set '+field_str+' where '+pkey_name+'=\''+str(sync_idx_data[pkey_name])+'\';' + print(update_sql) + r = local_db.execute(update_sql) + print(r) + + # 本地PK -> 查询主库 | 保证一致 + # local_select_data_len = len(local_select_data) + # if local_select_data_len > 0: + # for idx in range(local_select_data_len): + # if idx < skip_idx: + # continue + # local_idx_data = local_select_data[idx] + # print('local idx check', idx, skip_idx) + # local_inquery_sql = 'select * from ' + table_name+ ' where ' +pkey_name+' = '+ str(local_idx_data[pkey_name]) + # print(local_inquery_sql) + # sdata = sync_db.query(local_inquery_sql) + # sdata_len = len(sdata) + # print('sdata:',sdata,sdata_len) + # if sdata_len == 0: + # delete_sql = 'delete from ' + table_name + ' where ' +pkey_name+' = '+ str(local_idx_data[pkey_name]) + # print(delete_sql) + # r = local_db.execute(delete_sql) + # print(r) + # break + + + if is_break: + print("break all") + break + time.sleep(3) + print(f'data check cos:{time.time() - time_stats_s:.4f}s') + print("data supplementation completed") + mw.execShell('rm -rf '+tmp_dir) + return 'ok' + +############### --- 重要 同步---- ########### + +def asyncTmpfile(): + path = '/tmp/mysql_async_status.txt' + return path + + +def writeDbSyncStatus(data): + path = asyncTmpfile() + mw.writeFile(path, json.dumps(data)) + return True + +def fullSyncCmd(): + time_all_s = time.time() + args = getArgs() + data = checkArgs(args, ['db', 'sign']) + if not data[0]: + return data[1] + + db = args['db'] + sign = args['sign'] + + cmd = 'cd '+mw.getServerDir()+'/mdserver-web && source bin/activate && python3 plugins/mysql/index.py do_full_sync {"db":"'+db+'","sign":"'+sign+'"}' + return mw.returnJson(True,'ok',cmd) + +def doFullSync(version=''): + mode_file = getSyncModeFile() + if not os.path.exists(mode_file): + return mw.returnJson(False, '需要先设置同步配置') + + mode = mw.readFile(mode_file) + if mode == 'ssh': + return doFullSyncSSH(version) + if mode == 'sync-user': + return doFullSyncUser(version) + + +def isSimpleSyncCmd(sql): + new_sql = sql.lower() + if new_sql.find('master_auto_position') > 0: + return False + return True + +def getChannelNameForCmd(cmd): + cmd = cmd.lower() + cmd_arr = cmd.split('channel') + if len(cmd_arr) == 2: + cmd_channel_info = cmd_arr[1] + channel_name = cmd_channel_info.strip() + channel_name = channel_name.strip(';') + channel_name = channel_name.strip("'") + return channel_name + return '' + + + +def doFullSyncUserImportContentForChannel(file, channel_name): + # print(file, channel_name) + content = mw.readFile(file) + + slave_name = getSlaveName() + slave_name = slave_name.upper() + + content = content.replace('STOP '+slave_name+';', "STOP {} for channel '{}';".format(slave_name,channel_name)) + content = content.replace('START '+slave_name+';', "START {} for channel '{}';".format(slave_name,channel_name)) + + find_head = "CHANGE MASTER TO " + find_re = find_head+"(.*?);" + find_r = re.search(find_re, content, re.I|re.M) + if find_r: + find_rg = find_r.groups() + if len(find_rg)>0: + find_str = find_head+find_rg[0] + if find_str.lower().find('channel')==-1: + content = content.replace(find_str+';', find_str+" for channel '{}';".format(channel_name)) + + mw.writeFile(file,content) + return True + + +def doFullSyncUser(version=''): + slave_name = getSlaveName() + which_pv = mw.execShell('which pv') + is_exist_pv = False + if os.path.exists(which_pv[0]): + is_exist_pv = True + + time_all_s = time.time() + args = getArgs() + data = checkArgs(args, ['db', 'sign']) + if not data[0]: + return data[1] + + sync_db = args['db'] + sync_db_import = args['db'] + + if sync_db.lower() == 'all': + sync_db_import = '' + dbs = findBinlogSlaveDoDb() + dbs_str = '' + for x in dbs: + dbs_str += ' ' + x + sync_db = "--databases " + dbs_str.strip() + + sync_sign = args['sign'] + + db = pMysqlDb() + + # 重置 + # deleteSlaveFunc(sync_sign) + + conn = pSqliteDb('slave_sync_user') + if sync_sign != '': + data = conn.field('ip,port,user,pass,mode,cmd').where( + 'ip=?', (sync_sign,)).find() + else: + data = conn.field('ip,port,user,pass,mode,cmd').find() + + # print(data) + user = data['user'] + apass = data['pass'] + port = data['port'] + ip = data['ip'] + cmd = data['cmd'] + channel_name = getChannelNameForCmd(cmd) + + sync_mdb = getSyncMysqlDB(sync_db,sync_sign) + + bak_file = '/tmp/tmp.sql' + if os.path.exists(bak_file): + os.system("rm -rf " + bak_file) + + writeDbSyncStatus({'code': 0, 'msg': '开始同步...', 'progress': 0}) + dmp_option = '' + mode = recognizeDbMode() + if mode == 'gtid': + dmp_option = ' --set-gtid-purged=off ' + + time.sleep(1) + writeDbSyncStatus({'code': 1, 'msg': '正在停止从库...', 'progress': 15}) + + mdb8 = getMdb8Ver() + if mw.inArray(mdb8,version): + db.query("stop {} user='{}' password='{}';".format(slave_name,user, apass)) + else: + db.query("stop "+slave_name) + + time.sleep(1) + + writeDbSyncStatus({'code': 2, 'msg': '远程导出数据...', 'progress': 20}) + + # --master-data=2表示在dump过程中记录主库的binlog和pos点,并在dump文件中注释掉这一行 + # --master-data=1表示在dump过程中记录主库的binlog和pos点,并在dump文件中不注释掉这一行,即恢复时会执行 + + # --dump-slave=2表示在dump过程中,在从库dump,mysqldump进程也要在从库执行,记录当时主库的binlog和pos点,并在dump文件中注释掉这一行 + # --dump-slave=1表示在dump过程中,在从库dump,mysqldump进程也要在从库执行,记录当时主库的binlog和pos点,并在dump文件中不注释掉这一行 + + # --force --opt --single-transaction + # --skip-opt --create-options + # --master-data=1 + + find_run_dump = mw.execShell('ps -ef | grep mysqldump | grep -v grep') + if find_run_dump[0] != "": + print("正在远程导出数据中,别着急...") + writeDbSyncStatus({'code': 3.1, 'msg': '正在远程导出数据中,别着急...', 'progress': 19}) + return False + + time_s = time.time() + + ssl_mode = '--ssl-mode=DISABLED' + if pk_version.parse(version) > pk_version.parse("8.0"): + ssl_mode = '--ssl-mode=REQUIRED' + + if not os.path.exists(bak_file): + dmp_option += ' ' + if mw.inArray(mdb8,version): + # --compression-algorithms + dmp_option += " --source-data=1 --apply-replica-statements --include-source-host-port " + else: + dmp_option += " --master-data=1 --apply-slave-statements --include-master-host-port --compress " + + dump_sql_data = getServerDir() + "/bin/mysqldump --single-transaction --default-character-set=utf8mb4 -q " + dmp_option + " -h" + ip + " -P" + \ + port + " -u" + user + " -p'" + apass + "' " + ssl_mode + " " + sync_db + " > " + bak_file + print(dump_sql_data) + time_s = time.time() + r = mw.execShell(dump_sql_data) + print(r) + time_e = time.time() + export_cos = time_e - time_s + print("export cos:", export_cos) + + writeDbSyncStatus({'code': 3, 'msg': '导出耗时:'+str(int(export_cos))+'秒,正在到本地导入数据中...', 'progress': 40}) + + find_run_import = mw.execShell('ps -ef | grep mysql| grep '+ bak_file +' | grep -v grep') + if find_run_import[0] != "": + print("正在导入数据中,别着急...") + writeDbSyncStatus({'code': 4.1, 'msg': '正在导入数据中,别着急...', 'progress': 39}) + return False + + time_s = time.time() + if os.path.exists(bak_file): + # 重置 + db.execute('reset master') + + # 加快导入 - 开始 + # db.execute('set global innodb_flush_log_at_trx_commit = 2') + # db.execute('set global sync_binlog = 2000') + + if channel_name != '': + doFullSyncUserImportContentForChannel(bak_file, channel_name) + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + sock = getSocketFile() + + if is_exist_pv: + my_import_cmd = getServerDir() + '/bin/mysql -S ' + sock + " -uroot -p'" + pwd + "' " + sync_db_import + my_import_cmd = "pv -t -p " + bak_file + '|' + my_import_cmd + print(my_import_cmd) + os.system(my_import_cmd) + else: + my_import_cmd = getServerDir() + '/bin/mysql -S ' + sock + " -uroot -p'" + pwd + "' " + sync_db_import + ' < ' + bak_file + print(my_import_cmd) + mw.execShell(my_import_cmd) + + # 加快导入 - 结束 + # db.execute('set global innodb_flush_log_at_trx_commit = 1') + # db.execute('set global sync_binlog = 1') + + time_e = time.time() + import_cos = time_e - time_s + print("import cos:", import_cos) + writeDbSyncStatus({'code': 4, 'msg': '导入耗时:'+str(int(import_cos))+'秒', 'progress': 60}) + + time.sleep(3) + # print(cmd) + # r = db.query(cmd) + # print(r) + + if mw.inArray(mdb8,version): + db.query("start replica user='{}' password='{}';".format(user, apass)) + else: + db.query("start "+slave_name) + + db.query("start all "+slave_name) + time_all_e = time.time() + cos = time_all_e - time_all_s + writeDbSyncStatus({'code': 6, 'msg': '总耗时:'+str(int(cos))+'秒,从库重启完成...', 'progress': 100}) + + if os.path.exists(bak_file): + os.system("rm -rf " + bak_file) + + return True + + +def doFullSyncSSH(version=''): + + args = getArgs() + data = checkArgs(args, ['db', 'sign']) + if not data[0]: + return data[1] + + sync_db = args['db'] + sync_sign = args['sign'] + + db = pMysqlDb() + + id_rsa_conn = pSqliteDb('slave_id_rsa') + if sync_sign != '': + data = id_rsa_conn.field('ip,port,db_user,id_rsa').where( + 'ip=?', (sync_sign,)).find() + else: + data = id_rsa_conn.field('ip,port,db_user,id_rsa').find() + + SSH_PRIVATE_KEY = "/tmp/mysql_sync_id_rsa.txt" + id_rsa = data['id_rsa'].replace('\\n', '\n') + mw.writeFile(SSH_PRIVATE_KEY, id_rsa) + + ip = data["ip"] + master_port = data['port'] + db_user = data['db_user'] + print("master ip:", ip) + + writeDbSyncStatus({'code': 0, 'msg': '开始同步...', 'progress': 0}) + + import paramiko + paramiko.util.log_to_file('paramiko.log') + ssh = paramiko.SSHClient() + + print(SSH_PRIVATE_KEY) + if not os.path.exists(SSH_PRIVATE_KEY): + writeDbSyncStatus({'code': 0, 'msg': '需要配置SSH......', 'progress': 0}) + return 'fail' + + try: + # ssh.load_system_host_keys() + mw.execShell("chmod 600 " + SSH_PRIVATE_KEY) + key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + print(ip, master_port) + + # pkey=key + # key_filename=SSH_PRIVATE_KEY + ssh.connect(hostname=ip, port=int(master_port), + username='root', pkey=key) + except Exception as e: + print(str(e)) + writeDbSyncStatus( + {'code': 0, 'msg': 'SSH配置错误:' + str(e), 'progress': 0}) + return 'fail' + + writeDbSyncStatus({'code': 0, 'msg': '登录Master成功...', 'progress': 5}) + + dbname = args['db'] + cmd = "cd /www/server/mdserver-web && source bin/activate && python3 " + \ + getSPluginDir() + "/index.py dump_mysql_data {\"db\":'" + dbname + "'}" + print(cmd) + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + if result.strip() == 'ok': + writeDbSyncStatus({'code': 1, 'msg': '主服务器备份完成...', 'progress': 30}) + else: + writeDbSyncStatus( + {'code': 1, 'msg': '主服务器备份失败...:' + str(result), 'progress': 100}) + return 'fail' + + print("同步文件", "start") + # cmd = 'scp -P' + str(master_port) + ' -i ' + SSH_PRIVATE_KEY + \ + # ' root@' + ip + ':/tmp/dump.sql.gz /tmp' + t = ssh.get_transport() + sftp = paramiko.SFTPClient.from_transport(t) + copy_status = sftp.get("/tmp/dump.sql.gz", "/tmp/dump.sql.gz") + print("同步信息:", copy_status) + print("同步文件", "end") + if copy_status == None: + writeDbSyncStatus({'code': 2, 'msg': '数据同步本地完成...', 'progress': 40}) + + cmd = 'cd /www/server/mdserver-web && source bin/activate && python3 ' + \ + getSPluginDir() + \ + '/index.py get_master_rep_slave_user_cmd {"username":"' + \ + db_user + '","db":""}' + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + cmd_data = json.loads(result) + + db.query('stop slave') + writeDbSyncStatus({'code': 3, 'msg': '停止从库完成...', 'progress': 45}) + + cmd = cmd_data['data']['cmd'] + # 保证同步IP一致 + if cmd.find('SOURCE_HOST') > -1: + cmd = re.sub(r"SOURCE_HOST='(.*?)'", + "SOURCE_HOST='" + ip + "'", cmd, 1) + + if cmd.find('MASTER_HOST') > -1: + cmd = re.sub(r"MASTER_HOST='(.*?)'", + "MASTER_HOST='" + ip + "'", cmd, 1) + + db.query(cmd) + uinfo = cmd_data['data']['info'] + ps = uinfo['username'] + "|" + uinfo['password'] + id_rsa_conn.where('ip=?', (ip,)).setField('ps', ps) + writeDbSyncStatus({'code': 4, 'msg': '刷新从库同步信息完成...', 'progress': 50}) + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('mysql_root') + root_dir = getServerDir() + msock = root_dir + "/mysql.sock" + mw.execShell("cd /tmp && gzip -d dump.sql.gz") + cmd = root_dir + "/bin/mysql -S " + msock + \ + " -uroot -p" + pwd + " < /tmp/dump.sql" + + print(cmd) + import_data = mw.execShell(cmd) + if import_data[0] == '': + print(import_data[1]) + writeDbSyncStatus({'code': 5, 'msg': '导入数据完成...', 'progress': 90}) + else: + print(import_data[0]) + writeDbSyncStatus({'code': 5, 'msg': '导入数据失败...', 'progress': 100}) + return 'fail' + + mdb8 = getMdb8Ver() + if mw.inArray(mdb8,version): + db.query("start slave user='{}' password='{}';".format(uinfo['username'], uinfo['password'])) + else: + db.query("start slave") + + writeDbSyncStatus({'code': 6, 'msg': '从库重启完成...', 'progress': 100}) + + os.system("rm -rf " + SSH_PRIVATE_KEY) + os.system("rm -rf /tmp/dump.sql") + return True + + +def fullSync(version=''): + args = getArgs() + data = checkArgs(args, ['db', 'begin']) + if not data[0]: + return data[1] + + sign = '' + if 'sign' in args: + sign = args['sign'] + + status_file = asyncTmpfile() + if args['begin'] == '1': + cmd = 'cd ' + mw.getPanelDir() + ' && python3 ' + getPluginDir() + \ + '/index.py do_full_sync {"db":"' + \ + args['db'] + '","sign":"' + sign + '"} &' + # print(cmd) + mw.execShell(cmd) + return json.dumps({'code': 0, 'msg': '同步数据中!', 'progress': 0}) + + if os.path.exists(status_file): + c = mw.readFile(status_file) + tmp = json.loads(c) + if tmp['code'] == 1: + sys_dump_sql = "/tmp/dump.sql" + if os.path.exists(sys_dump_sql): + dump_size = os.path.getsize(sys_dump_sql) + tmp['msg'] = tmp['msg'] + ":" + "同步文件:" + mw.toSize(dump_size) + c = json.dumps(tmp) + + # if tmp['code'] == 6: + # os.remove(status_file) + return c + + return json.dumps({'code': 0, 'msg': '点击开始,开始同步!', 'progress': 0}) + + +def installPreInspection(version): + import psutil + mem = psutil.virtual_memory() + memTotal = mem.total + memG = memTotal/1024/1024/1024 + if memG > 2: + return 'ok' + + swap_path = mw.getServerDir() + "/swap" + if not os.path.exists(swap_path): + return "内存小,为了稳定安装MySQL,先安装swap插件!" + return 'ok' + + +def uninstallPreInspection(version): + data_dir = getDataDir() + if os.path.exists(data_dir): + stop(version) + + if mw.isDebugMode(): + return 'ok' + + + from utils.plugin import plugin as MwPlugin + MwPlugin.instance().removeIndex(getPluginName(), version) + + return "请手动删除MySQL[{}]
                              rm -rf {}".format(version, getServerDir()) + +if __name__ == "__main__": + func = sys.argv[1] + + version = "5.6" + version_pl = getServerDir() + "/version.pl" + if os.path.exists(version_pl): + version = mw.readFile(version_pl).strip() + + if func == 'status': + print(status(version)) + elif func == 'start': + print(start(version)) + elif func == 'stop': + print(stop(version)) + elif func == 'restart': + print(restart(version)) + elif func == 'reload': + print(reload(version)) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection(version)) + elif func == 'run_info': + print(runInfo(version)) + elif func == 'db_status': + print(myDbStatus(version)) + elif func == 'set_db_status': + print(setDbStatus(version)) + elif func == 'conf': + print(getConf()) + elif func == 'bin_log': + print(binLog(version)) + elif func == 'binlog_list': + print(binLogList()) + elif func == 'clean_bin_log': + print(cleanBinLog()) + elif func == 'error_log': + print(getErrorLog()) + elif func == 'show_log': + print(getShowLogFile()) + elif func == 'my_db_pos': + print(getMyDbPos()) + elif func == 'set_db_pos': + print(setMyDbPos(version)) + elif func == 'my_port': + print(getMyPort()) + elif func == 'set_my_port': + print(setMyPort(version)) + elif func == 'init_pwd': + print(initMysqlPwd()) + elif func == 'root_pwd': + print(rootPwd()) + elif func == 'get_db_list': + print(getDbList()) + elif func == 'set_db_backup': + print(setDbBackup()) + elif func == 'import_db_backup': + print(importDbBackup()) + elif func == 'import_db_backup_progress': + print(importDbBackupProgress()) + elif func == 'import_db_backup_progress_bar': + print(importDbBackupProgressBar()) + elif func == 'import_db_external': + print(importDbExternal()) + elif func == 'import_db_external_progress': + print(importDbExternalProgress()) + elif func == 'import_db_external_progress_bar': + print(importDbExternalProgressBar()) + elif func == 'delete_db_backup': + print(deleteDbBackup()) + elif func == 'get_db_backup_list': + print(getDbBackupList()) + elif func == 'get_db_backup_import_list': + print(getDbBackupImportList()) + elif func == 'add_db': + print(addDb()) + elif func == 'del_db': + print(delDb()) + elif func == 'sync_get_databases': + print(syncGetDatabases()) + elif func == 'sync_to_databases': + print(syncToDatabases()) + elif func == 'set_root_pwd': + print(setRootPwd(version)) + elif func == 'set_user_pwd': + print(setUserPwd(version)) + elif func == 'get_db_access': + print(getDbAccess()) + elif func == 'set_db_access': + print(setDbAccess()) + elif func == 'fix_db_access': + print(fixDbAccess(version)) + elif func == 'set_db_rw': + print(setDbRw(version)) + elif func == 'set_db_ps': + print(setDbPs()) + elif func == 'get_db_info': + print(getDbInfo()) + elif func == 'repair_table': + print(repairTable()) + elif func == 'opt_table': + print(optTable()) + elif func == 'alter_table': + print(alterTable()) + elif func == 'get_total_statistics': + print(getTotalStatistics()) + elif func == 'get_dbrun_mode': + print(getDbrunMode(version)) + elif func == 'set_dbrun_mode': + print(setDbrunMode(version)) + elif func == 'reset_master': + print(resetMaster(version)) + elif func == 'get_masterdb_list': + print(getMasterDbList(version)) + elif func == 'get_master_status': + print(getMasterStatus(version)) + elif func == 'set_master_status': + print(setMasterStatus(version)) + elif func == 'set_db_master': + print(setDbMaster(version)) + elif func == 'set_db_slave': + print(setDbSlave(version)) + elif func == 'set_dbmaster_access': + print(setDbMasterAccess()) + elif func == 'get_master_rep_slave_list': + print(getMasterRepSlaveList(version)) + elif func == 'add_master_rep_slave_user': + print(addMasterRepSlaveUser(version)) + elif func == 'del_master_rep_slave_user': + print(delMasterRepSlaveUser(version)) + elif func == 'update_master_rep_slave_user': + print(updateMasterRepSlaveUser(version)) + elif func == 'get_master_rep_slave_user_cmd': + print(getMasterRepSlaveUserCmd(version)) + elif func == 'get_slave_list': + print(getSlaveList(version)) + elif func == 'try_slave_sync_bugfix': + print(trySlaveSyncBugfix(version)) + elif func == 'get_slave_sync_cmd': + print(getSlaveSyncCmd(version)) + elif func == 'get_slave_ssh_list': + print(getSlaveSSHList(version)) + elif func == 'get_slave_ssh_by_ip': + print(getSlaveSSHByIp(version)) + elif func == 'add_slave_ssh': + print(addSlaveSSH(version)) + elif func == 'del_slave_ssh': + print(delSlaveSSH(version)) + elif func == 'update_slave_ssh': + print(updateSlaveSSH(version)) + elif func == 'get_slave_sync_user_list': + print(getSlaveSyncUserList(version)) + elif func == 'get_slave_sync_user_by_ip': + print(getSlaveSyncUserByIp(version)) + elif func == 'add_slave_sync_user': + print(addSlaveSyncUser(version)) + elif func == 'del_slave_sync_user': + print(delSlaveSyncUser(version)) + elif func == 'get_slave_sync_mode': + print(getSlaveSyncMode(version)) + elif func == 'set_slave_sync_mode': + print(setSlaveSyncMode(version)) + elif func == 'init_slave_status': + print(initSlaveStatus(version)) + elif func == 'set_slave_status': + print(setSlaveStatus(version)) + elif func == 'delete_slave': + print(deleteSlave(version)) + elif func == 'full_sync': + print(fullSync(version)) + elif func == 'do_full_sync': + print(doFullSync(version)) + elif func == 'full_sync_cmd': + print(fullSyncCmd()) + elif func == 'dump_mysql_data': + print(dumpMysqlData(version)) + elif func == 'sync_database_repair': + print(syncDatabaseRepair()) + elif func == 'sync_database_repair_log': + print(syncDatabaseRepairLog()) + else: + print('error') diff --git a/plugins/mysql/index_mysql.py b/plugins/mysql/index_mysql.py new file mode 100644 index 000000000..d06228d4b --- /dev/null +++ b/plugins/mysql/index_mysql.py @@ -0,0 +1,190 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +if mw.isAppleSystem(): + cmd = 'ls /usr/local/lib/ | grep python | cut -d \\ -f 1 | awk \'END {print}\'' + info = mw.execShell(cmd) + p = "/usr/local/lib/" + info[0].strip() + "/site-packages" + sys.path.append(p) + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'mysql' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getSPluginDir(): + return '/www/server/mdserver-web/plugins/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConf(): + path = getServerDir() + '/etc/my.cnf' + return path + + +def getDataDir(): + file = getConf() + content = mw.readFile(file) + rep = r'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getRelayLogName(): + file = getConf() + content = mw.readFile(file) + rep = r'relay-log\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getLogBinName(): + file = getConf() + content = mw.readFile(file) + rep = r'log-bin\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def binLogListLook(args): + + file = args['file'] + line = args['line'] + + data_dir = getDataDir() + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata = {} + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata + + +def binLogListLookDecode(args): + + file = args['file'] + line = args['line'] + + data_dir = getDataDir() + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults --base64-output=decode-rows -vvvv ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata = {} + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata + + +def binLogListTraceRelay(args): + rdata = {} + file = args['file'] + line = args['line'] + + relay_name = getRelayLogName() + data_dir = getDataDir() + alist = os.listdir(data_dir) + relay_list = [] + for x in range(len(alist)): + f = alist[x] + t = {} + if f.startswith(relay_name) and not f.endswith('.index'): + relay_list.append(f) + + relay_list = sorted(relay_list, reverse=True) + if len(relay_list) == 0: + rdata['cmd'] = '' + rdata['data'] = '无Relay日志' + return rdata + + file = relay_list[0] + + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults --base64-output=decode-rows -vvvv ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata + + +def binLogListTraceBinLog(args): + rdata = {} + file = args['file'] + line = args['line'] + + data_dir = getDataDir() + log_bin_name = getLogBinName() + + alist = os.listdir(data_dir) + log_bin_l = [] + for x in range(len(alist)): + f = alist[x] + t = {} + if f.startswith(log_bin_name) and not f.endswith('.index'): + log_bin_l.append(f) + + if len(log_bin_l) == 0: + rdata['cmd'] = '' + rdata['data'] = '无BINLOG' + return rdata + + log_bin_l = sorted(log_bin_l, reverse=True) + file = log_bin_l[0] + + my_bin = getServerDir() + '/bin' + my_binlog_cmd = my_bin + '/mysqlbinlog' + + cmd = my_binlog_cmd + ' --no-defaults --base64-output=decode-rows -vvvv ' + \ + data_dir + '/' + file + '|tail -' + str(line) + + data = mw.execShell(cmd) + + rdata['cmd'] = cmd + rdata['data'] = data[0] + + return rdata diff --git a/plugins/mysql/info.json b/plugins/mysql/info.json new file mode 100755 index 000000000..aa65aeedf --- /dev/null +++ b/plugins/mysql/info.json @@ -0,0 +1,23 @@ +{ + "sort":1, + "title":"MySQL", + "tip":"soft", + "name":"mysql", + "type":"运行环境", + "ps":"一种关系数据库管理系统!", + "coexist": false, + "install_pre_inspection":true, + "uninstall_pre_inspection":true, + "checks": "server/mysql/VERSION/bin/mysql", + "path": "server/mysql/VERSION", + "todo_versions":["5.6","5.7","8.0","8.2"], + "versions":["5.5", "5.6", "5.7","8.0","8.2","8.3","8.4","9.0","9.1","9.2","9.3","9.4"], + "updates":["5.5.62","5.6.50", "5.7.32","8.0.34","8.2.0","8.3.0","9.0.1","9.1.0","9.2.0","9.3.0","9.4.0"], + "shell":"install.sh", + "checks":"server/mysql", + "path":"server/mysql", + "author":"mysql", + "home":"https://dev.mysql.com/downloads/mysql", + "date":"2017-11-24", + "pid": "2" +} diff --git a/plugins/mysql/init.d/mysql.service.tpl b/plugins/mysql/init.d/mysql.service.tpl new file mode 100644 index 000000000..08c7e126c --- /dev/null +++ b/plugins/mysql/init.d/mysql.service.tpl @@ -0,0 +1,24 @@ +[Unit] +Description=MySQL Community Server +Documentation=man:mysqld(8) +Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html +After=network.service +After=syslog.target + +[Service] +User=mysql +Group=mysql +Type=simple +ExecStart={$SERVER_PATH}/mysql/bin/mysqld --defaults-file={$SERVER_PATH}/mysql/etc/my.cnf +ExecReload=/bin/kill -USR2 $MAINPID +TimeoutSec=0 +PermissionsStartOnly=true +LimitNOFILE=5000 +Restart=on-failure +RestartSec=10 +RestartPreventExitStatus=1 +PrivateTmp=false + + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/mysql/init.d/mysql.tpl b/plugins/mysql/init.d/mysql.tpl new file mode 100644 index 000000000..6a0a54174 --- /dev/null +++ b/plugins/mysql/init.d/mysql.tpl @@ -0,0 +1,384 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# Description: mysql service +# distro. For CentOS/Redhat run: 'chkconfig --add mysql' + +# Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB +# This file is public domain and comes with NO WARRANTY of any kind + +# MySQL daemon start/stop script. + +# Usually this is put in /etc/init.d (at least on machines SYSV R4 based +# systems) and linked to /etc/rc3.d/S99mysql and /etc/rc0.d/K01mysql. +# When this is done the mysql server will be started when the machine is +# started and shut down when the systems goes down. + +# Comments to support chkconfig on RedHat Linux +# chkconfig: 2345 64 36 +# description: A very fast and reliable SQL database engine. + +# Comments to support LSB init script conventions +### BEGIN INIT INFO +# Provides: mysql +# Required-Start: $local_fs $network $remote_fs +# Should-Start: ypbind nscd ldap ntpd xntpd +# Required-Stop: $local_fs $network $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop MySQL +# Description: MySQL is a very fast and reliable SQL database engine. +### END INIT INFO + +# If you install MySQL on some other places than /www/server/mysql, then you +# have to do one of the following things for this script to work: +# +# - Run this script from within the MySQL installation directory +# - Create a /etc/my.cnf file with the following information: +# [mysqld] +# basedir= +# - Add the above to any other configuration file (for example ~/.my.ini) +# and copy my_print_defaults to /usr/bin +# - Add the path to the mysql-installation-directory to the basedir variable +# below. +# +# If you want to affect other MySQL variables, you should make your changes +# in the /etc/my.cnf, ~/.my.cnf or other MySQL configuration files. + +# If you change base dir, you must also change datadir. These may get +# overwritten by settings in the MySQL configuration files. + +basedir= +datadir= + +# Default value, in seconds, afterwhich the script should timeout waiting +# for server start. +# Value here is overriden by value in my.cnf. +# 0 means don't wait at all +# Negative numbers mean to wait indefinitely +service_startup_timeout=900 + +# Lock directory for RedHat / SuSE. +lockdir='/var/lock/subsys' +lock_file_path="$lockdir/mysql" + +# The following variables are only set for letting mysql.server find things. + +# Set some defaults +mysqld_pid_file_path= +if test -z "$basedir" +then + basedir={$SERVER_APP_PATH} + bindir={$SERVER_APP_PATH}/bin + if test -z "$datadir" + then + datadir={$SERVER_APP_PATH}/data + fi + sbindir={$SERVER_APP_PATH}/bin + libexecdir={$SERVER_APP_PATH}/bin +else + bindir="$basedir/bin" + if test -z "$datadir" + then + datadir="$basedir/data" + fi + sbindir="$basedir/sbin" + libexecdir="$basedir/libexec" +fi + +# datadir_set is used to determine if datadir was set (and so should be +# *not* set inside of the --basedir= handler.) +datadir_set= + +# +# Use LSB init script functions for printing messages, if possible +# +lsb_functions="/lib/lsb/init-functions" +if test -f $lsb_functions ; then + . $lsb_functions +else + log_success_msg() + { + echo " SUCCESS! $@" + } + log_failure_msg() + { + echo " ERROR! $@" + } +fi + +PATH="/sbin:/usr/sbin:/bin:/usr/bin:$basedir/bin" +export PATH + +mode=$1 # start or stop + +[ $# -ge 1 ] && shift + + +other_args=--sql-mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" # uncommon, but needed when called from an RPM upgrade action + # Expected: "--skip-networking --skip-grant-tables" + # They are not checked here, intentionally, as it is the resposibility + # of the "spec" file author to give correct arguments only. + +case `echo "testing\c"`,`echo -n testing` in + *c*,-n*) echo_n= echo_c= ;; + *c*,*) echo_n=-n echo_c= ;; + *) echo_n= echo_c='\c' ;; +esac + +parse_server_arguments() { + for arg do + case "$arg" in + --basedir=*) basedir=`echo "$arg" | sed -e 's/^[^=]*=//'` + bindir="$basedir/bin" + if test -z "$datadir_set"; then + datadir="$basedir/data" + fi + sbindir="$basedir/sbin" + libexecdir="$basedir/libexec" + ;; + --datadir=*) datadir=`echo "$arg" | sed -e 's/^[^=]*=//'` + datadir_set=1 + ;; + --pid-file=*) mysqld_pid_file_path=`echo "$arg" | sed -e 's/^[^=]*=//'` ;; + --service-startup-timeout=*) service_startup_timeout=`echo "$arg" | sed -e 's/^[^=]*=//'` ;; + esac + done +} + +wait_for_pid () { + verb="$1" # created | removed + pid="$2" # process ID of the program operating on the pid-file + pid_file_path="$3" # path to the PID file. + + i=0 + avoid_race_condition="by checking again" + + while test $i -ne $service_startup_timeout ; do + + case "$verb" in + 'created') + # wait for a PID-file to pop into existence. + test -s "$pid_file_path" && i='' && break + ;; + 'removed') + # wait for this PID-file to disappear + test ! -s "$pid_file_path" && i='' && break + ;; + *) + echo "wait_for_pid () usage: wait_for_pid created|removed pid pid_file_path" + exit 1 + ;; + esac + + # if server isn't running, then pid-file will never be updated + if test -n "$pid"; then + if kill -0 "$pid" 2>/dev/null; then + : # the server still runs + else + # The server may have exited between the last pid-file check and now. + if test -n "$avoid_race_condition"; then + avoid_race_condition="" + continue # Check again. + fi + + # there's nothing that will affect the file. + log_failure_msg "The server quit without updating PID file ($pid_file_path)." + return 1 # not waiting any more. + fi + fi + + echo $echo_n ".$echo_c" + i=`expr $i + 1` + sleep 1 + + done + + if test -z "$i" ; then + log_success_msg + return 0 + else + log_failure_msg + return 1 + fi +} + +# Get arguments from the my.cnf file, +# the only group, which is read from now on is [mysqld] +if test -x "$bindir/my_print_defaults"; then + print_defaults="$bindir/my_print_defaults" +else + # Try to find basedir in /etc/my.cnf + conf=/etc/my.cnf + print_defaults= + if test -r $conf + then + subpat='^[^=]*basedir[^=]*=\(.*\)$' + dirs=`sed -e "/$subpat/!d" -e 's//\1/' $conf` + for d in $dirs + do + d=`echo $d | sed -e 's/[ ]//g'` + if test -x "$d/bin/my_print_defaults" + then + print_defaults="$d/bin/my_print_defaults" + break + fi + done + fi + + # Hope it's in the PATH ... but I doubt it + test -z "$print_defaults" && print_defaults="my_print_defaults" +fi + +# +# Read defaults file from 'basedir'. If there is no defaults file there +# check if it's in the old (depricated) place (datadir) and read it from there +# + +extra_args="" +if test -r "$basedir/my.cnf" +then + extra_args="-e $basedir/my.cnf" +else + if test -r "$datadir/my.cnf" + then + extra_args="-e $datadir/my.cnf" + fi +fi + +parse_server_arguments `$print_defaults $extra_args mysqld server mysql_server mysql.server` + +# +# Set pid file if not given +# +found_pid=`cd $datadir && ls |grep '.pid'` +if test -z "$mysqld_pid_file_path" +then + mysqld_pid_file_path=$datadir/$found_pid +else + case "$mysqld_pid_file_path" in + /* ) ;; + * ) mysqld_pid_file_path="$datadir/$mysqld_pid_file_path" ;; + esac +fi + +#ulimit -s unlimited +case "$mode" in + 'start') + # Start daemon + + # Safeguard (relative paths, core dumps..) + cd $basedir + + echo $echo_n "Starting MySQL" + if test -x $bindir/mysqld_safe + then + # Give extra arguments to mysqld with the my.cnf file. This script + # may be overwritten at next upgrade. + $bindir/mysqld_safe --defaults-file=$basedir/etc/my.cnf --datadir="$datadir" $other_args >/dev/null & + wait_for_pid created "$!" "$mysqld_pid_file_path"; return_value=$? + + # Make lock for RedHat / SuSE + if test -w "$lockdir" + then + touch "$lock_file_path" + fi + + exit $return_value + else + log_failure_msg "Couldn't find MySQL server ($bindir/mysqld_safe)" + fi + ;; + + 'stop') + # Stop daemon. We use a signal here to avoid having to know the + # root password. + + if test -s "$mysqld_pid_file_path" + then + mysqld_pid=`cat "$mysqld_pid_file_path"` + + if (kill -0 $mysqld_pid 2>/dev/null) + then + echo $echo_n "Shutting down MySQL" + kill $mysqld_pid + # mysqld should remove the pid file when it exits, so wait for it. + wait_for_pid removed "$mysqld_pid" "$mysqld_pid_file_path"; return_value=$? + else + log_failure_msg "MySQL server process #$mysqld_pid is not running!" + rm "$mysqld_pid_file_path" + fi + + # Delete lock for RedHat / SuSE + if test -f "$lock_file_path" + then + rm -f "$lock_file_path" + fi + exit $return_value + else + log_failure_msg "MySQL server PID file could not be found!" + fi + ;; + + 'restart') + # Stop the service and regardless of whether it was + # running or not, start it again. + if $0 stop $other_args; then + $0 start $other_args + else + log_failure_msg "Failed to stop running server, so refusing to try to start." + exit 1 + fi + ;; + + 'reload'|'force-reload') + if test -s "$mysqld_pid_file_path" ; then + read mysqld_pid < "$mysqld_pid_file_path" + kill -HUP $mysqld_pid && log_success_msg "Reloading service MySQL" + touch "$mysqld_pid_file_path" + else + log_failure_msg "MySQL PID file could not be found!" + exit 1 + fi + ;; + 'status') + # First, check to see if pid file exists + if test -s "$mysqld_pid_file_path" ; then + read mysqld_pid < "$mysqld_pid_file_path" + if kill -0 $mysqld_pid 2>/dev/null ; then + log_success_msg "MySQL running ($mysqld_pid)" + exit 0 + else + log_failure_msg "MySQL is not running, but PID file exists" + exit 1 + fi + else + # Try to find appropriate mysqld process + mysqld_pid=`pidof $libexecdir/mysqld` + + # test if multiple pids exist + pid_count=`echo $mysqld_pid | wc -w` + if test $pid_count -gt 1 ; then + log_failure_msg "Multiple MySQL running but PID file could not be found ($mysqld_pid)" + exit 5 + elif test -z $mysqld_pid ; then + if test -f "$lock_file_path" ; then + log_failure_msg "MySQL is not running, but lock file ($lock_file_path) exists" + exit 2 + fi + log_failure_msg "MySQL is not running" + exit 3 + else + log_failure_msg "MySQL is running but PID file could not be found" + exit 4 + fi + fi + ;; + *) + # usage + basename=`basename "$0"` + echo "Usage: $basename {start|stop|restart|reload|force-reload|status} [ MySQL server options ]" + exit 1 + ;; +esac + +exit 0 diff --git a/plugins/mysql/init.d/mysql8.0.tpl b/plugins/mysql/init.d/mysql8.0.tpl new file mode 100755 index 000000000..dda204c6b --- /dev/null +++ b/plugins/mysql/init.d/mysql8.0.tpl @@ -0,0 +1,377 @@ +#!/bin/sh +# Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB +# This file is public domain and comes with NO WARRANTY of any kind + +# MySQL daemon start/stop script. + +# Usually this is put in /etc/init.d (at least on machines SYSV R4 based +# systems) and linked to /etc/rc3.d/S99mysql and /etc/rc0.d/K01mysql. +# When this is done the mysql server will be started when the machine is +# started and shut down when the systems goes down. + +# Comments to support chkconfig on RedHat Linux +# chkconfig: 2345 64 36 +# description: A very fast and reliable SQL database engine. + +# Comments to support LSB init script conventions +### BEGIN INIT INFO +# Provides: mysql +# Required-Start: $local_fs $network $remote_fs +# Should-Start: ypbind nscd ldap ntpd xntpd +# Required-Stop: $local_fs $network $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop MySQL +# Description: MySQL is a very fast and reliable SQL database engine. +### END INIT INFO + +# If you install MySQL on some other places than /Users/midoks/Desktop/fwww/server/mysql, then you +# have to do one of the following things for this script to work: +# +# - Run this script from within the MySQL installation directory +# - Create a /etc/my.cnf file with the following information: +# [mysqld] +# basedir= +# - Add the above to any other configuration file (for example ~/.my.ini) +# and copy my_print_defaults to /usr/bin +# - Add the path to the mysql-installation-directory to the basedir variable +# below. +# +# If you want to affect other MySQL variables, you should make your changes +# in the /etc/my.cnf, ~/.my.cnf or other MySQL configuration files. + +# If you change base dir, you must also change datadir. These may get +# overwritten by settings in the MySQL configuration files. + +basedir= +datadir= + +# Default value, in seconds, afterwhich the script should timeout waiting +# for server start. +# Value here is overriden by value in my.cnf. +# 0 means don't wait at all +# Negative numbers mean to wait indefinitely +service_startup_timeout=900 + +# Lock directory for RedHat / SuSE. +lockdir='/var/lock/subsys' +lock_file_path="$lockdir/mysql" + +# The following variables are only set for letting mysql.server find things. + +# Set some defaults +mysqld_pid_file_path= +if test -z "$basedir" +then + basedir={$SERVER_APP_PATH} + bindir={$SERVER_APP_PATH}/bin + if test -z "$datadir" + then + datadir={$SERVER_APP_PATH}/data + fi + sbindir={$SERVER_APP_PATH}/bin + libexecdir={$SERVER_APP_PATH}/bin +else + bindir="$basedir/bin" + if test -z "$datadir" + then + datadir="$basedir/data" + fi + sbindir="$basedir/sbin" + libexecdir="$basedir/libexec" +fi + +# datadir_set is used to determine if datadir was set (and so should be +# *not* set inside of the --basedir= handler.) +datadir_set= + +# +# Use LSB init script functions for printing messages, if possible +# +lsb_functions="/lib/lsb/init-functions" +if test -f $lsb_functions ; then + . $lsb_functions +else + log_success_msg() + { + echo " SUCCESS! $@" + } + log_failure_msg() + { + echo " ERROR! $@" + } +fi + +PATH="/sbin:/usr/sbin:/bin:/usr/bin:$basedir/bin" +export PATH + +mode=$1 # start or stop + +[ $# -ge 1 ] && shift + + +other_args="$*" # uncommon, but needed when called from an RPM upgrade action + # Expected: "--skip-networking --skip-grant-tables" + # They are not checked here, intentionally, as it is the resposibility + # of the "spec" file author to give correct arguments only. + +case `echo "testing\c"`,`echo -n testing` in + *c*,-n*) echo_n= echo_c= ;; + *c*,*) echo_n=-n echo_c= ;; + *) echo_n= echo_c='\c' ;; +esac + +parse_server_arguments() { + for arg do + case "$arg" in + --basedir=*) basedir=`echo "$arg" | sed -e 's/^[^=]*=//'` + bindir="$basedir/bin" + if test -z "$datadir_set"; then + datadir="$basedir/data" + fi + sbindir="$basedir/sbin" + libexecdir="$basedir/libexec" + ;; + --datadir=*) datadir=`echo "$arg" | sed -e 's/^[^=]*=//'` + datadir_set=1 + ;; + --pid-file=*) mysqld_pid_file_path=`echo "$arg" | sed -e 's/^[^=]*=//'` ;; + --service-startup-timeout=*) service_startup_timeout=`echo "$arg" | sed -e 's/^[^=]*=//'` ;; + esac + done +} + +wait_for_pid () { + verb="$1" # created | removed + pid="$2" # process ID of the program operating on the pid-file + pid_file_path="$3" # path to the PID file. + + i=0 + avoid_race_condition="by checking again" + + while test $i -ne $service_startup_timeout ; do + + case "$verb" in + 'created') + # wait for a PID-file to pop into existence. + test -s "$pid_file_path" && i='' && break + ;; + 'removed') + # wait for this PID-file to disappear + test ! -s "$pid_file_path" && i='' && break + ;; + *) + echo "wait_for_pid () usage: wait_for_pid created|removed pid pid_file_path" + exit 1 + ;; + esac + + # if server isn't running, then pid-file will never be updated + if test -n "$pid"; then + if kill -0 "$pid" 2>/dev/null; then + : # the server still runs + else + # The server may have exited between the last pid-file check and now. + if test -n "$avoid_race_condition"; then + avoid_race_condition="" + continue # Check again. + fi + + # there's nothing that will affect the file. + log_failure_msg "The server quit without updating PID file ($pid_file_path)." + return 1 # not waiting any more. + fi + fi + + echo $echo_n ".$echo_c" + i=`expr $i + 1` + sleep 1 + + done + + if test -z "$i" ; then + log_success_msg + return 0 + else + log_failure_msg + return 1 + fi +} + +# Get arguments from the my.cnf file, +# the only group, which is read from now on is [mysqld] +if test -x "$bindir/my_print_defaults"; then + print_defaults="$bindir/my_print_defaults" +else + # Try to find basedir in /etc/my.cnf + conf=/etc/my.cnf + print_defaults= + if test -r $conf + then + subpat='^[^=]*basedir[^=]*=\(.*\)$' + dirs=`sed -e "/$subpat/!d" -e 's//\1/' $conf` + for d in $dirs + do + d=`echo $d | sed -e 's/[ ]//g'` + if test -x "$d/bin/my_print_defaults" + then + print_defaults="$d/bin/my_print_defaults" + break + fi + done + fi + + # Hope it's in the PATH ... but I doubt it + test -z "$print_defaults" && print_defaults="my_print_defaults" +fi + +# +# Read defaults file from 'basedir'. If there is no defaults file there +# check if it's in the old (depricated) place (datadir) and read it from there +# + +extra_args="" +if test -r "$basedir/my.cnf" +then + extra_args="-e $basedir/my.cnf" +fi + +parse_server_arguments `$print_defaults $extra_args mysqld server mysql_server mysql.server` + +# +# Set pid file if not given +# +found_pid=`cd $datadir && ls |grep '.pid'` +if test -z "$mysqld_pid_file_path" +then + mysqld_pid_file_path=$datadir/$found_pid +else + case "$mysqld_pid_file_path" in + /* ) ;; + * ) mysqld_pid_file_path="$datadir/$mysqld_pid_file_path" ;; + esac +fi + +case "$mode" in + 'start') + # Start daemon + + # Safeguard (relative paths, core dumps..) + cd $basedir + + echo $echo_n "Starting MySQL" + if test -x $bindir/mysqld_safe + then + # Give extra arguments to mysqld with the my.cnf file. This script + # may be overwritten at next upgrade. + $bindir/mysqld_safe --defaults-file=$basedir/etc/my.cnf --datadir="$datadir" $other_args >/dev/null & + wait_for_pid created "$!" "$mysqld_pid_file_path"; return_value=$? + + # Make lock for RedHat / SuSE + if test -w "$lockdir" + then + touch "$lock_file_path" + fi + + exit $return_value + else + log_failure_msg "Couldn't find MySQL server ($bindir/mysqld_safe)" + fi + ;; + + 'stop') + # Stop daemon. We use a signal here to avoid having to know the + # root password. + + if test -s "$mysqld_pid_file_path" + then + # signal mysqld_safe that it needs to stop + touch "$mysqld_pid_file_path.shutdown" + + mysqld_pid=`cat "$mysqld_pid_file_path"` + + if (kill -0 $mysqld_pid 2>/dev/null) + then + echo $echo_n "Shutting down MySQL" + kill $mysqld_pid + # mysqld should remove the pid file when it exits, so wait for it. + wait_for_pid removed "$mysqld_pid" "$mysqld_pid_file_path"; return_value=$? + else + log_failure_msg "MySQL server process #$mysqld_pid is not running!" + rm "$mysqld_pid_file_path" + fi + + # Delete lock for RedHat / SuSE + if test -f "$lock_file_path" + then + rm -f "$lock_file_path" + fi + exit $return_value + else + log_failure_msg "MySQL server PID file could not be found!" + fi + ;; + + 'restart') + # Stop the service and regardless of whether it was + # running or not, start it again. + if $0 stop $other_args; then + $0 start $other_args + else + log_failure_msg "Failed to stop running server, so refusing to try to start." + exit 1 + fi + ;; + + 'reload'|'force-reload') + if test -s "$mysqld_pid_file_path" ; then + read mysqld_pid < "$mysqld_pid_file_path" + kill -HUP $mysqld_pid && log_success_msg "Reloading service MySQL" + touch "$mysqld_pid_file_path" + else + log_failure_msg "MySQL PID file could not be found!" + exit 1 + fi + ;; + 'status') + # First, check to see if pid file exists + if test -s "$mysqld_pid_file_path" ; then + read mysqld_pid < "$mysqld_pid_file_path" + if kill -0 $mysqld_pid 2>/dev/null ; then + log_success_msg "MySQL running ($mysqld_pid)" + exit 0 + else + log_failure_msg "MySQL is not running, but PID file exists" + exit 1 + fi + else + # Try to find appropriate mysqld process + mysqld_pid=`pgrep -d' ' -f $libexecdir/mysqld` + + # test if multiple pids exist + pid_count=`echo $mysqld_pid | wc -w` + if test $pid_count -gt 1 ; then + log_failure_msg "Multiple MySQL running but PID file could not be found ($mysqld_pid)" + exit 5 + elif test -z $mysqld_pid ; then + if test -f "$lock_file_path" ; then + log_failure_msg "MySQL is not running, but lock file ($lock_file_path) exists" + exit 2 + fi + log_failure_msg "MySQL is not running" + exit 3 + else + log_failure_msg "MySQL is running but PID file could not be found" + exit 4 + fi + fi + ;; + *) + # usage + basename=`basename "$0"` + echo "Usage: $basename {start|stop|restart|reload|force-reload|status} [ MySQL server options ]" + exit 1 + ;; +esac + +exit 0 diff --git a/plugins/mysql/install.sh b/plugins/mysql/install.sh new file mode 100755 index 000000000..e8b294356 --- /dev/null +++ b/plugins/mysql/install.sh @@ -0,0 +1,67 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# 手动主从设置 +# https://www.cnblogs.com/whiteY/p/17331882.html + +# cd /www/server/mdserver-web/plugins/mysql && bash install.sh install 5.5 +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mysql/index.py try_slave_sync_bugfix {} +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mysql/index.py do_full_sync {"db":"xxx","sign":"","begin":1} +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mysql/index.py sync_database_repair {"db":"xxx","sign":""} +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mysql/index.py init_slave_status +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mysql/index.py install_pre_inspection +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mysql/index.py set_slave_status {"close":"change"} +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/mysql/index.py set_root_pwd {"password":"root","force":"2"} +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +action=$1 +type=$2 + +if id mysql &> /dev/null ;then + echo "mysql UID is `id -u mysql`" + echo "mysql Shell is `grep "^mysql:" /etc/passwd |cut -d':' -f7 `" +else + groupadd mysql + useradd -g mysql -s /usr/sbin/nologin mysql +fi + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + +if [ -d $serverPath/mysql ];then + exit 0 +fi + +if [ "${action}" == "uninstall" ];then + + if [ -f /usr/lib/systemd/system/mysql.service ] || [ -f /lib/systemd/system/mysql.service ];then + systemctl stop mysql + systemctl disable mysql + rm -rf /usr/lib/systemd/system/mysql.service + rm -rf /lib/systemd/system/mysql.service + systemctl daemon-reload + fi +fi + +sh -x $curPath/versions/$2/install.sh $1 + +if [ "${action}" == "install" ] && [ -d $serverPath/mysql ];then + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/mysql/index.py start ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/mysql/index.py initd_install ${type} +fi diff --git a/plugins/mysql/js/mysql.js b/plugins/mysql/js/mysql.js new file mode 100755 index 000000000..5c86987cb --- /dev/null +++ b/plugins/mysql/js/mysql.js @@ -0,0 +1,3012 @@ + +function myPost(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'mysql', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myPostN(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + $.post('/plugins/run', {name:'mysql', func:method, args:_args}, function(data) { + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + return syncPost('/plugins/run', {name:'mysql', func:method, args:_args}); +} + + +function myPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'mysql'; + req_data['func'] = method; + req_data['script']='index_mysql'; + args['version'] = version; + + + if (typeof(args) == 'string' && args == ''){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myPostCallbakN(method, version, args,callback){ + + var req_data = {}; + req_data['name'] = 'mysql'; + req_data['func'] = method; + req_data['script']='index_mysql'; + args['version'] = version; + + + if (typeof(args) == 'string' && args == ''){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function vaildPhpmyadmin(url,username,password){ + // console.log("Authorization: Basic " + btoa(username + ":" + password)); + $.ajax({ + type: "GET", + url: url, + dataType: 'json', + async: false, + username:username, + password:password, + headers: { + "Authorization": "Basic " + btoa(username + ":" + password) + }, + data: 'vaild', + success: function (){ + alert('Thanks for your comment!'); + } + }); +} + +function runInfo(){ + myPost('run_info','',function(data){ + + var rdata = $.parseJSON(data.data); + if (typeof(rdata['status']) != 'undefined'){ + layer.msg(rdata['msg'],{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + // Com_select , Qcache_inserts + var cache_size = ((parseInt(rdata.Qcache_hits) / (parseInt(rdata.Qcache_hits) + parseInt(rdata.Qcache_inserts))) * 100).toFixed(2) + '%'; + if (cache_size == 'NaN%') cache_size = 'OFF'; + var Con = '
                              \ + \ + \ + \ + \ + \ + \ +
                              启动时间' + getLocalTime(rdata.Run) + '每秒查询' + parseInt(rdata.Questions / rdata.Uptime) + '
                              总连接次数' + rdata.Connections + '每秒事务' + parseInt((parseInt(rdata.Com_commit) + parseInt(rdata.Com_rollback)) / rdata.Uptime) + '
                              发送' + toSize(rdata.Bytes_sent) + 'File' + rdata.File + '
                              接收' + toSize(rdata.Bytes_received) + 'Position' + rdata.Position + '
                              \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                              活动/峰值连接数' + rdata.Threads_running + '/' + rdata.Max_used_connections + '若值过大,增加max_connections
                              线程缓存命中率' + ((1 - rdata.Threads_created / rdata.Connections) * 100).toFixed(2) + '%若过低,增加thread_cache_size
                              索引命中率' + ((1 - rdata.Key_reads / rdata.Key_read_requests) * 100).toFixed(2) + '%若过低,增加key_buffer_size
                              Innodb索引命中率' + (rdata.Innodb_buffer_pool_read_requests / (rdata.Innodb_buffer_pool_read_requests+rdata.Innodb_buffer_pool_reads)).toFixed(2) + '%若过低,增加innodb_buffer_pool_size
                              查询缓存命中率' + cache_size + '' + lan.soft.mysql_status_ps5 + '
                              创建临时表到磁盘' + ((rdata.Created_tmp_disk_tables / rdata.Created_tmp_tables) * 100).toFixed(2) + '%若过大,尝试增加tmp_table_size
                              已打开的表' + rdata.Open_tables + '若过大,增加table_cache_size
                              没有使用索引的量' + rdata.Select_full_join + '若不为0,请检查数据表的索引是否合理
                              没有索引的JOIN量' + rdata.Select_range_check + '若不为0,请检查数据表的索引是否合理
                              排序后的合并次数' + rdata.Sort_merge_passes + '若值过大,增加sort_buffer_size
                              锁表次数' + rdata.Table_locks_waited + '若值过大,请考虑增加您的数据库性能
                              '; + $(".soft-man-con").html(Con); + }); +} + + +function myDbPos(){ + myPost('my_db_pos','',function(data){ + var con = '
                              \ +
                              \ + \ + \ + \ +
                              '; + $(".soft-man-con").html(con); + + $('#btn_change_path').click(function(){ + var datadir = $("input[name='datadir']").val(); + myPost('set_db_pos','datadir='+datadir,function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:rdata.status ? 1 : 5,time:2000,shade: [0.3, '#000']}); + }); + }); + }); +} + +function myPort(){ + myPost('my_port','',function(data){ + var con = '
                              \ +
                              \ + \ + \ +
                              '; + $(".soft-man-con").html(con); + + $('#btn_change_port').click(function(){ + var port = $("input[name='port']").val(); + myPost('set_my_port','port='+port,function(data){ + var rdata = $.parseJSON(data.data); + if (rdata.status){ + layer.msg('修改成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + }); +} + +//数据库配置状态 +function myPerfOpt() { + //获取MySQL配置 + myPost('db_status','',function(data){ + var rdata = $.parseJSON(data.data); + if ( typeof(rdata.status) != 'undefined' && !rdata.status){ + layer.msg(rdata.msg, {icon:2}); + return; + } + + + // console.log(rdata); + var key_buffer_size = toSizeM(rdata.mem.key_buffer_size); + var query_cache_size = toSizeM(rdata.mem.query_cache_size); + var tmp_table_size = toSizeM(rdata.mem.tmp_table_size); + var innodb_buffer_pool_size = toSizeM(rdata.mem.innodb_buffer_pool_size); + var innodb_additional_mem_pool_size = toSizeM(rdata.mem.innodb_additional_mem_pool_size); + var innodb_log_buffer_size = toSizeM(rdata.mem.innodb_log_buffer_size); + + var sort_buffer_size = toSizeM(rdata.mem.sort_buffer_size); + var read_buffer_size = toSizeM(rdata.mem.read_buffer_size); + var read_rnd_buffer_size = toSizeM(rdata.mem.read_rnd_buffer_size); + var join_buffer_size = toSizeM(rdata.mem.join_buffer_size); + var thread_stack = toSizeM(rdata.mem.thread_stack); + var binlog_cache_size = toSizeM(rdata.mem.binlog_cache_size); + + var a = key_buffer_size + query_cache_size + tmp_table_size + innodb_buffer_pool_size + innodb_additional_mem_pool_size + innodb_log_buffer_size; + var b = sort_buffer_size + read_buffer_size + read_rnd_buffer_size + join_buffer_size + thread_stack + binlog_cache_size; + var memSize = a + rdata.mem.max_connections * b; + + + var memCon = '
                              \ +
                              最大使用内存: \ + \ + ' + lan.soft.mysql_set_maxmem + ': MB\ +
                              \ +

                              key_buffer_sizeMB, ' + lan.soft.mysql_set_key_buffer_size + '

                              \ +

                              query_cache_sizeMB, ' + lan.soft.mysql_set_query_cache_size + '

                              \ +

                              tmp_table_sizeMB, ' + lan.soft.mysql_set_tmp_table_size + '

                              \ +

                              innodb_buffer_pool_sizeMB, ' + lan.soft.mysql_set_innodb_buffer_pool_size + '

                              \ +

                              innodb_log_buffer_sizeMB, ' + lan.soft.mysql_set_innodb_log_buffer_size + '

                              \ +

                              innodb_additional_mem_pool_sizeMB

                              \ +

                              sort_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_sort_buffer_size + '

                              \ +

                              read_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_read_buffer_size + '

                              \ +

                              read_rnd_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_read_rnd_buffer_size + '

                              \ +

                              join_buffer_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_join_buffer_size + '

                              \ +

                              thread_stackKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_thread_stack + '

                              \ +

                              binlog_cache_sizeKB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_binlog_cache_size + '

                              \ +

                              thread_cache_size ' + lan.soft.mysql_set_thread_cache_size + '

                              \ +

                              table_open_cache ' + lan.soft.mysql_set_table_open_cache + '

                              \ +

                              max_connections ' + lan.soft.mysql_set_max_connections + '

                              \ +
                              \ +
                              ' + + $(".soft-man-con").html(memCon); + + $(".conf_p input[name*='size'],.conf_p input[name='max_connections'],.conf_p input[name='thread_stack']").change(function() { + comMySqlMem(); + }); + + $(".conf_p select[name='mysql_set']").change(function() { + mySQLMemOpt($(this).val()); + comMySqlMem(); + }); + }); +} + +function reBootMySqld(){ + pluginOpService('mysql','restart',''); +} + + +//设置MySQL配置参数 +function setMySQLConf() { + $.post('/system/system_total', '', function(memInfo) { + var memSize = memInfo['memTotal']; + var setSize = parseInt($("input[name='memSize']").val()); + + if(memSize < setSize){ + var errMsg = "错误,内存分配过高!

                              物理内存: {1}MB
                              最大使用内存: {2}MB
                              可能造成的后果: 导致数据库不稳定,甚至无法启动MySQLd服务!"; + var msg = errMsg.replace('{1}',memSize).replace('{2}',setSize); + layer.msg(msg,{icon:2,time:5000}); + return; + } + + var query_cache_size = parseInt($("input[name='query_cache_size']").val()); + var query_cache_type = 0; + if (query_cache_size > 0) { + query_cache_type = 1; + } + var data = { + key_buffer_size: parseInt($("input[name='key_buffer_size']").val()), + query_cache_size: query_cache_size, + query_cache_type: query_cache_type, + tmp_table_size: parseInt($("input[name='tmp_table_size']").val()), + max_heap_table_size: parseInt($("input[name='tmp_table_size']").val()), + innodb_buffer_pool_size: parseInt($("input[name='innodb_buffer_pool_size']").val()), + innodb_log_buffer_size: parseInt($("input[name='innodb_log_buffer_size']").val()), + sort_buffer_size: parseInt($("input[name='sort_buffer_size']").val()), + read_buffer_size: parseInt($("input[name='read_buffer_size']").val()), + read_rnd_buffer_size: parseInt($("input[name='read_rnd_buffer_size']").val()), + join_buffer_size: parseInt($("input[name='join_buffer_size']").val()), + thread_stack: parseInt($("input[name='thread_stack']").val()), + binlog_cache_size: parseInt($("input[name='binlog_cache_size']").val()), + thread_cache_size: parseInt($("input[name='thread_cache_size']").val()), + table_open_cache: parseInt($("input[name='table_open_cache']").val()), + max_connections: parseInt($("input[name='max_connections']").val()) + }; + + myPost('set_db_status', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + reBootMySqld(); + },{ icon: rdata.status ? 1 : 2 }); + }); + },'json'); +} + + +//MySQL内存优化方案 +function mySQLMemOpt(opt) { + var query_size = parseInt($("input[name='query_cache_size']").val()); + switch (opt) { + case '0': + $("input[name='key_buffer_size']").val(8); + if (query_size) $("input[name='query_cache_size']").val(4); + $("input[name='tmp_table_size']").val(8); + $("input[name='innodb_buffer_pool_size']").val(16); + $("input[name='sort_buffer_size']").val(256); + $("input[name='read_buffer_size']").val(256); + $("input[name='read_rnd_buffer_size']").val(128); + $("input[name='join_buffer_size']").val(128); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(32); + $("input[name='thread_cache_size']").val(4); + $("input[name='table_open_cache']").val(32); + $("input[name='max_connections']").val(500); + break; + case '1': + $("input[name='key_buffer_size']").val(128); + if (query_size) $("input[name='query_cache_size']").val(64); + $("input[name='tmp_table_size']").val(64); + $("input[name='innodb_buffer_pool_size']").val(256); + $("input[name='sort_buffer_size']").val(768); + $("input[name='read_buffer_size']").val(768); + $("input[name='read_rnd_buffer_size']").val(512); + $("input[name='join_buffer_size']").val(1024); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(64); + $("input[name='thread_cache_size']").val(64); + $("input[name='table_open_cache']").val(128); + $("input[name='max_connections']").val(100); + break; + case '2': + $("input[name='key_buffer_size']").val(256); + if (query_size) $("input[name='query_cache_size']").val(128); + $("input[name='tmp_table_size']").val(384); + $("input[name='innodb_buffer_pool_size']").val(384); + $("input[name='sort_buffer_size']").val(768); + $("input[name='read_buffer_size']").val(768); + $("input[name='read_rnd_buffer_size']").val(512); + $("input[name='join_buffer_size']").val(2048); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(64); + $("input[name='thread_cache_size']").val(96); + $("input[name='table_open_cache']").val(192); + $("input[name='max_connections']").val(200); + break; + case '3': + $("input[name='key_buffer_size']").val(384); + if (query_size) $("input[name='query_cache_size']").val(192); + $("input[name='tmp_table_size']").val(512); + $("input[name='innodb_buffer_pool_size']").val(512); + $("input[name='sort_buffer_size']").val(1024); + $("input[name='read_buffer_size']").val(1024); + $("input[name='read_rnd_buffer_size']").val(768); + $("input[name='join_buffer_size']").val(2048); + $("input[name='thread_stack']").val(256); + $("input[name='binlog_cache_size']").val(128); + $("input[name='thread_cache_size']").val(128); + $("input[name='table_open_cache']").val(384); + $("input[name='max_connections']").val(300); + break; + case '4': + $("input[name='key_buffer_size']").val(512); + if (query_size) $("input[name='query_cache_size']").val(256); + $("input[name='tmp_table_size']").val(1024); + $("input[name='innodb_buffer_pool_size']").val(1024); + $("input[name='sort_buffer_size']").val(2048); + $("input[name='read_buffer_size']").val(2048); + $("input[name='read_rnd_buffer_size']").val(1024); + $("input[name='join_buffer_size']").val(4096); + $("input[name='thread_stack']").val(384); + $("input[name='binlog_cache_size']").val(192); + $("input[name='thread_cache_size']").val(192); + $("input[name='table_open_cache']").val(1024); + $("input[name='max_connections']").val(400); + break; + case '5': + $("input[name='key_buffer_size']").val(1024); + if (query_size) $("input[name='query_cache_size']").val(384); + $("input[name='tmp_table_size']").val(2048); + $("input[name='innodb_buffer_pool_size']").val(4096); + $("input[name='sort_buffer_size']").val(4096); + $("input[name='read_buffer_size']").val(4096); + $("input[name='read_rnd_buffer_size']").val(2048); + $("input[name='join_buffer_size']").val(8192); + $("input[name='thread_stack']").val(512); + $("input[name='binlog_cache_size']").val(256); + $("input[name='thread_cache_size']").val(256); + $("input[name='table_open_cache']").val(2048); + $("input[name='max_connections']").val(500); + break; + case '6': + $("input[name='key_buffer_size']").val(2048); + if (query_size) $("input[name='query_cache_size']").val(384); + $("input[name='tmp_table_size']").val(4096); + $("input[name='innodb_buffer_pool_size']").val(8192); + $("input[name='sort_buffer_size']").val(8192); + $("input[name='read_buffer_size']").val(8192); + $("input[name='read_rnd_buffer_size']").val(4096); + $("input[name='join_buffer_size']").val(16384); + $("input[name='thread_stack']").val(1024); + $("input[name='binlog_cache_size']").val(512); + $("input[name='thread_cache_size']").val(512); + $("input[name='table_open_cache']").val(4096); + $("input[name='max_connections']").val(1000); + break; + case '7': + $("input[name='key_buffer_size']").val(4096); + if (query_size) $("input[name='query_cache_size']").val(384); + $("input[name='tmp_table_size']").val(8192); + $("input[name='innodb_buffer_pool_size']").val(16384); + $("input[name='sort_buffer_size']").val(16384); + $("input[name='read_buffer_size']").val(16384); + $("input[name='read_rnd_buffer_size']").val(8192); + $("input[name='join_buffer_size']").val(16384); + $("input[name='thread_stack']").val(2048); + $("input[name='binlog_cache_size']").val(1024); + $("input[name='thread_cache_size']").val(1024); + $("input[name='table_open_cache']").val(8192); + $("input[name='max_connections']").val(2000); + break; + } +} + +//计算MySQL内存开销 +function comMySqlMem() { + var key_buffer_size = parseInt($("input[name='key_buffer_size']").val()); + var query_cache_size = parseInt($("input[name='query_cache_size']").val()); + var tmp_table_size = parseInt($("input[name='tmp_table_size']").val()); + var innodb_buffer_pool_size = parseInt($("input[name='innodb_buffer_pool_size']").val()); + var innodb_additional_mem_pool_size = parseInt($("input[name='innodb_additional_mem_pool_size']").val()); + var innodb_log_buffer_size = parseInt($("input[name='innodb_log_buffer_size']").val()); + + var sort_buffer_size = $("input[name='sort_buffer_size']").val() / 1024; + var read_buffer_size = $("input[name='read_buffer_size']").val() / 1024; + var read_rnd_buffer_size = $("input[name='read_rnd_buffer_size']").val() / 1024; + var join_buffer_size = $("input[name='join_buffer_size']").val() / 1024; + var thread_stack = $("input[name='thread_stack']").val() / 1024; + var binlog_cache_size = $("input[name='binlog_cache_size']").val() / 1024; + var max_connections = $("input[name='max_connections']").val(); + + var a = key_buffer_size + query_cache_size + tmp_table_size + innodb_buffer_pool_size + innodb_additional_mem_pool_size + innodb_log_buffer_size + var b = sort_buffer_size + read_buffer_size + read_rnd_buffer_size + join_buffer_size + thread_stack + binlog_cache_size + var memSize = a + max_connections * b + $("input[name='memSize']").val(memSize.toFixed(2)); +} + +function syncGetDatabase(){ + myPost('sync_get_databases', null, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function syncToDatabase(type){ + var data = []; + $('input[type="checkbox"].check:checked').each(function () { + if (!isNaN($(this).val())) data.push($(this).val()); + }); + var postData = 'type='+type+'&ids='+JSON.stringify(data); + myPost('sync_to_databases', postData, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function setRootPwd(type, pwd){ + if (type==1){ + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + return; + } + + var index = layer.open({ + type: 1, + area: '800px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + btn:["提交", "关闭", "复制ROOT密码", "修改本地ROOT记录", "强改ROOT密码"], + shadeClose: true, + content: "

                              \ +
                              \ + root密码\ +
                              \ + \ +
                              \ +
                              \ +
                              ", + yes:function(layerIndex){ + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layerIndex); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + }, + btn3:function(){ + var password = $("#MyPassword").val(); + copyText(password); + return false; + }, + btn4:function(layerIndex){ + layer.confirm('修改本地ROOT记录,确定修改?', { + btn: ['确定', '取消'] + }, function(index, layero){ + layer.close(index); + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password,force:'1'}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layerIndex); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + }); + return false; + }, + btn5:function(layerIndex){ + layer.confirm('强制修改MySQL密码,确定强制? (比较耗时)', { + btn: ['确定', '取消'] + }, function(index, layero){ + layer.close(index); + var password = $("#MyPassword").val(); + myPost('set_root_pwd', {password:password,force:'2'}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layerIndex); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + }); + return false; + } + }); +} + +function showHidePass(obj){ + var a = "glyphicon-eye-open"; + var b = "glyphicon-eye-close"; + + if($(obj).hasClass(a)){ + $(obj).removeClass(a).addClass(b); + $(obj).prev().text($(obj).prev().attr('data-pw')) + } + else{ + $(obj).removeClass(b).addClass(a); + $(obj).prev().text('***'); + } +} + +function checkSelect(){ + setTimeout(function () { + var num = $('input[type="checkbox"].check:checked').length; + // console.log(num); + if (num == 1) { + $('button[batch="true"]').hide(); + $('button[batch="false"]').show(); + }else if (num>1){ + $('button[batch="true"]').show(); + $('button[batch="false"]').show(); + }else{ + $('button[batch="true"]').hide(); + $('button[batch="false"]').hide(); + } + },5) +} + +function setDbRw(id,username,val){ + myPost('set_db_rw',{id:id,username:username,rw:val}, function(data){ + var rdata = $.parseJSON(data.data); + // layer.msg(rdata.msg,{icon:rdata.status ? 1 : 5,shade: [0.3, '#000']}); + showMsg(rdata.msg, function(){ + dbList(); + },{icon:rdata.status ? 1 : 5,shade: [0.3, '#000']}, 2000); + + }); +} + +function setDbAccess(username){ + myPost('get_db_access','username='+username, function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,shade: [0.3, '#000']}); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '设置数据库权限', + closeBtn: 1, + shift: 5, + btn:["提交","取消"], + shadeClose: true, + content: "
                              \ +
                              \ + 访问权限\ +
                              \ + \ +
                              \ +
                              \ +
                              ", + success:function(){ + if (rdata.msg == '127.0.0.1'){ + $('select[name="dataAccess"]').find("option[value='127.0.0.1']").attr("selected",true); + } else if (rdata.msg == '%'){ + $('select[name="dataAccess"]').find('option[value="%"]').attr("selected",true); + } else if ( rdata.msg == 'ip' ){ + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } else { + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#set_db_access").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['access']){ + dataObj['access'] = dataObj['dataAccess']; + if ( dataObj['dataAccess'] == 'ip'){ + if (dataObj['address']==''){ + layer.msg('IP地址不能空!',{icon:2,shade: [0.3, '#000']}); + return; + } + dataObj['access'] = dataObj['address']; + } + } + dataObj['username'] = username; + myPost('set_db_access', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); + + }); +} + +function fixDbAccess(username){ + myPost('fix_db_access', '', function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); +} + +function setDbPass(id, username, password){ + layer.open({ + type: 1, + area: '500px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                              \ +
                              \ + 用户名\ +
                              \ +
                              \ +
                              \ + 密码\ +
                              \ + \ +
                              \ +
                              \ + \ +
                              ", + yes:function(index){ + // var data = $("#mod_pwd").serialize(); + var data = {}; + data['name'] = $('input[name=name]').val(); + data['password'] = $('#MyPassword').val(); + data['id'] = $('input[name=id]').val(); + myPost('set_user_pwd', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); +} + +function addDatabase(type){ + layer.open({ + type: 1, + area: '500px', + title: '添加数据库', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                              \ +
                              \ + 数据库名\ +
                              \ + \ +
                              \ +
                              \ +
                              用户名
                              \ +
                              \ + 密码\ +
                              \ +
                              \ +
                              \ + 访问权限\ +
                              \ + \ +
                              \ +
                              \ + \ +
                              ", + success:function(){ + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='db_user']").val(v); + $("input[name='ps']").val(v); + }); + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index) { + var data = $("#add_db").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['address']){ + dataObj['address'] = dataObj['dataAccess']; + } + myPost('add_db', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + dbList(); + } + },{icon: rdata.status ? 1 : 2}, 2000); + }); + } + }); +} + +function delDb(id, name){ + safeMessage('删除['+name+']','您真的要删除['+name+']吗?',function(){ + var data='id='+id+'&name='+name; + myPost('del_db', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); +} + +function delDbBatch(){ + var arr = []; + $('input[type="checkbox"].check:checked').each(function () { + var _val = $(this).val(); + var _name = $(this).parent().next().text(); + if (!isNaN(_val)) { + arr.push({'id':_val,'name':_name}); + } + }); + + safeMessage('批量删除数据库','您共选择了[2]个数据库,删除后将无法恢复,真的要删除吗?',function(){ + var i = 0; + $(arr).each(function(){ + var data = myAsyncPost('del_db', this); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + } + i++; + }); + + var msg = '成功删除['+i+']个数据库!'; + showMsg(msg,function(){ + dbList(); + },{icon: 1}, 600); + }); +} + + +function setDbPs(id, name, obj) { + var _span = $(obj); + var _input = $(""); + _span.hide().after(_input); + _input.focus(); + _input.blur(function(){ + $(this).remove(); + var ps = _input.val(); + _span.text(ps).show(); + var data = {name:name,id:id,ps:ps}; + myPost('set_db_ps', data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + }); + _input.keyup(function(){ + if(event.keyCode == 13){ + _input.trigger('blur'); + } + }); +} + +function openPhpmyadmin(name,username,password){ + $.post('/plugins/run', {'name':'phpmyadmin','func':'plugins_db_support'}, function(data){ + var rdata = $.parseJSON(data.data); + + if (rdata.data['installed'] != 'ok'){ + layer.msg('phpMyAdmin未安装!',{icon:2,shade: [0.3, '#000']}); + return; + } + + if (rdata.data['status'] != 'start'){ + layer.msg('phpMyAdmin未启动',{icon:2,shade: [0.3, '#000']}); + return; + } + + if (rdata.data['cfg']['choose'] != 'mysql'){ + layer.msg('当前为['+rdata.data['cfg']['choose'] + ']模式,若要使用请修改phpMyAdmin访问切换.',{icon:2,shade: [0.3, '#000']}); + return; + } + var home_page = rdata.data['home_page']; + $("#toPHPMyAdmin").attr('action',home_page); + if($("#toPHPMyAdmin").attr('action').indexOf('phpmyadmin') == -1){ + layer.msg('请先安装phpMyAdmin',{icon:2,shade: [0.3, '#000']}); + setTimeout(function(){ window.location.href = '/soft'; },3000); + return; + } + //检查版本 + bigVer = rdata.data['version']; + if (bigVer>=4.5){ + + setTimeout(function(){ + $("#toPHPMyAdmin").submit(); + },2000); + layer.msg('phpMyAdmin['+data.data+']需要手动登录😭',{icon:16,shade: [0.3, '#000'],time:4000}); + + } else{ + var murl = $("#toPHPMyAdmin").attr('action'); + $("#pma_username").val(username); + $("#pma_password").val(password); + $("#db").val(name); + + layer.msg('正在打开phpMyAdmin',{icon:16,shade: [0.3, '#000'],time:2000}); + + setTimeout(function(){ + $("#toPHPMyAdmin").submit(); + },2000); + } + + },'json'); +} + +function delBackup(filename, name, path){ + if(typeof(path) == "undefined"){ + path = ""; + } + myPost('delete_db_backup',{filename:filename,path:path},function(){ + layer.msg('执行成功!'); + setTimeout(function(){ + setBackupReq(name); + },2000); + }); +} + +function downloadBackup(file){ + window.open('/files/download?filename='+encodeURIComponent(file)); +} + +function importBackup(file,name){ + myPost('import_db_backup',{file:file,name:name}, function(data){ + // console.log(data); + layer.msg('执行成功!'); + }); +} + +function importBackupProgress(file,name){ + myPost('import_db_backup_progress',{file:file,name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + title: "手动导入命令CMD【显示进度】", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                              \ +
                              \ +
                              '+rdata.data+'
                              \ +
                              \ +
                              ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + + +function importDbExternal(file,name){ + myPost('import_db_external',{file:file,name:name}, function(data){ + layer.msg('执行成功!'); + }); +} + +function importDbExternalProgress(file,name){ + myPost('import_db_external_progress',{file:file,name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + title: "手动导入命令CMD【显示进度】", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                              \ +
                              \ +
                              '+rdata.data+'
                              \ +
                              \ +
                              ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + +function setLocalImport(db_name){ + + //上传文件 + function uploadDbFiles(upload_dir){ + var up_db = layer.open({ + type:1, + closeBtn: 1, + title:"上传导入文件["+upload_dir+']', + area: ['500px','300px'], + shadeClose:false, + content:'
                              \ + \ + \ + \ + \ + \ + \ + 文件编码:\ + \ + \ + \ +
                                \ +
                                ', + success:function(){ + $('#filesClose').click(function(){ + layer.close(up_db); + }); + } + + }); + uploadStart(function(){ + getList(); + layer.close(up_db); + }); + } + + function getList(){ + myPost('get_db_backup_import_list',{}, function(data){ + var rdata = $.parseJSON(data.data); + + var file_list = rdata.data.list; + var upload_dir = rdata.data.upload_dir; + + var tbody = ''; + for (var i = 0; i < file_list.length; i++) { + tbody += '\ + ' + file_list[i]['name'] + '\ + ' + file_list[i]['size'] + '\ + ' + file_list[i]['time'] + '\ + \ + 导入 | \ + 导入进度 | \ + 删除\ + \ + '; + } + + $('#import_db_file_list').html(tbody); + $('input[name="upload_dir"]').val(upload_dir); + + $("#import_db_file_list .del").on('click',function(){ + var index = $(this).attr('index'); + var filename = file_list[index]["name"]; + myPost('delete_db_backup',{filename:filename,path:upload_dir},function(){ + showMsg('执行成功!', function(){ + getList(); + },{icon:1},2000); + }); + }); + }); + } + + var layerIndex = layer.open({ + type: 1, + title: "从文件导入数据", + area: ['700px', '380px'], + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + \ +
                                \ +
                                \ + \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                文件名称文件大小备份时间操作
                                \ +
                                \ +
                                  \ +
                                • 仅支持sql、zip、sql.gz、(tar.gz|gz|tgz)
                                • \ +
                                • zip、tar.gz压缩包结构:test.zip或test.tar.gz压缩包内,必需包含test.sql
                                • \ +
                                • 若文件过大,您还可以使用SFTP工具,将数据库文件上传到/www/backup/import
                                • \ +
                                \ +
                                \ +
                                ', + success:function(index){ + $('#btn_file_upload').click(function(){ + var upload_dir = $('input[name="upload_dir"]').val(); + uploadDbFiles(upload_dir); + }); + + getList(); + }, + }); + + +} + +function setBackup(db_name){ + var layerIndex = layer.open({ + type: 1, + title: "数据库备份详情", + area: ['700px', '280px'], + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + \ + \ +
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                文件名称文件大小备份时间操作
                                \ +
                                \ +
                                \ +
                                ', + success:function(index){ + $('#btn_backup').click(function(){ + myPost('set_db_backup',{name:db_name}, function(data){ + showMsg('执行成功!', function(){ + setBackupReq(db_name); + }, {icon:1}, 2000); + }); + }); + + $('#btn_local_import').click(function(){ + setLocalImport(db_name); + }); + + setBackupReq(db_name); + }, + }); +} + + +function setBackupReq(db_name, obj){ + myPost('get_db_backup_list', {name:db_name}, function(data){ + var rdata = $.parseJSON(data.data); + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + tbody += '\ + ' + rdata.data[i]['name'] + '\ + ' + rdata.data[i]['size'] + '\ + ' + rdata.data[i]['time'] + '\ + \ + 导入 | \ + 导入进度 | \ + 下载 | \ + 删除\ + \ + '; + } + $('#database_table tbody').html(tbody); + }); +} + +function dbList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + myPost('get_db_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list +=''; + list += '' + rdata.data[i]['name'] +''; + list += '' + rdata.data[i]['username'] +''; + list += '' + + '***' + + ''+ + ''+ + ''; + + + list += ''+rdata.data[i]['ps']+''; + list += ''; + + list += ''+(rdata.data[i]['is_backup']?'已备份':'未备份') +' | '; + + var rw = ''; + var rw_change = 'all'; + if (typeof(rdata.data[i]['rw'])!='undefined'){ + var rw_val = '读写'; + if (rdata.data[i]['rw'] == 'all'){ + rw_val = "所有"; + rw_change = 'rw'; + } else if (rdata.data[i]['rw'] == 'rw'){ + rw_val = "读写"; + rw_change = 'r'; + } else if (rdata.data[i]['rw'] == 'r'){ + rw_val = "只读"; + rw_change = 'all'; + } + rw = ''+rw_val+' | '; + } + + + list += '管理 | ' + + '工具 | ' + + '权限 | ' + + rw + + '改密 | ' + + '删除' + + ''; + list += ''; + } + + // + var con = '
                                \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + '+ + // ''+ + '\ + \ + \ + '+ list +'\ +
                                数据库名用户名密码备份备注操作
                                \ +
                                \ +
                                \ +
                                \ + 同步选中\ + 同步所有\ + 从服务器获取\ +
                                \ +
                                \ +
                                '; + + con += ''; + + $(".soft-man-con").html(con); + $('#databasePage').html(rdata.page); + + readerTableChecked(); + }); +} + + +function myBinRollingLogs(_name, func, _args, line){ + + var file_line = 100; + if ( typeof(line) != 'undefined' ){ + file_line = line; + } + + var reqTimer = null; + + function requestLogs(func,file,line){ + myPostCallbakN(func,'',{'file':file,"line":line}, function(rdata){ + var data = rdata.data.data; + var cmd = rdata.data.cmd; + if(data == '') { + data = '当前没有日志!'; + } + + $('#my_rolling_cmd').html(cmd); + + $('#my_rolling_copy').click(function(){ + copyText(cmd); + }); + + var ebody = ''; + $("#my_rolling_logs").html(ebody); + var ob = document.getElementById('roll_info_log'); + ob.scrollTop = ob.scrollHeight; + }); + } + + + layer.open({ + type: 1, + title: _name + '日志', + area: ['800px','700px'], + end: function(){ + if (reqTimer){ + clearInterval(reqTimer); + } + }, + content:'
                                \ +
                                \ + \ + \ + \ + \ +
                                cmd
                                \ +
                                \ +
                                \ +
                                \ + \ +
                                ', + success:function(){ + var fileName = _args['file']; + requestLogs(func,fileName,file_line); + reqTimer = setInterval(function(){ + requestLogs(func,fileName,file_line); + },1000); + } + }); +} + +function myBinLogsRender(page){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + _data['tojs'] = 'myBinLogsRender'; + myPost('binlog_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var list = ''; + for(i in rdata.data){ + list += ''; + + list += '' + rdata.data[i]['name'] +''; + list += '' + toSize(rdata.data[i]['size'])+''; + list += '' + rdata.data[i]['time'] +''; + + + list += ''; + list += '查看 | '; + list += '解码查看'; + list += ''; + } + + if (rdata.data.length ==0){ + list = '无数据'; + } + + $("#binlog_list tbody").html(list); + $('#binlog_page').html(rdata.page); + + + $('#binlog_list .look').click(function(){ + var i = $(this).data('index'); + var file = rdata.data[i]['name']; + myBinRollingLogs('查看BINLOG','binLogListLook',{'file':file },100); + }); + + $('#binlog_list .look_decode').click(function(){ + var i = $(this).data('index'); + var file = rdata.data[i]['name']; + myBinRollingLogs('查看解码BINLOG','binLogListLookDecode',{'file':file },100); + }); + }); +} + +function myBinLogs(){ + var con = '
                                \ + \ + \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ +
                                文件名称大小时间操作
                                \ +
                                \ +
                                \ +
                                \ +
                                '; + $(".soft-man-con").html(con); + myBinLogsRender(1); + + $('.soft-man-con .relay_trace').click(function(){ + myBinRollingLogs('中继日志跟踪','binLogListTraceRelay',{'file':''},100); + }); + + $('.soft-man-con .binlog_trace').click(function(){ + myBinRollingLogs('最新BINLOG日志跟踪','binLogListTraceBinLog',{'file':''},100); + }); +} + +function myLogs(){ + + myPost('bin_log', {status:1}, function(data){ + var rdata = $.parseJSON(data.data); + + var line_status = "" + if (rdata.status){ + line_status = '\ + '; + } else { + line_status = ''; + } + + var limitCon = '

                                \ + 二进制日志 ' + toSize(rdata.msg) + '\ + '+line_status+'\ +

                                错误日志

                                \ + \ +

                                '; + $(".soft-man-con").html(limitCon); + + //设置二进制日志 + $(".btn-bin").click(function () { + myPost('bin_log', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){myLogs();}, 2000); + }); + }); + + $(".clean-btn-bin").click(function () { + myPost('clean_bin_log', '', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){myLogs();}, 2000); + }); + }); + + //清空日志 + $(".btn-clear").click(function () { + myPost('error_log', 'close=1', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){myLogs();}, 2000); + }); + }) + + myPost('error_log', 'p=1', function(data){ + var rdata = $.parseJSON(data.data); + var error_body = ''; + if (rdata.status){ + error_body = rdata.data; + } else { + error_body = rdata.msg; + } + $("#error_log").html(error_body); + var ob = document.getElementById('error_log'); + ob.scrollTop = ob.scrollHeight; + }); + }); +} + + +function repCheckeds(tables) { + var dbs = [] + if (tables) { + dbs.push(tables) + } else { + var db_tools = $("input[value^='dbtools_']"); + for (var i = 0; i < db_tools.length; i++) { + if (db_tools[i].checked) dbs.push(db_tools[i].value.replace('dbtools_', '')); + } + } + + if (dbs.length < 1) { + layer.msg('请至少选择一张表!', { icon: 2 }); + return false; + } + return dbs; +} + +function repDatabase(db_name, tables) { + dbs = repCheckeds(tables); + + myPost('repair_table', { db_name: db_name, tables: JSON.stringify(dbs) }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + repTools(db_name, true); + },'已送修复指令,请稍候...'); +} + + +function optDatabase(db_name, tables) { + dbs = repCheckeds(tables); + + myPost('opt_table', { db_name: db_name, tables: JSON.stringify(dbs) }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + repTools(db_name, true); + },'已送优化指令,请稍候...'); +} + +function toDatabaseType(db_name, tables, type){ + dbs = repCheckeds(tables); + myPost('alter_table', { db_name: db_name, tables: JSON.stringify(dbs),table_type: type }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + repTools(db_name, true); + }, '已送引擎转换指令,请稍候...'); +} + + +function selectedTools(my_obj, db_name) { + var is_checked = false + + if (my_obj) is_checked = my_obj.checked; + var db_tools = $("input[value^='dbtools_']"); + var n = 0; + for (var i = 0; i < db_tools.length; i++) { + if (my_obj) db_tools[i].checked = is_checked; + if (db_tools[i].checked) n++; + } + if (n > 0) { + var my_btns = '\ + \ + \ + ' + $("#db_tools").html(my_btns); + } else { + $("#db_tools").html(''); + } +} + +function repTools(db_name, res){ + myPost('get_db_info', {name:db_name}, function(data){ + var rdata = $.parseJSON(data.data); + var types = { InnoDB: "MyISAM", MyISAM: "InnoDB" }; + var tbody = ''; + for (var i = 0; i < rdata.tables.length; i++) { + if (!types[rdata.tables[i].type]) continue; + tbody += '\ + \ + ' + rdata.tables[i].table_name + '\ + ' + rdata.tables[i].type + '\ + ' + rdata.tables[i].collation + '\ + ' + rdata.tables[i].rows_count + '\ + ' + rdata.tables[i].data_size + '\ + \ + 修复 |\ + 优化 |\ + 转为' + types[rdata.tables[i].type] + '\ + \ + ' + } + + if (res) { + $(".gztr").html(tbody); + $("#db_tools").html(''); + $("input[type='checkbox']").attr("checked", false); + $(".tools_size").html('大小:' + rdata.data_size); + return; + } + + layer.open({ + type: 1, + title: "MySQL工具箱【" + db_name + "】", + area: ['780px', '580px'], + closeBtn: 1, + shadeClose: false, + content: '
                                \ + \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + ' + tbody + '\ +
                                表名引擎字符集行数大小操作
                                \ +
                                \ +
                                \ +
                                  \ +
                                • 【修复】尝试使用REPAIR命令修复损坏的表,仅能做简单修复,若修复不成功请考虑使用myisamchk工具
                                • \ +
                                • 【优化】执行OPTIMIZE命令,可回收未释放的磁盘空间,建议每月执行一次
                                • \ +
                                • 【转为InnoDB/MyISAM】转换数据表引擎,建议将所有表转为InnoDB
                                • \ +
                                ' + }); + tableFixed('database_fix'); + }); +} + + +function setDbMaster(name){ + myPost('set_db_master', {name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + masterOrSlaveConf(); + }, 2000); + }); +} + + +function setDbSlave(name){ + myPost('set_db_slave', {name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + masterOrSlaveConf(); + }, 2000); + }); +} + + +function addMasterRepSlaveUser(){ + layer.open({ + type: 1, + area: '500px', + title: '添加同步账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","取消"], + content: "
                                \ +
                                用户名
                                \ +
                                \ + 密码\ +
                                \ +
                                \ + \ +
                                ", + success:function(){ + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='db_user']").val(v); + $("input[name='ps']").val(v); + }); + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#add_master").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['address']){ + dataObj['address'] = dataObj['dataAccess']; + } + + myPost('add_master_rep_slave_user', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + if (rdata.status){ + getMasterRepSlaveList(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); +} + + + +function updateMasterRepSlaveUser(username, password){ + + var index = layer.open({ + type: 1, + area: '500px', + title: '更新账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + content: "
                                \ +
                                用户名
                                \ +
                                \ + 密码\ +
                                \ +
                                \ + \ +
                                \ + \ +
                                \ +
                                ", + }); + + $('#submit_update_master').click(function(){ + var data = $("#update_master").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + myPost('update_master_rep_slave_user', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getMasterRepSlaveList(); + } + $('.layui-layer-close1').click(); + },{icon: rdata.status ? 1 : 2},600); + }); + }); +} + +function getMasterRepSlaveUserCmd(username, db=''){ + myPost('get_master_rep_slave_user_cmd', {username:username,db:db}, function(data){ + var rdata = $.parseJSON(data.data); + + if (!rdata['status']){ + layer.msg(rdata['msg']); + return; + } + + var cmd = rdata.data['cmd']; + + var loadOpen = layer.open({ + type: 1, + title: '同步命令', + area: '500px', + content:"
                                \ +
                                "+cmd+"
                                \ +
                                \ + \ +
                                \ +
                                ", + }); + }); +} + +function delMasterRepSlaveUser(username){ + myPost('del_master_rep_slave_user', {username:username}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function(){ + getMasterRepSlaveList(); + },{icon: rdata.status ? 1 : 2},1000) + }); +} + + +function setDbMasterAccess(username){ + myPost('get_db_access','username='+username, function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,shade: [0.3, '#000']}); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '设置数据库权限', + closeBtn: 1, + shift: 5, + btn:["提交","取消"], + shadeClose: true, + content: "
                                \ +
                                \ + 访问权限\ +
                                \ + \ +
                                \ +
                                \ +
                                ", + success:function(){ + if (rdata.msg == '127.0.0.1'){ + $('select[name="dataAccess"]').find("option[value='127.0.0.1']").attr("selected",true); + } else if (rdata.msg == '%'){ + $('select[name="dataAccess"]').find('option[value="%"]').attr("selected",true); + } else if ( rdata.msg == 'ip' ){ + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } else { + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#set_db_access").serialize(); + data = decodeURIComponent(data); + var dataObj = toArrayObject(data); + if(!dataObj['access']){ + dataObj['access'] = dataObj['dataAccess']; + if ( dataObj['dataAccess'] == 'ip'){ + if (dataObj['address']==''){ + layer.msg('IP地址不能空!',{icon:2,shade: [0.3, '#000']}); + return; + } + dataObj['access'] = dataObj['address']; + } + } + dataObj['username'] = username; + myPost('set_dbmaster_access', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); + + }); +} + + +function resetMaster(){ + myPost('reset_master', '', function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + },{icon: rdata.status ? 1 : 2}); + },'正在执行重置master命令[reset master]'); +} + +function getMasterRepSlaveList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + myPost('get_master_rep_slave_list', _data, function(data){ + // console.log(data); + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e){ + console.log(e); + } + var list = ''; + // console.log(rdata['data']); + var user_list = rdata['data']; + for (i in user_list) { + // console.log(i); + var name = user_list[i]['username']; + var password = user_list[i]['password']; + list += ''+name+'\ + '+password+'\ + \ + 修改 | \ + 删除 | \ + 权限 | \ + 从库同步命令\ + \ + '; + } + + $('#get_master_rep_slave_list_page tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + +function getMasterRepSlaveListPage(){ + var page = '
                                '; + page += '
                                添加同步账户
                                '; + + var loadOpen = layer.open({ + type: 1, + title: '同步账户列表', + area: '500px', + content:"
                                \ +
                                \ +
                                \ + \ + \ +
                                用户名密码操作
                                \ + "+page +"\ +
                                \ +
                                ", + success:function(){ + getMasterRepSlaveList(); + } + }); +} + + +function deleteSlave(sign){ + myPost('delete_slave', {sign:sign}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata['msg'], function(){ + masterOrSlaveConf(); + },{icon:rdata.status?1:2,time:3000},3000); + }); +} + + +function getFullSyncStatus(db){ + var timeId = null; + + myPost('get_slave_list', {page:1,page_size:100}, function(data){ + var rdata = $.parseJSON(data.data); + var rsource = rdata.data; + + if (db == 'ALL' && rsource.length>1){ + layer.msg("多主不支持该模式!",{icon:2}); + return; + } + + var dataSource = ''; + if (rsource.length>1){ + var sourceList = ''; + for (var i = 0; i < rsource.length; i++) { + if ('Channel_Name' in rsource[i]){ + sourceList += ''; + } + } + + dataSource = "

                                \ + 同步数据源:\ + \ +

                                "; + } + + layer.open({ + type: 1, + title: '全量同步['+db+']', + area: '500px', + content:"
                                \ +
                                \ + "+dataSource+"\ + \ +
                                \ +
                                0%
                                \ +
                                \ +
                                \ +
                                \ + 开始\ + 手动命令\ +
                                \ +
                                ", + cancel: function(){ + clearInterval(timeId); + }, + success:function(){ + $('#begin_full_sync').click(function(){ + var val = $(this).data('status'); + var sign = ''; + if (dataSource !=''){ + sign = $('select[name="data_source"]').val(); + } + if (val == 'init'){ + fullSync(db, sign, 1); + timeId = setInterval(function(){ + fullSync(db,sign,0); + }, 1000); + $(this).data('status','starting'); + $('#begin_full_sync').text("同步中"); + } else { + layer.msg("正在同步中..",{icon:0}); + } + }); + + $('#full_sync_cmd').click(function(){ + myPostN('full_sync_cmd', {'db':db,'sign':''}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.open({ + title: "手动执行命令CMD", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                                \ +
                                \ +
                                '+rdata.data+'
                                \ +
                                \ +
                                ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); + }); + } + }); + }); + + function fullSync(db,sign,begin){ + + myPostN('full_sync', {db:db,sign:sign,begin:begin}, function(data){ + var rdata = $.parseJSON(data.data); + $('#full_msg').text(rdata['msg']); + $('.progress-bar').css('width',rdata['progress']+'%'); + $('.progress-bar').text(rdata['progress']+'%'); + + if (rdata['code']==6 ||rdata['code']<0){ + layer.msg(rdata['msg']); + clearInterval(timeId); + $('#begin_full_sync').text("同步结束,再次同步?"); + $("#begin_full_sync").attr('data-status','init'); + } + }); + } +} + +function dataSyncVerify(db){ + var reqTimer = null; + + function requestLogs(layerIndex){ + myPostN('sync_database_repair_log', {db:db, sign:'',op:'get'}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + + if(!rdata.status) { + layer.close(layerIndex); + layer.msg(rdata.msg,{icon:2, time:2000}); + clearInterval(reqTimer); + return; + }; + + if (rdata.msg == ''){ + rdata.msg = '暂无数据!'; + } + + $("#data_verify_log").html(rdata.msg); + //滚动到最低 + var ob = document.getElementById('data_verify_log'); + ob.scrollTop = ob.scrollHeight; + }); + } + + layer.open({ + type: 1, + title: '同步数据库['+db+']数据校验', + area: '500px', + btn:[ "开始","取消","手动"], + content:"
                                \ + "+'
                                '+"\
                                +            
                                ", + cancel: function(){ + if (reqTimer){ + clearInterval(reqTimer); + } + }, + yes:function(index,layer_index){ + myPostN('sync_database_repair_log', {db:db, sign:'',op:'do'}, function(data){}); + layer.msg("执行成功"); + + requestLogs(layer_index); + reqTimer = setInterval(function(){ + requestLogs(layer_index); + },3000); + }, + success:function(){ + }, + btn3: function(){ + myPostN('sync_database_repair_log', {db:db, sign:'',op:'cmd'}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.open({ + title: "手动执行命令CMD", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                                \ +
                                \ +
                                '+rdata.data+'
                                \ +
                                \ +
                                ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); + return false; + } + + }); +} + +function addSlaveSSH(ip=''){ + + myPost('get_slave_ssh_by_ip', {ip:ip}, function(rdata){ + + var rdata = $.parseJSON(rdata.data); + + var ip = '127.0.0.1'; + var port = "22"; + var id_rsa = ''; + var db_user =''; + + if (rdata.data.length>0){ + ip = rdata.data[0]['ip']; + port = rdata.data[0]['port']; + id_rsa = rdata.data[0]['id_rsa']; + db_user = rdata.data[0]['db_user']; + } + + var index = layer.open({ + type: 1, + area: ['500px','480px'], + title: '添加SSH', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["确认","取消"], + content: "
                                \ +
                                IP
                                \ +
                                端口
                                \ +
                                同步账户[DB]
                                \ +
                                \ + ID_RSA\ +
                                \ +
                                \ + \ +
                                ", + success:function(){ + $('textarea[name="id_rsa"]').html(id_rsa); + }, + yes:function(index){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var db_user = $('input[name="db_user"]').val(); + var id_rsa = $('textarea[name="id_rsa"]').val(); + + var data = {ip:ip,port:port,id_rsa:id_rsa,db_user:db_user}; + myPost('add_slave_ssh', data, function(data){ + layer.close(index); + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSSHPage(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); + }); +} + + +function delSlaveSSH(ip){ + myPost('del_slave_ssh', {ip:ip}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSSHPage(); + } + },{icon: rdata.status ? 1 : 2}, 600); + }); +} + + +function delSlaveSyncUser(ip){ + myPost('del_slave_sync_user', {ip:ip}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSyncUserPage(); + } + },{icon: rdata.status ? 1 : 2}, 600); + }); +} + +function getSlaveSSHPage(page=1){ + var _data = {}; + _data['page'] = page; + _data['page_size'] = 5; + _data['tojs'] ='getSlaveSSHPage'; + myPost('get_slave_ssh_list', _data, function(data){ + var layerId = null; + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e) { + console.log(e); + } + var list = ''; + var ssh_list = rdata['data']; + for (i in ssh_list) { + var ip = ssh_list[i]['ip']; + var port = ssh_list[i]['port']; + + var id_rsa = '未设置'; + if ( ssh_list[i]['port'] != ''){ + id_rsa = '已设置'; + } + + var db_user = '未设置'; + if ( ssh_list[i]['db_user'] != ''){ + db_user = ssh_list[i]['db_user']; + } + + list += ''+ip+'\ + '+port+'\ + '+db_user+'\ + '+id_rsa+'\ + \ + 修改 | \ + 删除\ + \ + '; + } + + $('.get-slave-ssh-list tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + + + +function addSlaveSyncUser(ip=''){ + + myPost('get_slave_sync_user_by_ip', {ip:ip}, function(rdata){ + + var rdata = $.parseJSON(rdata.data); + + var ip = '127.0.0.1'; + var port = "22"; + var cmd = ''; + var user = 'input_sync_user'; + var pass = 'input_sync_pwd'; + var mode = '0'; + + if (rdata.data.length>0){ + ip = rdata.data[0]['ip']; + port = rdata.data[0]['port']; + cmd = rdata.data[0]['cmd']; + user = rdata.data[0]['user']; + pass = rdata.data[0]['pass']; + mode = rdata.data[0]['mode']; + } + + var index = layer.open({ + type: 1, + area: ['500px','510px'], + title: '同步账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["确认","取消"], + content: "
                                \ +
                                IP
                                \ +
                                端口
                                \ +
                                同步账户
                                \ +
                                同步密码
                                \ +
                                \ + 同步模式\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + CMD[必填]\ +
                                \ +
                                \ + \ +
                                ", + success:function(){ + $('textarea[name="cmd"]').html(cmd); + $('textarea[name="cmd"]').change(function(){ + var val = $(this).val(); + val = val.replace(';',''); + var a = {}; + if (val.toLowerCase().indexOf('for')>0){ + cmd_tmp = val.split('for'); + val = cmd_tmp[0].trim(); + + const channel_str = cmd_tmp[1].trim(); + const ch_reg = /channel \'(.*)\';/; + var match_val = channel_str.match(ch_reg); + if (match_val.length>1){ + a['channel'] = match_val[1]; + } + } + + var vlist = val.split(','); + for (var i in vlist) { + var tmp = toTrim(vlist[i]); + var tmp_a = tmp.split(" "); + var real_tmp = tmp_a[tmp_a.length-1]; + var kv = real_tmp.split("="); + a[kv[0]] = kv[1].replace("'",'').replace("'",''); + } + + if ('MASTER_HOST' in a){ + $('input[name="ip"]').val(a['MASTER_HOST']); + $('input[name="port"]').val(a['MASTER_PORT']); + $('input[name="user"]').val(a['MASTER_USER']); + $('input[name="pass"]').val(a['MASTER_PASSWORD']); + } else { + $('input[name="ip"]').val(a['SOURCE_HOST']); + $('input[name="port"]').val(a['SOURCE_PORT']); + $('input[name="user"]').val(a['SOURCE_USER']); + $('input[name="pass"]').val(a['SOURCE_PASSWORD']); + } + }); + }, + yes:function(index){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var user = $('input[name="user"]').val(); + var pass = $('input[name="pass"]').val(); + var cmd = $('textarea[name="cmd"]').val(); + var mode = $('select[name="mode"]').val(); + + var data = {ip:ip,port:port,cmd:cmd,user:user,pass:pass,mode:mode}; + myPost('add_slave_sync_user', data, function(ret_data){ + layer.close(index); + var rdata = $.parseJSON(ret_data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSyncUserPage(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); + }); +} + +function getSlaveSyncUserPage(page=1){ + var _data = {}; + _data['page'] = page; + _data['page_size'] = 5; + _data['tojs'] ='getSlaveSyncUserPage'; + myPost('get_slave_sync_user_list', _data, function(data){ + var layerId = null; + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e) { + console.log(e); + } + + var list = ''; + var user_list = rdata['data']; + for (i in user_list) { + var ip = user_list[i]['ip']; + var port = user_list[i]['port']; + var user = user_list[i]['user']; + var apass = user_list[i]['pass']; + + var cmd = '未设置'; + if (user_list[i]['cmd']!=''){ + cmd = '已设置'; + } + + list += ''+ip+'\ + '+port+'\ + '+user+'\ + '+apass+'\ + '+cmd+'\ + \ + 修改 | \ + 删除\ + \ + '; + } + + $('.get-slave-ssh-list tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + +function getSlaveCfg(){ + + myPost('get_slave_sync_mode', '', function(data){ + var rdata = $.parseJSON(data.data); + var mode_none = 'success'; + var mode_ssh = 'danger'; + var mode_sync_user = 'danger'; + if(rdata.status){ + var mode_none = 'danger'; + if (rdata.data == 'ssh'){ + var mode_ssh = 'success'; + var mode_sync_user = 'danger'; + } else { + var mode_ssh = 'danger'; + var mode_sync_user = 'success'; + } + } + + layerId = layer.open({ + type: 1, + title: '同步配置', + area: ['400px','180px'], + content:"
                                \ +

                                \ + 当前从库同步模式\ + \ + \ + \ + \ +

                                \ +
                                \ +

                                \ + 配置设置\ + \ + \ + \ +

                                \ +
                                ", + success:function(){ + $('.btn-slave-ssh').click(function(){ + getSlaveSSHList(); + }); + + $('.btn-slave-user').click(function(){ + getSlaveUserList(); + }); + + $('.slave-db-mode').click(function(){ + var _this = this; + var mode = 'none'; + if ($(this).hasClass('btn-ssh')){ + mode = 'ssh'; + } + if ($(this).hasClass('btn-sync-user')){ + mode = 'sync-user'; + } + + myPost('set_slave_sync_mode', {mode:mode}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + $('.slave-db-mode').remove('btn-success').addClass('btn-danger'); + $(_this).removeClass('btn-danger').addClass('btn-success'); + },{icon:rdata.status?1:2},2000); + }); + + }); + } + }); + }); +} + + +function getSlaveUserList(){ + + var page = '
                                '; + page += '
                                添加同步账户
                                '; + + layerId = layer.open({ + type: 1, + title: '同步账户列表', + area: '600px', + content:"
                                \ +
                                \ +
                                \ + \ + \ +
                                IPPORT同步账户同步密码CMD操作
                                \ + "+page +"\ +
                                \ +
                                ", + success:function(){ + getSlaveSyncUserPage(1); + } + }); +} + +function getSlaveSSHList(page=1){ + + var page = '
                                '; + page += '
                                添加SSH
                                '; + + layerId = layer.open({ + type: 1, + title: 'SSH列表', + area: '600px', + content:"
                                \ +
                                \ +
                                \ + \ + \ +
                                IPPORT同步账户SSH操作
                                \ + "+page +"\ +
                                \ +
                                ", + success:function(){ + getSlaveSSHPage(1); + } + }); +} + +function handlerRun(){ + myPostN('get_slave_sync_cmd', {}, function(data){ + var rdata = $.parseJSON(data.data); + var cmd = rdata['data']; + var loadOpen = layer.open({ + type: 1, + title: '手动执行', + area: '500px', + content:"
                                \ +
                                "+cmd+"
                                \ +
                                \ + \ +
                                \ +
                                ", + }); + copyPass(cmd); + $('.class-copy-cmd').click(function(){ + copyPass(cmd); + }); + }); +} + +function initSlaveStatus(){ + myPost('init_slave_status', '', function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + masterOrSlaveConf(); + } + },{icon:rdata.status?1:2},2000); + }); +} + +function masterOrSlaveConf(version=''){ + + function getMasterDbList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + + myPost('get_masterdb_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list += '' + rdata.data[i]['name'] +''; + list += '' + (rdata.data[i]['master']?'是':'否') +''; + list += '' + + ''+(rdata.data[i]['master']?'退出':'加入')+' | ' + + '同步命令' + + ''; + list += ''; + } + + var con = '
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                数据库名同步操作
                                \ +
                                \ +
                                \ +
                                \ + 同步账户列表\ +
                                \ +
                                '; + + $(".table_master_list").html(con); + $('#databasePage').html(rdata.page); + }); + } + + function getAsyncMasterDbList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + var mdb_ver = $('.plugin_version').attr('version'); + + myPost('get_slave_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + + var isHasSign = false; + for(i in rdata.data){ + + var v = rdata.data[i]; + if ('Channel_Name' in v && v['Channel_Name'] !=''){ + isHasSign = true; + } + + var status = "异常"; + if (mdb_ver >= 8){ + if (v['Replica_SQL_Running'] == 'Yes' && v['Replica_IO_Running'] == 'Yes'){ + status = "正常"; + } + + list += ''; + list += '' + rdata.data[i]['Source_Host'] +''; + list += '' + rdata.data[i]['Source_Port'] +''; + list += '' + rdata.data[i]['Source_User'] +''; + list += '' + rdata.data[i]['Relay_Source_Log_File'] +''; + list += '' + rdata.data[i]['Replica_IO_Running'] +''; + list += '' + rdata.data[i]['Replica_SQL_Running'] +''; + + } else { + if (v['Slave_SQL_Running'] == 'Yes' && v['Slave_IO_Running'] == 'Yes'){ + status = "正常"; + } + + list += ''; + list += '' + rdata.data[i]['Master_Host'] +''; + list += '' + rdata.data[i]['Master_Port'] +''; + list += '' + rdata.data[i]['Master_User'] +''; + list += '' + rdata.data[i]['Master_Log_File'] +''; + list += '' + rdata.data[i]['Slave_IO_Running'] +''; + list += '' + rdata.data[i]['Slave_SQL_Running'] +''; + } + + if (isHasSign){ + list += '' + v['Channel_Name'] +''; + } + + list += '' + status +''; + list += '' + + '删除' + + ''; + list += ''; + } + + var signThead_th = ''; + if (isHasSign){ + var signThead_th = '标识'; + } + + var con = '
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + '+signThead_th+'\ + \ + \ + \ + '+ list +'\ +
                                主[服务]端口用户日志IOSQL状态操作
                                \ +
                                \ +
                                '; + + //
                                \ + //
                                \ + // 添加\ + //
                                + $(".table_slave_status_list").html(con); + + $(".btn_delete_slave").click(function(){ + var id = $(this).data('id'); + var v = rdata.data[id]; + if ('Channel_Name' in v){ + deleteSlave(v['Channel_Name']); + } else{ + deleteSlave(); + } + }); + + $('.db_error').click(function(){ + var id = $(this).data('id'); + var info = rdata.data[id]; + + var err_line = ""; + err_line +="\ + IO错误\ + "+ (info['Last_IO_Error'] == '' ? '无':info['Last_IO_Error'])+"\ + "; + err_line +="\ + SQL错误\ + "+(info['Last_SQL_Error'] == '' ? '无':info['Last_SQL_Error'])+"\ + "; + + err_line +="\ + 状态\ + "+(info['Slave_SQL_Running_State'] == '' ? '无':info['Slave_SQL_Running_State']) +"\ + "; + + + var btn_list = ['复制错误',"取消"]; + if (info['Last_IO_Error'].search(/1236/i)>0){ + btn_list = ['复制错误',"取消","尝试修复"]; + } + layer.open({ + type: 1, + title: '同步异常信息', + area: ['600px','300px'], + btn:btn_list, + content:"
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + "+ err_line +"\ +
                                类型内容
                                \ +
                                \ +
                                \ +
                                ", + success:function(){ + if (info['Last_IO_Error'] != ''){ + copyText(info['Last_IO_Error']); + return; + } + + if (info['Last_SQL_Error'] != ''){ + copyText(info['Last_SQL_Error']); + return; + } + + if (info['Slave_SQL_Running_State'] != ''){ + copyText(info['Slave_SQL_Running_State']); + return; + } + }, + yes:function(){ + if (info['Last_IO_Error'] != ''){ + copyText(info['Last_IO_Error']); + return; + } + + if (info['Last_SQL_Error'] != ''){ + copyText(info['Last_SQL_Error']); + return; + } + + if (info['Slave_SQL_Running_State'] != ''){ + copyText(info['Slave_SQL_Running_State']); + return; + } + }, + btn3:function(){ + myPost('try_slave_sync_bugfix', {}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function(){ + masterOrSlaveConf(); + },{ icon: rdata.status ? 1 : 5 },2000); + }); + } + }); + }); + }); + } + + function getAsyncDataList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + myPost('get_masterdb_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list += '' + rdata.data[i]['name'] +''; + list += '' + + ''+(rdata.data[i]['slave']?'退出':'加入')+' | ' + + '同步 | ' + + '数据校验' + + ''; + list += ''; + } + + var con = '
                                \ +
                                \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                本地库名操作
                                \ +
                                \ +
                                \ +
                                \ + 手动命令\ + 全量同步\ +
                                \ +
                                '; + + $(".table_slave_list").html(con); + $('#databasePage').html(rdata.page); + }); + } + + + function getMasterStatus(){ + myPost('get_master_status', '', function(rdata){ + var rdata = $.parseJSON(rdata.data); + // console.log('mode:',rdata.data); + if ( typeof(rdata.status) != 'undefined' && !rdata.status && rdata.data == 'pwd'){ + layer.msg(rdata.msg, {icon:2}); + return; + } + + var rdata = rdata.data; + var limitCon = '\ +

                                \ + 主从同步模式\ + \ + \ +

                                \ +
                                \ +

                                \ + Master[主]配置\ + \ + \ +

                                \ +
                                \ + \ +
                                \ +
                                \ + \ +

                                \ + Slave[从]配置\ + \ + \ + \ +

                                \ +
                                \ + \ +
                                \ + \ +
                                \ + '; + $(".soft-man-con").html(limitCon); + + //设置主服务器配置 + $(".btn-master").click(function () { + myPost('set_master_status', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + getMasterStatus(); + }, 3000); + }); + }); + + $(".btn-slave").click(function () { + myPost('set_slave_status', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + getMasterStatus(); + }, 3000); + }); + }); + + $('.db-mode').click(function(){ + if ($(this).hasClass('btn-success')){ + //no action + return; + } + + var mode = 'classic'; + if ($(this).hasClass('btn-gtid')){ + mode = 'gtid'; + } + + layer.open({ + type:1, + title:"MySQL主从模式切换", + shadeClose:false, + btnAlign: 'c', + btn: ['切换并重启', '切换不重启'], + yes: function(index, layero){ + this.change(index,mode,"yes"); + + }, + btn2: function(index, layero){ + this.change(index,mode,"no"); + return false; + }, + change:function(index,mode,reload){ + console.log(index,mode,reload); + myPost('set_dbrun_mode',{'mode':mode,'reload':reload},function(data){ + layer.close(index); + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg ,function(){ + getMasterStatus(); + },{ icon: rdata.status ? 1 : 5 }); + }); + } + }); + }); + + if (rdata.status){ + getMasterDbList(); + } + + // if (rdata.slave_status){ + getAsyncMasterDbList(); + getAsyncDataList() + // } + }); + } + getMasterStatus(); +} diff --git a/plugins/mysql/lib/rpcgen.sh b/plugins/mysql/lib/rpcgen.sh new file mode 100644 index 000000000..9dcceff46 --- /dev/null +++ b/plugins/mysql/lib/rpcgen.sh @@ -0,0 +1,29 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +which rpcgen +if [ "$?" != "0" ];then + + if [ ! -f ${SOURCE_ROOT}/rpcsvc-proto-1.4.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/rpcsvc-proto-1.4.tar.gz https://github.com/thkukuk/rpcsvc-proto/releases/download/v1.4/rpcsvc-proto-1.4.tar.gz + fi + + if [ ! -d ${SERVER_ROOT}/rpcsvc-proto-1.4 ];then + cd ${SOURCE_ROOT} && tar -zxvf rpcsvc-proto-1.4.tar.gz + cd ${SOURCE_ROOT}/rpcsvc-proto-1.4 + ./configure && make && make install + fi + +fi \ No newline at end of file diff --git a/plugins/mysql/patch/mysql-5.5-fix-arm-client_plugin.patch b/plugins/mysql/patch/mysql-5.5-fix-arm-client_plugin.patch new file mode 100644 index 000000000..6c6f30ec2 --- /dev/null +++ b/plugins/mysql/patch/mysql-5.5-fix-arm-client_plugin.patch @@ -0,0 +1,37 @@ +diff -ruN mysql-5.5.42.orig/sql-common/client_plugin.c mysql-5.5.42/sql-common/client_plugin.c +--- mysql-5.5.42.orig/sql-common/client_plugin.c 2015-01-07 04:39:40.000000000 +0800 ++++ mysql-5.5.42/sql-common/client_plugin.c 2015-03-24 10:36:45.682700014 +0800 +@@ -233,6 +233,7 @@ + { + MYSQL mysql; + struct st_mysql_client_plugin **builtin; ++ va_list dummy; + + if (initialized) + return 0; +@@ -249,7 +250,7 @@ + pthread_mutex_lock(&LOCK_load_client_plugin); + + for (builtin= mysql_client_builtins; *builtin; builtin++) +- add_plugin(&mysql, *builtin, 0, 0, 0); ++ add_plugin(&mysql, *builtin, 0, 0, dummy); + + pthread_mutex_unlock(&LOCK_load_client_plugin); + +@@ -293,6 +294,7 @@ + mysql_client_register_plugin(MYSQL *mysql, + struct st_mysql_client_plugin *plugin) + { ++ va_list dummy; + if (is_not_initialized(mysql, plugin->name)) + return NULL; + +@@ -307,7 +309,7 @@ + plugin= NULL; + } + else +- plugin= add_plugin(mysql, plugin, 0, 0, 0); ++ plugin= add_plugin(mysql, plugin, 0, 0, dummy); + + pthread_mutex_unlock(&LOCK_load_client_plugin); + return plugin; diff --git a/plugins/mysql/scripts/tools.py b/plugins/mysql/scripts/tools.py new file mode 100755 index 000000000..d54330376 --- /dev/null +++ b/plugins/mysql/scripts/tools.py @@ -0,0 +1,119 @@ +# coding: utf-8 + +import sys +import os +import json +import time + +sys.path.append(os.getcwd() + "/class/core") +import mw +import db + +cmd = 'ls /usr/local/lib/ | grep python | cut -d \\ -f 1 | awk \'END {print}\'' +info = mw.execShell(cmd) +p = "/usr/local/lib/" + info[0].strip() + "/site-packages" +sys.path.append(p) + + +def set_mysql_root(password): + # 设置MySQL密码 + import db + import os + sql = db.Sql() + + root_mysql = '''#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +pwd=$1 +${server}/init.d/mysql stop +${server}/bin/mysqld_safe --skip-grant-tables& +echo '正在修改密码...'; +echo 'The set password...'; +sleep 6 +m_version=$(cat ${server}/version.pl|grep -E "(5.1.|5.5.|5.6.|mariadb)") +if [ "$m_version" != "" ];then + ${server}/bin/mysql -uroot -e "insert into mysql.user(Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Reload_priv,Shutdown_priv,Process_priv,File_priv,Grant_priv,References_priv,Index_priv,Alter_priv,Show_db_priv,Super_priv,Create_tmp_table_priv,Lock_tables_priv,Execute_priv,Repl_slave_priv,Repl_client_priv,Create_view_priv,Show_view_priv,Create_routine_priv,Alter_routine_priv,Create_user_priv,Event_priv,Trigger_priv,Create_tablespace_priv,User,Password,host)values('Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','root',password('${pwd}'),'127.0.0.1')" + ${server}/bin/mysql -uroot -e "insert into mysql.user(Select_priv,Insert_priv,Update_priv,Delete_priv,Create_priv,Drop_priv,Reload_priv,Shutdown_priv,Process_priv,File_priv,Grant_priv,References_priv,Index_priv,Alter_priv,Show_db_priv,Super_priv,Create_tmp_table_priv,Lock_tables_priv,Execute_priv,Repl_slave_priv,Repl_client_priv,Create_view_priv,Show_view_priv,Create_routine_priv,Alter_routine_priv,Create_user_priv,Event_priv,Trigger_priv,Create_tablespace_priv,User,Password,host)values('Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','root',password('${pwd}'),'localhost')" + ${server}/bin/mysql -uroot -e "UPDATE mysql.user SET password=PASSWORD('${pwd}') WHERE user='root'"; +else + ${server}/bin/mysql -uroot -e "UPDATE mysql.user SET authentication_string='' WHERE user='root'"; + ${server}/bin/mysql -uroot -e "FLUSH PRIVILEGES"; + ${server}/bin/mysql -uroot -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '${pwd}';"; +fi +${server} -uroot -e "FLUSH PRIVILEGES"; +pkill -9 mysqld_safe +pkill -9 mysqld +sleep 2 +${server}/init.d/mysql start + +echo '===========================================' +echo "root密码成功修改为: ${pwd}" +echo "The root password set ${pwd} successuful"''' + + server = mw.getServerDir() + '/mysql' + root_mysql = root_mysql.replace('${server}', server) + mw.writeFile('mysql_root.sh', root_mysql) + os.system("/bin/bash mysql_root.sh " + password) + os.system("rm -f mysql_root.sh") + + pos = mw.getServerDir() + '/mysql' + result = sql.table('config').dbPos(pos, 'mysql').where( + 'id=?', (1,)).setField('mysql_root', password) + + +def set_panel_pwd(password, ncli=False): + # 设置面板密码 + import db + sql = db.Sql() + result = sql.table('users').where('id=?', (1,)).setField( + 'password', mw.md5(password)) + username = sql.table('users').where('id=?', (1,)).getField('username') + if ncli: + print("|-用户名: " + username) + print("|-新密码: " + password) + else: + print(username) + + +def set_panel_username(username=None): + # 随机面板用户名 + import db + sql = db.Sql() + if username: + if len(username) < 5: + print("|-错误,用户名长度不能少于5位") + return + if username in ['admin', 'root']: + print("|-错误,不能使用过于简单的用户名") + return + + sql.table('users').where('id=?', (1,)).setField('username', username) + print("|-新用户名: %s" % username) + return + + username = sql.table('users').where('id=?', (1,)).getField('username') + if username == 'admin': + username = mw.getRandomString(8).lower() + sql.table('users').where('id=?', (1,)).setField('username', username) + print('username: ' + username) + + +def getServerIp(): + version = sys.argv[2] + ip = mw.execShell( + "curl -{} -sS --connect-timeout 5 -m 60 https://v6r.ipip.net/?format=text".format(version)) + print(ip[0]) + + +if __name__ == "__main__": + type = sys.argv[1] + if type == 'root': + set_mysql_root(sys.argv[2]) + elif type == 'panel': + set_panel_pwd(sys.argv[2]) + elif type == 'username': + set_panel_username() + elif type == 'getServerIp': + getServerIp() + else: + print('ERROR: Parameter error') diff --git a/plugins/mysql/versions/5.5/install.sh b/plugins/mysql/versions/5.5/install.sh new file mode 100755 index 000000000..e6ce62bab --- /dev/null +++ b/plugins/mysql/versions/5.5/install.sh @@ -0,0 +1,219 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.5.html#downloads +#https://dev.mysql.com/downloads/file/?id=480541 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` +sysArch=`arch` + +mysqlDir=${serverPath}/source/mysql + +VERSION=5.5.62 + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +Install_common(){ + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + + apt install -y build-essential + apt install -y cmake + apt install -y pkg-config + apt install -y libncurses5-dev + apt install -y libsystemd-dev + apt install -y libsasl2-dev + apt install -y libldap2-dev +} + +# 安装依赖 +Install_dep(){ + Install_common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-10 g++-10 + WHERE_DIR_GCC=/usr/bin/gcc-10 + WHERE_DIR_GPP=/usr/bin/g++-10 +} + +Install_dep_debain13(){ + Install_common + # add-apt-repository -y ppa:ubuntu-toolchain-r/test + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-12 g++-12 + WHERE_DIR_GCC=/usr/bin/gcc-12 + WHERE_DIR_GPP=/usr/bin/g++-12 +} + +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + if [ ! -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-${VERSION}.tar.gz + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-${VERSION}.tar.gz + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ ! -f $WHERE_DIR_GCC ];then + WHERE_DIR_GCC=`which gcc` + fi + + if [ ! -f $WHERE_DIR_GPP ];then + WHERE_DIR_GPP=`which g++` + fi + + # https://blog.whsir.com/post-7748.html + # arm架构编译MySQL5.5报错 + if [[ "$sysArch" =~ "aarch" ]];then + # wget --no-check-certificate -O ${mysqlDir}/mysql-5.5-fix-arm-client_plugin.patch https://down.whsir.com/downloads/mysql-5.5-fix-arm-client_plugin.patch + cd ${mysqlDir}/mysql-5.5.62 && patch -p1 < ${rootPath}/plugins/mysql/patch/mysql-5.5-fix-arm-client_plugin.patch + fi + + if [ "$OSNAME" == "ubuntu" ];then + Install_dep + fi + + OPTIONS='' + + if [ "$OSNAME" == "debian" ] && [ "$VERSION_ID" == "13" ];then + Install_dep_debain13 + # export CFLAGS="-D__s64=long long -D__u64='unsigned long long' -D__s32=int -D__u32='unsigned int' -D__u16='unsigned short'" + # export CXXFLAGS="$CFLAGS" + # OPTIONS="${OPTIONS} -DCMAKE_C_FLAGS=${CFLAGS}" + # OPTIONS="${OPTIONS} -DCMAKE_CXX_FLAGS=${CXXFLAGS}" + + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + export PKG_CONFIG_PATH=${serverPath}/lib/openssl10/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl10" + + # 经过测试,无法安装 + echo "debain13不支持5.5低版本编译" + exit 0 + fi + + if [ ! -d $serverPath/mysql ];then + cd ${mysqlDir}/mysql-5.5.62 && cmake \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + $OPTIONS \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '5.5' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-5.5.62 + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/5.6/install.sh b/plugins/mysql/versions/5.6/install.sh new file mode 100755 index 000000000..43f5d15f9 --- /dev/null +++ b/plugins/mysql/versions/5.6/install.sh @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.6.html +#https://dev.mysql.com/downloads/file/?id=489600 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` +sysArch=`arch` + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +mysqlDir=${serverPath}/source/mysql + +VERSION=5.6.51 +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + + if [ ! -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://cdn.mysql.com/Downloads/MySQL-5.6/mysql-${VERSION}.tar.gz + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-${VERSION}.tar.gz + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ ! -f $WHERE_DIR_GCC ];then + WHERE_DIR_GCC=`which gcc` + fi + + if [ ! -f $WHERE_DIR_GPP ];then + WHERE_DIR_GPP=`which g++` + fi + + if [ "$OSNAME" == "ubuntu" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl10.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl11/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl11" + fi + + if [ ! -d $serverPath/mysql ];then + cd ${mysqlDir}/mysql-${VERSION} && cmake \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DENABLE_DOWNLOADS=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DCMAKE_CXX_STANDARD=11 + + make -j${cpuCore} && make install && make clean + + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '5.6' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-5.6.* + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/5.7/install.sh b/plugins/mysql/versions/5.7/install.sh new file mode 100755 index 000000000..97b6ddc0e --- /dev/null +++ b/plugins/mysql/versions/5.7/install.sh @@ -0,0 +1,227 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.7.html +#https://dev.mysql.com/downloads/file/?id=489855 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mysqlDir=${serverPath}/source/mysql + +VERSION=5.7.44 + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +Install_common(){ + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + + apt install -y build-essential + apt install -y cmake + apt install -y pkg-config + apt install -y libncurses5-dev + apt install -y libsystemd-dev + apt install -y libsasl2-dev + apt install -y libldap2-dev +} + +# 安装依赖 +Install_dep(){ + Install_common + + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 +} + +Install_dep_debain13(){ + Install_common + + # add-apt-repository -y ppa:ubuntu-toolchain-r/test + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-12 g++-12 + WHERE_DIR_GCC=/usr/bin/gcc-12 + WHERE_DIR_GPP=/usr/bin/g++-12 +} + +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + if [ "$sysName" != "Darwin" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + if [ ! -f ${mysqlDir}/mysql-boost-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://cdn.mysql.com/archives/mysql-5.7/mysql-boost-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=1a637fce4599d9bf5f1c81699f086274 + if [ -f ${mysqlDir}/mysql-boost-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-boost-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql5.7 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://cdn.mysql.com/archives/mysql-5.7/mysql-boost-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-boost-${VERSION}.tar.gz + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ ! -f $WHERE_DIR_GCC ];then + WHERE_DIR_GCC=`which gcc` + fi + + if [ ! -f $WHERE_DIR_GPP ];then + WHERE_DIR_GPP=`which g++` + fi + + if [ "$OSNAME" == "ubuntu" ];then + Install_dep + fi + + if [ "$OSNAME" == "debian" ] && [ "$VERSION_ID" == "13" ];then + Install_dep_debain13 + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_11.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl11/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl11" + fi + + if [ ! -d $serverPath/mysql ];then + cd ${mysqlDir}/mysql-${VERSION} && cmake \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=${WHERE_DIR_GCC} \ + -DCMAKE_CXX_COMPILER=${WHERE_DIR_GPP} \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '5.7' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/8.0/install.sh b/plugins/mysql/versions/8.0/install.sh new file mode 100755 index 000000000..31c3da456 --- /dev/null +++ b/plugins/mysql/versions/8.0/install.sh @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.7.html +#https://dev.mysql.com/downloads/file/?id=489855 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mysqlDir=${serverPath}/source/mysql + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=8.0.37 +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -f ${mysqlDir}/mysql-boost-${VERSION}.tar.gz ];then + #wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-boost-${VERSION}.tar.gz + wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://cdn.mysql.com/archives/mysql-8.0/mysql-boost-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=e0cb61cbf6e1144c452368c4535ae931 + if [ -f ${mysqlDir}/mysql-boost-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-boost-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql8.0 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-boost-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-boost-${VERSION}.tar.gz + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl" + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ "$OSNAME" == "centos" ] && [ "$VERSION_ID" == "7" ];then + yum install -y libudev-devel + yum install -y centos-release-scl + yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils + + gcc --version + WHERE_DIR_GCC=/opt/rh/devtoolset-11/root/usr/bin/gcc + WHERE_DIR_GPP=/opt/rh/devtoolset-11/root/usr/bin/g++ + echo $WHERE_DIR_GCC + echo $WHERE_DIR_GPP + fi + + if [ "$OSNAME" == "ubuntu" ] && [ "$VERSION_ID" == "18.04" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + LIBTIRPC_VER=`pkg-config libtirpc --modversion` + if [ ! -f ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 ];then + wget --no-check-certificate -O ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.3.tar.bz2 + cd ${mysqlDir} && tar -jxvf libtirpc_1.3.3.orig.tar.bz2 + cd libtirpc-1.3.3 && ./configure + fi + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + + if [ "$OSNAME" == "opensuse" ];then + zypper install -y gcc11 + zypper install -y gcc11-c++ + + + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + if [ ! -d $serverPath/mysql ];then + # -DCMAKE_CXX_STANDARD=17 \ + cd ${mysqlDir}/mysql-${VERSION} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DWITH_READLINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + -DFORCE_INSOURCE_BUILD=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DDOWNLOAD_BOOST=0 \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '8.0' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/8.2/install.sh b/plugins/mysql/versions/8.2/install.sh new file mode 100755 index 000000000..6aa8fce4f --- /dev/null +++ b/plugins/mysql/versions/8.2/install.sh @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.7.html +#https://dev.mysql.com/downloads/file/?id=489855 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mysqlDir=${serverPath}/source/mysql + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=8.2.0 +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -f ${mysqlDir}/mysql-boost-${VERSION}.tar.gz ];then + #wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-boost-${VERSION}.tar.gz + wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://cdn.mysql.com/archives/mysql-8.2/mysql-boost-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=704ad9fb4779e76ce5e327285813c97c + if [ -f ${mysqlDir}/mysql-boost-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-boost-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql8.2 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-8.2/mysql-boost-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-boost-${VERSION}.tar.gz + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl" + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ "$OSNAME" == "centos" ] && [ "$VERSION_ID" == "7" ];then + yum install -y libudev-devel + yum install -y centos-release-scl + yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils + + gcc --version + WHERE_DIR_GCC=/opt/rh/devtoolset-11/root/usr/bin/gcc + WHERE_DIR_GPP=/opt/rh/devtoolset-11/root/usr/bin/g++ + echo $WHERE_DIR_GCC + echo $WHERE_DIR_GPP + fi + + if [ "$OSNAME" == "ubuntu" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + LIBTIRPC_VER=`pkg-config libtirpc --modversion` + if [ ! -f ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 ];then + wget --no-check-certificate -O ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.3.tar.bz2 + cd ${mysqlDir} && tar -jxvf libtirpc_1.3.3.orig.tar.bz2 + cd libtirpc-1.3.3 && ./configure + fi + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + + if [ "$OSNAME" == "opensuse" ];then + zypper install -y gcc11 + zypper install -y gcc11-c++ + + + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + if [ ! -d $serverPath/mysql ];then + # -DCMAKE_CXX_STANDARD=17 \ + cd ${mysqlDir}/mysql-${VERSION} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DWITH_READLINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + -DFORCE_INSOURCE_BUILD=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DDOWNLOAD_BOOST=0 \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '8.2' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/8.3/install.sh b/plugins/mysql/versions/8.3/install.sh new file mode 100755 index 000000000..a80545d43 --- /dev/null +++ b/plugins/mysql/versions/8.3/install.sh @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.7.html +#https://dev.mysql.com/downloads/file/?id=489855 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mysqlDir=${serverPath}/source/mysql + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=8.3.0 +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -f ${mysqlDir}/mysql-boost-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://cdn.mysql.com//Downloads/MySQL-8.3/mysql-boost-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=8c2399782217f5391322751c66ea261b + if [ -f ${mysqlDir}/mysql-boost-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-boost-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql8.3 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-boost-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-8.3/mysql-boost-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-boost-${VERSION}.tar.gz + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl" + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ "$OSNAME" == "centos" ] && [ "$VERSION_ID" == "7" ];then + yum install -y libudev-devel + yum install -y centos-release-scl + yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils + + gcc --version + WHERE_DIR_GCC=/opt/rh/devtoolset-11/root/usr/bin/gcc + WHERE_DIR_GPP=/opt/rh/devtoolset-11/root/usr/bin/g++ + echo $WHERE_DIR_GCC + echo $WHERE_DIR_GPP + fi + + if [ "$OSNAME" == "ubuntu" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + LIBTIRPC_VER=`pkg-config libtirpc --modversion` + if [ ! -f ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 ];then + wget --no-check-certificate -O ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.3.tar.bz2 + cd ${mysqlDir} && tar -jxvf libtirpc_1.3.3.orig.tar.bz2 + cd libtirpc-1.3.3 && ./configure + fi + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + + if [ "$OSNAME" == "opensuse" ];then + zypper install -y gcc11 + zypper install -y gcc11-c++ + + + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + if [ ! -d $serverPath/mysql ];then + # -DCMAKE_CXX_STANDARD=17 \ + cd ${mysqlDir}/mysql-${VERSION} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DWITH_READLINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + -DFORCE_INSOURCE_BUILD=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DDOWNLOAD_BOOST=0 \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '8.3' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/8.4/install.sh b/plugins/mysql/versions/8.4/install.sh new file mode 100755 index 000000000..f5a51f5fe --- /dev/null +++ b/plugins/mysql/versions/8.4/install.sh @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.7.html +#https://dev.mysql.com/downloads/file/?id=489855 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +mysqlDir=${serverPath}/source/mysql + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=8.4.2 +# https://dev.mysql.com/get/Downloads/MySQL-8.4/mysql-${VERSION}.tar.gz +# https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-boost-${VERSION}.tar.gz +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-8.4/mysql-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=a632063fdb1c7de2c5db47e1f66191cd + if [ -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql8.4 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-8.4/mysql-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-${VERSION}.tar.gz + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl" + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ "$OSNAME" == "centos" ] && [ "$VERSION_ID" == "7" ];then + yum install -y libudev-devel + yum install -y centos-release-scl + yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils + + gcc --version + WHERE_DIR_GCC=/opt/rh/devtoolset-11/root/usr/bin/gcc + WHERE_DIR_GPP=/opt/rh/devtoolset-11/root/usr/bin/g++ + echo $WHERE_DIR_GCC + echo $WHERE_DIR_GPP + fi + + if [ "$OSNAME" == "ubuntu" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + LIBTIRPC_VER=`pkg-config libtirpc --modversion` + if [ ! -f ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 ];then + wget --no-check-certificate -O ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.3.tar.bz2 + cd ${mysqlDir} && tar -jxvf libtirpc_1.3.3.orig.tar.bz2 + cd libtirpc-1.3.3 && ./configure + fi + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + + if [ "$OSNAME" == "opensuse" ];then + zypper install -y gcc11 + zypper install -y gcc11-c++ + + + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + if [ ! -d $serverPath/mysql ];then + # -DCMAKE_CXX_STANDARD=17 \ + cd ${mysqlDir}/mysql-${VERSION} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DWITH_READLINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + -DFORCE_INSOURCE_BUILD=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DDOWNLOAD_BOOST=0 \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '8.4' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/9.0/install.sh b/plugins/mysql/versions/9.0/install.sh new file mode 100755 index 000000000..a23fbeca3 --- /dev/null +++ b/plugins/mysql/versions/9.0/install.sh @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#https://dev.mysql.com/downloads/mysql/5.7.html +#https://dev.mysql.com/downloads/file/?id=489855 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + + +mysqlDir=${serverPath}/source/mysql + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=9.0.1 +# https://dev.mysql.com/get/Downloads/MySQL-9.0/mysql-${VERSION}.tar.gz +# https://cdn.mysql.com//Downloads/MySQL-9.0/mysql-boost-${VERSION}.tar.gz +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-9.0/mysql-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=90dc27a8b64eee938a0bb045c580b80c + if [ -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql9.0 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-9.0/mysql-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-${VERSION}.tar.gz + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl" + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ "$OSNAME" == "centos" ] && [ "$VERSION_ID" == "7" ];then + yum install -y libudev-devel + yum install -y centos-release-scl + yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils + + gcc --version + WHERE_DIR_GCC=/opt/rh/devtoolset-11/root/usr/bin/gcc + WHERE_DIR_GPP=/opt/rh/devtoolset-11/root/usr/bin/g++ + echo $WHERE_DIR_GCC + echo $WHERE_DIR_GPP + fi + + if [ "$OSNAME" == "ubuntu" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + LIBTIRPC_VER=`pkg-config libtirpc --modversion` + if [ ! -f ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 ];then + wget --no-check-certificate -O ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.3.tar.bz2 + cd ${mysqlDir} && tar -jxvf libtirpc_1.3.3.orig.tar.bz2 + cd libtirpc-1.3.3 && ./configure + fi + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + + + OPTIONS="${OPTIONS} -DFORCE_INSOURCE_BUILD=1" + OPTIONS="${OPTIONS} -D_FORTIFY_SOURCE=2" + fi + + + if [ "$OSNAME" == "opensuse" ];then + zypper install -y gcc11 + zypper install -y gcc11-c++ + + + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + if [ ! -d $serverPath/mysql ];then + # -DCMAKE_CXX_STANDARD=17 \ + cd ${mysqlDir}/mysql-${VERSION} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DWITH_READLINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + -DFORCE_INSOURCE_BUILD=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DDOWNLOAD_BOOST=0 \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '9.0' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/9.1/install.sh b/plugins/mysql/versions/9.1/install.sh new file mode 100755 index 000000000..f1af11b05 --- /dev/null +++ b/plugins/mysql/versions/9.1/install.sh @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://dev.mysql.com/downloads/mysql/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + + +mysqlDir=${serverPath}/source/mysql + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=9.1.0 +# https://dev.mysql.com/get/Downloads/MySQL-9.1/mysql-${VERSION}.tar.gz +# https://cdn.mysql.com//Downloads/MySQL-9.1/mysql-boost-${VERSION}.tar.gz +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-9.1/mysql-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=eb2c6bbd20569d2690bc7e34312f5210 + if [ -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql9.1 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-9.1/mysql-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-${VERSION}.tar.gz + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl" + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ "$OSNAME" == "centos" ] && [ "$VERSION_ID" == "7" ];then + yum install -y libudev-devel + yum install -y centos-release-scl + yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils + + gcc --version + WHERE_DIR_GCC=/opt/rh/devtoolset-11/root/usr/bin/gcc + WHERE_DIR_GPP=/opt/rh/devtoolset-11/root/usr/bin/g++ + echo $WHERE_DIR_GCC + echo $WHERE_DIR_GPP + fi + + if [ "$OSNAME" == "ubuntu" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + LIBTIRPC_VER=`pkg-config libtirpc --modversion` + if [ ! -f ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 ];then + wget --no-check-certificate -O ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.3.tar.bz2 + cd ${mysqlDir} && tar -jxvf libtirpc_1.3.3.orig.tar.bz2 + cd libtirpc-1.3.3 && ./configure + fi + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + + + OPTIONS="${OPTIONS} -DFORCE_INSOURCE_BUILD=1" + OPTIONS="${OPTIONS} -D_FORTIFY_SOURCE=2" + fi + + + if [ "$OSNAME" == "opensuse" ];then + zypper install -y gcc11 + zypper install -y gcc11-c++ + + + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + if [ ! -d $serverPath/mysql ];then + # -DCMAKE_CXX_STANDARD=17 \ + cd ${mysqlDir}/mysql-${VERSION} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DWITH_READLINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + -DFORCE_INSOURCE_BUILD=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DDOWNLOAD_BOOST=0 \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '9.1' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/9.2/install.sh b/plugins/mysql/versions/9.2/install.sh new file mode 100755 index 000000000..2f9adbd3d --- /dev/null +++ b/plugins/mysql/versions/9.2/install.sh @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://dev.mysql.com/downloads/mysql/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + + +mysqlDir=${serverPath}/source/mysql + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=9.2.0 +# https://dev.mysql.com/get/Downloads/MySQL-9.2/mysql-${VERSION}.tar.gz +# https://cdn.mysql.com//Downloads/MySQL-9.2/mysql-boost-${VERSION}.tar.gz +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-9.2/mysql-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=acabc8aa764a94a8b10f90284c6e60c5 + if [ -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql9.2 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-9.2/mysql-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-${VERSION}.tar.gz + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl" + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ "$OSNAME" == "centos" ] && [ "$VERSION_ID" == "7" ];then + yum install -y libudev-devel + yum install -y centos-release-scl + yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils + + gcc --version + WHERE_DIR_GCC=/opt/rh/devtoolset-11/root/usr/bin/gcc + WHERE_DIR_GPP=/opt/rh/devtoolset-11/root/usr/bin/g++ + echo $WHERE_DIR_GCC + echo $WHERE_DIR_GPP + fi + + if [ "$OSNAME" == "ubuntu" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + LIBTIRPC_VER=`pkg-config libtirpc --modversion` + if [ ! -f ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 ];then + wget --no-check-certificate -O ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.3.tar.bz2 + cd ${mysqlDir} && tar -jxvf libtirpc_1.3.3.orig.tar.bz2 + cd libtirpc-1.3.3 && ./configure + fi + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + + + OPTIONS="${OPTIONS} -DFORCE_INSOURCE_BUILD=1" + OPTIONS="${OPTIONS} -D_FORTIFY_SOURCE=2" + fi + + + if [ "$OSNAME" == "opensuse" ];then + zypper install -y gcc11 + zypper install -y gcc11-c++ + + + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + if [ ! -d $serverPath/mysql ];then + # -DCMAKE_CXX_STANDARD=17 \ + cd ${mysqlDir}/mysql-${VERSION} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DWITH_READLINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + -DFORCE_INSOURCE_BUILD=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DDOWNLOAD_BOOST=0 \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '9.2' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/9.3/install.sh b/plugins/mysql/versions/9.3/install.sh new file mode 100755 index 000000000..cbd100f1e --- /dev/null +++ b/plugins/mysql/versions/9.3/install.sh @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://dev.mysql.com/downloads/mysql/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + + +mysqlDir=${serverPath}/source/mysql + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=9.3.0 +# https://dev.mysql.com/get/Downloads/MySQL-9.2/mysql-${VERSION}.tar.gz +# https://cdn.mysql.com//Downloads/MySQL-9.2/mysql-boost-${VERSION}.tar.gz +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-9.3/mysql-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=acabc8aa764a94a8b10f90284c6e60c5 + if [ -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql9.3 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-9.3/mysql-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-${VERSION}.tar.gz + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl" + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ "$OSNAME" == "centos" ] && [ "$VERSION_ID" == "7" ];then + yum install -y libudev-devel + yum install -y centos-release-scl + yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils + + gcc --version + WHERE_DIR_GCC=/opt/rh/devtoolset-11/root/usr/bin/gcc + WHERE_DIR_GPP=/opt/rh/devtoolset-11/root/usr/bin/g++ + echo $WHERE_DIR_GCC + echo $WHERE_DIR_GPP + fi + + if [ "$OSNAME" == "ubuntu" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + LIBTIRPC_VER=`pkg-config libtirpc --modversion` + if [ ! -f ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 ];then + wget --no-check-certificate -O ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.3.tar.bz2 + cd ${mysqlDir} && tar -jxvf libtirpc_1.3.3.orig.tar.bz2 + cd libtirpc-1.3.3 && ./configure + fi + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + + + OPTIONS="${OPTIONS} -DFORCE_INSOURCE_BUILD=1" + OPTIONS="${OPTIONS} -D_FORTIFY_SOURCE=2" + fi + + + if [ "$OSNAME" == "opensuse" ];then + zypper install -y gcc11 + zypper install -y gcc11-c++ + + + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + if [ ! -d $serverPath/mysql ];then + # -DCMAKE_CXX_STANDARD=17 \ + cd ${mysqlDir}/mysql-${VERSION} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DWITH_READLINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + -DFORCE_INSOURCE_BUILD=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DDOWNLOAD_BOOST=0 \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '9.3' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/mysql/versions/9.4/install.sh b/plugins/mysql/versions/9.4/install.sh new file mode 100755 index 000000000..d3615db3a --- /dev/null +++ b/plugins/mysql/versions/9.4/install.sh @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://dev.mysql.com/downloads/mysql/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + + +mysqlDir=${serverPath}/source/mysql + +_os=`uname` +echo "use system: ${_os}" +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +VERSION=9.4.0 +# https://dev.mysql.com/get/Downloads/MySQL-9.4/mysql-${VERSION}.tar.gz +# https://cdn.mysql.com/archives/mysql-9.4/mysql-9.4.0.tar.gz +Install_mysql() +{ + mkdir -p ${mysqlDir} + echo '正在安装脚本文件...' + + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + cd ${rootPath}/plugins/mysql/lib && /bin/bash rpcgen.sh + + INSTALL_CMD=cmake + # check cmake version + CMAKE_VERSION=`cmake -version | grep version | awk '{print $3}' | awk -F '.' '{print $1}'` + if [ "$CMAKE_VERSION" -eq "2" ];then + mkdir -p /var/log/mariadb + touch /var/log/mariadb/mariadb.log + INSTALL_CMD=cmake3 + fi + + if [ ! -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://dev.mysql.com/get/Downloads/MySQL-9.4/mysql-${VERSION}.tar.gz + fi + + #检测文件是否损坏. + md5_mysql_ok=c770f276fb84019be6fbe0a57b32efbc + if [ -f ${mysqlDir}/mysql-${VERSION}.tar.gz ];then + md5_mysql=`md5sum ${mysqlDir}/mysql-${VERSION}.tar.gz | awk '{print $1}'` + if [ "${md5_mysql_ok}" == "${md5_mysql}" ]; then + echo "mysql9.4 file check ok" + else + # 重新下载 + rm -rf ${mysqlDir}/mysql-${VERSION} + wget --no-check-certificate -O ${mysqlDir}/mysql-${VERSION}.tar.gz --tries=3 https://cdn.mysql.com/archives/mysql-9.4/mysql-${VERSION}.tar.gz + fi + fi + + if [ ! -d ${mysqlDir}/mysql-${VERSION} ];then + cd ${mysqlDir} && tar -zxvf ${mysqlDir}/mysql-${VERSION}.tar.gz + fi + + OPTIONS='' + ##check openssl version + OPENSSL_VERSION=`openssl version|awk '{print $2}'|awk -F '.' '{print $1}'` + if [ "${OPENSSL_VERSION}" -ge "3" ];then + #openssl version to high + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + OPTIONS="-DWITH_SSL=${serverPath}/lib/openssl" + fi + + WHERE_DIR_GCC=/usr/bin/gcc + WHERE_DIR_GPP=/usr/bin/g++ + if [ "$OSNAME" == "centos" ] && [ "$VERSION_ID" == "7" ];then + yum install -y libudev-devel + yum install -y centos-release-scl + yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++ devtoolset-11-binutils + + gcc --version + WHERE_DIR_GCC=/opt/rh/devtoolset-11/root/usr/bin/gcc + WHERE_DIR_GPP=/opt/rh/devtoolset-11/root/usr/bin/g++ + echo $WHERE_DIR_GCC + echo $WHERE_DIR_GPP + fi + + if [ "$OSNAME" == "ubuntu" ];then + apt install -y libudev-dev + apt install -y libtirpc-dev + apt install -y libssl-dev + apt install -y libgssglue-dev + apt install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + + LIBTIRPC_VER=`pkg-config libtirpc --modversion` + if [ ! -f ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 ];then + wget --no-check-certificate -O ${mysqlDir}/libtirpc_1.3.3.orig.tar.bz2 https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.3.tar.bz2 + cd ${mysqlDir} && tar -jxvf libtirpc_1.3.3.orig.tar.bz2 + cd libtirpc-1.3.3 && ./configure + fi + + export PKG_CONFIG_PATH=/usr/lib/pkgconfig + apt install -y gcc-11 g++-11 + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + + + OPTIONS="${OPTIONS} -DFORCE_INSOURCE_BUILD=1" + OPTIONS="${OPTIONS} -D_FORTIFY_SOURCE=2" + fi + + + if [ "$OSNAME" == "opensuse" ];then + zypper install -y gcc11 + zypper install -y gcc11-c++ + + + WHERE_DIR_GCC=/usr/bin/gcc-11 + WHERE_DIR_GPP=/usr/bin/g++-11 + fi + + if [ ! -d $serverPath/mysql ];then + # -DCMAKE_CXX_STANDARD=17 \ + cd ${mysqlDir}/mysql-${VERSION} && ${INSTALL_CMD} \ + -DCMAKE_INSTALL_PREFIX=$serverPath/mysql \ + -DMYSQL_USER=mysql \ + -DMYSQL_TCP_PORT=3306 \ + -DMYSQL_UNIX_ADDR=/var/tmp/mysql.sock \ + -DWITH_MYISAM_STORAGE_ENGINE=1 \ + -DWITH_INNOBASE_STORAGE_ENGINE=1 \ + -DWITH_MEMORY_STORAGE_ENGINE=1 \ + -DENABLED_LOCAL_INFILE=1 \ + -DWITH_PARTITION_STORAGE_ENGINE=1 \ + -DWITH_READLINE=1 \ + -DEXTRA_CHARSETS=all \ + -DDEFAULT_CHARSET=utf8mb4 \ + -DDEFAULT_COLLATION=utf8mb4_general_ci \ + -DDOWNLOAD_BOOST=1 \ + -DFORCE_INSOURCE_BUILD=1 \ + $OPTIONS \ + -DCMAKE_C_COMPILER=$WHERE_DIR_GCC \ + -DCMAKE_CXX_COMPILER=$WHERE_DIR_GPP \ + -DDOWNLOAD_BOOST=0 \ + -DWITH_BOOST=${mysqlDir}/mysql-${VERSION}/boost/ + make -j${cpuCore} && make install && make clean + + if [ -d $serverPath/mysql ];then + rm -rf ${mysqlDir}/mysql-${VERSION} + echo '9.4' > $serverPath/mysql/version.pl + echo "${VERSION}安装完成" + else + # rm -rf ${mysqlDir}/mysql-${VERSION} + echo "${VERSION}安装失败" + exit 1 + fi + fi +} + +Uninstall_mysql() +{ + rm -rf $serverPath/mysql + echo '卸载完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_mysql +else + Uninstall_mysql +fi diff --git a/plugins/nezha/ico.png b/plugins/nezha/ico.png new file mode 100644 index 000000000..6b5f8a3f2 Binary files /dev/null and b/plugins/nezha/ico.png differ diff --git a/plugins/nezha/index.html b/plugins/nezha/index.html new file mode 100755 index 000000000..f2eb9c9ea --- /dev/null +++ b/plugins/nezha/index.html @@ -0,0 +1,26 @@ +
                                +
                                +
                                +
                                +

                                面板服务

                                +

                                面板自启动

                                +

                                面板配置

                                +

                                面板配置文件

                                +

                                AGENT服务

                                +

                                AGENT自启动

                                +

                                AGENT配置

                                +

                                说明

                                +
                                +
                                +
                                +
                                +
                                +
                                + + \ No newline at end of file diff --git a/plugins/nezha/index.py b/plugins/nezha/index.py new file mode 100755 index 000000000..d79ad4bc2 --- /dev/null +++ b/plugins/nezha/index.py @@ -0,0 +1,368 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import socket +import json + +from datetime import datetime + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +class App: + __setupPath = '/www/server/nezha' + __cfg = '' + __agent_cfg = '' + + def __init__(self): + self.__setupPath = self.getServerDir() + self.__cfg = self.__setupPath + '/nezha.cfg' + self.__agent_cfg = self.__setupPath + '/agent.cfg' + + def getArgs(self): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':', 1) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + + return tmp + + def checkArgs(self, data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + def __release_port(self, port): + from collections import namedtuple + try: + import firewall_api + firewall_api.firewall_api().addAcceptPortArgs(port, 'nezha', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + + def openPort(self): + for i in ["9527", "5555"]: + self.__release_port(i) + return True + + def getPluginName(self): + return 'nezha' + + def getPluginDir(self): + return mw.getPluginDir() + '/' + self.getPluginName() + + def getServerDir(self): + return mw.getServerDir() + '/' + self.getPluginName() + + def getInitdConfTpl(self): + path = self.getPluginDir() + "/init.d/nezha.tpl" + return path + + def getInitdAgentConfTpl(self): + path = self.getPluginDir() + "/init.d/nezha-agent.tpl" + return path + + def getHomeDir(self): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + else: + return '/root' + + def getRunUser(self): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + else: + return 'root' + + def status(self): + cmd = "ps -ef|grep " + self.getPluginName() + \ + " |grep -v grep | grep -v nezha-agent | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return "stop" + return 'start' + + def status_agent(self): + cmd = "ps -ef | grep nezha-agent | grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + def contentReplace(self, content): + + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$RUN_USER}', self.getRunUser()) + content = content.replace('{$HOME_DIR}', self.getHomeDir()) + + return content + + def initDreplace(self): + + file_tpl = self.getInitdConfTpl() + service_path = mw.getServerDir() + + initD_path = self.getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + self.openPort() + + file_bin = initD_path + '/' + self.getPluginName() + + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = self.contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/nezha.service' + systemServiceTpl = self.getPluginDir() + '/init.d/nezha.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = self.contentReplace(se_content) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + def contentAgentReplace(self, content): + path = self.__agent_cfg + if os.path.exists(path): + data = self.get_agent_cfg() + content = content.replace('{$APP_HOST}', data['host']) + content = content.replace('{$APP_SECRET}', data['secret']) + + return content + + def initDAgent(self): + file_tpl = self.getInitdAgentConfTpl() + + initD_path = self.getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + + file_agent_bin = initD_path + '/nezha-agent' + + content = mw.readFile(file_tpl) + content = self.contentReplace(content) + content = self.contentAgentReplace(content) + mw.writeFile(file_agent_bin, content) + mw.execShell('chmod +x ' + file_agent_bin) + + # systemd + sysDir = mw.systemdCfgDir() + sysService = sysDir + '/nezha-agent.service' + sysServiceTpl = self.getPluginDir() + '/init.d/nezha-agent.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(sysServiceTpl) + content = self.contentReplace(content) + content = self.contentAgentReplace(content) + mw.writeFile(sysService, content) + mw.execShell('systemctl daemon-reload') + + return file_agent_bin + + def init_cfg(self): + self.initDreplace() + self.initDAgent() + + def imOp(self, method): + + file = self.initDreplace() + + if not mw.isAppleSystem(): + cmd = 'systemctl {} {}'.format(method, self.getPluginName()) + data = mw.execShell(cmd) + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[0] + + def start(self): + return self.imOp('start') + + def stop(self): + return self.imOp('stop') + + def restart(self): + return self.imOp('restart') + + def reload(self): + return self.imOp('reload') + + def agOp(self, method): + file = self.initDAgent() + + path = self.__agent_cfg + if not os.path.exists(path): + return '请先设置Agent配置!' + + if not mw.isAppleSystem(): + cmd = 'systemctl {} {}'.format(method, 'nezha-agent') + data = mw.execShell(cmd) + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[0] + + def start_agent(self): + return self.agOp('start') + + def stop_agent(self): + return self.agOp('stop') + + def restart_agent(self): + return self.agOp('restart') + + def reload_agent(self): + return self.agentOp('reload') + + def initd_status(self): + cmd = 'systemctl status nezha | grep loaded | grep "enabled;"' + data = mw.execShell(cmd) + if data[0] == '': + return 'fail' + return 'ok' + + def initd_install(self): + mw.execShell('systemctl enable nezha') + return 'ok' + + def initd_uninstall(self): + mw.execShell('systemctl disable nezha') + return 'ok' + + def initd_status_agent(self): + cmd = 'systemctl status nezha-agent | grep loaded | grep "enabled;"' + data = mw.execShell(cmd) + if data[0] == '': + return 'fail' + return 'ok' + + def initd_install_agent(self): + mw.execShell('systemctl enable nezha-agent') + return 'ok' + + def initd_uninstall_agent(self): + mw.execShell('systemctl disable nezha-agent') + return 'ok' + + def conf(self): + return self.getServerDir() + '/dashboard/data/config.yaml' + + def nezha_cfg(self): + path = self.__cfg + if not os.path.exists(path): + d = {} + cmd_un = 'cd ' + self.getServerDir() + '/dashboard && ./nezha conf -u ""' + mw.execShell(cmd_un) + td = mw.execShell(cmd_un) + d['username'] = td[0].strip() + + pwd = mw.getRandomString(16) + cmd_pwd = 'cd ' + self.getServerDir() + '/dashboard && ./nezha conf -u "" -p ' + pwd + td = mw.execShell(cmd_pwd) + d['password'] = pwd + + mw.writeFile(path, mw.enDoubleCrypt('nezha', mw.getJson(d))) + + info = mw.readFile(path) + info = mw.deDoubleCrypt('nezha', info) + + info = json.loads(info) + return mw.returnJson(True, 'ok', info) + + def nezha_save_cfg(self): + args = self.getArgs() + data = self.checkArgs(args, ['username', 'password']) + if not data[0]: + return data[1] + path = self.__cfg + + cmd = 'cd ' + self.getServerDir() + '/dashboard && ./nezha conf -su "' + \ + args['username'] + '" -p ' + args['password'] + # print(cmd) + t = mw.execShell(cmd) + # print(t) + mw.writeFile(path, mw.enDoubleCrypt('nezha', mw.getJson(args))) + return mw.returnJson(True, '修改成功!') + + def get_agent_cfg(self): + path = self.__agent_cfg + info = mw.readFile(path) + info = mw.deDoubleCrypt('agent', info) + info = json.loads(info) + return info + + def agent_cfg(self): + path = self.__agent_cfg + if not os.path.exists(path): + d = {} + d['host'] = '127.0.0.1:5555' + d['secret'] = 'secret' + mw.writeFile(path, mw.enDoubleCrypt('agent', mw.getJson(d))) + + info = self.get_agent_cfg() + return mw.returnJson(True, 'ok', info) + + def agent_save_cfg(self): + args = self.getArgs() + data = self.checkArgs(args, ['host', 'secret']) + if not data[0]: + return data[1] + path = self.__agent_cfg + mw.writeFile(path, mw.enDoubleCrypt('agent', mw.getJson(args))) + return mw.returnJson(True, '修改成功!') + + def run_log(self): + return self.getServerDir() + '/logs/nezha.log' + + +if __name__ == "__main__": + func = sys.argv[1] + classApp = App() + try: + data = eval("classApp." + func + "()") + print(data) + except Exception as e: + print('error:' + str(e)) diff --git a/plugins/nezha/info.json b/plugins/nezha/info.json new file mode 100755 index 000000000..afb4e6990 --- /dev/null +++ b/plugins/nezha/info.json @@ -0,0 +1,18 @@ +{ + "sort": 7, + "ps": "哪吒监控面板/改造版", + "name": "nezha", + "title": "哪吒监控", + "shell": "install.sh", + "versions":["0.15.2"], + "tip": "soft", + "checks": "server/nezha", + "path": "server/nezha", + "display": 1, + "author": "midoks", + "date": "2023-06-26", + "home": "https://github.com/midoks/nezha", + "doc1": "https://nezha.wiki/", + "type": 0, + "pid": "5" +} \ No newline at end of file diff --git a/plugins/nezha/init.d/nezha-agent.service.tpl b/plugins/nezha/init.d/nezha-agent.service.tpl new file mode 100644 index 000000000..4d36207c8 --- /dev/null +++ b/plugins/nezha/init.d/nezha-agent.service.tpl @@ -0,0 +1,30 @@ +[Unit] +Description=Nezha Agent +After=syslog.target +#After=network.target +#After=nezha-dashboard.service + +[Service] +# Modify these two values and uncomment them if you have +# repos with lots of files and get an HTTP error 500 because +# of that +### +#LimitMEMLOCK=infinity +#LimitNOFILE=65535 +Type=simple +User=root +Group=root +WorkingDirectory={$SERVER_PATH}/nezha/agent/ +ExecStart={$SERVER_PATH}/nezha/agent/nezha-agent -s {$APP_HOST} -p {$APP_SECRET} +Restart=always +#Environment=DEBUG=true + +# Some distributions may not support these hardening directives. If you cannot start the service due +# to an unknown option, comment out the ones not supported by your version of systemd. +#ProtectSystem=full +#PrivateDevices=yes +#PrivateTmp=yes +#NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/nezha/init.d/nezha-agent.tpl b/plugins/nezha/init.d/nezha-agent.tpl new file mode 100644 index 000000000..d6dd23fd9 --- /dev/null +++ b/plugins/nezha/init.d/nezha-agent.tpl @@ -0,0 +1,90 @@ +#!/bin/bash +# chkconfig: 2345 55 25 +# description: Nezha Agent Service + +### BEGIN INIT INFO +# Provides: Nezha Agent +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts Nezha Agent +# Description: starts the Nezha Agent +### END INIT INFO + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + +if [ -f /etc/init.d/functions ];then + . /etc/init.d/functions +fi + +if [ -f /etc/rc.d/init.d/functions ];then + . /etc/rc.d/init.d/functions +fi + +SERVICENAME=nezha-agent +LOG_PATH={$SERVER_PATH}/nezha/logs +APP_PATH={$SERVER_PATH}/nezha/agent +APP_HOST={$APP_HOST} +APP_SECRET={$APP_SECRET} + + +app_start(){ + isStart=`ps -ef|grep "${SERVICENAME}" | grep -v grep | grep -v python | grep -v "/bin/bash" | awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "Starting ${SERVICENAME}... \c" + cd $APP_PATH + exec nohup ${APP_PATH}/${SERVICENAME} -d -s ${APP_HOST} -p ${APP_SECRET} >> ${LOG_PATH}/${SERVICENAME}.log 2>&1 & + isStart="" + while [[ "$isStart" == "" ]]; + do + echo -e ".\c" + sleep 0.5 + isStart=$(ps -ef|grep "${SERVICENAME}" |grep -v grep | grep -v python | grep -v "/bin/bash" | awk '{print $2}') + let n+=1 + if [ $n -gt 15 ];then + break; + fi + done + if [ "$isStart" == '' ];then + echo -e "\033[31mError: ${SERVICENAME} service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "Starting ${SERVICENAME}(pid $(echo $isStart)) already running" + fi +} + +app_stop(){ + pids=`ps -ef | grep "${SERVICENAME}" | grep -v grep | grep -v python | grep -v "/bin/bash" |awk '{print $2}'` + arr=($pids) + echo -e "Stopping ${SERVICENAME}... \c" + for p in ${arr[@]} + do + # echo "$p" + kill -9 $p + done + echo -e "\033[32mdone\033[0m" +} + +app_status(){ + isStart=`ps -ef | grep "${SERVICENAME}" | grep -v grep | grep -v python | grep -v "/bin/bash" | awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "${SERVICENAME} not running" + else + echo -e "${SERVICENAME}(pid $(echo $isStart)) already running" + fi +} + +case "$1" in + 'start') app_start;; + 'stop') app_stop;; + 'status') app_status;; + 'reload') + app_stop + app_start;; + 'restart') + app_stop + app_start;; +esac \ No newline at end of file diff --git a/plugins/nezha/init.d/nezha.service.tpl b/plugins/nezha/init.d/nezha.service.tpl new file mode 100644 index 000000000..0752b3eb5 --- /dev/null +++ b/plugins/nezha/init.d/nezha.service.tpl @@ -0,0 +1,30 @@ +[Unit] +Description=Nezha Dashborad +After=syslog.target +After=network.target +After=mariadb.service mysqld.service postgresql.service memcached.service redis.service + +[Service] +# Modify these two values and uncomment them if you have +# repos with lots of files and get an HTTP error 500 because +# of that +### +#LimitMEMLOCK=infinity +#LimitNOFILE=65535 +Type=simple +#User=root +#Group=root +WorkingDirectory={$SERVER_PATH}/nezha/dashboard/ +ExecStart={$SERVER_PATH}/nezha/dashboard/nezha web +Restart=always +#Environment=DEBUG=true + +# Some distributions may not support these hardening directives. If you cannot start the service due +# to an unknown option, comment out the ones not supported by your version of systemd. +ProtectSystem=full +PrivateDevices=yes +PrivateTmp=yes +NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/nezha/init.d/nezha.tpl b/plugins/nezha/init.d/nezha.tpl new file mode 100644 index 000000000..4f1e20404 --- /dev/null +++ b/plugins/nezha/init.d/nezha.tpl @@ -0,0 +1,87 @@ +#!/bin/bash +# chkconfig: 2345 55 25 +# description: Nezha Service + +### BEGIN INIT INFO +# Provides: Nezha +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts Nezha +# Description: starts the Nezha +### END INIT INFO + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + +if [ -f /etc/init.d/functions ];then + . /etc/init.d/functions +fi + +if [ -f /etc/rc.d/init.d/functions ];then + . /etc/rc.d/init.d/functions +fi + +SERVICENAME=nezha +LOG_PATH={$SERVER_PATH}/${SERVICENAME}/logs +APP_PATH={$SERVER_PATH}/${SERVICENAME}/dashboard + + +app_start(){ + isStart=`ps -ef|grep "${SERVICENAME} web" | grep -v grep | grep -v python | grep -v "/bin/bash" | awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "Starting ${SERVICENAME}... \c" + cd $APP_PATH && exec nohup ${APP_PATH}/${SERVICENAME} web >> ${LOG_PATH}/${SERVICENAME}.log 2>&1 & + isStart="" + while [[ "$isStart" == "" ]]; + do + echo -e ".\c" + sleep 0.5 + isStart=$(ps -ef|grep "${SERVICENAME} web" |grep -v grep | grep -v python | grep -v "/bin/bash" | awk '{print $2}') + let n+=1 + if [ $n -gt 15 ];then + break; + fi + done + if [ "$isStart" == '' ];then + echo -e "\033[31mError: ${SERVICENAME} service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "Starting ${SERVICENAME}(pid $(echo $isStart)) already running" + fi +} + +app_stop(){ + pids=`ps -ef | grep "${SERVICENAME} web" | grep -v grep | grep -v python | grep -v "/bin/bash" |awk '{print $2}'` + arr=($pids) + echo -e "Stopping ${SERVICENAME}... \c" + for p in ${arr[@]} + do + # echo "$p" + kill -9 $p + done + echo -e "\033[32mdone\033[0m" +} + +app_status(){ + isStart=`ps -ef | grep "${SERVICENAME} web" | grep -v grep | grep -v python | grep -v "/bin/bash" | awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "${SERVICENAME} not running" + else + echo -e "${SERVICENAME}(pid $(echo $isStart)) already running" + fi +} + +case "$1" in + 'start') app_start;; + 'stop') app_stop;; + 'status') app_status;; + 'reload') + app_stop + app_start;; + 'restart') + app_stop + app_start;; +esac \ No newline at end of file diff --git a/plugins/nezha/install.sh b/plugins/nezha/install.sh new file mode 100755 index 000000000..0a007b9a4 --- /dev/null +++ b/plugins/nezha/install.sh @@ -0,0 +1,41 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +action=$1 +type=$2 + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + +sh -x $curPath/versions/$2/install.sh $1 + +if [ "${action}" == "uninstall" ];then + if [ -f /usr/lib/systemd/system/nezha.service ] || [ -f /lib/systemd/system/nezha.service ] ;then + systemctl stop nezha + systemctl disable nezha + rm -rf /usr/lib/systemd/system/nezha.service + rm -rf /lib/systemd/system/nezha.service + systemctl daemon-reload + fi + + if [ -f /usr/lib/systemd/system/nezha-agent.service ] || [ -f /lib/systemd/system/nezha-agent.service ] ;then + systemctl stop nezha-agent + systemctl disable nezha-agent + rm -rf /usr/lib/systemd/system/nezha-agent.service + rm -rf /lib/systemd/system/nezha-agent.service + systemctl daemon-reload + fi +fi diff --git a/plugins/nezha/js/nezha.js b/plugins/nezha/js/nezha.js new file mode 100755 index 000000000..b6add248d --- /dev/null +++ b/plugins/nezha/js/nezha.js @@ -0,0 +1,184 @@ +var nezha = { + plugin_name: 'nezha', + init: function () { + var _this = this; + }, + + send:function(info){ + var tips = info['tips']; + var method = info['method']; + var args = info['data']; + var callback = info['success']; + + var loadT = layer.msg(tips, { icon: 16, time: 0, shade: 0.3 }); + + var data = {}; + data['name'] = 'nezha'; + data['func'] = method; + data['version'] = $('.plugin_version').attr('version'); + + if (typeof(args) == 'string'){ + data['args'] = JSON.stringify(toArrayObject(args)); + } else { + data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', data, function(res) { + layer.close(loadT); + if (!res.status){ + layer.msg(res.msg,{icon:2,time:10000}); + return; + } + + var ret_data = $.parseJSON(res.data); + console.log("send:",ret_data); + // if (!ret_data.status){ + // layer.msg(ret_data.msg,{icon:2,time:2000}); + // return; + // } + + // console.log("send2:",ret_data); + + if(typeof(callback) == 'function'){ + callback(ret_data); + } + },'json'); + }, + + postCallback:function(info){ + var tips = info['tips']; + var method = info['method']; + var args = info['data']; + var callback = info['success']; + + var loadT = layer.msg(tips, { icon: 16, time: 0, shade: 0.3 }); + + var data = {}; + data['name'] = 'nezha'; + data['func'] = method; + data['version'] = $('.plugin_version').attr('version'); + + if (typeof(args) == 'string'){ + data['args'] = JSON.stringify(toArrayObject(args)); + } else { + data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', data, function(res) { + layer.close(loadT); + if (!res.status){ + layer.msg(res.msg,{icon:2,time:10000}); + return; + } + + var ret_data = $.parseJSON(res.data); + if (!ret_data.status){ + layer.msg(ret_data.msg,{icon:2,time:2000}); + return; + } + + if(typeof(callback) == 'function'){ + callback(res); + } + },'json'); + }, + + repeatPwd:function (a, id) { + $("#"+id).val(randomStrPwd(a)) + }, + + save_cfg:function(version){ + var username = $("input[name='username']").val(); + var password = $("input[name='password']").val(); + + this.send({ + tips:'正在设置中...', + data:{'username':username,'password':password}, + method:'nezha_save_cfg', + success:function(rdata){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + } + }); + }, + + cfg:function(version){ + this.send({ + tips:'正在获取中...', + method:'nezha_cfg', + success:function(data){ + var d = data.data; + var value = '

                                \ + 用户名\ + \ + ,\ +

                                '; + + value += '

                                \ + 密码\ + \ + ,\ +

                                '; + var conf = '
                                \ + ' + value + '\ +
                                \ + \ + \ +
                                \ +
                                ' + $(".soft-man-con").html(conf); + } + }); + }, + + agent_save_cfg:function(version){ + var host = $("input[name='host']").val(); + var secret = $("input[name='secret']").val(); + + this.send({ + tips:'正在设置中...', + data:{'host':host,'secret':secret}, + method:'agent_save_cfg', + success:function(rdata){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + } + }); + }, + + agent_cfg:function(version){ + this.send({ + tips:'正在获取中...', + method:'agent_cfg', + success:function(data){ + var d = data.data; + var value = '

                                \ + 地址\ + \ + ,如1.1.1.1:5444\ +

                                '; + + value += '

                                \ + 密钥\ + \ + ,密钥\ +

                                '; + var conf = '
                                \ + ' + value + '\ +
                                \ + \ + \ +
                                \ +
                                ' + $(".soft-man-con").html(conf); + } + }); + }, + + readme:function (){ + var readme = '
                                  '; + readme += '
                                • 安装时不会自动启动。
                                • '; + readme += '
                                • 哪吒面板是改造版,用户名和密码登录【面板配置】,不依赖github/gitee/gitlab。
                                • '; + readme += '
                                • Agent需要先手动填写正确信息。
                                • '; + readme += '
                                '; + $('.soft-man-con').html(readme); + } +} diff --git a/plugins/nezha/versions/0.15.2/install.sh b/plugins/nezha/versions/0.15.2/install.sh new file mode 100755 index 000000000..4ed401a67 --- /dev/null +++ b/plugins/nezha/versions/0.15.2/install.sh @@ -0,0 +1,157 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=0.15.2 + +# bash install.sh install 0.15.2 +# cd /www/server/mdserver-web/plugins/nezha && bash install.sh install 0.15.2 +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/nezha && bash install.sh install 0.15.2 + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +OSNAME_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +ARCH="amd64" + +get_arch() { + TMP_ARCH=`arch` + if [ "$TMP_ARCH" == "x86_64" ];then + ARCH="amd64" + elif [ "$TMP_ARCH" == "aarch64" ];then + ARCH="arm64" + else + echo $ARCH + fi +} + +load_vars() { + OS=$(uname | tr '[:upper:]' '[:lower:]') + TARGET_DIR="$serverPath/nezha/dashboard" + + + ## China_IP + if [[ -z "${CN}" ]]; then + if [[ $(curl -m 10 -s https://ipapi.co/json | grep 'China') != "" ]]; then + CN=true + fi + fi + + if [[ -z "${CN}" ]]; then + GITHUB_RAW_URL="raw.githubusercontent.com/midoks/nezha/main" + GITHUB_URL="github.com" + else + GITHUB_RAW_URL="cdn.jsdelivr.net/gh/midoks/nezha@main" + GITHUB_URL="dn-dao-github-mirror.daocloud.io" + fi + + echo $GITHUB_RAW_URL + echo $GITHUB_URL +} + +# download file +download_file() { + url="${1}" + destination="${2}" + + printf "Fetching ${url} \n\n" + + if test -x "$(command -v curl)"; then + code=$(curl --connect-timeout 15 -w '%{http_code}' -L "${url}" -o "${destination}") + elif test -x "$(command -v wget)"; then + code=$(wget -t2 -T15 -O "${destination}" --server-response "${url}" 2>&1 | awk '/^ HTTP/{print $2}' | tail -1) + else + printf "\e[1;31mNeither curl nor wget was available to perform http requests.\e[0m\n" + exit 1 + fi + + if [ "${code}" != 200 ]; then + printf "\e[1;31mRequest failed with code %s\e[0m\n" $code + exit 1 + else + printf "\n\e[1;33mDownload succeeded\e[0m\n" + fi +} + + +Install_dashborad(){ + echo '正在安装哪吒监控...' + mkdir -p $serverPath/source + + if [ ! -f $TARGET_DIR/nezha ];then + + DOWNLOAD_URL="https://${GITHUB_URL}/midoks/nezha/releases/download/v${VERSION}/nezha-${OS}-${ARCH}.zip" + + DOWNLOAD_FILE="$(mktemp).zip" + download_file $DOWNLOAD_URL $DOWNLOAD_FILE + + if [ ! -d $TARGET_DIR ]; then + mkdir -p $TARGET_DIR + fi + + unzip $DOWNLOAD_FILE -d $TARGET_DIR + rm -rf $DOWNLOAD_FILE + fi + +} + +Install_agent(){ + echo -e "正在下载监控端" + mkdir -p $serverPath/source + + version=v0.15.1 + + AGENT_TARGET_DIR="$serverPath/nezha/agent" + + DOWNLOAD_URL="https://${GITHUB_URL}/nezhahq/agent/releases/download/${version}/nezha-agent_${OS}_${ARCH}.zip" + DOWNLOAD_FILE="$(mktemp).zip" + + if [ ! -f $AGENT_TARGET_DIR/nezha-agent ];then + download_file $DOWNLOAD_URL $DOWNLOAD_FILE + + if [ ! -d $AGENT_TARGET_DIR ]; then + mkdir -p $AGENT_TARGET_DIR + fi + + unzip $DOWNLOAD_FILE -d $AGENT_TARGET_DIR + rm -rf $DOWNLOAD_FILE + fi +} + +Install_App() +{ + load_vars + get_arch + + Install_dashborad + Install_agent + + if [ -d $serverPath/nezha ];then + echo "$VERSION" > $serverPath/nezha/version.pl + cd ${rootPath} && python3 ${rootPath}/plugins/nezha/index.py init_cfg + fi + echo 'install successful' +} + +Uninstall_App() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/nezha/index.py initd_uninstall + cd ${rootPath} && python3 ${rootPath}/plugins/nezha/index.py initd_uninstall_agent + cd ${rootPath} && python3 ${rootPath}/plugins/nezha/index.py stop + cd ${rootPath} && python3 ${rootPath}/plugins/nezha/index.py stop_agent + rm -rf $serverPath/nezha + echo "install fail" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/ollama/ico.png b/plugins/ollama/ico.png new file mode 100644 index 000000000..43a486ed5 Binary files /dev/null and b/plugins/ollama/ico.png differ diff --git a/plugins/ollama/index.html b/plugins/ollama/index.html new file mode 100755 index 000000000..acf153d78 --- /dev/null +++ b/plugins/ollama/index.html @@ -0,0 +1,21 @@ +
                                +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                说明

                                +
                                +
                                +
                                +
                                +
                                +
                                + + \ No newline at end of file diff --git a/plugins/ollama/index.py b/plugins/ollama/index.py new file mode 100755 index 000000000..306e8c418 --- /dev/null +++ b/plugins/ollama/index.py @@ -0,0 +1,220 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import socket +import json + +from datetime import datetime + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +class App: + __setupPath = '/www/server/ollama' + __cfg = '' + __agent_cfg = '' + + def __init__(self): + self.__setupPath = self.getServerDir() + + def getArgs(self): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':', 1) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + + return tmp + + def checkArgs(self, data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + + def getPluginName(self): + return 'ollama' + + def getPluginDir(self): + return mw.getPluginDir() + '/' + self.getPluginName() + + def getServerDir(self): + return mw.getServerDir() + '/' + self.getPluginName() + + def getHomeDir(self): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + else: + return '/root' + + def getRunUser(self): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + else: + return 'root' + + def status(self): + cmd = "ps -ef|grep " + self.getPluginName() + " |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return "stop" + return 'start' + + def contentReplace(self, content): + + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$RUN_USER}', self.getRunUser()) + content = content.replace('{$HOME_DIR}', self.getHomeDir()) + + return content + + def initDreplace(self): + + file_tpl = self.getInitdConfTpl() + service_path = mw.getServerDir() + + initD_path = self.getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + self.openPort() + + file_bin = initD_path + '/' + self.getPluginName() + + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = self.contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + # systemDir = mw.systemdCfgDir() + # systemService = systemDir + '/nezha.service' + # systemServiceTpl = self.getPluginDir() + '/init.d/nezha.service.tpl' + # if os.path.exists(systemDir) and not os.path.exists(systemService): + # service_path = mw.getServerDir() + # se_content = mw.readFile(systemServiceTpl) + # se_content = self.contentReplace(se_content) + # mw.writeFile(systemService, se_content) + # mw.execShell('systemctl daemon-reload') + + return file_bin + + def contentAgentReplace(self, content): + path = self.__agent_cfg + if os.path.exists(path): + data = self.get_agent_cfg() + content = content.replace('{$APP_HOST}', data['host']) + content = content.replace('{$APP_SECRET}', data['secret']) + + return content + + + def init_cfg(self): + self.initDreplace() + + def oaOp(self, method): + + file = self.initDreplace() + + if not mw.isAppleSystem(): + cmd = 'systemctl {} {}'.format(method, self.getPluginName()) + data = mw.execShell(cmd) + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[0] + + def start(self): + return self.oaOp('start') + + def stop(self): + return self.oaOp('stop') + + def restart(self): + return self.oaOp('restart') + + def reload(self): + return self.oaOp('reload') + + def agOp(self, method): + + if not mw.isAppleSystem(): + cmd = 'systemctl {} {}'.format(method, self.getPluginName()) + data = mw.execShell(cmd) + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[0] + + def start_agent(self): + return self.agOp('start') + + def stop_agent(self): + return self.agOp('stop') + + def restart_agent(self): + return self.agOp('restart') + + def reload_agent(self): + return self.agentOp('reload') + + def initd_status(self): + cmd = 'systemctl status '+self.getPluginName()+' | grep loaded | grep "enabled;"' + data = mw.execShell(cmd) + if data[0] == '': + return 'fail' + return 'ok' + + def initd_install(self): + mw.execShell('systemctl enable '+self.getPluginName()) + return 'ok' + + def initd_uninstall(self): + mw.execShell('systemctl disable '+self.getPluginName()) + return 'ok' + + + +if __name__ == "__main__": + func = sys.argv[1] + classApp = App() + try: + data = eval("classApp." + func + "()") + print(data) + except Exception as e: + print('error:' + str(e)) diff --git a/plugins/ollama/info.json b/plugins/ollama/info.json new file mode 100755 index 000000000..bd0636625 --- /dev/null +++ b/plugins/ollama/info.json @@ -0,0 +1,17 @@ +{ + "sort": 1, + "ps": "Ollama是一个开源跨平台大模型工具", + "name": "ollama", + "title": "ollama", + "shell": "install.sh", + "versions":["1.0"], + "tip": "soft", + "checks": "server/ollama", + "path": "server/ollama", + "display": 1, + "author": "ollama", + "date": "2026-03-12", + "home": "https://ollama.com/", + "type": 7, + "pid": "7" +} \ No newline at end of file diff --git a/plugins/ollama/install.sh b/plugins/ollama/install.sh new file mode 100755 index 000000000..8ca7752a8 --- /dev/null +++ b/plugins/ollama/install.sh @@ -0,0 +1,33 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +action=$1 +type=$2 + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + +sh -x $curPath/versions/$2/install.sh $1 + +if [ "${action}" == "uninstall" ];then + if [ -f /usr/lib/systemd/system/ollama.service ] || [ -f /lib/systemd/system/ollama.service ] ;then + systemctl stop ollama + systemctl disable ollama + rm -rf /usr/lib/systemd/system/ollama.service + rm -rf /lib/systemd/system/ollama.service + systemctl daemon-reload + fi +fi diff --git a/plugins/ollama/js/ollama.js b/plugins/ollama/js/ollama.js new file mode 100755 index 000000000..6a48d9c1b --- /dev/null +++ b/plugins/ollama/js/ollama.js @@ -0,0 +1,104 @@ +var ollama = { + plugin_name: 'ollama', + init: function () { + var _this = this; + }, + + send:function(info){ + var tips = info['tips']; + var method = info['method']; + var args = info['data']; + var callback = info['success']; + + var loadT = layer.msg(tips, { icon: 16, time: 0, shade: 0.3 }); + + var data = {}; + data['name'] = 'ollama'; + data['func'] = method; + data['version'] = $('.plugin_version').attr('version'); + + if (typeof(args) == 'string'){ + data['args'] = JSON.stringify(toArrayObject(args)); + } else { + data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', data, function(res) { + layer.close(loadT); + if (!res.status){ + layer.msg(res.msg,{icon:2,time:10000}); + return; + } + + var ret_data = $.parseJSON(res.data); + console.log("send:",ret_data); + // if (!ret_data.status){ + // layer.msg(ret_data.msg,{icon:2,time:2000}); + // return; + // } + + // console.log("send2:",ret_data); + + if(typeof(callback) == 'function'){ + callback(ret_data); + } + },'json'); + }, + + postCallback:function(info){ + var tips = info['tips']; + var method = info['method']; + var args = info['data']; + var callback = info['success']; + + var loadT = layer.msg(tips, { icon: 16, time: 0, shade: 0.3 }); + + var data = {}; + data['name'] = 'ollama'; + data['func'] = method; + data['version'] = $('.plugin_version').attr('version'); + + if (typeof(args) == 'string'){ + data['args'] = JSON.stringify(toArrayObject(args)); + } else { + data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', data, function(res) { + layer.close(loadT); + if (!res.status){ + layer.msg(res.msg,{icon:2,time:10000}); + return; + } + + var ret_data = $.parseJSON(res.data); + if (!ret_data.status){ + layer.msg(ret_data.msg,{icon:2,time:2000}); + return; + } + + if(typeof(callback) == 'function'){ + callback(res); + } + },'json'); + }, + + readme:function (){ + var readme = '
                                  '; + readme += '
                                • 常用命令说明:
                                • '; + readme += '
                                '; + readme += '
                                '; + readme += ''; + readme += ''; + readme += ''; + readme += ''; + readme += ''; + readme += ''; + readme += ''; + readme += ''; + readme += ''; + readme += ''; + readme += '
                                命令说明示例
                                ollama pull <模型名>仅下载指定的模型到本地,不立即运行ollama pull deepseek-r1:7b
                                ollama run <模型名>下载(若本地无)并运行模型,进入交互界面ollama run llama3.2
                                ollama list列出所有已下载到本地的模型ollama list
                                ollama ps查看当前正在运行的模型ollama ps
                                ollama stop <模型名>停止一个正在运行的模型ollama stop llama3.2
                                ollama rm <模型名>从本地删除指定模型,释放磁盘空间ollama rm llama3.2
                                ollama serve启动 Ollama 后台服务(首次运行模型时也会自动启动)ollama serve
                                '; + $('.soft-man-con').html(readme); + } +} diff --git a/plugins/ollama/versions/1.0/install.sh b/plugins/ollama/versions/1.0/install.sh new file mode 100755 index 000000000..46d295eef --- /dev/null +++ b/plugins/ollama/versions/1.0/install.sh @@ -0,0 +1,77 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=1.0 +sysName=`uname` + +# bash install.sh install 1.0 +# cd /www/server/mdserver-web/plugins/ollama && bash install.sh install 1.0 +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/ollama && bash install.sh install 1.0 + +bash ${rootPath}/scripts/getos.sh + + + +OSNAME=`cat ${rootPath}/data/osname.pl` +OSNAME_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +ARCH="amd64" + +get_arch() { + TMP_ARCH=`arch` + if [ "$TMP_ARCH" == "x86_64" ];then + ARCH="amd64" + elif [ "$TMP_ARCH" == "aarch64" ];then + ARCH="arm64" + else + echo $ARCH + fi +} + +Install_App() +{ + curl -fsSL https://ollama.com/install.sh | sh + + mkdir -p $serverPath/ollama + echo "$VERSION" > $serverPath/ollama/version.pl + + cd ${rootPath} && python3 ${rootPath}/plugins/ollama/index.py start + echo 'install successful' +} + +Uninstall_App() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/ollama/index.py stop + + systemctl stop ollama.service + systemctl disable ollama.service + rm -rf /etc/systemd/system/ollama.service + systemctl daemon-reload + rm -rf $(which ollama) + rm -rf /usr/share/ollama + rm -rf ~/.ollama + + userdel ollama + groupdel ollama + + if command -v apt &> /dev/null; then + apt remove -y ollama + fi + + rm -rf $serverPath/ollama + echo "install fail" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/op_load_balance/conf/load_balance.conf b/plugins/op_load_balance/conf/load_balance.conf new file mode 100644 index 000000000..0dff636c3 --- /dev/null +++ b/plugins/op_load_balance/conf/load_balance.conf @@ -0,0 +1,2 @@ +## 指定共享内存 +lua_shared_dict healthcheck 10m; \ No newline at end of file diff --git a/plugins/op_load_balance/conf/rewrite.tpl.conf b/plugins/op_load_balance/conf/rewrite.tpl.conf new file mode 100644 index 000000000..1554bf2c7 --- /dev/null +++ b/plugins/op_load_balance/conf/rewrite.tpl.conf @@ -0,0 +1,64 @@ +location / { + proxy_pass http://{$UPSTREAM_NAME}; + + client_max_body_size 10m; + client_body_buffer_size 128k; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $proxy_host; + proxy_connect_timeout 90; + proxy_send_timeout 90; + proxy_read_timeout 90; + proxy_buffer_size 4k; + proxy_buffers 4 32k; + proxy_busy_buffers_size 64k; + proxy_temp_file_write_size 64k; +} + + +#location /upstream_status { +# allow 127.0.0.1; +# deny all; +# access_log off; +# default_type text/plain; +# content_by_lua_block { +# local hc = require "resty.upstream.healthcheck" +# ngx.say("OpenResty Worker PID: ", ngx.worker.pid()) +# ngx.print(hc.status_page()) +# } +#} + +location /upstream_status_{$UPSTREAM_NAME} { + allow 127.0.0.1; + deny all; + access_log off; + default_type text/plain; + content_by_lua_block { + local json = require "cjson" + local ok, upstream = pcall(require, "ngx.upstream") + if not ok then + ngx.print("[]") + return + end + + local get_primary_peers = upstream.get_primary_peers + local get_backup_peers = upstream.get_backup_peers + local upstream_name = "{$UPSTREAM_NAME}" + + peers, err = get_primary_peers(upstream_name) + if not peers then + ngx.print("[]") + return + end + + peers_backup, err = get_backup_peers(upstream_name) + if peers_backup then + for k, v in pairs(peers_backup) do + table.insert(peers,v) + end + end + + ngx.print(json.encode(peers)) + } +} \ No newline at end of file diff --git a/plugins/op_load_balance/conf/upstream.tpl.conf b/plugins/op_load_balance/conf/upstream.tpl.conf new file mode 100644 index 000000000..711bbec01 --- /dev/null +++ b/plugins/op_load_balance/conf/upstream.tpl.conf @@ -0,0 +1,5 @@ +upstream {$UPSTREAM_NAME} +{ + {$NODE_ALGO} + {$NODE_SERVER_LIST} +} \ No newline at end of file diff --git a/plugins/op_load_balance/ico.png b/plugins/op_load_balance/ico.png new file mode 100644 index 000000000..57850d8d4 Binary files /dev/null and b/plugins/op_load_balance/ico.png differ diff --git a/plugins/op_load_balance/index.html b/plugins/op_load_balance/index.html new file mode 100755 index 000000000..c789b699c --- /dev/null +++ b/plugins/op_load_balance/index.html @@ -0,0 +1,21 @@ +
                                +
                                +
                                +

                                服务

                                +

                                负载均衡

                                +
                                +
                                +
                                +
                                +
                                + +
                                + + + \ No newline at end of file diff --git a/plugins/op_load_balance/index.py b/plugins/op_load_balance/index.py new file mode 100755 index 000000000..262a20b04 --- /dev/null +++ b/plugins/op_load_balance/index.py @@ -0,0 +1,478 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import subprocess +import json +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'op_load_balance' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + path = getServerDir() + "/cfg.json" + + if not os.path.exists(path): + mw.writeFile(path, '[]') + + c = mw.readFile(path) + return json.loads(c) + + +def writeConf(data): + path = getServerDir() + "/cfg.json" + mw.writeFile(path, json.dumps(data)) + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$APP_PATH}', app_path) + return content + + +def restartWeb(): + mw.opWeb('stop') + mw.opWeb('start') + + +def loadBalanceConf(): + path = mw.getServerDir() + '/web_conf/nginx/vhost/load_balance.conf' + return path + + +def initDreplace(): + + dst_conf_tpl = getPluginDir() + '/conf/load_balance.conf' + dst_conf = loadBalanceConf() + + if not os.path.exists(dst_conf): + con = mw.readFile(dst_conf_tpl) + mw.writeFile(dst_conf, con) + + +def status(): + if not mw.getWebStatus(): + return 'stop' + + dst_conf = loadBalanceConf() + if not os.path.exists(dst_conf): + return 'stop' + + return 'start' + + +def start(): + initDreplace() + restartWeb() + return 'ok' + + +def stop(): + dst_conf = loadBalanceConf() + os.remove(dst_conf) + + deleteLoadBalanceAllCfg() + restartWeb() + return 'ok' + + +def restart(): + restartWeb() + return 'ok' + + +def reload(): + restartWeb() + return 'ok' + + +def installPreInspection(): + check_op = mw.getServerDir() + "/openresty" + if not os.path.exists(check_op): + return "请先安装OpenResty" + return 'ok' + + +def deleteLoadBalanceAllCfg(): + cfg = getConf() + upstream_dir = mw.getServerDir() + '/web_conf/nginx/upstream' + lua_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_worker_by_lua_file' + rewrite_dir = mw.getServerDir() + '/web_conf/nginx/rewrite' + vhost_dir = mw.getServerDir() + '/web_conf/nginx/vhost' + + for conf in cfg: + upstream_file = upstream_dir + '/' + conf['upstream_name'] + '.conf' + if os.path.exists(upstream_file): + os.remove(upstream_file) + + lua_file = lua_dir + '/' + conf['upstream_name'] + '.lua' + if os.path.exists(lua_file): + os.remove(lua_file) + + rewrite_file = rewrite_dir + '/' + conf['domain'] + '.conf' + mw.writeFile(rewrite_file, '') + + path = vhost_dir + '/' + conf['domain'] + '.conf' + + content = mw.readFile(path) + content = re.sub('include ' + upstream_file + ';' + "\n", '', content) + mw.writeFile(path, content) + + mw.opLuaInitWorkerFile() + + +def makeConfServerList(data): + slist = '' + for x in data: + slist += 'server ' + slist += x['ip'] + ':' + x['port'] + + if x['state'] == '0': + slist += ' down;\n\t' + continue + + if x['state'] == '2': + slist += ' backup;\n\t' + continue + + slist += ' weight=' + x['weight'] + slist += ' max_fails=' + x['max_fails'] + slist += ' fail_timeout=' + x['fail_timeout'] + "s;\n\t" + return slist + + +def makeLoadBalanceAllCfg(row): + # 生成所有配置 + cfg = getConf() + + upstream_dir = mw.getServerDir() + '/web_conf/nginx/upstream' + rewrite_dir = mw.getServerDir() + '/web_conf/nginx/rewrite' + vhost_dir = mw.getServerDir() + '/web_conf/nginx/vhost' + upstream_tpl = getPluginDir() + '/conf/upstream.tpl.conf' + rewrite_tpl = getPluginDir() + '/conf/rewrite.tpl.conf' + + if not os.path.exists(upstream_dir): + os.makedirs(upstream_dir) + + conf = cfg[row] + + # replace vhost start + vhost_file = vhost_dir + '/' + conf['domain'] + '.conf' + vcontent = mw.readFile(vhost_file) + + vhost_find_str = 'upstream/' + conf['upstream_name'] + '.conf' + vhead = 'include ' + mw.getServerDir() + '/web_conf/nginx/' + \ + vhost_find_str + ';' + + vpos = vcontent.find(vhost_find_str) + if vpos < 0: + vcontent = vhead + "\n" + vcontent + mw.writeFile(vhost_file, vcontent) + # replace vhost end + + # make upstream start + upstream_file = upstream_dir + '/' + conf['upstream_name'] + '.conf' + content = '' + if len(conf['node_list']) > 0: + content = mw.readFile(upstream_tpl) + slist = makeConfServerList(conf['node_list']) + content = content.replace('{$NODE_SERVER_LIST}', slist) + content = content.replace('{$UPSTREAM_NAME}', conf['upstream_name']) + if conf['node_algo'] != 'polling': + content = content.replace('{$NODE_ALGO}', conf['node_algo'] + ';') + else: + content = content.replace('{$NODE_ALGO}', '') + mw.writeFile(upstream_file, content) + # make upstream end + + # make rewrite start + rewrite_file = rewrite_dir + '/' + conf['domain'] + '.conf' + rcontent = '' + if len(conf['node_list']) > 0: + rcontent = mw.readFile(rewrite_tpl) + rcontent = rcontent.replace('{$UPSTREAM_NAME}', conf['upstream_name']) + mw.writeFile(rewrite_file, rcontent) + # make rewrite end + + # health check start + lua_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_worker_by_lua_file' + lua_init_worker_file = lua_dir + '/' + conf['upstream_name'] + '.lua' + if conf['node_health_check'] == 'ok': + lua_dir_tpl = getPluginDir() + '/lua/health_check.lua.tpl' + content = mw.readFile(lua_dir_tpl) + content = content.replace('{$UPSTREAM_NAME}', conf['upstream_name']) + content = content.replace('{$DOMAIN}', conf['domain']) + mw.writeFile(lua_init_worker_file, content) + else: + if os.path.exists(lua_init_worker_file): + os.remove(lua_init_worker_file) + + mw.opLuaInitWorkerFile() + # health check end + return True + + +def add_load_balance(args): + + data = checkArgs( + args, ['domain', 'upstream_name', 'node_algo', 'node_list', 'node_health_check']) + if not data[0]: + return data[1] + + domain_json = args['domain'] + + tmp = json.loads(domain_json) + domain = tmp['domain'] + + cfg = getConf() + cfg_len = len(cfg) + tmp = {} + tmp['domain'] = domain + tmp['data'] = args['domain'] + tmp['upstream_name'] = args['upstream_name'] + tmp['node_algo'] = args['node_algo'] + tmp['node_list'] = args['node_list'] + tmp['node_health_check'] = args['node_health_check'] + cfg.append(tmp) + writeConf(cfg) + + import site_api + sobj = site_api.site_api() + domain_path = mw.getWwwDir() + '/' + domain + + ps = '负载均衡[' + domain + ']' + data = sobj.add(domain_json, '80', ps, domain_path, '00') + + makeLoadBalanceAllCfg(cfg_len) + mw.restartWeb() + return mw.returnJson(True, '添加成功', data) + + +def edit_load_balance(args): + data = checkArgs( + args, ['row', 'node_algo', 'node_list', 'node_health_check']) + if not data[0]: + return data[1] + + row = int(args['row']) + + cfg = getConf() + tmp = cfg[row] + tmp['node_algo'] = args['node_algo'] + tmp['node_list'] = args['node_list'] + tmp['node_health_check'] = args['node_health_check'] + cfg[row] = tmp + writeConf(cfg) + + makeLoadBalanceAllCfg(row) + mw.restartWeb() + return mw.returnJson(True, '修改成功', data) + + +def loadBalanceList(): + cfg = getConf() + return mw.returnJson(True, 'ok', cfg) + + +def loadBalanceDelete(): + args = getArgs() + data = checkArgs(args, ['row']) + if not data[0]: + return data[1] + + row = int(args['row']) + + cfg = getConf() + data = cfg[row] + + import site_api + sobj = site_api.site_api() + + sid = mw.M('sites').where('name=?', (data['domain'],)).getField('id') + + if type(sid) == list: + del(cfg[row]) + writeConf(cfg) + return mw.returnJson(False, '已经删除了!') + + status = sobj.delete(sid, data['domain'], 1) + status_data = json.loads(status) + + if status_data['status']: + del(cfg[row]) + writeConf(cfg) + + upstream_dir = mw.getServerDir() + '/web_conf/nginx/upstream' + rewrite_dir = mw.getServerDir() + '/web_conf/nginx/rewrite' + + upstream_file = upstream_dir + '/' + data['upstream_name'] + '.conf' + if os.path.exists(upstream_file): + os.remove(upstream_file) + + rewrite_file = rewrite_dir + '/' + data['domain'] + '.conf' + if os.path.exists(rewrite_file): + mw.writeFile(rewrite_file, '') + + return mw.returnJson(status_data['status'], status_data['msg']) + + +def http_get(url): + ret = re.search(r'https://', url) + if ret: + try: + from gevent import monkey + monkey.patch_ssl() + import requests + ret = requests.get(url=str(url), verify=False, timeout=10) + status = [200, 301, 302, 404, 403] + if ret.status_code in status: + return True + else: + return False + except: + return False + else: + try: + if sys.version_info[0] == 2: + import urllib2 + rec = urllib2.urlopen(url, timeout=3) + else: + import urllib.request + rec = urllib.request.urlopen(url, timeout=3) + status = [200, 301, 302, 404, 403] + if rec.getcode() in status: + return True + return False + except: + return False + + +def checkUrl(): + args = getArgs() + data = checkArgs(args, ['ip', 'port', 'path']) + if not data[0]: + return data[1] + + ip = args['ip'] + port = args['port'] + path = args['path'] + + if port == '443': + url = 'https://' + str(ip) + ':' + str(port) + str(path.strip()) + else: + url = 'http://' + str(ip) + ':' + str(port) + str(path.strip()) + ret = http_get(url) + if not ret: + return mw.returnJson(False, '访问节点[%s]失败' % url) + return mw.returnJson(True, '访问节点[%s]成功' % url) + + +def getHealthStatus(): + args = getArgs() + data = checkArgs(args, ['row']) + if not data[0]: + return data[1] + + row = int(args['row']) + + cfg = getConf() + data = cfg[row] + + url = 'http://' + data['domain'] + \ + '/upstream_status_' + data['upstream_name'] + + url_data = mw.httpGet(url) + return mw.returnJson(True, 'ok', json.loads(url_data)) + + +def getLogs(): + args = getArgs() + data = checkArgs(args, ['domain']) + if not data[0]: + return data[1] + + domain = args['domain'] + logs = mw.getLogsDir() + '/' + domain + '.log' + return logs + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'add_load_balance': + print(addLoadBalance()) + elif func == 'load_balance_list': + print(loadBalanceList()) + elif func == 'load_balance_delete': + print(loadBalanceDelete()) + elif func == 'check_url': + print(checkUrl()) + elif func == 'get_logs': + print(getLogs()) + elif func == 'get_health_status': + print(getHealthStatus()) + else: + print('error') diff --git a/plugins/op_load_balance/info.json b/plugins/op_load_balance/info.json new file mode 100755 index 000000000..695903c81 --- /dev/null +++ b/plugins/op_load_balance/info.json @@ -0,0 +1,17 @@ +{ + "sort":3, + "title":"OP负载均衡", + "tip":"soft", + "name":"op_load_balance", + "type":"其他插件", + "install_pre_inspection":true, + "ps":"基于OpenResty的负载均衡", + "shell":"install.sh", + "checks":"server/op_load_balance", + "path":"server/op_load_balance", + "author":"midoks", + "home":"https://github.com/midoks", + "date":"2023-02-02", + "pid": "1", + "versions": ["1.0"] +} \ No newline at end of file diff --git a/plugins/op_load_balance/install.sh b/plugins/op_load_balance/install.sh new file mode 100755 index 000000000..d34795d33 --- /dev/null +++ b/plugins/op_load_balance/install.sh @@ -0,0 +1,44 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + + +action=$1 +version=$2 +sys_os=`uname` + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +if [ "$sys_os" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_App(){ + echo '正在安装脚本文件...' + mkdir -p $serverPath/op_load_balance + echo "${version}" > $serverPath/op_load_balance/version.pl + cd ${rootPath} && python3 ${rootPath}/plugins/op_load_balance/index.py start + echo '安装OP负载均衡成功!' +} + +Uninstall_App(){ + cd ${rootPath} && python3 ${rootPath}/plugins/op_load_balance/index.py stop + rm -rf $serverPath/op_load_balance +} + + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/op_load_balance/js/app.js b/plugins/op_load_balance/js/app.js new file mode 100644 index 000000000..3ffca2292 --- /dev/null +++ b/plugins/op_load_balance/js/app.js @@ -0,0 +1,606 @@ +function ooPost(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'op_load_balance', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function ooAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'op_load_balance', func:method, args:_args}); +} + +function ooPostCallbak(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'op_load_balance'; + req_data['func'] = method; + args['version'] = '1.0'; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function addNode(){ + layer.open({ + type: 1, + area: ['450px','580px'], + title: '添加节点', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:['提交','关闭'], + content: "
                                \ +
                                \ + IP地址\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 端口\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 验证文件路径\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 节点状态\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 权重\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 阈值\ +
                                \ + 次\ +
                                \ +
                                \ +
                                \ + 恢复时间\ +
                                \ + 秒\ +
                                \ +
                                \ +
                                  \ +
                                • 备份状态: 指当其它节点都无法使用时才会使用此节点
                                • \ +
                                • 参与状态: 正常参与负载均衡,请至少添加1个普通节点
                                • \ +
                                • 验证文件路径: 用于检查文件路径地址是否可用
                                • \ +
                                • IP地址: 仅支持IP地址,否则无法正常参与负载均衡
                                • \ +
                                • 阈值: 在恢复时间的时间段内,如果OpenResty与节点通信尝试失败的次数达到此值,OpenResty就认为服务器不可用
                                • \ +
                                \ +
                                ", + success:function(){ + }, + yes:function(index) { + + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var path = $('input[name="path"]').val(); + var state = $('select[name="state"]').val(); + var weight = $('input[name="weight"]').val(); + var max_fails = $('input[name="max_fails"]').val(); + var fail_timeout = $('input[name="fail_timeout"]').val(); + + ooPost('check_url', {ip:ip,port:port,path:path},function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + $('#nodecon .nulltr').hide(); + + var tbody = ''; + tbody +=''+ip+''; + tbody +=''+port+''; + tbody +=''+path+''; + + tbody +=""; + + tbody +=''; + tbody +=''; + tbody +=''; + tbody +='删除'; + tbody += ''; + $('#nodecon').append(tbody); + + $('#nodecon .delete').click(function(){ + $(this).parent().parent().remove(); + if ($('#nodecon tr').length == 1 ){ + $('#nodecon .nulltr').show(); + } + }); + } + },{ icon: rdata.status ? 1 : 2 }, 2000); + }); + } + }); +} + +function addBalance(){ + layer.open({ + type: 1, + area: ['750px','460px'], + title: '创建负载', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:['提交','关闭'], + content: "
                                \ +
                                \ + 域名\ +
                                \ +
                                \ +
                                \ + 负载名称\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 节点调度\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 节点健康检查\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 节点\ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                IP地址端口验证路径状态权重阀值恢复时间操作
                                当前节点为空,请至少添加一个普通节点
                                \ +
                                \ + 添加节点\ +
                                \ +
                                \ +
                                ", + success:function(){ + $('textarea[name="load_domain"]').attr('placeholder','每行填写一个域名,默认为80端口。\n泛解析添加方法 *.domain.com\n如另加端口格式为 www.domain.com:88'); + var rval = getRandomString(6); + $('input[name="upstream_name"]').val('load_balance_'+rval); + + $('.add_node').click(function(){ + addNode(); + }); + }, + yes:function(index) { + var data = {}; + + var upstream_name = $('input[name="upstream_name"]').val(); + if (upstream_name == ''){ + layer.msg('负载名称不能为空!',{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var domain = $('textarea[name="load_domain"]').val().replace('http://','').replace('https://','').split("\n"); + if (domain[0] == ''){ + layer.msg('域名不能为空!',{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var domainlist = ''; + for(var i=1; i\ +
                                \ + 负载名称\ +
                                \ +
                                \ +
                                \ + 节点调度\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 节点健康检查\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 节点\ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                IP地址端口验证路径状态权重阀值恢复时间操作
                                当前节点为空,请至少添加一个普通节点
                                \ +
                                \ + 添加节点\ +
                                \ +
                                \ + ", + success:function(){ + $('input[name="upstream_name"]').val(data['upstream_name']); + $('select[name="node_algo"]').val(data['node_algo']); + + $('input[name="node_health_check"]').prop('checked',false); + if (data['node_health_check'] == 'ok'){ + $('input[name="node_health_check"]').prop('checked',true); + } + + var node_list = data['node_list']; + if (node_list.length>0){ + $('#nodecon .nulltr').hide(); + } + + var state_option_list = { + '1':'参与者', + '2':'备份', + '0':'停用', + } + + for (var n in node_list) { + + var tbody = ''; + tbody +=''+node_list[n]['ip']+''; + tbody +=''+node_list[n]['port']+''; + tbody +=''+node_list[n]['path']+''; + + tbody +=""; + + tbody +=''; + tbody +=''; + tbody +=''; + tbody +='删除'; + tbody += ''; + $('#nodecon').append(tbody); + } + + $('#nodecon .delete').click(function(){ + $(this).parent().parent().remove(); + if ($('#nodecon tr').length == 1 ){ + $('#nodecon .nulltr').show(); + } + }); + + $('.add_node').click(function(){ + addNode(); + }); + }, + yes:function(index) { + var data = {}; + + data['node_algo'] = $('select[name="node_algo"]').val(); + data['node_health_check'] = 'fail'; + if ($('input[name="node_health_check"]').prop('checked')){ + data['node_health_check'] = 'ok'; + } + + var node_list = []; + $('#nodecon tr').each(function(){ + + var ip = $(this).find('td').eq(0).text(); + var port = $(this).find('td').eq(1).text(); + + if (port == ''){return;} + + var path = $(this).find('td').eq(2).text(); + var state = $(this).find('select[name="state"]').val(); + var weight = $(this).find('input[name="weight"]').val(); + var max_fails = $(this).find('input[name="max_fails"]').val(); + var fail_timeout = $(this).find('input[name="fail_timeout"]').val(); + + var tmp = { + ip:ip, + port:port, + path:path, + state:state, + weight:weight, + max_fails:max_fails, + fail_timeout:fail_timeout, + } + node_list.push(tmp); + }); + data['node_list'] = node_list; + data['row'] = row; + ooPostCallbak('edit_load_balance', data, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + layer.close(index); + loadBalanceListRender(); + },{ icon: rdata.status ? 1 : 2 }, 2000); + }); + } + }); +} + +function loadBalanceListRender(){ + ooPost('load_balance_list', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var alist = rdata.data; + + var tbody = ''; + for (var i = 0; i < alist.length; i++) { + tbody += ''; + tbody += ''+alist[i]['domain']+''; + tbody += ''+alist[i]['upstream_name']+''; + tbody += ''+alist[i]['node_list'].length+''; + tbody += '查看'; + tbody += '查看'; + tbody += '修改 | 删除'; + tbody += ''; + } + + $('#nodeTable').html(tbody); + $('.nodeTablePage .Pcount').text('共'+alist.length+'条'); + $('#nodeTable .edit').click(function(){ + var row = $(this).data('row'); + editBalance(alist[row],row); + }); + + $('#nodeTable .log_look').click(function(){ + var row = $(this).data('row'); + var args = {'domain':alist[row]['domain']}; + pluginRollingLogs('op_load_balance','','get_logs',JSON.stringify(args),20); + }); + + $('#nodeTable .health_status').click(function(){ + var row = $(this).data('row'); + ooPost('get_health_status', {row:row}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + + var tval = ''; + for (var i = 0; i < rdata.data.length; i++) { + tval += ''; + tval += ''+rdata.data[i]['name']+''; + + if (typeof(rdata.data[i]['down']) != 'undefined' && rdata.data[i]['down']){ + tval += '不正常'; + } else{ + tval += '正常'; + } + tval += ''; + } + + var tbody = "
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + "+tval+"\ +
                                地址状态
                                \ +
                                \ +
                                \ +
                                "; + + layer.open({ + type: 1, + area: ['500px','300px'], + title: '节点状态', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:['提交','关闭'], + content:tbody, + }); + }); + }); + + $('#nodeTable .delete').click(function(){ + var row = $(this).data('row'); + ooPost('load_balance_delete', {row:row}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + loadBalanceListRender(); + },{ icon: rdata.status ? 1 : 2 }, 2000); + }); + }); + + }); +} + +function loadBalanceList() { + var body = '
                                \ +
                                \ + \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                网站负载名称节点日志状态操作
                                \ +
                                \ +
                                共0条
                                \ +
                                \ +
                                \ +
                                \ +
                                \ + '; + $(".soft-man-con").html(body); + loadBalanceListRender(); +} diff --git a/plugins/op_load_balance/lua/health_check.lua.tpl b/plugins/op_load_balance/lua/health_check.lua.tpl new file mode 100644 index 000000000..94411c942 --- /dev/null +++ b/plugins/op_load_balance/lua/health_check.lua.tpl @@ -0,0 +1,18 @@ +local hc = require "resty.upstream.healthcheck" +local ok, err = hc.spawn_checker { + shm = "healthcheck", + type = "http", + upstream = "{$UPSTREAM_NAME}", + http_req = "GET / HTTP/1.0\r\nHost: {$UPSTREAM_NAME}\r\n\r\n", + interval = 2000, + timeout = 6000, + fall = 3, + rise = 2, + valid_statuses = {200, 302}, + concurrency = 20, +} + +if not ok then + ngx.log(ngx.ERR, "=======> load balance health checker error: ", err) + return +end \ No newline at end of file diff --git a/plugins/op_waf/class/luamaker.py b/plugins/op_waf/class/luamaker.py new file mode 100644 index 000000000..fa0735aa4 --- /dev/null +++ b/plugins/op_waf/class/luamaker.py @@ -0,0 +1,61 @@ +import sys +import os + + +class luamaker: + """ + lua 处理器 + """ + @staticmethod + def makeLuaTable(table): + """ + table 转换为 lua table 字符串 + """ + _tableMask = {} + _keyMask = {} + + def analysisTable(_table, _indent, _parent): + if isinstance(_table, tuple): + _table = list(_table) + if isinstance(_table, list): + _table = dict(zip(range(1, len(_table) + 1), _table)) + if isinstance(_table, dict): + _tableMask[id(_table)] = _parent + cell = [] + thisIndent = _indent + " " + for k in _table: + if sys.version_info[0] == 2: + if type(k) not in [int, float, bool, list, dict, tuple]: + k = k.encode() + + if not (isinstance(k, str) or isinstance(k, int) or isinstance(k, float)): + return + key = isinstance( + k, int) and "[" + str(k) + "]" or "[\"" + str(k) + "\"]" + if _parent + key in _keyMask.keys(): + return + _keyMask[_parent + key] = True + var = None + v = _table[k] + if sys.version_info[0] == 2: + if type(v) not in [int, float, bool, list, dict, tuple]: + v = v.encode() + if isinstance(v, str): + # print("lua", var) + v = v.replace("\\", "\\\\") + v = v.replace("\"", "\\\"") + var = "\"" + v + "\"" + + elif isinstance(v, bool): + var = v and "true" or "false" + elif isinstance(v, int) or isinstance(v, float): + var = str(v) + else: + var = analysisTable(v, thisIndent, _parent + key) + + cell.append(thisIndent + key + " = " + str(var)) + lineJoin = ",\n" + return "{\n" + lineJoin.join(cell) + "\n" + _indent + "}" + else: + pass + return analysisTable(table, "", "root") diff --git a/plugins/op_waf/conf/init.sql b/plugins/op_waf/conf/init.sql new file mode 100644 index 000000000..210a5d2b0 --- /dev/null +++ b/plugins/op_waf/conf/init.sql @@ -0,0 +1,27 @@ +PRAGMA synchronous = 0; +PRAGMA page_size = 4096; +PRAGMA journal_mode = wal; +PRAGMA journal_size_limit = 1073741824; + +CREATE TABLE IF NOT EXISTS `logs` ( + `time` INTEGER, + `ip` TEXT, + `domain` TEXT, + `server_name` TEXT, + `method` TEXT, + `status_code` INTEGER, + `user_agent` TEXT, + `uri` TEXT, + `rule_name` TEXT, + `reason` TEXT +); + + +CREATE INDEX time_idx ON logs(`time`); +CREATE INDEX ip_idx ON logs (`ip`); +CREATE INDEX uri_idx ON logs (`uri`); +CREATE INDEX method_idx ON logs (`method`); +CREATE INDEX server_name_idx ON logs (`server_name`); +CREATE INDEX status_code_idx ON logs (`status_code`); +CREATE INDEX all_union_idx ON logs (`status_code`, `time`, `ip`, `domain`, `server_name`, `method`, `uri`); + diff --git a/plugins/op_waf/conf/luawaf.conf b/plugins/op_waf/conf/luawaf.conf new file mode 100755 index 000000000..3c93a082d --- /dev/null +++ b/plugins/op_waf/conf/luawaf.conf @@ -0,0 +1,3 @@ +lua_shared_dict waf_limit 30m; +lua_shared_dict waf_drop_ip 10m; +lua_shared_dict waf_drop_sum 10m; \ No newline at end of file diff --git a/plugins/op_waf/ico.png b/plugins/op_waf/ico.png new file mode 100644 index 000000000..55dabc43d Binary files /dev/null and b/plugins/op_waf/ico.png differ diff --git a/plugins/op_waf/index.html b/plugins/op_waf/index.html new file mode 100755 index 000000000..73ee75db1 --- /dev/null +++ b/plugins/op_waf/index.html @@ -0,0 +1,268 @@ + +
                                +
                                +
                                +

                                服务

                                +

                                首页

                                +

                                全局配置

                                +

                                站点配置

                                +

                                地区限制

                                +

                                封锁历史

                                + +
                                + +
                                +
                                +
                                +
                                + +
                                + + \ No newline at end of file diff --git a/plugins/op_waf/index.py b/plugins/op_waf/index.py new file mode 100755 index 000000000..92698b3e9 --- /dev/null +++ b/plugins/op_waf/index.py @@ -0,0 +1,1593 @@ +# coding:utf-8 + +''' +cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/op_waf && bash install.sh install 0.3.2 +python3 /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/op_waf/index.py reload +''' +import sys +import io +import os +import time +import subprocess +import json +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'op_waf' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':', 1) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +sys.path.append(getPluginDir() + "/class") +from luamaker import luamaker + + +def listToLuaFile(path, lists): + content = luamaker.makeLuaTable(lists) + content = "return " + content + mw.writeFile(path, content) + + +def htmlToLuaFile(path, content): + content = "return [[" + content + "]]" + mw.writeFile(path, content) + + +def getConf(): + path = mw.getServerDir() + "/openresty/nginx/conf/nginx.conf" + return path + + +def dstWafConfPath(): + return mw.getServerDir() + "/web_conf/nginx/vhost/opwaf.conf" + + +def pSqliteDb(dbname='logs'): + name = "waf" + db_dir = getServerDir() + '/logs/' + + if not os.path.exists(db_dir): + mw.execShell('mkdir -p ' + db_dir) + + file = db_dir + name + '.db' + if not os.path.exists(file): + conn = mw.M(dbname).dbPos(db_dir, name) + sql = mw.readFile(getPluginDir() + '/conf/init.sql') + sql_list = sql.split(';') + for index in range(len(sql_list)): + conn.execute(sql_list[index]) + else: + conn = mw.M(dbname).dbPos(db_dir, name) + + conn.execute("PRAGMA synchronous = 0") + conn.execute("PRAGMA page_size = 4096") + conn.execute("PRAGMA journal_mode = wal") + conn.execute("PRAGMA journal_size_limit = 1073741824") + return conn + + +def initDomainInfo(conf_reload=False): + data = [] + path_domains = getJsonPath('domains') + _list = mw.M('sites').field('id,name,path').where( + 'status=?', ('1',)).order('id desc').select() + + for i in range(len(_list)): + tmp = {} + tmp['name'] = _list[i]['name'] + tmp['path'] = _list[i]['path'] + + _list_domain = mw.M('domain').field('name').where( + 'pid=?', (_list[i]['id'],)).order('id desc').select() + + tmp_j = [] + for j in range(len(_list_domain)): + tmp_j.append(_list_domain[j]['name']) + + tmp['domains'] = tmp_j + data.append(tmp) + cjson = mw.getJson(data) + mw.writeFile(path_domains, cjson) + + +def initSiteInfo(conf_reload=False): + data = [] + + path_site = getJsonPath('site') + path_domains = getJsonPath('domains') + path_config = getJsonPath('config') + + config_contents = mw.readFile(path_config) + config_contents = json.loads(config_contents) + + domain_contents = mw.readFile(path_domains) + domain_contents = json.loads(domain_contents) + + try: + site_contents = mw.readFile(path_site) + if not site_contents: + site_contents = "{}" + except Exception as e: + site_contents = "{}" + + site_contents = json.loads(site_contents) + site_contents_new = {} + for x in range(len(domain_contents)): + name = domain_contents[x]['name'] + if name in site_contents: + site_contents_new[name] = site_contents[name] + else: + tmp = {} + tmp['cdn'] = True + tmp['log'] = True + tmp['get'] = True + tmp['post'] = True + tmp['open'] = True + + tmp['cc'] = config_contents['cc'] + tmp['retry'] = config_contents['retry'] + tmp['get'] = config_contents['get'] + tmp['post'] = config_contents['post'] + tmp['user-agent'] = config_contents['user-agent'] + tmp['cookie'] = config_contents['cookie'] + tmp['scan'] = config_contents['scan'] + tmp['safe_verify'] = config_contents['safe_verify'] + + cdn_header = ['x-forwarded-for', + 'x-real-ip', + 'x-forwarded', + 'forwarded-for', + 'forwarded', + 'true-client-ip', + 'client-ip', + 'ali-cdn-real-ip', + 'cdn-src-ip', + 'cdn-real-ip', + 'cf-connecting-ip', + 'x-cluster-client-ip', + 'wl-proxy-client-ip', + 'proxy-client-ip', + 'true-client-ip', + 'HTTP_CF_CONNECTING_IP'] + tmp['cdn_header'] = cdn_header + + disable_upload_ext = ["php", "jsp"] + tmp['disable_upload_ext'] = disable_upload_ext + + disable_path = ['sql'] + tmp['disable_ext'] = disable_path + + site_contents_new[name] = tmp + + cjson = mw.getJson(site_contents_new) + mw.writeFile(path_site, cjson) + + +def initTotalInfo(conf_reload=False): + data = [] + + path_total = getJsonPath('total') + path_domains = getJsonPath('domains') + + domain_contents = mw.readFile(path_domains) + domain_contents = json.loads(domain_contents) + + try: + total_contents = mw.readFile(path_total) + except Exception as e: + total_contents = "{}" + + total_contents = json.loads(total_contents) + total_contents_new = {} + for x in range(len(domain_contents)): + name = domain_contents[x]['name'] + if 'sites' in total_contents and name in total_contents['sites']: + pass + else: + tmp = {} + tmp['cdn'] = 0 + tmp['log'] = 0 + tmp['get'] = 0 + tmp['post'] = 0 + tmp['total'] = 0 + tmp['path'] = 0 + tmp['php_path'] = 0 + tmp['upload_ext'] = 0 + _name = {} + _name[name] = tmp + total_contents['sites'] = _name + + total_contents['start_time'] = str(time.time()) + cjson = mw.getJson(total_contents) + mw.writeFile(path_total, cjson) + + +def contentReplace(content): + service_path = mw.getServerDir() + waf_root = getServerDir() + waf_path = waf_root + "/waf" + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$WAF_PATH}', waf_path) + content = content.replace('{$WAF_ROOT}', waf_root) + + if mw.isAppleSystem(): + content = content.replace('{$MMDB_FILE_SUFFIX}', 'dylib') + else: + content = content.replace('{$MMDB_FILE_SUFFIX}', 'so') + + return content + + +def autoMakeLuaConfSingle(file, conf_reload=False): + path = getServerDir() + "/waf/rule/" + file + ".json" + dst_path = getServerDir() + "/waf/conf/rule_" + file + ".lua" + if not os.path.exists(dst_path) or conf_reload: + content = mw.readFile(path) + # print(content) + content = json.loads(content) + listToLuaFile(dst_path, content) + + +def autoCpImport(file): + path = getPluginDir() + "/waf/" + file + ".json" + dst_path = getServerDir() + "/waf/" + file + ".json" + content = mw.readFile(path) + mw.writeFile(dst_path, content) + + +def autoMakeLuaImportSingle(file, conf_reload=False): + path = getServerDir() + "/waf/" + file + ".json" + dst_path = getServerDir() + "/waf/conf/waf_" + file + ".lua" + if not os.path.exists(dst_path) or conf_reload: + content = mw.readFile(path) + # print(content) + content = json.loads(content) + listToLuaFile(dst_path, content) + + +def autoMakeLuaHtmlSingle(file, conf_reload=False): + path = getServerDir() + "/waf/html/" + file + ".html" + dst_path = getServerDir() + "/waf/html/html_" + file + ".lua" + if not os.path.exists(dst_path) or conf_reload: + content = mw.readFile(path) + htmlToLuaFile(dst_path, content) + + +def autoCpHtml(file): + path = getPluginDir() + "/waf/html/" + file + ".html" + dst_path = getServerDir() + "/waf/html/" + file + ".html" + content = mw.readFile(path) + mw.writeFile(dst_path, content) + + +def autoMakeLuaConf(conf_reload=False, cp_reload=False): + conf_list = ['args', 'cookie', 'ip_black', 'ip_white', + 'ipv6_black', 'post', 'scan_black', 'url', + 'url_white', 'user_agent'] + for x in conf_list: + autoMakeLuaConfSingle(x, conf_reload) + + import_list = ['config', 'site', 'domains', 'area_limit'] + for x in import_list: + autoMakeLuaImportSingle(x, conf_reload) + + html_list = ['get', 'post', 'safe_js', 'user_agent', 'cookie', 'other'] + for x in html_list: + if cp_reload: + autoCpHtml(x) + autoMakeLuaHtmlSingle(x, conf_reload) + + +def initDefaultInfo(conf_reload=False): + path = getServerDir() + dst_path = path + "/waf/default.pl" + default_site = '' + if os.path.exists(dst_path): + return True + source_path = path + "/waf/domains.json" + content = mw.readFile(source_path) + content = json.loads(content) + + ddata = {} + dlist = [] + for i in content: + dlist.append(i["name"]) + + dlist.append('unset') + ddata["list"] = dlist + if len(ddata["list"]) < 1: + default_site = "unset" + else: + default_site = dlist[0] + + mw.writeFile(dst_path, default_site) + + +def getSiteListData(): + path = getServerDir() + source_path = path + "/waf/domains.json" + dst_path = path + "/waf/default.pl" + + content = mw.readFile(source_path) + content = json.loads(content) + dlist = [] + for i in content: + dlist.append(i["name"]) + dlist.append('unset') + + default_site = mw.readFile(dst_path) + + data = {} + data['list'] = dlist + data['default'] = default_site + return data + + +def setDefaultSite(name): + path = getServerDir() + dst_path = path + "/waf/default.pl" + mw.writeFile(dst_path, name) + return mw.returnJson(True, 'OK') + + +def getDefaultSite(): + data = getSiteListData() + return mw.returnJson(True, 'OK', data) + + +def getCountry(): + data = ['中国大陆以外的地区(包括[中国特别行政区:港,澳,台])', '中国大陆(不包括[中国特别行政区:港,澳,台])', '中国香港', '中国澳门', '中国台湾', + '美国', '日本', '英国', '德国', '韩国', '法国', '巴西', '加拿大', '意大利', '澳大利亚', '荷兰', '俄罗斯', '印度', '瑞典', '西班牙', '墨西哥', + '比利时', '南非', '波兰', '瑞士', '阿根廷', '印度尼西亚', '埃及', '哥伦比亚', '土耳其', '越南', '挪威', '芬兰', '丹麦', '乌克兰', '奥地利', + '伊朗', '智利', '罗马尼亚', '捷克', '泰国', '沙特阿拉伯', '以色列', '新西兰', '委内瑞拉', '摩洛哥', '马来西亚', '葡萄牙', '爱尔兰', '新加坡', + '欧洲联盟', '匈牙利', '希腊', '菲律宾', '巴基斯坦', '保加利亚', '肯尼亚', '阿拉伯联合酋长国', '阿尔及利亚', '塞舌尔', '突尼斯', '秘鲁', '哈萨克斯坦', + '斯洛伐克', '斯洛文尼亚', '厄瓜多尔', '哥斯达黎加', '乌拉圭', '立陶宛', '塞尔维亚', '尼日利亚', '克罗地亚', '科威特', '巴拿马', '毛里求斯', '白俄罗斯', + '拉脱维亚', '多米尼加', '卢森堡', '爱沙尼亚', '苏丹', '格鲁吉亚', '安哥拉', '玻利维亚', '赞比亚', '孟加拉国', '巴拉圭', '波多黎各', '坦桑尼亚', + '塞浦路斯', '摩尔多瓦', '阿曼', '冰岛', '叙利亚', '卡塔尔', '波黑', '加纳', '阿塞拜疆', '马其顿', '约旦', '萨尔瓦多', '伊拉克', '亚美尼亚', '马耳他', + '危地马拉', '巴勒斯坦', '斯里兰卡', '特立尼达和多巴哥', '黎巴嫩', '尼泊尔', '纳米比亚', '巴林', '洪都拉斯', '莫桑比克', '尼加拉瓜', '卢旺达', '加蓬', + '阿尔巴尼亚', '利比亚', '吉尔吉斯坦', '柬埔寨', '古巴', '喀麦隆', '乌干达', '塞内加尔', '乌兹别克斯坦', '黑山', '关岛', '牙买加', '蒙古', '文莱', + '英属维尔京群岛', '留尼旺', '库拉索岛', '科特迪瓦', '开曼群岛', '巴巴多斯', '马达加斯加', '伯利兹', '新喀里多尼亚', '海地', '马拉维', '斐济', '巴哈马', + '博茨瓦纳', '扎伊尔', '阿富汗', '莱索托', '百慕大', '埃塞俄比亚', '美属维尔京群岛', '列支敦士登', '津巴布韦', '直布罗陀', '苏里南', '马里', '也门', + '老挝', '塔吉克斯坦', '安提瓜和巴布达', '贝宁', '法属玻利尼西亚', '圣基茨和尼维斯', '圭亚那', '布基纳法索', '马尔代夫', '泽西岛', '摩纳哥', '巴布亚新几内亚', + '刚果', '塞拉利昂', '吉布提', '斯威士兰', '缅甸', '毛里塔尼亚', '法罗群岛', '尼日尔', '安道尔', '阿鲁巴', '布隆迪', '圣马力诺', '利比里亚', + '冈比亚', '不丹', '几内亚', '圣文森特岛', '荷兰加勒比区', '圣马丁', '多哥', '格陵兰', '佛得角', '马恩岛', '索马里', '法属圭亚那', '西萨摩亚', + '土库曼斯坦', '瓜德罗普', '马里亚那群岛', '瓦努阿图', '马提尼克', '赤道几内亚', '南苏丹', '梵蒂冈', '格林纳达', '所罗门群岛', '特克斯和凯科斯群岛', '多米尼克', + '乍得', '汤加', '瑙鲁', '圣多美和普林西比', '安圭拉岛', '法属圣马丁', '图瓦卢', '库克群岛', '密克罗尼西亚联邦', '根西岛', '东帝汶', '中非', + '几内亚比绍', '帕劳', '美属萨摩亚', '厄立特里亚', '科摩罗', '圣皮埃尔和密克隆', '瓦利斯和富图纳', '英属印度洋领地', '托克劳', '马绍尔群岛', '基里巴斯', + '纽埃', '诺福克岛', '蒙特塞拉特岛', '朝鲜', '马约特', '圣卢西亚', '圣巴泰勒米岛'] + return mw.returnJson(True, 'ok', data) + + +def autoMakeConfig(conf_reload=False, cp_reload=False): + initDomainInfo(conf_reload) + initSiteInfo(conf_reload) + initTotalInfo(conf_reload) + autoMakeLuaConf(conf_reload, cp_reload) + initDefaultInfo(conf_reload) + + +def setConfRestartWeb(): + autoMakeConfig(True, False) + mw.opWeb('stop') + mw.opWeb('start') + + +def restartWeb(): + mw.opWeb('stop') + mw.opWeb('start') + + +def makeOpDstRunLua(conf_reload=False): + root_init_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_by_lua_file' + root_worker_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_worker_by_lua_file' + root_access_dir = mw.getServerDir() + '/web_conf/nginx/lua/access_by_lua_file' + path = getServerDir() + path_tpl = getPluginDir() + + waf_common_dst = path + "/waf/lua/waf_common.lua" + if not os.path.exists(waf_common_dst) or conf_reload: + waf_common_tpl = path_tpl + "/waf/lua/waf_common.lua" + content = mw.readFile(waf_common_tpl) + content = contentReplace(content) + mw.writeFile(waf_common_dst, content) + + waf_init_dst = root_init_dir + "/waf_init_preload.lua" + if not os.path.exists(waf_init_dst) or conf_reload: + waf_init_tpl = path_tpl + "/waf/lua/init_preload.lua" + content = mw.readFile(waf_init_tpl) + content = contentReplace(content) + mw.writeFile(waf_init_dst, content) + + init_worker_dst = root_worker_dir + '/opwaf_init_worker.lua' + if not os.path.exists(init_worker_dst) or conf_reload: + init_worker_tpl = path_tpl + "/waf/lua/init_worker.lua" + content = mw.readFile(init_worker_tpl) + content = contentReplace(content) + mw.writeFile(init_worker_dst, content) + + access_file_dst = root_access_dir + '/opwaf_init.lua' + if not os.path.exists(access_file_dst) or conf_reload: + access_file_tpl = path_tpl + "/waf/lua/init.lua" + access_file_dst_s = path + "/waf/lua/init.lua" + content = mw.readFile(access_file_tpl) + content = contentReplace(content) + mw.writeFile(access_file_dst, content) + mw.writeFile(access_file_dst_s, content) + + waf_mmdb_dst = path + "/waf/lua/waf_maxminddb.lua" + if not os.path.exists(waf_mmdb_dst) or conf_reload: + waf_mmdb_tpl = path_tpl + "/waf/lua/waf_maxminddb.lua" + content = mw.readFile(waf_mmdb_tpl) + content = contentReplace(content) + mw.writeFile(waf_mmdb_dst, content) + + mw.opLuaMakeAll() + return True + + +def makeOpDstStopLua(): + root_init_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_by_lua_file' + root_worker_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_worker_by_lua_file' + root_access_dir = mw.getServerDir() + '/web_conf/nginx/lua/access_by_lua_file' + + waf_init_dst = root_init_dir + "/waf_init_preload.lua" + if os.path.exists(waf_init_dst): + os.remove(waf_init_dst) + + init_worker_dst = root_worker_dir + '/opwaf_init_worker.lua' + if os.path.exists(init_worker_dst): + os.remove(init_worker_dst) + + access_file_dst = root_access_dir + '/opwaf_init.lua' + if os.path.exists(access_file_dst): + os.remove(access_file_dst) + + wafconf = dstWafConfPath() + if os.path.exists(wafconf): + os.remove(wafconf) + + mw.opLuaMakeAll() + return True + + +def initDreplace(): + path = getServerDir() + if not os.path.exists(path + '/waf/lua'): + sdir = getPluginDir() + '/waf' + cmd = 'cp -rf ' + sdir + ' ' + path + mw.execShell(cmd) + + logs_path = path + '/logs' + if not os.path.exists(logs_path): + mw.execShell('mkdir -p ' + logs_path) + + debug_log = path + '/debug.log' + if not os.path.exists(debug_log): + mw.execShell('echo "" > ' + debug_log) + + config = path + '/waf/config.json' + content = mw.readFile(config) + content = json.loads(content) + content['reqfile_path'] = path + "/waf/html" + mw.writeFile(config, mw.getJson(content)) + + makeOpDstRunLua() + + waf_conf = dstWafConfPath() + if not os.path.exists(waf_conf): + waf_tpl = getPluginDir() + "/conf/luawaf.conf" + content = mw.readFile(waf_tpl) + content = contentReplace(content) + mw.writeFile(waf_conf, content) + + autoMakeConfig(True, False) + + pSqliteDb() + + if not mw.isAppleSystem(): + mw.execShell("chown -R www:www " + path) + return path + + +def status(): + path = getConf() + if not os.path.exists(path): + return 'stop' + + waf_conf = dstWafConfPath() + if not os.path.exists(waf_conf): + return 'stop' + return 'start' + + +def start(): + initDreplace() + + import tool_task + tool_task.createBgTask() + + restartWeb() + return 'ok' + + +def stop(): + + makeOpDstStopLua() + + import tool_task + tool_task.removeBgTask() + + restartWeb() + return 'ok' + + +def restart(): + restartWeb() + return 'ok' + + +def reload(): + mw.opWeb('stop') + + makeOpDstRunLua(True) + autoMakeConfig(True, False) + + elog = mw.getServerDir() + "/openresty/nginx/logs/error.log" + if os.path.exists(elog): + mw.execShell('rm -rf ' + elog) + + mw.opWeb('start') + return 'ok' + +def reload_hook(): + s = status() + if s == 'start': + return reload() + return 'ok' + + +def getJsonPath(name): + path = getServerDir() + "/waf/" + name + ".json" + return path + + +def getRuleJsonPath(name): + path = getServerDir() + "/waf/rule/" + name + ".json" + return path + + +def getRule(): + args = getArgs() + data = checkArgs(args, ['rule_name']) + if not data[0]: + return data[1] + + rule_name = args['rule_name'] + fpath = getRuleJsonPath(rule_name) + content = mw.readFile(fpath) + return mw.returnJson(True, 'ok', content) + + +def addRule(): + args = getArgs() + data = checkArgs(args, ['ruleName', 'ruleValue', 'ps']) + if not data[0]: + return data[1] + + ruleValue = args['ruleValue'] + ruleName = args['ruleName'] + ps = args['ps'] + + fpath = getRuleJsonPath(ruleName) + content = mw.readFile(fpath) + content = json.loads(content) + + tmp_k = [] + tmp_k.append(1) + tmp_k.append(ruleValue) + tmp_k.append(ps) + tmp_k.append(1) + + content.append(tmp_k) + + cjson = mw.getJson(content) + mw.writeFile(fpath, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!', content) + + +def removeRule(): + args = getArgs() + data = checkArgs(args, ['ruleName', 'index']) + if not data[0]: + return data[1] + + index = int(args['index']) + ruleName = args['ruleName'] + + fpath = getRuleJsonPath(ruleName) + content = mw.readFile(fpath) + content = json.loads(content) + + k = content[index] + content.remove(k) + + cjson = mw.getJson(content) + mw.writeFile(fpath, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!', content) + + +def setRuleState(): + args = getArgs() + data = checkArgs(args, ['ruleName', 'index']) + if not data[0]: + return data[1] + + index = int(args['index']) + ruleName = args['ruleName'] + + fpath = getRuleJsonPath(ruleName) + content = mw.readFile(fpath) + content = json.loads(content) + + b = content[index][0] + if b == 1: + content[index][0] = 0 + else: + content[index][0] = 1 + + cjson = mw.getJson(content) + mw.writeFile(fpath, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!', content) + + +def modifyRule(): + args = getArgs() + data = checkArgs(args, ['index', 'ruleName', 'ruleBody', 'rulePs']) + if not data[0]: + return data[1] + + index = int(args['index']) + ruleName = args['ruleName'] + ruleBody = args['ruleBody'] + rulePs = args['rulePs'] + + fpath = getRuleJsonPath(ruleName) + content = mw.readFile(fpath) + content = json.loads(content) + + tmp = content[index] + + tmp_k = [] + tmp_k.append(tmp[0]) + tmp_k.append(ruleBody) + tmp_k.append(rulePs) + tmp_k.append(tmp[3]) + + content[index] = tmp_k + + cjson = mw.getJson(content) + mw.writeFile(fpath, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!', content) + + +def getSiteRule(): + args = getArgs() + data = checkArgs(args, ['siteName', 'ruleName']) + if not data[0]: + return data[1] + + siteName = args['siteName'] + siteRule = args['ruleName'] + + path = getJsonPath('site') + content = mw.readFile(path) + content = json.loads(content) + + r = content[siteName][siteRule] + + cjson = mw.getJson(r) + return mw.returnJson(True, 'ok!', cjson) + + +def addSiteRule(): + args = getArgs() + data = checkArgs(args, ['siteName', 'ruleName', 'ruleValue']) + if not data[0]: + return data[1] + + siteName = args['siteName'] + siteRule = args['ruleName'] + ruleValue = args['ruleValue'] + + path = getJsonPath('site') + content = mw.readFile(path) + content = json.loads(content) + + content[siteName][siteRule].append(ruleValue) + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def addIpWhite(): + args = getArgs() + data = checkArgs(args, ['start_ip', 'end_ip']) + if not data[0]: + return data[1] + + start_ip = args['start_ip'] + end_ip = args['end_ip'] + + path = getRuleJsonPath('ip_white') + content = mw.readFile(path) + content = json.loads(content) + + data = [] + + start_ip_list = start_ip.split('.') + tmp = [] + for x in range(len(start_ip_list)): + tmp.append(int(start_ip_list[x])) + + end_ip_list = end_ip.split('.') + tmp2 = [] + for x in range(len(end_ip_list)): + tmp2.append(int(end_ip_list[x])) + + data.append(tmp) + data.append(tmp2) + + content.append(data) + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def removeIpWhite(): + args = getArgs() + data = checkArgs(args, ['index']) + if not data[0]: + return data[1] + + index = args['index'] + + path = getRuleJsonPath('ip_white') + content = mw.readFile(path) + content = json.loads(content) + + k = content[int(index)] + content.remove(k) + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def addIpBlack(): + args = getArgs() + data = checkArgs(args, ['start_ip', 'end_ip']) + if not data[0]: + return data[1] + + start_ip = args['start_ip'] + end_ip = args['end_ip'] + + path = getRuleJsonPath('ip_black') + content = mw.readFile(path) + content = json.loads(content) + + data = [] + + start_ip_list = start_ip.split('.') + tmp = [] + for x in range(len(start_ip_list)): + tmp.append(int(start_ip_list[x])) + + end_ip_list = end_ip.split('.') + tmp2 = [] + for x in range(len(end_ip_list)): + tmp2.append(int(end_ip_list[x])) + + data.append(tmp) + data.append(tmp2) + + content.append(data) + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def removeIpBlack(): + args = getArgs() + data = checkArgs(args, ['index']) + if not data[0]: + return data[1] + + index = args['index'] + + path = getRuleJsonPath('ip_black') + content = mw.readFile(path) + content = json.loads(content) + + k = content[int(index)] + content.remove(k) + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def setIpv6Black(): + args = getArgs() + data = checkArgs(args, ['addr']) + if not data[0]: + return data[1] + + addr = args['addr'].replace('_', ':') + path = getRuleJsonPath('ipv6_black') + + content = mw.readFile(path) + content = json.loads(content) + content.append(addr) + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def delIpv6Black(): + args = getArgs() + data = checkArgs(args, ['addr']) + if not data[0]: + return data[1] + + addr = args['addr'].replace('_', ':') + path = getRuleJsonPath('ipv6_black') + + content = mw.readFile(path) + content = json.loads(content) + + content.remove(addr) + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def removeSiteRule(): + args = getArgs() + data = checkArgs(args, ['siteName', 'ruleName', 'index']) + if not data[0]: + return data[1] + + siteName = args['siteName'] + siteRule = args['ruleName'] + index = args['index'] + + path = getJsonPath('site') + content = mw.readFile(path) + content = json.loads(content) + + ruleValue = content[siteName][siteRule][int(index)] + content[siteName][siteRule].remove(ruleValue) + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def setObjStatus(): + args = getArgs() + data = checkArgs(args, ['obj', 'statusCode']) + if not data[0]: + return data[1] + + conf = getJsonPath('config') + content = mw.readFile(conf) + cobj = json.loads(content) + + o = args['obj'] + status = int(args['statusCode']) + cobj[o]['status'] = status + + cjson = mw.getJson(cobj) + mw.writeFile(conf, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def setRetry(): + args = getArgs() + data = checkArgs(args, ['retry', 'retry_time', + 'retry_cycle', 'is_open_global']) + if not data[0]: + return data[1] + + conf = getJsonPath('config') + content = mw.readFile(conf) + cobj = json.loads(content) + + ## 修复数据类型错误 + tmp = args + tmp['retry'] = int(tmp['retry']) + tmp['retry_time'] = int(tmp['retry_time']) + tmp['retry_cycle'] = int(tmp['retry_cycle']) + + cobj['retry'] = tmp + cjson = mw.getJson(cobj) + mw.writeFile(conf, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!', []) + + +def setSafeVerify(): + args = getArgs() + data = checkArgs(args, ['auto', 'time', 'cpu', 'mode']) + if not data[0]: + return data[1] + + conf = getJsonPath('config') + content = mw.readFile(conf) + cobj = json.loads(content) + + cobj['safe_verify']['time'] = args['time'] + cobj['safe_verify']['cpu'] = int(args['cpu']) + cobj['safe_verify']['mode'] = args['mode'] + + if args['auto'] == '0': + cobj['safe_verify']['auto'] = False + else: + cobj['safe_verify']['auto'] = True + + cjson = mw.getJson(cobj) + mw.writeFile(conf, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!', []) + + +def setSiteRetry(): + return mw.returnJson(True, '设置成功-?!', []) + + +def setCcConf(): + args = getArgs() + data = checkArgs(args, ['siteName', 'cycle', 'limit', + 'endtime', 'is_open_global']) + if not data[0]: + return data[1] + + conf = getJsonPath('config') + content = mw.readFile(conf) + cobj = json.loads(content) + + tmp = cobj['cc'] + + tmp['cycle'] = int(args['cycle']) + tmp['limit'] = int(args['limit']) + tmp['endtime'] = int(args['endtime']) + tmp['is_open_global'] = args['is_open_global'] + tmp['increase'] = args['increase'] + cobj['cc'] = tmp + + cjson = mw.getJson(cobj) + mw.writeFile(conf, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!', []) + + +def setSiteCcConf(): + return mw.returnJson(False, '暂未开发!', []) + + +def saveScanRule(): + args = getArgs() + data = checkArgs(args, ['header', 'cookie', 'args']) + if not data[0]: + return data[1] + + path = getRuleJsonPath('scan_black') + cjson = mw.getJson(args) + mw.writeFile(path, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '设置成功!', []) + + +def getSiteConfig(): + path = getJsonPath('site') + content = mw.readFile(path) + + content = json.loads(content) + + total = getJsonPath('total') + total_content = mw.readFile(total) + total_content = json.loads(total_content) + + # print total_content + + for x in content: + tmp = [] + tmp_v = {} + if 'sites' in total_content and x in total_content['sites']: + tmp_v = total_content['sites'][x] + + key_list = ['get', 'post', 'user-agent', 'cookie', 'cdn', 'cc'] + for kx in range(len(key_list)): + ktmp = {} + + if kx in tmp_v: + ktmp['value'] = tmp_v[key_list[kx]] + else: + ktmp['value'] = '' + ktmp['key'] = key_list[kx] + tmp.append(ktmp) + + # print tmp + content[x]['total'] = tmp + + content = mw.getJson(content) + return mw.returnJson(True, 'ok!', content) + + +def getSiteConfigByName(): + args = getArgs() + data = checkArgs(args, ['siteName']) + if not data[0]: + return data[1] + path = getJsonPath('site') + content = mw.readFile(path) + content = json.loads(content) + + siteName = args['siteName'] + retData = {} + if siteName in content: + retData = content[siteName] + + return mw.returnJson(True, 'ok!', retData) + + +def addSiteCdnHeader(): + args = getArgs() + data = checkArgs(args, ['siteName', 'cdn_header']) + if not data[0]: + return data[1] + path = getJsonPath('site') + content = mw.readFile(path) + content = json.loads(content) + + siteName = args['siteName'] + retData = {} + if siteName in content: + content[siteName]['cdn_header'].append(args['cdn_header']) + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '添加成功!') + + +def removeSiteCdnHeader(): + args = getArgs() + data = checkArgs(args, ['siteName', 'cdn_header']) + if not data[0]: + return data[1] + path = getJsonPath('site') + content = mw.readFile(path) + content = json.loads(content) + + siteName = args['siteName'] + retData = {} + if siteName in content: + content[siteName]['cdn_header'].remove(args['cdn_header']) + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + + setConfRestartWeb() + return mw.returnJson(True, '删除成功!') + + +def outputData(): + args = getArgs() + data = checkArgs(args, ['sname']) + if not data[0]: + return data[1] + + path = getRuleJsonPath(args['sname']) + content = mw.readFile(path) + return mw.returnJson(True, 'ok', content) + + +def importData(): + args = getArgs() + data = checkArgs(args, ['sname', 'pdata']) + if not data[0]: + return data[1] + + path = getRuleJsonPath(args['sname']) + + source_data = mw.readFile(path) + source_data = json.loads(source_data) + + save_data = [] + save_data.append(source_data[0]) + pdata = args['pdata'].strip() + try: + pdata = json.loads(pdata) + mw.writeFile(path, json.dumps(pdata)) + except Exception as e: + pdata = pdata.split("\\n") + for x in pdata: + pval = x.strip() + if pval != "": + vv = json.loads(pval) + save_data.append(vv[0]) + mw.writeFile(path, json.dumps(save_data)) + # restartWeb() + return mw.returnJson(True, '设置成功!') + + +def getLogsList(): + args = getArgs() + data = checkArgs(args, ['site', 'page', 'page_size', 'tojs']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + domain = args['site'] + tojs = args['tojs'] + + setDefaultSite(domain) + + conn = pSqliteDb('logs') + + field = 'time,ip,domain,server_name,method,uri,user_agent,rule_name,reason' + limit = str(page_size) + ' offset ' + str(page_size * (page - 1)) + + condition = '' + conn = conn.field(field) + conn = conn.where("1=1", ()).where("domain=?", (domain,)) + + clist = conn.limit(limit).order('time desc').inquiry() + count_key = "count(*) as num" + count = conn.field(count_key).limit('').order('').inquiry() + # print(count) + count = count[0][count_key] + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = tojs + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.returnJson(True, 'ok!', data) + + +def getSafeLogs(): + args = getArgs() + data = checkArgs(args, ['siteName', 'toDate', 'p']) + if not data[0]: + return data[1] + + path = getServerDir() + '/logs' + file = path + '/' + args['siteName'] + '_' + args['toDate'] + '.log' + if not os.path.exists(file): + return mw.returnJson(False, "文件不存在!") + + retData = [] + file = open(file) + while 1: + lines = file.readlines(100000) + if not lines: + break + for line in lines: + + retData.append(json.loads(line)) + + return mw.returnJson(True, '设置成功!', retData) + + +def setObjOpen(): + args = getArgs() + data = checkArgs(args, ['obj']) + if not data[0]: + return data[1] + + conf = getJsonPath('config') + content = mw.readFile(conf) + cobj = json.loads(content) + + o = args['obj'] + if cobj[o]["open"]: + cobj[o]["open"] = False + else: + cobj[o]["open"] = True + + cjson = mw.getJson(cobj) + mw.writeFile(conf, cjson) + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def setSiteObjOpen(): + args = getArgs() + data = checkArgs(args, ['siteName', 'obj']) + if not data[0]: + return data[1] + + siteName = args['siteName'] + obj = args['obj'] + + path = getJsonPath('site') + content = mw.readFile(path) + content = json.loads(content) + + if type(content[siteName][obj]) == bool: + if content[siteName][obj]: + content[siteName][obj] = False + else: + content[siteName][obj] = True + else: + if content[siteName][obj]['open']: + content[siteName][obj]['open'] = False + else: + content[siteName][obj]['open'] = True + + cjson = mw.getJson(content) + mw.writeFile(path, cjson) + setConfRestartWeb() + return mw.returnJson(True, '设置成功!') + + +def getWafSrceen(): + conf = getJsonPath('total') + return mw.readFile(conf) + + +def getWafConf(): + conf = getJsonPath('config') + return mw.readFile(conf) + + +def areaLimitSwitch(): + args = getArgs() + data = checkArgs(args, ['area_limit']) + if not data[0]: + return data[1] + + path_config = getJsonPath('config') + + config_contents = mw.readFile(path_config) + config_contents = json.loads(config_contents) + + msg = '关闭成功!' + if args['area_limit'] == 'on': + msg = '开启成功!' + config_contents['area_limit'] = True + else: + config_contents['area_limit'] = False + + mw.writeFile(path_config, json.dumps(config_contents)) + + autoMakeConfig(True, True) + restart() + return mw.returnJson(True, msg) + + +def getAreaLimit(): + conf = getJsonPath('area_limit') + if not os.path.exists(conf): + mw.writeFile(conf, '[]') + + d = mw.readFile(conf) + data = json.loads(d) + return mw.returnJson(True, 'ok!', data) + + +def delAreaLimit(): + args = getArgs() + data = checkArgs(args, ['site', 'types', 'region']) + if not data[0]: + return data[1] + + type_list = ["refuse", "accept"] + if not args['types'] in type_list: + return mw.returnJson(False, '输入的类型错误!') + + region_l = args['region'].split(",") + site_l = args['site'].split(",") + + paramMode = {} + for i in region_l: + if not i: + continue + i = i.strip() + if not i in paramMode: + paramMode[i] = "1" + + sitesMode = {} + for i in site_l: + i = i.strip() + if not i: + continue + + if not i in sitesMode: + sitesMode[i] = "1" + + if len(paramMode) == 0: + return mw.returnJson(False, '输入的请求类型错误!') + if len(sitesMode) == 0: + return mw.returnJson(False, '输入的站点错误!') + + conf = getJsonPath('area_limit') + t_data = json.loads(mw.readFile(conf)) + + data = {"site": sitesMode, "types": args['types'], "region": paramMode} + if not data in t_data: + return mw.returnJson(False, '不存在!') + + t_data.remove(data) + mw.writeFile(conf, json.dumps(t_data)) + + setConfRestartWeb() + return mw.returnJson(True, '删除成功!') + + +def addAreaLimit(): + args = getArgs() + data = checkArgs(args, ['site', 'types', 'region']) + if not data[0]: + return data[1] + + type_list = ["refuse", "accept"] + if not args['types'] in type_list: + return mw.returnJson(False, '输入的类型错误!') + + region_l = args['region'].split(",") + site_l = args['site'].split(",") + + paramMode = {} + for i in region_l: + if not i: + continue + i = i.strip() + if not i in paramMode: + paramMode[i] = "1" + + if '海外' in paramMode and '中国' in paramMode: + return mw.returnJson(False, '不允许设置【中国大陆】和【中国大陆以外地区】一同开启地区限制!') + + sitesMode = {} + for i in site_l: + i = i.strip() + if not i: + continue + + if not i in sitesMode: + sitesMode[i] = "1" + + if len(paramMode) == 0: + return mw.returnJson(False, '输入的请求类型错误!') + if len(sitesMode) == 0: + return mw.returnJson(False, '输入的站点错误!') + + conf = getJsonPath('area_limit') + t_data = json.loads(mw.readFile(conf)) + + data = {"site": sitesMode, "types": args['types'], "region": paramMode} + if data in t_data: + return mw.returnJson(False, '已存在!') + + t_data.insert(0, data) + mw.writeFile(conf, json.dumps(t_data)) + + setConfRestartWeb() + return mw.returnJson(True, '添加成功!') + + +def cleanDropIp(): + url = "http://127.0.0.1/clean_waf_drop_ip" + data = mw.httpGet(url) + return mw.returnJson(True, 'ok!', data) + + +def testRun(): + # args = getArgs() + # data = checkArgs(args, ['siteName']) + # if not data[0]: + # return data[1] + + default_path = getServerDir() + "/waf/default.pl" + default_site = mw.readFile(default_path) + url = "http://" + default_site + '/?t=../etc/passwd' + returnData = mw.httpGet(url, 10) + + # url = "https://" + default_site + '/?t=../etc/passwd' + # returnData = mw.httpGet(url, 3) + return mw.returnJson(True, '测试运行成功!', returnData) + + +def installPreInspection(): + check_op = mw.getServerDir() + "/openresty" + if not os.path.exists(check_op): + return "请先安装OpenResty" + return 'ok' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'get_rule': + print(getRule()) + elif func == 'add_rule': + print(addRule()) + elif func == 'remove_rule': + print(removeRule()) + elif func == 'set_rule_state': + print(setRuleState()) + elif func == 'modify_rule': + print(modifyRule()) + elif func == 'get_site_rule': + print(getSiteRule()) + elif func == 'add_site_rule': + print(addSiteRule()) + elif func == 'add_ip_white': + print(addIpWhite()) + elif func == 'remove_ip_white': + print(removeIpWhite()) + elif func == 'add_ip_black': + print(addIpBlack()) + elif func == 'remove_ip_black': + print(removeIpBlack()) + elif func == 'set_ipv6_black': + print(setIpv6Black()) + elif func == 'del_ipv6_black': + print(delIpv6Black()) + elif func == 'remove_site_rule': + print(removeSiteRule()) + elif func == 'set_obj_status': + print(setObjStatus()) + elif func == 'set_obj_open': + print(setObjOpen()) + elif func == 'set_site_obj_open': + print(setSiteObjOpen()) + elif func == 'set_cc_conf': + print(setCcConf()) + elif func == 'set_site_cc_conf': + print(setSiteCcConf()) + elif func == 'set_retry': + print(setRetry()) + elif func == 'set_safe_verify': + print(setSafeVerify()) + elif func == 'set_site_retry': + print(setSiteRetry()) + elif func == 'save_scan_rule': + print(saveScanRule()) + elif func == 'get_site_config': + print(getSiteConfig()) + elif func == 'get_default_site': + print(getDefaultSite()) + elif func == 'get_country': + print(getCountry()) + elif func == 'get_site_config_byname': + print(getSiteConfigByName()) + elif func == 'add_site_cdn_header': + print(addSiteCdnHeader()) + elif func == 'remove_site_cdn_header': + print(removeSiteCdnHeader()) + elif func == 'get_logs_list': + print(getLogsList()) + elif func == 'get_safe_logs': + print(getSafeLogs()) + elif func == 'output_data': + print(outputData()) + elif func == 'import_data': + print(importData()) + elif func == 'waf_srceen': + print(getWafSrceen()) + elif func == 'waf_conf': + print(getWafConf()) + elif func == 'area_limit_switch': + print(areaLimitSwitch()) + elif func == 'get_area_limit': + print(getAreaLimit()) + elif func == 'add_area_limit': + print(addAreaLimit()) + elif func == 'del_area_limit': + print(delAreaLimit()) + elif func == 'clean_drop_ip': + print(cleanDropIp()) + elif func == 'test_run': + print(testRun()) + else: + print('error') diff --git a/plugins/op_waf/info.json b/plugins/op_waf/info.json new file mode 100755 index 000000000..fe18198c7 --- /dev/null +++ b/plugins/op_waf/info.json @@ -0,0 +1,29 @@ +{ + "hook":[ + { + "tag":"site_cb", + "site_cb": { + "title":"网站统计", + "name":"op_waf", + "add":{"func":"reload_hook"}, + "update":{"func":"reload_hook"}, + "delete":{"func":"reload_hook"} + } + } + ], + "sort":2, + "title":"OP防火墙", + "tip":"soft", + "name":"op_waf", + "type":"其他插件", + "ps":"有效防止sql注入/xss/一句话木马等常见渗透攻击", + "install_pre_inspection":true, + "shell":"install.sh", + "checks":"server/op_waf", + "path":"server/op_waf", + "author":"loveshell", + "home":"https://github.com/loveshell/ngx_lua_waf", + "date":"2019-04-21", + "pid": "1", + "versions": ["0.4.1"] +} \ No newline at end of file diff --git a/plugins/op_waf/install.old.sh b/plugins/op_waf/install.old.sh new file mode 100755 index 000000000..51ba267e4 --- /dev/null +++ b/plugins/op_waf/install.old.sh @@ -0,0 +1,141 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /www/server/mdserver-web/plugins/op_waf && bash install.sh install 0.4.1 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +action=$1 +version=$2 +sys_os=`uname` + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +if [ "$sys_os" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +# /www/server/openresty/luajit/bin/luajit /www/server/op_waf/waf/lua/waf_common.lua +# /www/server/openresty/luajit/bin/luajit -bl /www/server/op_waf/waf/lua/waf_common.lua +# /www/server/openresty/luajit/bin/luajit /www/server/web_conf/nginx/lua/access_by_lua_file.lua + + +Install_App(){ + + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/op_waf + mkdir -p $serverPath/op_waf + + # luarocks + if [ ! -f $serverPath/source/op_waf/luarocks-3.5.0.tar.gz ];then + wget --no-check-certificate -O $serverPath/source/op_waf/luarocks-3.5.0.tar.gz http://luarocks.org/releases/luarocks-3.5.0.tar.gz + fi + + # which luarocks + if [ ! -d $serverPath/op_waf/luarocks ];then + cd $serverPath/source/op_waf && tar xvf luarocks-3.5.0.tar.gz + # cd luarocks-3.9.1 && ./configure && make bootstrap + + cd luarocks-3.5.0 && ./configure --prefix=$serverPath/op_waf/luarocks \ + --with-lua-include=$serverPath/openresty/luajit/include/luajit-2.1 \ + --with-lua-bin=$serverPath/openresty/luajit/bin + make -I${serverPath}/openresty/luajit/bin + make install + fi + + + if [ ! -f $serverPath/source/op_waf/lsqlite3_fsl09y.zip ];then + wget --no-check-certificate -O $serverPath/source/op_waf/lsqlite3_fsl09y.zip http://lua.sqlite.org/index.cgi/zip/lsqlite3_fsl09y.zip?uuid=fsl_9y + cd $serverPath/source/op_waf && unzip lsqlite3_fsl09y.zip + fi + + if [ ! -d $serverPath/source/op_waf/lsqlite3_fsl09y ];then + cd $serverPath/source/op_waf && unzip lsqlite3_fsl09y.zip + fi + + PATH=${serverPath}/openresty/luajit:${serverPath}/openresty/luajit/include/luajit-2.1:$PATH + export PATH=$PATH:$serverPath/op_waf/luarocks/bin + + if [ ! -f $serverPath/op_waf/waf/conf/lsqlite3.so ];then + if [ "${sys_os}" == "Darwin" ];then + cd $serverPath/source/op_waf/lsqlite3_fsl09y + find_cfg=`cat Makefile | grep 'SQLITE_DIR'` + if [ "$find_cfg" == "" ];then + LIB_SQLITE_DIR=`brew info sqlite | grep /usr/local/Cellar/sqlite | cut -d \ -f 1 | awk 'END {print}'` + echo $LIB_SQLITE_DIR + sed -i $BAK "s#\$(ROCKSPEC)#\$(ROCKSPEC) SQLITE_DIR=${LIB_SQLITE_DIR}#g" Makefile + fi + make + else + cd $serverPath/source/op_waf/lsqlite3_fsl09y && make + fi + fi + + # copy to code path + DEFAULT_DIR=$serverPath/op_waf/luarocks/lib/lua/5.1 + if [ -f ${DEFAULT_DIR}/lsqlite3.so ];then + mkdir -p $serverPath/op_waf/waf/conf + cp -rf ${DEFAULT_DIR}/lsqlite3.so $serverPath/op_waf/waf/conf/lsqlite3.so + fi + + cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") + HTTP_PREFIX="https://" + if [ ! -z "$cn" ];then + HTTP_PREFIX="https://mirror.ghproxy.com/" + fi + + # download GeoLite Data + GeoLite2_TAG=`curl -sL "https://api.github.com/repos/P3TERX/GeoLite.mmdb/releases/latest" | grep '"tag_name":' | cut -d'"' -f4` + #if [ ! -f $serverPath/op_waf/GeoLite2-City.mmdb ];then + wget --no-check-certificate -O $serverPath/op_waf/GeoLite2-City.mmdb ${HTTP_PREFIX}github.com/P3TERX/GeoLite.mmdb/releases/download/${GeoLite2_TAG}/GeoLite2-City.mmdb + #fi + + #if [ ! -f $serverPath/op_waf/GeoLite2-Country.mmdb ];then + wget --no-check-certificate -O $serverPath/op_waf/GeoLite2-Country.mmdb ${HTTP_PREFIX}github.com/P3TERX/GeoLite.mmdb/releases/download/${GeoLite2_TAG}/GeoLite2-Country.mmdb + #fi + + libmaxminddb_ver='1.7.1' + if [ ! -f $serverPath/op_waf/waf/mmdb/lib/libmaxminddb.a ] && [ ! -f $serverPath/op_waf/waf/mmdb/lib/libmaxminddb.so ];then + libmaxminddb_local_path=$serverPath/source/op_waf/libmaxminddb-${libmaxminddb_ver}.tar.gz + libmaxminddb_url_path=${HTTP_PREFIX}github.com/maxmind/libmaxminddb/releases/download/${libmaxminddb_ver}/libmaxminddb-${libmaxminddb_ver}.tar.gz + if [ ! -f ${libmaxminddb_local_path} ]; then + wget --no-check-certificate -O ${libmaxminddb_local_path} ${libmaxminddb_url_path} + fi + + cd $serverPath/source/op_waf && tar -zxvf ${libmaxminddb_local_path} && \ + cd $serverPath/source/op_waf/libmaxminddb-${libmaxminddb_ver} && \ + ./configure --prefix=$serverPath/op_waf/waf/mmdb && make && make install + fi + + echo "${version}" > $serverPath/op_waf/version.pl + echo '安装OP防火墙成功!' + + cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py start + echo "cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py start" + sleep 2 + cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py reload +} + +Uninstall_App(){ + + cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py stop + if [ "$?" == "0" ];then + rm -rf $serverPath/op_waf + fi +} + + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/op_waf/install.sh b/plugins/op_waf/install.sh new file mode 100755 index 000000000..58d8578fb --- /dev/null +++ b/plugins/op_waf/install.sh @@ -0,0 +1,155 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /www/server/mdserver-web/plugins/op_waf && bash install.sh install 0.4.1 +# cd /www/server/mdserver-web && python3 plugins/op_waf/index.py start +# cd /www/server/mdserver-web && python3 plugins/op_waf/index.py stop +# cd /www/server/mdserver-web && python3 plugins/op_waf/tool_task.py run + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +action=$1 +version=$2 +sys_os=`uname` + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +if [ "$sys_os" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +# /www/server/openresty/luajit/bin/luajit /www/server/op_waf/waf/lua/waf_common.lua +# /www/server/openresty/luajit/bin/luajit -bl /www/server/op_waf/waf/lua/waf_common.lua +# /www/server/openresty/luajit/bin/luajit /www/server/web_conf/nginx/lua/access_by_lua_file.lua + + +Install_App(){ + + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/op_waf + mkdir -p $serverPath/op_waf + + # luarocks + if [ ! -f $serverPath/source/op_waf/luarocks-3.5.0.tar.gz ];then + wget --no-check-certificate -O $serverPath/source/op_waf/luarocks-3.5.0.tar.gz http://luarocks.org/releases/luarocks-3.5.0.tar.gz + fi + + # which luarocks + if [ ! -d $serverPath/op_waf/luarocks ];then + cd $serverPath/source/op_waf && tar xvf luarocks-3.5.0.tar.gz + # cd luarocks-3.9.1 && ./configure && make bootstrap + + cd luarocks-3.5.0 && ./configure --prefix=$serverPath/op_waf/luarocks \ + --with-lua-include=$serverPath/openresty/luajit/include/luajit-2.1 \ + --with-lua-bin=$serverPath/openresty/luajit/bin + make -I${serverPath}/openresty/luajit/bin + make install + fi + + + # if [ ! -f $serverPath/source/op_waf/lsqlite3_v096.zip ];then + # wget --no-check-certificate -O $serverPath/source/op_waf/lsqlite3_v096.zip http://lua.sqlite.org/home/zip/lsqlite3_v096.zip?uuid=v0.9.6 + # fi + + if [ ! -f $serverPath/source/op_waf/lsqlite3_v096.zip ];then + wget --no-check-certificate -O $serverPath/source/op_waf/lsqlite3_v096.zip https://github.com/midoks/mdserver-web/releases/download/0.18.4/lsqlite3_v096.zip + fi + + if [ ! -d $serverPath/source/op_waf/lsqlite3_v096 ];then + cd $serverPath/source/op_waf && unzip lsqlite3_v096.zip + fi + + PATH=${serverPath}/openresty/luajit:${serverPath}/openresty/luajit/include/luajit-2.1:$PATH + export PATH=$PATH:$serverPath/op_waf/luarocks/bin + + if [ ! -f $serverPath/op_waf/waf/conf/lsqlite3.so ];then + if [ "${sys_os}" == "Darwin" ];then + cd $serverPath/source/op_waf/lsqlite3_v096 + find_cfg=`cat Makefile | grep 'SQLITE_DIR'` + if [ "$find_cfg" == "" ];then + LIB_SQLITE_DIR=`brew info sqlite | grep /opt/homebrew/Cellar/sqlite | cut -d \ -f 1 | awk 'END {print}'` + echo $LIB_SQLITE_DIR + sed -i $BAK "s#\$(ROCKSPEC)#\$(ROCKSPEC) SQLITE_DIR=${LIB_SQLITE_DIR}#g" Makefile + fi + make + else + cd $serverPath/source/op_waf/lsqlite3_v096 && make + fi + fi + + # copy to code path + DEFAULT_DIR=$serverPath/op_waf/luarocks/lib/lua/5.1 + if [ -f ${DEFAULT_DIR}/lsqlite3.so ];then + mkdir -p $serverPath/op_waf/waf/conf + cp -rf ${DEFAULT_DIR}/lsqlite3.so $serverPath/op_waf/waf/conf/lsqlite3.so + fi + + cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") + HTTP_PREFIX="https://" + if [ ! -z "$cn" ];then + HTTP_PREFIX="https://gh-proxy.com/" + fi + + # download GeoLite Data + GeoLite2_TAG=`curl -sL "https://api.github.com/repos/P3TERX/GeoLite.mmdb/releases/latest" | grep '"tag_name":' | cut -d'"' -f4` + if [ ! -f $serverPath/source/op_waf/GeoLite2-City.mmdb ];then + wget --no-check-certificate -O $serverPath/source/op_waf/GeoLite2-City.mmdb ${HTTP_PREFIX}github.com/P3TERX/GeoLite.mmdb/releases/download/${GeoLite2_TAG}/GeoLite2-City.mmdb + fi + + if [ ! -f $serverPath/op_waf/GeoLite2-City.mmdb ];then + cp -rf $serverPath/source/op_waf/GeoLite2-City.mmdb $serverPath/op_waf/GeoLite2-City.mmdb + fi + + if [ ! -f $serverPath/source/op_waf/GeoLite2-Country.mmdb ];then + wget --no-check-certificate -O $serverPath/source/op_waf/GeoLite2-Country.mmdb ${HTTP_PREFIX}github.com/P3TERX/GeoLite.mmdb/releases/download/${GeoLite2_TAG}/GeoLite2-Country.mmdb + fi + + if [ ! -f $serverPath/op_waf/GeoLite2-Country.mmdb ];then + cp -rf $serverPath/source/op_waf/GeoLite2-Country.mmdb $serverPath/op_waf/GeoLite2-Country.mmdb + fi + + + libmaxminddb_ver='1.12.2' + if [ ! -f $serverPath/op_waf/waf/mmdb/lib/libmaxminddb.a ] && [ ! -f $serverPath/op_waf/waf/mmdb/lib/libmaxminddb.so ];then + libmaxminddb_local_path=$serverPath/source/op_waf/libmaxminddb-${libmaxminddb_ver}.tar.gz + libmaxminddb_url_path=${HTTP_PREFIX}github.com/maxmind/libmaxminddb/releases/download/${libmaxminddb_ver}/libmaxminddb-${libmaxminddb_ver}.tar.gz + if [ ! -f ${libmaxminddb_local_path} ]; then + wget --no-check-certificate -O ${libmaxminddb_local_path} ${libmaxminddb_url_path} + fi + + cd $serverPath/source/op_waf && tar -zxvf ${libmaxminddb_local_path} && \ + cd $serverPath/source/op_waf/libmaxminddb-${libmaxminddb_ver} && \ + ./configure --prefix=$serverPath/op_waf/waf/mmdb && make && make install + fi + + echo "${version}" > $serverPath/op_waf/version.pl + echo '安装OP防火墙成功!' + + cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py start + echo "cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py start" + sleep 2 + cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py reload +} + +Uninstall_App(){ + cd ${rootPath} && python3 ${rootPath}/plugins/op_waf/index.py stop + if [ "$?" == "0" ];then + rm -rf $serverPath/op_waf + fi +} + + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/op_waf/js/op_waf.js b/plugins/op_waf/js/op_waf.js new file mode 100755 index 000000000..5440c5a69 --- /dev/null +++ b/plugins/op_waf/js/op_waf.js @@ -0,0 +1,2105 @@ + +function owPost(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'op_waf', func:method, args:JSON.stringify(args)}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function owPostN(method, args, callback){ + $.post('/plugins/run', {name:'op_waf', func:method, args:JSON.stringify(args)}, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function getRuleByName(rule_name, callback){ + owPost('get_rule', {rule_name:rule_name}, function(data){ + callback(data); + }); +} + + +function setRequestCode(ruleName, statusCode){ + layer.open({ + type: 1, + title: "设置响应代码【" + ruleName + "】", + area: '300px', + shift: 5, + closeBtn: 1, + shadeClose: true, + content: '
                                \ +
                                \ + 响应代码\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + \ +
                                \ +
                                ' + }); +} + +function setState(ruleName){ + var statusCode = $('#statusCode').val(); + owPost('set_obj_status', {obj:ruleName,statusCode:statusCode},function(data){ + var rdata = $.parseJSON(data.data); + if (rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + wafGloabl(); + } else { + layer.msg('设置失败!',{icon:0,time:2000,shade: [0.3, '#000']}); + } + }); +} + +function setObjOpen(ruleName){ + owPost('set_obj_open', {obj:ruleName},function(data){ + var rdata = $.parseJSON(data.data); + if (rdata.status){ + + showMsg(rdata.msg, function(){ + wafGloabl(); + },{icon:1,time:2000,shade: [0.3, '#000']},2000); + } else { + layer.msg('设置失败!',{icon:0,time:2000,shade: [0.3, '#000']}); + } + }); +} + + +//保存CC规则 +function saveCcRule(siteName,is_open_global, type) { + var increase = "0"; + if(type == 2){ + // set_aicc_open('start'); + increase = "0"; + } else { + // set_aicc_open('stop'); + increase = type; + } + increase = "0"; + var pdata = { + siteName:siteName, + cycle: $("input[name='cc_cycle']").val(), + limit: $("input[name='cc_limit']").val(), + endtime: $("input[name='cc_endtime']").val(), + is_open_global:is_open_global, + increase:increase + } + console.log(pdata); + var act = 'set_cc_conf'; + if (siteName != 'undefined') act = 'set_site_cc_conf'; + + owPost(act, pdata, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + setTimeout(function(){ + if (siteName != 'undefined') { + siteWafConfig(siteName, 1); + } else { + wafGloabl(); + } + },1000); + }); +} + + +function setCcRule(cycle, limit, endtime, siteName, increase){ + var incstr = '
                              • 此处设置仅对当前站点有效。
                              • '; + if (siteName == 'undefined') { + incstr = '
                              • 此处设置的是初始值,新添加站点时将继承,对现有站点无效。
                              • '; + } + //
                                \ + // 增强模式\ + //
                                \ + // \ + //
                                \ + //
                                \ + //
                                \ + // 四层防御\ + //
                                \ + // \ + //
                                \ + //
                                \ + //
                              • 增强模式:CC防御加强版,开启后可能会影响用户体验,建议在用户受到CC攻击时开启。
                              • \ + + create_l = layer.open({ + type: 1, + title: "设置CC规则", + area: '540px', + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + 周期\ +
                                \ +
                                \ +
                                \ + 频率\ +
                                \ +
                                \ +
                                \ + 封锁时间\ +
                                \ +
                                \ +
                                  '+ incstr + '\ +
                                • '+ cycle + ' 秒内累计请求同一URL超过 ' + limit + ' 次,触发CC防御,封锁此IP ' + endtime + '
                                • \ +
                                • 请不要设置过于严格的CC规则,以免影响正常用户体验
                                • \ +
                                • 全局应用:全局设置当前CC规则,且覆盖当前全部站点的CC规则
                                • \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                ', + success:function(layero,index){ + $('.btn_cc_all').click(function(){ + saveCcRule(siteName,1,$('[name="enhance_mode"]').val()); + }); + $('.btn_cc_present').click(function(){ + saveCcRule(siteName,0,$('[name="enhance_mode"]').val()); + }); + } + }); +} + + +//设置retry规则 +function setRetry(retry_cycle, retry, retry_time, siteName) { + create_layer = layer.open({ + type: 1, + title: "设置恶意容忍规则", + area: '500px', + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + 周期\ +
                                \ +
                                \ +
                                \ + 频率\ +
                                \ +
                                \ +
                                \ + 封锁时间\ +
                                \ +
                                \ +
                                  \ +
                                • '+ retry_cycle + ' 秒内累计恶意请求超过 ' + retry + ' 次,封锁 ' + retry_time + '
                                • \ +
                                • 全局应用:全局设置当前恶意容忍规则,且覆盖当前全部站点的恶意容忍规则
                                • \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                ', + success:function(){ + $('.btn_retry_all').click(function(){ + saveRetry(siteName,1); + }); + $('.btn_retry_present').click(function(){ + saveRetry(siteName,0); + }); + } + }); +} + + + +//设置safe_verify规则 +function setSafeVerify(auto, cpu, time, mode,siteName) { + var svlayer = layer.open({ + type: 1, + title: "设置强制安全验证", + area: '500px', + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + CPU\ +
                                %
                                \ +
                                \ +
                                \ + 通行时间\ +
                                \ + 秒\ +
                                \ +
                                \ +
                                \ + 验证模式\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 开启自动\ +
                                \ + \ +
                                \ +
                                \ +
                                  \ +
                                • 全局设置强制安全验证
                                • \ +
                                • 开启自动后:cpu超过['+cpu+'%]后,强制验证。
                                • \ +
                                \ +
                                \ + \ +
                                \ +
                                ', + success:function(index){ + $('.btn_sv_present').click(function(){ + var pdata = { + siteName: siteName, + cpu: $("input[name='cpu']").val(), + auto: $("select[name='auto']").val(), + mode: $("select[name='mode']").val(), + time: $("input[name='time']").val(), + } + var act = 'set_safe_verify'; + owPost(act, pdata, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function() { + layer.close(svlayer); + wafGloabl(); + },{ icon: rdata.status ? 1 : 2 },1000); + }); + }); + + + }, + }); +} + + +//保存retry规则 +function saveRetry(siteName,type) { + var pdata = { + siteName: siteName, + retry: $("input[name='retry']").val(), + retry_time: $("input[name='retry_time']").val(), + retry_cycle: $("input[name='retry_cycle']").val(), + is_open_global:type + } + + var act = 'set_retry'; + if (siteName != undefined) act = 'set_site_retry'; + owPost(act, pdata, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + layer.close(create_layer); + wafGloablRefresh(1000); + }); +} + +function addRule(ruleName) { + var pdata = { + 'ruleValue': $("input[name='ruleValue']").val(), + 'ps': $("input[name='rulePs']").val(), + 'ruleName': ruleName + } + + owPost('add_rule', pdata, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + setTimeout(function(){ + setObjConf(ruleName, 1); + },1000); + } + }); +} + +function modifyRule(index, ruleName) { + var ruleValue = $('.rule_body_' + index).text(); + $('.rule_body_' + index).html(''); + var rulePs = $('.rule_ps_' + index).text(); + $('.rule_ps_' + index).html(''); + $('.rule_modify_' + index).html('保存 | 取消'); + $(".modr_cancel_" + index).click(function () { + $('.rule_body_' + index).html(ruleValue); + $('.rule_ps_' + index).html(rulePs); + $('.rule_modify_' + index).html('编辑'); + }) +} + +function modifyRuleSave(index, ruleName) { + var pdata = { + index: index, + ruleName: ruleName, + ruleBody: $("textarea[name='rule_body_" + index + "']").val(), + rulePs: $("input[name='rule_ps_" + index + "']").val() + } + + owPost('modify_rule', pdata, function(data){ + var rdata = $.parseJSON(data.data); + + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + setTimeout(function(){ + setObjConf(ruleName, 1); + },1000); + } + }); +} + +function removeRule(ruleName, index) { + var pdata = { + 'index': index, + 'ruleName': ruleName + } + safeMessage('删除规则', '您真的要删除这条过滤规则吗?', function () { + owPost('remove_rule', pdata, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + setTimeout(function(){ + setObjConf(ruleName, 1); + },1000); + } + }); + }); +} + +function setRuleState(ruleName, index) { + var pdata = { + 'index': index, + 'ruleName': ruleName + } + + owPost('set_rule_state', pdata, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + setTimeout(function(){ + setObjConf(ruleName, 1); + },1000); + } + }); +} + +//设置规则 +function setObjConf(ruleName, type) { + if (type == undefined) { + create_l = layer.open({ + type: 1, + title: "编辑规则【" + ruleName + "】", + area: ['700px', '530px'], + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + \ + \ + \
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                规则说明操作状态
                                \ +
                                \ +
                                \ +
                                  \ +
                                • 注意:如果您不了解正则表达式,请不要随意修改规则内容
                                • \ +
                                • 您可以添加或修改规则内容,但请使用正则表达式
                                • \ +
                                • 内置规则允许修改,但不可以直接删除,您可以设置规则状态来定义防火墙是否使用此规则
                                • \ +
                                ' + }); + tableFixed("jc-file-table"); + } + + getRuleByName(ruleName, function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + var tbody = '' + for (var i = 0; i < rdata.length; i++) { + var removeRule = '' + if (rdata[i][3] != 0) removeRule = ' | 删除'; + tbody += '\ + ' + rdata[i][1] + '\ + ' + rdata[i][2] + '\ + 编辑' + removeRule + '\ + \ +
                                \ + \ + \ +
                                \ + \ + ' + } + $("#set_obj_conf_con").html(tbody); + }); +} + + +//常用扫描器 +function scanRule() { + + getRuleByName('scan_black', function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + + create_l = layer.open({ + type: 1, + title: "常用扫描器过滤规则", + area: '650px', + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + Header\ +
                                \ +
                                \ +
                                \ + Cookie\ +
                                \ +
                                \ +
                                \ + Args\ +
                                \ +
                                \ +
                                  \ +
                                • 会同时过滤key和value,请谨慎设置
                                • \ +
                                • 请使用正则表达式,提交前应先备份原有表达式
                                • \ +
                                \ +
                                \ + \ +
                                \ +
                                ' + }); + }); +} + +//保存扫描器规则 +function saveScanRule() { + pdata = { + header: $("textarea[name='scan_header']").val(), + cookie: $("textarea[name='scan_cookie']").val(), + args: $("textarea[name='scan_args']").val() + } + owPost('save_scan_rule', pdata,function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + layer.close(create_l); + wafGloablRefresh(1000); + }); +} + +//添加IP段到IP白名单 +function addIpWhite() { + var pdata = { + start_ip: $("input[name='start_ip']").val(), + end_ip: $("input[name='end_ip']").val() + } + + if (pdata['start_ip'].split('.').length < 4 || pdata['end_ip'].split('.').length < 4) { + layer.msg('起始IP或结束IP格式不正确!'); + return; + } + + owPost('add_ip_white', pdata, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + setTimeout(function(){ + ipWhite(1); + },1000); + } + }); +} + +//从IP白名单删除IP段 +function removeIpWhite(index) { + owPost('remove_ip_white', { index: index }, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata.status) { + setTimeout(function(){ + ipWhite(1); + },1000); + } + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +function funDownload(content, filename) { + // 创建隐藏的可下载链接 + var eleLink = document.createElement('a'); + eleLink.download = filename; + eleLink.style.display = 'none'; + // 字符内容转变成blob地址 + var blob = new Blob([content]); + eleLink.href = URL.createObjectURL(blob); + // 触发点击 + document.body.appendChild(eleLink); + eleLink.click(); + // 然后移除 + document.body.removeChild(eleLink); +} + +function outputLayer(rdata, name, type) { + window.Load_layer = layer.open({ + type: 1, + title: type ? "导出数据" : "导入数据", + area: ['400px', '370px'], + shadeClose: false, + content: '
                                ' + + '
                                ' + + '
                                ' + + '' + + '
                                导入格式如下:' + + (name == 'ip_white' || name == 'ip_black' ? "[[[127, 0, 0, 1],[127, 0, 0, 255]],[[192, 0, 0, 1],[192, 0, 0, 255]]]" : "[\"^/test\",\"^/web\"]") + + '
                                ' + + '
                                ' + + '
                                ' + + '
                                ' + + (type ? '' : '') + + '
                                ' + + '
                                ', + }); + var lead_error = CodeMirror.fromTextArea(document.getElementById("lead_data"), { + mode: 'html', + matchBrackets: true, + matchtags: true, + autoMatchParens: true + }); + setTimeout(function () { + $('.btn_save').on('click', function () { + importData(name, lead_error.getValue(), function(){ + layer.close(window.Load_layer); + ipWhiteLoadList(); + }); + }) + $('.btn_save_to').on('click', function () { + funDownload(lead_error.getValue(), name + '.json'); + }); + $('#focus_tips').on('click', function () { + $('.placeholder').hide(); + }); + }, 100); +} + + +//导出数据 +function outputData(name, callback) { + var loadT = layer.msg('正在导出数据..', { icon: 16, time: 0 }); + + owPost('output_data', { sname: name } , function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + if (callback) callback(rdata,res); + outputLayer(rdata, name, true); + }); +} + +//导入数据 +function importData(name, pdata, callback) { + owPost('import_data', { sname: name, pdata: pdata } , function(data){ + var rdata = $.parseJSON(data.data); + if (callback) callback(); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function fileInput(name) { + outputLayer('', name, false); +} + + +function ipWhiteLoadList(){ + getRuleByName('ip_white', function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + var tbody = '' + for (var i = 0; i < rdata.length; i++) { + tbody += '\ + '+ rdata[i][0].join('.') + '\ + '+ rdata[i][1].join('.') + '\ + 删除\ + ' + } + $("#ip_white_con").html(tbody); + }); +} +//IP白名单 +function ipWhite(type) { + if (type == undefined) { + create_l = layer.open({ + type: 1, + title: "管理IP白名单", + area: ['500px', '500px'], + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + \ + \ + \
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                超始IP结束IP操作
                                \ +
                                \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                  \ +
                                • 所有规则对白名单中的IP段无效,包括IP黑名单和URL黑名单,IP白名单具备最高优先权
                                • \ +
                                \ +
                                \ +
                                \ +
                                ', + success:function(index,layero){ + // $('.tab_list .tab_block').click(function(){ + // $(this).addClass('active').siblings().removeClass('active'); + // console.log($(this).index()); + // if($(this).index() === 0){ + // $('.ipv4_list').show().next().hide(); + // }else{ + // $('.ipv4_list').hide().next().show(); + // } + // }); + //
                                IPv4白名单
                                IPv6白名单
                                \ + } + }); + tableFixed("ipWhite"); + } + ipWhiteLoadList(); +} + +//IP白名单 +function urlWhite(type) { + + var ruleName = "url_white"; + + if (type == undefined) { + create_l = layer.open({ + type: 1, + title: "管理URL白名单", + area: ['700px', '530px'], + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + \ + \ + \
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                规则说明操作状态
                                \ +
                                \ +
                                \ +
                                  \ +
                                • 注意:如果您不了解正则表达式,请不要随意修改规则内容
                                • \ +
                                • 您可以添加或修改规则内容,但请使用正则表达式
                                • \ +
                                • 内置规则允许修改,但不可以直接删除,您可以设置规则状态来定义防火墙是否使用此规则
                                • \ +
                                ' + }); + tableFixed("jc-file-table"); + } + + getRuleByName(ruleName, function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + console.log(rdata); + var tbody = '' + for (var i = 0; i < rdata.length; i++) { + var removeRule = '' + if (rdata[i][3] != 0) removeRule = ' | 删除'; + tbody += '\ + ' + rdata[i][1] + '\ + ' + rdata[i][2] + '\ + 编辑' + removeRule + '\ + \ +
                                \ + \ + \ +
                                \ + \ + ' + } + $("#set_obj_conf_con").html(tbody); + }); +} + + +// 获取IPV4黑名单 +function getIpv4Address(callback){ + getRuleByName('ip_black', function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + callback(rdata); + }); +} + +// 获取IPV6黑名单 +function getIpv6Address(callback){ + getRuleByName('ipv6_black', function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + callback(rdata); + }); +} + + +// 添加ipv6请求 +function addIpv6Req(ip,callback){ + var ip = ip.replace(/:/g, '_'); + owPost('set_ipv6_black', {addr:ip}, function(data){ + var rdata = $.parseJSON(data.data); + if(callback) callback(rdata); + }); +} + +// 添加ipv6请求 +function removeIpv6Black(ip,callback){ + var ip = ip.replace(/:/g, '_'); + owPost('del_ipv6_black', {addr:ip}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + $('.tab_list .tab_block:eq(1)').click(); + + if(callback) callback(rdata); + }); +} + +//添加IP段到IP黑名单 +function addIpBlack() { + var pdata = { + start_ip: $("input[name='start_ip']").val(), + end_ip: $("input[name='end_ip']").val() + } + + if (pdata['start_ip'].split('.').length < 4 || pdata['end_ip'].split('.').length < 4) { + layer.msg('起始IP或结束IP格式不正确!'); + return; + } + + owPost('add_ip_black', pdata, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata.status) { + ipBlack(1); + } + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function addIpBlackArgs(ip) { + var pdata = { + start_ip: ip, + end_ip: ip, + } + + if (pdata['start_ip'].split('.').length < 4 || pdata['end_ip'].split('.').length < 4) { + layer.msg('起始IP或结束IP格式不正确!'); + return; + } + + owPost('add_ip_black', pdata, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +//从IP黑名单删除IP段 +function removeIpBlack(index) { + owPost('remove_ip_black', { index: index }, function (data) { + var rdata = $.parseJSON(data.data); + if (rdata.status) { + ipBlack(1); + } + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +//IP黑名单 +function ipBlack(type) { + if (type == undefined) { + create_l = layer.open({ + type: 1, + title: "管理IP黑名单", + area: ['500px', '500px'], + closeBtn: 1, + shadeClose: false, + content: '
                                IPv4黑名单
                                IPv6黑名单
                                \ +
                                \ +
                                \ + \ + \ + \
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                超始IP结束IP操作
                                \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                \ +
                                  \ +
                                • 黑名单中的IP段将被禁止访问,IP白名单中已存在的除外
                                • \ +
                                \ +
                                \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                \ +
                                \ + \ + \ + \ +
                                IPv6地址操作
                                \ +
                                \ +
                                \ +
                                  \ +
                                • 黑名单中的IP段将被禁止访问,IP白名单中已存在的除外
                                • \ +
                                \ +
                                ', + success:function(index,layero){ + $('.tab_list .tab_block').click(function(){ + $(this).addClass('active').siblings().removeClass('active'); + if($(this).index() === 0){ + $('.ipv4_block').show().next().hide(); + getIpv4Address(function(rdata){ + var tbody = '' + for (var i = 0; i < rdata.length; i++) { + tbody += '\ + '+ rdata[i][0].join('.') + '\ + '+ rdata[i][1].join('.') + '\ + 删除\ + ' + } + $("#ip_black_con").html(tbody); + }); + }else{ + $('.ipv4_block').hide().next().show(); + getIpv6Address(function(res){ + var tbody = '',rdata = res; + for (var i = 0; i < rdata.length; i++) { + tbody += '\ + '+ rdata[i] + '\ + 删除\ + ' + } + $("#ipv6_black_con").html(tbody); + }); + } + }); + $('.btn_add_ipv6').click(function(){ + var ipv6 = $('[name="ipv6_address"]').val(); + addIpv6Req(ipv6, function(res){ + layer.msg(res.msg,{icon:res.status?1:2}); + if(res.status){ + $('[name="ipv6_address"]').val(''); + $('.tab_list .tab_block:eq(1)').click(); + } + }); + }); + $('.tab_list .tab_block:eq(0)').click(); + } + }); + tableFixed("ipBlack"); + } else { + $('.tab_list .tab_block:eq(0)').click(); + } +} + +function wafScreen(){ + + owPost('waf_srceen', {}, function(data){ + var rdata = $.parseJSON(data.data); + + var end_time = Date.now(); + var cos_time = (end_time/1000) - parseInt(rdata['start_time']); + var cos_day = parseInt(parseInt(cos_time)/86400); + + var con = '
                                总拦截'+rdata.total+'
                                '; + con += '
                                安全防护'+cos_day+'
                                '; + + con += '
                                \ +
                                POST渗透'+rdata.rules.post+'
                                \ +
                                GET渗透'+rdata.rules.args+'
                                \ +
                                CC攻击'+rdata.rules.cc+'
                                \ +
                                恶意User-Agent'+rdata.rules.user_agent+'
                                \ +
                                Cookie渗透'+rdata.rules.cookie+'
                                \ +
                                恶意扫描'+rdata.rules.scan+'
                                \ +
                                恶意HEAD请求0
                                \ +
                                URI自定义拦截'+rdata.rules.url+'
                                \ +
                                URI保护'+rdata.rules.args+'
                                \ +
                                恶意文件上传'+rdata.rules.upload_ext+'
                                \ +
                                禁止的扩展名'+rdata.rules.path+'
                                \ +
                                禁止PHP脚本'+rdata.rules.php_path+'
                                \ +
                                '; + + con += '
                                  \ +
                                • 在此处关闭防火墙后,所有站点将失去保护
                                • \ +
                                • 网站防火墙会使nginx有一定的性能损失(<5% 10C静态并发测试结果)
                                • \ +
                                • 网站防火墙仅主要针对网站渗透攻击,暂时不具备系统加固功能
                                • \ +
                                '; + + $(".soft-man-con").html(con); + }); +} + +function wafGloablRefresh(time){ + setTimeout(function(){ + wafGloabl(); + }, time); +} + +function wafGloabl(){ + owPost('waf_conf', {}, function(data){ + var rdata = $.parseJSON(data.data); + + var con = '
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                名称描述响应状态操作
                                CC防御防御CC攻击,具体防御参数请到站点配置中调整'+rdata.cc.status+'
                                \ + \ +
                                \ +
                                初始规则
                                恶意容忍度封锁连续恶意请求,请到站点配置中调整容忍阈值' + rdata.cc.status + '--初始规则
                                强制安全验证'+rdata.safe_verify.ps+'--
                                \ + \ +
                                \ +
                                设置 | 响应内容
                                GET-URI过滤'+ rdata.get.ps + '' + rdata.get.status + '
                                \ + \ + \ +
                                规则 | 响应内容
                                GET-参数过滤'+ rdata.get.ps + '' + rdata.get.status + '
                                \ + \ + \ +
                                规则 | 响应内容
                                POST过滤'+ rdata.post.ps + '' + rdata.post.status + '
                                \ + \ + \ +
                                规则 | 响应内容
                                User-Agent过滤'+ rdata['user-agent'].ps + '' + rdata['user-agent'].status + '
                                \ + \ + \ +
                                规则 | 响应内容
                                Cookie过滤'+ rdata.cookie.ps + '' + rdata.cookie.status + '
                                \ + \ + \ +
                                规则 | 响应内容
                                常见扫描器'+ rdata.scan.ps + '' + rdata.scan.status + '
                                \ + \ + \ +
                                设置
                                URL白名单所有规则对URL白名单无效----设置
                                IP白名单所有规则对IP白名单无效----设置
                                IP黑名单禁止访问的IP' + rdata.cc.status + '--设置
                                其它'+ rdata.other.ps + '----响应内容
                                \ +
                                '; + + + con += '
                                  \ +
                                • 继承: 全局设置将在站点配置中自动继承为默认值
                                • \ +
                                • 优先级: IP白名单>IP黑名单>URL白名单>URL黑名单>CC防御>User-Agent>URI过滤>URL参数>Cookie>POST
                                • \ +
                                '; + $(".soft-man-con").html(con); + }); +} + +//返回css +function back_css(v) { + if (v > 0) { + return 'tipsval' + } + else { + return 'tipsval tipsvalnull' + } +} + +function html_encode(value) { + return $('
                                ').html(value).text(); +} + +function html_decode(value) { + return $('
                                ').text(value).html(); +} + +//添加站点过滤规则 +function addSiteRule(siteName, ruleName) { + var pdata = { + ruleValue: $("input[name='site_rule_value']").val(), + siteName: siteName, + ruleName: ruleName + } + + if (pdata['ruleValue'] == '') { + layer.msg('过滤规则不能为空'); + $("input[name='site_rule_value']").focus(); + return; + } + + owPost('add_site_rule', pdata, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + setTimeout(function(){ + siteRuleAdmin(siteName, ruleName, 1); + },1000); + } + }); +} + +//删除站点过滤规则 +function removeSiteRule(siteName, ruleName, index) { + var pdata = { + index: index, + siteName: siteName, + ruleName: ruleName + } + + owPost('remove_site_rule', pdata, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + if (ruleName == 'url_tell') { + site_url_tell(siteName, 1); + return; + } + + if (ruleName == 'url_rule') { + site_url_rule(siteName, 1); + return; + } + + setTimeout(function(){ + siteRuleAdmin(siteName, ruleName, 1); + },1000); + } + }); +} + +//网站规则管理 +function siteRuleAdmin(siteName, ruleName, type) { + var placeho = ''; + var ps = ''; + var title = ''; + switch (ruleName) { + case 'disable_php_path': + placeho = 'URI地址,支持正则表达式'; + ps = '
                              • 此处请不要包含URI参数,一般针对目录URL,示例:/admin
                              • ' + title = '禁止运行PHP的URL地址' + break; + case 'disable_path': + placeho = 'URI地址,支持正则表达式'; + ps = '
                              • 此处请不要包含URI参数,一般针对目录URL,示例:/admin
                              • ' + title = '禁止访问的URL地址' + break; + case 'disable_ext': + placeho = '扩展名,不包含点(.),示例:sql'; + ps = '
                              • 直接填要被禁止访问的扩展名,如我希望禁止访问*.sql文件:sql
                              • ' + title = '禁止访问的扩展名' + break; + case 'disable_upload_ext': + placeho = '扩展名,不包含点(.),示例:sql'; + ps = '
                              • 直接填要被禁止访问的扩展名,如我希望禁止上传*.php文件:php
                              • ' + title = '禁止上传的文件类型' + break; + } + if (type == undefined) { + create_l = layer.open({ + type: 1, + title: "管理网站过滤规则【" + title + "】", + area: ['500px', '500px'], + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + \ + \
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                规则操作
                                \ +
                                \ +
                                \ +
                                  \ +
                                • 除正则表达式语句外规则值对大小写不敏感,建议统一使用小写
                                • '+ ps + '\ +
                                ' + }); + tableFixed("siteRuleAdmin"); + } + + owPost('get_site_rule', { siteName: siteName, ruleName: ruleName }, function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + var tbody = '' + for (var i = 0; i < rdata.length; i++) { + tbody += '\ + '+ rdata[i] + '\ + 删除\ + ' + } + $("#site_rule_admin_con").html(tbody); + }); +} + +//CDN-Header配置 +function cdnHeader(siteName, type) { + if (type == undefined) { + create_l = layer.open({ + type: 1, + title: "管理网站【" + siteName + "】CDN-Headers", + area: ['500px', '500px'], + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ + \ + \
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                header操作
                                \ +
                                \ +
                                \ +
                                  \ +
                                • 防火墙将尝试在以上header中获取客户IP
                                • \ +
                                ' + }); + tableFixed("cdnHeader"); + } + + owPost('get_site_config_byname', { siteName: siteName }, function(data){ + var tmp = $.parseJSON(data.data); + var t1 = tmp.data; + var rdata = t1['cdn_header']; + var tbody = '' + for (var i = 0; i < rdata.length; i++) { + tbody += '\ + '+ rdata[i] + '\ + 删除\ + ' + } + $("#cdn_header_con").html(tbody); + }); +} + +//添加CDN-Header +function addCdnHeader(siteName) { + var pdata = { + cdn_header: $("input[name='cdn_header_key']").val(), + siteName: siteName + } + + if (pdata['cdn_header'] == '') { + layer.msg('header不能为空'); + $("input[name='cdn_header_key']").focus(); + return; + } + + owPost('add_site_cdn_header', pdata, function(data){ + var rdata = $.parseJSON(data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + setTimeout(function(){ + cdnHeader(siteName, 1); + },1000); + } + }); +} + + //删除CDN-Header +function removeCdnHeader(siteName, cdn_header_key) { + owPost('remove_site_cdn_header', { siteName: siteName, cdn_header: cdn_header_key }, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + setTimeout(function(){ + cdnHeader(siteName, 1); + },1000); + } + }); +} + +//设置网站防御功能 +function setSiteObjState(siteName, obj) { + // var loadT = layer.msg('正在处理,请稍候..', { icon: 16, time: 0 }); + owPost('set_site_obj_open', { siteName: siteName, obj: obj } , function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + setTimeout(function(){ + siteWafConfig(siteName, 1); + // siteConfig(); + },1000); + }); + // $.post('/plugin?action=a&name=btwaf&s=set_site_obj_open', { siteName: siteName, obj: obj }, function (rdata) { + // layer.close(loadT); + // site_waf_config(siteName, 1); + // siteconfig(); + // layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + // }); +} + + +//网站规则设置 +function setSiteObjConf(siteName, ruleName, type) { + if (type == undefined) { + create_l = layer.open({ + type: 1, + title: "编辑网站【" + siteName + "】规则【" + ruleName + "】", + area: ['700px', '530px'], + closeBtn: 1, + shadeClose: false, + content: '
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                规则说明状态
                                \ +
                                \ +
                                \ +
                                  \ +
                                • 此处继承全局设置中已启用的规则
                                • \ +
                                • 此处的设置仅对当前站点有效
                                • \ +
                                ' + }); + tableFixed("SetSiteObjConf"); + } + + getRuleByName(ruleName, function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + var tbody = ''; + var tbody = ''; + for (var i = 0; i < rdata.length; i++) { + if (rdata[i][0] == -1) continue; + tbody += '\ + '+ rdata[i][1] + '\ + '+ rdata[i][2] + '\ + \ +
                                \ +
                                \ + \ + ' + } + $("#set_site_obj_conf_con").html(tbody) + }); +} + +//网站设置 +function siteWafConfig(siteName, type) { + if (type == undefined) { + create_2 = layer.open({ + type: 1, + title: "网站配置【" + siteName + "】", + area: ['700px', '500px'], + closeBtn: 1, + shadeClose: false, + content: '
                                ' + }); + } + + owPost('get_site_config_byname', { siteName: siteName }, function(data){ + var tmp = $.parseJSON(data.data); + var rdata = tmp.data; + nginx_config = rdata; + var con = '
                                \ +
                                \ + 网站防火墙开关\ +
                                \ + \ + \ +
                                \ +
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                名称描述状态操作
                                CC防御'+ rdata.cc.cycle + ' 秒内,请求同一URI累计超过 ' + rdata.cc.limit + ' 次,封锁IP ' + rdata.cc.endtime + '\ +
                                \ + \ + \ +
                                \ +
                                设置
                                恶意容忍设置'+ rdata.retry.retry_cycle + ' 秒内,累计超过 ' + rdata.retry.retry + ' 次恶意请求,封锁IP ' + rdata.retry.retry_time + '  --设置
                                GET-URI过滤'+ rdata.get.ps + '\ +
                                \ + \ + \ +
                                \ +
                                规则
                                GET-参数过滤'+ rdata.get.ps + '\ +
                                \ + \ + \ +
                                \ +
                                规则
                                POST过滤'+ rdata.post.ps + '\ +
                                \ + \ + \ +
                                \ +
                                规则
                                User-Agent过滤'+ rdata['user-agent'].ps + '\ +
                                \ + \ + \ +
                                \ +
                                规则
                                Cookie过滤'+ rdata.cookie.ps + '\ +
                                \ + \ + \ +
                                \ +
                                规则
                                常见扫描器'+ rdata.scan.ps + '\ +
                                \ + \ + \ +
                                \ +
                                设置
                                使用CDN该站点使用了CDN,启用后方可正确获取客户IP\ +
                                \ + \ + \ +
                                \ +
                                设置
                                禁止扩展名禁止访问指定扩展名  --设置
                                禁止上传的文件类型禁止上传指定的文件类型  --设置
                                \ +
                                \ +
                                \ +
                                  \ +
                                • 注意: 此处大部分配置,仅对当前站点有效!
                                • \ +
                                \ +
                                '; + $("#s_w_c").html(con); + }); +} + + + +function wafSite(){ + owPost('get_site_config', {}, function(data){ + var tmp = $.parseJSON(data.data); + var rdata = $.parseJSON(tmp.data); + var tbody = ''; + var i = 0; + $.each(rdata, function (k, v) { + i += 1; + tbody += '\ + ' + k + '\ + ' + v.total[1].value + '\ + ' + v.total[0].value + '\ + ' + v.total[3].value + '\ + ' + v.total[4].value + '\ + \ + ' + v.total[2].value + '\ + \ +
                                \ + \ + \ +
                                \ + \ + 日志 \ + '; + //| 设置 + }); + + var con = '
                                \ +
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ tbody + '\ +
                                站点GETPOSTUACookieCDNCC防御状态操作
                                \ +
                                \ +
                                \ +
                                \ +
                                '; + $(".soft-man-con").html(con); + tableFixed("siteCon_fix"); + }); +} + + +function wafAreaLimitRender(){ + function keyVal(obj){ + var str = []; + $.each(obj, function (index, item) { + if (item == 1) { + if (index == 'allsite') index = '所有站点'; + if (index == '海外') index = '中国大陆以外的地区(包括[港,澳,台])'; + if (index == '中国') index = '中国大陆(不包括[港,澳,台])'; + str.push(index); + } + }); + return str.toString(); + } + owPost('get_area_limit', {}, function(rdata) { + var rdata = $.parseJSON(rdata.data); + if (!rdata.status) { + layer.msg(rdata.msg, { icon: 2, time: 2000 }); + return; + } + + var list = ''; + var rlist = rdata.data; + + for (var i = 0; i < rlist.length; i++) { + var op = ''; + var type = rlist[i]['types'] === 'refuse' ? '拦截' : '只放行'; + var region_str = keyVal(rlist[i]['region']); + var site_str = keyVal(rlist[i]['site']); + + op += '删除'; + + list += ''; + list += '' + region_str + ''; + list += '' + site_str + ''; + list += '' + type + ''; + + list += '' + op + ''; + list += ''; + } + + $('#con_list tbody').html(list); + $('.area_limit_del').click(function(){ + var data_id = $(this).data('id'); + + var site = [],region = []; + $.each(rlist[data_id]['site'], function (index, item) { + site.push(index); + }); + $.each(rlist[data_id]['region'], function (index, item) { + region.push(index); + }); + + var type = rlist[data_id]['types']; + + owPost('del_area_limit', { + site:site.toString(), + region:region.toString(), + types:type, + }, function(rdata) { + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + if (rdata.status){ + wafAreaLimit(); + } + },{ icon: rdata.status ? 1 : 2 }); + }); + }); + }); +} + +function wafAreaLimitSwitch(){ + owPostN('waf_conf', {}, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['area_limit']){ + $('#area_limit_switch').prop('checked', true); + } else{ + $('#area_limit_switch').prop('checked',false); + } + }); +} + +function setWafAreaLimitSwitch(){ + var area_limit_switch = $('#area_limit_switch').prop('checked'); + // console.log(area_limit_switch); + var area_limit = 'off'; + if (!area_limit_switch){ + area_limit = 'on'; + } + owPostN('area_limit_switch', {'area_limit': area_limit}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +// 地区限制 +function wafAreaLimit(){ + var con = '
                                \ + \ + \ + \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ +
                                地区站点类型操作
                                \ +
                                \ +
                                \ +
                                '; + $(".soft-man-con").html(con); + wafAreaLimitRender(); + wafAreaLimitSwitch(); + + $('#create_area_limit').click(function(){ + var site_list; + var area_list; + var site_length = 0; + layer.open({ + type: 1, + title: '添加地区限制', + area: ['450px','280px'], + closeBtn: 1, + btn: ['添加', '取消'], + content: '
                                \ +
                                \ + 类型\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 站点\ +
                                \ +
                                \ +
                                \ +
                                \ +
                                \ + 地区\ +
                                \ +
                                \ +
                                ', + success: function (layers, index) { + document.getElementById('layui-layer' + index).getElementsByClassName('layui-layer-content')[0].style.overflow = 'unset'; + + site_list = xmSelect.render({ + el: '#site_list', + language: 'zn', + toolbar: {show: true,}, + paging: true, + pageSize: 10, + data: [], + }); + + owPostN('get_default_site','', function(rdata){ + var rdata = $.parseJSON(rdata.data); + var rlist = rdata.data.list; + + + var pdata = []; + for (var i = 0; i < rlist.length; i++) { + var tval = rlist[i]; + if (tval != 'unset'){ + var t = {name:rlist[i],value:rlist[i]}; + pdata.push(t); + } + } + site_length = pdata.length; + site_list.update({data:pdata}); + }); + + area_list = xmSelect.render({ + el: '#area_list', + language: 'zn', + toolbar: {show: true,}, + filterable: true, + data: [], + }); + owPostN('get_country','', function(rdata){ + var rdata = $.parseJSON(rdata.data); + var rlist = rdata.data; + + var pdata = []; + for (var i = 0; i < rlist.length; i++) { + var tval = rlist[i]; + if (tval != 'unset'){ + var t = {name:tval,value:tval}; + pdata.push(t); + } + } + + area_list.update({data:pdata}); + }); + }, + yes: function (indexs) { + + var reg_type = $('select[name="type"]').val(); + var site_val = site_list.getValue('value'); + var area_val = area_list.getValue('value'); + + if (area_val.length <1) return layer.msg('地区最少选一个!', { icon: 2 }); + if (site_val.length <1) return layer.msg('站点最少选一个!', { icon: 2 }); + + var site = ''; + if (site_length === site_val.length) { + site = 'allsite'; + } else { + site = site_val.join(); + } + + var area = area_val.join(); + var region = area.replace('中国大陆以外的地区(包括[中国特别行政区:港,澳,台])', '海外') + .replace('中国大陆(不包括[中国特别行政区:港,澳,台])', '中国') + .replace('中国香港', '香港') + .replace('中国澳门', '澳门') + .replace('中国台湾', '台湾'); + + owPost('add_area_limit',{ + site:site, + types:reg_type, + region:region, + }, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(indexs); + wafAreaLimit(); + } + },{ icon: rdata.status ? 1 : 2 }); + }); + + }, + }); + }); +} + +function wafLogRequest(page){ + var args = {}; + args['page'] = page; + args['page_size'] = 10; + args['site'] = $('select[name="site"]').val(); + + var query_date = 'today'; + if ($('#time_choose').attr("data-name") != ''){ + query_date = $('#time_choose').attr("data-name"); + } else { + query_date = $('#search_time button.cur').attr("data-name"); + } + + args['query_date'] = query_date; + args['tojs'] = 'wafLogRequest'; + + owPost('get_logs_list', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var list = ''; + var data = rdata.data.data; + if (data.length > 0){ + for(i in data){ + list += ''; + list += '' + getLocalTime(data[i]['time'])+''; + list += '' + data[i]['domain'] +''; + list += '' + data[i]['ip'] +''; + list += '' + data[i]['uri'] +'';// data[i]['uri'] + list += '' + data[i]['rule_name'] +''; + list += '' + entitiesEncode(data[i]['reason']) +'';//data[i]['reason'] + list += '详情'; + list += ''; + } + } else{ + list += '封锁日志为空'; + } + + var table = '
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                时间域名IPURI规则名原因操作
                                \ +
                                \ +
                                '; + $('#ws_table').html(table); + $('#wsPage').html(rdata.data.page); + + $(".tablescroll .details").click(function(){ + var index = $(this).attr('data-id'); + var res = data[index]; + var ip = res.ip; + var time = getLocalTime(res.time); + layer.open({ + type: 1, + title: "【"+res.domain + "】详情", + area: '600px', + closeBtn: 1, + shadeClose: false, + content: '
                                \ + \ +
                                时间'+ time + '用户IP' + escapeHTML(ip) + '
                                类型' + escapeHTML(res.method) + '过滤器' + escapeHTML(res.rule_name) + '
                                \ +
                                URI地址
                                \ +
                                '+ escapeHTML(res.uri) + '
                                \ +
                                User-Agent
                                \ +
                                '+ escapeHTML(res.user_agent) + '
                                \ +
                                过滤规则
                                \ +
                                '+ escapeHTML(res.rule_name) + '
                                \ +
                                Reason
                                \ +
                                '+ escapeHTML(res.reason) + '
                                \ +
                                ' + }) + }); + }); +} + +function wafLogs(){ + var randstr = getRandomString(10); + + + var html = '
                                \ +
                                \ + 网站: \ + \ + 时间: \ +
                                \ +
                                \ + \ + \ + \ + \ +
                                \ + \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                \ +
                                \ +
                                '; + $(".soft-man-con").html(html); + // wafLogRequest(1); + + $("#UncoverAll").click(function(){ + owPost('clean_drop_ip',{},function(data){ + var rdata = $.parseJSON(data.data); + var ndata = $.parseJSON(rdata.data); + if (ndata.status == 0){ + layer.msg("解封所有成功",{icon:1,time:2000,shade: [0.3, '#000']}); + } else{ + layer.msg("解封所有异常:"+ndata.msg,{icon:5,time:2000,shade: [0.3, '#000']}); + } + }); + }); + + //测试demo + $("#testRun").click(function(){ + owPost('test_run',{},function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function(){ + wafLogRequest(1); + },{icon:1,shade: [0.3, '#000']},2000); + }); + }); + + //日期范围 + laydate.render({ + elem: '#time_choose', + value:'', + range:true, + done:function(value, startDate, endDate){ + if(!value){ + return false; + } + + $('#search_time button').each(function(){ + $(this).removeClass('cur'); + }); + + var timeA = value.split('-'); + var start = $.trim(timeA[0]+'-'+timeA[1]+'-'+timeA[2]) + var end = $.trim(timeA[3]+'-'+timeA[4]+'-'+timeA[5]) + query_txt = toUnixTime(start + " 00:00:00") + "-"+ toUnixTime(end + " 00:00:00") + + $('#time_choose').attr("data-name",query_txt); + $('#time_choose').addClass("cur"); + + wafLogRequest(1); + }, + }); + + $('#search_time button:eq(0)').addClass('cur'); + $('#search_time button').click(function(){ + $('#search_time button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $('#time_choose').attr("data-name",''); + $('#time_choose').removeClass("cur"); + + $(this).addClass('cur'); + + wafLogRequest(1); + }); + + owPostN('get_default_site',{},function(rdata){ + $('select[name="site"]').html(''); + + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + var default_site = rdata["default"]; + var select = ''; + for (var i = 0; i < rdata["list"].length; i++) { + if (default_site == rdata["list"][i]){ + select += ''; + } else{ + select += ''; + } + } + $('select[name="site"]').html(select); + wafLogRequest(1); + + $('select[name="site"]').change(function(){ + wafLogRequest(1); + }); + }); + +} + + +function wafOpLogs(){ + var con = '
                                \ + \ + \ + \ + \ + \ + \ + \ +
                                名称描述响应状态操作
                                \ +
                                '; + $(".soft-man-con").html(con); +} diff --git a/plugins/op_waf/shell/cpu_usage.sh b/plugins/op_waf/shell/cpu_usage.sh new file mode 100644 index 000000000..4de0bf2c3 --- /dev/null +++ b/plugins/op_waf/shell/cpu_usage.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +DST_DIR=/www/server/op_waf +DIS_FILE=${DST_DIR}/cpu.info + +CPU_USAGE=`top -bn 1 | fgrep 'Cpu(s)' | awk '{print 100 -$8}' | awk -F . '{print $1}'` +echo $CPU_USAGE +echo $CPU_USAGE > $DIS_FILE +echo "done success!" \ No newline at end of file diff --git a/plugins/op_waf/shell/cpu_usage_file.sh b/plugins/op_waf/shell/cpu_usage_file.sh new file mode 100644 index 000000000..d7df7e19a --- /dev/null +++ b/plugins/op_waf/shell/cpu_usage_file.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +DST_DIR=/www/server/op_waf +DIS_FILE=${DST_DIR}/cpu.info + +function GetCpuUsage(){ + cpu_info=`cat /proc/stat | head -n 1` + idle_cpu=`echo $cpu_info|awk '{print $2}'` + cpu_total_time=0 + for ci in ${cpu_info[@]}; do + if [ "$ci" == "cpu" ];then + continue + else + #echo $ci + cpu_total_time=`expr $cpu_total_time + $ci` + fi + done + #echo "idle_cpu:${idle_cpu}" + #echo "cpu_total_time:${cpu_total_time}" + + cpu_percet=$(awk "BEGIN{print ((${cpu_total_time}-${idle_cpu})/${cpu_total_time})*100}") + echo "${cpu_percet}" + return 0 +} + +# one value detal +getOne=`GetCpuUsage` + +CPU_USAGE=`echo $getOne | awk -F . '{print $1}'` +echo "cpu usage:${CPU_USAGE}" +echo $CPU_USAGE > $DIS_FILE +echo "done success!" + + +# two value compare + +# getOne=`GetCpuUsage` +# echo "getOne:$getOne" +# sleep 1 +# getTwo=`GetCpuUsage` +# echo "getTwo:$getTwo" + +# cpu_percet_calc=$(awk "BEGIN{print (${getOne}+${getTwo})/2}") +# echo "cpu_percet_calc:${cpu_percet_calc}" + +# #echo '0.61212' | awk -F . '{print $1}' +# CPU_USAGE=`echo $cpu_percet_calc | awk -F . '{print $1}'` +# echo "cpu usage:${CPU_USAGE}" +# echo $CPU_USAGE > $DIS_FILE +# echo "done success!" \ No newline at end of file diff --git a/plugins/op_waf/t/bench/bench.sh b/plugins/op_waf/t/bench/bench.sh new file mode 100755 index 000000000..e0f3acd36 --- /dev/null +++ b/plugins/op_waf/t/bench/bench.sh @@ -0,0 +1,30 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +resty=$rootPath/openresty/bin/resty + +RUN_CMD=$resty +if [ ! -f $resty ];then + RUN_CMD=/www/server/openresty/bin/resty +fi + + +# test +# $RUN_CMD simple.lua +# $RUN_CMD test_gsub.lua + +# $RUN_CMD --shdict 'limit 10m' test_find_server_name.lua +# $RUN_CMD --stap --shdict 'limit 10m' test_find_server_name.lua + +# $RUN_CMD test_rand.lua +# $RUN_CMD test_ffi_time.lua +$RUN_CMD test_get_cpu.lua \ No newline at end of file diff --git a/plugins/op_waf/t/bench/simple.lua b/plugins/op_waf/t/bench/simple.lua new file mode 100755 index 000000000..7f52523a0 --- /dev/null +++ b/plugins/op_waf/t/bench/simple.lua @@ -0,0 +1,18 @@ +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end + +collectgarbage() + +ngx.update_time() +local begin = ngx.now() +local N = 1e7 +for i = 1, N do + target() +end +ngx.update_time() + +ngx.say("elapsed: ", (ngx.now() - begin) / N) \ No newline at end of file diff --git a/plugins/op_waf/t/bench/test_ffi_time.lua b/plugins/op_waf/t/bench/test_ffi_time.lua new file mode 100644 index 000000000..1e8cfb866 --- /dev/null +++ b/plugins/op_waf/t/bench/test_ffi_time.lua @@ -0,0 +1,62 @@ +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end + +-- 以上为预热操作 +collectgarbage() + +local ffi = require("ffi") +ffi.cdef[[ + struct timeval { + long int tv_sec; + long int tv_usec; + }; + int gettimeofday(struct timeval *tv, void *tz); +]]; +local tm = ffi.new("struct timeval"); + +-- 返回微秒级时间戳 +local function current_time_millis() + ffi.C.gettimeofday(tm,nil); + local sec = tonumber(tm.tv_sec); + local usec = tonumber(tm.tv_usec); + return sec + usec * 10^-6; +end + + +ngx.update_time() +local begin = ngx.now() +local N = 1e7 +for i = 1, N do + target() +end +ngx.update_time() + +ngx.say("elapsed: ", (ngx.now() - begin) / N) + + +ngx.update_time() +local begin = ngx.now() +local N = 1e7 +for i = 1, N do + target() +end +ngx.update_time() + +ngx.say("elapsed[1]: ", (ngx.now() - begin) / N) + + + + +ngx.update_time() +local begin = current_time_millis() +local N = 1e7 +for i = 1, N do + target() +end +ngx.update_time() + +ngx.say("ffi elapsed: ", (current_time_millis() - begin) / N) \ No newline at end of file diff --git a/plugins/op_waf/t/bench/test_find_server_name.lua b/plugins/op_waf/t/bench/test_find_server_name.lua new file mode 100644 index 000000000..89d50dd09 --- /dev/null +++ b/plugins/op_waf/t/bench/test_find_server_name.lua @@ -0,0 +1,75 @@ +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end + +-- 以上为预热操作 +collectgarbage() + +local config_domains = { + [1] = { + ["name"] = "t1.cn", + ["path"] = "/www/wwwroot/t1.cn", + ["domains"] = { + [1] = "t1.cn", + [2] = "t3.cn" + } + } +} + +local function get_server_name(request_name) + for _,v in ipairs(config_domains) + do + for _,cd_name in ipairs(v['domains']) + do + if request_name == cd_name then + return v['name'] + end + end + end + return request_name +end + + +local function get_server_name_cache(request_name) + local cache_name = ngx.shared.limit:get(request_name) + if cache_name then return cache_name end + + for _,v in ipairs(config_domains) + do + for _,cd_name in ipairs(v['domains']) + do + if request_name == cd_name then + ngx.shared.limit:set(cd_name,v['name'],3600) + return v['name'] + end + end + end + return request_name +end + + +ngx.update_time() +local begin = ngx.now() +local N = 1e7 +for i = 1, N do + get_server_name("t3.cn") +end +ngx.update_time() + +ngx.say("test get_server_name elapsed: ", (ngx.now() - begin) / N) + + + + +ngx.update_time() +local begin = ngx.now() +local N = 1e7 +for i = 1, N do + get_server_name_cache("t3.cn") +end +ngx.update_time() + +ngx.say("test get_server_name_cache elapsed: ", (ngx.now() - begin) / N) \ No newline at end of file diff --git a/plugins/op_waf/t/bench/test_get_cpu.lua b/plugins/op_waf/t/bench/test_get_cpu.lua new file mode 100644 index 000000000..bfd566084 --- /dev/null +++ b/plugins/op_waf/t/bench/test_get_cpu.lua @@ -0,0 +1,69 @@ +-- cd /www/server/mdserver-web/plugins/op_waf/t/bench && bash bench.sh + +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end + +local function file_exists(path) + local file = io.open(path, "rb") + if file then file:close() end + return file ~= nil +end + +-- 以上为预热操作 +collectgarbage() + +local json = require "cjson" +local ngx_re = require "ngx.re" + +local function data_split(self, str,reps ) + local rsList = {} + string.gsub(str,'[^'..reps..']+',function(w) + table.insert(rsList,w) + end) + return rsList +end + +local function get_cpu_stat() + local cpu_total = 0 + local fp = io.open('/proc/stat','r') + local cpu_line = fp:read() + fp:close() + + local list = ngx_re.split(cpu_line," ") + table.remove(list,1) + table.remove(list,1) + + local idie = list[4] + for i,v in pairs(list) + do + cpu_total = cpu_total + v + end + + local use_percent = tonumber(100-(idie/cpu_total)*100) + + return cpu_total,idie,use_percent +end + +local function get_cpu_percent() + local cpu_total,idie,use_percent = get_cpu_stat() + ngx.sleep(2) + local cpu_total2,idie2,use_percent2 = get_cpu_stat() + local cpu_usage_percent = tonumber(100-(((idie2-idie)/(cpu_total2-cpu_total))*100)) + ngx.say("cpu_usage_percent:"..cpu_usage_percent) + return cpu_usage_percent +end + +ngx.update_time() +local begin = ngx.now() +local N = 1e1 +for i = 1, N do + get_cpu_stat() +end +ngx.update_time() + +ngx.say("test_get_cpu elapsed: ", (ngx.now() - begin)) + diff --git a/plugins/op_waf/t/bench/test_gsub.lua b/plugins/op_waf/t/bench/test_gsub.lua new file mode 100644 index 000000000..6d9a7a972 --- /dev/null +++ b/plugins/op_waf/t/bench/test_gsub.lua @@ -0,0 +1,47 @@ + +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end +-- 以上为预热操作 +collectgarbage() + +local function test_string_gsub(str,reps) + local resultStrList = {} + string.gsub(str,'[^'..reps..']+', function(w) + table.insert(resultStrList,w) + return w + end) +end + + +local function test_ngx_string_gsub(str,reps) + local resultStrList = {} + ngx.re.gsub(str,'[^'..reps..']+', function(w) + table.insert(resultStrList,w[0]) + return w + end, "ijo") +end + +ngx.update_time() +local begin = ngx.now() +local N = 1e6 +for i = 1, N do + test_string_gsub("2409:8a62:e20:95f0:45b7:233e:f003:c0ab",",") +end +ngx.update_time() + +ngx.say("test_string_gsub elapsed: ", (ngx.now() - begin) / N) + + +ngx.update_time() +local begin = ngx.now() +local N = 1e6 +for i = 1, N do + test_ngx_string_gsub("2409:8a62:e20:95f0:45b7:233e:f003:c0ab",",") +end +ngx.update_time() + +ngx.say("test_ngx_string_gsub elapsed: ", (ngx.now() - begin) / N) diff --git a/plugins/op_waf/t/bench/test_rand.lua b/plugins/op_waf/t/bench/test_rand.lua new file mode 100644 index 000000000..2c0b51f3c --- /dev/null +++ b/plugins/op_waf/t/bench/test_rand.lua @@ -0,0 +1,72 @@ +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end + +-- 以上为预热操作 +collectgarbage() + + + +local function get_random_t1(n) + math.randomseed(ngx.time()) + local t = { + "0","1","2","3","4","5","6","7","8","9", + "a","b","c","d","e","f","g","h","i","j", + "k","l","m","n","o","p","q","r","s","t", + "u","v","w","x","y","z", + "A","B","C","D","E","F","G","H","I","J", + "K","L","M","N","O","P","Q","R","S","T", + "U","V","W","X","Y","Z", + } + local s = "" + for i = 1, n do + s = s .. t[math.random(#t)] + end + return s +end + + + +local function get_random_t2(n) + local t = { + "0","1","2","3","4","5","6","7","8","9", + "a","b","c","d","e","f","g","h","i","j", + "k","l","m","n","o","p","q","r","s","t", + "u","v","w","x","y","z", + "A","B","C","D","E","F","G","H","I","J", + "K","L","M","N","O","P","Q","R","S","T", + "U","V","W","X","Y","Z", + } + local s = "" + for i = 1, n do + s = s .. t[math.random(#t)] + end + return s +end + +ngx.update_time() +local begin = ngx.now() +local N = 1e5 +for i = 1, N do + get_random_t1(16) +end +ngx.update_time() + +ngx.say("test get_random_t1 elapsed: ", (ngx.now() - begin) / N) + + +ngx.update_time() +local begin = ngx.now() +local N = 1e5 +math.randomseed(ngx.time()) +for i = 1, N do + get_random_t2(16) +end +ngx.update_time() + +ngx.say("test get_random_t2 elapsed: ", (ngx.now() - begin) / N) + + diff --git a/plugins/op_waf/t/index.py b/plugins/op_waf/t/index.py new file mode 100644 index 000000000..98bd75f28 --- /dev/null +++ b/plugins/op_waf/t/index.py @@ -0,0 +1,328 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +import os +import sys +import time +import string +import json +import hashlib +import shlex +import datetime +import subprocess +import re +from random import Random + + +TEST_URL = "http://t1.cn/" +# TEST_URL = "https://www.zzzvps.com/" + + +def writeFile(filename, str): + # 写文件内容 + try: + fp = open(filename, 'w+') + fp.write(str) + fp.close() + return True + except Exception as e: + return False + + +def httpGet(url, timeout=10): + import urllib.request + + try: + req = urllib.request.urlopen(url, timeout=timeout) + result = req.read().decode('utf-8') + return result + + except Exception as e: + return str(e) + + +def httpGet__Header(url, headers, timeout=10): + import urllib.request + try: + req = urllib.request.Request(url, headers=headers) + response = urllib.request.urlopen(req) + result = response.read().decode('utf-8') + return result + + except Exception as e: + return str(e) + + +def httpUpload(url, timeout=10): + try: + import requests + + files = { + 'file': open('/Users/midoks/Desktop/mwdev/server/op_waf/version.pl', 'rb') + } + res = requests.post(url=url, files=files) + return res + except Exception as e: + return "http.upload:" + str(e) + + +def httpUploadPhp(url, timeout=10): + try: + import requests + + writeFile("/tmp/tmp.php", "") + + files = { + 'file': open('/tmp/tmp.php', 'rb') + } + res = requests.post(url=url, files=files) + return res + except Exception as e: + return "http.upload:" + str(e) + + +def httpUploadPhpData(url, timeout=10): + try: + import requests + + writeFile("/tmp/tmp.py", "") + + files = { + 'file': open('/tmp/tmp.py', 'rb') + } + res = requests.post(url=url, files=files) + return res + except Exception as e: + return "http.upload:" + str(e) + + +def httpGet__UA(url, ua, timeout=10): + import urllib.request + headers = {'user-agent': ua} + try: + req = urllib.request.Request(url, headers=headers) + response = urllib.request.urlopen(req) + result = response.read().decode('utf-8') + return result + + except Exception as e: + return str(e) + + +def httpGet__cdn(url, ip, timeout=10): + import urllib.request + headers = {'x-forwarded-for': ip} + try: + req = urllib.request.Request(url, headers=headers) + response = urllib.request.urlopen(req) + result = response.read().decode('utf-8') + return result + + except Exception as e: + return str(e) + + +def httpPost(url, data, timeout=10): + """ + 发送POST请求 + @url 被请求的URL地址(必需) + @data POST参数,可以是字符串或字典(必需) + @timeout 超时时间默认60秒 + return string + """ + if sys.version_info[0] == 2: + try: + import urllib + import urllib2 + import ssl + ssl._create_default_https_context = ssl._create_unverified_context + data = urllib.urlencode(data) + req = urllib2.Request(url, data) + response = urllib2.urlopen(req, timeout=timeout) + return response.read() + except Exception as ex: + return str(ex) + else: + try: + import urllib.request + import ssl + try: + ssl._create_default_https_context = ssl._create_unverified_context + except: + pass + data = urllib.parse.urlencode(data).encode('utf-8') + req = urllib.request.Request(url, data) + response = urllib.request.urlopen(req, timeout=timeout) + result = response.read() + if type(result) == bytes: + result = result.decode('utf-8') + return result + except Exception as ex: + return str(ex) + + +def test_Dir(): + ''' + 目录保存 + ''' + url = TEST_URL + '?t=../etc/passwd' + print("args test start") + url_val = httpGet(url, 10) + print(url_val) + print("args test end") + + +def test_UA(): + ''' + user-agent 过滤 + ''' + url = TEST_URL + print("user-agent test start") + url_val = httpGet__UA(url, 'ApacheBench') + print(url_val) + print("user-agent test end") + + +def test_Header(): + ''' + user-agent 过滤 + ''' + url = TEST_URL + print("user-agent test start") + url_val = httpGet__Header(url, {'X-forwarded-For': '../etc/passwd'}) + print(url_val) + print("user-agent test end") + + +def test_UA_for(num): + ''' + user-agent 过滤 + ''' + url = TEST_URL + print("user-agent test start") + for x in range(num): + url_val = httpGet__UA(url, 'ApacheBench') + print(url_val) + print("user-agent test end") + + +def test_cdn(): + ''' + user-agent 过滤 + ''' + url = TEST_URL + print("cdn test start") + url_val = httpGet__cdn(url, '2409:8a62:e20:95f0:45b7:233e:f003:c0ab') + print(url_val) + + url_val2 = httpGet__cdn(url, '91.245.227.173') + print(url_val2) + print("cdn test end") + + +def test_POST(): + ''' + user-agent 过滤 + ''' + url = TEST_URL + print("POST test start") + url_val = httpPost(url, {'data': "substr($mmsss,0,1)"}) + # url_val = httpPost(url, {'data': "123123"}) + print(url_val) + print("POST test end") + + +def test_scan(): + ''' + 目录保存 + ''' + url = TEST_URL + 'acunetix_wvs_security_test?t=1' + print("scan test start") + url_val = httpGet(url, 10) + print(url_val) + print("scan test end") + + +def test_CC(): + ''' + 目录保存 + ''' + url = TEST_URL + 'ok.txt' + print("CC test start") + + for x in range(122): + url_val = httpGet(url, 10) + print(url_val) + + print("CC test end") + + +def test_url_ext(): + ''' + 目录保存 + ''' + url = TEST_URL + 't.sql' + print("url_ext start") + url_val = httpGet(url, 10) + print(url_val) + + print("url_ext end") + + +def test_OK(): + ''' + 目录保存 + ''' + url = TEST_URL + print("ok test start") + url_val = httpGet(url, 10) + print(url_val) + print("ok test end") + + +def test_Upload(): + ''' + 上传文件 + ''' + url = TEST_URL + print("upload test start") + url_val = httpUpload(url, 10) + print(url_val) + + print("upload test end") + + print("upload php test start") + url_val = httpUploadPhp(url, 10) + print(url_val) + print("upload php test start") + + print("upload php data test start") + url_val = httpUploadPhpData(url, 10) + print(url_val) + print("upload php data test start") + + +def test_start(): + # test_OK() + test_Dir() + # test_UA() + # test_Header() + # test_UA_for(1000) + # test_POST() + # test_scan() + # test_CC() + # test_url_ext() + # test_cdn() + # test_Upload() + + +if __name__ == "__main__": + # os.system('cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/op_waf && sh install.sh uninstall 0.2.2 && sh install.sh install 0.2.2') + os.system('cd /Users/midoks/Desktop/mwdev/server/mdserver-web/ && python3 /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/op_waf/index.py reload') + # os.system('cd /Users/midoks/Desktop/mwdev/server/mdserver-web/ && python3 plugins/openresty/index.py stop && python3 plugins/openresty/index.py start') + test_start() diff --git a/plugins/op_waf/t/ngx_debug.sh b/plugins/op_waf/t/ngx_debug.sh new file mode 100644 index 000000000..1e50adadb --- /dev/null +++ b/plugins/op_waf/t/ngx_debug.sh @@ -0,0 +1,148 @@ +#!/bin/sh +export PATH=$PATH:/opt/stap/bin:/opt/stapxx + +# cd /www/server/mdserver-web/plugins/op_waf/t && bash ngx_debug.sh lua ok +# cd /www/server/mdserver-web/plugins/op_waf/t && bash ngx_debug.sh c ok + + +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh + zypper install cron wget curl zip unzip +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Amazon Linux" /etc/issue || grep -Eq "Amazon Linux" /etc/*-release; then + OSNAME='amazon' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/os-release; then + OSNAME='debian' + apt update -y + apt install -y wget curl zip unzip tar cron +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/os-release; then + OSNAME='ubuntu' + apt update -y + apt install -y wget curl zip unzip tar cron +else + OSNAME='unknow' +fi + + +# https://moonbingbing.gitbooks.io/openresty-best-practices/content/flame_graph/install.html +# apt install elfutils +# sudo apt-get install -y systemtap gcc +# sudo apt-get install linux-headers-generic gcc libcap-dev +# apt-get install -y libdw-dev +# apt-get install -y fakeroot build-essential crash kexec-tools makedumpfile kernel-wedge kernel-package +# apt-get install -y git-core libncurses5 libncurses5-dev libelf-dev asciidoc binutils-dev +# apt-get build-dep linux + +# cat > /etc/apt/sources.list.d/ddebs.list << EOF +# deb http://ddebs.ubuntu.com/ precise main restricted universe multiverse +# EOF +# +# apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ECDCAD72428D7C01 +# apt-get update + +if [ $# -ne 2 ] +then + echo "Usage: ./`basename $0` lua/c NAME" + exit +fi + +pids=`ps -ef|grep nginx | grep -v grep | awk '{print $2}'` +name=$2 + + +# /opt/openresty-systemtap-toolkit/ngx-active-reqs -p 496435 +# /opt/openresty-systemtap-toolkit/sample-bt -p 496435 -t 5 -k > a.bt +# kernel-debuginfo-common kernel-debuginfo +# apt install -y kernel-debuginfo-common kernel-debuginfo +# apt install -y kernel-* + +if [ "$OSNAME" == "debian" ];then + apt install -y systemtap + apt-get install -y build-essential + apt-get install -y linux-headers-$(uname -r) +elif [ "$OSNAME" == "centos" ];then + yum install -y kernel-devel-$(uname -r) +fi + +# /opt/stapxx/samples/lj-lua-stacks.sxx --arg time=5 --skip-badvars -x 45266 > tmp.bt + + +if [ ! -d /opt/openresty-systemtap-toolkit ];then + cd /opt && git clone https://github.com/openresty/openresty-systemtap-toolkit +fi + +if [ ! -d /opt/stapxx ];then + cd /opt && git clone https://github.com/openresty/stapxx +fi + +# stap++ -I ./tapset -x 45266 --arg limit=10 samples/ngx-upstream-post-conn.sxx +# dpkg -i --force-overwrite /var/cache/apt/archives/linux-tools-common_5.4.0-128.144_all.deb + +# /opt/openresty-systemtap-toolkit/ngx-active-reqs -p 45266 + +# git clone git://sourceware.org/git/systemtap.git +# ./configure --prefix=/opt/stap --disable-docs --disable-publican --disable-refdocs CFLAGS="-g -O2" + +if [ ! -d /opt/FlameGraph ];then + cd /opt && git clone https://github.com/brendangregg/FlameGraph +fi + +for pid in ${pids[@]}; do + echo "strace:$pid" + if [ $1 == "lua" ]; then + # --without-luajit-gc64 | lua 模式编译时需要使用此参数 + /opt/openresty-systemtap-toolkit/ngx-sample-lua-bt -p $pid --luajit20 -t 30 >temp.bt + /opt/openresty-systemtap-toolkit/fix-lua-bt temp.bt >${name}_${pid}.bt + elif [ $1 == "c" ]; then + /opt/openresty-systemtap-toolkit/sample-bt -p $pid -t 10 -u > ${name}_${pid}.bt + else + echo "type is only lua/c" + exit + fi + + /opt/FlameGraph/stackcollapse-stap.pl ${name}_${pid}.bt >${name}_${pid}.cbt + /opt/FlameGraph/flamegraph.pl ${name}_${pid}.cbt >${name}_${pid}.svg + rm -f temp.bt ${name}_${pid}.bt ${name}_${pid}.cbt + echo "strace:$pid, end!" + echo "${name}_${pid}.svg -- make ok" +done + +# if [ $1 == "lua" ]; then +# # /opt/openresty-systemtap-toolkit/ngx-sample-lua-bt -p 377452 --luajit20 -t 30 >temp.bt +# /opt/openresty-systemtap-toolkit/ngx-sample-lua-bt -p $pid --luajit20 -t 30 >temp.bt +# # /opt/openresty-systemtap-toolkit/fix-lua-bt temp.bt >t1.bt +# /opt/openresty-systemtap-toolkit/fix-lua-bt temp.bt >${name}.bt +# elif [ $1 == "c" ]; then +# # /opt/openresty-systemtap-toolkit/sample-bt -p 496435 -t 10 -u > t2.bt +# /opt/openresty-systemtap-toolkit/sample-bt -p $pid -t 10 -u > ${name}.bt +# else +# echo "type is only lua/c" +# exit +# fi + + +# # debuginfo-install kernel-3.10.0-1160.80.1.el7.x86_64 +# # /opt/FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded +# # /opt/FlameGraph/flamegraph.pl perf.folded > perf.svg + +# /opt/FlameGraph/stackcollapse-stap.pl ${name}.bt >${name}.cbt +# /opt/FlameGraph/flamegraph.pl ${name}.cbt >${name}.svg +# rm -f temp.bt ${name}.bt ${name}.cbt + diff --git a/plugins/op_waf/t/ngx_demo.sh b/plugins/op_waf/t/ngx_demo.sh new file mode 100644 index 000000000..08ea0473b --- /dev/null +++ b/plugins/op_waf/t/ngx_demo.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# cd /www/server/mdserver-web/plugins/op_waf/t && sh ngx_demo.sh +# cd /www/wwwroot/dev156.cachecha.com && sh ngx_demo.sh + + +# only openresty +# pid=`ps -ef|grep openresty | grep -v grep | awk '{print $2}'` +# perf record -F 99 -p $pid -g -- sleep 60 + + +#全部 +perf record -F 99 -g -a -- sleep 60 + + +perf script -i perf.data &> perf.unfold +/opt/FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded +/opt/FlameGraph/flamegraph.pl perf.folded > perf.svg \ No newline at end of file diff --git a/plugins/op_waf/t/readme.md b/plugins/op_waf/t/readme.md new file mode 100755 index 000000000..f43425fd0 --- /dev/null +++ b/plugins/op_waf/t/readme.md @@ -0,0 +1,29 @@ +# 火焰图安装 [ubuntu 20.04] +``` + +sudo apt-get install -y linux-tools-common linux-tools-generic linux-tools-`uname -r` +apt-get update -y +sudo apt -y install elfutils +apt-get install -y systemtap gcc +sudo apt-get install -y linux-headers-generic gcc libcap-dev +apt install -y kernel-debuginfo-common kernel-debuginfo +``` + +# 测试有效性 +``` +stap -ve 'probe begin { log("hello systemtap!") exit() }' + + +stap -e 'probe kernel.function("sys_open") {log("hello world") exit()}' + + +stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}' +``` + + +# openresty 测试 +``` + +cd /www/server/mdserver-web/plugins/op_waf/t && sh ngx_debug.sh lua t1 +cd /www/server/mdserver-web/plugins/op_waf/t && sh ngx_debug.sh c t2 +``` diff --git a/plugins/op_waf/t/test.sh b/plugins/op_waf/t/test.sh new file mode 100755 index 000000000..182eb1876 --- /dev/null +++ b/plugins/op_waf/t/test.sh @@ -0,0 +1,22 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +# apt -y install apache2-utils +# yum -y install httpd-tools + +# ab -c 3000 -n 10000000 http://www.zzzvps.com/ +# /cc https://www.zzzvps.com/ 120 +# ab -c 10 -n 1000 http://t1.cn/wp-admin/index.php +# ab -c 1000 -n 1000000 http://dev156.cachecha.com/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +python3 index.py diff --git a/plugins/op_waf/tool_task.py b/plugins/op_waf/tool_task.py new file mode 100644 index 000000000..3387569fc --- /dev/null +++ b/plugins/op_waf/tool_task.py @@ -0,0 +1,167 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'op_waf' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getTaskConf(): + conf = getServerDir() + "/task_config.json" + return conf + + +def getConfigData(): + conf = getTaskConf() + if os.path.exists(conf): + return json.loads(mw.readFile(getTaskConf())) + return { + "task_id": -1, + "period": "day-n", + "where1": "7", + "hour": "0", + "minute": "15", + } + + +def createBgTask(): + removeBgTask() + createBgTaskByName(getPluginName()) + + +def createBgTaskByName(name): + cfg = getConfigData() + + _name = "[勿删]OP防火墙后台任务" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in cfg.keys() and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + print("计划任务已经存在!") + return True + + mw_dir = mw.getPanelDir() + cmd = ''' +mw_dir=%s +rname=%s +plugin_path=%s +script_path=%s +logs_file=$plugin_path/${rname}.log +''' % (mw_dir, name, getServerDir(), getPluginDir()) + cmd += 'echo "★【`date +"%Y-%m-%d %H:%M:%S"`】 STSRT★" >> $logs_file' + "\n" + cmd += 'echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" >> $logs_file' + "\n" + + cmd += 'echo "cd $mw_dir && source bin/activate && python3 $script_path/tool_task.py run >> $logs_file 2>&1"' + "\n" + cmd += 'cd $mw_dir && source bin/activate && python3 $script_path/tool_task.py run >> $logs_file 2>&1' + "\n" + + + cmd += 'echo "【`date +"%Y-%m-%d %H:%M:%S"`】 END★" >> $logs_file' + "\n" + cmd += 'echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" >> $logs_file' + "\n" + + params = { + 'name': _name, + 'type': cfg['period'], + 'week': "", + 'where1': cfg['where1'], + 'hour': cfg['hour'], + 'minute': cfg['minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + cfg["task_id"] = task_id + mw.writeFile(getTaskConf(), json.dumps(cfg)) + + +def removeBgTask(): + cfg = getConfigData() + for x in range(len(cfg)): + if "task_id" in cfg.keys() and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data['status']: + cfg["task_id"] = -1 + mw.writeFile(getTaskConf(), json.dumps(cfg)) + return True + return False + + +def getCpuUsed(): + path = getServerDir() + "/cpu.info" + if mw.isAppleSystem(): + import psutil + used = psutil.cpu_percent(interval=1) + mw.writeFile(path, str(int(used))) + else: + cmd = "top -bn 1 | fgrep 'Cpu(s)' | awk '{print 100 -$8}' | awk -F . '{print $1}'" + data = mw.execShell(cmd) + mw.writeFile(path, str(int(data[0].strip()))) + + +def pSqliteDb(dbname='logs'): + db_dir = getServerDir() + '/logs/' + conn = mw.M(dbname).dbPos(db_dir, "waf") + + conn.execute("PRAGMA synchronous = 0") + conn.execute("PRAGMA cache_size = 8000") + conn.execute("PRAGMA page_size = 32768") + conn.execute("PRAGMA journal_mode = wal") + conn.execute("PRAGMA journal_size_limit = 1073741824") + return conn + +def run(): + now_t = int(time.time()) + logs_conn = pSqliteDb('logs') + del_hot_log = "delete from logs where time<{}".format(now_t) + print(del_hot_log) + r = logs_conn.execute(del_hot_log) + return 'ok' + + +if __name__ == "__main__": + if len(sys.argv) > 1: + action = sys.argv[1] + if action == "remove": + removeBgTask() + elif action == "add": + createBgTask() + elif action == "run": + run() diff --git a/plugins/op_waf/waf/area_limit.json b/plugins/op_waf/waf/area_limit.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/plugins/op_waf/waf/area_limit.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/plugins/op_waf/waf/conf/readme.md b/plugins/op_waf/waf/conf/readme.md new file mode 100755 index 000000000..2bbbd3c5c --- /dev/null +++ b/plugins/op_waf/waf/conf/readme.md @@ -0,0 +1 @@ +自动生成配置文件 \ No newline at end of file diff --git a/plugins/op_waf/waf/config.json b/plugins/op_waf/waf/config.json new file mode 100755 index 000000000..9e74eba4e --- /dev/null +++ b/plugins/op_waf/waf/config.json @@ -0,0 +1,67 @@ +{ + "reqfile_path": "{$WAF_PATH}/html", + "retry": { + "retry_time": 180, + "is_open_global": 0, + "retry": 6, + "retry_cycle": 60 + }, + "log": true, + "scan": { + "status": 444, + "ps": "过滤常见扫描测试工具的渗透测试", + "open": true, + "reqfile": "" + }, + "cc": { + "status": 444, + "ps": "过虑CC攻击", + "limit": 120, + "endtime": 300, + "open": true, + "cycle": 60 + }, + "safe_verify": { + "status": 200, + "ps": "强制安全校验", + "reqfile": "safe_js.html", + "open": false, + "mode":"local", + "cpu": 50, + "auto": true, + "time": 86400 + }, + "get": { + "status": 200, + "ps": "过滤uri、uri参数中常见sql注入、xss等攻击", + "open": true, + "reqfile": "get.html" + }, + "log_save": 30, + "user-agent": { + "status": 200, + "ps": "通常用于过滤浏览器、蜘蛛及一些自动扫描器", + "open": true, + "reqfile": "user_agent.html" + }, + "other": { + "status": 200, + "ps": "其它非通用过滤", + "reqfile": "other.html" + }, + "cookie": { + "status": 200, + "ps": "过滤利用Cookie发起的渗透攻击", + "open": true, + "reqfile": "cookie.html" + }, + "logs_path": "/www/wwwlogs/waf", + "post": { + "status": 200, + "ps": "过滤POST参数中常见sql注入、xss等攻击", + "open": true, + "reqfile": "post.html" + }, + "area_limit":false, + "open": true +} \ No newline at end of file diff --git a/plugins/op_waf/waf/domains.json b/plugins/op_waf/waf/domains.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/plugins/op_waf/waf/domains.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/plugins/op_waf/waf/html/cookie.html b/plugins/op_waf/waf/html/cookie.html new file mode 100755 index 000000000..b0e79a85e --- /dev/null +++ b/plugins/op_waf/waf/html/cookie.html @@ -0,0 +1,38 @@ + + + + +OP网站防火墙 + + + + +
                                +
                                OP网站防火墙
                                +
                                +

                                您的请求带有不合法参数,已被网站管理员设置拦截!

                                +

                                可能原因:

                                +
                                  +
                                1. 您提交的内容包含危险的攻击请求
                                2. +
                                +

                                如何解决:

                                +
                                  +
                                1. 检查提交内容;
                                2. +
                                3. 如网站托管,请联系空间提供商;
                                4. +
                                5. 普通网站访客,请联系网站管理员;
                                6. +
                                +
                                +
                                + + + diff --git a/plugins/op_waf/waf/html/get.html b/plugins/op_waf/waf/html/get.html new file mode 100755 index 000000000..b0e79a85e --- /dev/null +++ b/plugins/op_waf/waf/html/get.html @@ -0,0 +1,38 @@ + + + + +OP网站防火墙 + + + + +
                                +
                                OP网站防火墙
                                +
                                +

                                您的请求带有不合法参数,已被网站管理员设置拦截!

                                +

                                可能原因:

                                +
                                  +
                                1. 您提交的内容包含危险的攻击请求
                                2. +
                                +

                                如何解决:

                                +
                                  +
                                1. 检查提交内容;
                                2. +
                                3. 如网站托管,请联系空间提供商;
                                4. +
                                5. 普通网站访客,请联系网站管理员;
                                6. +
                                +
                                +
                                + + + diff --git a/plugins/op_waf/waf/html/other.html b/plugins/op_waf/waf/html/other.html new file mode 100755 index 000000000..b0e79a85e --- /dev/null +++ b/plugins/op_waf/waf/html/other.html @@ -0,0 +1,38 @@ + + + + +OP网站防火墙 + + + + +
                                +
                                OP网站防火墙
                                +
                                +

                                您的请求带有不合法参数,已被网站管理员设置拦截!

                                +

                                可能原因:

                                +
                                  +
                                1. 您提交的内容包含危险的攻击请求
                                2. +
                                +

                                如何解决:

                                +
                                  +
                                1. 检查提交内容;
                                2. +
                                3. 如网站托管,请联系空间提供商;
                                4. +
                                5. 普通网站访客,请联系网站管理员;
                                6. +
                                +
                                +
                                + + + diff --git a/plugins/op_waf/waf/html/post.html b/plugins/op_waf/waf/html/post.html new file mode 100755 index 000000000..b0e79a85e --- /dev/null +++ b/plugins/op_waf/waf/html/post.html @@ -0,0 +1,38 @@ + + + + +OP网站防火墙 + + + + +
                                +
                                OP网站防火墙
                                +
                                +

                                您的请求带有不合法参数,已被网站管理员设置拦截!

                                +

                                可能原因:

                                +
                                  +
                                1. 您提交的内容包含危险的攻击请求
                                2. +
                                +

                                如何解决:

                                +
                                  +
                                1. 检查提交内容;
                                2. +
                                3. 如网站托管,请联系空间提供商;
                                4. +
                                5. 普通网站访客,请联系网站管理员;
                                6. +
                                +
                                +
                                + + + diff --git a/plugins/op_waf/waf/html/safe_js.html b/plugins/op_waf/waf/html/safe_js.html new file mode 100755 index 000000000..d65eef4c6 --- /dev/null +++ b/plugins/op_waf/waf/html/safe_js.html @@ -0,0 +1,167 @@ + + + + +OP网站防火墙|安全校验 + + + + +
                                +
                                OP网站防火墙|安全校验
                                +
                                +

                                3

                                +
                                + +
                                + + + + + diff --git a/plugins/op_waf/waf/html/user_agent.html b/plugins/op_waf/waf/html/user_agent.html new file mode 100755 index 000000000..b0e79a85e --- /dev/null +++ b/plugins/op_waf/waf/html/user_agent.html @@ -0,0 +1,38 @@ + + + + +OP网站防火墙 + + + + +
                                +
                                OP网站防火墙
                                +
                                +

                                您的请求带有不合法参数,已被网站管理员设置拦截!

                                +

                                可能原因:

                                +
                                  +
                                1. 您提交的内容包含危险的攻击请求
                                2. +
                                +

                                如何解决:

                                +
                                  +
                                1. 检查提交内容;
                                2. +
                                3. 如网站托管,请联系空间提供商;
                                4. +
                                5. 普通网站访客,请联系网站管理员;
                                6. +
                                +
                                +
                                + + + diff --git a/plugins/op_waf/waf/lua/init.lua b/plugins/op_waf/waf/lua/init.lua new file mode 100644 index 000000000..e3d2d5dd0 --- /dev/null +++ b/plugins/op_waf/waf/lua/init.lua @@ -0,0 +1,749 @@ + +local json = require "cjson" +local ngx_match = ngx.re.find + +local __WAF = require "waf_common" + +-- print(json.encode(__C)) +local C = __WAF:getInstance() + +local config = require "waf_config" +local site_config = require "waf_site" +local config_domains = require "waf_domains" + + +C:setConfData(config, site_config) +C:setDebug(true) + +-- C:D("config:"..C:to_json(config)) + +local get_html = require "html_get" +local post_html = require "html_post" +local other_html = require "html_other" +local user_agent_html = require "html_user_agent" +local cc_safe_js_html = require "html_safe_js" +local cookie_html = require "html_cookie" + +local args_rules = require "rule_args" +local ip_white_rules = require "rule_ip_white" +local ip_black_rules = require "rule_ip_black" +local ipv6_black_rules = require "rule_ipv6_black" +local scan_black_rules = require "rule_scan_black" +local user_agent_rules = require "rule_user_agent" +local post_rules = require "rule_post" +local cookie_rules = require "rule_cookie" +local url_rules = require "rule_url" +local url_white_rules = require "rule_url_white" + +local waf_area_limit = require "waf_area_limit" + +-- local server_name = string.gsub(C:get_sn(config_domains),'_','.') +local server_name = C:get_sn(config_domains) +local function initParams() + local data = {} + data['server_name'] = server_name + data['ip'] = C:get_real_ip(server_name) + data['ipn'] = C:arrip(data['ip']) + data['request_header'] = ngx.req.get_headers() + data['uri'] = tostring(ngx.unescape_uri(ngx.var.uri)) + data['uri_request_args'] = ngx.req.get_uri_args() + data['method'] = ngx.req.get_method() + data['request_uri'] = tostring(ngx.var.request_uri) + data['status_code'] = ngx.status + data['user_agent'] = data['request_header']['user-agent'] + data['cookie'] = ngx.var.http_cookie + data['time'] = ngx.time() + + return data +end + +local params = initParams() +-- C:D(C:to_json(params)) +C:setParams(params) + +local cpu_percent = ngx.shared.waf_limit:get("cpu_usage") +if not cpu_percent then + cpu_percent = 0 +end + +local function get_return_state(rstate,rmsg) + result = {} + result['status'] = rstate + result['msg'] = rmsg + return result +end + +local function get_waf_drop_ip() + local data = ngx.shared.waf_drop_ip:get_keys(0) + return data +end + +local function return_json(status,msg) + ngx.header.content_type = "application/json" + result = {} + result['status'] = status + result['msg'] = msg + ngx.say(json.encode(data)) + ngx.exit(200) +end + +local function is_chekc_table(data,strings) + if type(data) ~= 'table' then return 1 end + if not data then return 1 end + data = chekc_ip_timeout(data) + for k,v in pairs(data) + do + if strings == v['ip'] then + return 3 + end + end + return 2 +end + +local function remove_waf_drop_ip() + ngx.header.content_type = "application/json" + local ip = params['uri_request_args']['ip'] + + if not ip or not C:is_ipaddr(ip) then + local data = get_return_state(-1, "格式错误") + ngx.say(json.encode(data)) + ngx.exit(200) + return true + end + + local sign = "remove_waf_drop_ip" + if C:is_working(sign) then + local data = get_return_state(-1, "fail") + ngx.say(json.encode(data)) + ngx.exit(200) + return true + end + + C:lock_working(sign) + ngx.shared.waf_drop_ip:delete(ip) + C:unlock_working(sign) + + local data = get_return_state(0, "ok") + ngx.say(json.encode(data)) + ngx.exit(200) +end + +local function clean_waf_drop_ip() + ngx.header.content_type = "application/json" + + local sign = "clean_waf_drop_ip" + if C:is_working(sign) then + local data = get_return_state(-1, "fail") + ngx.say(json.encode(data)) + ngx.exit(200) + return true + end + + C:lock_working(sign) + ngx.shared.waf_drop_ip:flush_all() + C:unlock_working(sign) + + local data = get_return_state(0, "ok") + ngx.say(json.encode(data)) + ngx.exit(200) +end + +local function min_route() + if ngx.var.remote_addr ~= '127.0.0.1' then return false end + local uri = params['uri'] + if uri == '/get_waf_drop_ip' then + ngx.header.content_type = "application/json" + local data = get_return_state(0, get_waf_drop_ip()) + ngx.say(json.encode(data)) + ngx.exit(200) + elseif uri == '/remove_waf_drop_ip' then + remove_waf_drop_ip() + elseif uri == '/clean_waf_drop_ip' then + clean_waf_drop_ip() + end +end + +local function waf_get_args() + if not config['get']['open'] or not C:is_site_config('get') then return false end + -- C:D("waf_get_args:"..C:to_json(args_rules)..":"..json.encode(params['uri_request_args'])) + if C:ngx_match_list(args_rules, params['uri_request_args']) then + C:write_log('args','regular') + C:return_html(config['get']['status'], get_html) + return true + end + return false +end + + +local function waf_ip_white() + for _,rule in ipairs(ip_white_rules) + do + if C:compare_ip(rule) then + return true + end + end + return false +end + +local function waf_url_white() + if C:ngx_match_list(url_white_rules, params['uri']) then + return true + end + return false +end + +local function waf_ip_black() + -- ipv4 ip black + for _,rule in ipairs(ip_black_rules) + do + if C:compare_ip(rule) then + ngx.exit(config['cc']['status']) + return true + end + end + + -- ipv6 ip black + for _,rule in ipairs(ipv6_black_rules) + do + if rule == params['ip'] then + ngx.exit(config['cc']['status']) + return true + end + end + return false +end + + +local function waf_user_agent() + -- user_agent 过滤 + -- if not config['user-agent']['open'] or not C:is_site_config('user-agent') then return false end + + -- C:D("waf_user_agent;user_agent_rules:"..json.encode(user_agent_rules)..",ua:"..tostring(params['request_header']['user-agent'])) + if C:ngx_match_list(user_agent_rules, params['request_header']['user-agent']) then + -- C:D("waf_user_agent........... true") + C:write_log('user_agent','regular') + C:return_html(config['user-agent']['status'], user_agent_html) + return true + end + + -- C:D("waf_user_agent........... false") + return false +end + + +local function waf_drop_ip() + local ip = params['ip'] + local count = ngx.shared.waf_drop_ip:get(ip) + if not count then return false end + + local retry = config['retry']['retry'] + -- C:D("waf_drop;count:"..tostring(count)..",retry:"..tostring(retry)) + -- C:D("waf_drop;count > retry:"..tostring(count > retry)) + if count > retry then + -- C:D("waf_drop_ip........... true") + ngx.exit(config['cc']['status']) + return true + end + -- C:D("waf_drop_ip........... false") + return false +end + +local function waf_cc() + if not config['cc']['open'] then return false end + if not C:is_site_config('cc') then return false end + + local ip = params['ip'] + + -- 多次cc,才封禁。 + -- local ip_lock = ngx.shared.waf_drop_ip:get(ip) + -- if ip_lock then + -- if ip_lock > 0 then + -- ngx.exit(config['cc']['status']) + -- return true + -- end + -- end + + local request_uri = params['request_uri'] + + local token = ngx.md5(ip .. '_' .. request_uri) + local count = ngx.shared.waf_limit:get(token) + + local endtime = config['cc']['endtime'] + local waf_limit = config['cc']['limit'] + local cycle = config['cc']['cycle'] + + if count then + if count > waf_limit then + + local safe_count, _ = ngx.shared.waf_drop_sum:get(ip) + if not safe_count then + ngx.shared.waf_drop_sum:set(ip, 1, 86400) + safe_count = 1 + else + ngx.shared.waf_drop_sum:incr(ip, 1) + end + local lock_time = (endtime * safe_count) + if lock_time > 86400 then lock_time = 86400 end + + ngx.shared.waf_drop_ip:set(ip, 1, lock_time) + local reason = cycle..'秒内累计超过'..waf_limit..'次请求,封锁' .. lock_time .. '秒' + C:write_log('cc', reason) + C:log(params, 'cc',reason) + ngx.exit(config['cc']['status']) + return true + else + ngx.shared.waf_limit:incr(token, 1) + end + else + ngx.shared.waf_drop_sum:set(ip, 1, 86400) + ngx.shared.waf_limit:set(token, 1, cycle) + end + return false +end + +-- 是否符合开强制验证条件 +local function is_open_waf_cc_increase() + + if config['safe_verify']['open'] then + return true + end + + -- C:D("waf config:"..json.encode(config)) + if config['safe_verify']['auto'] then + if cpu_percent >= config['safe_verify']['cpu'] then + return true + end + + if site_config[server_name] and site_config[server_name]['safe_verify']['open'] then + if cpu_percent >= site_config[server_name]['safe_verify']['cpu'] then + return true + end + end + end + + return false +end + + +--强制验证是否使用正常浏览器访问网站 +local function waf_cc_increase() + if not is_open_waf_cc_increase() then return false end + + local ip = params['ip'] + local uri = params['uri'] + local cache_token = ngx.md5(ip .. '_' .. server_name) + + --判断是否已经通过验证 + if ngx.shared.waf_limit:get(cache_token) then return false end + + local cache_rand_key = ip..':rand' + local cache_rand = ngx.shared.waf_limit:get(cache_rand_key) + if not cache_rand then + cache_rand = C:get_random(8) + ngx.shared.waf_limit:set(cache_rand_key,cache_rand,30) + end + + local make_token = "waf_unbind_"..cache_rand.."_"..cache_token + local make_uri_str = "?token="..make_token + local make_uri = "/"..make_uri_str + + -- C:D("token:"..tostring(params['uri_request_args']['token'])) + if params['uri_request_args']['token'] then + ngx.header.content_type = "application/json" + local args_token = params['uri_request_args']['token'] + if args_token == make_token then + ngx.shared.waf_limit:set(cache_token, 1, tonumber(config['safe_verify']['time'])) + local data = get_return_state(0, "ok") + ngx.say(json.encode(data)) + ngx.exit(200) + end + local data = get_return_state(0, "unset") + ngx.say(json.encode(data)) + ngx.exit(200) + end + + if not config['safe_verify']['mode'] then return false end + + -- C:D(C:to_json(params['uri_request_args'])) + if config['safe_verify']['mode'] == 'url' then + local page = 'safe_verify_'..cache_token + local request_uri = params['request_uri'] + local to_url = '/?'..page..'&f='..request_uri + + local cache_url_key = cache_token..':url' + local cache_url_val = ngx.shared.waf_limit:get(cache_url_key) + + if params['uri_request_args'][page] then + local cc_html = ngx.re.gsub(cc_safe_js_html, "{uri}", make_uri_str) + return C:return_html(200, cc_html) + end + + if not cache_url_val then + ngx.shared.waf_limit:set(cache_url_key, request_uri,30) + return ngx.redirect(to_url) + end + end + + if config['safe_verify']['mode'] == 'local' then + local cc_html = ngx.re.gsub(cc_safe_js_html, "{uri}", make_uri_str) + C:return_html(200, cc_html) + end +end + + +local function waf_url() + if not config['get']['open'] or not C:is_site_config('get') then return false end + --正则-- + -- C:D("waf_url:"..json.encode(url_rules)..":uri:"..params["uri"]) + if C:ngx_match_list(url_rules, params["uri"]) then + C:write_log('url','regular') + C:return_html(config['get']['status'], get_html) + return true + end + return false +end + + +local function waf_scan_black() + -- 扫描软件禁止 + if not config['scan']['open'] or not C:is_site_config('scan') then return false end + if not params["cookie"] then + if C:ngx_match_string(scan_black_rules['cookie'], tostring(params["cookie"]),'scan') then + C:write_log('scan','regular') + ngx.exit(config['scan']['status']) + return true + end + end + + if C:ngx_match_string(scan_black_rules['args'], params["request_uri"], 'scan') then + C:write_log('scan','regular') + ngx.exit(config['scan']['status']) + return true + end + + for key,value in pairs(params["request_header"]) + do + if C:ngx_match_string(scan_black_rules['header'], key, 'scan') then + C:write_log('scan','regular') + ngx.exit(config['scan']['status']) + return true + end + end + return false +end + +local function waf_post() + if not config['post']['open'] or not C:is_site_config('post') then return false end + if params['method'] ~= "POST" then return false end + local content_length = tonumber(params["request_header"]['content-length']) + local max_len = 640 * 1020000 + if content_length > max_len then return false end + if C:get_boundary() then return false end + ngx.req.read_body() + + local request_args = params['uri_request_args'] + if not request_args then return false end + + for key, val in pairs(request_args) do + if type(val) == "table" then + if type(val[1]) == "boolean" then + return false + end + data = table.concat(val, ", ") + else + data = val + end + end + + -- C:D("post:"..json.encode(data)) + if C:ngx_match_list(post_rules, data) then + C:write_log('post','regular') + C:return_html(config['post']['status'], post_html) + return true + end + return false +end + +local function X_Forwarded() + + if params['method'] ~= "GET" then return false end + if not config['get']['open'] or not C:is_site_config('get') then return false end + if not params["request_header"]['X-forwarded-For'] then return false end + + if C:ngx_match_list(args_rules, params["request_header"]['X-forwarded-For']) then + C:write_log('args','regular') + C:return_html(config['get']['status'], get_html) + return true + end + return false +end + + +local function post_X_Forwarded() + if not config['post']['open'] or not C:is_site_config('post') then return false end + if params['method'] ~= "POST" then return false end + if not params["request_header"]['X-forwarded-For'] then return false end + if C:ngx_match_list(post_rules, params["request_header"]['X-forwarded-For']) then + C:write_log('post','regular') + C:return_html(config['post']['status'], post_html) + return true + end + return false +end + +local function url_ext() + if site_config[server_name] == nil then return false end + for _,rule in ipairs(site_config[server_name]['disable_ext']) + do + if C:ngx_match_string("\\."..rule.."$", params['uri'],'url_ext') then + if rule == "php" then + C:write_log('php_path','regular') + else + C:write_log('path','regular') + end + C:return_html(config['other']['status'], other_html) + return true + end + end + return false +end + +local function disable_upload_ext(ext) + if not ext then return false end + local ext = string.lower(ext) + if C:is_key(site_config[server_name]['disable_upload_ext'], ext) then + C:write_log('upload_ext', '上传扩展名黑名单') + C:return_html(config['other']['status'],other_html) + return true + end + return false +end + +local function post_data() + if params["method"] ~= "POST" then return false end + -- C:D("content-length:"..params["request_header"]['content-length']) + local content_length = tonumber(params["request_header"]['content-length']) + if not content_length then return false end + local max_len = 2560 * 1024000 + if content_length > max_len then return false end + local boundary = C:get_boundary() + -- C:D("boundary:".. tostring( boundary) ) + if boundary then + ngx.req.read_body() + local data = ngx.req.get_body_data() + if not data then return false end + local tmp = ngx.re.match(data,[[filename=\"(.+)\.(.*)\"]]) + if not tmp or not tmp[2] then return false end + -- C:D("upload_ext:".. tostring(tmp[2]) ) + disable_upload_ext(tmp[2]) + end + return false +end + +local function waf_cookie() + if not config['cookie']['open'] or not C:is_site_config('cookie') then return false end + if not params["request_header"]['cookie'] then return false end + if type(params["request_header"]['cookie']) ~= "string" then return false end + request_cookie = string.lower(params["request_header"]['cookie']) + if C:ngx_match_list(cookie_rules,request_cookie,'cookie') then + C:write_log('cookie','regular') + C:return_html(config['cookie']['status'],cookie_html) + return true + end + return false +end + +local geo=nil +local waf_country="" + +local function initmaxminddb() + if geo==nil then + maxminddb ,geo = pcall(function() return require 'waf_maxminddb' end) + if not maxminddb then + C:D("debug waf error on :"..tostring(geo)) + return nil + end + end + if type(geo)=='number' then return nil end + local ok2,data=pcall(function() + if not geo.initted() then + geo.init("{$WAF_ROOT}/GeoLite2-City.mmdb") + end + end ) + if not ok2 then + geo=nil + end +end + + +local function get_ip_country(ip) + initmaxminddb() + if type(geo)=='number' then return "2" end + if geo==nil then return "2" end + if geo.lookup==nil then return "2" end + local res,err=geo.lookup(ip or ngx.var.remote_addr) + if not res then + return "2" + else + -- C:D("res:"..tostring(res)) + return res + end +end + +local function get_country() + local ip = params['ip'] + local ip_local = ngx.shared.waf_limit:get("get_country"..ip) + if ip_local then + return ip_local + end + local ip_postion=get_ip_country(ip) + if ip_postion=="2" then return false end + if ip_postion["country"]==nil then return false end + if ip_postion["country"]["names"]==nil then return false end + if ip_postion["country"]["names"]["zh-CN"]==nil then return false end + ngx.shared.waf_limit:set("get_country"..ip,ip_postion["country"]["names"]["zh-CN"],3600) + return ip_postion["country"]["names"]["zh-CN"] +end + +local function area_limit(overall_country, server_name, status) + + if not status then + return false + end + + if overall_country and overall_country~="" and C:count_size(waf_area_limit)>=1 then + for k, val in pairs(waf_area_limit) do + -- C:D(tostring(k)..':'..tostring(val['site']['allsite']) ..':'.. tostring(val['site']['allsite'] == '1') ..':'.. tostring(val['site']['allsite'])) + if val['site']['allsite'] and val['site']['allsite'] == '1' and val['types'] == 'refuse' then + for rk, reg_val in pairs(val['region']) do + if rk == overall_country then + ngx.exit(403) + return true + end + end + end + + if val['site'][server_name] and val['site'][server_name] == '1' and val['types'] == 'refuse' then + for rk, reg_val in pairs(val['region']) do + if rk == overall_country then + ngx.exit(403) + return true + end + end + end + + if val['site']['allsite'] and val['site']['allsite'] == '1' and val['types'] == 'accept' then + for rk, reg_val in pairs(val['region']) do + if rk == overall_country then + return false + end + end + ngx.exit(403) + return true + end + + if val['site'][server_name] and val['site'][server_name] == '1' and val['types'] == 'accept' then + for rk, reg_val in pairs(val['region']) do + if rk == overall_country then + return false + end + end + ngx.exit(403) + return true + end + end + end + return false +end + +function run_app_waf() + min_route() + -- C:D("min_route") + + if site_config[server_name] and site_config[server_name]['open'] then + + + -- white ip + if waf_ip_white() then return true end + -- C:D("waf_ip_white") + + + -- url white + if waf_url_white() then return true end + -- C:D("waf_url_white") + + -- black ip + if waf_ip_black() then return true end + -- C:D("waf_ip_black") + + -- 封禁ip返回 + if waf_drop_ip() then return true end + -- C:D("waf_drop_ip") + + -- country limit + if config['area_limit'] then + local waf_country = get_country() + if waf_country then + if area_limit(waf_country, server_name, site_config[server_name]['open']) then return true end + end + end + + -- ua check + if waf_user_agent() then return true end + -- C:D("waf_user_agent") + if waf_url() then return true end + -- C:D("waf_url") + + -- cc setting + if waf_cc_increase() then return true end + -- C:D("waf_cc_increase") + if waf_cc() then return true end + -- C:D("waf_cc") + + -- cookie检查 + if waf_cookie() then return true end + -- C:D("waf_cookie") + + -- args参数拦截 + if waf_get_args() then return true end + -- C:D("waf_get_args") + + -- 扫描软件禁止 + if waf_scan_black() then return true end + -- C:D("waf_scan_black") + if waf_post() then return true end + -- C:D("waf_post") + -------------------------------------------------------- + -------------------------------------------------------- + if X_Forwarded() then return true end + -- C:D("X_Forwarded") + if post_X_Forwarded() then return true end + -- C:D("post_X_Forwarded") + if url_ext() then return true end + -- C:D("url_ext") + if post_data() then return true end + -- C:D("post_data") + end +end + + +local waf_run_status = nil +function waf() + if waf_run_status then + run_app_waf() + else + local ok,waf_err=pcall(function() + run_app_waf() + end) + + if waf_err ~= nil then + C:D("----waf error-----"..tostring(waf_err)) + end + + if ok then + waf_run_status = true + end + end +end + +waf() \ No newline at end of file diff --git a/plugins/op_waf/waf/lua/init_preload.lua b/plugins/op_waf/waf/lua/init_preload.lua new file mode 100644 index 000000000..8087b0065 --- /dev/null +++ b/plugins/op_waf/waf/lua/init_preload.lua @@ -0,0 +1,11 @@ +local waf_root = "{$WAF_ROOT}" +local waf_cpath = waf_root.."/waf/lua/?.lua;"..waf_root.."/waf/conf/?.lua;"..waf_root.."/waf/html/?.lua;" +local waf_sopath = waf_root.."/waf/conf/?.so;" + +if not package.path:find(waf_cpath) then + package.path = waf_cpath .. package.path +end + +if not package.cpath:find(waf_sopath) then + package.cpath = waf_sopath .. package.cpath +end \ No newline at end of file diff --git a/plugins/op_waf/waf/lua/init_worker.lua b/plugins/op_waf/waf/lua/init_worker.lua new file mode 100644 index 000000000..73ebb4125 --- /dev/null +++ b/plugins/op_waf/waf/lua/init_worker.lua @@ -0,0 +1,58 @@ +local waf_root = "{$WAF_ROOT}" + +-- local waf_cpath = waf_root.."/waf/lua/?.lua;"..waf_root.."/waf/conf/?.lua;"..waf_root.."/waf/html/?.lua;" +-- local waf_sopath = waf_root.."/waf/conf/?.so;" +-- if not package.path:find(waf_cpath) then +-- package.path = waf_cpath .. package.path +-- end + +-- if not package.cpath:find(waf_sopath) then +-- package.cpath = waf_sopath .. package.cpath +-- end + +local json = require "cjson" + +local __WAF_C = require "waf_common" +local WAF_C = __WAF_C:getInstance() + +local waf_config = require "waf_config" +local waf_site_config = require "waf_site" +WAF_C:setConfData(waf_config, waf_site_config) +WAF_C:setDebug(true) + +-- C:D("init worker"..tostring(ngx.worker.id())) + +local function waf_timer_stats_total_log(premature) + WAF_C:timer_stats_total() +end + +local function waf_clean_expire_data(premature) + WAF_C:clean_log() +end + +ngx.shared.waf_limit:set("cpu_usage", 0, 10) +function waf_timer_every_get_cpu(premature) + if WAF_C:file_exists('/proc/stat') then + local lua_cpu_percent = WAF_C:get_cpu_percent() + -- WAF_C:D("lua_cpu_percent:"..tostring(lua_cpu_percent)) + ngx.shared.waf_limit:set("cpu_usage", math.floor(lua_cpu_percent), 10) + else + local cpu_percent = WAF_C:read_file_body(waf_root.."/cpu.info") + -- WAF_C:D("cpu_usage:"..tostring(cpu_percent )) + if cpu_percent then + ngx.shared.waf_limit:set("cpu_usage", tonumber(cpu_percent), 10) + else + ngx.shared.waf_limit:set("cpu_usage", 0, 10) + end + end +end + +if ngx.worker.id() == 0 then + + ngx.timer.every(6, waf_timer_every_get_cpu) + -- 异步执行 + ngx.timer.every(3, waf_timer_stats_total_log) + ngx.timer.every(10, waf_clean_expire_data) + + WAF_C:cron() +end \ No newline at end of file diff --git a/plugins/op_waf/waf/lua/waf_common.lua b/plugins/op_waf/waf/lua/waf_common.lua new file mode 100644 index 000000000..2e2b6adca --- /dev/null +++ b/plugins/op_waf/waf/lua/waf_common.lua @@ -0,0 +1,836 @@ +local waf_root = "{$WAF_ROOT}" +local waf_cpath = waf_root.."/waf/lua/?.lua;"..waf_root.."/waf/conf/?.lua;"..waf_root.."/waf/html/?.lua;" +local waf_sopath = waf_root.."/waf/conf/?.so;" + +if not package.path:find(waf_cpath) then + package.path = waf_cpath .. package.path +end + +if not package.cpath:find(waf_sopath) then + package.cpath = waf_sopath .. package.cpath +end + +local setmetatable = setmetatable +local _M = { _VERSION = '0.02' } +local mt = { __index = _M } + +local json = require "cjson" +local sqlite3 = require "lsqlite3" + +local ngx_re = require "ngx.re" +local ngx_match = ngx.re.find + +local debug_mode = false + +local cpath = waf_root.."/waf/" +local log_dir = waf_root.."/logs/" +local rpath = cpath.."/rule/" + +function _M.new(self) + + local self = { + waf_root = waf_root, + cpath = cpath, + rpath = rpath, + logdir = log_dir, + config = '', + site_config = '', + server_name = '', + global_tatal = nil, + params = nil, + } + return setmetatable(self, mt) +end + + +-- function _M.getInstance(self) +-- if rawget(self, "instance") == nil then +-- rawset(self, "instance", self.new()) +-- end +-- assert(self.instance ~= nil) +-- return self.instance +-- end + +function _M.getInstance(self) + if self.instance == nil then + self.instance = self:new() + end + assert(self.instance ~= nil) + return self.instance +end + +-- 后台任务 +function _M.cron(self) + + local timer_every_import_data = function(premature) + + local llen, _ = ngx.shared.waf_limit:llen('waf_limit_logs') + if llen == 0 then + return true + end + + local db = self:initDB() + + db:exec([[BEGIN TRANSACTION]]) + + local stmt2 = db:prepare[[INSERT INTO logs(time, ip, domain, server_name, method, status_code, uri, user_agent, rule_name, reason) + VALUES(:time, :ip, :domain, :server_name, :method, :status_code, :uri, :user_agent, :rule_name, :reason)]] + + if not stmt2 then + self:D("waf timer db:prepare fail!:"..tostring(stmt2)) + return false + end + + for i=1,llen do + local data, _ = ngx.shared.waf_limit:lpop('waf_limit_logs') + -- self:D("waf_limit_logs:"..data) + if not data then + break + end + + local info = json.decode(data) + + stmt2:bind_names{ + time=info["time"], + ip=info["ip"], + domain=info["server_name"], + server_name=info["server_name"], + method=info["method"], + status_code=info["status_code"], + user_agent=info["user_agent"], + uri=info["request_uri"], + rule_name=info['rule_name'], + reason=info['reason'] + } + + local res, err = stmt2:step() + if tostring(res) == "5" then + self:D("waf the step database connection is busy, so it will be stored later.") + return false + end + stmt2:reset() + end + + local res, err = db:execute([[COMMIT]]) + if db and db:isopen() then + db:close() + end + + end + ngx.timer.every(0.5, timer_every_import_data) +end + +function _M.initDB(self) + local path = log_dir .. "/waf.db" + local db, err = sqlite3.open(path) + + if err then + self:D("initDB err:"..tostring(err)) + return nil + end + + db:exec([[PRAGMA synchronous = 0]]) + db:exec([[PRAGMA cache_size = 8000]]) + db:exec([[PRAGMA page_size = 32768]]) + db:exec([[PRAGMA journal_mode = wal]]) + db:exec([[PRAGMA journal_size_limit = 1073741824]]) + return db +end + +function _M.clean_log(self) + local db = self:initDB() + local now_date = os.date("*t") + local save_day = 90 + local save_date_timestamp = os.time{year=now_date.year, + month=now_date.month, day=now_date.day-save_day, hour=0} + -- delete expire data + db:exec("DELETE FROM web_logs WHERE time<"..tostring(save_date_timestamp)) +end + +function _M.log(self, args, rule_name, reason) + + args["rule_name"] = rule_name + args["reason"] = reason + + local push_data = json.encode(args) + + ngx.shared.waf_limit:rpush("waf_limit_logs", push_data) + -- self:D("push_data:"..push_data) + + -- local db = self:initDB() + + -- local stmt2 = db:prepare[[INSERT INTO logs(time, ip, domain, server_name, method, status_code, uri, user_agent, rule_name, reason) + -- VALUES(:time, :ip, :domain, :server_name, :method, :status_code, :uri, :user_agent, :rule_name, :reason)]] + + -- db:exec([[BEGIN TRANSACTION]]) + + -- stmt2:bind_names{ + -- time=args["time"], + -- ip=args["ip"], + -- domain=args["server_name"], + -- server_name=args["server_name"], + -- method=args["method"], + -- status_code=args["status_code"], + -- user_agent=args["user_agent"], + -- uri=args["request_uri"], + -- rule_name=rule_name, + -- reason=reason + -- } + + -- local res, err = stmt2:step() + -- -- self:D("LOG[1]:"..tostring(res)..":"..tostring(err)) + + -- if tostring(res) == "5" then + -- self.D("waf the step database connection is busy, so it will be stored later.") + -- return false + -- end + -- stmt2:reset() + + -- local res, err = db:execute([[COMMIT]]) + -- -- self:D("LOG[2]:"..tostring(res)..":"..tostring(err)) + -- if db and db:isopen() then + -- db:close() + -- end + -- return true +end + + +function _M.setDebug(self, mode) + debug_mode = mode +end + + +-- 调试方式 +function _M.D(self, msg) + if not debug_mode then return true end + + local _msg = '' + if type(msg) == 'table' then + _msg = _msg.."args->\n" + for key, val in pairs(msg) do + _msg = _msg..tostring(key)..':'..tostring(val).."\n" + end + elseif type(msg) == 'string' then + _msg = msg + elseif type(msg) == 'nil' then + _msg = 'nil' + else + _msg = msg + end + + local fp = io.open(waf_root.."/debug.log", "ab") + if fp == nil then + return nil + end + + -- local localtime = os.date("%Y-%m-%d %H:%M:%S") + local localtime = ngx.localtime() + if server_name then + fp:write(tostring(_msg) .. "\n") + else + fp:write(localtime..":"..tostring(_msg) .. "\n") + end + + fp:flush() + fp:close() + return true +end + +function _M.is_working(self,sign) + local work_status = ngx.shared.waf_limit:get(sign.."_working") + if work_status ~= nil and work_status == true then + return true + end + return false +end + +function _M.lock_working(self, sign) + local working_key = sign.."_working" + ngx.shared.waf_limit:set(working_key, true, 60) +end + +function _M.unlock_working(self, sign) + local working_key = sign.."_working" + ngx.shared.waf_limit:set(working_key, false) +end + + +local function write_file_clear(filename, body) + fp = io.open(filename,'w') + if fp == nil then + return nil + end + fp:write(body) + fp:flush() + fp:close() + return true +end + +function _M.setConfData( self, config, site_config ) + self.config = config + self.site_config = site_config + +end + + +function _M.setParams( self, params ) + self.params = params +end + +function _M.count_size(data) + local count=0 + if type(data)~="table" then return count end + for k,v in pairs(data) + do + count=count+1 + end + return count +end + + +function _M.is_min(self, ip1, ip2) + n = 0 + for _,v in ipairs({1,2,3,4}) + do + if ip1[v] == ip2[v] then + n = n + 1 + elseif ip1[v] > ip2[v] then + break + else + return false + end + end + return true +end + +function _M.is_max(self,ip1,ip2) + n = 0 + for _,v in ipairs({1,2,3,4}) + do + if ip1[v] == ip2[v] then + n = n + 1 + elseif ip1[v] < ip2[v] then + break + else + return false + end + end + return true +end + +function _M.split(self, str,reps ) + local rsList = {} + string.gsub(str,'[^'..reps..']+',function(w) + table.insert(rsList,w) + end) + return rsList +end + +function _M.arrip(self, ipstr) + if ipstr == 'unknown' then return {0,0,0,0} end + if string.find(ipstr,':') then return ipstr end + iparr = self:split(ipstr,'.') + iparr[1] = tonumber(iparr[1]) + iparr[2] = tonumber(iparr[2]) + iparr[3] = tonumber(iparr[3]) + iparr[4] = tonumber(iparr[4]) + return iparr +end + + +function _M.compare_ip(self,ips) + local ip = self.params["ip"] + local ipn = self.params["ipn"] + if ip == 'unknown' then return true end + if string.find(ip,':') then return false end + if not self:is_max(ipn,ips[2]) then return false end + if not self:is_min(ipn,ips[1]) then return false end + return true +end + + + +function _M.to_json(self, msg) + return json.encode(msg) +end + +function _M.return_state(status,msg) + result = {} + result['status'] = status + result['msg'] = msg + return result +end + +function _M.return_message(self, status, msg) + ngx.header.content_type = "application/json" + local data = self:return_state(status, msg) + ngx.say(json.encode(data)) + ngx.exit(200) +end + +function _M.return_html(self, status, html) + ngx.header.content_type = "text/html" + ngx.header.Cache_Control = "no-cache" + status = tonumber(status) + + -- self:D("return_html:"..tostring(status)) + if status == 200 then + ngx.say(html) + end + ngx.exit(status) +end + +function _M.read_file_body(self, filename) + local fp = io.open(filename, 'r') + if fp == nil then + return nil + end + local fbody = fp:read("*a") + fp:close() + if fbody == '' then + return nil + end + return fbody +end + +function _M.read_file(self, name) + f = self.rpath .. name .. '.json' + local fbody = self:read_file_body(f) + if fbody == nil then + return {} + end + + local data = json.decode(fbody) + return data +end + +function _M.file_exists(self,path) + local file = io.open(path, "rb") + if file then file:close() end + return file ~= nil +end + + +function _M.select_rule(self, rules) + if not rules then return {} end + new_rules = {} + for i,v in ipairs(rules) + do + if v[1] == 1 then + table.insert(new_rules,v[2]) + end + end + return new_rules +end + +function _M.read_file_table( self, name ) + return self:select_rule(self:read_file(name)) +end + + +function _M.read_file_body_decode(self, name) + return json.decode(self:read_file_body(name)) +end + +function _M.write_file(self, filename, body) + fp = io.open(filename,'ab') + if fp == nil then + return nil + end + fp:write(body) + fp:flush() + fp:close() + return true +end + +function _M.write_file_clear(self, filename, body) + return write_file_clear(filename, body) +end + +function _M.write_to_file(self, logstr) + local server_name = self.params['server_name'] + local filename = self.logdir .. '/' .. server_name .. '_' .. ngx.today() .. '.log' + self:write_file(filename, logstr) + return true +end + +-- 是否文件迁入数据库中 +function _M.is_migrating(self) + local migrating = self.waf_root +"/migrating" + local file = io.open(migrating, "rb") + if file then return true end + return false +end + + +function _M.continue_key(self,key) + key = tostring(key) + if string.len(key) > 64 then return false end; + local keys = { "content", "contents", "body", "msg", "file", "files", "img", "newcontent" } + for _,k in ipairs(keys) + do + if k == key then return false end; + end + return true; +end + + +function _M.array_len(self, arr) + if not arr then return 0 end + local count = 0 + for _,v in ipairs(arr) + do + count = count + 1 + end + return count +end + +function _M.is_ipaddr(self, client_ip) + local cipn = self:split(client_ip,'.') + if self:array_len(cipn) < 4 then return false end + for _,v in ipairs({1,2,3,4}) + do + local ipv = tonumber(cipn[v]) + if ipv == nil then return false end + if ipv > 255 or ipv < 0 then return false end + end + return true +end + +-- 定时异步同步统计信息 +function _M.timer_stats_total(self) + local total_path = self.cpath .. 'total.json' + local total = ngx.shared.waf_limit:get(total_path) + if not total then + return false + end + return self:write_file_clear(total_path,total) +end + +function _M.stats_total(self, name, rule) + local server_name = self.params['server_name'] + local total_path = cpath .. 'total.json' + local total = ngx.shared.waf_limit:get(total_path) + + if not total then + local tbody = self:read_file_body(total_path) + total = json.decode(tbody) + else + total = json.decode(total) + end + + if not total then return false end + + -- 开始计算 + if not total['sites'] then total['sites'] = {} end + if not total['sites'][server_name] then total['sites'][server_name] = {} end + if not total['sites'][server_name][name] then total['sites'][server_name][name] = 0 end + if not total['rules'] then total['rules'] = {} end + if not total['rules'][name] then total['rules'][name] = 0 end + if not total['total'] then total['total'] = 0 end + total['total'] = total['total'] + 1 + total['sites'][server_name][name] = total['sites'][server_name][name] + 1 + total['rules'][name] = total['rules'][name] + 1 + + ngx.shared.waf_limit:set(total_path,json.encode(total)) + + -- 异步执行 + -- 现在改再init_workder.lua 定时执行 + -- ngx.timer.every(3, timer_stats_total_log) +end + + +-- 获取配置域名 +function _M.get_sn(self, config_domains) + local request_name = ngx.var.server_name + local cache_name = ngx.shared.waf_limit:get(request_name) + if cache_name then return cache_name end + + for _,v in ipairs(config_domains) + do + for _,cd_name in ipairs(v['domains']) + do + if request_name == cd_name then + ngx.shared.waf_limit:set(request_name,v['name'],86400) + return v['name'] + end + end + end + return "unset" +end + +function _M.get_random(self,n) + math.randomseed(ngx.time()) + local t = { + "0","1","2","3","4","5","6","7","8","9", + "a","b","c","d","e","f","g","h","i","j", + "k","l","m","n","o","p","q","r","s","t", + "u","v","w","x","y","z", + "A","B","C","D","E","F","G","H","I","J", + "K","L","M","N","O","P","Q","R","S","T", + "U","V","W","X","Y","Z", + } + local s = "" + for i =1, n do + s = s .. t[math.random(#t)] + end + return s +end + + + +function _M.is_ngx_match_orgin(self,rule, match, sign) + if ngx_match(ngx.unescape_uri(match), rule, "isjo") then + error_rule = rule .. ' >> ' .. sign .. ':' .. match + return true + end + return false +end + + +function _M.ngx_match_string(self, rule, content,sign) + local t = self:is_ngx_match_orgin(rule, content, sign) + if t then + return true + end + + return false +end + +function _M.ngx_match_list(self, rules, content) + local args_type = type(content) + for i,rule in ipairs(rules) + do + if rule[1] == 1 then + if args_type == 'string' then + -- self:D("string: "..tostring(rule[2])..":".. tostring(content)..":"..tostring(rule[3])) + local t = self:is_ngx_match_orgin(rule[2], content, rule[3]) + if t then + return true + end + end + + if args_type == 'table' then + for _,arg_v in pairs(content) do + -- self:D("table : "..tostring(rule[2])..":".. tostring(arg_v)..":"..tostring(rule[3])) + local t = self:is_ngx_match_orgin(rule[2], arg_v, rule[3]) + if t then + return true + end + end + end + + end + end + return false +end + +function _M.is_ngx_match_ua(self, rules, content) + -- ngx.header.content_type = "text/html" + for i,rule in ipairs(rules) + do + -- 开启的规则,才匹配。 + if rule[1] == 1 then + local t = self:is_ngx_match_orgin(rule[2], content, rule[3]) + if t then + return true + end + end + end + return false +end + +function _M.is_ngx_match_post(self, rules, content) + for i,rule in ipairs(rules) + do + -- 开启的规则,才匹配。 + if rule[1] == 1 then + local t = self:is_ngx_match_orgin(rule[2],content, rule[3]) + if t then + return true + end + end + end + return false +end + + +function _M.write_log(self, name, rule) + local config = self.config + local params = self.params + + local ip = params['ip'] + local ngx_time = ngx.time() + + local retry = config['retry']['retry'] + local retry_time = config['retry']['retry_time'] + local retry_cycle = config['retry']['retry_cycle'] + + local count = ngx.shared.waf_drop_ip:get(ip) + if count then + ngx.shared.waf_drop_ip:incr(ip, 1) + else + ngx.shared.waf_drop_ip:set(ip, 1, retry_cycle) + end + + if config['log'] ~= true or self:is_site_config('log') ~= true then return false end + local method = params['method'] + if error_rule then + rule = error_rule + error_rule = nil + end + + local count = ngx.shared.waf_drop_ip:get(ip) + -- self:D("write_log; count:" ..tostring(count).. ",retry:" .. tostring(retry) ) + if (count > retry and name ~= 'cc') then + local safe_count,_ = ngx.shared.waf_drop_sum:get(ip) + if not safe_count then + ngx.shared.waf_drop_sum:set(ip, 1, 86400) + safe_count = 1 + else + ngx.shared.waf_drop_sum:incr(ip, 1) + end + local lock_time = retry_time * safe_count + if lock_time > 86400 then lock_time = 86400 end + + retry_times = retry + 1 + ngx.shared.waf_drop_ip:set(ip, retry_times, lock_time) + + local reason = retry_cycle .. '秒以内累计超过'..retry..'次以上非法请求,封锁'.. lock_time ..'秒' + self:log(params, name, reason) + elseif name ~= 'cc' then + self:log(params, name, rule) + end + + self:stats_total(name, rule) +end + + +function _M.get_real_ip(self, server_name) + local client_ip = "unknown" + local site_config = self.site_config + if site_config[server_name] then + if site_config[server_name]['cdn'] then + local request_header = ngx.req.get_headers() + for _,v in ipairs(site_config[server_name]['cdn_header']) + do + if request_header[v] ~= nil and request_header[v] ~= "" then + local header_tmp = request_header[v] + if type(header_tmp) == "table" then header_tmp = header_tmp[1] end + client_ip = self:split(header_tmp,',')[1] + -- return client_ip + break; + end + end + end + end + + + -- ipv6 + if type(client_ip) == 'table' then client_ip = "" end + if client_ip ~= "unknown" and ngx.re.match(client_ip,"^([a-fA-F0-9]*):") then + return client_ip + end + + -- ipv4 + if not ngx.re.match(client_ip,"\\d+\\.\\d+\\.\\d+\\.\\d+") == nil or not self:is_ipaddr(client_ip) then + client_ip = ngx.var.remote_addr + if client_ip == nil then + client_ip = "unknown" + end + end + return client_ip +end + + +function _M.is_site_config(self,cname) + local site_config = self.site_config + if site_config[server_name] ~= nil then + if cname == 'cc' then + return site_config[server_name][cname]['open'] + else + return site_config[server_name][cname] + end + end + return true +end + +function _M.get_boundary(self) + local header = self.params["request_header"]["content-type"] + if not header then return nil end + if type(header) == "table" then + header = header[1] + end + + local m = string.match(header, ";%s*boundary=\"([^\"]+)\"") + if m then + return m + end + return string.match(header, ";%s*boundary=([^\",;]+)") +end + + +function _M.is_key(self, arr, key) + for _,v in ipairs(arr) do + if v == key then + return true + end + end + return false +end + +function _M.get_cpu_stat(self) + local cpu_total = 0 + local fp = io.open('/proc/stat','r') + local cpu_line = fp:read() + fp:close() + + local list = ngx_re.split(cpu_line," ") + table.remove(list,1) + table.remove(list,1) + + local idie = list[4] + for i,v in pairs(list) + do + cpu_total = cpu_total + v + end + + local use_percent = tonumber(100-(idie/cpu_total)*100) + return cpu_total,idie,use_percent +end + +function _M.get_cpu_percent(self) + local cpu_total,idie,use_percent = self:get_cpu_stat() + ngx.sleep(2) + local cpu_total2,idie2,use_percent2 = self:get_cpu_stat() + local cpu_usage_percent = tonumber(100-(((idie2-idie)/(cpu_total2-cpu_total))*100)) + return cpu_usage_percent +end + + +function _M.return_post_data(self) + if method ~= "POST" then return false end + content_length = tonumber(self.params["request_header"]['content-length']) + if not content_length then return false end + max_len = 2560 * 1024000 + if content_length > max_len then return false end + local boundary = self:get_boundary() + if boundary then + ngx.req.read_body() + local data = ngx.req.get_body_data() + if not data then return false end + local tmp = ngx.re.match(data,[[filename=\"(.+)\.(.*)\"]]) + if not tmp then return false end + if not tmp[2] then return false end + return tmp[2] + + end + return false +end + + +function _M.t(self) + ngx.say(',,,') +end + +return _M diff --git a/plugins/op_waf/waf/lua/waf_maxminddb.lua b/plugins/op_waf/waf/lua/waf_maxminddb.lua new file mode 100644 index 000000000..d98f03630 --- /dev/null +++ b/plugins/op_waf/waf/lua/waf_maxminddb.lua @@ -0,0 +1,362 @@ +--[[ + Copyright 2017-now anjia (anjia0532@gmail.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +]] + +-- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/f96633e2428f8f7bcc1e2a7a28b747b33233a8db/resty/maxminddb.lua#L5-L12 + +local ffi = require ('ffi') +local ffi_new = ffi.new +local ffi_str = ffi.string +local ffi_cast = ffi.cast +local ffi_gc = ffi.gc +local C = ffi.C + +local _M ={} +_M._VERSION = '1.3.3' +local mt = { __index = _M } + +-- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/f96633e2428f8f7bcc1e2a7a28b747b33233a8db/resty/maxminddb.lua#L36-L126 +ffi.cdef[[ + +typedef long int ssize_t; + +typedef unsigned int mmdb_uint128_t __attribute__ ((__mode__(TI))); + +typedef struct MMDB_entry_s { + struct MMDB_s *mmdb; + uint32_t offset; +} MMDB_entry_s; + +typedef struct MMDB_lookup_result_s { + bool found_entry; + MMDB_entry_s entry; + uint16_t netmask; +} MMDB_lookup_result_s; + +typedef struct MMDB_entry_data_s { + bool has_data; + union { + uint32_t pointer; + const char *utf8_string; + double double_value; + const uint8_t *bytes; + uint16_t uint16; + uint32_t uint32; + int32_t int32; + uint64_t uint64; + mmdb_uint128_t uint128; + bool boolean; + float float_value; + }; + + uint32_t offset; + uint32_t offset_to_next; + uint32_t data_size; + uint32_t type; +} MMDB_entry_data_s; + +typedef struct MMDB_entry_data_list_s { + MMDB_entry_data_s entry_data; + struct MMDB_entry_data_list_s *next; +} MMDB_entry_data_list_s; + +typedef struct MMDB_description_s { + const char *language; + const char *description; +} MMDB_description_s; + +typedef struct MMDB_metadata_s { + uint32_t node_count; + uint16_t record_size; + uint16_t ip_version; + const char *database_type; + struct { + size_t count; + const char **names; + } languages; + uint16_t binary_format_major_version; + uint16_t binary_format_minor_version; + uint64_t build_epoch; + struct { + size_t count; + MMDB_description_s **descriptions; + } description; +} MMDB_metadata_s; + +typedef struct MMDB_ipv4_start_node_s { + uint16_t netmask; + uint32_t node_value; +} MMDB_ipv4_start_node_s; + +typedef struct MMDB_s { + uint32_t flags; + const char *filename; + ssize_t file_size; + const uint8_t *file_content; + const uint8_t *data_section; + uint32_t data_section_size; + const uint8_t *metadata_section; + uint32_t metadata_section_size; + uint16_t full_record_byte_size; + uint16_t depth; + MMDB_ipv4_start_node_s ipv4_start_node; + MMDB_metadata_s metadata; +} MMDB_s; + +typedef char * pchar; + +MMDB_lookup_result_s MMDB_lookup_string(MMDB_s *const mmdb, const char *const ipstr, int *const gai_error,int *const mmdb_error); +int MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb); +int MMDB_aget_value(MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, const char *const *const path); +char *MMDB_strerror(int error_code); + +int MMDB_get_entry_data_list(MMDB_entry_s *start, MMDB_entry_data_list_s **const entry_data_list); +void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list); +void MMDB_close(MMDB_s *const mmdb); +const char *gai_strerror(int errcode); +]] + +-- error codes +-- https://github.com/maxmind/libmaxminddb/blob/master/include/maxminddb.h#L66 +local MMDB_SUCCESS = 0 +local MMDB_FILE_OPEN_ERROR = 1 +local MMDB_CORRUPT_SEARCH_TREE_ERROR = 2 +local MMDB_INVALID_METADATA_ERROR = 3 +local MMDB_IO_ERROR = 4 +local MMDB_OUT_OF_MEMORY_ERROR = 5 +local MMDB_UNKNOWN_DATABASE_FORMAT_ERROR = 6 +local MMDB_INVALID_DATA_ERROR = 7 +local MMDB_INVALID_LOOKUP_PATH_ERROR = 8 +local MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR = 9 +local MMDB_INVALID_NODE_NUMBER_ERROR = 10 +local MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR = 11 + +-- data type +-- https://github.com/maxmind/libmaxminddb/blob/master/include/maxminddb.h#L40 +local MMDB_DATA_TYPE_EXTENDED = 0 +local MMDB_DATA_TYPE_POINTER = 1 +local MMDB_DATA_TYPE_UTF8_STRING = 2 +local MMDB_DATA_TYPE_DOUBLE = 3 +local MMDB_DATA_TYPE_BYTES = 4 +local MMDB_DATA_TYPE_UINT16 = 5 +local MMDB_DATA_TYPE_UINT32 = 6 +local MMDB_DATA_TYPE_MAP = 7 +local MMDB_DATA_TYPE_INT32 = 8 +local MMDB_DATA_TYPE_UINT64 = 9 +local MMDB_DATA_TYPE_UINT128 = 10 +local MMDB_DATA_TYPE_ARRAY = 11 +local MMDB_DATA_TYPE_CONTAINER = 12 +local MMDB_DATA_TYPE_END_MARKER = 13 +local MMDB_DATA_TYPE_BOOLEAN = 14 +local MMDB_DATA_TYPE_FLOAT = 15 + +-- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/f96633e2428f8f7bcc1e2a7a28b747b33233a8db/resty/maxminddb.lua#L136-L138 +-- you should install the libmaxminddb to your system +local maxm = ffi.load('{$WAF_ROOT}/waf/mmdb/lib/libmaxminddb.{$MMDB_FILE_SUFFIX}') +--https://github.com/maxmind/libmaxminddb +local mmdb = ffi_new('MMDB_s') +local initted = false + +local function mmdb_strerror(rc) + return ffi_str(maxm.MMDB_strerror(rc)) +end + +local function gai_strerror(rc) + return ffi_str(C.gai_strerror(rc)) +end + +function _M.init(dbfile) + if not initted then + local maxmind_ready = maxm.MMDB_open(dbfile,0,mmdb) + + if maxmind_ready ~= MMDB_SUCCESS then + return nil, mmdb_strerror(maxmind_ready) + end + + initted = true + + ffi_gc(mmdb, maxm.MMDB_close) + end + return initted +end + +function _M.initted() + return initted +end + +-- https://github.com/maxmind/libmaxminddb/blob/master/src/maxminddb.c#L1938 +-- LOCAL MMDB_entry_data_list_s *dump_entry_data_list( FILE *stream, MMDB_entry_data_list_s *entry_data_list, int indent, int *status) +local function _dump_entry_data_list(entry_data_list,status) + + if not entry_data_list then + return nil,MMDB_INVALID_DATA_ERROR + end + + local entry_data_item = entry_data_list[0].entry_data + local data_type = entry_data_item.type + local data_size = entry_data_item.data_size + local result + + if data_type == MMDB_DATA_TYPE_MAP then + result = {} + + local size = entry_data_item.data_size + + entry_data_list = entry_data_list[0].next + + while(size > 0 and entry_data_list) + do + entry_data_item = entry_data_list[0].entry_data + data_type = entry_data_item.type + data_size = entry_data_item.data_size + + if MMDB_DATA_TYPE_UTF8_STRING ~= data_type then + return nil,MMDB_INVALID_DATA_ERROR + end + + local key = ffi_str(entry_data_item.utf8_string,data_size) + + if not key then + return nil,MMDB_OUT_OF_MEMORY_ERROR + end + + local val + entry_data_list = entry_data_list[0].next + entry_data_list,status,val = _dump_entry_data_list(entry_data_list) + + if status ~= MMDB_SUCCESS then + return nil,status + end + + result[key] = val + + size = size -1 + end + + + elseif entry_data_list[0].entry_data.type == MMDB_DATA_TYPE_ARRAY then + local size = entry_data_list[0].entry_data.data_size + result = {} + + entry_data_list = entry_data_list[0].next + + local i = 1 + while(i <= size and entry_data_list) + do + local val + entry_data_list,status,val = _dump_entry_data_list(entry_data_list) + + if status ~= MMDB_SUCCESS then + return nil,nil,val + end + + result[i] = val + i = i + 1 + end + + + else + entry_data_item = entry_data_list[0].entry_data + data_type = entry_data_item.type + data_size = entry_data_item.data_size + + local val + -- string type "key":"val" + -- other type "key":val + -- default other type + if data_type == MMDB_DATA_TYPE_UTF8_STRING then + val = ffi_str(entry_data_item.utf8_string,data_size) + if not val then + status = MMDB_OUT_OF_MEMORY_ERROR + return nil,status + end + elseif data_type == MMDB_DATA_TYPE_BYTES then + val = ffi_str(ffi_cast('char * ',entry_data_item.bytes),data_size) + if not val then + status = MMDB_OUT_OF_MEMORY_ERROR + return nil,status + end + elseif data_type == MMDB_DATA_TYPE_DOUBLE then + val = entry_data_item.double_value + elseif data_type == MMDB_DATA_TYPE_FLOAT then + val = entry_data_item.float_value + elseif data_type == MMDB_DATA_TYPE_UINT16 then + val = entry_data_item.uint16 + elseif data_type == MMDB_DATA_TYPE_UINT32 then + val = entry_data_item.uint32 + elseif data_type == MMDB_DATA_TYPE_BOOLEAN then + val = entry_data_item.boolean + elseif data_type == MMDB_DATA_TYPE_UINT64 then + val = entry_data_item.uint64 + elseif data_type == MMDB_DATA_TYPE_INT32 then + val = entry_data_item.int32 + else + return nil,MMDB_INVALID_DATA_ERROR + end + + result = val + entry_data_list = entry_data_list[0].next + end + + status = MMDB_SUCCESS + return entry_data_list,status,result +end + +function _M.lookup(ip) + + if not initted then + return nil, "not initialized" + end + + -- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/f96633e2428f8f7bcc1e2a7a28b747b33233a8db/resty/maxminddb.lua#L159-L176 + local gai_error = ffi_new('int[1]') + local mmdb_error = ffi_new('int[1]') + + local result = maxm.MMDB_lookup_string(mmdb,ip,gai_error,mmdb_error) + + if mmdb_error[0] ~= MMDB_SUCCESS then + return nil,'lookup failed: ' .. mmdb_strerror(mmdb_error[0]) + end + + if gai_error[0] ~= MMDB_SUCCESS then + return nil,'lookup failed: ' .. gai_strerror(gai_error[0]) + end + + if true ~= result.found_entry then + return nil,'not found' + end + + local entry_data_list = ffi_cast('MMDB_entry_data_list_s **const',ffi_new("MMDB_entry_data_list_s")) + + local status = maxm.MMDB_get_entry_data_list(result.entry,entry_data_list) + + if status ~= MMDB_SUCCESS then + return nil,'get entry data failed: ' .. mmdb_strerror(status) + end + + local head = entry_data_list[0] -- Save so this can be passed to free fn. + local _,status,result = _dump_entry_data_list(entry_data_list) + maxm.MMDB_free_entry_data_list(head) + + if status ~= MMDB_SUCCESS then + return nil,'dump entry data failed: ' .. mmdb_strerror(status) + end + + + return result +end + +-- copy from https://github.com/lilien1010/lua-resty-maxminddb/blob/master/resty/maxminddb.lua#L208 +-- https://www.maxmind.com/en/geoip2-databases you should download the mmdb file from maxmind + +return _M; \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/args.json b/plugins/op_waf/waf/rule/args.json new file mode 100644 index 000000000..6cfbbd73d --- /dev/null +++ b/plugins/op_waf/waf/rule/args.json @@ -0,0 +1,28 @@ +[ + [1, "\\.\\./\\.\\./", "\u76ee\u5f55\u4fdd\u62a41", 0], + [1, "/\\*", "\u76ee\u5f55\u4fdd\u62a42", 0], + [1, "(?:etc\\/\\W*passwd)", "\u76ee\u5f55\u4fdd\u62a43", 0], + [1, "(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\\:\\/", "PHP\u6d41\u534f\u8bae\u8fc7\u6ee41", 0], + [1, "\\:\\$", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee41", 0], + [1, "\\$\\{", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee42", 0], + [1, "base64_decode\\(", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee43", 0], + [1, "(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|char|chr|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\\(", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee44", 0], + [1, "\\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\\[", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee45", 0], + [1, "\\s+(or|xor|and)\\s+.*(=|<|>|'|\")", "SQL\u6ce8\u5165\u8fc7\u6ee41", 0], + [1, "select.+(from|limit)", "SQL\u6ce8\u5165\u8fc7\u6ee42", 0], + [1, "(?:(union(.*?)select))", "SQL\u6ce8\u5165\u8fc7\u6ee43", 0], + [1, "sleep\\((\\s*)(\\d*)(\\s*)\\)", "SQL\u6ce8\u5165\u8fc7\u6ee45", 0], + [1, "benchmark\\((.*)\\,(.*)\\)", "SQL\u6ce8\u5165\u8fc7\u6ee46", 0], + [1, "(?:from\\W+information_schema\\W)", "SQL\u6ce8\u5165\u8fc7\u6ee47", 0], + [1, "(?:(?:current_)user|database|schema|connection_id)\\s*\\(", "SQL\u6ce8\u5165\u8fc7\u6ee48", 0], + [1, "into(\\s+)+(?:dump|out)file\\s*", "SQL\u6ce8\u5165\u8fc7\u6ee49", 0], + [1, "group\\s+by.+\\(", "SQL\u6ce8\u5165\u8fc7\u6ee410", 0], + [1, "\\<(iframe|script|body|img|layer|div|meta|style|base|object|input)", "XSS\u8fc7\u6ee41", 0], + [0, "(onmouseover|onerror|onload)\\=", "XSS\u8fc7\u6ee42", 0], + [1, "(invokefunction|call_user_func_array|\\\\think\\\\)", "ThinkPHP payload\u5c01\u5835", 0], + [1, "^url_array\\[.*\\]$", "Metinfo6.x XSS\u6f0f\u6d1e", 0], + [1, "(extractvalue\\(|concat\\(0x|user\\(\\)|substring\\(|count\\(\\*\\)|substring\\(hex\\(|updatexml\\()", "SQL\u62a5\u9519\u6ce8\u5165\u8fc7\u6ee401", 0], + [1, "(@@version|load_file\\(|NAME_CONST\\(|exp\\(\\~|floor\\(rand\\(|geometrycollection\\(|multipoint\\(|polygon\\(|multipolygon\\(|linestring\\(|multilinestring\\()", "SQL\u62a5\u9519\u6ce8\u5165\u8fc7\u6ee402", 0], + [1, "(substr\\()", "SQL\u6ce8\u5165\u8fc7\u6ee410", 0], + [1, "\\|+\\s+[\\w\\W]+=[\\w\\W]+", "SQL\u6ce8\u5165\u8fc7\u6ee41", 0] +] \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/cookie.json b/plugins/op_waf/waf/rule/cookie.json new file mode 100755 index 000000000..51d5e565e --- /dev/null +++ b/plugins/op_waf/waf/rule/cookie.json @@ -0,0 +1,21 @@ +[ + [1, "\\.\\./\\.\\./", "\u76ee\u5f55\u4fdd\u62a41", 0], + [1, "(?:etc\\/\\W*passwd)", "\u76ee\u5f55\u4fdd\u62a43", 0], + [1, "(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\\:\\/", "PHP\u6d41\u534f\u8bae\u8fc7\u6ee41", 0], + [1, "\\:\\$", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee41", 0], + [1, "\\$\\{", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee42", 0], + [1, "base64_decode\\(", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee43", 0], + [1, "\\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\\[", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee45", 0], + [1, "\\b(or|xor|and)\\b.*(=|<|>|'|\")", "SQL\u6ce8\u5165\u8fc7\u6ee41", 0], + [1, "select.+(from|limit)", "SQL\u6ce8\u5165\u8fc7\u6ee42", 0], + [1, "(?:(union(.*?)select))", "SQL\u6ce8\u5165\u8fc7\u6ee43", 0], + [0, "having|load_file", "SQL\u6ce8\u5165\u8fc7\u6ee44", 0], + [1, "sleep\\((\\s*)(\\d*)(\\s*)\\)", "SQL\u6ce8\u5165\u8fc7\u6ee45", 0], + [1, "benchmark\\((.*)\\,(.*)\\)", "SQL\u6ce8\u5165\u8fc7\u6ee46", 0], + [1, "(?:from\\W+information_schema\\W)", "SQL\u6ce8\u5165\u8fc7\u6ee47", 0], + [1, "(?:(?:current_)user|database|schema|connection_id)\\s*\\(", "SQL\u6ce8\u5165\u8fc7\u6ee48", 0], + [1, "into(\\s+)+(?:dump|out)file\\s*", "SQL\u6ce8\u5165\u8fc7\u6ee49", 0], + [1, "group\\s+by.+\\(", "SQL\u6ce8\u5165\u8fc7\u6ee410", 0], + [0, "\\<(iframe|script|body|img|layer|div|meta|style|base|object|input)", "XSS\u8fc7\u6ee41", 0], + [1, "(onmouseover|onerror|onload)\\=", "XSS\u8fc7\u6ee42", 0] +] \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/ip_black.json b/plugins/op_waf/waf/rule/ip_black.json new file mode 100755 index 000000000..86bb9fea1 --- /dev/null +++ b/plugins/op_waf/waf/rule/ip_black.json @@ -0,0 +1 @@ +[[[94, 130, 9, 116], [94, 130, 9, 116]]] \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/ip_white.json b/plugins/op_waf/waf/rule/ip_white.json new file mode 100755 index 000000000..ae76d5de3 --- /dev/null +++ b/plugins/op_waf/waf/rule/ip_white.json @@ -0,0 +1 @@ +[[[127,0,0,1], [127, 0, 0, 255]]] \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/ipv6_black.json b/plugins/op_waf/waf/rule/ipv6_black.json new file mode 100755 index 000000000..0637a088a --- /dev/null +++ b/plugins/op_waf/waf/rule/ipv6_black.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/post.json b/plugins/op_waf/waf/rule/post.json new file mode 100755 index 000000000..92213f01e --- /dev/null +++ b/plugins/op_waf/waf/rule/post.json @@ -0,0 +1,23 @@ +[ + [1, "(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\\:\\/", "PHP\u6d41\u534f\u8bae\u8fc7\u6ee41", 0], + [1, "base64_decode\\(", "\u4e00\u53e5\u8bdd*\u5c4f\u853d\u7684\u5173\u952e\u5b57*\u8fc7\u6ee41", 0], + [1, "(?:define|eval|file_get_contents|include|require_once|shell_exec|phpinfo|system|passthru|chr|char|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog|file_put_contents|fopen|urldecode|scandir)\\(", "\u4e00\u53e5\u8bdd*\u5c4f\u853d\u7684\u5173\u952e\u5b57*\u8fc7\u6ee42", 0], + [1, "\\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)", "\u4e00\u53e5\u8bdd*\u5c4f\u853d\u7684\u5173\u952e\u5b57*\u8fc7\u6ee43", 0], [1, "\\s+(or|xor|and)\\s+(=|<|>|'|\")", "SQL\u6ce8\u5165\u8fc7\u6ee41", 0], + [1, "select\\s+.+(from|limit)\\s+", "SQL\u6ce8\u5165\u8fc7\u6ee42", 0], + [1, "(?:(union(.*?)select))", "SQL\u6ce8\u5165\u8fc7\u6ee43", 0], + [1, "sleep\\((\\s*)(\\d*)(\\s*)\\)", "SQL\u6ce8\u5165\u8fc7\u6ee45", 0], + [1, "benchmark\\((.*)\\,(.*)\\)", "SQL\u6ce8\u5165\u8fc7\u6ee46", 0], + [1, "(?:from\\W+information_schema\\W)", "SQL\u6ce8\u5165\u8fc7\u6ee47", 0], + [1, "(?:(?:current_)user|database|schema|connection_id)\\s*\\(", "SQL\u6ce8\u5165\u8fc7\u6ee48", 0], + [1, "into(\\s+)+(?:dump|out)file\\s*", "SQL\u6ce8\u5165\u8fc7\u6ee49", 0], + [1, "group\\s+by.+\\(", "SQL\u6ce8\u5165\u8fc7\u6ee410", 0], + [0, "\\<(iframe|script|body|img|layer|div|meta|style|base|object|input)", "XSS\u8fc7\u6ee41", 0], + [0, "(onmouseover|onerror|onload)\\=", "XSS\u8fc7\u6ee42", 0], + [1, "(extractvalue\\(|concat\\(0x|user\\(\\)|substring\\(|count\\(\\*\\)|substring\\(hex\\(|updatexml\\()", "SQL\u62a5\u9519\u6ce8\u5165\u8fc7\u6ee401", 0], + [1, "(@@version|load_file\\(|NAME_CONST\\(|exp\\(\\~|floor\\(rand\\(|geometrycollection\\(|multipoint\\(|polygon\\(|multipolygon\\(|linestring\\(|multilinestring\\()", "SQL\u62a5\u9519\u6ce8\u5165\u8fc7\u6ee402", 0], + [1, "(substr\\()", "SQL\u6ce8\u5165\u8fc7\u6ee410", 0], + [1, "(ORD\\(|MID\\(|IFNULL\\(|CAST\\(|CHAR\\))", "SQL\u6ce8\u5165\u8fc7\u6ee41", 0], + [1, "(EXISTS\\(|SELECT\\#|\\(SELECT)", "SQL\u6ce8\u5165\u8fc7\u6ee41", 0], + [1, "(array_map\\(\"ass)", "\u83dc\u5200\u6d41\u91cf\u8fc7\u6ee4", 0], + [1, "(?:define|eval|file_get_contents|include|require_once|shell_exec|phpinfo|system|passthru|chr|char|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog|file_put_contents|fopen|urldecode)\\(", "\u4e00\u53e5\u8bdd*\u5c4f\u853d\u7684\u5173\u952e\u5b57*\u8fc7\u6ee42", 0] +] \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/scan_black.json b/plugins/op_waf/waf/rule/scan_black.json new file mode 100755 index 000000000..a567c4a80 --- /dev/null +++ b/plugins/op_waf/waf/rule/scan_black.json @@ -0,0 +1 @@ +{"header": "(Acunetix-Aspect|Acunetix-Aspect-Password|Acunetix-Aspect-Queries|X-WIPP|X-RequestManager-Memo|X-Request-Memo|X-Scan-Memo)", "args": "(/acunetix-wvs-test-for-some-inexistent-file|netsparker|acunetix_wvs_security_test|AppScan|XSS@HERE)", "cookie": "(CustomCookie|acunetixCookie)"} \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/url.json b/plugins/op_waf/waf/rule/url.json new file mode 100755 index 000000000..28604915d --- /dev/null +++ b/plugins/op_waf/waf/rule/url.json @@ -0,0 +1 @@ +[[1, "\\.(htaccess|mysql_history|bash_history|DS_Store|idea|user\\.ini)", "\u6587\u4ef6\u76ee\u5f55\u8fc7\u6ee41", 0], [1, "\\.(bak|inc|old|mdb|sql|php~|swp|java|class)$", "\u6587\u4ef6\u76ee\u5f55\u8fc7\u6ee42", 0], [1, "^/(vhost|bbs|host|wwwroot|www|site|root|backup|data|ftp|db|admin|website|web).*\\.(rar|sql|zip|tar\\.gz|tar)$", "\u6587\u4ef6\u76ee\u5f55\u8fc7\u6ee43", 0], [1, "/(hack|shell|spy|phpspy)\\.php$", "PHP\u811a\u672c\u6267\u884c\u8fc7\u6ee41", 0], [1, "^/(attachments|css|uploadfiles|static|forumdata|cache|avatar)/(\\w+).(php|jsp)$", "PHP\u811a\u672c\u6267\u884c\u8fc7\u6ee42", 0], [1, "(?:(union(.*?)select))", "SQL\u6ce8\u5165\u8fc7\u6ee41", 0], [1, "(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\\(", "\u4e00\u53e5\u8bdd\u6728\u9a6c\u8fc7\u6ee41", 1]] \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/url_white.json b/plugins/op_waf/waf/rule/url_white.json new file mode 100755 index 000000000..2293f0683 --- /dev/null +++ b/plugins/op_waf/waf/rule/url_white.json @@ -0,0 +1 @@ +[[1,"^/(phpmyadmin)","MySQL[phpMyAdmin]", 0]] \ No newline at end of file diff --git a/plugins/op_waf/waf/rule/user_agent.json b/plugins/op_waf/waf/rule/user_agent.json new file mode 100755 index 000000000..3e8a87a05 --- /dev/null +++ b/plugins/op_waf/waf/rule/user_agent.json @@ -0,0 +1,8 @@ +[ + [1,"(HTTrack|Apache-HttpClient|harvest|audit|dirbuster|pangolin|nmap|sqln|hydra|Parser|libwww|BBBike|sqlmap|w3af|owasp|Nikto|fimap|havij|zmeu|BabyKrokodil|netsparker|httperf| SF/)","关键词过滤1",0], + [1,"(ApacheBench)","AB测试",0], + [1,"(Amazonbot)","Amazon爬虫",0], + [1,"(SemrushBot)","Semrush爬虫",0], + [1,"(^$|Apache-HttpClient|colly|curl|okhttp|Go-http-client|python-requests|Python-urllib|python-httpx|Scrapy|aiohttp|Nmap Scripting Engine|Java|fasthttp|Wget)","非法脚本",0], + [1,"(CensysInspect|intelx\\.io_bot|InternetMeasurement|ips-agent|MJ12Bot|NetcraftSurveyAgent|SemrushBot|l9scan|SEOlyt|kirkland-signature|ZoominfoBot|Expanse|CheckMarkNetwork|dotbot|Pandalytics|Screaming Frog SEO Spider|W3C_CSS_Validator_JFouffa)","非法扫描器",0] +] diff --git a/plugins/op_waf/waf/site.json b/plugins/op_waf/waf/site.json new file mode 100755 index 000000000..9e26dfeeb --- /dev/null +++ b/plugins/op_waf/waf/site.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/plugins/op_waf/waf/total.json b/plugins/op_waf/waf/total.json new file mode 100644 index 000000000..f9a5f91d2 --- /dev/null +++ b/plugins/op_waf/waf/total.json @@ -0,0 +1 @@ +{"rules":{"path":0,"php_path":0,"upload_ext":0,"user_agent":0,"scan":0,"cookie":0,"post":0,"args":0,"url":0,"cc":0},"sites":{},"total":0} \ No newline at end of file diff --git a/plugins/openresty/check.sh b/plugins/openresty/check.sh new file mode 100755 index 000000000..e6b36e6de --- /dev/null +++ b/plugins/openresty/check.sh @@ -0,0 +1,35 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# OpenResty服务名称 +service_name="openresty" + +# 检查OpenResty是否正在运行 +if systemctl is-active --quiet "$service_name"; then + # 检查是否存在僵尸进程 + zombie_processes=$(ps -ef | grep -i openresty | grep -v grep | awk '{print $2}' | xargs ps -o state= -p 2>/dev/null | grep -c Z) + if [ "$zombie_processes" -gt 0 ]; then + echo "kill nginx 僵尸进程" + ps -ef|grep nginx| grep -v grep| awk '{print $2}' | xargs kill -9 + echo "检测到OpenResty僵尸进程,正在重启服务..." + systemctl restart "$service_name" + echo "服务已重启" + else + echo "OpenResty运行正常" + fi +else + echo "kill nginx" + ps -ef|grep nginx| grep -v grep| awk '{print $2}' | xargs kill -9 + echo "OpenResty未运行,正在启动服务..." + systemctl start "$service_name" + echo "服务已启动" +fi + +NGINX_IDS=`ps -ef|grep nginx | grep -v grep| awk '{print $2}'` +if [ "$NGINX_IDS" == "" ];then + ps -ef|grep nginx| grep -v grep| awk '{print $2}' | xargs kill -9 + systemctl start "$service_name" + echo "OpenResty未运行,正在启动服务..." +fi + diff --git a/plugins/openresty/conf/lua.conf b/plugins/openresty/conf/lua.conf new file mode 100644 index 000000000..fd0ddba03 --- /dev/null +++ b/plugins/openresty/conf/lua.conf @@ -0,0 +1,13 @@ +lua_package_path "{$SERVER_PATH}/web_conf/nginx/lua/?.lua;{$SERVER_PATH}/openresty/lualib/?.lua;;"; +lua_package_cpath "{$SERVER_PATH}/web_conf/nginx/lua/?.so;{$SERVER_PATH}/openresty/lualib/?.so;;"; + +lua_code_cache on; + +#init_by_lua_file +init_by_lua_file {$SERVER_PATH}/web_conf/nginx/lua/empty.lua; + +#init_worker_by_lua +init_worker_by_lua_file {$SERVER_PATH}/web_conf/nginx/lua/empty.lua; + +#access_by_lua_file +access_by_lua_file {$SERVER_PATH}/web_conf/nginx/lua/empty.lua; \ No newline at end of file diff --git a/plugins/openresty/conf/nginx.conf b/plugins/openresty/conf/nginx.conf new file mode 100644 index 000000000..d0b66ff63 --- /dev/null +++ b/plugins/openresty/conf/nginx.conf @@ -0,0 +1,93 @@ +user {$OS_USER} {$OS_USER_GROUP}; +worker_processes auto; +worker_cpu_affinity auto; +error_log {$SERVER_PATH}/openresty/nginx/logs/error.log crit; +pid {$SERVER_PATH}/openresty/nginx/logs/nginx.pid; + +worker_rlimit_nofile 65535; + +events +{ + use {$EVENT_MODEL}; + worker_connections 51200; + multi_accept on; + + # file/video close the mutex lock when under high load + # accept_mutex off; +} + +http +{ + include mime.types; + + include {$SERVER_PATH}/web_conf/nginx/lua/lua.conf; + #include proxy.conf; + + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" "$request_time"'; + + server_names_hash_bucket_size 512; + client_header_buffer_size 32k; + large_client_header_buffers 4 32k; + client_body_buffer_size 50m; + client_max_body_size 50m; + + # video big file opt + # aio threads; + directio 4m; + output_buffers 16 512k; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + keepalive_timeout 60; + + fastcgi_connect_timeout 300; + fastcgi_send_timeout 300; + fastcgi_read_timeout 300; + fastcgi_buffer_size 64k; + fastcgi_buffers 4 64k; + fastcgi_busy_buffers_size 128k; + fastcgi_temp_file_write_size 256k; + fastcgi_intercept_errors on; + + gzip on; + gzip_min_length 1k; + gzip_buffers 4 16k; + gzip_http_version 1.1; + gzip_comp_level 9; + gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml; + gzip_vary on; + gzip_proxied expired no-cache no-store private auth; + gzip_disable "MSIE [1-6]\."; + + limit_conn_zone $binary_remote_addr zone=perip:10m; + limit_conn_zone $server_name zone=perserver:10m; + + # CACEH_BEGIN + proxy_buffering on; + proxy_buffer_size 1024k; + proxy_buffers 16 1024k; + proxy_busy_buffers_size 2048k; + proxy_temp_file_write_size 2048k; + proxy_cache_path {$SERVER_PATH}/openresty/nginx/proxy_cache_temp levels=1:2 keys_zone=mw_cache:512m inactive=5m max_size=2g use_temp_path=off; + #proxy timeout + proxy_connect_timeout 3s; + proxy_read_timeout 5s; + proxy_send_timeout 5s; + + fastcgi_cache_key "$scheme$request_method$host$request_uri"; + fastcgi_cache_path {$SERVER_PATH}/openresty/nginx/fastcgi_cache_temp levels=1:2 keys_zone=mw_cache_fcgi:100m inactive=60m max_size=5g; + fastcgi_cache_use_stale error timeout invalid_header http_500; + fastcgi_ignore_headers Cache-Control Expires Set-Cookie; + # CACEH_END + + server_tokens off; + access_log /dev/null; + + include {$SERVER_PATH}/web_conf/nginx/vhost/*.conf; +} + diff --git a/plugins/openresty/conf/vhost/0.nginx_status.conf b/plugins/openresty/conf/vhost/0.nginx_status.conf new file mode 100644 index 000000000..751645a4c --- /dev/null +++ b/plugins/openresty/conf/vhost/0.nginx_status.conf @@ -0,0 +1,10 @@ +server { + listen 80; + listen [::]:80; + server_name 127.0.0.1; + allow 127.0.0.1; + location /nginx_status { + stub_status on; + access_log off; + } +} \ No newline at end of file diff --git a/plugins/openresty/conf/vhost/0.websocket.conf b/plugins/openresty/conf/vhost/0.websocket.conf new file mode 100644 index 000000000..60e726a51 --- /dev/null +++ b/plugins/openresty/conf/vhost/0.websocket.conf @@ -0,0 +1,4 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} diff --git a/plugins/openresty/ico.png b/plugins/openresty/ico.png new file mode 100644 index 000000000..39d56b20c Binary files /dev/null and b/plugins/openresty/ico.png differ diff --git a/plugins/openresty/index.html b/plugins/openresty/index.html new file mode 100755 index 000000000..8b7a409df --- /dev/null +++ b/plugins/openresty/index.html @@ -0,0 +1,24 @@ +
                                + +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                配置修改

                                +

                                负载状态

                                +

                                性能调整

                                +

                                错误日志

                                +

                                维护功能

                                +
                                +
                                +
                                +
                                +
                                + +
                                + + \ No newline at end of file diff --git a/plugins/openresty/index.py b/plugins/openresty/index.py new file mode 100755 index 000000000..58c921fab --- /dev/null +++ b/plugins/openresty/index.py @@ -0,0 +1,668 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import threading +import subprocess +import re + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'openresty' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + # print(args) + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':',2) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':',2) + tmp[t[0]] = t[1] + # print(tmp) + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def clearTemp(): + path_bin = getServerDir() + "/nginx" + mw.execShell('rm -rf ' + path_bin + '/client_body_temp') + mw.execShell('rm -rf ' + path_bin + '/fastcgi_temp') + mw.execShell('rm -rf ' + path_bin + '/proxy_temp') + mw.execShell('rm -rf ' + path_bin + '/scgi_temp') + mw.execShell('rm -rf ' + path_bin + '/uwsgi_temp') + + +def getConf(): + path = getServerDir() + "/nginx/conf/nginx.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + '/conf/nginx.conf' + return path + + +def getOs(): + data = {} + data['os'] = mw.getOs() + ng_exe_bin = getServerDir() + "/nginx/sbin/nginx" + + # if mw.isAppleSystem(): + # data['auth'] = True + # return mw.getJson(data) + + if checkAuthEq(ng_exe_bin, 'root'): + data['auth'] = True + else: + data['auth'] = False + return mw.getJson(data) + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/nginx.tpl" + return path + + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pid\s*(.*);' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getFileOwner(filename): + import pwd + stat = os.lstat(filename) + uid = stat.st_uid + pw = pwd.getpwuid(uid) + return pw.pw_name + + +def checkAuthEq(file, owner='root'): + fowner = getFileOwner(file) + if (fowner == owner): + return True + return False + + +def confReplace(): + service_path = mw.getServerDir() + content = mw.readFile(getConfTpl()) + content = content.replace('{$SERVER_PATH}', service_path) + + user = 'www' + user_group = 'www' + + current_os = mw.getOs() + if current_os == 'darwin': + # macosx do + # user = mw.execShell( + # "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + user = 'midoks' + user_group = 'staff' + content = content.replace('{$EVENT_MODEL}', 'kqueue') + elif current_os.startswith('freebsd'): + content = content.replace('{$EVENT_MODEL}', 'kqueue') + else: + content = content.replace('{$EVENT_MODEL}', 'epoll') + + content = content.replace('{$OS_USER}', user) + content = content.replace('{$OS_USER_GROUP}', user_group) + + # ng_conf_md5 = '' + # ng_conf_md5_file = getServerDir() + '/nginx_conf.md5' + # if not os.path.exists(ng_conf_md5_file): + # ng_conf_md5 = mw.md5(content) + # mw.writeFile(ng_conf_md5_file, ng_conf_md5) + # else: + # ng_conf_md5 = mw.writeFile(ng_conf_md5_file).strip() + + # 主配置文件 + nconf = getServerDir() + '/nginx/conf/nginx.conf' + mw.writeFile(nconf, content) + + # lua配置 + lua_conf_dir = mw.getServerDir() + '/web_conf/nginx/lua' + if not os.path.exists(lua_conf_dir): + mw.execShell('mkdir -p ' + lua_conf_dir) + + lua_conf = lua_conf_dir + '/lua.conf' + lua_conf_tpl = getPluginDir() + '/conf/lua.conf' + lua_content = mw.readFile(lua_conf_tpl) + lua_content = lua_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(lua_conf, lua_content) + + empty_lua = lua_conf_dir + '/empty.lua' + if not os.path.exists(empty_lua): + mw.writeFile(empty_lua, '') + + mw.opLuaMakeAll() + + # 静态配置 + php_conf = mw.getServerDir() + '/web_conf/php/conf' + if not os.path.exists(php_conf): + mw.execShell('mkdir -p ' + php_conf) + static_conf = mw.getServerDir() + '/web_conf/php/conf/enable-php-00.conf' + if not os.path.exists(static_conf): + mw.writeFile(static_conf, 'set $PHP_ENV 0;') + + # vhost + vhost_dir = mw.getServerDir() + '/web_conf/nginx/vhost' + vhost_tpl_dir = getPluginDir() + '/conf/vhost' + if not os.path.exists(vhost_dir): + mw.execShell('mkdir -p ' + vhost_dir) + + vhost_list = ['0.websocket.conf', '0.nginx_status.conf'] + for f in vhost_list: + a_conf = vhost_dir + '/' + f + a_conf_tpl = vhost_tpl_dir + '/' + f + if not os.path.exists(a_conf): + mw.writeFile(a_conf, mw.readFile(a_conf_tpl)) + + # copy resty lib + src_resty_dir = getPluginDir()+'/resty/*' + dst_resty_dir = getServerDir()+'/lualib/resty' + mw.execShell('cp -rf ' + src_resty_dir + ' ' + dst_resty_dir) + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + + # OpenResty is not installed + if not os.path.exists(getServerDir()): + print("ok") + exit(0) + + # init.d + file_bin = initD_path + '/' + getPluginName() + if not os.path.exists(initD_path): + os.mkdir(initD_path) + + # initd replace + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # config replace + confReplace() + + # give nginx root permission + ng_exe_bin = getServerDir() + "/nginx/sbin/nginx" + if not checkAuthEq(ng_exe_bin, 'root'): + user = 'www' + user_group = 'www' + current_os = mw.getOs() + if current_os == 'darwin': + user = 'root' + user_group = 'staff' + args = getArgs() + if not 'pwd' in args: + print("权限不足,需要认证启动!") + exit(0) + + sudoPwd = args['pwd'] + cmd_own = 'chown -R ' + user+':' + user_group + ' ' + ng_exe_bin + mw.execShell('echo %s|sudo -S %s' % (sudoPwd, cmd_own)) + cmd_mod = 'chmod 755 ' + ng_exe_bin + mw.execShell('echo %s|sudo -S %s' % (sudoPwd, cmd_mod)) + cmd_s = 'chmod u+s ' + ng_exe_bin + mw.execShell('echo %s|sudo -S %s' % (sudoPwd, cmd_s)) + + # systemd + # /usr/lib/systemd/system + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/openresty.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/openresty.service.tpl' + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def status(): + pid_file = getPidFile() + if not os.path.exists(pid_file): + return 'stop' + return 'start' + + +def restyOp(method): + file = initDreplace() + + # 启动时,先检查一下配置文件 + check = getServerDir() + "/bin/openresty -t" + check_data = mw.execShell(check) + if not check_data[1].find('test is successful') > -1: + return check_data[1] + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service openresty ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' openresty') + if data[1] == '': + return 'ok' + return data[1] + + +def op_submit_systemctl_restart(): + current_os = mw.getOs() + if current_os.startswith("freebsd"): + mw.execShell('service openresty restart') + return True + + mw.execShell('systemctl restart openresty') + return True + + +def op_submit_init_restart(file): + mw.execShell(file + ' restart') + + +def restyOp_restart(): + file = initDreplace() + + # 启动时,先检查一下配置文件 + check = getServerDir() + "/bin/openresty -t" + check_data = mw.execShell(check) + if not check_data[1].find('test is successful') > -1: + return 'ERROR: 配置出错
                                ' + check_data[1].replace("\n", '
                                ') + '
                                ' + + if not mw.isAppleSystem(): + threading.Timer(2, op_submit_systemctl_restart).start() + return 'ok' + + threading.Timer(2, op_submit_init_restart, args=(file,)).start() + return 'ok' + + +def start(): + return restyOp('start') + + +def stop(): + r = restyOp('stop') + pid_file = getPidFile() + if os.path.exists(pid_file): + os.remove(pid_file) + return r + + +def restart(): + return restyOp_restart() + + +def reload(): + confReplace() + return restyOp('reload') + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status openresty | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable openresty') + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable openresty') + return 'ok' + +def getNgxStatusPort(): + ngx_status_file = mw.getServerDir() + '/web_conf/nginx/vhost/0.nginx_status.conf' + content = mw.readFile(ngx_status_file) + rep = r'listen\s*(.*);' + tmp = re.search(rep, content) + port = tmp.groups()[0].strip() + return port + + +def runInfo(): + op_status = status() + if op_status == 'stop': + return mw.returnJson(False, "未启动!") + + port = getNgxStatusPort() + # 取Openresty负载状态 + try: + url = 'http://127.0.0.1:%s/nginx_status' % port + result = mw.httpGet(url, timeout=3) + tmp = result.split() + data = {} + data['active'] = tmp[2] + data['accepts'] = tmp[9] + data['handled'] = tmp[7] + data['requests'] = tmp[8] + data['Reading'] = tmp[11] + data['Writing'] = tmp[13] + data['Waiting'] = tmp[15] + return mw.getJson(data) + except Exception as e: + try: + url = 'http://' + mw.getHostAddr() + ':%s/nginx_status' % port + result = mw.httpGet(url) + tmp = result.split() + data = {} + data['active'] = tmp[2] + data['accepts'] = tmp[9] + data['handled'] = tmp[7] + data['requests'] = tmp[8] + data['Reading'] = tmp[11] + data['Writing'] = tmp[13] + data['Waiting'] = tmp[15] + return mw.getJson(data) + except Exception as e: + return mw.returnJson(False, "oprenresty异常!") + + except Exception as e: + return mw.returnJson(False, "oprenresty not started!") + + +def errorLogPath(): + return getServerDir() + '/nginx/logs/error.log' + + +def getCfg(): + cfg = getConf() + content = mw.readFile(cfg) + + unitrep = "[kmgKMG]" + cfg_args = [ + {"name": "worker_processes", "ps": "处理进程,auto表示自动,数字表示进程数", 'type': 2}, + {"name": "worker_connections", "ps": "最大并发链接数", 'type': 2}, + {"name": "keepalive_timeout", "ps": "连接超时时间", 'type': 2}, + {"name": "gzip", "ps": "是否开启压缩传输", 'type': 1}, + {"name": "gzip_min_length", "ps": "最小压缩文件", 'type': 2}, + {"name": "gzip_comp_level", "ps": "压缩率", 'type': 2}, + {"name": "client_max_body_size", "ps": "最大上传文件", 'type': 2}, + {"name": "server_names_hash_bucket_size", + "ps": "服务器名字的hash表大小", 'type': 2}, + {"name": "client_header_buffer_size", "ps": "客户端请求头buffer大小", 'type': 2}, + ] + + # {"name": "client_body_buffer_size", "ps": "请求主体缓冲区"} + rdata = [] + for i in cfg_args: + rep = r"(%s)\s+(\w+)" % i["name"] + k = re.search(rep, content) + if not k: + return mw.returnJson(False, "获取 key {} 失败".format(k)) + k = k.group(1) + v = re.search(rep, content) + if not v: + return mw.returnJson(False, "获取 value {} 失败".format(v)) + v = v.group(2) + + if re.search(unitrep, v): + u = str.upper(v[-1]) + v = v[:-1] + if len(u) == 1: + psstr = u + "B," + i["ps"] + else: + psstr = u + "," + i["ps"] + else: + u = "" + + kv = {"name": k, "value": v, "unit": u, + "ps": i["ps"], "type": i["type"]} + rdata.append(kv) + + return mw.returnJson(True, "ok", rdata) + +def replaceChar(value, index, new_char): + return value[:index] + new_char + value[index+1:] + +def makeWorkerCpuAffinity(val): + if val == "auto": + return "auto" + + if mw.isNumber(val): + core_num = int(val) + default_core_str = "0"*core_num + core_num_arr = [] + for x in range(core_num): + t = replaceChar(default_core_str, x , "1") + core_num_arr.append(t) + return " ".join(core_num_arr) + + return 'auto' + +def setCfg(): + + args = getArgs() + data = checkArgs(args, [ + 'worker_processes', 'worker_connections', 'keepalive_timeout', + 'gzip', 'gzip_min_length', 'gzip_comp_level', 'client_max_body_size', + 'server_names_hash_bucket_size', 'client_header_buffer_size' + ]) + if not data[0]: + return data[1] + + cfg = getConf() + mw.backFile(cfg) + content = mw.readFile(cfg) + + unitrep = "[kmgKMG]" + cfg_args = [ + {"name": "worker_processes", "ps": "处理进程,auto表示自动,数字表示进程数", 'type': 2}, + {"name": "worker_connections", "ps": "最大并发链接数", 'type': 2}, + {"name": "keepalive_timeout", "ps": "连接超时时间", 'type': 2}, + {"name": "gzip", "ps": "是否开启压缩传输", 'type': 1}, + {"name": "gzip_min_length", "ps": "最小压缩文件", 'type': 2}, + {"name": "gzip_comp_level", "ps": "压缩率", 'type': 2}, + {"name": "client_max_body_size", "ps": "最大上传文件", 'type': 2}, + {"name": "server_names_hash_bucket_size", + "ps": "服务器名字的hash表大小", 'type': 2}, + {"name": "client_header_buffer_size", "ps": "客户端请求头buffer大小", 'type': 2}, + ] + + # print(args) + for k, v in args.items(): + # print(k, v) + rep = r"%s\s+[^kKmMgG\;\n]+" % k + if k == "worker_processes" or k == "gzip": + if not re.search(r"auto|on|off|\d+", v): + return mw.returnJson(False, '参数值错误') + else: + if not re.search(r"\d+", v): + return mw.returnJson(False, '参数值错误,请输入数字整数') + + if k == "worker_processes" : + k_wca = "worker_cpu_affinity" + rep_wca = r"%s\s+[^\;\n]+" % k_wca + v_wca = makeWorkerCpuAffinity(v) + newconf = "%s %s" % (k_wca, v_wca) + content = re.sub(rep_wca, newconf, content) + + + if re.search(rep, content): + newconf = "%s %s" % (k, v) + content = re.sub(rep, newconf, content) + elif re.search(rep, content): + newconf = "%s %s" % (k, v) + content = re.sub(rep, newconf, content) + + mw.writeFile(cfg, content) + isError = mw.checkWebConfig() + if (isError != True): + mw.restoreFile(cfg) + return mw.returnJson(False, 'ERROR: 配置出错
                                ' + isError.replace("\n", '
                                ') + '
                                ') + + mw.restartWeb() + return mw.returnJson(True, '设置成功') + + +def cronAddCheck(): + try: + import tool_task + tool_task.createBgTask() + return mw.returnJson(True, '添加检查任务成功') + except Exception as e: + return mw.returnJson(False, '添加检查任务失败:'+str(e)) + +def cronDelCheck(): + try: + import tool_task + tool_task.removeBgTask() + return mw.returnJson(True, '删除检查任务成功') + except Exception as e: + return mw.returnJson(False, '删除检查任务失败:'+str(e)) + +def cronCheck(): + return 'ok' + + +def installPreInspection(): + return 'ok' + + +if __name__ == "__main__": + + version = '1.27.1' + version_pl = getServerDir() + "/version.pl" + if os.path.exists(version_pl): + version = mw.readFile(version_pl) + + + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'get_os': + print(getOs()) + elif func == 'run_info': + print(runInfo()) + elif func == 'error_log': + print(errorLogPath()) + elif func == 'get_cfg': + print(getCfg()) + elif func == 'set_cfg': + print(setCfg()) + elif func == 'check': + print(cronCheck()) + elif func == 'cron_add_check': + print(cronAddCheck()) + elif func == 'cron_del_check': + print(cronDelCheck()) + else: + print('error') diff --git a/plugins/openresty/info.json b/plugins/openresty/info.json new file mode 100755 index 000000000..5fb252408 --- /dev/null +++ b/plugins/openresty/info.json @@ -0,0 +1,18 @@ +{ + "sort": 0, + "title":"OpenResty", + "tip":"soft", + "name":"openresty", + "type":"其他插件", + "ps":"轻量级,占有内存少,并发能力强", + "shell":"install.sh", + "install_pre_inspection":true, + "checks":"server/openresty", + "path":"server/openresty", + "author":"agentzh", + "home":"http://openresty.org", + "date":"2017-11-24", + "pid": "1", + "versions": ["1.17.8","1.19.3","1.21.4","1.25.3","1.27.1","rtmp"], + "updates": ["1.17.8.2","1.19.3.1","1.21.4.2","1.25.3.2"] +} \ No newline at end of file diff --git a/plugins/openresty/init.d/nginx.tpl b/plugins/openresty/init.d/nginx.tpl new file mode 100644 index 000000000..d773f931a --- /dev/null +++ b/plugins/openresty/init.d/nginx.tpl @@ -0,0 +1,118 @@ +#! /bin/sh +# chkconfig: 2345 55 25 +# Description: Startup script for nginx webserver on Debian. Place in /etc/init.d and +# run 'update-rc.d -f nginx defaults', or use the appropriate command on your +# distro. For CentOS/Redhat run: 'chkconfig --add openresty' + +### BEGIN INIT INFO +# Provides: nginx +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts the nginx web server +# Description: starts nginx using start-stop-daemon +### END INIT INFO + + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/homebrew/bin +NAME=nginx +NGINX_BIN={$SERVER_PATH}/openresty/bin/openresty +CONFIGFILE={$SERVER_PATH}/openresty/nginx/conf/$NAME.conf +PIDFILE={$SERVER_PATH}/openresty/nginx/logs/$NAME.pid + +case "$1" in + start) + echo -n "Starting $NAME... " + if [ -f $PIDFILE ];then + mPID=`cat $PIDFILE` + isStart=`ps ax | awk '{ print $1 }' | grep -e "^${mPID}$"` + if [ "$isStart" != '' ];then + echo "$NAME (pid `pidof $NAME`) already running." + exit 1 + fi + fi + + $NGINX_BIN -c $CONFIGFILE + + if [ "$?" != 0 ] ; then + echo " failed" + exit 1 + else + echo " done" + fi + ;; + + stop) + echo -n "Stoping $NAME... " + if [ -f $PIDFILE ];then + mPID=`cat $PIDFILE` + isStart=`ps ax | awk '{ print $1 }' | grep -e "^${mPID}$"` + if [ "$isStart" = '' ];then + echo "$NAME is not running." + exit 1 + fi + else + echo "$NAME is not running." + exit 1 + fi + $NGINX_BIN -s stop + + if [ "$?" != 0 ] ; then + echo " failed. Use force-quit" + exit 1 + else + echo " done" + fi + ;; + + status) + if [ -f $PIDFILE ];then + mPID=`cat $PIDFILE` + isStart=`ps ax | awk '{ print $1 }' | grep -e "^${mPID}$"` + if [ "$isStart" != '' ];then + echo "$NAME (pid `pidof $NAME`) already running." + exit 1 + else + echo "$NAME is stopped" + exit 0 + fi + else + echo "$NAME is stopped" + exit 0 + fi + ;; + restart) + $0 stop + sleep 1 + $0 start + ;; + + reload) + echo -n "Reload service $NAME... " + if [ -f $PIDFILE ];then + mPID=`cat $PIDFILE` + isStart=`ps ax | awk '{ print $1 }' | grep -e "^${mPID}$"` + if [ "$isStart" != '' ];then + $NGINX_BIN -s reload + echo " done" + else + echo "$NAME is not running, can't reload." + exit 1 + fi + else + echo "$NAME is not running, can't reload." + exit 1 + fi + ;; + + configtest) + echo -n "Test $NAME configure files... " + $NGINX_BIN -t + ;; + + *) + echo "Usage: $0 {start|stop|restart|reload|status|configtest}" + exit 1 + ;; +esac diff --git a/plugins/openresty/init.d/openresty.service.tpl b/plugins/openresty/init.d/openresty.service.tpl new file mode 100644 index 000000000..b3369a3b8 --- /dev/null +++ b/plugins/openresty/init.d/openresty.service.tpl @@ -0,0 +1,14 @@ +[Unit] +Description=OpenResty is a dynamic web platform based on NGINX and LuaJIT. +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/openresty/bin/openresty -c {$SERVER_PATH}/openresty/nginx/conf/nginx.conf +ExecStop={$SERVER_PATH}/openresty/bin/openresty -s stop +ExecReload={$SERVER_PATH}/openresty/bin/openresty -s reload +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/openresty/install.sh b/plugins/openresty/install.sh new file mode 100755 index 000000000..66ac75122 --- /dev/null +++ b/plugins/openresty/install.sh @@ -0,0 +1,61 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4 +# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4 + +# cd /www/server/mdserver-web && python3 plugins/openresty/index.py run_info + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +action=$1 +type=$2 + +VERSION=$2 +openrestyDir=${serverPath}/source/openresty + +if id www &> /dev/null ;then + echo "www uid is `id -u www`" + echo "www shell is `grep "^www:" /etc/passwd |cut -d':' -f7 `" +else + groupadd www + useradd -g www -s /bin/bash www +fi + +if [ "${2}" == "" ];then + echo '缺少安装脚本版本...' + exit 0 +fi + +if [ "${action}" == "uninstall" ];then + if [ -f /usr/lib/systemd/system/openresty.service ] || [ -f /lib/systemd/system/openresty.service ];then + systemctl stop openresty + rm -rf /usr/systemd/system/openresty.service + rm -rf /lib/systemd/system/openresty.service + systemctl daemon-reload + fi + + if [ -f $serverPath/openresty/init.d/openresty ];then + $serverPath/openresty/init.d/openresty stop + fi + + rm -rf $serverPath/openresty +fi + +sh -x $curPath/versions/$2/install.sh $1 + +if [ "${action}" == "install" ] && [ -d $serverPath/openresty ];then + echo "${VERSION}" > $serverPath/openresty/version.pl + + mkdir -p $serverPath/web_conf/php/conf + echo 'set $PHP_ENV 0;' > $serverPath/web_conf/php/conf/enable-php-00.conf + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/openresty/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/openresty/index.py initd_install +fi diff --git a/plugins/openresty/js/openresty.js b/plugins/openresty/js/openresty.js new file mode 100755 index 000000000..8b538a0f8 --- /dev/null +++ b/plugins/openresty/js/openresty.js @@ -0,0 +1,236 @@ +function orPost(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'openresty', func:method, args:JSON.stringify(args)}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function orPluginService(_name, version){ + var data = {name:_name, func:'status'} + if ( typeof(version) != 'undefined' ){ + data['version'] = version; + } else { + version = ''; + } + + orPost('status', data, function(data){ + if (data.data == 'start'){ + orPluginSetService(_name, true, version); + } else { + orPluginSetService(_name, false, version); + } + }); +} + +function orPluginSetService(_name ,status, version){ + var serviceCon ='

                                当前状态:'+(status ? '开启' : '关闭' )+ + '

                                \ + \ + \ + \ +
                                '; + $(".soft-man-con").html(serviceCon); +} + + +function orPluginOpService(a, b, v,request_callback) { + + var c = "name=" + a + "&func=" + b; + if(v != ''){ + c = c + '&version='+v; + } + + var d = ""; + + switch(b) { + case "stop":d = '停止';break; + case "start":d = '启动';break; + case "restart":d = '重启';break; + case "reload":d = '重载';break; + } + layer.confirm( msgTpl('您真的要{1}{2}{3}服务吗?', [d,a,v]), {icon:3,closeBtn: 2}, function() { + orPost('get_os',{},function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata['auth']){ + layer.prompt({title: '检查到权限不足,需要输入密码!', formType: 1},function(pwd, index){ + + layer.close(index); + var data = {'pwd':pwd}; + c += '&args='+JSON.stringify(data); + orPluginOpServiceOp(a,b,c,d,a,v,request_callback); + }); + } else { + orPluginOpServiceOp(a,b,c,d,a,v,request_callback); + + } + }); + }) +} + +function orPluginOpServiceOp(a,b,c,d,a,v,request_callback){ + + var request_path = "/plugins/run"; + if (request_callback == 'yes'){ + request_path = "/plugins/callback"; + } + + var e = layer.msg(msgTpl('正在{1}{2}{3}服务,请稍候...',[d,a,v]), {icon: 16,time: 0}); + $.post(request_path, c, function(g) { + layer.close(e); + + var f = g.data == 'ok' ? msgTpl('{1}{2}服务已{3}',[a,v,d]) : msgTpl('{1}{2}服务{3}失败!',[a,v,d]); + layer.msg(f, {icon: g.data == 'ok' ? 1 : 2}); + + if( b != "reload" && g.data == 'ok' ) { + if ( b == 'start' ) { + orPluginSetService(a, true, v); + } else if ( b == 'stop' ){ + orPluginSetService(a, false, v); + } + } + + if( g.status && g.data != 'ok' ) { + layer.msg(g.data, {icon: 2,time: 10000,shade: 0.3}); + } + + },'json').error(function() { + layer.close(e); + layer.msg('操作异常!', {icon: 2}); + }); +} + + +//查看Nginx负载状态 +function getOpStatus() { + var loadT = layer.msg('正在处理,请稍后...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'openresty', func:'run_info'}, function(data) { + layer.close(loadT); + try { + var rdata = $.parseJSON(data.data); + if ('status' in rdata && !rdata.status){ + showMsg(rdata.msg, function(){}, null,3000); + return; + } + + var con = "
                                \ + \ + \ + \ + \ + \ + \ + \ +
                                活动连接(Active connections)" + rdata.active + "
                                总连接次数(accepts)" + rdata.accepts + "
                                总握手次数(handled)" + rdata.handled + "
                                总请求数(requests)" + rdata.requests + "
                                请求数(Reading)" + rdata.Reading + "
                                响应数(Writing)" + rdata.Writing + "
                                驻留进程(Waiting)" + rdata.Waiting + "
                                "; + $(".soft-man-con").html(con); + }catch(err){ + showMsg(data.data, function(){}, null,3000); + } + },'json'); +} + + +function setOpCfg(){ + orPost('get_cfg', {}, function(data){ + var rdata = $.parseJSON(data.data); + var rdata = rdata.data; + // console.log(rdata); + + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '70' + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = ''; + break; + case 1: + var selected_1 = (rdata[i].value == 'on') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'off') ? 'selected' : ''; + ibody = ''; + break; + } + mlist += '

                                ' + rdata[i].name + '' + ibody + ""+rdata[i].unit+"" +', ' + rdata[i].ps + '

                                '; + } + var con = '
                                \ + ' + mlist + '\ +
                                \ + \ + \ +
                                \ +
                                ' + $(".soft-man-con").html(con); + }); +} + +function submitConf() { + var data = { + worker_processes: $("input[name='worker_processes']").val(), + worker_connections: $("input[name='worker_connections']").val(), + keepalive_timeout: $("input[name='keepalive_timeout']").val(), + gzip: $("select[name='gzip']").val() || 'on', + gzip_min_length: $("input[name='gzip_min_length']").val(), + gzip_comp_level: $("input[name='gzip_comp_level']").val(), + client_max_body_size: $("input[name='client_max_body_size']").val(), + server_names_hash_bucket_size: $("input[name='server_names_hash_bucket_size']").val(), + client_header_buffer_size: $("input[name='client_header_buffer_size']").val(), + }; + + // console.log(data); + orPost('set_cfg', data, function(rdata){ + var rdata = $.parseJSON(rdata.data); + // console.log(rdata); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function otherFunc(){ + var con = '

                                \ + \ + \ +

                                '; + $(".soft-man-con").html(con); +} + +function cronAddCheck(){ + orPost('cron_add_check', {}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function cronDelCheck(){ + orPost('cron_del_check', {}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + + + + + + + + + + + diff --git a/plugins/openresty/tool_task.py b/plugins/openresty/tool_task.py new file mode 100644 index 000000000..6029ad9ee --- /dev/null +++ b/plugins/openresty/tool_task.py @@ -0,0 +1,124 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'openresty' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getTaskConf(): + conf = getServerDir() + "/task_config.json" + return conf + + +def getConfigData(): + conf = getTaskConf() + if os.path.exists(conf): + return json.loads(mw.readFile(getTaskConf())) + return { + "task_id": -1, + "period": "minute-n", + "where1": "3", + "hour": "0", + "minute": "0", + } + + +def createBgTask(): + removeBgTask() + createBgTaskByName(getPluginName()) + + +def createBgTaskByName(name): + args = getConfigData() + _name = "[OpenResty]检查任务" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in args and args["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (args["task_id"],)).find() + if res and res["id"] == args["task_id"]: + print("计划任务已经存在!") + return True + + mw_dir = mw.getPanelDir() + cmd = ''' +mw_dir=%s +rname=%s +plugin_path=%s +script_path=%s +''' % (mw_dir, name, getServerDir(), getPluginDir()) + cmd += 'echo "bash $script_path/check.sh"' + "\n" + cmd += 'cd $mw_dir && bash $script_path/check.sh' + "\n" + + params = { + 'name': _name, + 'type': args['period'], + 'week': "", + 'where1': args['where1'], + 'hour': args['hour'], + 'minute': args['minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + args["task_id"] = task_id + args["name"] = name + mw.writeFile(getTaskConf(), json.dumps(args)) + + +def removeBgTask(): + cfg = getConfigData() + if "task_id" in cfg and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data["status"]: + cfg["task_id"] = -1 + mw.writeFile(getTaskConf(), json.dumps(cfg)) + return True + return False + + +if __name__ == "__main__": + if len(sys.argv) > 1: + action = sys.argv[1] + if action == "remove": + removeBgTask() + elif action == "add": + createBgTask() diff --git a/plugins/openresty/versions/1.17.8/install.sh b/plugins/openresty/versions/1.17.8/install.sh new file mode 100644 index 000000000..544c97b9f --- /dev/null +++ b/plugins/openresty/versions/1.17.8/install.sh @@ -0,0 +1,168 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 +# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +action=$1 +type=$2 + +VERSION=1.17.8.2 +openrestyDir=${serverPath}/source/openresty + +Install_openresty() +{ + if [ -d $serverPath/openresty ];then + exit 0 + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + mkdir -p ${openrestyDir} + echo '正在安装脚本文件...' + + # wget -O openresty-1.21.4.1.tar.gz https://openresty.org/download/openresty-1.21.4.1.tar.gz + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz https://openresty.org/download/openresty-${VERSION}.tar.gz -T 3 + fi + + DOWNLOAD_SIZE=`wc -c ${openrestyDir}/openresty-${VERSION}.tar.gz | awk '{print $1}'` + if [ "$DOWNLOAD_SIZE" == "0" ];then + echo 'download failed, download again' + rm -rf ${openrestyDir}/openresty-${VERSION}.tar.gz + fi + + # Last Download Method + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz http://dl.midoks.icu/soft/openresty/openresty-${VERSION}.tar.gz -T 3 + fi + + cd ${openrestyDir} && tar -zxvf openresty-${VERSION}.tar.gz + + OPTIONS='' + + opensslVersion="1.1.1p" + libresslVersion="3.9.1" + pcreVersion='8.38' + if [ "$sysName" == "Darwin" ];then + + if [ ! -f ${openrestyDir}/pcre-${pcreVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/pcre-${pcreVersion}.tar.gz https://netix.dl.sourceforge.net/project/pcre/pcre/${pcreVersion}/pcre-${pcreVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/pcre-${pcreVersion} ];then + cd ${openrestyDir} && tar -zxvf pcre-${pcreVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-pcre=${openrestyDir}/pcre-${pcreVersion}" + + + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + # BREW_DIR=`which brew` + # BREW_DIR=${BREW_DIR/\/bin\/brew/} + + # brew info openssl@1.1 | grep /opt/homebrew/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}' + # OPENSSL_LIB_DEPEND_DIR=`brew info openssl@1.1 | grep ${BREW_DIR}/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}'` + # OPTIONS="${OPTIONS} --with-openssl=${OPENSSL_LIB_DEPEND_DIR}" + else + echo "openssl" + # if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + # wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + # fi + + # if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + # cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + # fi + # OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + fi + + + # --with-openssl=$serverPath/source/lib/openssl-1.0.2q + cd ${openrestyDir}/openresty-${VERSION} && ./configure \ + --prefix=$serverPath/openresty \ + $OPTIONS \ + --with-stream \ + --with-http_v2_module \ + --with-http_ssl_module \ + --with-http_slice_module \ + --with-http_stub_status_module \ + --with-http_sub_module \ + --with-http_realip_module + # --without-luajit-gc64 + # --with-debug + # 用于调式 + + CMD_MAKE=`which gmake` + if [ "$?" == "0" ];then + gmake -j${cpuCore} && gmake install && gmake clean + else + make -j${cpuCore} && make install && make clean + fi + + if [ -d ${openrestyDir}/pcre-${pcreVersion} ];then + rm -rf ${openrestyDir}/pcre-${pcreVersion} + fi + + if [ -d ${openrestyDir}/openssl-${opensslVersion} ];then + rm -rf ${openrestyDir}/openssl-${opensslVersion} + fi + + if [ -d ${openrestyDir}/libressl-${libresslVersion} ];then + rm -rf ${openrestyDir}/libressl-${libresslVersion} + fi + + if [ -d $openrestyDir/openresty-${VERSION} ];then + rm -rf $openrestyDir/openresty-${VERSION} + fi + + echo '安装完成' +} + +Uninstall_openresty() +{ + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_openresty +else + Uninstall_openresty +fi diff --git a/plugins/openresty/versions/1.19.3/install.sh b/plugins/openresty/versions/1.19.3/install.sh new file mode 100644 index 000000000..2af69a9b1 --- /dev/null +++ b/plugins/openresty/versions/1.19.3/install.sh @@ -0,0 +1,168 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 +# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +action=$1 +type=$2 + +VERSION=1.19.3.1 +openrestyDir=${serverPath}/source/openresty + +Install_openresty() +{ + if [ -d $serverPath/openresty ];then + exit 0 + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + mkdir -p ${openrestyDir} + echo '正在安装脚本文件...' + + # wget -O openresty-1.21.4.1.tar.gz https://openresty.org/download/openresty-1.21.4.1.tar.gz + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz https://openresty.org/download/openresty-${VERSION}.tar.gz -T 3 + fi + + DOWNLOAD_SIZE=`wc -c ${openrestyDir}/openresty-${VERSION}.tar.gz | awk '{print $1}'` + if [ "$DOWNLOAD_SIZE" == "0" ];then + echo 'download failed, download again' + rm -rf ${openrestyDir}/openresty-${VERSION}.tar.gz + fi + + # Last Download Method + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz http://dl.midoks.icu/soft/openresty/openresty-${VERSION}.tar.gz -T 3 + fi + + cd ${openrestyDir} && tar -zxvf openresty-${VERSION}.tar.gz + + OPTIONS='' + OPTIONS="${OPTIONS} --with-ipv6" + + opensslVersion="1.1.1p" + libresslVersion="3.9.1" + pcreVersion='8.38' + if [ "$sysName" == "Darwin" ];then + + if [ ! -f ${openrestyDir}/pcre-${pcreVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/pcre-${pcreVersion}.tar.gz https://netix.dl.sourceforge.net/project/pcre/pcre/${pcreVersion}/pcre-${pcreVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/pcre-${pcreVersion} ];then + cd ${openrestyDir} && tar -zxvf pcre-${pcreVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-pcre=${openrestyDir}/pcre-${pcreVersion}" + + + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + # BREW_DIR=`which brew` + # BREW_DIR=${BREW_DIR/\/bin\/brew/} + + # brew info openssl@1.1 | grep /opt/homebrew/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}' + # OPENSSL_LIB_DEPEND_DIR=`brew info openssl@1.1 | grep ${BREW_DIR}/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}'` + # OPTIONS="${OPTIONS} --with-openssl=${OPENSSL_LIB_DEPEND_DIR}" + else + echo "openssl" + # if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + # wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + # fi + + # if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + # cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + # fi + # OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + fi + + + # --with-openssl=$serverPath/source/lib/openssl-1.0.2q + cd ${openrestyDir}/openresty-${VERSION} && ./configure \ + --prefix=$serverPath/openresty \ + $OPTIONS \ + --with-stream \ + --with-http_v2_module \ + --with-http_ssl_module \ + --with-http_slice_module \ + --with-http_stub_status_module \ + --with-http_sub_module \ + --with-http_realip_module + # --without-luajit-gc64 + # --with-debug + # 用于调式 + + CMD_MAKE=`which gmake` + if [ "$?" == "0" ];then + gmake -j${cpuCore} && gmake install && gmake clean + else + make -j${cpuCore} && make install && make clean + fi + + if [ -d ${openrestyDir}/pcre-${pcreVersion} ];then + rm -rf ${openrestyDir}/pcre-${pcreVersion} + fi + + if [ -d ${openrestyDir}/openssl-${opensslVersion} ];then + rm -rf ${openrestyDir}/openssl-${opensslVersion} + fi + + if [ -d ${openrestyDir}/libressl-${libresslVersion} ];then + rm -rf ${openrestyDir}/libressl-${libresslVersion} + fi + + if [ -d $openrestyDir/openresty-${VERSION} ];then + rm -rf $openrestyDir/openresty-${VERSION} + fi + echo '安装完成' +} + +Uninstall_openresty() +{ + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_openresty +else + Uninstall_openresty +fi diff --git a/plugins/openresty/versions/1.21.4/install.sh b/plugins/openresty/versions/1.21.4/install.sh new file mode 100644 index 000000000..0ceaa3a91 --- /dev/null +++ b/plugins/openresty/versions/1.21.4/install.sh @@ -0,0 +1,168 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 +# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +action=$1 +type=$2 + +VERSION=1.21.4.4 + +openrestyDir=${serverPath}/source/openresty + +Install_openresty() +{ + if [ -d $serverPath/openresty ];then + exit 0 + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + mkdir -p ${openrestyDir} + echo '正在安装脚本文件...' + + # wget -O openresty-1.21.4.1.tar.gz https://openresty.org/download/openresty-1.21.4.1.tar.gz + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz https://openresty.org/download/openresty-${VERSION}.tar.gz -T 3 + fi + + DOWNLOAD_SIZE=`wc -c ${openrestyDir}/openresty-${VERSION}.tar.gz | awk '{print $1}'` + if [ "$DOWNLOAD_SIZE" == "0" ];then + echo 'download failed, download again' + rm -rf ${openrestyDir}/openresty-${VERSION}.tar.gz + fi + + # Last Download Method + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz http://dl.midoks.icu/soft/openresty/openresty-${VERSION}.tar.gz -T 3 + fi + + cd ${openrestyDir} && tar -zxvf openresty-${VERSION}.tar.gz + + OPTIONS='' + + opensslVersion="1.1.1p" + libresslVersion="3.9.1" + pcreVersion='8.38' + if [ "$sysName" == "Darwin" ];then + + if [ ! -f ${openrestyDir}/pcre-${pcreVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/pcre-${pcreVersion}.tar.gz https://netix.dl.sourceforge.net/project/pcre/pcre/${pcreVersion}/pcre-${pcreVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/pcre-${pcreVersion} ];then + cd ${openrestyDir} && tar -zxvf pcre-${pcreVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-pcre=${openrestyDir}/pcre-${pcreVersion}" + + + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + # BREW_DIR=`which brew` + # BREW_DIR=${BREW_DIR/\/bin\/brew/} + + # brew info openssl@1.1 | grep /opt/homebrew/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}' + # OPENSSL_LIB_DEPEND_DIR=`brew info openssl@1.1 | grep ${BREW_DIR}/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}'` + # OPTIONS="${OPTIONS} --with-openssl=${OPENSSL_LIB_DEPEND_DIR}" + else + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + fi + + + # --with-openssl=$serverPath/source/lib/openssl-1.0.2q + cd ${openrestyDir}/openresty-${VERSION} && ./configure \ + --prefix=$serverPath/openresty \ + $OPTIONS \ + --with-stream \ + --with-http_v2_module \ + --with-http_ssl_module \ + --with-http_slice_module \ + --with-http_stub_status_module \ + --with-http_sub_module \ + --with-http_realip_module + # --without-luajit-gc64 + # --with-debug + # 用于调式 + + CMD_MAKE=`which gmake` + if [ "$?" == "0" ];then + gmake -j${cpuCore} && gmake install && gmake clean + else + make -j${cpuCore} && make install && make clean + fi + + + if [ -d ${openrestyDir}/pcre-${pcreVersion} ];then + rm -rf ${openrestyDir}/pcre-${pcreVersion} + fi + + if [ -d ${openrestyDir}/openssl-${opensslVersion} ];then + rm -rf ${openrestyDir}/openssl-${opensslVersion} + fi + + if [ -d ${openrestyDir}/libressl-${libresslVersion} ];then + rm -rf ${openrestyDir}/libressl-${libresslVersion} + fi + + if [ -d $openrestyDir/openresty-${VERSION} ];then + rm -rf $openrestyDir/openresty-${VERSION} + fi + echo '安装完成' +} + +Uninstall_openresty() +{ + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_openresty +else + Uninstall_openresty +fi diff --git a/plugins/openresty/versions/1.25.3/install.sh b/plugins/openresty/versions/1.25.3/install.sh new file mode 100644 index 000000000..0becee2f0 --- /dev/null +++ b/plugins/openresty/versions/1.25.3/install.sh @@ -0,0 +1,183 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 +# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +action=$1 +type=$2 + +VERSION=1.25.3.2 + +openrestyDir=${serverPath}/source/openresty + +Install_openresty() +{ + if [ -d $serverPath/openresty ];then + exit 0 + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + mkdir -p ${openrestyDir} + echo '正在安装脚本文件...' + + # wget -O openresty-1.21.4.1.tar.gz https://openresty.org/download/openresty-1.21.4.1.tar.gz + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz https://openresty.org/download/openresty-${VERSION}.tar.gz -T 3 + fi + + DOWNLOAD_SIZE=`wc -c ${openrestyDir}/openresty-${VERSION}.tar.gz | awk '{print $1}'` + if [ "$DOWNLOAD_SIZE" == "0" ];then + echo 'download failed, download again' + rm -rf ${openrestyDir}/openresty-${VERSION}.tar.gz + fi + + # Last Download Method + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz http://dl.midoks.icu/soft/openresty/openresty-${VERSION}.tar.gz -T 3 + fi + + cd ${openrestyDir} && tar -zxvf openresty-${VERSION}.tar.gz + + OPTIONS='' + + opensslVersion="1.1.1p" + libresslVersion="3.9.1" + pcreVersion='8.38' + if [ "$sysName" == "Darwin" ];then + + if [ ! -f ${openrestyDir}/pcre-${pcreVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/pcre-${pcreVersion}.tar.gz https://netix.dl.sourceforge.net/project/pcre/pcre/${pcreVersion}/pcre-${pcreVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/pcre-${pcreVersion} ];then + cd ${openrestyDir} && tar -zxvf pcre-${pcreVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-pcre=${openrestyDir}/pcre-${pcreVersion}" + + + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + # BREW_DIR=`which brew` + # BREW_DIR=${BREW_DIR/\/bin\/brew/} + + # brew info openssl@1.1 | grep /opt/homebrew/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}' + # OPENSSL_LIB_DEPEND_DIR=`brew info openssl@1.1 | grep ${BREW_DIR}/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}'` + # OPTIONS="${OPTIONS} --with-openssl=${OPENSSL_LIB_DEPEND_DIR}" + else + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + fi + + if [[ "$VERSION" =~ "1.25.3" ]]; then + OPTIONS="${OPTIONS} --with-http_v3_module" + + if [ ! -f ${openrestyDir}/libressl-${libresslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/libressl-${libresslVersion}.tar.gz https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${libresslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/libressl-${libresslVersion} ];then + cd ${openrestyDir} && tar -zxvf libressl-${libresslVersion}.tar.gz + fi + + OPTIONS="${OPTIONS} --with-cc-opt=-I${openrestyDir}/libressl-${libresslVersion}/libressl/build/include" + OPTIONS="${OPTIONS} --with-cc-opt=-I${openrestyDir}/libressl-${libresslVersion}/libressl/build/lib" + fi + + + cd ${openrestyDir}/openresty-${VERSION} && ./configure \ + --prefix=$serverPath/openresty \ + $OPTIONS \ + --with-stream \ + --with-http_v2_module \ + --with-http_ssl_module \ + --with-http_slice_module \ + --with-http_stub_status_module \ + --with-http_sub_module \ + --with-http_realip_module + # --without-luajit-gc64 + # --with-debug + # 用于调式 + + CMD_MAKE=`which gmake` + if [ "$?" == "0" ];then + gmake -j${cpuCore} && gmake install && gmake clean + else + make -j${cpuCore} && make install && make clean + fi + + + if [ -d ${openrestyDir}/pcre-${pcreVersion} ];then + rm -rf ${openrestyDir}/pcre-${pcreVersion} + fi + + if [ -d ${openrestyDir}/openssl-${opensslVersion} ];then + rm -rf ${openrestyDir}/openssl-${opensslVersion} + fi + + if [ -d ${openrestyDir}/libressl-${libresslVersion} ];then + rm -rf ${openrestyDir}/libressl-${libresslVersion} + fi + + if [ -d $openrestyDir/openresty-${VERSION} ];then + rm -rf $openrestyDir/openresty-${VERSION} + fi + echo '安装完成' +} + +Uninstall_openresty() +{ + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_openresty +else + Uninstall_openresty +fi diff --git a/plugins/openresty/versions/1.27.1/install.sh b/plugins/openresty/versions/1.27.1/install.sh new file mode 100644 index 000000000..42704f27c --- /dev/null +++ b/plugins/openresty/versions/1.27.1/install.sh @@ -0,0 +1,183 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.2 +# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.27.1 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +action=$1 +type=$2 + +VERSION=1.27.1.2 + +openrestyDir=${serverPath}/source/openresty + +Install_openresty() +{ + if [ -d $serverPath/openresty ];then + exit 0 + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + mkdir -p ${openrestyDir} + echo '正在安装脚本文件...' + + # wget -O openresty-1.21.4.1.tar.gz https://openresty.org/download/openresty-1.21.4.1.tar.gz + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz https://openresty.org/download/openresty-${VERSION}.tar.gz -T 3 + fi + + DOWNLOAD_SIZE=`wc -c ${openrestyDir}/openresty-${VERSION}.tar.gz | awk '{print $1}'` + if [ "$DOWNLOAD_SIZE" == "0" ];then + echo 'download failed, download again' + rm -rf ${openrestyDir}/openresty-${VERSION}.tar.gz + fi + + # Last Download Method + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz http://dl.midoks.icu/soft/openresty/openresty-${VERSION}.tar.gz -T 3 + fi + + cd ${openrestyDir} && tar -zxvf openresty-${VERSION}.tar.gz + + OPTIONS='' + + opensslVersion="1.1.1p" + libresslVersion="3.9.1" + pcreVersion='8.38' + if [ "$sysName" == "Darwin" ];then + + if [ ! -f ${openrestyDir}/pcre-${pcreVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/pcre-${pcreVersion}.tar.gz https://netix.dl.sourceforge.net/project/pcre/pcre/${pcreVersion}/pcre-${pcreVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/pcre-${pcreVersion} ];then + cd ${openrestyDir} && tar -zxvf pcre-${pcreVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-pcre=${openrestyDir}/pcre-${pcreVersion}" + + + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + # BREW_DIR=`which brew` + # BREW_DIR=${BREW_DIR/\/bin\/brew/} + + # brew info openssl@1.1 | grep /opt/homebrew/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}' + # OPENSSL_LIB_DEPEND_DIR=`brew info openssl@1.1 | grep ${BREW_DIR}/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}'` + # OPTIONS="${OPTIONS} --with-openssl=${OPENSSL_LIB_DEPEND_DIR}" + else + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + fi + + if [[ "$VERSION" =~ "1.25.3" ]] || [[ "$VERSION" =~ "1.27.1" ]];then + OPTIONS="${OPTIONS} --with-http_v3_module" + + if [ ! -f ${openrestyDir}/libressl-${libresslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/libressl-${libresslVersion}.tar.gz https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${libresslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/libressl-${libresslVersion} ];then + cd ${openrestyDir} && tar -zxvf libressl-${libresslVersion}.tar.gz + fi + + OPTIONS="${OPTIONS} --with-cc-opt=-I${openrestyDir}/libressl-${libresslVersion}/libressl/build/include" + OPTIONS="${OPTIONS} --with-cc-opt=-I${openrestyDir}/libressl-${libresslVersion}/libressl/build/lib" + fi + + + cd ${openrestyDir}/openresty-${VERSION} && ./configure \ + --prefix=$serverPath/openresty \ + $OPTIONS \ + --with-stream \ + --with-http_v2_module \ + --with-http_ssl_module \ + --with-http_slice_module \ + --with-http_stub_status_module \ + --with-http_sub_module \ + --with-http_realip_module + # --without-luajit-gc64 + # --with-debug + # 用于调式 + + CMD_MAKE=`which gmake` + if [ "$?" == "0" ];then + gmake -j${cpuCore} && gmake install && gmake clean + else + make -j${cpuCore} && make install && make clean + fi + + + if [ -d ${openrestyDir}/pcre-${pcreVersion} ];then + rm -rf ${openrestyDir}/pcre-${pcreVersion} + fi + + if [ -d ${openrestyDir}/openssl-${opensslVersion} ];then + rm -rf ${openrestyDir}/openssl-${opensslVersion} + fi + + if [ -d ${openrestyDir}/libressl-${libresslVersion} ];then + rm -rf ${openrestyDir}/libressl-${libresslVersion} + fi + + if [ -d $openrestyDir/openresty-${VERSION} ];then + rm -rf $openrestyDir/openresty-${VERSION} + fi + echo 'Installation of Openresty completed' +} + +Uninstall_openresty() +{ + echo 'Uninstalling Openresty completed' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_openresty +else + Uninstall_openresty +fi diff --git a/plugins/openresty/versions/1.29.2/install.sh b/plugins/openresty/versions/1.29.2/install.sh new file mode 100644 index 000000000..bdfb1f969 --- /dev/null +++ b/plugins/openresty/versions/1.29.2/install.sh @@ -0,0 +1,220 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/openresty && bash install.sh install 1.29.2 +# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.29.2 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +action=$1 +type=$2 + +VERSION=1.29.2.3 + +openrestyDir=${serverPath}/source/openresty + +Install_openresty() +{ + if [ "${action}" == "install" ];then + if [ -d $serverPath/openresty ];then + exit 0 + fi + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + mkdir -p ${openrestyDir} + echo '正在安装脚本文件...' + + # wget -O openresty-1.21.4.1.tar.gz https://openresty.org/download/openresty-1.21.4.1.tar.gz + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz https://openresty.org/download/openresty-${VERSION}.tar.gz -T 3 + fi + + DOWNLOAD_SIZE=`wc -c ${openrestyDir}/openresty-${VERSION}.tar.gz | awk '{print $1}'` + if [ "$DOWNLOAD_SIZE" == "0" ];then + echo 'download failed, download again' + rm -rf ${openrestyDir}/openresty-${VERSION}.tar.gz + fi + + # Last Download Method + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz http://dl.midoks.icu/soft/openresty/openresty-${VERSION}.tar.gz -T 3 + fi + + cd ${openrestyDir} && tar -zxvf openresty-${VERSION}.tar.gz + + OPTIONS='' + + opensslVersion="3.5.5" + libresslVersion="3.9.1" + pcreVersion='8.45' + if [ "$sysName" == "Darwin" ];then + + if [ ! -f ${openrestyDir}/pcre-${pcreVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/pcre-${pcreVersion}.tar.gz https://netix.dl.sourceforge.net/project/pcre/pcre/${pcreVersion}/pcre-${pcreVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/pcre-${pcreVersion} ];then + cd ${openrestyDir} && tar -zxvf pcre-${pcreVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-pcre=${openrestyDir}/pcre-${pcreVersion}" + + + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + # BREW_DIR=`which brew` + # BREW_DIR=${BREW_DIR/\/bin\/brew/} + + # brew info openssl@1.1 | grep /opt/homebrew/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}' + # OPENSSL_LIB_DEPEND_DIR=`brew info openssl@1.1 | grep ${BREW_DIR}/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}'` + # OPTIONS="${OPTIONS} --with-openssl=${OPENSSL_LIB_DEPEND_DIR}" + else + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + fi + + if [[ "$VERSION" =~ "1.29.2" ]];then + OPTIONS="${OPTIONS} --with-http_v3_module" + + # if [ ! -f ${openrestyDir}/libressl-${libresslVersion}.tar.gz ];then + # wget --no-check-certificate -O ${openrestyDir}/libressl-${libresslVersion}.tar.gz https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${libresslVersion}.tar.gz + # fi + + # if [ ! -d ${openrestyDir}/libressl-${libresslVersion} ];then + # cd ${openrestyDir} && tar -zxvf libressl-${libresslVersion}.tar.gz + # fi + + # OPTIONS="${OPTIONS} --with-cc-opt=-I${openrestyDir}/libressl-${libresslVersion}/libressl/build/include" + # OPTIONS="${OPTIONS} --with-cc-opt=-I${openrestyDir}/libressl-${libresslVersion}/libressl/build/lib" + fi + + # br + if [ ! -d ${openrestyDir}/openresty-${VERSION}/ngx_brotli ];then + cd ${openrestyDir}/openresty-${VERSION} && git clone https://github.com/wxx9248/ngx_brotli.git + cd ${openrestyDir}/openresty-${VERSION}/ngx_brotli && git submodule update --init + + OPTIONS="${OPTIONS} --add-module=${openrestyDir}/openresty-${VERSION}/ngx_brotli" + fi + + OPTIONS="${OPTIONS} --with-threads" + OPTIONS="${OPTIONS} --with-file-aio" + OPTIONS="${OPTIONS} --with-pcre-jit" + OPTIONS="${OPTIONS} --with-http_gzip_static_module" + + #zstd + if [ ! -d ${openrestyDir}/zstd-nginx-module ];then + cd ${openrestyDir} && wget -O $openrestyDir/zstd-nginx-module.tar.gz https://github.com/tokers/zstd-nginx-module/archive/refs/heads/master.tar.gz + cd ${openrestyDir} && tar -zxvf zstd-nginx-module.tar.gz + + pkg-config --exists --print-errors libzstd + if [ "$?" == "0" ];then + OPTIONS="${OPTIONS} --add-module=${openrestyDir}/zstd-nginx-module-master" + fi + fi + + + + cd ${openrestyDir}/openresty-${VERSION} && ./configure \ + --prefix=$serverPath/openresty \ + $OPTIONS \ + --with-stream \ + --with-http_v2_module \ + --with-http_ssl_module \ + --with-http_slice_module \ + --with-http_stub_status_module \ + --with-http_sub_module \ + --with-http_realip_module + # --without-luajit-gc64 + # --with-debug + # 用于调式 + + CMD_MAKE=`which gmake` + if [ "$?" == "0" ];then + gmake -j${cpuCore} && gmake install && gmake clean + else + make -j${cpuCore} && make install && make clean + fi + + + if [ -d ${openrestyDir}/pcre-${pcreVersion} ];then + rm -rf ${openrestyDir}/pcre-${pcreVersion} + fi + + if [ -d ${openrestyDir}/openssl-${opensslVersion} ];then + rm -rf ${openrestyDir}/openssl-${opensslVersion} + fi + + if [ -d ${openrestyDir}/libressl-${libresslVersion} ];then + rm -rf ${openrestyDir}/libressl-${libresslVersion} + fi + + if [ -d $openrestyDir/openresty-${VERSION} ];then + rm -rf $openrestyDir/openresty-${VERSION} + fi + + if [ -d $openrestyDir/zstd-nginx-module-master ];then + rm -rf $openrestyDir/zstd-nginx-module-master + fi + + # if [ -d $openrestyDir/ngx_brotli ];then + # rm -rf $openrestyDir/ngx_brotli + # fi + echo 'Installation of Openresty completed' +} + +Uninstall_openresty() +{ + echo 'Uninstalling Openresty completed' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_openresty +elif [ "${1}" == "upgrade" ];then + Install_openresty +else + Uninstall_openresty +fi diff --git a/plugins/openresty/versions/rtmp/install.sh b/plugins/openresty/versions/rtmp/install.sh new file mode 100644 index 000000000..11a8f5d88 --- /dev/null +++ b/plugins/openresty/versions/rtmp/install.sh @@ -0,0 +1,199 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/openresty && bash install.sh install 1.27.1.1 +# cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.27.1.1 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysName=`uname` +action=$1 +type=$2 + +VERSION=1.27.1.1 + +openrestyDir=${serverPath}/source/openresty + +Install_openresty() +{ + if [ -d $serverPath/openresty ];then + exit 0 + fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + else + cpuCore="1" + fi + # ----- cpu end ------ + + mkdir -p ${openrestyDir} + echo '正在安装脚本文件...' + + # wget -O openresty-1.21.4.1.tar.gz https://openresty.org/download/openresty-1.21.4.1.tar.gz + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz https://openresty.org/download/openresty-${VERSION}.tar.gz -T 3 + fi + + DOWNLOAD_SIZE=`wc -c ${openrestyDir}/openresty-${VERSION}.tar.gz | awk '{print $1}'` + if [ "$DOWNLOAD_SIZE" == "0" ];then + echo 'download failed, download again' + rm -rf ${openrestyDir}/openresty-${VERSION}.tar.gz + fi + + # Last Download Method + if [ ! -f ${openrestyDir}/openresty-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openresty-${VERSION}.tar.gz http://dl.midoks.icu/soft/openresty/openresty-${VERSION}.tar.gz -T 3 + fi + + cd ${openrestyDir} && tar -zxvf openresty-${VERSION}.tar.gz + + OPTIONS='' + + opensslVersion="1.1.1p" + libresslVersion="3.9.1" + pcreVersion='8.38' + if [ "$sysName" == "Darwin" ];then + + if [ ! -f ${openrestyDir}/pcre-${pcreVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/pcre-${pcreVersion}.tar.gz https://netix.dl.sourceforge.net/project/pcre/pcre/${pcreVersion}/pcre-${pcreVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/pcre-${pcreVersion} ];then + cd ${openrestyDir} && tar -zxvf pcre-${pcreVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-pcre=${openrestyDir}/pcre-${pcreVersion}" + + + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + # BREW_DIR=`which brew` + # BREW_DIR=${BREW_DIR/\/bin\/brew/} + + # brew info openssl@1.1 | grep /opt/homebrew/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}' + # OPENSSL_LIB_DEPEND_DIR=`brew info openssl@1.1 | grep ${BREW_DIR}/Cellar/openssl@1.1 | cut -d \ -f 1 | awk 'END {print}'` + # OPTIONS="${OPTIONS} --with-openssl=${OPENSSL_LIB_DEPEND_DIR}" + else + if [ ! -f ${openrestyDir}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/openssl-${opensslVersion} ];then + cd ${openrestyDir} && tar -zxvf openssl-${opensslVersion}.tar.gz + fi + OPTIONS="${OPTIONS} --with-openssl=${openrestyDir}/openssl-${opensslVersion}" + + fi + + if [[ "$VERSION" =~ "1.25.3" ]]; then + OPTIONS="${OPTIONS} --with-http_v3_module" + + if [ ! -f ${openrestyDir}/libressl-${libresslVersion}.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/libressl-${libresslVersion}.tar.gz https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${libresslVersion}.tar.gz + fi + + if [ ! -d ${openrestyDir}/libressl-${libresslVersion} ];then + cd ${openrestyDir} && tar -zxvf libressl-${libresslVersion}.tar.gz + fi + + OPTIONS="${OPTIONS} --with-cc-opt=-I${openrestyDir}/libressl-${libresslVersion}/libressl/build/include" + OPTIONS="${OPTIONS} --with-cc-opt=-I${openrestyDir}/libressl-${libresslVersion}/libressl/build/lib" + fi + + # rtmp推流功能 + nginx_rtmp_ver=1.2.2 + if [ ! -f ${openrestyDir}/nginx-rtmp-module.tar.gz ];then + wget --no-check-certificate -O ${openrestyDir}/nginx-rtmp-module.tar.gz https://github.com/arut/nginx-rtmp-module/archive/refs/tags/v${nginx_rtmp_ver}.tar.gz + fi + + if [ ! -d ${openrestyDir}/nginx-rtmp-module.tar.gz ];then + cd ${openrestyDir} && tar -zxvf nginx-rtmp-module.tar.gz + fi + OPTIONS="${OPTIONS} --add-module=${openrestyDir}/nginx-rtmp-module-${nginx_rtmp_ver}" + + + cd ${openrestyDir}/openresty-${VERSION} && ./configure \ + --prefix=$serverPath/openresty \ + $OPTIONS \ + --with-stream \ + --with-http_v2_module \ + --with-http_ssl_module \ + --with-http_slice_module \ + --with-http_stub_status_module \ + --with-http_sub_module \ + --with-http_realip_module + # --without-luajit-gc64 + # --with-debug + # 用于调式 + + CMD_MAKE=`which gmake` + if [ "$?" == "0" ];then + gmake -j${cpuCore} && gmake install && gmake clean + else + make -j${cpuCore} && make install && make clean + fi + + + if [ -d ${openrestyDir}/pcre-${pcreVersion} ];then + rm -rf ${openrestyDir}/pcre-${pcreVersion} + fi + + if [ -d ${openrestyDir}/openssl-${opensslVersion} ];then + rm -rf ${openrestyDir}/openssl-${opensslVersion} + fi + + if [ -d ${openrestyDir}/libressl-${libresslVersion} ];then + rm -rf ${openrestyDir}/libressl-${libresslVersion} + fi + + if [ -d ${openrestyDir}/nginx-rtmp-module-${nginx_rtmp_ver} ];then + rm -rf ${openrestyDir}/nginx-rtmp-module-${nginx_rtmp_ver} + fi + + if [ -d $openrestyDir/openresty-${VERSION} ];then + rm -rf $openrestyDir/openresty-${VERSION} + fi + + echo '安装完成' +} + +Uninstall_openresty() +{ + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_openresty +else + Uninstall_openresty +fi diff --git a/plugins/pgadmin/conf/config_local.py b/plugins/pgadmin/conf/config_local.py new file mode 100644 index 000000000..fa416ed04 --- /dev/null +++ b/plugins/pgadmin/conf/config_local.py @@ -0,0 +1,6 @@ +LOG_FILE = '{$DATA_PATH}/pgadmin4/pgadmin4.log' +SQLITE_PATH = '{$DATA_PATH}/pgadmin4/pgadmin4.db' +SESSION_DB_PATH = '{$DATA_PATH}/pgadmin4/sessions' +STORAGE_DIR = '{$DATA_PATH}/pgadmin4/storage' +AZURE_CREDENTIAL_CACHE_DIR = '{$DATA_PATH}/pgadmin4/azurecredentialcache' +SERVER_MODE = True \ No newline at end of file diff --git a/plugins/pgadmin/conf/pgadmin.conf b/plugins/pgadmin/conf/pgadmin.conf new file mode 100755 index 000000000..3d30beb94 --- /dev/null +++ b/plugins/pgadmin/conf/pgadmin.conf @@ -0,0 +1,21 @@ +server +{ + listen 5051; + server_name 127.0.0.1; + index index.html index.htm index.php; + root {$SERVER_PATH}/pgadmin; + + #error_page 404 /404.html; + + #AUTH_START + auth_basic "Authorization"; + auth_basic_user_file {$SERVER_PATH}/pgadmin/pg.pass; + #AUTH_END + + location / { + proxy_pass http://unix:/tmp/pgadmin4.sock; + } + + access_log {$SERVER_PATH}/pgadmin/access.log; + error_log {$SERVER_PATH}/pgadmin/error.log; +} \ No newline at end of file diff --git a/plugins/pgadmin/ico.png b/plugins/pgadmin/ico.png new file mode 100644 index 000000000..7749e148d Binary files /dev/null and b/plugins/pgadmin/ico.png differ diff --git a/plugins/pgadmin/index.html b/plugins/pgadmin/index.html new file mode 100755 index 000000000..be24cd8d6 --- /dev/null +++ b/plugins/pgadmin/index.html @@ -0,0 +1,22 @@ +
                                +
                                +
                                +

                                服务

                                +

                                重写模版

                                +

                                主页

                                +

                                安全设置

                                +

                                访问日志

                                +

                                错误日志

                                +
                                +
                                +
                                +
                                +
                                + +
                                + \ No newline at end of file diff --git a/plugins/pgadmin/index.py b/plugins/pgadmin/index.py new file mode 100755 index 000000000..fd973ebff --- /dev/null +++ b/plugins/pgadmin/index.py @@ -0,0 +1,422 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'pgadmin' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + return mw.getServerDir() + '/web_conf/nginx/vhost/pgadmin.conf' + + +def getPort(): + file = getConf() + content = mw.readFile(file) + rep = r'listen\s*(.*);' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + + +def getHomePage(): + try: + port = getPort() + ip = '127.0.0.1' + if not mw.isAppleSystem(): + ip = mw.getLocalIp() + + cfg = getCfg() + auth = cfg['username']+':'+cfg['password'] + url = 'http://' + auth + '@' + ip + ':' + port + '/' + return mw.returnJson(True, 'OK', url) + except Exception as e: + return mw.returnJson(False, '插件未启动!') + + + +def contentReplace(content): + cfg = getCfg() + service_path = mw.getServerDir() + + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$APP_PATH}', service_path+'/'+getPluginName()+'/data') + + port = cfg["port"] + rep = r'listen\s*(.*);' + content = re.sub(rep, "listen " + port + ';', content) + return content + + +def initCfg(): + cfg = getServerDir() + "/cfg.json" + if not os.path.exists(cfg): + data = {} + data['port'] = '5051' + data['path'] = '' + data['username'] = 'admin' + data['password'] = 'admin' + + data['web_pg_username'] = 'mdserver-web@gmail.com' + data['web_pg_password'] = 'admin' + mw.writeFile(cfg, json.dumps(data)) + + +def setCfg(key, val): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + data = json.loads(data) + data[key] = val + mw.writeFile(cfg, json.dumps(data)) + + +def getCfg(): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + data = json.loads(data) + return data + + +def returnCfg(): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + return data + + +def __release_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'pgAdmin默认端口', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def __delete_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().delAcceptPort(port, 'tcp') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def openPort(): + conf = getCfg() + port = conf['port'] + for i in [port]: + __release_port(i) + return True + + +def delPort(): + conf = getCfg() + port = conf['port'] + for i in [port]: + __delete_port(i) + return True + + +def cleanNginxLog(): + log_a = accessLog() + log_e = errorLog() + + for i in [log_a, log_e]: + if os.path.exists(i): + cmd = "echo '' > " + i + mw.execShell(cmd) + +def getPythonName(): + cmd = "ls /www/server/pgadmin/run/lib/ | grep python | cut -d \\ -f 1 | awk 'END {print}'" + data = mw.execShell(cmd) + return data[0].strip(); + +def initPgConfFile(): + pyname = getPythonName() + file_tpl = getPluginDir() + '/conf/config_local.py' + dst_file = getServerDir()+'/run/lib/'+pyname+'/site-packages/pgadmin4/config_local.py' + if not os.path.exists(dst_file): + service_path = mw.getServerDir() + content = mw.readFile(file_tpl) + content = content.replace('{$DATA_PATH}', service_path+'/'+getPluginName()+'/data') + mw.writeFile(dst_file, content) + + +def initReplace(): + initPgConfFile() + + file_tpl = getPluginDir() + '/conf/pgadmin.conf' + file_run = getConf() + if not os.path.exists(file_run): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_run, content) + + pass_path = getServerDir() + '/pg.pass' + if not os.path.exists(pass_path): + username = mw.getRandomString(8) + password = mw.getRandomString(10) + pass_cmd = username + ':' + mw.hasPwd(password) + setCfg('username', username) + setCfg('password', password) + mw.writeFile(pass_path, pass_cmd) + + # pg_conf = getCfg() + judge_file = getServerDir()+'/data/pgadmin4/pgadmin4.db' + if not os.path.exists(judge_file): + pg_init_bash = getPluginDir()+'/pg_init.sh' + pg_rand = mw.getRandomString(8) + pg_username = "mw."+pg_rand+"@gmail.com" + setCfg('web_pg_username', pg_username) + pg_password = mw.getRandomString(10) + setCfg('web_pg_password', pg_password) + t = mw.execShell("bash "+ pg_init_bash + " " + pg_username + " " + pg_password) + # print(t) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/pgadmin.service' + + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/pgadmin.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PY_VER}', getPythonName()) + + + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + +def pgOp(method): + file = initReplace() + + current_os = mw.getOs() + if current_os == "darwin": + return 'ok' + + if current_os.startswith("freebsd"): + data = mw.execShell('service' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method+ ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + +def status(): + sock = '/tmp/pgadmin4.sock' + if os.path.exists(sock): + return 'start' + return 'stop' + + +def start(): + initCfg() + openPort() + + pgOp('start') + + mw.restartWeb() + return 'ok' + + +def stop(): + pgOp('stop') + + conf = getConf() + if os.path.exists(conf): + os.remove(conf) + + delPort() + mw.restartWeb() + return 'ok' + + +def restart(): + cleanNginxLog() + state = pgOp('restart') + mw.restartWeb() + return state + + +def reload(): + cleanNginxLog() + return pgOp('reload') + +def getPgOption(): + data = getCfg() + return mw.returnJson(True, 'ok', data) + + +def getPgPort(): + try: + port = getPort() + return mw.returnJson(True, 'OK', port) + except Exception as e: + # print(e) + return mw.returnJson(False, '插件未启动!') + + +def setPgPort(): + args = getArgs() + data = checkArgs(args, ['port']) + if not data[0]: + return data[1] + + port = args['port'] + if port == '80': + return mw.returnJson(False, '80端不能使用!') + + file = getConf() + if not os.path.exists(file): + return mw.returnJson(False, '插件未启动!') + content = mw.readFile(file) + rep = r'listen\s*(.*);' + content = re.sub(rep, "listen " + port + ';', content) + mw.writeFile(file, content) + + setCfg("port", port) + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPgUsername(): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + + username = args['username'] + setCfg('username', username) + + cfg = getCfg() + pma_path = getServerDir() + '/pg.pass' + username = mw.getRandomString(10) + pass_cmd = cfg['username'] + ':' + mw.hasPwd(cfg['password']) + mw.writeFile(pma_path, pass_cmd) + + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPgPassword(): + args = getArgs() + data = checkArgs(args, ['password']) + if not data[0]: + return data[1] + + password = args['password'] + setCfg('password', password) + + cfg = getCfg() + pma_path = getServerDir() + '/pg.pass' + username = mw.getRandomString(10) + pass_cmd = cfg['username'] + ':' + mw.hasPwd(cfg['password']) + mw.writeFile(pma_path, pass_cmd) + + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def accessLog(): + return getServerDir() + '/access.log' + +def errorLog(): + return getServerDir() + '/error.log' + + +def installVersion(): + return mw.readFile(getServerDir() + '/version.pl') + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'conf': + print(getConf()) + elif func == 'version': + print(installVersion()) + elif func == 'get_cfg': + print(returnCfg()) + elif func == 'get_home_page': + print(getHomePage()) + elif func == 'get_pg_port': + print(getPgPort()) + elif func == 'set_pg_port': + print(setPgPort()) + elif func == 'get_pg_option': + print(getPgOption()) + elif func == 'set_pg_username': + print(setPgUsername()) + elif func == 'set_pg_password': + print(setPgPassword()) + elif func == 'access_log': + print(accessLog()) + elif func == 'error_log': + print(errorLog()) + else: + print('error') diff --git a/plugins/pgadmin/info.json b/plugins/pgadmin/info.json new file mode 100755 index 000000000..e1f4eb44e --- /dev/null +++ b/plugins/pgadmin/info.json @@ -0,0 +1,15 @@ +{ + "title":"pgadmin", + "tip":"soft", + "name":"pgadmin", + "type":"运行环境", + "ps":"全能Postgres数据库Web管理工具", + "versions":["4"], + "shell":"install.sh", + "checks":"server/pgadmin", + "path": "server/pgadmin", + "author":"pgadmin", + "home":"https://www.pgadmin.org/download/pgadmin-4-python/", + "date":"2024-10-1", + "pid": "2" +} \ No newline at end of file diff --git a/plugins/pgadmin/init.d/pgadmin.service.tpl b/plugins/pgadmin/init.d/pgadmin.service.tpl new file mode 100644 index 000000000..1d709f353 --- /dev/null +++ b/plugins/pgadmin/init.d/pgadmin.service.tpl @@ -0,0 +1,17 @@ +# It's not recommended to modify this file in-place, because it +# will be overwritten during upgrades. If you want to customize, +# the best way is to use the "systemctl edit" command. +# systemctl daemon-reload + +[Unit] +Description=pgadmin service +After=network.target + +[Service] + +ExecStart={$SERVER_PATH}/pgadmin/run/bin/gunicorn --bind unix:/tmp/pgadmin4.sock --workers=1 --threads=25 --chdir {$SERVER_PATH}/pgadmin/run/lib/{$PY_VER}/site-packages/pgadmin4 pgAdmin4:app +ExecReload=/bin/kill -USR2 $MAINPID +PrivateTmp=false + +[Install] +WantedBy=multi-user.target diff --git a/plugins/pgadmin/install.sh b/plugins/pgadmin/install.sh new file mode 100755 index 000000000..682ddd2dd --- /dev/null +++ b/plugins/pgadmin/install.sh @@ -0,0 +1,127 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } +function version_le() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" == "$1"; } +function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" != "$1"; } +function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + + +P_VER=`python3 -V | awk '{print $2}'` +echo "python:$P_VER" + +# https://cn.linux-console.net/?p=6560 + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/pgadmin && bash install.sh install 4 +# cd /www/server/mdserver-web/plugins/pgadmin && bash install.sh install 4 + +# source /www/server/pgadmin/run/bin/activate +# python /www/server/pgadmin/run/lib/python3.10/site-packages/pgadmin4/setup.py --help +# python /www/server/pgadmin/run/lib/python3.10/site-packages/pgadmin4/setup.py add-user mdserver-web@gmail.com 123123 +# cd /www/server/mdserver-web && python3 plugins/pgadmin/index.py start +# cd /www/server/mdserver-web && python3 plugins/pgadmin/index.py stop + +if [ "$sys_os" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +sysName=`uname` +echo "use system: ${sysName}" + +if [ "${sysName}" == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' +elif grep -Eqi "Raspbian" /etc/issue || grep -Eq "Raspbian" /etc/*-release; then + OSNAME='raspbian' +else + OSNAME='unknow' +fi + +Install_pgadmin() +{ + # if [ -d $serverPath/pgadmin ];then + # exit 0 + # fi + + if version_lt "$P_VER" "3.8.0" ;then + echo 'Python版本太低,无法安装' + exit 0 + fi + PG_DIR=${serverPath}/pgadmin/run + PG_DATA_DIR=${serverPath}/pgadmin/data + mkdir -p $PG_DIR + mkdir -p $PG_DATA_DIR + echo "${1}" > ${serverPath}/pgadmin/version.pl + + if [ ! -f $PG_DIR/bin/activate ];then + if version_ge "$P_VER" "3.11.0" ;then + echo "python3 > 3.11" + cd $PG_DIR && python3 -m venv $PG_DIR + else + echo "python3 < 3.10" + cd $PG_DIR && python3 -m venv . + fi + fi + + if [ -f ${PG_DIR}/bin/activate ];then + source ${PG_DIR}/bin/activate + fi + pip install gunicorn + pip install pgadmin4 + + # pip install https://ftp.postgresql.org/pub/pgadmin/pgadmin4/v8.10/pip/pgadmin4-8.10-py3-none-any.whl + # pip install https://ftp.postgresql.org/pub/pgadmin/pgadmin4/v8.13/pip/pgadmin4-8.13-py3-none-any.whl + + cd ${rootPath} && python3 ${rootPath}/plugins/pgadmin/index.py start + echo '安装完成' +} + +Uninstall_pgadmin() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/pgadmin/index.py stop + + if [ -f /usr/lib/systemd/system/pgadmin.service ];then + systemctl stop pgadmin + systemctl disable pgadmin + rm -rf /usr/lib/systemd/system/pgadmin.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/pgadmin.service ];then + systemctl stop pgadmin + systemctl disable pgadmin + rm -rf /lib/systemd/system/pgadmin.service + systemctl daemon-reload + fi + + rm -rf ${serverPath}/pgadmin + # rm -rf /var/lib/pgadmin + # rm -rf /var/log/pgadmin + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_pgadmin $2 +else + Uninstall_pgadmin $2 +fi diff --git a/plugins/pgadmin/js/pgadmin.js b/plugins/pgadmin/js/pgadmin.js new file mode 100755 index 000000000..0cccb0db9 --- /dev/null +++ b/plugins/pgadmin/js/pgadmin.js @@ -0,0 +1,119 @@ +function pgPost(method,args,callback){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'pgadmin', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function pgAsyncPost(method,args){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'pgadmin', func:method, args:_args}); +} + +function homePage(){ + pgPost('get_home_page', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + var con = ''; + $(".soft-man-con").html(con); + }); +} + + +//phpmyadmin安全设置 +function safeConf() { + pgPost('get_pg_option', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + var cfg = rdata.data; + var con = '
                                \ + 访问端口\ + \ + \ +
                                \ +
                                \ + 用户名\ + \ + \ +
                                \ +
                                \ + 密码\ + \ + \ +
                                \ +
                                \ +
                                pgadmin登录信息
                                \ +
                                \ + PG登录用户名\ + \ + \ +
                                \ +
                                \ + PG登录密码\ + \ + \ +
                                '; + $(".soft-man-con").html(con); + }); +} + +function setPgUsername(){ + var username = $("input[name=username]").val(); + pgPost('set_pg_username',{'username':username}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function setPgPassword(){ + var password = $("input[name=password]").val(); + pgPost('set_pg_password',{'password':password}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +//修改phpmyadmin端口 +function setPgPort() { + var pmport = $("#pmport").val(); + if (pmport < 80 || pmport > 65535) { + layer.msg('端口范围不合法!', { icon: 2 }); + return; + } + var data = 'port=' + pmport; + + pgPost('set_pg_port',data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} \ No newline at end of file diff --git a/plugins/pgadmin/pg_init.sh b/plugins/pgadmin/pg_init.sh new file mode 100644 index 000000000..31c9dd6bb --- /dev/null +++ b/plugins/pgadmin/pg_init.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +python_ver=`ls /www/server/pgadmin/run/lib/ | grep python | cut -d \ -f 1 | awk 'END {print}'` + +email=$1 +email_pwd=$2 + +expect <<-EOF +set time 10 +spawn gunicorn --bind unix:/tmp/pgadmin4.sock \ +--workers=1 \ +--threads=25 \ +--chdir /www/server/pgadmin/run/lib/${python_ver}/site-packages/pgadmin4 pgAdmin4:app +expect { + "Email address:" { send "${email}\r"; exp_continue } + "Password" { send "${email_pwd}\r"; exp_continue } + "Retype password" { send "${email_pwd}\r" } +} +expect eof +EOF + +pids=$(ps aux | grep 'pgAdmin4:app' | grep -v grep | awk '{print $2}') +arr=($pids) +for p in ${arr[@]} +do + kill -9 $p > /dev/null 2>&1 +done + + diff --git a/plugins/php-apt/conf/app_start.php b/plugins/php-apt/conf/app_start.php new file mode 100644 index 000000000..5d1b7ce8c --- /dev/null +++ b/plugins/php-apt/conf/app_start.php @@ -0,0 +1,55 @@ +save_run($xhprof_data, 'xhprof_foo'); + + $profiler_url = sprintf('http://{$LOCAL_IP}:5858/index.php?run=%s&source=xhprof_foo', $run_id); + // echo ""; + + $style_css = 'position:fixed;bottom: 0px;right: 0px;z-index: 100000000;color: red;background-color: black;padding: 0px;margin: 0px;'; + echo 'XHProf分析结果'; + +} + +if (extension_loaded('xhprof') + && isset($_GET[XHProf_Name]) && $_GET[XHProf_Name] == 'ok' && + (! in_array($_SERVER['SCRIPT_NAME'], ['/xhprof_html/callgraph.php', + '/xhprof_html/index.php']))) { + app_xhprof_start(); + include_once $_SERVER['SCRIPT_FILENAME']; + app_xhprof_end(); + exit; +} + +?> diff --git a/plugins/php-apt/conf/enable-php.conf b/plugins/php-apt/conf/enable-php.conf new file mode 100644 index 000000000..612058e6a --- /dev/null +++ b/plugins/php-apt/conf/enable-php.conf @@ -0,0 +1,9 @@ +set $PHP_ENV 1; +location ~ [^/]\.php(/|$) +{ + try_files $uri =404; + fastcgi_pass unix:/run/php/php{$PHP_VERSION}-fpm.sock; + fastcgi_index index.php; + include fastcgi.conf; + include {$SERVER_PATH}/web_conf/php/pathinfo.conf; +} \ No newline at end of file diff --git a/plugins/php-apt/conf/pathinfo.conf b/plugins/php-apt/conf/pathinfo.conf new file mode 100644 index 000000000..47b573684 --- /dev/null +++ b/plugins/php-apt/conf/pathinfo.conf @@ -0,0 +1,8 @@ +set $real_script_name $fastcgi_script_name; +if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") { + set $real_script_name $1; + set $path_info $2; + } +fastcgi_param SCRIPT_FILENAME $document_root$real_script_name; +fastcgi_param SCRIPT_NAME $real_script_name; +fastcgi_param PATH_INFO $path_info; \ No newline at end of file diff --git a/plugins/php-apt/conf/php-fpm.conf b/plugins/php-apt/conf/php-fpm.conf new file mode 100644 index 000000000..aeee58e60 --- /dev/null +++ b/plugins/php-apt/conf/php-fpm.conf @@ -0,0 +1,5 @@ +[global] +pid = /run/php/php{$PHP_VERSION}-fpm.pid +error_log = /var/log/php{$PHP_VERSION}-fpm.log +include=/etc/php/{$PHP_VERSION}/fpm/pool.d/*.conf +php_value[auto_prepend_file]={$SERVER_PATH}/php-apt/app_start.php \ No newline at end of file diff --git a/plugins/php-apt/conf/phpinfo.conf b/plugins/php-apt/conf/phpinfo.conf new file mode 100644 index 000000000..401302116 --- /dev/null +++ b/plugins/php-apt/conf/phpinfo.conf @@ -0,0 +1,4 @@ +location /{$PHP_VERSION} { + root {$ROOT_PATH}/phpinfo; + include {$SERVER_PATH}/web_conf/php/conf/enable-php-apt{$PHP_VERSION}.conf; +} \ No newline at end of file diff --git a/plugins/php-apt/conf/www.conf b/plugins/php-apt/conf/www.conf new file mode 100644 index 000000000..5d9015a48 --- /dev/null +++ b/plugins/php-apt/conf/www.conf @@ -0,0 +1,23 @@ +[www] +user = {$PHP_USER} +group = {$PHP_GROUP} + +listen = /run/php/php{$PHP_VERSION}-fpm.sock +listen.owner = {$PHP_USER} +listen.group = {$PHP_GROUP} +listen.backlog = 4096 +pm = dynamic +pm.max_children = 2 +pm.start_servers = 1 +pm.min_spare_servers = 1 +pm.max_spare_servers = 2 +pm.status_path = /phpfpm_status_apt{$PHP_VERSION} +pm.max_requests = 1000 +request_terminate_timeout = 30 +request_slowlog_timeout = 10 +slowlog = /var/log/fpm-php{$PHP_VERSION}.www.slow.log + +;php_admin_flag[log_errors] = on +;php_admin_value[error_log] = /var/log/fpm-php{$PHP_VERSION}.www.log + + diff --git a/plugins/php-apt/ico.png b/plugins/php-apt/ico.png new file mode 100755 index 000000000..59def5702 Binary files /dev/null and b/plugins/php-apt/ico.png differ diff --git a/plugins/php-apt/index.html b/plugins/php-apt/index.html new file mode 100755 index 000000000..9e72b6b57 --- /dev/null +++ b/plugins/php-apt/index.html @@ -0,0 +1,67 @@ + + + +
                                +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                安装扩展

                                +

                                配置修改

                                +

                                常用功能

                                +

                                配置文件

                                +

                                FPM配置

                                +

                                禁用函数

                                +

                                性能调整

                                +

                                负载状况

                                +

                                会话管理

                                +

                                FPM日志

                                +

                                慢日志

                                +
                                +
                                +
                                +
                                +
                                + +
                                + \ No newline at end of file diff --git a/plugins/php-apt/index.py b/plugins/php-apt/index.py new file mode 100755 index 000000000..8b5a38f23 --- /dev/null +++ b/plugins/php-apt/index.py @@ -0,0 +1,928 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import shutil + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def localVersion(v): + return v[0:1]+v[2:3] + +def getPluginName(): + return 'php-apt' + + +def getAppDir(): + return mw.getServerDir()+'/'+getPluginName() + +def getServerDir(): + return '/etc/php' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(version): + path = getServerDir() + '/' + version + '/fpm/php.ini' + return path + + +def getFpmConfFile(version): + return getServerDir() + '/' + version + '/fpm/pool.d/mw.conf' + +def getFpmFile(version): + return getServerDir() + '/' + version + '/fpm/php-fpm.conf' + +def status(version): + # ps -ef|grep 'php/81' |grep -v grep | grep -v python | awk '{print $2} + cmd = "ps -ef|grep 'php/" + version + "' |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def contentReplace(content, version): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PHP_VERSION}', version) + content = content.replace('{$LOCAL_IP}', mw.getLocalIp()) + + if mw.isAppleSystem(): + # user = mw.execShell( + # "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + content = content.replace('{$PHP_USER}', 'nobody') + content = content.replace('{$PHP_GROUP}', 'nobody') + + rep = r'listen.owner\s*=\s*(.+)\r?\n' + val = ';listen.owner = nobody\n' + content = re.sub(rep, val, content) + + rep = r'listen.group\s*=\s*(.+)\r?\n' + val = ';listen.group = nobody\n' + content = re.sub(rep, val, content) + + rep = r'user\s*=\s*(.+)\r?\n' + val = ';user = nobody\n' + content = re.sub(rep, val, content) + + rep = r'[^\.]group\s*=\s*(.+)\r?\n' + val = ';group = nobody\n' + content = re.sub(rep, val, content) + + else: + content = content.replace('{$PHP_USER}', 'www') + content = content.replace('{$PHP_GROUP}', 'www') + return content + + +def getDstEnablePHP(version): + sdir = mw.getServerDir() + dfile = sdir + '/web_conf/php/conf/enable-php-apt' + version + '.conf' + return dfile + + +def makeOpConf(version): + + sdir = mw.getServerDir() + + dst_dir_conf = sdir + '/web_conf/php/conf' + if not os.path.exists(dst_dir_conf): + mw.execShell('mkdir -p ' + dst_dir_conf) + + pathinfo = sdir + '/web_conf/php/pathinfo.conf' + if not os.path.exists(pathinfo): + source_pathinfo = getPluginDir() + '/conf/pathinfo.conf' + shutil.copyfile(source_pathinfo, pathinfo) + + info = getPluginDir() + '/info.json' + content = mw.readFile(info) + content = json.loads(content) + versions = content['versions'] + tpl = getPluginDir() + '/conf/enable-php.conf' + tpl_content = mw.readFile(tpl) + dfile = getDstEnablePHP(version) + if not os.path.exists(dfile): + w_content = contentReplace(tpl_content, version) + mw.writeFile(dfile, w_content) + + +def phpFpmWwwReplace(version): + service_php_fpm_dir = getServerDir() + '/' + version + '/fpm/pool.d' + if not os.path.exists(service_php_fpm_dir): + os.mkdir(service_php_fpm_dir) + + service_php_fpmwww = service_php_fpm_dir + '/www.conf' + if os.path.exists(service_php_fpmwww): + # 原来文件备份 + mw.execShell('mv ' + service_php_fpmwww + + ' ' + service_php_fpmwww + '.bak') + + service_php_fpm_mw = service_php_fpm_dir + '/mw.conf' + if not os.path.exists(service_php_fpm_mw): + tpl_php_fpmwww = getPluginDir() + '/conf/www.conf' + content = mw.readFile(tpl_php_fpmwww) + content = contentReplace(content, version) + mw.writeFile(service_php_fpm_mw, content) + + +def deleteConfList(version): + enable_conf = getDstEnablePHP(version) + if os.path.exists(enable_conf): + os.remove(enable_conf) + +def phpPrependFile(version): + app_start = getAppDir() + '/app_start.php' + if not os.path.exists(app_start): + tpl = getPluginDir() + '/conf/app_start.php' + content = mw.readFile(tpl) + content = contentReplace(content, version) + mw.writeFile(app_start, content) + +def phpFpmReplace(version): + desc_php_fpm = getServerDir() + '/' + version + '/fpm/php-fpm.conf' + + tpl_php_fpm = getPluginDir() + '/conf/php-fpm.conf' + content = mw.readFile(tpl_php_fpm) + content = contentReplace(content, version) + mw.writeFile(desc_php_fpm, content) + return True + + +def initReplace(version): + makeOpConf(version) + phpFpmWwwReplace(version) + + install_ok = getAppDir() + "/" + localVersion(version) + "/install.ok" + if not os.path.exists(install_ok): + phpFpmReplace(version) + + phpini = getConf(version) + ssl_crt = mw.getSslCrt() + + cmd_openssl = "sed -i \"s#;openssl.cafile=#openssl.cafile=" + ssl_crt + "#\" " + phpini + mw.execShell(cmd_openssl) + cmd_curl = "sed -i \"s#;curl.cainfo =#curl.cainfo=" + ssl_crt + "#\" " + phpini + mw.execShell(cmd_curl) + + mw.writeFile(install_ok, 'ok') + + phpPrependFile(version) + # systemd + # mw.execShell('systemctl daemon-reload') + return 'ok' + + +def phpOp(version, method): + if method == 'start': + initReplace(version) + + if mw.isAppleSystem(): + return 'fail' + + data = mw.execShell('systemctl ' + method + ' ' +'php' + version + '-fpm') + if data[1] == '': + return 'ok' + return data[1] + + +def start(version): + return phpOp(version, 'start') + + +def stop(version): + status = phpOp(version, 'stop') + deleteConfList(version) + return status + + +def restart(version): + return phpOp(version, 'restart') + + +def reload(version): + return phpOp(version, 'reload') + + +def initdStatus(version): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status php' + version + \ + '-fpm | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(version): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable php' + version + '-fpm') + return 'ok' + + +def initdUinstall(version): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable php' + version + '-fpm') + return 'ok' + + +def fpmLog(version): + return '/var/log/php' + version + '-fpm.log' + + +def fpmSlowLog(version): + return '/var/log/fpm-php' + version + '.www.slow.log' + + +def getPhpConf(version): + gets = [ + {'name': 'short_open_tag', 'type': 1, 'ps': '短标签支持'}, + {'name': 'asp_tags', 'type': 1, 'ps': 'ASP标签支持'}, + {'name': 'max_execution_time', 'type': 2, 'ps': '最大脚本运行时间'}, + {'name': 'max_input_time', 'type': 2, 'ps': '最大输入时间'}, + {'name': 'max_input_vars', 'type': 2, 'ps': '最大输入数量'}, + {'name': 'memory_limit', 'type': 2, 'ps': '脚本内存限制'}, + {'name': 'post_max_size', 'type': 2, 'ps': 'POST数据最大尺寸'}, + {'name': 'file_uploads', 'type': 1, 'ps': '是否允许上传文件'}, + {'name': 'upload_max_filesize', 'type': 2, 'ps': '允许上传文件的最大尺寸'}, + {'name': 'max_file_uploads', 'type': 2, 'ps': '允许同时上传文件的最大数量'}, + {'name': 'default_socket_timeout', 'type': 2, 'ps': 'Socket超时时间'}, + {'name': 'error_reporting', 'type': 3, 'ps': '错误级别'}, + {'name': 'display_errors', 'type': 1, 'ps': '是否输出详细错误信息'}, + {'name': 'cgi.fix_pathinfo', 'type': 0, 'ps': '是否开启pathinfo'}, + {'name': 'date.timezone', 'type': 3, 'ps': '时区'} + ] + phpini = mw.readFile(getConf(version)) + result = [] + for g in gets: + rep = g['name'] + r'\s*=\s*([0-9A-Za-z_& ~]+)(\s*;?|\r?\n)' + tmp = re.search(rep, phpini) + if not tmp: + continue + g['value'] = tmp.groups()[0] + result.append(g) + return mw.getJson(result) + + +def submitPhpConf(version): + gets = ['display_errors', 'cgi.fix_pathinfo', 'date.timezone', 'short_open_tag', + 'asp_tags', 'max_execution_time', 'max_input_time', 'max_input_vars', 'memory_limit', + 'post_max_size', 'file_uploads', 'upload_max_filesize', 'max_file_uploads', + 'default_socket_timeout', 'error_reporting'] + args = getArgs() + filename = getConf(version) + phpini = mw.readFile(filename) + for g in gets: + if g in args: + rep = g + r'\s*=\s*(.+)\r?\n' + val = g + ' = ' + args[g] + '\n' + phpini = re.sub(rep, val, phpini) + mw.writeFile(filename, phpini) + reload(version) + return mw.returnJson(True, '设置成功') + + +def getLimitConf(version): + fileini = getConf(version) + phpini = mw.readFile(fileini) + filefpm = getFpmConfFile(version) + phpfpm = mw.readFile(filefpm) + + # print fileini, filefpm + data = {} + try: + rep = r"upload_max_filesize\s*=\s*([0-9]+)M" + tmp = re.search(rep, phpini).groups() + data['max'] = tmp[0] + except: + data['max'] = '50' + + try: + rep = r"request_terminate_timeout\s*=\s*([0-9]+)\n" + tmp = re.search(rep, phpfpm).groups() + data['maxTime'] = tmp[0] + except: + data['maxTime'] = 0 + + try: + rep = r"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n" + tmp = re.search(rep, phpini).groups() + + if tmp[0] == '1': + data['pathinfo'] = True + else: + data['pathinfo'] = False + except: + data['pathinfo'] = False + + return mw.getJson(data) + + +def setMaxTime(version): + args = getArgs() + data = checkArgs(args, ['time']) + if not data[0]: + return data[1] + + time = args['time'] + if int(time) < 30 or int(time) > 86400: + return mw.returnJson(False, '请填写30-86400间的值!') + + filefpm = getFpmConfFile(version) + conf = mw.readFile(filefpm) + rep = r"request_terminate_timeout\s*=\s*([0-9]+)\n" + conf = re.sub(rep, "request_terminate_timeout = " + time + "\n", conf) + mw.writeFile(filefpm, conf) + + fileini = getConf(version) + phpini = mw.readFile(fileini) + rep = r"max_execution_time\s*=\s*([0-9]+)\r?\n" + phpini = re.sub(rep, "max_execution_time = " + time + "\n", phpini) + rep = r"max_input_time\s*=\s*([0-9]+)\r?\n" + phpini = re.sub(rep, "max_input_time = " + time + "\n", phpini) + mw.writeFile(fileini, phpini) + return mw.returnJson(True, '设置成功!') + + +def setMaxSize(version): + args = getArgs() + data = checkArgs(args, ['max']) + if not data[0]: + return data[1] + + maxVal = args['max'] + if int(maxVal) < 2: + return mw.returnJson(False, '上传大小限制不能小于2MB!') + + path = getConf(version) + conf = mw.readFile(path) + rep = r"\nupload_max_filesize\s*=\s*[0-9]+M" + conf = re.sub(rep, u'\nupload_max_filesize = ' + maxVal + 'M', conf) + rep = r"\npost_max_size\s*=\s*[0-9]+M" + conf = re.sub(rep, u'\npost_max_size = ' + maxVal + 'M', conf) + mw.writeFile(path, conf) + + msg = mw.getInfo('设置PHP-{1}最大上传大小为[{2}MB]!', (version, maxVal,)) + mw.writeLog('插件管理[PHP]', msg) + return mw.returnJson(True, '设置成功!') + + +def getFpmConfig(version): + + filefpm = getFpmConfFile(version) + conf = mw.readFile(filefpm) + data = {} + rep = r"\s*pm.max_children\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['max_children'] = tmp[0] + + rep = r"\s*pm.start_servers\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['start_servers'] = tmp[0] + + rep = r"\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['min_spare_servers'] = tmp[0] + + rep = r"\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['max_spare_servers'] = tmp[0] + + rep = r"\s*pm\s*=\s*(\w+)\s*" + tmp = re.search(rep, conf).groups() + data['pm'] = tmp[0] + return mw.getJson(data) + + +def setFpmConfig(version): + args = getArgs() + # if not 'max' in args: + # return 'missing time args!' + + # version = args['version'] + max_children = args['max_children'] + start_servers = args['start_servers'] + min_spare_servers = args['min_spare_servers'] + max_spare_servers = args['max_spare_servers'] + pm = args['pm'] + + filefpm = getFpmConfFile(version) + conf = mw.readFile(filefpm) + + rep = r"\s*pm.max_children\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.max_children = " + max_children, conf) + + rep = r"\s*pm.start_servers\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.start_servers = " + start_servers, conf) + + rep = r"\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.min_spare_servers = " + min_spare_servers, conf) + + rep = r"\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.max_spare_servers = " + max_spare_servers + "\n", conf) + + rep = r"\s*pm\s*=\s*(\w+)\s*" + conf = re.sub(rep, "\npm = " + pm + "\n", conf) + + mw.writeFile(filefpm, conf) + reload(version) + + msg = mw.getInfo('设置PHP-{1}并发设置,max_children={2},start_servers={3},min_spare_servers={4},max_spare_servers={5}', + (version, max_children, start_servers, min_spare_servers, max_spare_servers,)) + mw.writeLog('插件管理[PHP]', msg) + return mw.returnJson(True, '设置成功!') + + +def getFpmAddress(version): + fpm_address = '/run/php/php{}-fpm.sock'.format(version) + php_fpm_file = getFpmConfFile(version) + try: + content = readFile(php_fpm_file) + tmp = re.findall(r"listen\s*=\s*(.+)", content) + if not tmp: + return fpm_address + if tmp[0].find('sock') != -1: + return fpm_address + if tmp[0].find(':') != -1: + listen_tmp = tmp[0].split(':') + if bind: + fpm_address = (listen_tmp[0], int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(tmp[0])) + return fpm_address + except: + return fpm_address + + +def getFpmStatus(version): + + stat = status(version) + if stat == 'stop': + return mw.returnJson(False, 'PHP[' + version + ']未启动!!!') + + sock_file = getFpmAddress(version) + try: + sock_data = mw.requestFcgiPHP(sock_file, '/phpfpm_status_apt' + version + '?json') + + result = str(sock_data, encoding='utf-8') + data = json.loads(result) + fTime = time.localtime(int(data['start time'])) + data['start time'] = time.strftime('%Y-%m-%d %H:%M:%S', fTime) + except Exception as e: + return mw.returnJson(False, str(e)) + + return mw.returnJson(True, "OK", data) + + +def getSessionConf(version): + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(filename) + + rep = r'session.save_handler\s*=\s*([0-9A-Za-z_& ~]+)(\s*;?|\r?\n)' + save_handler = re.search(rep, phpini) + if save_handler: + save_handler = save_handler.group(1) + else: + save_handler = "files" + + reppath = r'\nsession.save_path\s*=\s*"tcp\:\/\/([\d\.]+):(\d+).*\r?\n' + passrep = r'\nsession.save_path\s*=\s*"tcp://[\w\.\?\:]+=(.*)"\r?\n' + memcached = r'\nsession.save_path\s*=\s*"([\d\.]+):(\d+)"' + save_path = re.search(reppath, phpini) + if not save_path: + save_path = re.search(memcached, phpini) + passwd = re.search(passrep, phpini) + port = "" + if passwd: + passwd = passwd.group(1) + else: + passwd = "" + if save_path: + port = save_path.group(2) + save_path = save_path.group(1) + + else: + save_path = "" + + data = {"save_handler": save_handler, "save_path": save_path, + "passwd": passwd, "port": port} + return mw.returnJson(True, 'ok', data) + + +def setSessionConf(version): + + args = getArgs() + + ip = args['ip'] + port = args['port'] + passwd = args['passwd'] + save_handler = args['save_handler'] + + if save_handler != "files": + iprep = r"(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})" + if not re.search(iprep, ip): + return mw.returnJson(False, '请输入正确的IP地址') + + try: + port = int(port) + if port >= 65535 or port < 1: + return mw.returnJson(False, '请输入正确的端口号') + except: + return mw.returnJson(False, '请输入正确的端口号') + prep = r"[\~\`\/\=]" + if re.search(prep, passwd): + return mw.returnJson(False, '请不要输入以下特殊字符 " ~ ` / = "') + + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + phpini = mw.readFile(filename) + + session_tmp = getServerDir() + "/tmp/session" + + rep = r'session.save_handler\s*=\s*(.+)\r?\n' + val = r'session.save_handler = ' + save_handler + '\n' + phpini = re.sub(rep, val, phpini) + + content = mw.execShell( + 'cat /etc/php/' + version + '/fpm/conf.d/*' + " | grep -v '^;' |tr -s '\n'") + content = content[0] + + if save_handler == "memcached": + if not re.search("memcached.so", phpini): + return mw.returnJson(False, '请先安装%s扩展' % save_handler) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "%s:%s" \n' % (ip, port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "' + "/var/lib/php/sessions" + '"', + '\n;session.save_path = "' + "/var/lib/php/sessions" + '"' + val, phpini) + + if save_handler == "memcache": + if not content.find('memcache') > -1: + return mw.returnJson(False, '请先安装%s扩展' % save_handler) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "%s:%s" \n' % (ip, port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "' + "/var/lib/php/sessions" + '"', + '\n;session.save_path = "' + "/var/lib/php/sessions" + '"' + val, phpini) + + if save_handler == "redis": + if not content.find('redis') > -1: + return mw.returnJson(False, '请先安装%s扩展' % save_handler) + if passwd: + passwd = "?auth=" + passwd + else: + passwd = "" + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "tcp://%s:%s%s"\n' % (ip, port, passwd) + res = re.search(rep, phpini) + if res: + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "' + "/var/lib/php/sessions" + '"', + '\n;session.save_path = "' + "/var/lib/php/sessions" + '"' + val, phpini) + + if save_handler == "files": + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "' + session_tmp + '"\n' + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "' + "/var/lib/php/sessions" + '"', + '\n;session.save_path = "' + "/var/lib/php/sessions" + '"' + val, phpini) + + mw.writeFile(filename, phpini) + reload(version) + return mw.returnJson(True, '设置成功!') + + +def getSessionCount_Origin(version): + session_tmp = getServerDir() + "/tmp/session" + d = ["/tmp", "/var/lib/php/sessions", session_tmp] + count = 0 + for i in d: + if not os.path.exists(i): + mw.execShell('mkdir -p %s' % i) + list = os.listdir(i) + for l in list: + if os.path.isdir(i + "/" + l): + l1 = os.listdir(i + "/" + l) + for ll in l1: + if "sess_" in ll: + count += 1 + continue + if "sess_" in l: + count += 1 + + s = "find /tmp -mtime +1 |grep 'sess_' | wc -l" + old_file = int(mw.execShell(s)[0].split("\n")[0]) + + s = "find " + session_tmp + " -mtime +1 |grep 'sess_'|wc -l" + old_file += int(mw.execShell(s)[0].split("\n")[0]) + return {"total": count, "oldfile": old_file} + + +def getSessionCount(version): + data = getSessionCount_Origin(version) + return mw.returnJson(True, 'ok!', data) + + +def cleanSessionOld(version): + s = "find /tmp -mtime +1 |grep 'sess_'|xargs rm -f" + mw.execShell(s) + + session_tmp = getServerDir() + "/tmp/session" + s = "find " + session_tmp + " -mtime +1 |grep 'sess_' |xargs rm -f" + mw.execShell(s) + old_file_conf = getSessionCount_Origin(version)["oldfile"] + if old_file_conf == 0: + return mw.returnJson(True, '清理成功') + else: + return mw.returnJson(True, '清理失败') + + +def getDisableFunc(version): + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(filename) + data = {} + rep = r"disable_functions\s*=\s{0,1}(.*)\n" + tmp = re.search(rep, phpini).groups() + data['disable_functions'] = tmp[0] + return mw.getJson(data) + + +def setDisableFunc(version): + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + + args = getArgs() + disable_functions = args['disable_functions'] + + phpini = mw.readFile(filename) + rep = r"disable_functions\s*=\s*.*\n" + phpini = re.sub(rep, 'disable_functions = ' + + disable_functions + "\n", phpini) + + msg = mw.getInfo('修改PHP-{1}的禁用函数为[{2}]', (version, disable_functions,)) + mw.writeLog('插件管理[PHP-YUM]', msg) + mw.writeFile(filename, phpini) + reload(version) + return mw.returnJson(True, '设置成功!') + + +def getPhpinfo(version): + stat = status(version) + if stat == 'stop': + return 'PHP[' + version + ']未启动,不可访问!!!' + + sock_file = getFpmAddress(version) + root_dir = mw.getFatherDir() + '/phpinfo' + + mw.execShell("rm -rf " + root_dir) + mw.execShell("mkdir -p " + root_dir) + mw.writeFile(root_dir + '/phpinfo.php', '') + sock_data = mw.requestFcgiPHP(sock_file, '/phpinfo.php', root_dir) + os.system("rm -rf " + root_dir) + phpinfo = str(sock_data, encoding='utf-8') + return phpinfo + + +def get_php_info(args): + inputVer = args['version'] + version = inputVer[0] + '.' + inputVer[1] + return getPhpinfo(version) + + +def getLibConf(version): + fname = getConf(version) + if not os.path.exists(fname): + return mw.returnJson(False, '指定PHP版本不存在!') + + # phpini = mw.readFile(fname) + content = mw.execShell('cat /etc/php/' + version + '/fpm/conf.d/*' + " | grep -v '^;' |tr -s '\n'") + content = content[0] + + libpath = getPluginDir() + '/versions/phplib.conf' + phplib = json.loads(mw.readFile(libpath)) + + libs = [] + tasks = mw.M('tasks').where("status!=?", ('1',)).field('status,name').select() + for lib in phplib: + lib['task'] = '1' + for task in tasks: + tmp = mw.getStrBetween('[', ']', task['name']) + if not tmp: + continue + tmp1 = tmp.split('-') + if tmp1[0].lower() == lib['name'].lower(): + lib['task'] = task['status'] + lib['phpversions'] = [] + lib['phpversions'].append(tmp1[1].replace('.','')) + if content.find(lib['check']) == -1: + lib['status'] = False + else: + lib['status'] = True + libs.append(lib) + return mw.returnJson(True, 'OK!', libs) + + +def installLib(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + cmd = "cd " + getPluginDir() + "/versions && /bin/bash common.sh " + version + ' install ' + name + install_name = '安装PHPAPT[' + name + '-' + version + ']' + import thisdb + thisdb.addTask(name=install_name,cmd=cmd) + + mw.triggerTask() + return mw.returnJson(True, '已将下载任务添加到队列!') + + +def uninstallLib(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + execstr = "cd " + getPluginDir() + "/versions && /bin/bash common.sh " + version + ' uninstall ' + name + + data = mw.execShell(execstr) + # data[0] == '' and + if data[1] == '': + return mw.returnJson(True, '已经卸载成功!') + else: + return mw.returnJson(False, '卸载错误信息!:' + data[1]) + +def getConfAppStart(): + pstart = mw.getServerDir() + '/php-apt/app_start.php' + return pstart + +def opcacheBlacklistFile(): + op_bl = mw.getServerDir() + '/php-apt/opcache-blacklist.txt' + return op_bl + +def installPreInspection(version): + sys = mw.execShell( + "cat /etc/*-release | grep PRETTY_NAME |awk -F = '{print $2}' | awk -F '\"' '{print $2}'| awk '{print $1}'") + + if sys[1] != '': + return '不支持改系统' + + sys_id = mw.execShell( + "cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F '\"' '{print $2}'") + + sysName = sys[0].strip().lower() + sysId = sys_id[0].strip() + + if not sysName in ('debian', 'ubuntu'): + return '仅支持debian,ubuntu' + + return 'ok' + +if __name__ == "__main__": + + if len(sys.argv) < 3: + print('missing parameters') + exit(0) + + func = sys.argv[1] + + inputVer = sys.argv[2] + version = inputVer[0] + '.' + inputVer[1] + + if func == 'status': + print(status(version)) + elif func == 'start': + print(start(version)) + elif func == 'stop': + print(stop(version)) + elif func == 'restart': + print(restart(version)) + elif func == 'reload': + print(reload(version)) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'initd_status': + print(initdStatus(version)) + elif func == 'initd_install': + print(initdInstall(version)) + elif func == 'initd_uninstall': + print(initdUinstall(version)) + elif func == 'fpm_log': + print(fpmLog(version)) + elif func == 'fpm_slow_log': + print(fpmSlowLog(version)) + elif func == 'conf': + print(getConf(version)) + elif func == 'app_start': + print(getConfAppStart()) + elif func == 'opcache_blacklist_file': + print(opcacheBlacklistFile()) + elif func == 'get_php_conf': + print(getPhpConf(version)) + elif func == 'get_fpm_conf_file': + print(getFpmConfFile(version)) + elif func == 'get_fpm_file': + print(getFpmFile(version)) + elif func == 'submit_php_conf': + print(submitPhpConf(version)) + elif func == 'get_limit_conf': + print(getLimitConf(version)) + elif func == 'set_max_time': + print(setMaxTime(version)) + elif func == 'set_max_size': + print(setMaxSize(version)) + elif func == 'get_fpm_conf': + print(getFpmConfig(version)) + elif func == 'set_fpm_conf': + print(setFpmConfig(version)) + elif func == 'get_fpm_status': + print(getFpmStatus(version)) + elif func == 'get_session_conf': + print(getSessionConf(version)) + elif func == 'set_session_conf': + print(setSessionConf(version)) + elif func == 'get_session_count': + print(getSessionCount(version)) + elif func == 'clean_session_old': + print(cleanSessionOld(version)) + elif func == 'get_disable_func': + print(getDisableFunc(version)) + elif func == 'set_disable_func': + print(setDisableFunc(version)) + elif func == 'get_phpinfo': + print(getPhpinfo(version)) + elif func == 'get_lib_conf': + print(getLibConf(version)) + elif func == 'install_lib': + print(installLib(version)) + elif func == 'uninstall_lib': + print(uninstallLib(version)) + else: + print("fail") diff --git a/plugins/php-apt/index_php_apt.py b/plugins/php-apt/index_php_apt.py new file mode 100755 index 000000000..15eaed2b6 --- /dev/null +++ b/plugins/php-apt/index_php_apt.py @@ -0,0 +1,171 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import shutil + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +if mw.isAppleSystem(): + cmd = 'ls /usr/local/lib/ | grep python | cut -d \\ -f 1 | awk \'END {print}\'' + info = mw.execShell(cmd) + p = "/usr/local/lib/" + info[0].strip() + "/site-packages" + sys.path.append(p) + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'php-apt' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return '/etc/php' + + +def getInitDFile(version): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + return '/etc/init.d/' + getPluginName() + version + + +def getConf(version): + path = getServerDir() + '/' + version + '/fpm/php.ini' + return path + + +def getFpmConfFile(version): + return getServerDir() + '/' + version + '/fpm/pool.d/mw.conf' + + +def status_progress(version): + # ps -ef|grep 'php/81' |grep -v grep | grep -v python | awk '{print $2} + cmd = "ps aux|grep 'php/" + version + \ + "' |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def getPhpSocket(version): + path = getFpmConfFile(version) + content = mw.readFile(path) + rep = r'listen\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def status(version): + ''' + sock文件判断是否启动 + ''' + sock = getPhpSocket(version) + if sock.find(':'): + return status_progress(version) + + if not os.path.exists(sock): + return 'stop' + return 'start' + + +def getFpmAddress(version): + fpm_address = '/run/php/php{}-fpm.sock'.format(version) + php_fpm_file = getFpmConfFile(version) + try: + content = readFile(php_fpm_file) + tmp = re.findall(r"listen\s*=\s*(.+)", content) + if not tmp: + return fpm_address + if tmp[0].find('sock') != -1: + return fpm_address + if tmp[0].find(':') != -1: + listen_tmp = tmp[0].split(':') + if bind: + fpm_address = (listen_tmp[0], int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(tmp[0])) + return fpm_address + except: + return fpm_address + + +def getPhpinfo(version): + stat = status(version) + if stat == 'stop': + return 'PHP[' + version + ']未启动,不可访问!!!' + + sock_file = getFpmAddress(version) + # print(sock_file) + root_dir = mw.getFatherDir() + '/phpinfo' + + mw.execShell("rm -rf " + root_dir) + mw.execShell("mkdir -p " + root_dir) + mw.writeFile(root_dir + '/phpinfo.php', '') + sock_data = mw.requestFcgiPHP(sock_file, '/phpinfo.php', root_dir) + os.system("rm -rf " + root_dir) + phpinfo = str(sock_data, encoding='utf-8') + return phpinfo + + +def libConfCommon(version): + fname = getConf(version) + if not os.path.exists(fname): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(fname) + + libpath = getPluginDir() + '/versions/phplib.conf' + phplib = json.loads(mw.readFile(libpath)) + + libs = [] + tasks = mw.M('tasks').where("status!=?", ('1',)).field('status,name').select() + for lib in phplib: + lib['task'] = '1' + for task in tasks: + tmp = mw.getStrBetween('[', ']', task['name']) + if not tmp: + continue + tmp1 = tmp.split('-') + if tmp1[0].lower() == lib['name'].lower(): + lib['task'] = task['status'] + lib['phpversions'] = [] + lib['phpversions'].append(tmp1[1]) + if phpini.find(lib['check']) == -1: + lib['status'] = False + else: + lib['status'] = True + libs.append(lib) + return libs + + +def get_php_info(args): + version = args['version'] + apt_ver = version[0:1]+'.'+version[1:2] + return getPhpinfo(apt_ver) + + +def get_lib_conf(data): + libs = libConfCommon(data['version']) + return mw.returnData(True, 'OK!', libs) diff --git a/plugins/php-apt/info.json b/plugins/php-apt/info.json new file mode 100755 index 000000000..d13b200c6 --- /dev/null +++ b/plugins/php-apt/info.json @@ -0,0 +1,19 @@ +{ + "sort": 7, + "ps": "PHP是世界上最好的编程语言(极速安装)", + "shell": "install.sh", + "name": "php-apt", + "title": "PHP[APT]", + "coexist": true, + "versions": ["56","70","71","72","73","74","80","81","82","83","84", "85"], + "install_pre_inspection":true, + "tip": "soft", + "checks": "server/php-apt/VERSION", + "path": "server/php-apt/VERSION", + "display": 1, + "author": "Zend", + "date": "2022-07-07", + "home": "https://www.php.net", + "type": "PHP语言解释器", + "pid": "6" +} \ No newline at end of file diff --git a/plugins/php-apt/install.sh b/plugins/php-apt/install.sh new file mode 100755 index 000000000..ac4eefb2b --- /dev/null +++ b/plugins/php-apt/install.sh @@ -0,0 +1,127 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/php-apt && bash install.sh install 56 + +if id www &> /dev/null ;then + echo "www uid is `id -u www`" + echo "www shell is `grep "^www:" /etc/passwd |cut -d':' -f7 `" +else + groupadd www + useradd -g www -s /sbin/nologin www + # useradd -g www -s /bin/bash www +fi + +_os=`uname` +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' +else + OSNAME='unknow' +fi + +action=$1 +type=$2 +apt_ver=${type:0:1}.${type:1:2} + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + + +if [ "$OSNAME" == "ubuntu" ];then + find_source=`ls /etc/apt/sources.list.d | grep ondrej-ubuntu-php` + if [ "$find_source" == "" ];then + echo "y" | LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php && apt update -y + fi +fi +# apt install $(grep-aptavail -S PHP-defaults -s Package -n) + + +if [ ! -f /etc/apt/sources.list.d/php.list ] && [ "$OSNAME" == "debian" ];then + # install php source + apt install -y apt-transport-https lsb-release ca-certificates curl + cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") + if [ ! -z "$cn" ];then + curl -o /usr/share/keyrings/deb.sury.org-php.gpg https://mirror.sjtu.edu.cn/sury/php/apt.gpg + sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://mirror.sjtu.edu.cn/sury/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' + else + curl -o /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg + sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' + fi + apt update -y +fi + + +if [ "${action}" == "uninstall" ] && [ -d ${serverPath}/php-apt/${type} ];then + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/php-apt/index.py stop ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/php-apt/index.py initd_uninstall ${type} + + if [ -f /lib/systemd/system/php${apt_ver}-fpm.service ];then + rm -rf /lib/systemd/system/php${apt_ver}-fpm.service + fi + + if [ -f /lib/systemd/system/system/php${apt_ver}-fpm.service ];then + rm -rf /lib/systemd/system/php${apt_ver}-fpm.service + fi + + systemctl daemon-reload +fi + +cd ${curPath} && sh -x $curPath/versions/$2/install.sh $1 + +if [ "${action}" == "install" ] && [ -d ${serverPath}/php-apt/${type} ];then + apt update -y + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/php-apt/index.py start ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/php-apt/index.py restart ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/php-apt/index.py initd_install ${type} + + # 安装通用扩展 + echo "install PHP-APT[${type}] extend start" + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install curl + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install gd + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install iconv + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install exif + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install intl + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install xml + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install mcrypt + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install bcmath + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install mysqlnd + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install mysql + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install gettext + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install redis + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install memcached + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install mbstring + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install zip + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install mongodb + cd ${rootPath}/plugins/php-apt/versions && bash common.sh ${apt_ver} install opcache + echo "install PHP-APT[${type}] extend end" + + if [ ! -f /usr/local/bin/composer ];then + cd /tmp + curl -sS https://getcomposer.org/installer | /usr/bin/php${apt_ver} + mv composer.phar /usr/local/bin/composer + fi + + systemctl restart php${apt_ver}-fpm +fi + + diff --git a/plugins/php-apt/js/php.js b/plugins/php-apt/js/php.js new file mode 100755 index 000000000..3f1d4055c --- /dev/null +++ b/plugins/php-apt/js/php.js @@ -0,0 +1,747 @@ +function phpPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'php-apt'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function phpPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'php-apt'; + req_data['func'] = method; + req_data['script']='index_php_apt'; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +//配置修改 +function phpSetConfig(version) { + phpPost('get_php_conf', version,'',function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '70' + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = '' + break; + case 1: + var selected_1 = (rdata[i].value == 'On') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'Off') ? 'selected' : ''; + ibody = '' + break; + } + mlist += '

                                ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

                                ' + } + var phpCon = '
                                \ + ' + mlist + '\ +
                                \ +
                                ' + $(".soft-man-con").html(phpCon); + }); +} + + +//提交PHP配置 +function submitConf(version) { + var data = { + version: version, + display_errors: $("select[name='display_errors']").val(), + 'cgi.fix_pathinfo': $("select[name='cgi.fix_pathinfo']").val(), + 'date.timezone': $("input[name='date.timezone']").val(), + short_open_tag: $("select[name='short_open_tag']").val(), + asp_tags: $("select[name='asp_tags']").val() || 'On', + safe_mode: $("select[name='safe_mode']").val(), + max_execution_time: $("input[name='max_execution_time']").val(), + max_input_time: $("input[name='max_input_time']").val(), + max_input_vars: $("input[name='max_input_vars']").val(), + memory_limit: $("input[name='memory_limit']").val(), + post_max_size: $("input[name='post_max_size']").val(), + file_uploads: $("select[name='file_uploads']").val(), + upload_max_filesize: $("input[name='upload_max_filesize']").val(), + max_file_uploads: $("input[name='max_file_uploads']").val(), + default_socket_timeout: $("input[name='default_socket_timeout']").val(), + error_reporting: $("input[name='error_reporting']").val() || 'On' + }; + + phpPost('submit_php_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + // console.log(rdata); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +//php超时限制 +function phpCommonFunc(version){ + phpPost('get_limit_conf', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + var con = '

                                \ + 超时限制\ + , 秒\ + \ +

                                '; + + con += '

                                \ + 上传限制\ + ,MB\ + \ +

                                '; + + // \ + con += '

                                \ + \ + \ + \ + \ +

                                '; + + $(".soft-man-con").html(con); + }); +} + +//设置超时限制 +function setPHPMaxTime(version) { + var max = $(".phpTimeLimit").val(); + phpPost('set_max_time',version,{'time':max},function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + phpCommonFunc(version); + },{ icon: rdata.status ? 1 : 2 }); + + }); +} +//设置PHP上传限制 +function setPHPMaxSize(version) { + max = $(".phpUploadLimit").val(); + if (max < 2) { + alert(max); + layer.msg('上传大小限制不能小于2M', { icon: 2 }); + return; + } + + phpPost('set_max_size',version,{'max':max},function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function phpPreload(version){ + phpPost('app_start',version,{},function(data){ + onlineEditFile(0, data['data']); + }); +} + +function phpOpcacheBlacklist(version){ + phpPost('opcache_blacklist_file',version,{},function(data){ + onlineEditFile(0, data['data']); + }); +} + +function phpFpmRoot(version){ + phpPost('get_fpm_file',version,{},function(data){ + onlineEditFile(0, data['data']); + }); +} + +function getFpmConfig(version, pool = 'www'){ + phpPost('get_fpm_conf', version, {'pool':pool}, function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var limitList = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + var pms = [{ 'name': 'static', 'title': '静态' }, { 'name': 'dynamic', 'title': '动态' },{ 'name': 'ondemand', 'title': '按需' }]; + var pmList = ''; + for (var i = 0; i < pms.length; i++) { + pmList += ''; + } + + var poolHtml = "" + + ""; + + var body = "
                                " + + "

                                应用池[pool]:

                                " + + "

                                并发方案:

                                " + + "

                                运行模式:*PHP-FPM运行模式

                                " + + "

                                max_children:*允许创建的最大子进程数

                                " + + "

                                start_servers: *起始进程数(服务启动后初始进程数量)

                                " + + "

                                min_spare_servers: *最小空闲进程数(清理空闲进程后的保留数量)

                                " + + "

                                max_spare_servers: *最大空闲进程数(当空闲进程达到此值时清理)

                                " + + "
                                " + + "
                                "; + + $(".soft-man-con").html(body); + $("select[name='limit']").change(function() { + var type = $(this).val(); + var max_children = rdata.max_children; + var start_servers = rdata.start_servers; + var min_spare_servers = rdata.min_spare_servers; + var max_spare_servers = rdata.max_spare_servers; + switch (type) { + case '0': + max_children = 2; + start_servers = 1; + min_spare_servers = 1; + max_spare_servers = 2; + break; + case '1': + max_children = 5; + start_servers = 2; + min_spare_servers = 1; + max_spare_servers = 5; + break; + case '2': + max_children = 10; + start_servers = 2; + min_spare_servers = 1; + max_spare_servers = 10; + break; + case '3': + max_children = 30; + start_servers = 5; + min_spare_servers = 5; + max_spare_servers = 20; + break; + case '4': + max_children = 50; + start_servers = 15; + min_spare_servers = 15; + max_spare_servers = 35; + break; + case '5': + max_children = 100; + start_servers = 20; + min_spare_servers = 20; + max_spare_servers = 70; + break; + case '6': + max_children = 200; + start_servers = 25; + min_spare_servers = 25; + max_spare_servers = 150; + break; + case '7': + max_children = 300; + start_servers = 30; + min_spare_servers = 30; + max_spare_servers = 180; + break; + case '8': + max_children = 500; + start_servers = 35; + min_spare_servers = 35; + max_spare_servers = 250; + break; + case '9': + max_children = 2000; + start_servers = 40; + min_spare_servers = 40; + max_spare_servers = 255; + break; + } + + $("input[name='max_children']").val(max_children); + $("input[name='start_servers']").val(start_servers); + $("input[name='min_spare_servers']").val(min_spare_servers); + $("input[name='max_spare_servers']").val(max_spare_servers); + }); + + $('select[name="pool"]').change(function(){ + var pool = $(this).val(); + getFpmConfig(version, pool); + }); + }); +} + +function setFpmConfig(version){ + var max_children = Number($("input[name='max_children']").val()); + var start_servers = Number($("input[name='start_servers']").val()); + var min_spare_servers = Number($("input[name='min_spare_servers']").val()); + var max_spare_servers = Number($("input[name='max_spare_servers']").val()); + var pm = $("select[name='pm']").val(); + + if (max_children < max_spare_servers) { + layer.msg('max_spare_servers 不能大于 max_children', { icon: 2 }); + return; + } + + if (min_spare_servers > start_servers) { + layer.msg('min_spare_servers 不能大于 start_servers', { icon: 2 }); + return; + } + + if (max_spare_servers < min_spare_servers) { + layer.msg('min_spare_servers 不能大于 max_spare_servers', { icon: 2 }); + return; + } + + if (max_children < start_servers) { + layer.msg('start_servers 不能大于 max_children', { icon: 2 }); + return; + } + + if (max_children < 1 || start_servers < 1 || min_spare_servers < 1 || max_spare_servers < 1) { + layer.msg('配置值不能小于1', { icon: 2 }); + return; + } + + var data = { + version:version, + max_children:max_children, + start_servers:start_servers, + min_spare_servers:min_spare_servers, + max_spare_servers:max_spare_servers, + pm:pm, + }; + phpPost('set_fpm_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +function getFpmStatus(version){ + phpPost('get_fpm_status', version, '', function(ret_data){ + var tmp_data = $.parseJSON(ret_data.data); + if(!tmp_data.status){ + layer.msg(tmp_data.msg, { icon: tmp_data.status ? 1 : 2 }); + return; + } + + var rdata = tmp_data.data; + var php_fpm_status = '动态'; + if (rdata['process manager'] == 'dynamic'){ + php_fpm_status = '动态'; + } else if(rdata['process manager'] == 'static'){ + php_fpm_status = '静态'; + } else if(rdata['process manager'] == 'ondemand'){ + php_fpm_status = '按需'; + } + + + var con = "
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                应用池(pool)" + rdata.pool + "
                                进程管理方式(process manager)" + php_fpm_status + "
                                启动日期(start time)" + rdata['start time'] + "
                                请求数(accepted conn)" + rdata['accepted conn'] + "
                                请求队列(listen queue)" + rdata['listen queue'] + "
                                最大等待队列(max listen queue)" + rdata['max listen queue'] + "
                                socket队列长度(listen queue len)" + rdata['listen queue len'] + "
                                空闲进程数量(idle processes)" + rdata['idle processes'] + "
                                活跃进程数量(active processes)" + rdata['active processes'] + "
                                总进程数量(total processes)" + rdata['total processes'] + "
                                最大活跃进程数量(max active processes)" + rdata['max active processes'] + "
                                到达进程上限次数(max children reached)" + rdata['max children reached'] + "
                                慢请求数量(slow requests)" + rdata['slow requests'] + "
                                "; + $(".soft-man-con").html(con); + $(".GetPHPStatus td,.GetPHPStatus th").css("padding", "7px"); + }); +} + +//禁用函数 +function disableFunc(version) { + phpPost('get_disable_func', version,'',function(data){ + var rdata = $.parseJSON(data.data); + var disable_functions = rdata.disable_functions.split(','); + var dbody = '' + for (var i = 0; i < disable_functions.length; i++) { + if (disable_functions[i] == '') continue; + dbody += "" + disable_functions[i] + "删除"; + } + + var con = "
                                " + + "" + + "" + + "
                                " + + "
                                " + + "" + + "" + dbody + "" + + "
                                名称操作
                                "; + + con += '
                                  \ +
                                • 在此处可以禁用指定函数的调用,以增强环境安全性!
                                • \ +
                                • 强烈建议禁用如exec,system等危险函数!
                                • \ +
                                '; + + $(".soft-man-con").html(con); + }); +} + + + +function getSessionConfig(version){ + phpPost('get_session_conf', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + if(!rdata.status){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + var rdata = rdata.data; + + var cacheList = "" + + "" + + "" + + ""; + + + var info = rdata.save_path.split(":"); + var con = "
                                " + + "

                                存储模式:

                                " + + "

                                IP地址:

                                " + + "

                                端口:

                                " + + "

                                密码:

                                " + + "

                                " + + "
                                \ +
                                  \ +
                                • 若你的站点并发比较高,使用Redis,Memcache能有效提升PHP并发能力
                                • \ +
                                • 若调整Session模式后,网站访问异常,请切换回原来的模式
                                • \ +
                                • 切换Session模式会使在线的用户会话丢失,请在流量小的时候切换
                                • \ +
                                \ +
                                \ +
                                \ + "; + + $(".soft-man-con").html(con); + + if (rdata.save_handler == 'files'){ + $('input[name="ip"]').attr('disabled','disabled'); + $('input[name="port"]').attr('disabled','disabled'); + $('input[name="passwd"]').attr('placeholder','如果没有密码留空'); + $('input[name="passwd"]').attr('disabled','disabled'); + } + + // change event + $("select[name='save_handler']").change(function() { + var type = $(this).val(); + + var passwd = $('input[name="passwd"]').val(); + if (passwd == ""){ + $('input[name="passwd"]').attr('placeholder','如果没有密码留空'); + } + + var ip = $('input[name="ip"]').val(); + if (ip == ""){ + $('input[name="ip"]').val('127.0.0.1'); + } + + switch (type) { + case 'redis': + var port = $('input[name="port"]').val(); + if (port == ""){ + $('input[name="port"]').val('6379'); + } + $('input[name="ip"]').removeAttr('disabled'); + $('input[name="port"]').removeAttr('disabled'); + $('input[name="passwd"]').removeAttr('disabled'); + break; + case 'files': + $('input[name="ip"]').val("").attr('disabled','disabled'); + $('input[name="port"]').val("").attr('disabled','disabled'); + $('input[name="passwd"]').val("").attr('disabled','disabled'); + break; + case 'memcache': + var port = $('input[name="port"]').val(); + if (port == ""){ + $('input[name="port"]').val('11211'); + } + $('input[name="ip"]').removeAttr('disabled'); + $('input[name="port"]').removeAttr('disabled'); + $('input[name="passwd"]').removeAttr('disabled'); + break; + case 'memcached': + var port = $('input[name="port"]').val(); + if (port == ""){ + $('input[name="port"]').val('11211'); + } + $('input[name="ip"]').removeAttr('disabled'); + $('input[name="port"]').removeAttr('disabled'); + $('input[name="passwd"]').removeAttr('disabled'); + break; + } + }); + + //load session stats + phpPost('get_session_count', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + if(!rdata.status){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + var rdata = rdata.data; + + var html_var = "
                                清理Session文件
                                \ +
                                \ +
                                \ +
                                总Session文件数量"+rdata.total+"
                                \ +
                                可清理的Session文件数量"+rdata.oldfile+"
                                \ +
                                \ + "; + + $("#session_clear").html(html_var); + + + $('#clean_func').click(function(){ + phpPost('clean_session_old', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + showMsg(rdata.msg,function(){ + getSessionConfig(version); + },{ icon: rdata.status ? 1 : 2 }); + }); + }); + }); + }); + +} + +function setSessionConfig(version){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var passwd = $('input[name="passwd"]').val(); + var save_handler = $("select[name='save_handler']").val(); + var data = { + ip:ip, + port:port, + passwd:passwd, + save_handler:save_handler, + }; + phpPost('set_session_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +//设置禁用函数 +function setDisableFunc(version, act, fs) { + var fsArr = fs.split(','); + if (act == 1) { + var functions = $("#disable_function_val").val(); + for (var i = 0; i < fsArr.length; i++) { + if (functions == fsArr[i]) { + layer.msg(lan.soft.fun_msg, { icon: 5 }); + return; + } + } + fs += ',' + functions; + msg = '添加成功'; + } else { + + fs = ''; + for (var i = 0; i < fsArr.length; i++) { + if (act == fsArr[i]) continue; + fs += fsArr[i] + ',' + } + msg = '删除成功'; + fs = fs.substr(0, fs.length - 1); + } + + var data = { + 'version':version, + 'disable_functions':fs, + }; + + phpPost('set_disable_func', version,data,function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.status ? msg : rdata.msg, function(){ + disableFunc(version); + } ,{ icon: rdata.status ? 1 : 2 }); + }); +} + + +//phpinfo +function getPhpinfo(version) { + var con = ''; + $(".soft-man-con").html(con); +} + +//获取PHPInfo +function getPHPInfo_old(version) { + phpPost('get_phpinfo', version, '', function(data){ + var rdata = data.data; + layer.open({ + type: 1, + title: "PHP-" + version + "-PHPINFO", + area: ['90%', '90%'], + closeBtn: 2, + shadeClose: true, + content: rdata + }); + }); +} + +function getPHPInfo(version) { + phpPostCallbak('get_php_info', version, {}, function(data){ + if (!data.status){ + layer.msg(rdata.msg, { icon: 2 }); + return; + } + + layer.open({ + type: 1, + title: "PHP-" + version + "-PHPINFO", + area: ['70%', '90%'], + closeBtn: 2, + shadeClose: true, + content: data.data.replace('a:link {color: #009; text-decoration: none; background-color: #fff;}', '').replace('a:link {color: #000099; text-decoration: none; background-color: #ffffff;}', '') + }); + }) +} + + + +function phpLibConfig(version){ + + phpPost('get_lib_conf', version, '', function(data){ + var rdata = $.parseJSON(data.data); + + if (!rdata.status){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + + var libs = rdata.data; + var body = ''; + var opt = ''; + + for (var i = 0; i < libs.length; i++) { + if (libs[i].versions.indexOf(version) == -1){ + continue; + } + + if (libs[i]['task'] == '-1' && libs[i].phpversions.indexOf(version) != -1) { + opt = '安装.' + } else if (libs[i]['task'] == '0' && libs[i].phpversions.indexOf(version) != -1) { + opt = '等待.' + } else if (libs[i].status) { + opt = '卸载' + } else { + opt = '安装' + } + + body += '' + + '' + libs[i].name + '' + + '' + libs[i].type + '' + + '' + libs[i].msg + '' + + '' + + '' + opt + '' + + ''; + } + + + var con = '
                                ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + body + '' + + '
                                名称类型说明状态操作
                                ' + + '
                                ' + + '
                                  \ +
                                • 请按实际需求安装扩展,不要安装不必要的PHP扩展,这会影响PHP执行效率,甚至出现异常
                                • \ +
                                • Redis扩展只允许在1个PHP版本中使用,安装到其它PHP版本请在[软件管理]重装Redis
                                • \ +
                                • opcache/xcache/apc等脚本缓存扩展,请只安装其中1个,否则可能导致您的站点程序异常
                                • \ +
                                • ioncube要在ZendGuardLoader/opcache前安装,否则可能导致您的站点程序异常
                                • \ +
                                '; + $('.soft-man-con').html(con); + }); + +} + +//安装扩展 +function installPHPLib(version, name, title, pathinfo) { + layer.confirm('您真的要卸载{1}吗?'.replace('{1}', name), { icon: 3, closeBtn: 2 }, function() { + name = name.toLowerCase(); + var data = "name=" + name + "&version=" + version + "&type=1"; + + phpPost('install_lib', version, data, function(data){ + var rdata = $.parseJSON(data.data); + // layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + showMsg(rdata.msg, function(){ + getTaskCount(); + phpLibConfig(version); + },{ icon: rdata.status ? 1 : 2 }); + + }); + }); +} + +//卸载扩展 +function uninstallPHPLib(version, name, title, pathinfo) { + layer.confirm('您真的要安装{1}吗?'.replace('{1}', name), { icon: 3, closeBtn: 2 }, function() { + name = name.toLowerCase(); + var data = 'name=' + name + '&version=' + version; + phpPost('uninstall_lib', version, data, function(data){ + var rdata = $.parseJSON(data.data); + // layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + showMsg(rdata.msg, function(){ + getTaskCount(); + phpLibConfig(version); + },{ icon: rdata.status ? 1 : 2 },5000); + + }); + }); +} \ No newline at end of file diff --git a/plugins/php-apt/versions/56/install.sh b/plugins/php-apt/versions/56/install.sh new file mode 100755 index 000000000..ba3973dc5 --- /dev/null +++ b/plugins/php-apt/versions/56/install.sh @@ -0,0 +1,50 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=5.6 +PHP_VER=56 + +# apt -y install php5.6 php5.6-fpm php5.6-dev +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + + +# apt -y remove php5.6 php5.6-* +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/70/install.sh b/plugins/php-apt/versions/70/install.sh new file mode 100755 index 000000000..3631acf80 --- /dev/null +++ b/plugins/php-apt/versions/70/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=7.0 +PHP_VER=70 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/71/install.sh b/plugins/php-apt/versions/71/install.sh new file mode 100755 index 000000000..c8955a33a --- /dev/null +++ b/plugins/php-apt/versions/71/install.sh @@ -0,0 +1,48 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=7.1 +PHP_VER=71 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/72/install.sh b/plugins/php-apt/versions/72/install.sh new file mode 100755 index 000000000..816f7f4db --- /dev/null +++ b/plugins/php-apt/versions/72/install.sh @@ -0,0 +1,51 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=7.2 +PHP_VER=72 + + +# apt -y install php7.2 php7.2-fpm php7.2-dev +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +# systemctl status php7.2-fpm +# apt -y remove php7.2 php7.2-fpm php7.2-dev +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/73/install.sh b/plugins/php-apt/versions/73/install.sh new file mode 100755 index 000000000..475825e55 --- /dev/null +++ b/plugins/php-apt/versions/73/install.sh @@ -0,0 +1,48 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=7.3 +PHP_VER=73 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/74/install.sh b/plugins/php-apt/versions/74/install.sh new file mode 100755 index 000000000..956a864ff --- /dev/null +++ b/plugins/php-apt/versions/74/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=7.4 +PHP_VER=74 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/80/install.sh b/plugins/php-apt/versions/80/install.sh new file mode 100755 index 000000000..f30fba31c --- /dev/null +++ b/plugins/php-apt/versions/80/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=8.0 +PHP_VER=80 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/81/install.sh b/plugins/php-apt/versions/81/install.sh new file mode 100755 index 000000000..124c765ad --- /dev/null +++ b/plugins/php-apt/versions/81/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=8.1 +PHP_VER=81 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/82/install.sh b/plugins/php-apt/versions/82/install.sh new file mode 100755 index 000000000..b23415303 --- /dev/null +++ b/plugins/php-apt/versions/82/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=8.2 +PHP_VER=82 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/83/install.sh b/plugins/php-apt/versions/83/install.sh new file mode 100755 index 000000000..e0ace4c94 --- /dev/null +++ b/plugins/php-apt/versions/83/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=8.3 +PHP_VER=83 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/84/install.sh b/plugins/php-apt/versions/84/install.sh new file mode 100755 index 000000000..435102c7a --- /dev/null +++ b/plugins/php-apt/versions/84/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=8.4 +PHP_VER=84 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/85/install.sh b/plugins/php-apt/versions/85/install.sh new file mode 100755 index 000000000..ba55c8dae --- /dev/null +++ b/plugins/php-apt/versions/85/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +version=8.5 +PHP_VER=85 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +apt -y install php${version} php${version}-fpm php${version}-dev +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-apt/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ +#------------------------ uninstall start ------------------------------------# +apt -y remove php${version} php${version}-* +rm -rf $serverPath/php-apt/${PHP_VER} +echo "卸载php-${version}..." +#------------------------ uninstall start ------------------------------------# +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-apt/versions/all_test.sh b/plugins/php-apt/versions/all_test.sh new file mode 100644 index 000000000..b74035747 --- /dev/null +++ b/plugins/php-apt/versions/all_test.sh @@ -0,0 +1,56 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/php-apt/versions && /bin/bash all_test.sh +# cd /www/server/mdserver-web/plugins/php-apt && bash install.sh install 85 + + +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/php-apt/index.py start 5.6 + + +# cd /www/server/mdserver-web +# cd /www/server/mdserver-web/plugins/php-apt/versions && /bin/bash common.sh 5.6 install yaf +# cd /www/server/mdserver-web/plugins/php-apt/versions && /bin/bash common.sh 7.1 install swoole + + +PHP_VER_LIST=(56 70 71 72 73 74 80 81 82 83 84) +for PHP_VER in ${PHP_VER_LIST[@]}; do + echo "php${PHP_VER} -- start" + if [ ! -d /www/server/php-apt/${PHP_VER} ];then + cd /www/server/mdserver-web/plugins/php-apt && bash install.sh install ${PHP_VER} + fi + echo "php${PHP_VER} -- end" +done + + +cd $curPath + +PHP_VER_LIST_EXT=(56 70 71 72 73 74 80 81 82 83 84) +PHP_EXT_LIST=(ioncube pdo mysqlnd sqlite3 odbc opcache mcrypt fileinfo \ + exif gd intl memcache memcached redis imagick xdebug xhprof \ + swoole yaf phalcon yar mongodb yac solr seaslog mbstring zip zstd) +for PHP_VER in ${PHP_VER_LIST_EXT[@]}; do + echo "php${PHP_VER} EXT -- start" + version=${PHP_VER:0:1}.${PHP_VER:1:2} + extVer=`bash $curPath/lib.sh $version` + + for EXT in ${PHP_EXT_LIST[@]}; do + extFile=/usr/lib/php/${extVer}/${EXT}.so + echo "${PHP_VER} ${EXT} start" + if [ ! -f $extFile ];then + /bin/bash common.sh $version install ${EXT} + fi + echo "${PHP_VER} ${EXT} end" + done + + echo "php${PHP_VER} EXT -- end" +done + + diff --git a/plugins/php-apt/versions/common.sh b/plugins/php-apt/versions/common.sh new file mode 100644 index 000000000..42e859fb4 --- /dev/null +++ b/plugins/php-apt/versions/common.sh @@ -0,0 +1,60 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +version=$1 +action=$2 +extName=$3 + +which bc +if [ "$?" != "0" ];then + apt install -y bc +fi + + +# echo $1,$2,$3 + +# echo $curPath +# echo $rootPath +# echo $serverPath + +FILE=${curPath}/${version}/${extName}.sh +FILE_COMMON=${curPath}/common/${extName}.sh + +# apt install -y php81-php-yar +if [ "$action" == 'install' ];then + + if [ -f $FILE ];then + bash ${curPath}/${version}/${extName}.sh install $version + elif [ -f $FILE_COMMON ];then + bash ${FILE_COMMON} install ${version} + else + apt install -y php${version}-${extName} + fi +fi + +# apt remove -y php81-php-yar +if [ "$action" == 'uninstall' ];then + if [ -f $FILE ];then + bash ${curPath}/${version}/${extName}.sh uninstall $version + elif [ -f $FILE_COMMON ];then + bash ${FILE_COMMON} uninstall ${version} + else + apt remove -y php${version}-${extName} + fi +fi + +echo "apt install -y php${version}-${extName}" +echo "apt remove -y php${version}-${extName}" + +php_status=`systemctl status php${version}-fpm | grep inactive` +echo "status:$php_status" +if [ "$php_status" == "" ];then + systemctl restart php${version}-fpm +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/common/brotli.sh b/plugins/php-apt/versions/common/brotli.sh new file mode 100755 index 000000000..89769c8e4 --- /dev/null +++ b/plugins/php-apt/versions/common/brotli.sh @@ -0,0 +1,94 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=brotli +LIBV=0.18.3 + +extVer=`bash $curPath/lib.sh $version` +extFile=/usr/lib/php/${extVer}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + #cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' + isInstall=`cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' |grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + /usr/bin/phpize${version} + ./configure --with-php-config=/usr/bin/php-config${version} + make && make install && make clean + + fi + echo "$extFile checking ..." + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + + echo "" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "[${LIBNAME}]" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "extension=${LIBNAME}.so" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "/usr/bin/php-config${version}" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + + if [ -f /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini ];then + rm -rf /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + rm -rf $extFile + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/common/ioncube.sh b/plugins/php-apt/versions/common/ioncube.sh new file mode 100755 index 000000000..9beeb3cbf --- /dev/null +++ b/plugins/php-apt/versions/common/ioncube.sh @@ -0,0 +1,99 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=ioncube +LIBV=0 + +if [ `echo "$version > 8.3"|bc` -eq 1 ];then + echo "I won't support it" + exit 0 +fi + + +extVer=`bash $curPath/lib.sh $version` +extFile=/usr/lib/php/${extVer}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +SORT_LIBNAME="10-${LIBNAME}" + +Install_lib() +{ + + isInstall=`cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' |grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -f $php_lib/ioncube_loaders_lin.tar.gz ];then + wget -O $php_lib/ioncube_loaders_lin.tar.gz https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_x86-64.tar.gz + cd $php_lib && tar -zxvf ioncube_loaders_lin.tar.gz + fi + cd $php_lib/ioncube + + cp -rf $php_lib/ioncube/ioncube_loader_lin_${version}.so $extFile + + fi + + echo "$extFile checking ..." + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + echo "" >> /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini + echo "[${LIBNAME}]" >> /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini + echo "zend_extension=${LIBNAME}.so" >> /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "/usr/bin/php-config${version}" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + + if [ -f /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini ];then + rm -rf /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini + rm -rf $extFile + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/common/opcache.sh b/plugins/php-apt/versions/common/opcache.sh new file mode 100755 index 000000000..b73b83b57 --- /dev/null +++ b/plugins/php-apt/versions/common/opcache.sh @@ -0,0 +1,55 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=opcache + +ext_dir=/etc/php/${version}/fpm/conf.d +ext_file=${ext_dir}/10-opcache.ini + +echo $ext_file + +OP_BL=${serverPath}/server/php-apt/opcache-blacklist.txt +if [ ! -f $OP_BL ];then + touch $OP_BL +fi + +if [ "$actionType" == 'install' ];then + apt install -y php${version}-${LIBNAME} + + echo "ls ${ext_dir} | grep "${LIBNAME}.ini"| cut -d \ -f 1" + find_opcache=`ls ${ext_dir} | grep "${LIBNAME}.ini"| cut -d \ -f 1` + echo $find_opcache + if [ "$find_opcache" != "" ];then + ext_file=${ext_dir}/${find_opcache} + fi + echo $ext_file + echo "zend_extension=${LIBNAME}.so" >> $ext_file + echo "opcache.enable=1" >> $ext_file + echo "opcache.memory_consumption=128" >> $ext_file + echo "opcache.interned_strings_buffer=8" >> $ext_file + echo "opcache.max_accelerated_files=4000" >> $ext_file + echo "opcache.revalidate_freq=60" >> $ext_file + echo "opcache.fast_shutdown=1" >> $ext_file + echo "opcache.enable_cli=1" >> $ext_file + echo "opcache.jit=1205" >> $ext_file + echo "opcache.jit_buffer_size=64M" >> $ext_file + echo "opcache.save_comments=0" >> $ext_file + echo "opcache.blacklist_filename=${OP_BL}" >> $ext_file + +elif [ "$actionType" == 'uninstall' ];then + rm -rf $ext_file + echo 'cannot uninstall' +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/common/phalcon.sh b/plugins/php-apt/versions/common/phalcon.sh new file mode 100755 index 000000000..758ee0961 --- /dev/null +++ b/plugins/php-apt/versions/common/phalcon.sh @@ -0,0 +1,58 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=phalcon +LIBV=0 + + +# if [ `echo "$version > 7.4"|bc` -eq 1 ];then +# echo "I won't support it" +# exit 0 +# fi + +CMD='apt ' +if [ "$actionType" == 'install' ];then + CMD="$CMD install -y php${version}-" +elif [ "$actionType" == 'uninstall' ];then + CMD="$CMD uninstall -y php${version}-" +fi + +if [ "$version" == '5.6' ];then + CMD="${CMD}phalcon3" +elif [[ "$version" == '7.0' ]]; then + CMD="${CMD}phalcon3" +elif [[ "$version" == '7.1' ]]; then + CMD="${CMD}phalcon3" +elif [[ "$version" == '7.2' ]]; then + CMD="${CMD}phalcon4" +elif [[ "$version" == '7.3' ]]; then + CMD="${CMD}phalcon3" +elif [[ "$version" == '7.4' ]]; then + CMD="${CMD}phalcon4" +elif [[ "$version" == '8.0' ]]; then + CMD="${CMD}phalcon5" +elif [[ "$version" == '8.1' ]]; then + CMD="${CMD}phalcon5" +elif [[ "$version" == '8.2' ]]; then + CMD="${CMD}phalcon5" +elif [[ "$version" == '8.3' ]]; then + CMD="${CMD}phalcon5" +elif [[ "$version" == '8.4' ]]; then + CMD="${CMD}phalcon5" +fi + +$CMD +echo "$CMD" diff --git a/plugins/php-apt/versions/common/seaslog.sh b/plugins/php-apt/versions/common/seaslog.sh new file mode 100755 index 000000000..bf99d75b0 --- /dev/null +++ b/plugins/php-apt/versions/common/seaslog.sh @@ -0,0 +1,97 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +LIBNAME=SeasLog +_LIBNAME=$(echo $LIBNAME | tr '[A-Z]' '[a-z]') + +sysName=`uname` +LIBV=2.2.0 + +extVer=`bash $curPath/lib.sh $version` +extFile=/usr/lib/php/${extVer}/${_LIBNAME}.so + + + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + #cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' + isInstall=`cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' |grep "${_LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${_LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + /usr/bin/phpize${version} + ./configure --with-php-config=/usr/bin/php-config${version} + make && make install && make clean + + fi + echo "$extFile checking ..." + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + _LIBNAME=$(echo $LIBNAME | tr '[A-Z]' '[a-z]') + echo "" >> /etc/php/${version}/fpm/conf.d/${_LIBNAME}.ini + echo "[${_LIBNAME}]" >> /etc/php/${version}/fpm/conf.d/${_LIBNAME}.ini + echo "extension=${_LIBNAME}.so" >> /etc/php/${version}/fpm/conf.d/${_LIBNAME}.ini + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "/usr/bin/php-config${version}" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ -f /etc/php/${version}/fpm/conf.d/${_LIBNAME}.ini ];then + rm -rf /etc/php/${version}/fpm/conf.d/${_LIBNAME}.ini + rm -rf $extFile + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/common/sg11.sh b/plugins/php-apt/versions/common/sg11.sh new file mode 100644 index 000000000..e10e2d3bf --- /dev/null +++ b/plugins/php-apt/versions/common/sg11.sh @@ -0,0 +1,123 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +# https://www.sourceguardian.com/loaders.html + +# support 52-81 + +LIBNAME=sg11 +LIBV=0 +sysName=`uname` +actionType=$1 +version=$2 +# SG_VER=${version:0:1}.${version:1:2} +SG_VER=${version} + +extVer=`bash $curPath/lib.sh $version` +extFile=/usr/lib/php/${extVer}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +SORT_LIBNAME="10-${LIBNAME}" + +Install_lib() +{ + bash ${rootPath}/scripts/getos.sh + OSNAME=`cat ${rootPath}/data/osname.pl` + + echo "${OSNAME}" + + DEFAULT_OSNAME=linux-x86_64 + SUFFIX_NAME=lin + if [ "$OSNAME" == 'macos' ];then + DEFAULT_OSNAME=macosx + SUFFIX_NAME=dar + fi + + isInstall=`cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' |grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + mkdir -p $php_lib/sg11 + if [ ! -f $php_lib/sg11_loaders.tar.bz2 ];then + curl -sSLo $php_lib/sg11_loaders.tar.bz2 https://www.sourceguardian.com/loaders/download/loaders.tar.bz2 + echo "cd $php_lib && tar -jxvf $php_lib/sg11_loaders.tar.bz2 -C $php_lib/sg11" + cd $php_lib && tar -jxvf $php_lib/sg11_loaders.tar.bz2 -C $php_lib/sg11 + fi + + + if [ ! -d $php_lib/sg11/macosx ];then + cd $php_lib && tar -jxvf $php_lib/sg11_loaders.tar.bz2 -C $php_lib/sg11 + fi + cd $php_lib/sg11 + # echo "mv $php_lib/sg11/${DEFAULT_OSNAME}/ixed.${SG_VER}.lin $extFile" + if [ -f $php_lib/sg11/${DEFAULT_OSNAME}/ixed.${SG_VER}.${SUFFIX_NAME} ];then + cp -rf $php_lib/sg11/${DEFAULT_OSNAME}/ixed.${SG_VER}.${SUFFIX_NAME} $extFile + else + echo 'Not supported temporarily' + exit + fi + + if [ "$OSNAME" == 'macos' ];then + xattr -c * $extFile + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini + echo "[${LIBNAME}]" >> /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini + echo "extension=${LIBNAME}.so" >> /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "/usr/bin/php-config${version}" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + + if [ -f /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini ];then + rm -rf /etc/php/${version}/fpm/conf.d/${SORT_LIBNAME}.ini + rm -rf $extFile + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/common/swoole.sh b/plugins/php-apt/versions/common/swoole.sh new file mode 100755 index 000000000..adfca0639 --- /dev/null +++ b/plugins/php-apt/versions/common/swoole.sh @@ -0,0 +1,124 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=swoole +LIBV=6.1.6 + + +APT_INSTALL=0 + +if [ `echo "$version < 7.0"|bc` -eq 1 ];then + LIBV=1.10.1 +elif [ "$version" == "7.1" ];then + LIBV=4.5.2 +elif [ "$version" == "7.0" ];then + LIBV=4.3.0 +elif [ "$version" == "8.5" ];then + LIBV=6.1.6 +else + echo 'ok' + APT_INSTALL=1 +fi + + +# to Apt +if [ "$APT_INSTALL" == "1" ];then + if [ "$actionType" == 'install' ];then + apt install -y php${version}-${LIBNAME} + elif [ "$actionType" == 'uninstall' ];then + apt remove -y php${version}-${LIBNAME} + fi + exit 0 +fi + + +extVer=`bash $curPath/lib.sh $version` +extFile=/usr/lib/php/${extVer}/${LIBNAME}.so + + + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + #cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' + isInstall=`cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' |grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + /usr/bin/phpize${version} + ./configure --with-php-config=/usr/bin/php-config${version} + make && make install && make clean + + fi + echo "$extFile checking ..." + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + + echo "" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "[${LIBNAME}]" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "extension=${LIBNAME}.so" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "/usr/bin/php-config${version}" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + + if [ -f /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini ];then + rm -rf /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + rm -rf $extFile + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/common/yaf.sh b/plugins/php-apt/versions/common/yaf.sh new file mode 100755 index 000000000..ccbca3c91 --- /dev/null +++ b/plugins/php-apt/versions/common/yaf.sh @@ -0,0 +1,101 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=yaf +LIBV=3.3.6 + +if [ `echo "$version < 7.0"|bc` -eq 1 ];then + LIBV=2.3.5 +fi + +extVer=`bash $curPath/lib.sh $version` +extFile=/usr/lib/php/${extVer}/${LIBNAME}.so + + + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + #cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' + isInstall=`cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' |grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + /usr/bin/phpize${version} + ./configure --with-php-config=/usr/bin/php-config${version} + make && make install && make clean + + fi + echo "$extFile checking ..." + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + + echo "" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "[${LIBNAME}]" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "extension=${LIBNAME}.so" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "${LIBNAME}.use_namespace=1" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "/usr/bin/php-config${version}" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + + if [ -f /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini ];then + rm -rf /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + rm -rf $extFile + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/common/yar.sh b/plugins/php-apt/versions/common/yar.sh new file mode 100755 index 000000000..4e1aaa64a --- /dev/null +++ b/plugins/php-apt/versions/common/yar.sh @@ -0,0 +1,105 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=yar +LIBV=2.3.3 + +if [[ "$version" =~ "5.0" ]];then + LIBV=1.2.5 +fi + +if [[ "$version" =~ "7.0" ]];then + LIBV=2.3.3 +fi + +extVer=`bash $curPath/lib.sh $version` +extFile=/usr/lib/php/${extVer}/${LIBNAME}.so + + + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + #cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' + isInstall=`cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' |grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + /usr/bin/phpize${version} + ./configure --with-php-config=/usr/bin/php-config${version} + make && make install && make clean + + fi + echo "$extFile checking ..." + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + + echo "" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "[${LIBNAME}]" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "extension=${LIBNAME}.so" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + echo "${LIBNAME}.expose_info=false" >> /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "/usr/bin/php-config${version}" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + + if [ -f /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini ];then + rm -rf /etc/php/${version}/fpm/conf.d/${LIBNAME}.ini + rm -rf $extFile + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/lib.sh b/plugins/php-apt/versions/lib.sh new file mode 100644 index 000000000..27114a858 --- /dev/null +++ b/plugins/php-apt/versions/lib.sh @@ -0,0 +1,37 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +version=$1 + +if [ "$version" == '5.6' ];then + echo '20131226' +elif [[ "$version" == '7.0' ]]; then + echo '20151012' +elif [[ "$version" == '7.1' ]]; then + echo '20160303' +elif [[ "$version" == '7.2' ]]; then + echo '20170718' +elif [[ "$version" == '7.3' ]]; then + echo '20180731' +elif [[ "$version" == '7.4' ]]; then + echo '20190902' +elif [[ "$version" == '8.0' ]]; then + echo '20200930' +elif [[ "$version" == '8.1' ]]; then + echo '20210902' +elif [[ "$version" == '8.2' ]]; then + echo '20220829' +elif [[ "$version" == '8.3' ]]; then + echo '20230831' +elif [[ "$version" == '8.4' ]]; then + echo '20240924' +elif [[ "$version" == '8.5' ]]; then + echo '20250925' +fi \ No newline at end of file diff --git a/plugins/php-apt/versions/phplib.conf b/plugins/php-apt/versions/phplib.conf new file mode 100755 index 000000000..eec09e6cf --- /dev/null +++ b/plugins/php-apt/versions/phplib.conf @@ -0,0 +1,802 @@ +[ + { + "name": "sg11", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83" + ], + "type": "脚本解密", + "msg": "用于解密SG11加密脚本!", + "shell": "sg11.sh", + "check": "sg11.so" + }, + { + "name": "ionCube", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "81", + "82", + "83" + ], + "type": "脚本解密", + "msg": "用于解密ionCube Encoder加密脚本!", + "shell": "ioncube.sh", + "check": "ioncube" + }, + { + "name": "pdo", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "数据库访问抽象模块!", + "shell": "pdo.sh", + "check": "pdo" + }, + { + "name": "mysqlnd", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "用于使用MySQL数据库的模块!", + "shell": "mysqlnd.sh", + "check": "mysqlnd" + }, + { + "name": "mysql", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "用于使用MySQL数据库的模块!", + "shell": "mysql.sh", + "check": "mysql" + }, + { + "name": "sqlite3", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "用于使用sqlite3数据库的模块!", + "shell": "sqlite3.sh", + "check": "sqlite3" + }, + { + "name": "oci8", + "versions": [], + "type": "数据库", + "msg": "用于使用OCI8数据库的模块!", + "shell": "oci8.sh", + "check": "oci8" + }, + { + "name": "odbc", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "用于使用ODBC数据库的模块!", + "shell": "odbc.sh", + "check": "odbc" + }, + { + "name": "ZendGuardLoader", + "versions": [ + ], + "type": "脚本解密", + "msg": "用于解密ZendGuard加密脚本!", + "shell": "zend_guard_loader.sh", + "check": "ZendGuardLoader" + }, + { + "name": "ZendOptimizer", + "versions": [ + "52" + ], + "type": "脚本解密", + "msg": "用于解密ZendOptimizer加密脚本!", + "shell": "zend_optimizer.sh", + "check": "ZendOptimizer" + }, + { + "name": "opcache", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "用于加速PHP脚本!", + "shell": "opcache.sh", + "check": "opcache" + }, + { + "name": "mcrypt", + "versions": [ + "71", + "72", + "73", + "74", + "80", + "81" + ], + "type": "通用扩展", + "msg": "加密软件!", + "shell": "mcrypt.sh", + "check": "mcrypt" + }, + { + "name": "ldap", + "versions": [ + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "轻型目录访问协议", + "shell": "ldap.sh", + "check": "ldap" + }, + { + "name": "gmp", + "versions": [ + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "数学扩展", + "shell": "gmp.sh", + "check": "gmp" + }, + { + "name": "brotli", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "压缩", + "msg": "压缩算法", + "shell": "brotli.sh", + "check": "brotli.so" + }, + { + "name": "bcmath", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "高精度计算!", + "shell": "bcmath.sh", + "check": "bcmath.so" + }, + { + "name": "fileinfo", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于FILE!", + "shell": "fileinfo.sh", + "check": "fileinfo" + }, + { + "name": "exif", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于图像文件格式!", + "shell": "exif.sh", + "check": "exif" + }, + { + "name": "igbinary", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "序列化扩展!", + "shell": "igbinary.sh", + "check": "igbinary.so" + }, + { + "name": "xml", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于xml解析!", + "shell": "xml.sh", + "check": "xml" + }, + { + "name": "curl", + "versions": [ + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "通用CURL库!", + "shell": "curl.sh", + "check": "curl" + }, + { + "name": "gd", + "versions": [ + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "通用GD库!", + "shell": "gd.sh", + "check": "gd" + }, + { + "name": "intl", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "提供国际化支持", + "shell": "intl.sh", + "check": "intl" + }, + { + "name": "memcache", + "versions": [ + "74", + "80", + "81" + ], + "type": "缓存器", + "msg": "强大的内容缓存器,不支持集群", + "shell": "memcache.sh", + "check": "memcache" + }, + { + "name": "memcached", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "强大的内容缓存器,支持集群", + "shell": "memcached.sh", + "check": "memcached" + }, + { + "name": "redis", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "更强大的内容缓存器,支持集群", + "shell": "redis.sh", + "check": "redis" + }, + { + "name": "apcu", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "脚本缓存器", + "shell": "apcu.sh", + "check": "apcu" + }, + { + "name": "imagick", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "比GD更强大的图形库", + "shell": "imagick.sh", + "check": "imagick" + }, + { + "name": "xdebug", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "调试器", + "msg": "不多说,不了解的不要安装", + "shell": "xdebug.sh", + "check": "xdebug" + }, + { + "name": "xhprof", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "性能分析", + "msg": "不多说,不了解的不要安装!", + "shell": "xhprof.sh", + "check": "xhprof" + }, + { + "name": "Swoole", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "异步、并行、高性能网络通信引擎", + "shell": "swoole.sh", + "check": "swoole" + }, + { + "name": "eAccelerator", + "versions": [ + "52", + "53" + ], + "type": "缓存器", + "msg": "内容缓存器", + "shell": "eaccelerator.sh", + "check": "eaccelerator" + }, + { + "name": "yaf", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83" + ], + "type": "框架", + "msg": "Yaf是一个C语言编写的PHP框架", + "shell": "yaf.sh", + "check": "yaf" + }, + { + "name": "phalcon", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "框架", + "msg": "PHP框架", + "shell": "phalcon.sh", + "check": "phalcon" + }, + { + "name": "yar", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74" + ], + "type": "框架", + "msg": "Yar是一个RPC框架", + "shell": "yar.sh", + "check": "yar" + }, + { + "name": "mongo", + "versions": [ + "56" + ], + "type": "通用扩展", + "msg": "Mongodb数据库连接驱动", + "shell": "mongo.sh", + "check": "mongo" + }, + { + "name": "mongodb", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "Mongodb数据库连接驱动", + "shell": "mongodb.sh", + "check": "mongodb" + }, + { + "name": "yac", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81" + ], + "type": "缓存器", + "msg": "高性能无锁共享内存Cache", + "shell": "yac.sh", + "check": "yac" + }, + { + "name": "solr", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81" + ], + "type": "大数据", + "msg": "SOLR全文搜索服务", + "shell": "solr.sh", + "check": "solr" + }, + { + "name": "seaslog", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "日志", + "msg": "SeasLog高性能日志记录", + "shell": "seaslog.sh", + "check": "seaslog" + }, + { + "name": "mbstring", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于需要多字节字符串处理的模块", + "shell": "mbstring.sh", + "check": "mbstring" + }, + { + "name": "zip", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "压缩", + "msg": "压缩组件", + "shell": "zip.sh", + "check": "zip" + }, + { + "name": "zstd", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "压缩", + "msg": "压缩组件", + "shell": "zstd.sh", + "check": "zstd" + } +] \ No newline at end of file diff --git a/plugins/php-guard/ico.png b/plugins/php-guard/ico.png new file mode 100755 index 000000000..93c14c00a Binary files /dev/null and b/plugins/php-guard/ico.png differ diff --git a/plugins/php-guard/index.html b/plugins/php-guard/index.html new file mode 100755 index 000000000..70715852c --- /dev/null +++ b/plugins/php-guard/index.html @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/plugins/php-guard/index.py b/plugins/php-guard/index.py new file mode 100755 index 000000000..6846179e9 --- /dev/null +++ b/plugins/php-guard/index.py @@ -0,0 +1,70 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import shutil + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'php-guard' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(version): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + version + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print('start') + else: + print("fail") diff --git a/plugins/php-guard/info.json b/plugins/php-guard/info.json new file mode 100755 index 000000000..6493c64f7 --- /dev/null +++ b/plugins/php-guard/info.json @@ -0,0 +1,18 @@ +{ + "sort": 5, + "ps": "监控PHP-FPM运行状态,防止大批量出现502错误!", + "shell": "install.sh", + "name": "php-guard", + "title": "PHP守护", + "versions": "1.0", + "updates": "1.0", + "tip": "soft", + "checks": "server/php-guard", + "path": "server/php-guard", + "display": 1, + "author": "midoks", + "date": "2019-03-01", + "home": "https://github.com/midoks", + "type": "语言解释器", + "pid": "1" +} \ No newline at end of file diff --git a/plugins/php-guard/install.sh b/plugins/php-guard/install.sh new file mode 100755 index 000000000..b1edae490 --- /dev/null +++ b/plugins/php-guard/install.sh @@ -0,0 +1,34 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +Install_pg() +{ + echo '安装PHP守护中...' + echo 'True' > ${rootPath}/data/502Task.pl + + mkdir -p $serverPath/php-guard + echo '1.0' > $serverPath/php-guard/version.pl + echo '安装PHP守护成功!!' +} + +Uninstall_pg() +{ + rm -rf ${rootPath}/data/502Task.pl + rm -rf $serverPath/php-guard + echo '卸载PHP守护成功!!' +} + + +action=$1 +host=$2 +if [ "${1}" == 'install' ];then + Install_pg +else + Uninstall_pg +fi diff --git a/plugins/php-yum/all_test.sh b/plugins/php-yum/all_test.sh new file mode 100644 index 000000000..eabbe6db5 --- /dev/null +++ b/plugins/php-yum/all_test.sh @@ -0,0 +1,41 @@ +#! /bin/sh +export PATH=$PATH:/opt/local/bin:/opt/local/sbin:/opt/local/share/man:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin +DIR=$(cd "$(dirname "$0")"; pwd) + + +PHP_VER=71 +echo "php${PHP_VER} -- start" +cmd_ext=$(ls -l $DIR/versions/$PHP_VER/ |awk '{print $9}') +cd $DIR && /bin/bash install.sh install $PHP_VER +for ii in $cmd_ext +do + if [ "install.sh" == "$ii" ];then + echo '' > /tmp/t.log + else + cd $DIR/versions/$PHP_VER && /bin/bash $ii install $PHP_VER + fi +done +echo "php${PHP_VER} -- end" + + +PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82 83) +# PHP_VER_LIST=(81) +for PHP_VER in ${PHP_VER_LIST[@]}; do + echo "php${PHP_VER} -- start" + if [ -d /www/server/php/${PHP_VER} ];then + cmd_ext=$(ls -l $DIR/versions/$PHP_VER/ |awk '{print $9}') + for ii in $cmd_ext + do + echo "${ii}" + if [ "install.sh" == "$ii" ];then + echo '' > /tmp/t.log + else + cd $DIR/versions/$PHP_VER/ && bash $ii install ${PHP_VER} + fi + done + fi + echo "php${PHP_VER} -- end" +done + + +rm -rf /tmp/t.log diff --git a/plugins/php-yum/conf/app_start.php b/plugins/php-yum/conf/app_start.php new file mode 100644 index 000000000..5d1b7ce8c --- /dev/null +++ b/plugins/php-yum/conf/app_start.php @@ -0,0 +1,55 @@ +save_run($xhprof_data, 'xhprof_foo'); + + $profiler_url = sprintf('http://{$LOCAL_IP}:5858/index.php?run=%s&source=xhprof_foo', $run_id); + // echo ""; + + $style_css = 'position:fixed;bottom: 0px;right: 0px;z-index: 100000000;color: red;background-color: black;padding: 0px;margin: 0px;'; + echo 'XHProf分析结果'; + +} + +if (extension_loaded('xhprof') + && isset($_GET[XHProf_Name]) && $_GET[XHProf_Name] == 'ok' && + (! in_array($_SERVER['SCRIPT_NAME'], ['/xhprof_html/callgraph.php', + '/xhprof_html/index.php']))) { + app_xhprof_start(); + include_once $_SERVER['SCRIPT_FILENAME']; + app_xhprof_end(); + exit; +} + +?> diff --git a/plugins/php-yum/conf/enable-php.conf b/plugins/php-yum/conf/enable-php.conf new file mode 100644 index 000000000..9f8461da7 --- /dev/null +++ b/plugins/php-yum/conf/enable-php.conf @@ -0,0 +1,9 @@ +set $PHP_ENV 1; +location ~ [^/]\.php(/|$) +{ + try_files $uri =404; + fastcgi_pass unix:/var/opt/remi/php{$PHP_VERSION}/run/php-fpm/www.sock; + fastcgi_index index.php; + include fastcgi.conf; + include {$SERVER_PATH}/web_conf/php/pathinfo.conf; +} \ No newline at end of file diff --git a/plugins/php-yum/conf/pathinfo.conf b/plugins/php-yum/conf/pathinfo.conf new file mode 100644 index 000000000..47b573684 --- /dev/null +++ b/plugins/php-yum/conf/pathinfo.conf @@ -0,0 +1,8 @@ +set $real_script_name $fastcgi_script_name; +if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") { + set $real_script_name $1; + set $path_info $2; + } +fastcgi_param SCRIPT_FILENAME $document_root$real_script_name; +fastcgi_param SCRIPT_NAME $real_script_name; +fastcgi_param PATH_INFO $path_info; \ No newline at end of file diff --git a/plugins/php-yum/conf/php-fpm.conf b/plugins/php-yum/conf/php-fpm.conf new file mode 100644 index 000000000..6152806c0 --- /dev/null +++ b/plugins/php-yum/conf/php-fpm.conf @@ -0,0 +1,4 @@ +[global] +pid = /var/opt/remi/php{$PHP_VERSION}/run/php-fpm/php-fpm.pid +include=/etc/opt/remi/php{$PHP_VERSION}/php-fpm.d/*.conf +php_value[auto_prepend_file]={$SERVER_PATH}/php-yum/app_start.php \ No newline at end of file diff --git a/plugins/php-yum/conf/phpinfo.conf b/plugins/php-yum/conf/phpinfo.conf new file mode 100644 index 000000000..b3c51e657 --- /dev/null +++ b/plugins/php-yum/conf/phpinfo.conf @@ -0,0 +1,4 @@ +location /{$PHP_VERSION} { + root {$ROOT_PATH}/phpinfo; + include {$SERVER_PATH}/web_conf/php/conf/enable-php-yum{$PHP_VERSION}.conf; +} \ No newline at end of file diff --git a/plugins/php-yum/conf/www.conf b/plugins/php-yum/conf/www.conf new file mode 100644 index 000000000..a8149e3db --- /dev/null +++ b/plugins/php-yum/conf/www.conf @@ -0,0 +1,22 @@ +[www] +user = {$PHP_USER} +group = {$PHP_GROUP} + +listen = /var/opt/remi/php{$PHP_VERSION}/run/php-fpm/www.sock +listen.owner = {$PHP_USER} +listen.group = {$PHP_GROUP} +listen.backlog = 4096 + +pm = dynamic +pm.max_children = 50 +pm.start_servers = 5 +pm.min_spare_servers = 5 +pm.max_spare_servers = 35 +pm.status_path = /phpfpm_status_yum{$PHP_VERSION} +pm.max_requests = 1000 +request_terminate_timeout = 30 +request_slowlog_timeout = 10 +slowlog = /var/opt/remi/php{$PHP_VERSION}/log/php-fpm/www-slow.log + +php_admin_flag[log_errors] = on +php_admin_value[error_log] = /var/opt/remi/php{$PHP_VERSION}/log/php-fpm/error.log \ No newline at end of file diff --git a/plugins/php-yum/ico.png b/plugins/php-yum/ico.png new file mode 100755 index 000000000..59def5702 Binary files /dev/null and b/plugins/php-yum/ico.png differ diff --git a/plugins/php-yum/index.html b/plugins/php-yum/index.html new file mode 100755 index 000000000..d94adc433 --- /dev/null +++ b/plugins/php-yum/index.html @@ -0,0 +1,66 @@ + + +
                                +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                安装扩展

                                +

                                配置修改

                                +

                                常用功能

                                +

                                配置文件

                                +

                                FPM配置

                                +

                                禁用函数

                                +

                                性能调整

                                +

                                负载状况

                                +

                                会话管理

                                +

                                FPM日志

                                +

                                慢日志

                                +
                                +
                                +
                                +
                                +
                                + +
                                + \ No newline at end of file diff --git a/plugins/php-yum/index.py b/plugins/php-yum/index.py new file mode 100755 index 000000000..6f92b5572 --- /dev/null +++ b/plugins/php-yum/index.py @@ -0,0 +1,931 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import shutil + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'php-yum' + + +def getAppDir(): + return mw.getServerDir()+'/'+getPluginName() + +def getServerDir(): + return '/etc/opt/remi' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(version): + path = getServerDir() + '/php' + version + '/php.ini' + return path + + +def status(version): + # ps -ef|grep 'php/81' |grep -v grep | grep -v python | awk '{print $2} + cmd = "ps -ef|grep 'remi/php" + version + \ + "' |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def contentReplace(content, version): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PHP_VERSION}', version) + content = content.replace('{$LOCAL_IP}', mw.getLocalIp()) + + if mw.isAppleSystem(): + # user = mw.execShell( + # "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + content = content.replace('{$PHP_USER}', 'nobody') + content = content.replace('{$PHP_GROUP}', 'nobody') + + rep = r'listen.owner\s*=\s*(.+)\r?\n' + val = ';listen.owner = nobody\n' + content = re.sub(rep, val, content) + + rep = r'listen.group\s*=\s*(.+)\r?\n' + val = ';listen.group = nobody\n' + content = re.sub(rep, val, content) + + rep = r'user\s*=\s*(.+)\r?\n' + val = ';user = nobody\n' + content = re.sub(rep, val, content) + + rep = r'[^\.]group\s*=\s*(.+)\r?\n' + val = ';group = nobody\n' + content = re.sub(rep, val, content) + + else: + content = content.replace('{$PHP_USER}', 'www') + content = content.replace('{$PHP_GROUP}', 'www') + return content + + +def makeOpenrestyConf(version): + + sdir = mw.getServerDir() + + dst_dir = sdir + '/web_conf/php' + dst_dir_conf = sdir + '/web_conf/php/conf' + + if not os.path.exists(dst_dir): + mw.execShell('mkdir -p ' + dst_dir) + + if not os.path.exists(dst_dir_conf): + mw.execShell('mkdir -p ' + dst_dir_conf) + + d_pathinfo = sdir + '/web_conf/php/pathinfo.conf' + if not os.path.exists(d_pathinfo): + s_pathinfo = getPluginDir() + '/conf/pathinfo.conf' + shutil.copyfile(s_pathinfo, d_pathinfo) + + info = getPluginDir() + '/info.json' + content = mw.readFile(info) + content = json.loads(content) + versions = content['versions'] + tpl = getPluginDir() + '/conf/enable-php.conf' + tpl_content = mw.readFile(tpl) + dfile = sdir + '/web_conf/php/conf/enable-php-yum' + version + '.conf' + if not os.path.exists(dfile): + w_content = contentReplace(tpl_content, version) + mw.writeFile(dfile, w_content) + + +def phpFpmWwwReplace(version): + service_php_fpm_dir = getServerDir() + '/php' + version + '/php-fpm.d/' + if not os.path.exists(service_php_fpm_dir): + os.mkdir(service_php_fpm_dir) + + service_php_fpmwww = service_php_fpm_dir + '/www.conf' + if os.path.exists(service_php_fpmwww): + # 原来文件备份 + mw.execShell('mv ' + service_php_fpmwww + + ' ' + service_php_fpmwww + '.bak') + + service_php_fpm_mw = service_php_fpm_dir + '/mw.conf' + if not os.path.exists(service_php_fpm_mw): + tpl_php_fpmwww = getPluginDir() + '/conf/www.conf' + content = mw.readFile(tpl_php_fpmwww) + content = contentReplace(content, version) + mw.writeFile(service_php_fpm_mw, content) + +def phpPrependFile(version): + app_start = getAppDir() + '/app_start.php' + if not os.path.exists(app_start): + tpl = getPluginDir() + '/conf/app_start.php' + content = mw.readFile(tpl) + content = contentReplace(content, version) + mw.writeFile(app_start, content) + +def getFpmConfFile(version): + return getServerDir() + '/php' + version + '/php-fpm.d/mw.conf' + + +def getFpmFile(version): + return getServerDir() + '/php' + version + '/php-fpm.conf' + + +def getDstEnablePHP(version): + sdir = mw.getServerDir() + dfile = sdir + '/web_conf/php/conf/enable-php-yum' + version + '.conf' + return dfile + +def deleteConfList(version): + enable_conf = getDstEnablePHP(version) + if os.path.exists(enable_conf): + os.remove(enable_conf) + + +def phpFpmReplace(version): + desc_php_fpm = getFpmFile(version) + tpl_php_fpm = getPluginDir() + '/conf/php-fpm.conf' + content = mw.readFile(tpl_php_fpm) + content = contentReplace(content, version) + mw.writeFile(desc_php_fpm, content) + return True + + +def initReplace(version): + makeOpenrestyConf(version) + phpFpmWwwReplace(version) + + install_ok = getAppDir() + "/" + version + "/install.ok" + if not os.path.exists(install_ok): + phpFpmReplace(version) + + phpini = getConf(version) + ssl_crt = mw.getSslCrt() + + cmd_openssl = "sed -i \"s#;openssl.cafile=#openssl.cafile=" + ssl_crt + "#\" " + phpini + mw.execShell(cmd_openssl) + cmd_curl = "sed -i \"s#;curl.cainfo =#curl.cainfo=" + ssl_crt + "#\" " + phpini + mw.execShell(cmd_curl) + mw.writeFile(install_ok, 'ok') + + phpPrependFile(version) + # systemd + # mw.execShell('systemctl daemon-reload') + return 'ok' + + +def phpOp(version, method): + if method == 'start': + initReplace(version) + + if mw.isAppleSystem(): + return 'fail' + data = mw.execShell('systemctl ' + method + ' ' + + 'php' + version + '-php-fpm') + if data[1] == '': + return 'ok' + return data[1] + + +def start(version): + return phpOp(version, 'start') + + +def stop(version): + status = phpOp(version, 'stop') + deleteConfList(version) + return status + + +def restart(version): + return phpOp(version, 'restart') + + +def reload(version): + return phpOp(version, 'reload') + + +def initdStatus(version): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status php' + version + '-php-fpm | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(version): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable php' + version + '-php-fpm') + return 'ok' + + +def initdUinstall(version): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable php' + version + '-php-fpm') + return 'ok' + + +def fpmLog(version): + f = '/var/opt/remi/php' + version + '/log/php-fpm.log' + if os.path.exists(f): + return f + return '/var/opt/remi/php' + version + '/log/php-fpm/error.log' + + +def fpmSlowLog(version): + return '/var/opt/remi/php' + version + '/log/php-fpm/www-slow.log' + + +def getPhpConf(version): + gets = [ + {'name': 'short_open_tag', 'type': 1, 'ps': '短标签支持'}, + {'name': 'asp_tags', 'type': 1, 'ps': 'ASP标签支持'}, + {'name': 'max_execution_time', 'type': 2, 'ps': '最大脚本运行时间'}, + {'name': 'max_input_time', 'type': 2, 'ps': '最大输入时间'}, + {'name': 'max_input_vars', 'type': 2, 'ps': '最大输入数量'}, + {'name': 'memory_limit', 'type': 2, 'ps': '脚本内存限制'}, + {'name': 'post_max_size', 'type': 2, 'ps': 'POST数据最大尺寸'}, + {'name': 'file_uploads', 'type': 1, 'ps': '是否允许上传文件'}, + {'name': 'upload_max_filesize', 'type': 2, 'ps': '允许上传文件的最大尺寸'}, + {'name': 'max_file_uploads', 'type': 2, 'ps': '允许同时上传文件的最大数量'}, + {'name': 'default_socket_timeout', 'type': 2, 'ps': 'Socket超时时间'}, + {'name': 'error_reporting', 'type': 3, 'ps': '错误级别'}, + {'name': 'display_errors', 'type': 1, 'ps': '是否输出详细错误信息'}, + {'name': 'cgi.fix_pathinfo', 'type': 0, 'ps': '是否开启pathinfo'}, + {'name': 'date.timezone', 'type': 3, 'ps': '时区'} + ] + phpini = mw.readFile(getConf(version)) + result = [] + for g in gets: + rep = g['name'] + r'\s*=\s*([0-9A-Za-z_& ~]+)(\s*;?|\r?\n)' + tmp = re.search(rep, phpini) + if not tmp: + continue + g['value'] = tmp.groups()[0] + result.append(g) + return mw.getJson(result) + + +def submitPhpConf(version): + gets = ['display_errors', 'cgi.fix_pathinfo', 'date.timezone', 'short_open_tag', + 'asp_tags', 'max_execution_time', 'max_input_time', 'max_input_vars', 'memory_limit', + 'post_max_size', 'file_uploads', 'upload_max_filesize', 'max_file_uploads', + 'default_socket_timeout', 'error_reporting'] + args = getArgs() + filename = getConf(version) + phpini = mw.readFile(filename) + for g in gets: + if g in args: + rep = g + r'\s*=\s*(.+)\r?\n' + val = g + ' = ' + args[g] + '\n' + phpini = re.sub(rep, val, phpini) + mw.writeFile(filename, phpini) + reload(version) + return mw.returnJson(True, '设置成功') + + +def getLimitConf(version): + fileini = getConf(version) + phpini = mw.readFile(fileini) + filefpm = getFpmConfFile(version) + phpfpm = mw.readFile(filefpm) + + # print fileini, filefpm + data = {} + try: + rep = r"upload_max_filesize\s*=\s*([0-9]+)M" + tmp = re.search(rep, phpini).groups() + data['max'] = tmp[0] + except: + data['max'] = '50' + + try: + rep = r"request_terminate_timeout\s*=\s*([0-9]+)\n" + tmp = re.search(rep, phpfpm).groups() + data['maxTime'] = tmp[0] + except: + data['maxTime'] = 0 + + try: + rep = r"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n" + tmp = re.search(rep, phpini).groups() + + if tmp[0] == '1': + data['pathinfo'] = True + else: + data['pathinfo'] = False + except: + data['pathinfo'] = False + + return mw.getJson(data) + + +def setMaxTime(version): + args = getArgs() + data = checkArgs(args, ['time']) + if not data[0]: + return data[1] + + time = args['time'] + if int(time) < 30 or int(time) > 86400: + return mw.returnJson(False, '请填写30-86400间的值!') + + filefpm = getFpmConfFile(version) + conf = mw.readFile(filefpm) + rep = r"request_terminate_timeout\s*=\s*([0-9]+)\n" + conf = re.sub(rep, "request_terminate_timeout = " + time + "\n", conf) + mw.writeFile(filefpm, conf) + + fileini = getConf(version) + phpini = mw.readFile(fileini) + rep = r"max_execution_time\s*=\s*([0-9]+)\r?\n" + phpini = re.sub(rep, "max_execution_time = " + time + "\n", phpini) + rep = r"max_input_time\s*=\s*([0-9]+)\r?\n" + phpini = re.sub(rep, "max_input_time = " + time + "\n", phpini) + mw.writeFile(fileini, phpini) + return mw.returnJson(True, '设置成功!') + + +def setMaxSize(version): + args = getArgs() + data = checkArgs(args, ['max']) + if not data[0]: + return data[1] + + maxVal = args['max'] + if int(maxVal) < 2: + return mw.returnJson(False, '上传大小限制不能小于2MB!') + + path = getConf(version) + conf = mw.readFile(path) + rep = r"\nupload_max_filesize\s*=\s*[0-9]+M" + conf = re.sub(rep, u'\nupload_max_filesize = ' + maxVal + 'M', conf) + rep = r"\npost_max_size\s*=\s*[0-9]+M" + conf = re.sub(rep, u'\npost_max_size = ' + maxVal + 'M', conf) + mw.writeFile(path, conf) + + msg = mw.getInfo('设置PHP-{1}最大上传大小为[{2}MB]!', (version, maxVal,)) + mw.writeLog('插件管理[PHP]', msg) + return mw.returnJson(True, '设置成功!') + + +def getFpmConfig(version): + + filefpm = getFpmConfFile(version) + conf = mw.readFile(filefpm) + data = {} + rep = r"\s*pm.max_children\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['max_children'] = tmp[0] + + rep = r"\s*pm.start_servers\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['start_servers'] = tmp[0] + + rep = r"\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['min_spare_servers'] = tmp[0] + + rep = r"\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['max_spare_servers'] = tmp[0] + + rep = r"\s*pm\s*=\s*(\w+)\s*" + tmp = re.search(rep, conf).groups() + data['pm'] = tmp[0] + return mw.getJson(data) + + +def setFpmConfig(version): + args = getArgs() + # if not 'max' in args: + # return 'missing time args!' + + max_children = args['max_children'] + start_servers = args['start_servers'] + min_spare_servers = args['min_spare_servers'] + max_spare_servers = args['max_spare_servers'] + pm = args['pm'] + + # file = getServerDir() + '/php' + version + '/php-fpm.d/www.conf' + filefpm = getFpmConfFile(version) + conf = mw.readFile(filefpm) + + rep = r"\s*pm.max_children\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.max_children = " + max_children, conf) + + rep = r"\s*pm.start_servers\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.start_servers = " + start_servers, conf) + + rep = r"\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.min_spare_servers = " + + min_spare_servers, conf) + + rep = r"\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.max_spare_servers = " + + max_spare_servers + "\n", conf) + + rep = r"\s*pm\s*=\s*(\w+)\s*" + conf = re.sub(rep, "\npm = " + pm + "\n", conf) + + mw.writeFile(filefpm, conf) + reload(version) + + msg = mw.getInfo('设置PHP-{1}并发设置,max_children={2},start_servers={3},min_spare_servers={4},max_spare_servers={5}', (version, max_children, + start_servers, min_spare_servers, max_spare_servers,)) + mw.writeLog('插件管理[PHP]', msg) + return mw.returnJson(True, '设置成功!') + + +def getFpmAddress(version): + fpm_address = '/var/opt/remi/php{}/run/php-fpm/www.sock'.format(version) + php_fpm_file = getFpmConfFile(version) + try: + content = readFile(php_fpm_file) + tmp = re.findall(r"listen\s*=\s*(.+)", content) + if not tmp: + return fpm_address + if tmp[0].find('sock') != -1: + return fpm_address + if tmp[0].find(':') != -1: + listen_tmp = tmp[0].split(':') + if bind: + fpm_address = (listen_tmp[0], int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(tmp[0])) + return fpm_address + except: + return fpm_address + + +def getFpmStatus(version): + stat = status(version) + if stat == 'stop': + return mw.returnJson(False, 'PHP[' + version + ']未启动!!!') + + sock_file = getFpmAddress(version) + try: + sock_data = mw.requestFcgiPHP(sock_file, '/phpfpm_status_yum' + version + '?json') + + result = str(sock_data, encoding='utf-8') + data = json.loads(result) + fTime = time.localtime(int(data['start time'])) + data['start time'] = time.strftime('%Y-%m-%d %H:%M:%S', fTime) + except Exception as e: + return mw.returnJson(False, str(e)) + + # print(data) + return mw.returnJson(True, "OK", data) + + +def getSessionConf(version): + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(filename) + + rep = r'session.save_handler\s*=\s*([0-9A-Za-z_& ~]+)(\s*;?|\r?\n)' + save_handler = re.search(rep, phpini) + if save_handler: + save_handler = save_handler.group(1) + else: + save_handler = "files" + + reppath = r'\nsession.save_path\s*=\s*"tcp\:\/\/([\d\.]+):(\d+).*\r?\n' + passrep = r'\nsession.save_path\s*=\s*"tcp://[\w\.\?\:]+=(.*)"\r?\n' + memcached = r'\nsession.save_path\s*=\s*"([\d\.]+):(\d+)"' + save_path = re.search(reppath, phpini) + if not save_path: + save_path = re.search(memcached, phpini) + passwd = re.search(passrep, phpini) + port = "" + if passwd: + passwd = passwd.group(1) + else: + passwd = "" + if save_path: + port = save_path.group(2) + save_path = save_path.group(1) + + else: + save_path = "" + + data = {"save_handler": save_handler, "save_path": save_path, + "passwd": passwd, "port": port} + return mw.returnJson(True, 'ok', data) + + +def setSessionConf(version): + + args = getArgs() + + ip = args['ip'] + port = args['port'] + passwd = args['passwd'] + save_handler = args['save_handler'] + + if save_handler != "files": + iprep = r"(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})" + if not re.search(iprep, ip): + return mw.returnJson(False, '请输入正确的IP地址') + + try: + port = int(port) + if port >= 65535 or port < 1: + return mw.returnJson(False, '请输入正确的端口号') + except: + return mw.returnJson(False, '请输入正确的端口号') + prep = r"[\~\`\/\=]" + if re.search(prep, passwd): + return mw.returnJson(False, '请不要输入以下特殊字符 " ~ ` / = "') + + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + phpini = mw.readFile(filename) + + session_tmp = getServerDir() + "/tmp/session" + + rep = r'session.save_handler\s*=\s*(.+)\r?\n' + val = r'session.save_handler = ' + save_handler + '\n' + phpini = re.sub(rep, val, phpini) + + content = mw.execShell('cat /etc/opt/remi/php' + + version + "/php.d/* | grep -v '^;' |tr -s '\n'") + content = content[0] + + if save_handler == "memcached": + if not content.find("memcached.so") > -1: + return mw.returnJson(False, '请先安装%s扩展' % save_handler) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "%s:%s" \n' % (ip, port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', + '\n;session.save_path = "/tmp"' + val, phpini) + + if save_handler == "memcache": + if not content.find("memcache.so") > -1: + return mw.returnJson(False, '请先安装%s扩展' % save_handler) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "%s:%s" \n' % (ip, port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', + '\n;session.save_path = "/tmp"' + val, phpini) + + if save_handler == "redis": + if not content.find("redis.so") > -1: + return mw.returnJson(False, '请先安装%s扩展' % save_handler) + if passwd: + passwd = "?auth=" + passwd + else: + passwd = "" + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "tcp://%s:%s%s"\n' % (ip, port, passwd) + res = re.search(rep, phpini) + if res: + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', + '\n;session.save_path = "/tmp"' + val, phpini) + + if save_handler == "files": + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "' + session_tmp + '"\n' + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', + '\n;session.save_path = "/tmp"' + val, phpini) + + mw.writeFile(filename, phpini) + restart(version) + return mw.returnJson(True, '设置成功!') + + +def getSessionCount_Origin(version): + session_tmp = getServerDir() + "/tmp/session" + d = ["/tmp", session_tmp] + count = 0 + for i in d: + if not os.path.exists(i): + mw.execShell('mkdir -p %s' % i) + list = os.listdir(i) + for l in list: + if os.path.isdir(i + "/" + l): + l1 = os.listdir(i + "/" + l) + for ll in l1: + if "sess_" in ll: + count += 1 + continue + if "sess_" in l: + count += 1 + + s = "find /tmp -mtime +1 |grep 'sess_' | wc -l" + old_file = int(mw.execShell(s)[0].split("\n")[0]) + + s = "find " + session_tmp + " -mtime +1 |grep 'sess_'|wc -l" + old_file += int(mw.execShell(s)[0].split("\n")[0]) + return {"total": count, "oldfile": old_file} + + +def getSessionCount(version): + data = getSessionCount_Origin(version) + return mw.returnJson(True, 'ok!', data) + + +def cleanSessionOld(version): + s = "find /tmp -mtime +1 |grep 'sess_'|xargs rm -f" + mw.execShell(s) + + session_tmp = getServerDir() + "/tmp/session" + s = "find " + session_tmp + " -mtime +1 |grep 'sess_' |xargs rm -f" + mw.execShell(s) + old_file_conf = getSessionCount_Origin(version)["oldfile"] + if old_file_conf == 0: + return mw.returnJson(True, '清理成功') + else: + return mw.returnJson(True, '清理失败') + + +def getDisableFunc(version): + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(filename) + data = {} + rep = r"disable_functions\s*=\s{0,1}(.*)\n" + tmp = re.search(rep, phpini).groups() + data['disable_functions'] = tmp[0] + return mw.getJson(data) + + +def setDisableFunc(version): + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + + args = getArgs() + disable_functions = args['disable_functions'] + + phpini = mw.readFile(filename) + rep = r"disable_functions\s*=\s*.*\n" + phpini = re.sub(rep, 'disable_functions = ' + disable_functions + "\n", phpini) + + msg = mw.getInfo('修改PHP-{1}的禁用函数为[{2}]', (version, disable_functions,)) + mw.writeLog('插件管理[PHP-YUM]', msg) + mw.writeFile(filename, phpini) + reload(version) + return mw.returnJson(True, '设置成功!') + + +def getPhpinfo(version): + stat = status(version) + if stat == 'stop': + return 'PHP[' + version + ']未启动,不可访问!' + + sock_file = getFpmAddress(version) + root_dir = mw.getFatherDir() + '/phpinfo' + + mw.execShell("rm -rf " + root_dir) + mw.execShell("mkdir -p " + root_dir) + mw.writeFile(root_dir + '/phpinfo.php', '') + sock_data = mw.requestFcgiPHP(sock_file, '/phpinfo.php', root_dir) + os.system("rm -rf " + root_dir) + phpinfo = str(sock_data, encoding='utf-8') + return phpinfo + + +def get_php_info(args): + return getPhpinfo(args['version']) + + +def getLibConf(version): + fname = getConf(version) + if not os.path.exists(fname): + return mw.returnJson(False, '指定PHP版本不存在!') + + # phpini = mw.readFile(fname) + cmd = 'cat /etc/opt/remi/php' +version + "/php.d/* | grep -v '^;' |tr -s '\n'" + content = mw.execShell(cmd) + content = content[0] + + libpath = getPluginDir() + '/versions/phplib.conf' + phplib = json.loads(mw.readFile(libpath)) + + libs = [] + tasks = mw.M('tasks').where("status!=?", ('1',)).field('status,name').select() + for lib in phplib: + lib['task'] = '1' + for task in tasks: + tmp = mw.getStrBetween('[', ']', task['name']) + if not tmp: + continue + tmp1 = tmp.split('-') + if tmp1[0].lower() == lib['name'].lower(): + lib['task'] = task['status'] + lib['phpversions'] = [] + lib['phpversions'].append(tmp1[1]) + if content.find(lib['check']) == -1: + lib['status'] = False + else: + lib['status'] = True + libs.append(lib) + return mw.returnJson(True, 'OK!', libs) + + +def installLib(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + cmd = "cd " + getPluginDir() + "/versions && /bin/bash common.sh " + version + ' install ' + name + install_name = '安装PHPYUM[' + name + '-' + version + ']' + import thisdb + thisdb.addTask(name=install_name,cmd=cmd) + + mw.triggerTask() + return mw.returnJson(True, '已将下载任务添加到队列!') + + +def uninstallLib(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + execstr = "cd " + getPluginDir() + '/versions/' + " && /bin/bash common.sh " + version + ' uninstall ' + name + + data = mw.execShell(execstr) + # data[0] == '' and + if data[1] == '': + return mw.returnJson(True, '已经卸载成功!') + else: + return mw.returnJson(False, '卸载错误信息!:' + data[1]) + +def getConfAppStart(): + pstart = mw.getServerDir() + '/php-yum/app_start.php' + return pstart + +def opcacheBlacklistFile(): + op_bl = mw.getServerDir() + '/php-yum/opcache-blacklist.txt' + return op_bl + +def installPreInspection(version): + + cmd = "cat /etc/*-release | grep PRETTY_NAME |awk -F = '{print $2}' | awk -F '\"' '{print $2}'| awk '{print $1}'" + sys = mw.execShell(cmd) + if sys[1] != '': + return '不支持该系统' + + cmd = "cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F '\"' '{print $2}'" + sys_id = mw.execShell(cmd) + + sysName = sys[0].strip().lower() + sysId = sys_id[0].strip() + + if not sysName in ['centos','almalinux','fedora','rocky']: + return '暂时仅支持centos,almalinux,fedora,rocky' + return 'ok' + + + +if __name__ == "__main__": + + if len(sys.argv) < 3: + print('missing parameters') + exit(0) + + func = sys.argv[1] + version = sys.argv[2] + + if func == 'status': + print(status(version)) + elif func == 'start': + print(start(version)) + elif func == 'stop': + print(stop(version)) + elif func == 'restart': + print(restart(version)) + elif func == 'reload': + print(reload(version)) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'initd_status': + print(initdStatus(version)) + elif func == 'initd_install': + print(initdInstall(version)) + elif func == 'initd_uninstall': + print(initdUinstall(version)) + elif func == 'fpm_log': + print(fpmLog(version)) + elif func == 'fpm_slow_log': + print(fpmSlowLog(version)) + elif func == 'conf': + print(getConf(version)) + elif func == 'app_start': + print(getConfAppStart()) + elif func == 'opcache_blacklist_file': + print(opcacheBlacklistFile()) + elif func == 'get_php_conf': + print(getPhpConf(version)) + elif func == 'get_fpm_conf_file': + print(getFpmConfFile(version)) + elif func == 'get_fpm_file': + print(getFpmFile(version)) + elif func == 'submit_php_conf': + print(submitPhpConf(version)) + elif func == 'get_limit_conf': + print(getLimitConf(version)) + elif func == 'set_max_time': + print(setMaxTime(version)) + elif func == 'set_max_size': + print(setMaxSize(version)) + elif func == 'get_fpm_conf': + print(getFpmConfig(version)) + elif func == 'set_fpm_conf': + print(setFpmConfig(version)) + elif func == 'get_fpm_status': + print(getFpmStatus(version)) + elif func == 'get_session_conf': + print(getSessionConf(version)) + elif func == 'set_session_conf': + print(setSessionConf(version)) + elif func == 'get_session_count': + print(getSessionCount(version)) + elif func == 'clean_session_old': + print(cleanSessionOld(version)) + elif func == 'get_disable_func': + print(getDisableFunc(version)) + elif func == 'set_disable_func': + print(setDisableFunc(version)) + elif func == 'get_phpinfo': + print(getPhpinfo(version)) + elif func == 'get_lib_conf': + print(getLibConf(version)) + elif func == 'install_lib': + print(installLib(version)) + elif func == 'uninstall_lib': + print(uninstallLib(version)) + else: + print("fail") diff --git a/plugins/php-yum/index_php_yum.py b/plugins/php-yum/index_php_yum.py new file mode 100755 index 000000000..07f623974 --- /dev/null +++ b/plugins/php-yum/index_php_yum.py @@ -0,0 +1,149 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import shutil + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'php' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return '/etc/opt/remi' + + +def getInitDFile(version): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + return '/etc/init.d/' + getPluginName() + version + + +def getConf(version): + path = getServerDir() + '/php' + version + '/php.ini' + return path + + +def getFpmConfFile(version): + return getServerDir() + '/php' + version + '/php-fpm.d/mw.conf' + + +def getPhpSocket(version): + path = getFpmConfFile(version) + content = mw.readFile(path) + rep = r'listen\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def status(version): + # ps -ef|grep 'php/81' |grep -v grep | grep -v python | awk '{print $2} + cmd = "ps -ef|grep 'remi/php" + version + "' |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def getFpmAddress(version): + fpm_address = '/var/opt/remi/php{}/run/php-fpm/www.sock'.format(version) + php_fpm_file = getFpmConfFile(version) + try: + content = readFile(php_fpm_file) + tmp = re.findall(r"listen\s*=\s*(.+)", content) + if not tmp: + return fpm_address + if tmp[0].find('sock') != -1: + return fpm_address + if tmp[0].find(':') != -1: + listen_tmp = tmp[0].split(':') + if bind: + fpm_address = (listen_tmp[0], int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(tmp[0])) + return fpm_address + except: + return fpm_address + + +def getPhpinfo(version): + stat = status(version) + if stat == 'stop': + return 'PHP[' + version + ']未启动,不可访问!!!' + + sock_file = getFpmAddress(version) + root_dir = mw.getFatherDir() + '/phpinfo' + + mw.execShell("rm -rf " + root_dir) + mw.execShell("mkdir -p " + root_dir) + mw.writeFile(root_dir + '/phpinfo.php', '') + sock_data = mw.requestFcgiPHP(sock_file, '/phpinfo.php', root_dir) + os.system("rm -rf " + root_dir) + phpinfo = str(sock_data, encoding='utf-8') + return phpinfo + + +def libConfCommon(version): + fname = getConf(version) + if not os.path.exists(fname): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(fname) + + libpath = getPluginDir() + '/versions/phplib.conf' + phplib = json.loads(mw.readFile(libpath)) + + libs = [] + tasks = mw.M('tasks').where( + "status!=?", ('1',)).field('status,name').select() + for lib in phplib: + lib['task'] = '1' + for task in tasks: + tmp = mw.getStrBetween('[', ']', task['name']) + if not tmp: + continue + tmp1 = tmp.split('-') + if tmp1[0].lower() == lib['name'].lower(): + lib['task'] = task['status'] + lib['phpversions'] = [] + lib['phpversions'].append(tmp1[1]) + if phpini.find(lib['check']) == -1: + lib['status'] = False + else: + lib['status'] = True + libs.append(lib) + return libs + + +def get_php_info(args): + return getPhpinfo(args['version']) + + +def get_lib_conf(data): + libs = libConfCommon(data['version']) + return mw.returnData(True, 'OK!', libs) diff --git a/plugins/php-yum/info.json b/plugins/php-yum/info.json new file mode 100755 index 000000000..1c58a264d --- /dev/null +++ b/plugins/php-yum/info.json @@ -0,0 +1,19 @@ +{ + "sort": 7, + "ps": "PHP是世界上最好的编程语言(极速安装)", + "shell": "install.sh", + "name": "php-yum", + "title": "PHP[YUM]", + "coexist": true, + "versions": ["74","80","81","82","83","84","85"], + "install_pre_inspection":true, + "tip": "soft", + "checks": "server/php-yum/VERSION", + "path": "server/php-yum/VERSION", + "display": 1, + "author": "Zend", + "date": "2022-07-07", + "home": "https://www.php.net", + "type": "PHP语言解释器", + "pid": "6" +} \ No newline at end of file diff --git a/plugins/php-yum/install.sh b/plugins/php-yum/install.sh new file mode 100755 index 000000000..4ed4d994b --- /dev/null +++ b/plugins/php-yum/install.sh @@ -0,0 +1,116 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +if id www &> /dev/null ;then + echo "www uid is `id -u www`" + echo "www shell is `grep "^www:" /etc/passwd |cut -d':' -f7 `" +else + groupadd www + useradd -g www -s /sbin/nologin www + # useradd -g www -s /bin/bash www +fi + +action=$1 +type=$2 + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + +# cd /www/server/mdserver-web/plugins/php-yum/versions && bash common.sh 83 install opcache + +#获取信息和版本 +# bash /www/server/mdserver-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +if [ "$OSNAME" == "alma" ];then + rpm -Uvh http://rpms.remirepo.net/enterprise/remi-release-${VERSION_ID}.rpm +fi + +if [ "$OSNAME" == "rocky" ];then + rpm -Uvh http://rpms.remirepo.net/enterprise/remi-release-${VERSION_ID}.rpm +fi + +if [ "$OSNAME" == "centos" ];then + rpm -Uvh http://rpms.remirepo.net/enterprise/remi-release-${VERSION_ID}.rpm +fi + + +# rpm -Uvh http://rpms.remirepo.net/fedora/remi-release-31.rpm +if [ "$OSNAME" == "fedora" ];then + rpm -Uvh http://rpms.remirepo.net/fedora/remi-release-${VERSION_ID}.rpm +fi + + + +if [ "${action}" == "uninstall" ] && [ -d ${serverPath}/php-yum/${type} ];then + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/php-yum/index.py stop ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/php-yum/index.py initd_uninstall ${type} + + if [ -f /lib/systemd/system/php${type}-php-fpm.service ];then + rm -rf /lib/systemd/system/php${type}-fpm.service + fi + + if [ -f /lib/systemd/system/system/php${type}-php-fpm.service ];then + rm -rf /lib/systemd/system/php${type}-php-fpm.service + fi + + systemctl daemon-reload +fi + +cd ${curPath} && sh -x $curPath/versions/$2/install.sh $1 + +if [ "${action}" == "install" ] && [ -d ${serverPath}/php-yum/${type} ];then + + # 安装通用扩展 + echo "install PHP-YUM[${type}] extend start" + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install mysqlnd + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install mysql + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install gd + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install iconv + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install exif + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install intl + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install mcrypt + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install bcmath + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install openssl + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install gettext + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install redis + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install memcached + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install mbstring + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install mongodb + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install zip + cd ${rootPath}/plugins/php-yum/versions && bash common.sh ${type} install simplexml + + echo "install PHP-YUM[${type}] extend end" + + #初始化 + cd ${rootPath} && python3 plugins/php-yum/index.py start ${type} + cd ${rootPath} && python3 plugins/php-yum/index.py initd_install ${type} + + if [ ! -f /usr/local/bin/composer ];then + cd /tmp + curl -sS https://getcomposer.org/installer | /opt/remi/php${type}/root/usr/bin/php + mv composer.phar /usr/local/bin/composer + fi + + echo "PHP-YUM[${type}] start ..." + systemctl restart php${type}-php-fpm + echo "PHP-YUM[${type}] start ok" +fi + + diff --git a/plugins/php-yum/js/php.js b/plugins/php-yum/js/php.js new file mode 100755 index 000000000..5629b8983 --- /dev/null +++ b/plugins/php-yum/js/php.js @@ -0,0 +1,743 @@ +function phpPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'php-yum'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function phpPostCallback(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'php-yum'; + req_data['func'] = method; + req_data['script']='index_php_yum'; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +//配置修改 +function phpSetConfig(version) { + phpPost('get_php_conf', version,'',function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '70' + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = '' + break; + case 1: + var selected_1 = (rdata[i].value == 'On') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'Off') ? 'selected' : ''; + ibody = '' + break; + } + mlist += '

                                ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

                                ' + } + var phpCon = '
                                \ + ' + mlist + '\ +
                                \ +
                                ' + $(".soft-man-con").html(phpCon); + }); +} + + +//提交PHP配置 +function submitConf(version) { + var data = { + version: version, + display_errors: $("select[name='display_errors']").val(), + 'cgi.fix_pathinfo': $("select[name='cgi.fix_pathinfo']").val(), + 'date.timezone': $("input[name='date.timezone']").val(), + short_open_tag: $("select[name='short_open_tag']").val(), + asp_tags: $("select[name='asp_tags']").val() || 'On', + safe_mode: $("select[name='safe_mode']").val(), + max_execution_time: $("input[name='max_execution_time']").val(), + max_input_time: $("input[name='max_input_time']").val(), + max_input_vars: $("input[name='max_input_vars']").val(), + memory_limit: $("input[name='memory_limit']").val(), + post_max_size: $("input[name='post_max_size']").val(), + file_uploads: $("select[name='file_uploads']").val(), + upload_max_filesize: $("input[name='upload_max_filesize']").val(), + max_file_uploads: $("input[name='max_file_uploads']").val(), + default_socket_timeout: $("input[name='default_socket_timeout']").val(), + error_reporting: $("input[name='error_reporting']").val() || 'On' + }; + + phpPost('submit_php_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + // console.log(rdata); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + + +//php超时限制 +function phpCommonFunc(version){ + phpPost('get_limit_conf', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + var con = '

                                \ + 超时限制\ + , 秒\ + \ +

                                '; + + con += '

                                \ + 上传限制\ + ,MB\ + \ +

                                '; + + con += '

                                \ + \ + \ + \ + \ +

                                '; + + $(".soft-man-con").html(con); + }); +} + +//设置超时限制 +function setPHPMaxTime(version) { + var max = $(".phpTimeLimit").val(); + phpPost('set_max_time',version,{'time':max},function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + phpCommonFunc(version); + },{ icon: rdata.status ? 1 : 2 }); + + }); +} +//设置PHP上传限制 +function setPHPMaxSize(version) { + max = $(".phpUploadLimit").val(); + if (max < 2) { + alert(max); + layer.msg('上传大小限制不能小于2M', { icon: 2 }); + return; + } + + phpPost('set_max_size',version,{'max':max},function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function phpPreload(version){ + phpPost('app_start',version,{},function(data){ + onlineEditFile(0, data['data']); + }); +} + +function phpOpcacheBlacklist(version){ + phpPost('opcache_blacklist_file',version,{},function(data){ + onlineEditFile(0, data['data']); + }); +} + +function phpFpmRoot(version){ + phpPost('get_fpm_file',version,{},function(data){ + onlineEditFile(0, data['data']); + }); +} + +function getFpmConfig(version, pool = 'www'){ + phpPost('get_fpm_conf', version, {'pool':pool}, function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var limitList = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + var pms = [{ 'name': 'static', 'title': '静态' }, { 'name': 'dynamic', 'title': '动态' },{ 'name': 'ondemand', 'title': '按需' }]; + var pmList = ''; + for (var i = 0; i < pms.length; i++) { + pmList += ''; + } + + var poolHtml = "" + + ""; + + var body = "
                                " + + "

                                应用池[pool]:

                                " + + "

                                并发方案:

                                " + + "

                                运行模式:*PHP-FPM运行模式

                                " + + "

                                max_children:*允许创建的最大子进程数

                                " + + "

                                start_servers: *起始进程数(服务启动后初始进程数量)

                                " + + "

                                min_spare_servers: *最小空闲进程数(清理空闲进程后的保留数量)

                                " + + "

                                max_spare_servers: *最大空闲进程数(当空闲进程达到此值时清理)

                                " + + "
                                " + + "
                                "; + + $(".soft-man-con").html(body); + $("select[name='limit']").change(function() { + var type = $(this).val(); + var max_children = rdata.max_children; + var start_servers = rdata.start_servers; + var min_spare_servers = rdata.min_spare_servers; + var max_spare_servers = rdata.max_spare_servers; + switch (type) { + case '0': + max_children = 2; + start_servers = 1; + min_spare_servers = 1; + max_spare_servers = 2; + break; + case '1': + max_children = 5; + start_servers = 2; + min_spare_servers = 1; + max_spare_servers = 5; + break; + case '2': + max_children = 10; + start_servers = 2; + min_spare_servers = 1; + max_spare_servers = 10; + break; + case '3': + max_children = 30; + start_servers = 5; + min_spare_servers = 5; + max_spare_servers = 20; + break; + case '4': + max_children = 50; + start_servers = 15; + min_spare_servers = 15; + max_spare_servers = 35; + break; + case '5': + max_children = 100; + start_servers = 20; + min_spare_servers = 20; + max_spare_servers = 70; + break; + case '6': + max_children = 200; + start_servers = 25; + min_spare_servers = 25; + max_spare_servers = 150; + break; + case '7': + max_children = 300; + start_servers = 30; + min_spare_servers = 30; + max_spare_servers = 180; + break; + case '8': + max_children = 500; + start_servers = 35; + min_spare_servers = 35; + max_spare_servers = 250; + break; + case '9': + max_children = 2000; + start_servers = 40; + min_spare_servers = 40; + max_spare_servers = 255; + break; + } + + $("input[name='max_children']").val(max_children); + $("input[name='start_servers']").val(start_servers); + $("input[name='min_spare_servers']").val(min_spare_servers); + $("input[name='max_spare_servers']").val(max_spare_servers); + }); + + $('select[name="pool"]').change(function(){ + var pool = $(this).val(); + getFpmConfig(version, pool); + }); + }); +} + +function setFpmConfig(version){ + var max_children = Number($("input[name='max_children']").val()); + var start_servers = Number($("input[name='start_servers']").val()); + var min_spare_servers = Number($("input[name='min_spare_servers']").val()); + var max_spare_servers = Number($("input[name='max_spare_servers']").val()); + var pm = $("select[name='pm']").val(); + + if (max_children < max_spare_servers) { + layer.msg('max_spare_servers 不能大于 max_children', { icon: 2 }); + return; + } + + if (min_spare_servers > start_servers) { + layer.msg('min_spare_servers 不能大于 start_servers', { icon: 2 }); + return; + } + + if (max_spare_servers < min_spare_servers) { + layer.msg('min_spare_servers 不能大于 max_spare_servers', { icon: 2 }); + return; + } + + if (max_children < start_servers) { + layer.msg('start_servers 不能大于 max_children', { icon: 2 }); + return; + } + + if (max_children < 1 || start_servers < 1 || min_spare_servers < 1 || max_spare_servers < 1) { + layer.msg('配置值不能小于1', { icon: 2 }); + return; + } + + var data = { + version:version, + max_children:max_children, + start_servers:start_servers, + min_spare_servers:min_spare_servers, + max_spare_servers:max_spare_servers, + pm:pm, + }; + phpPost('set_fpm_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +function getFpmStatus(version){ + phpPost('get_fpm_status', version, '', function(ret_data){ + var tmp_data = $.parseJSON(ret_data.data); + if(!tmp_data.status){ + layer.msg(tmp_data.msg, { icon: tmp_data.status ? 1 : 2 }); + return; + } + + var rdata = tmp_data.data; + var php_fpm_status = '动态'; + if (rdata['process manager'] == 'dynamic'){ + php_fpm_status = '动态'; + } else if(rdata['process manager'] == 'static'){ + php_fpm_status = '静态'; + } else if(rdata['process manager'] == 'ondemand'){ + php_fpm_status = '按需'; + } + + var con = "
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                应用池(pool)" + rdata.pool + "
                                进程管理方式(process manager)" + php_fpm_status + "
                                启动日期(start time)" + rdata['start time'] + "
                                请求数(accepted conn)" + rdata['accepted conn'] + "
                                请求队列(listen queue)" + rdata['listen queue'] + "
                                最大等待队列(max listen queue)" + rdata['max listen queue'] + "
                                socket队列长度(listen queue len)" + rdata['listen queue len'] + "
                                空闲进程数量(idle processes)" + rdata['idle processes'] + "
                                活跃进程数量(active processes)" + rdata['active processes'] + "
                                总进程数量(total processes)" + rdata['total processes'] + "
                                最大活跃进程数量(max active processes)" + rdata['max active processes'] + "
                                到达进程上限次数(max children reached)" + rdata['max children reached'] + "
                                慢请求数量(slow requests)" + rdata['slow requests'] + "
                                "; + $(".soft-man-con").html(con); + $(".GetPHPStatus td,.GetPHPStatus th").css("padding", "7px"); + }); +} + +function getSessionConfig(version){ + phpPost('get_session_conf', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + if(!rdata.status){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + var rdata = rdata.data; + + var cacheList = "" + + "" + + "" + + ""; + + + var info = rdata.save_path.split(":"); + var con = "
                                " + + "

                                存储模式:

                                " + + "

                                IP地址:

                                " + + "

                                端口:

                                " + + "

                                密码:

                                " + + "

                                " + + "
                                \ +
                                  \ +
                                • 若你的站点并发比较高,使用Redis,Memcache能有效提升PHP并发能力
                                • \ +
                                • 若调整Session模式后,网站访问异常,请切换回原来的模式
                                • \ +
                                • 切换Session模式会使在线的用户会话丢失,请在流量小的时候切换
                                • \ +
                                \ +
                                \ +
                                \ +
                                "; + + $(".soft-man-con").html(con); + + if (rdata.save_handler == 'files'){ + $('input[name="ip"]').attr('disabled','disabled'); + $('input[name="port"]').attr('disabled','disabled'); + $('input[name="passwd"]').attr('placeholder','如果没有密码留空'); + $('input[name="passwd"]').attr('disabled','disabled'); + } + + // change event + $("select[name='save_handler']").change(function() { + var type = $(this).val(); + + var passwd = $('input[name="passwd"]').val(); + if (passwd == ""){ + $('input[name="passwd"]').attr('placeholder','如果没有密码留空'); + } + + var ip = $('input[name="ip"]').val(); + if (ip == ""){ + $('input[name="ip"]').val('127.0.0.1'); + } + + switch (type) { + case 'redis': + var port = $('input[name="port"]').val(); + if (port == ""){ + $('input[name="port"]').val('6379'); + } + $('input[name="ip"]').removeAttr('disabled'); + $('input[name="port"]').removeAttr('disabled'); + $('input[name="passwd"]').removeAttr('disabled'); + break; + case 'files': + $('input[name="ip"]').val("").attr('disabled','disabled'); + $('input[name="port"]').val("").attr('disabled','disabled'); + $('input[name="passwd"]').val("").attr('disabled','disabled'); + break; + case 'memcache': + var port = $('input[name="port"]').val(); + if (port == ""){ + $('input[name="port"]').val('11211'); + } + $('input[name="ip"]').removeAttr('disabled'); + $('input[name="port"]').removeAttr('disabled'); + $('input[name="passwd"]').removeAttr('disabled'); + break; + case 'memcached': + var port = $('input[name="port"]').val(); + if (port == ""){ + $('input[name="port"]').val('11211'); + } + $('input[name="ip"]').removeAttr('disabled'); + $('input[name="port"]').removeAttr('disabled'); + $('input[name="passwd"]').removeAttr('disabled'); + break; + } + }); + + //load session stats + phpPost('get_session_count', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + if(!rdata.status){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + var rdata = rdata.data; + + var html_var = "
                                清理Session文件
                                \ +
                                \ +
                                \ +
                                总Session文件数量"+rdata.total+"
                                \ +
                                可清理的Session文件数量"+rdata.oldfile+"
                                \ +
                                \ + "; + + $("#session_clear").html(html_var); + + + $('#clean_func').click(function(){ + phpPost('clean_session_old', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + showMsg(rdata.msg,function(){ + getSessionConfig(version); + },{ icon: rdata.status ? 1 : 2 }); + }); + }); + }); + }); + +} + +function setSessionConfig(version){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var passwd = $('input[name="passwd"]').val(); + var save_handler = $("select[name='save_handler']").val(); + var data = { + ip:ip, + port:port, + passwd:passwd, + save_handler:save_handler, + }; + phpPost('set_session_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +//禁用函数 +function disableFunc(version) { + phpPost('get_disable_func', version,'',function(data){ + var rdata = $.parseJSON(data.data); + var disable_functions = rdata.disable_functions.split(','); + var dbody = '' + for (var i = 0; i < disable_functions.length; i++) { + if (disable_functions[i] == '') continue; + dbody += "" + disable_functions[i] + "删除"; + } + + var con = "
                                " + + "" + + "" + + "
                                " + + "
                                " + + "" + + "" + dbody + "" + + "
                                名称操作
                                "; + + con += '
                                  \ +
                                • 在此处可以禁用指定函数的调用,以增强环境安全性!
                                • \ +
                                • 强烈建议禁用如exec,system等危险函数!
                                • \ +
                                '; + + $(".soft-man-con").html(con); + }); +} +//设置禁用函数 +function setDisableFunc(version, act, fs) { + var fsArr = fs.split(','); + if (act == 1) { + var functions = $("#disable_function_val").val(); + for (var i = 0; i < fsArr.length; i++) { + if (functions == fsArr[i]) { + layer.msg(lan.soft.fun_msg, { icon: 5 }); + return; + } + } + fs += ',' + functions; + msg = '添加成功'; + } else { + + fs = ''; + for (var i = 0; i < fsArr.length; i++) { + if (act == fsArr[i]) continue; + fs += fsArr[i] + ',' + } + msg = '删除成功'; + fs = fs.substr(0, fs.length - 1); + } + + var data = { + 'version':version, + 'disable_functions':fs, + }; + + phpPost('set_disable_func', version,data,function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.status ? msg : rdata.msg, function(){ + disableFunc(version); + } ,{ icon: rdata.status ? 1 : 2 }); + }); +} + + +//phpinfo +// function getPhpinfo(version) { +// var con = ''; +// $(".soft-man-con").html(con); +// } + +//获取PHPInfo +function getPHPInfo_old(version) { + phpPost('get_phpinfo', version, '', function(data){ + var rdata = data.data; + layer.open({ + type: 1, + title: "PHP-" + version + "-PHPINFO", + area: ['90%', '90%'], + closeBtn: 2, + shadeClose: true, + content: rdata + }); + }); +} + +function getPHPInfo(version) { + phpPostCallback('get_php_info', version, {}, function(data){ + if (!data.status){ + layer.msg(rdata.msg, { icon: 2 }); + return; + } + + layer.open({ + type: 1, + title: "PHP-" + version + "-PHPINFO", + area: ['70%', '90%'], + closeBtn: 2, + shadeClose: true, + content: data.data.replace('a:link {color: #009; text-decoration: none; background-color: #fff;}', '').replace('a:link {color: #000099; text-decoration: none; background-color: #ffffff;}', '') + }); + }) +} + + + +function phpLibConfig(version){ + + phpPost('get_lib_conf', version, '', function(data){ + var rdata = $.parseJSON(data.data); + + if (!rdata.status){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + + var libs = rdata.data; + var body = ''; + var opt = ''; + + for (var i = 0; i < libs.length; i++) { + if (libs[i].versions.indexOf(version) == -1){ + continue; + } + + if (libs[i]['task'] == '-1' && libs[i].phpversions.indexOf(version) != -1) { + opt = '安装.' + } else if (libs[i]['task'] == '0' && libs[i].phpversions.indexOf(version) != -1) { + opt = '等待.' + } else if (libs[i].status) { + opt = '卸载' + } else { + opt = '安装' + } + + body += '' + + '' + libs[i].name + '' + + '' + libs[i].type + '' + + '' + libs[i].msg + '' + + '' + + '' + opt + '' + + ''; + } + + + var con = '
                                ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + body + '' + + '
                                名称类型说明状态操作
                                ' + + '
                                ' + + '
                                  \ +
                                • 请按实际需求安装扩展,不要安装不必要的PHP扩展,这会影响PHP执行效率,甚至出现异常
                                • \ +
                                • Redis扩展只允许在1个PHP版本中使用,安装到其它PHP版本请在[软件管理]重装Redis
                                • \ +
                                • opcache/xcache/apc等脚本缓存扩展,请只安装其中1个,否则可能导致您的站点程序异常
                                • \ +
                                • ioncube要在ZendGuardLoader/opcache前安装,否则可能导致您的站点程序异常
                                • \ +
                                '; + $('.soft-man-con').html(con); + }); + +} + +//安装扩展 +function installPHPLib(version, name, title, pathinfo) { + layer.confirm('您真的要安装{1}吗?'.replace('{1}', name), { icon: 3, closeBtn: 2 }, function() { + name = name.toLowerCase(); + var data = "name=" + name + "&version=" + version + "&type=1"; + + phpPost('install_lib', version, data, function(data){ + var rdata = $.parseJSON(data.data); + // layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + showMsg(rdata.msg, function(){ + getTaskCount(); + phpLibConfig(version); + },{ icon: rdata.status ? 1 : 2 }); + + }); + }); +} + +//卸载扩展 +function uninstallPHPLib(version, name, title, pathinfo) { + layer.confirm('您真的要卸载{1}吗?'.replace('{1}', name), { icon: 3, closeBtn: 2 }, function() { + name = name.toLowerCase(); + var data = 'name=' + name + '&version=' + version; + phpPost('uninstall_lib', version, data, function(data){ + var rdata = $.parseJSON(data.data); + // layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + showMsg(rdata.msg, function(){ + getTaskCount(); + phpLibConfig(version); + },{ icon: rdata.status ? 1 : 2 },5000); + + }); + }); +} \ No newline at end of file diff --git a/plugins/php-yum/versions/74/install.sh b/plugins/php-yum/versions/74/install.sh new file mode 100755 index 000000000..b2098e1e5 --- /dev/null +++ b/plugins/php-yum/versions/74/install.sh @@ -0,0 +1,40 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=7.4.x +PHP_VER=74 + + +Install_php() +{ +#------------------------ install start ------------------------------------# + +yum install -y php74 php74-php-fpm +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-yum/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + yum remove -y php74 php74-php-fpm php74-* + rm -rf $serverPath/php-yum/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-yum/versions/80/install.sh b/plugins/php-yum/versions/80/install.sh new file mode 100755 index 000000000..070c2544c --- /dev/null +++ b/plugins/php-yum/versions/80/install.sh @@ -0,0 +1,41 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.0.x +PHP_VER=80 + + +Install_php() +{ +#------------------------ install start ------------------------------------# + + +yum install -y php80 php80-php-fpm +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-yum/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + # $serverPath/php-ya/init.d/php${PHP_VER} stop + rm -rf $serverPath/php-yum/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-yum/versions/81/install.sh b/plugins/php-yum/versions/81/install.sh new file mode 100755 index 000000000..5877e21df --- /dev/null +++ b/plugins/php-yum/versions/81/install.sh @@ -0,0 +1,39 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.1.x +PHP_VER=81 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +yum install -y php81 php81-php-fpm +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-yum/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + yum remove -y php81 php81-php-fpm php81-* + rm -rf $serverPath/php-yum/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-yum/versions/82/install.sh b/plugins/php-yum/versions/82/install.sh new file mode 100755 index 000000000..6440c2966 --- /dev/null +++ b/plugins/php-yum/versions/82/install.sh @@ -0,0 +1,39 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.2.x +PHP_VER=82 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +yum install -y php82 php82-php-fpm +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-yum/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + yum remove -y php82 php82-php-fpm php82-* + rm -rf $serverPath/php-yum/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-yum/versions/83/install.sh b/plugins/php-yum/versions/83/install.sh new file mode 100755 index 000000000..1a195b8c9 --- /dev/null +++ b/plugins/php-yum/versions/83/install.sh @@ -0,0 +1,39 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.3.x +PHP_VER=83 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +yum install -y php83 php83-php-fpm +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-yum/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + yum remove -y php83 php83-php-fpm php83-* + rm -rf $serverPath/php-yum/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-yum/versions/84/install.sh b/plugins/php-yum/versions/84/install.sh new file mode 100755 index 000000000..105495362 --- /dev/null +++ b/plugins/php-yum/versions/84/install.sh @@ -0,0 +1,39 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.4.x +PHP_VER=84 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +yum install -y php84 php84-php-fpm +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-yum/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + yum remove -y php84 php84-php-fpm php84-* + rm -rf $serverPath/php-yum/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-yum/versions/85/install.sh b/plugins/php-yum/versions/85/install.sh new file mode 100755 index 000000000..98c778845 --- /dev/null +++ b/plugins/php-yum/versions/85/install.sh @@ -0,0 +1,39 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.5.x +PHP_VER=85 + + +Install_php() +{ +#------------------------ install start ------------------------------------# +yum install -y php85 php85-php-fpm +if [ "$?" == "0" ];then + mkdir -p $serverPath/php-yum/${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + yum remove -y php85 php85-php-fpm php85-* + rm -rf $serverPath/php-yum/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php-yum/versions/common.sh b/plugins/php-yum/versions/common.sh new file mode 100644 index 000000000..7eb72bfd8 --- /dev/null +++ b/plugins/php-yum/versions/common.sh @@ -0,0 +1,69 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +version=$1 +action=$2 +extName=$3 + +# echo $1,$2,$3 + +# echo $curPath +# echo $rootPath +# echo $serverPath + +FILE=${curPath}/${version}/${extName}.sh +FILE_COMMON=${curPath}/common/${extName}.sh +# yum install -y php81-php-yar +# yum install -y php74-php-pecl-mysql + + +if [ "$action" == 'install' ];then + + if [ -f $FILE ];then + bash ${curPath}/${version}/${extName}.sh install + elif [ -f $FILE_COMMON ];then + bash ${FILE_COMMON} install ${version} + else + yum install -y php${version}-php-${extName} + yum install -y php${version}-php-pecl-${extName} + fi + + # if [ "${extName}" == "mysql" ];then + # yum install -y php74-php-pecl-mysql + # fi +fi + +# yum remove -y php81-php-yar +if [ "$action" == 'uninstall' ];then + + if [ -f $FILE ];then + bash ${curPath}/${version}/${extName}.sh uninstall + elif [ -f $FILE_COMMON ];then + bash ${FILE_COMMON} uninstall ${version} + else + yum remove -y php${version}-php-${extName} + yum remove -y php${version}-php-pecl-${extName} + fi +fi + +echo "yum install -y php${version}-php-${extName}" +echo "yum install -y php${version}-php-pecl-${extName}" +echo "-----------------------------------------------" +echo "yum remove -y php${version}-php-${extName}" +echo "yum remove -y php${version}-php-pecl-${extName}" + + +echo "systemctl restart php${version}-php-fpm" +php_status=`systemctl status php${version}-php-fpm | grep inactive` +echo "php_status:${php_status}" +if [ "$php_status" == "" ];then + systemctl restart php${version}-php-fpm +fi + diff --git a/plugins/php-yum/versions/common/bak_brotli.sh b/plugins/php-yum/versions/common/bak_brotli.sh new file mode 100755 index 000000000..a23a344b0 --- /dev/null +++ b/plugins/php-yum/versions/common/bak_brotli.sh @@ -0,0 +1,89 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=brotli +LIBV=0.15.2 + +SORT_LIBNAME="10-${LIBNAME}" +extVer=`bash $curPath/lib.sh $version` +extFile=/opt/remi/php${version}/root/usr/lib64/php +extSoFile=$extFile/modules/${LIBNAME}.so +cfgDir=/etc/opt/remi/php${version}/php.d +extIni=${cfgDir}/10-${LIBNAME}.ini + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat /etc/php/${version}/fpm/conf.d/* | grep -v '^;' |tr -s '\n' |grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + /opt/remi/php${version}/root/usr/bin/phpize + ./configure --with-php-config=/opt/remi/php${version}/root/usr/bin/ + make && make install && make clean + + fi + echo "$extFile checking ..." + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + cho "" >> $extIni + echo "[${LIBNAME}]" >> $extIni + echo "extension=${LIBNAME}.so" >> $extIni + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ -f $extIni ];then + rm -rf $extIni + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-yum/versions/common/ioncube.sh b/plugins/php-yum/versions/common/ioncube.sh new file mode 100755 index 000000000..feb87ab73 --- /dev/null +++ b/plugins/php-yum/versions/common/ioncube.sh @@ -0,0 +1,94 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=ioncube +LIBV=0 + +if [ `echo "$version > 82"|bc` -eq 1 ];then + echo "I won't support it" + exit 0 +fi + +SORT_LIBNAME="10-${LIBNAME}" +extVer=`bash $curPath/lib.sh $version` +extFile=/opt/remi/php${version}/root/usr/lib64/php +extSoFile=$extFile/modules/${LIBNAME}.so +cfgDir=/etc/opt/remi/php${version}/php.d +extIni=${cfgDir}/10-${LIBNAME}.ini + +echo $extSoFile +echo $extIni + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + + +min_ver=${version:0:1}.${version:1:2} + +Install_lib() +{ + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -f $php_lib/ioncube_loaders_lin.tar.gz ];then + wget -O $php_lib/ioncube_loaders_lin.tar.gz https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_x86-64.tar.gz + cd $php_lib && tar -zxvf ioncube_loaders_lin.tar.gz + fi + cd $php_lib/ioncube + + cp -rf $php_lib/ioncube/ioncube_loader_lin_${min_ver}.so $extSoFile + + fi + + echo "$extSoFile checking ..." + if [ ! -f "$extSoFile" ];then + echo "ERROR!" + return; + fi + + echo "" >> $extIni + echo "[${LIBNAME}]" >> $extIni + echo "zend_extension=${LIBNAME}.so" >> $extIni + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + + if [ -f $extIni ];then + rm -rf $extIni + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-yum/versions/common/opcache.sh b/plugins/php-yum/versions/common/opcache.sh new file mode 100755 index 000000000..873253b79 --- /dev/null +++ b/plugins/php-yum/versions/common/opcache.sh @@ -0,0 +1,60 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=opcache + +cfgDir=/etc/opt/remi + +OP_BL=${serverPath}/server/php-yum/opcache-blacklist.txt +if [ ! -f $OP_BL ];then + touch $OP_BL +fi +ext_dir=${cfgDir}/php${version}/php.d +ext_file=${ext_dir}/10-opcache.ini + +echo $ext_file + +if [ "$actionType" == 'install' ];then + yum install -y php${version}-php-${LIBNAME} + echo "ls ${cfgDir}/php${version}/php.d | grep "${LIBNAME}.ini"| cut -d \ -f 1" + find_opcache=`ls ${cfgDir}/php${version}/php.d | grep "${LIBNAME}.ini"| cut -d \ -f 1` + echo $find_opcache + if [ "$find_opcache" != "" ];then + ext_file=${ext_dir}/${find_opcache} + fi + echo $ext_file + echo "zend_extension=${LIBNAME}" >> $ext_file + echo "opcache.enable=1" >> $ext_file + echo "opcache.memory_consumption=128" >> $ext_file + echo "opcache.interned_strings_buffer=8" >> $ext_file + echo "opcache.max_accelerated_files=4000" >> $ext_file + echo "opcache.revalidate_freq=60" >> $ext_file + echo "opcache.fast_shutdown=1" >> $ext_file + echo "opcache.enable_cli=1" >> $ext_file + echo "opcache.jit=1205" >> $ext_file + echo "opcache.jit_buffer_size=64M" >> $ext_file + echo "opcache.save_comments=0" >> $ext_file + echo "opcache.blacklist_filename=${OP_BL}" >> $ext_file +elif [ "$actionType" == 'uninstall' ];then + if [ -f ${ext_dir}/10-opcache.ini.rpmsave ];then + ext_file=${ext_dir}/10-opcache.ini.rpmsave + fi + + # yum remove -y php83-php-opcache + yum remove -y php${version}-php-${LIBNAME} + rm -rf $ext_file + echo 'cannot uninstall' +fi \ No newline at end of file diff --git a/plugins/php-yum/versions/common/sg11.sh b/plugins/php-yum/versions/common/sg11.sh new file mode 100644 index 000000000..afe03f329 --- /dev/null +++ b/plugins/php-yum/versions/common/sg11.sh @@ -0,0 +1,122 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +# https://www.sourceguardian.com/loaders.html + +# support 52-83 + +LIBNAME=sg11 +LIBV=0 + +sysName=`uname` +actionType=$1 +version=$2 +SG_VER=${version:0:1}.${version:1:2} + + +SORT_LIBNAME="10-${LIBNAME}" +extVer=`bash $curPath/lib.sh $version` +extFile=/opt/remi/php${version}/root/usr/lib64/php +extSoFile=$extFile/modules/${LIBNAME}.so +cfgDir=/etc/opt/remi/php${version}/php.d +extIni=${cfgDir}/10-${LIBNAME}.ini + + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + bash ${rootPath}/scripts/getos.sh + OSNAME=`cat ${rootPath}/data/osname.pl` + if [ "$OSNAME" == 'macos' ];then + VERSION_ID=none + else + VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + fi + + echo "${OSNAME}:${VERSION_ID}" + + DEFAULT_OSNAME=linux-x86_64 + SUFFIX_NAME=lin + if [ "$OSNAME" == 'macos' ];then + DEFAULT_OSNAME=macosx + SUFFIX_NAME=dar + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + + mkdir -p $php_lib + mkdir -p $php_lib/sg11 + + if [ ! -f $php_lib/sg11_loaders.tar.bz2 ];then + curl -sSLo $php_lib/sg11_loaders.tar.bz2 https://www.sourceguardian.com/loaders/download/loaders.tar.bz2 + echo "cd $php_lib && tar -jxvf $php_lib/sg11_loaders.tar.bz2 -C $php_lib/sg11" + cd $php_lib && tar -jxvf $php_lib/sg11_loaders.tar.bz2 -C $php_lib/sg11 + fi + + + if [ ! -d $php_lib/sg11/macosx ];then + cd $php_lib && tar -jxvf $php_lib/sg11_loaders.tar.bz2 -C $php_lib/sg11 + fi + cd $php_lib/sg11 + + + if [ -f $php_lib/sg11/${DEFAULT_OSNAME}/ixed.${SG_VER}.${SUFFIX_NAME} ];then + cp -rf $php_lib/sg11/${DEFAULT_OSNAME}/ixed.${SG_VER}.${SUFFIX_NAME} $extSoFile + else + echo 'Not supported temporarily' + exit + fi + + if [ "$OSNAME" == 'macos' ];then + xattr -c * $extSoFile + fi + fi + + if [ ! -f "$extSoFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $extIni + echo "[${LIBNAME}]" >> $extIni + echo "extension=${LIBNAME}.so" >> $extIni + + systemctl restart php${version}-fpm + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ -f $extIni ];then + rm -rf $extIni + fi + + systemctl restart php${version}-fpm + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php-yum/versions/lib.sh b/plugins/php-yum/versions/lib.sh new file mode 100644 index 000000000..91a8e8d27 --- /dev/null +++ b/plugins/php-yum/versions/lib.sh @@ -0,0 +1,61 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +version=$1 +action=$2 + + +php_fpm_service_file=/lib/systemd/system/php${version}-php-fpm.service +if [ -f /usr/lib/systemd/system/php${version}-php-fpm.service ];then + php_fpm_service_file=/lib/systemd/system/php${version}-php-fpm.service +fi + +php_status=`systemctl status php${version}-php-fpm | grep inactive` +if [ "$php_status" != "" ];then + systemctl ${action} php${version}-php-fpm +fi + +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +version=$1 + +if [ "$version" == '5.6' ];then + echo '20131226' +elif [[ "$version" == '7.0' ]]; then + echo '20151012' +elif [[ "$version" == '7.1' ]]; then + echo '20160303' +elif [[ "$version" == '7.2' ]]; then + echo '20170718' +elif [[ "$version" == '7.3' ]]; then + echo '20180731' +elif [[ "$version" == '7.4' ]]; then + echo '20190902' +elif [[ "$version" == '8.0' ]]; then + echo '20200930' +elif [[ "$version" == '8.1' ]]; then + echo '20210902' +elif [[ "$version" == '8.2' ]]; then + echo '20220829' +elif [[ "$version" == '8.3' ]]; then + echo '20230831' +elif [[ "$version" == '8.4' ]]; then + echo '20240924' +elif [[ "$version" == '8.5' ]]; then + echo '20250925' +fi \ No newline at end of file diff --git a/plugins/php-yum/versions/phplib.conf b/plugins/php-yum/versions/phplib.conf new file mode 100755 index 000000000..eec09e6cf --- /dev/null +++ b/plugins/php-yum/versions/phplib.conf @@ -0,0 +1,802 @@ +[ + { + "name": "sg11", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83" + ], + "type": "脚本解密", + "msg": "用于解密SG11加密脚本!", + "shell": "sg11.sh", + "check": "sg11.so" + }, + { + "name": "ionCube", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "81", + "82", + "83" + ], + "type": "脚本解密", + "msg": "用于解密ionCube Encoder加密脚本!", + "shell": "ioncube.sh", + "check": "ioncube" + }, + { + "name": "pdo", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "数据库访问抽象模块!", + "shell": "pdo.sh", + "check": "pdo" + }, + { + "name": "mysqlnd", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "用于使用MySQL数据库的模块!", + "shell": "mysqlnd.sh", + "check": "mysqlnd" + }, + { + "name": "mysql", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "用于使用MySQL数据库的模块!", + "shell": "mysql.sh", + "check": "mysql" + }, + { + "name": "sqlite3", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "用于使用sqlite3数据库的模块!", + "shell": "sqlite3.sh", + "check": "sqlite3" + }, + { + "name": "oci8", + "versions": [], + "type": "数据库", + "msg": "用于使用OCI8数据库的模块!", + "shell": "oci8.sh", + "check": "oci8" + }, + { + "name": "odbc", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "数据库", + "msg": "用于使用ODBC数据库的模块!", + "shell": "odbc.sh", + "check": "odbc" + }, + { + "name": "ZendGuardLoader", + "versions": [ + ], + "type": "脚本解密", + "msg": "用于解密ZendGuard加密脚本!", + "shell": "zend_guard_loader.sh", + "check": "ZendGuardLoader" + }, + { + "name": "ZendOptimizer", + "versions": [ + "52" + ], + "type": "脚本解密", + "msg": "用于解密ZendOptimizer加密脚本!", + "shell": "zend_optimizer.sh", + "check": "ZendOptimizer" + }, + { + "name": "opcache", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "用于加速PHP脚本!", + "shell": "opcache.sh", + "check": "opcache" + }, + { + "name": "mcrypt", + "versions": [ + "71", + "72", + "73", + "74", + "80", + "81" + ], + "type": "通用扩展", + "msg": "加密软件!", + "shell": "mcrypt.sh", + "check": "mcrypt" + }, + { + "name": "ldap", + "versions": [ + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "轻型目录访问协议", + "shell": "ldap.sh", + "check": "ldap" + }, + { + "name": "gmp", + "versions": [ + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "数学扩展", + "shell": "gmp.sh", + "check": "gmp" + }, + { + "name": "brotli", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "压缩", + "msg": "压缩算法", + "shell": "brotli.sh", + "check": "brotli.so" + }, + { + "name": "bcmath", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "高精度计算!", + "shell": "bcmath.sh", + "check": "bcmath.so" + }, + { + "name": "fileinfo", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于FILE!", + "shell": "fileinfo.sh", + "check": "fileinfo" + }, + { + "name": "exif", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于图像文件格式!", + "shell": "exif.sh", + "check": "exif" + }, + { + "name": "igbinary", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "序列化扩展!", + "shell": "igbinary.sh", + "check": "igbinary.so" + }, + { + "name": "xml", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于xml解析!", + "shell": "xml.sh", + "check": "xml" + }, + { + "name": "curl", + "versions": [ + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "通用CURL库!", + "shell": "curl.sh", + "check": "curl" + }, + { + "name": "gd", + "versions": [ + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "通用GD库!", + "shell": "gd.sh", + "check": "gd" + }, + { + "name": "intl", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "提供国际化支持", + "shell": "intl.sh", + "check": "intl" + }, + { + "name": "memcache", + "versions": [ + "74", + "80", + "81" + ], + "type": "缓存器", + "msg": "强大的内容缓存器,不支持集群", + "shell": "memcache.sh", + "check": "memcache" + }, + { + "name": "memcached", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "强大的内容缓存器,支持集群", + "shell": "memcached.sh", + "check": "memcached" + }, + { + "name": "redis", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "更强大的内容缓存器,支持集群", + "shell": "redis.sh", + "check": "redis" + }, + { + "name": "apcu", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "脚本缓存器", + "shell": "apcu.sh", + "check": "apcu" + }, + { + "name": "imagick", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "比GD更强大的图形库", + "shell": "imagick.sh", + "check": "imagick" + }, + { + "name": "xdebug", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "调试器", + "msg": "不多说,不了解的不要安装", + "shell": "xdebug.sh", + "check": "xdebug" + }, + { + "name": "xhprof", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "性能分析", + "msg": "不多说,不了解的不要安装!", + "shell": "xhprof.sh", + "check": "xhprof" + }, + { + "name": "Swoole", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "异步、并行、高性能网络通信引擎", + "shell": "swoole.sh", + "check": "swoole" + }, + { + "name": "eAccelerator", + "versions": [ + "52", + "53" + ], + "type": "缓存器", + "msg": "内容缓存器", + "shell": "eaccelerator.sh", + "check": "eaccelerator" + }, + { + "name": "yaf", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83" + ], + "type": "框架", + "msg": "Yaf是一个C语言编写的PHP框架", + "shell": "yaf.sh", + "check": "yaf" + }, + { + "name": "phalcon", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "框架", + "msg": "PHP框架", + "shell": "phalcon.sh", + "check": "phalcon" + }, + { + "name": "yar", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74" + ], + "type": "框架", + "msg": "Yar是一个RPC框架", + "shell": "yar.sh", + "check": "yar" + }, + { + "name": "mongo", + "versions": [ + "56" + ], + "type": "通用扩展", + "msg": "Mongodb数据库连接驱动", + "shell": "mongo.sh", + "check": "mongo" + }, + { + "name": "mongodb", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "Mongodb数据库连接驱动", + "shell": "mongodb.sh", + "check": "mongodb" + }, + { + "name": "yac", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81" + ], + "type": "缓存器", + "msg": "高性能无锁共享内存Cache", + "shell": "yac.sh", + "check": "yac" + }, + { + "name": "solr", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81" + ], + "type": "大数据", + "msg": "SOLR全文搜索服务", + "shell": "solr.sh", + "check": "solr" + }, + { + "name": "seaslog", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "日志", + "msg": "SeasLog高性能日志记录", + "shell": "seaslog.sh", + "check": "seaslog" + }, + { + "name": "mbstring", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于需要多字节字符串处理的模块", + "shell": "mbstring.sh", + "check": "mbstring" + }, + { + "name": "zip", + "versions": [ + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "压缩", + "msg": "压缩组件", + "shell": "zip.sh", + "check": "zip" + }, + { + "name": "zstd", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "压缩", + "msg": "压缩组件", + "shell": "zstd.sh", + "check": "zstd" + } +] \ No newline at end of file diff --git a/plugins/php/conf/app_start.php b/plugins/php/conf/app_start.php new file mode 100644 index 000000000..c03b949d9 --- /dev/null +++ b/plugins/php/conf/app_start.php @@ -0,0 +1,56 @@ +save_run($xhprof_data, 'xhprof_foo'); + + $profiler_url = sprintf('http://{$LOCAL_IP}:5858/index.php?run=%s&source=xhprof_foo', $run_id); + // echo ""; + + $style_css = 'position:fixed;bottom: 0px;right: 0px;z-index: 100000000;color: red;background-color: black;padding: 0px;margin: 0px;'; + echo 'XHProf分析结果'; + +} + +if (extension_loaded('xhprof') + && isset($_GET[XHProf_Name]) && $_GET[XHProf_Name] == 'ok' && + (! in_array($_SERVER['SCRIPT_NAME'], array('/xhprof_html/callgraph.php','/xhprof_html/index.php')))) { + app_xhprof_start(); + register_shutdown_function('app_xhprof_end'); + include_once $_SERVER['SCRIPT_FILENAME']; + exit; +} + +?> diff --git a/plugins/php/conf/backup.conf b/plugins/php/conf/backup.conf new file mode 100644 index 000000000..eaa1fd87c --- /dev/null +++ b/plugins/php/conf/backup.conf @@ -0,0 +1,19 @@ +[backup] +user = {$PHP_USER} +group = {$PHP_GROUP} + +listen = /tmp/php-cgi-{$PHP_VERSION}.backup.sock +listen.owner = {$PHP_USER} +listen.group = {$PHP_GROUP} +listen.backlog = 4096 + +pm = dynamic +pm.max_children = 30 +pm.start_servers = 5 +pm.min_spare_servers = 5 +pm.max_spare_servers = 20 +pm.status_path = /phpfpm_status_{$PHP_VERSION}_backup +pm.max_requests = 1000 +request_terminate_timeout = 30 +request_slowlog_timeout = 10 +slowlog = {$SERVER_PATH}/php/{$PHP_VERSION}/var/log/www-slow.log \ No newline at end of file diff --git a/plugins/php/conf/enable-php-upstream.conf b/plugins/php/conf/enable-php-upstream.conf new file mode 100644 index 000000000..80d75206c --- /dev/null +++ b/plugins/php/conf/enable-php-upstream.conf @@ -0,0 +1,4 @@ +upstream MW-UPSTREAM-PHP{$PHP_VERSION} { + server unix:/tmp/php-cgi-{$PHP_VERSION}.sock; + server unix:/tmp/php-cgi-{$PHP_VERSION}.backup.sock; +} \ No newline at end of file diff --git a/plugins/php/conf/enable-php.bak.conf b/plugins/php/conf/enable-php.bak.conf new file mode 100644 index 000000000..2046ac2cb --- /dev/null +++ b/plugins/php/conf/enable-php.bak.conf @@ -0,0 +1,9 @@ +set $PHP_ENV 1; +location ~ [^/]\.php(/|$) +{ + try_files $uri =404; + fastcgi_pass unix:/tmp/php-cgi-{$PHP_VERSION}.sock; + fastcgi_index index.php; + include fastcgi.conf; + include {$SERVER_PATH}/web_conf/php/pathinfo.conf; +} \ No newline at end of file diff --git a/plugins/php/conf/enable-php.conf b/plugins/php/conf/enable-php.conf new file mode 100644 index 000000000..230b25418 --- /dev/null +++ b/plugins/php/conf/enable-php.conf @@ -0,0 +1,10 @@ +set $PHP_ENV 1; +location ~ [^/]\.php(/|$) +{ + try_files $uri =404; + #fastcgi_pass unix:/tmp/php-cgi-{$PHP_VERSION}.sock; + fastcgi_pass MW-UPSTREAM-PHP{$PHP_VERSION}; + fastcgi_index index.php; + include fastcgi.conf; + include {$SERVER_PATH}/web_conf/php/pathinfo.conf; +} \ No newline at end of file diff --git a/plugins/php/conf/pathinfo.conf b/plugins/php/conf/pathinfo.conf new file mode 100644 index 000000000..fc5ad5219 --- /dev/null +++ b/plugins/php/conf/pathinfo.conf @@ -0,0 +1,11 @@ +set $real_script_name $fastcgi_script_name; +if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") { + set $real_script_name $1; + set $path_info $2; + } +fastcgi_param SCRIPT_FILENAME $document_root$real_script_name; +fastcgi_param SCRIPT_NAME $real_script_name; +fastcgi_param PATH_INFO $path_info; + +#修复http3不传HOST问题 +fastcgi_param HTTP_HOST $host; \ No newline at end of file diff --git a/plugins/php/conf/php-fpm-52.conf b/plugins/php/conf/php-fpm-52.conf new file mode 100644 index 000000000..444c2c816 --- /dev/null +++ b/plugins/php/conf/php-fpm-52.conf @@ -0,0 +1,156 @@ + + + + All relative paths in this config are relative to php's install prefix + +
                                + + Pid file + /www/server/php/52/var/run/php-fpm.pid + + Error log file + /www/server/php/52/var/log/php-fpm.log + + Log level + notice + + When this amount of php processes exited with SIGSEGV or SIGBUS ... + 10 + + ... in a less than this interval of time, a graceful restart will be initiated. + Useful to work around accidental curruptions in accelerator's shared memory. + 1m + + Time limit on waiting child's reaction on signals from master + 5s + + Set to 'no' to debug fpm + yes + +
                                + + + +
                                + + Name of pool. Used in logs and stats. + default + + Address to accept fastcgi requests on. + Valid syntax is 'ip.ad.re.ss:port' or just 'port' or '/path/to/unix/socket' + /tmp/php-cgi-52.sock + + + + Set listen(2) backlog + -1 + + Set permissions for unix socket, if one used. + In Linux read/write permissions must be set in order to allow connections from web server. + Many BSD-derrived systems allow connections regardless of permissions. + www + www + 0666 + + + Additional php.ini defines, specific to this pool of workers. + + + + + + Unix user of processes + www + + Unix group of processes + www + + Process manager settings + + + Sets style of controling worker process count. + Valid values are 'static' and 'apache-like' + static + + Sets the limit on the number of simultaneous requests that will be served. + Equivalent to Apache MaxClients directive. + Equivalent to PHP_FCGI_CHILDREN environment in original php.fcgi + Used with any pm_style. + 5 + + Settings group for 'apache-like' pm style + + + Sets the number of server processes created on startup. + Used only when 'apache-like' pm_style is selected + 20 + + Sets the desired minimum number of idle server processes. + Used only when 'apache-like' pm_style is selected + 5 + + Sets the desired maximum number of idle server processes. + Used only when 'apache-like' pm_style is selected + 35 + + + + + + The timeout (in seconds) for serving a single request after which the worker process will be terminated + Should be used when 'max_execution_time' ini option does not stop script execution for some reason + '0s' means 'off' + 0s + + The timeout (in seconds) for serving of single request after which a php backtrace will be dumped to slow.log file + '0s' means 'off' + 0s + + The log file for slow requests + var/log/www-slow.log + + Set open file desc rlimit + 1024 + + Set max core size rlimit + 0 + + Chroot to this directory at the start, absolute path + + + Chdir to this directory at the start, absolute path + + + Redirect workers' stdout and stderr into main error log. + If not set, they will be redirected to /dev/null, according to FastCGI specs + yes + + How much requests each process should execute before respawn. + Useful to work around memory leaks in 3rd party libraries. + For endless request processing please specify 0 + Equivalent to PHP_FCGI_MAX_REQUESTS + 500 + + Comma separated list of ipv4 addresses of FastCGI clients that allowed to connect. + Equivalent to FCGI_WEB_SERVER_ADDRS environment in original php.fcgi (5.2.2+) + Makes sense only with AF_INET listening socket. + 127.0.0.1 + + Pass environment variables like LD_LIBRARY_PATH + All $VARIABLEs are taken from current environment + + $HOSTNAME + /usr/local/bin:/usr/bin:/bin + /tmp + /tmp + /tmp + $OSTYPE + $MACHTYPE + 2 + + +
                                + +
                                + +
                                diff --git a/plugins/php/conf/php-fpm.conf b/plugins/php/conf/php-fpm.conf new file mode 100644 index 000000000..004ea8e87 --- /dev/null +++ b/plugins/php/conf/php-fpm.conf @@ -0,0 +1,6 @@ + +[global] +pid = run/php-fpm.pid +error_log = log/php-fpm.log +include={$SERVER_PATH}/php/{$PHP_VERSION}/etc/php-fpm.d/*.conf +php_value[auto_prepend_file]={$SERVER_PATH}/php/app_start.php \ No newline at end of file diff --git a/plugins/php/conf/php5.ini b/plugins/php/conf/php5.ini new file mode 100644 index 000000000..4a0fa3bd1 --- /dev/null +++ b/plugins/php/conf/php5.ini @@ -0,0 +1,204 @@ +[PHP] +engine = On +short_open_tag = On +asp_tags = Off +precision = 14 +output_buffering = 4096 +zlib.output_compression = Off +implicit_flush = Off +unserialize_callback_func = +serialize_precision = 17 +zend.enable_gc = On +expose_php = Off +max_execution_time = 30 +max_input_time = 60 +max_input_vars = 1000 +memory_limit = 128M +error_reporting = E_ALL & ~E_NOTICE +display_errors = On +display_startup_errors = On +log_errors = On +log_errors_max_len = 1024 +ignore_repeated_errors = Off +ignore_repeated_source = Off +report_memleaks = On +track_errors = On +html_errors = On +variables_order = "GPCS" +request_order = "GP" +register_argc_argv = Off +auto_globals_jit = On +post_max_size = 20M +auto_prepend_file = +auto_append_file = +default_mimetype = "text/html" +doc_root = +user_dir = +enable_dl = Off +always_populate_raw_post_data=-1 + + +file_uploads = On +upload_tmp_dir = "{$SERVER_PATH}/php/tmp/upload" +upload_max_filesize = 2M +max_file_uploads = 20 + +allow_url_fopen = On +allow_url_include = Off +default_socket_timeout = 60 + +disable_functions = exec,passthru,shell_exec,system,popen,show_source,fastcgi_finish_request + +[CLI Server] +cli_server.color = On + +[Date] +date.timezone = PRC + +[filter] +[iconv] +iconv.input_encoding = ISO-8859-1 +iconv.internal_encoding = ISO-8859-1 +iconv.output_encoding = ISO-8859-1 + + +[Pdo_mysql] +pdo_mysql.cache_size = 2000 +pdo_mysql.default_socket=/www/server/mysql/mysql.sock +#pdo_mysql.default_socket=/www/server/mariadb/mysql.sock +#pdo_mysql.default_socket=/www/server/mysql-community/mysql.sock + +[Phar] + +[mail function] +SMTP = localhost +smtp_port = 25 +sendmail_path = /usr/sbin/sendmail -t -i +mail.add_x_header = On + + +[SQL] +sql.safe_mode = Off + +[ODBC] +odbc.allow_persistent = On +odbc.check_persistent = On +odbc.max_persistent = -1 +odbc.max_links = -1 +odbc.defaultlrl = 4096 +odbc.defaultbinmode = 1 + + +[Interbase] +ibase.allow_persistent = 1 +ibase.max_persistent = -1 +ibase.max_links = -1 +ibase.timestampformat = "%Y-%m-%d %H:%M:%S" +ibase.dateformat = "%Y-%m-%d" +ibase.timeformat = "%H:%M:%S" + +[MySQL] +mysql.allow_local_infile = On +mysql.allow_persistent = On +mysql.cache_size = 2000 +mysql.max_persistent = -1 +mysql.max_links = -1 +mysql.default_port = +mysql.default_host = +mysql.default_user = +mysql.default_password = +mysql.connect_timeout = 60 +mysql.trace_mode = Off +#mysql.default_socket = +mysql.default_socket=/www/server/mysql/mysql.sock +#mysql.default_socket=/www/server/mariadb/mysql.sock +#mysql.default_socket=/www/server/mysql-community/mysql.sock + +[MySQLi] +mysqli.max_persistent = -1 +mysqli.allow_persistent = On +mysqli.max_links = -1 +mysqli.cache_size = 2000 +mysqli.default_port = 3306 +mysqli.default_host = +mysqli.default_user = +mysqli.default_pw = +mysqli.reconnect = Off +#mysql.default_socket = +mysqli.default_socket=/www/server/mysql/mysql.sock +#mysqli.default_socket=/www/server/mariadb/mysql.sock +#mysqli.default_socket=/www/server/mysql-community/mysql.sock + +[mysqlnd] +mysqlnd.collect_statistics = On +mysqlnd.collect_memory_statistics = On + +[OCI8] + + +[PostgreSQL] +pgsql.allow_persistent = On +pgsql.auto_reset_persistent = Off +pgsql.max_persistent = -1 +pgsql.max_links = -1 +pgsql.ignore_notice = 0 +pgsql.log_notice = 0 + +[Sybase-CT] +sybct.allow_persistent = On +sybct.max_persistent = -1 +sybct.max_links = -1 +sybct.min_server_severity = 10 +sybct.min_client_severity = 10 + +[curl] +curl.cainfo = {$SSL_CRT} + +[browscap] + +[Session] +session.save_handler = files +session.save_path = "{$SERVER_PATH}/php/tmp/session" +session.use_strict_mode = 0 +session.use_cookies = 1 +session.use_only_cookies = 1 +session.name = PHPSESSID +session.auto_start = 0 +session.cookie_lifetime = 0 +session.cookie_path = / +session.cookie_domain = +session.cookie_httponly = +session.serialize_handler = php +session.gc_probability = 1 +session.gc_divisor = 1000 +session.gc_maxlifetime = 1440 +session.referer_check = +session.cache_limiter = nocache +session.cache_expire = 180 +session.use_trans_sid = 0 +session.hash_function = 0 +session.hash_bits_per_character = 5 +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + + +[MSSQL] +mssql.allow_persistent = On +mssql.max_persistent = -1 +mssql.max_links = -1 +mssql.min_error_severity = 10 +mssql.min_message_severity = 10 +mssql.compatibility_mode = Off +mssql.secure_connection = Off + +[Tidy] +tidy.clean_output = Off + +[soap] +soap.wsdl_cache_enabled=1 +soap.wsdl_cache_dir="{$SERVER_PATH}/php/tmp" +soap.wsdl_cache_ttl=86400 +soap.wsdl_cache_limit = 5 + + +[ldap] +ldap.max_links = -1 diff --git a/plugins/php/conf/php7.ini b/plugins/php/conf/php7.ini new file mode 100644 index 000000000..576936e02 --- /dev/null +++ b/plugins/php/conf/php7.ini @@ -0,0 +1,206 @@ +[PHP] +engine = On +short_open_tag = On +asp_tags = Off +precision = 14 +output_buffering = 4096 +zlib.output_compression = Off +implicit_flush = Off +unserialize_callback_func = +serialize_precision = 17 +zend.enable_gc = On +expose_php = Off +max_execution_time = 30 +max_input_time = 60 +max_input_vars = 1000 +memory_limit = 128M +error_reporting = E_ALL & ~E_NOTICE +display_errors = On +display_startup_errors = On +log_errors = On +log_errors_max_len = 1024 +ignore_repeated_errors = Off +ignore_repeated_source = Off +report_memleaks = On +html_errors = On +variables_order = "GPCS" +request_order = "GP" +register_argc_argv = Off +auto_globals_jit = On +post_max_size = 20M +auto_prepend_file = +auto_append_file = +default_mimetype = "text/html" +doc_root = +user_dir = +enable_dl = Off +always_populate_raw_post_data=-1 + + +file_uploads = On +upload_tmp_dir = "{$SERVER_PATH}/php/tmp/upload" +upload_max_filesize = 2M +max_file_uploads = 20 + +allow_url_fopen = On +allow_url_include = Off +default_socket_timeout = 60 + +disable_functions = exec,passthru,shell_exec,system,popen,show_source,fastcgi_finish_request + +[CLI Server] +cli_server.color = On + +[Date] +date.timezone = PRC + +[filter] +[iconv] +iconv.input_encoding = ISO-8859-1 +iconv.internal_encoding = ISO-8859-1 +iconv.output_encoding = ISO-8859-1 + + +[Pdo_mysql] +pdo_mysql.cache_size = 2000 +#mysql.default_socket = +pdo_mysql.default_socket=/www/server/mysql/mysql.sock +#pdo_mysql.default_socket=/www/server/mariadb/mysql.sock +#pdo_mysql.default_socket=/www/server/mysql-community/mysql.sock + + +[mail function] +SMTP = localhost +smtp_port = 25 +sendmail_path = /usr/sbin/sendmail -t -i +mail.add_x_header = On + + +[SQL] +sql.safe_mode = Off + +[ODBC] +odbc.allow_persistent = On +odbc.check_persistent = On +odbc.max_persistent = -1 +odbc.max_links = -1 +odbc.defaultlrl = 4096 +odbc.defaultbinmode = 1 + + +[Interbase] +ibase.allow_persistent = 1 +ibase.max_persistent = -1 +ibase.max_links = -1 +ibase.timestampformat = "%Y-%m-%d %H:%M:%S" +ibase.dateformat = "%Y-%m-%d" +ibase.timeformat = "%H:%M:%S" + +[MySQL] +mysql.allow_local_infile = On +mysql.allow_persistent = On +mysql.cache_size = 2000 +mysql.max_persistent = -1 +mysql.max_links = -1 +mysql.default_port = +mysql.default_host = +mysql.default_user = +mysql.default_password = +mysql.connect_timeout = 60 +mysql.trace_mode = Off +#mysql.default_socket = +mysql.default_socket=/www/server/mysql/mysql.sock +#mysql.default_socket=/www/server/mariadb/mysql.sock +#mysql.default_socket=/www/server/mysql-community/mysql.sock + +[MySQLi] +mysqli.max_persistent = -1 +mysqli.allow_persistent = On +mysqli.max_links = -1 +mysqli.cache_size = 2000 +mysqli.default_port = 3306 +mysqli.default_host = +mysqli.default_user = +mysqli.default_pw = +mysqli.reconnect = Off +#mysqli.default_socket = +mysqli.default_socket=/www/server/mysql/mysql.sock +#mysqli.default_socket=/www/server/mariadb/mysql.sock +#mysqli.default_socket=/www/server/mysql-community/mysql.sock + +[mysqlnd] +mysqlnd.collect_statistics = On +mysqlnd.collect_memory_statistics = On + + +[PostgreSQL] +pgsql.allow_persistent = On +pgsql.auto_reset_persistent = Off +pgsql.max_persistent = -1 +pgsql.max_links = -1 +pgsql.ignore_notice = 0 +pgsql.log_notice = 0 + +[Sybase-CT] +sybct.allow_persistent = On +sybct.max_persistent = -1 +sybct.max_links = -1 +sybct.min_server_severity = 10 +sybct.min_client_severity = 10 + +[curl] +curl.cainfo = {$SSL_CRT} + +[bcmath] +bcmath.scale = 0 + +[browscap] + +[Session] +session.save_handler = files +session.save_path = "{$SERVER_PATH}/php/tmp/session" +session.use_strict_mode = 0 +session.use_cookies = 1 +session.use_only_cookies = 1 +session.name = PHPSESSID +session.auto_start = 0 +session.cookie_lifetime = 0 +session.cookie_path = / +session.cookie_domain = +session.cookie_httponly = +session.serialize_handler = php +session.gc_probability = 1 +session.gc_divisor = 1000 +session.gc_maxlifetime = 1440 +session.referer_check = +session.cache_limiter = nocache +session.cache_expire = 180 +session.use_trans_sid = 0 +session.hash_function = 0 +session.hash_bits_per_character = 5 +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + + +[MSSQL] +mssql.allow_persistent = On +mssql.max_persistent = -1 +mssql.max_links = -1 +mssql.min_error_severity = 10 +mssql.min_message_severity = 10 +mssql.compatibility_mode = Off +mssql.secure_connection = Off + + +[Tidy] +tidy.clean_output = Off + +[soap] +soap.wsdl_cache_enabled=1 +soap.wsdl_cache_dir="{$SERVER_PATH}/php/tmp" +soap.wsdl_cache_ttl=86400 +soap.wsdl_cache_limit = 5 + +[sysvshm] + +[ldap] +ldap.max_links = -1 diff --git a/plugins/php/conf/php8.ini b/plugins/php/conf/php8.ini new file mode 100644 index 000000000..96381c231 --- /dev/null +++ b/plugins/php/conf/php8.ini @@ -0,0 +1,208 @@ +[PHP] +engine = On +short_open_tag = On +asp_tags = Off +precision = 14 +output_buffering = 4096 +zlib.output_compression = Off +implicit_flush = Off +unserialize_callback_func = +serialize_precision = 17 +zend.enable_gc = On +expose_php = Off +max_execution_time = 30 +max_input_time = 60 +max_input_vars = 1000 +memory_limit = 128M +error_reporting = E_ALL & ~E_NOTICE +display_errors = On +display_startup_errors = On +log_errors = On +log_errors_max_len = 1024 +ignore_repeated_errors = Off +ignore_repeated_source = Off +report_memleaks = On +html_errors = On +variables_order = "GPCS" +request_order = "GP" +register_argc_argv = Off +auto_globals_jit = On +post_max_size = 20M +auto_prepend_file = +auto_append_file = +default_mimetype = "text/html" +doc_root = +user_dir = +enable_dl = Off +always_populate_raw_post_data=-1 + + +file_uploads = On +upload_tmp_dir = "{$SERVER_PATH}/php/tmp/upload" +upload_max_filesize = 2M +max_file_uploads = 20 + +allow_url_fopen = On +allow_url_include = Off +default_socket_timeout = 60 + +disable_functions = exec,passthru,shell_exec,system,popen,show_source,fastcgi_finish_request + +[CLI Server] +cli_server.color = On + +[Date] +date.timezone = PRC + +[filter] +[iconv] +iconv.input_encoding = ISO-8859-1 +iconv.internal_encoding = ISO-8859-1 +iconv.output_encoding = ISO-8859-1 + + +[Pdo_mysql] +pdo_mysql.cache_size = 2000 +#mysql.default_socket = +pdo_mysql.default_socket=/www/server/mysql/mysql.sock +#pdo_mysql.default_socket=/www/server/mariadb/mysql.sock +#pdo_mysql.default_socket=/www/server/mysql-community/mysql.sock + + + +[mail function] +SMTP = localhost +smtp_port = 25 +sendmail_path = /usr/sbin/sendmail -t -i +mail.add_x_header = On + + +[SQL] +sql.safe_mode = Off + +[ODBC] +odbc.allow_persistent = On +odbc.check_persistent = On +odbc.max_persistent = -1 +odbc.max_links = -1 +odbc.defaultlrl = 4096 +odbc.defaultbinmode = 1 + + +[Interbase] +ibase.allow_persistent = 1 +ibase.max_persistent = -1 +ibase.max_links = -1 +ibase.timestampformat = "%Y-%m-%d %H:%M:%S" +ibase.dateformat = "%Y-%m-%d" +ibase.timeformat = "%H:%M:%S" + +[MySQL] +mysql.allow_local_infile = On +mysql.allow_persistent = On +mysql.cache_size = 2000 +mysql.max_persistent = -1 +mysql.max_links = -1 +mysql.default_port = +mysql.default_host = +mysql.default_user = +mysql.default_password = +mysql.connect_timeout = 60 +mysql.trace_mode = Off +#mysql.default_socket = +mysql.default_socket=/www/server/mysql/mysql.sock +#mysql.default_socket=/www/server/mariadb/mysql.sock +#mysql.default_socket=/www/server/mysql-community/mysql.sock + + +[MySQLi] +mysqli.max_persistent = -1 +mysqli.allow_persistent = On +mysqli.max_links = -1 +mysqli.cache_size = 2000 +mysqli.default_port = 3306 +mysqli.default_host = +mysqli.default_user = +mysqli.default_pw = +mysqli.reconnect = Off +#mysqli.default_socket = +mysqli.default_socket=/www/server/mysql/mysql.sock +#mysqli.default_socket=/www/server/mariadb/mysql.sock +#mysqli.default_socket=/www/server/mysql-community/mysql.sock + +[mysqlnd] +mysqlnd.collect_statistics = On +mysqlnd.collect_memory_statistics = On + +[OCI8] + + +[PostgreSQL] +pgsql.allow_persistent = On +pgsql.auto_reset_persistent = Off +pgsql.max_persistent = -1 +pgsql.max_links = -1 +pgsql.ignore_notice = 0 +pgsql.log_notice = 0 + +[Sybase-CT] +sybct.allow_persistent = On +sybct.max_persistent = -1 +sybct.max_links = -1 +sybct.min_server_severity = 10 +sybct.min_client_severity = 10 + +[curl] +curl.cainfo = {$SSL_CRT} + +[bcmath] +bcmath.scale = 0 + +[browscap] + +[Session] +session.save_handler = files +session.save_path = "{$SERVER_PATH}/php/tmp/session" +session.use_strict_mode = 0 +session.use_cookies = 1 +session.use_only_cookies = 1 +session.name = PHPSESSID +session.auto_start = 0 +session.cookie_lifetime = 0 +session.cookie_path = / +session.cookie_domain = +session.cookie_httponly = +session.serialize_handler = php +session.gc_probability = 1 +session.gc_divisor = 1000 +session.gc_maxlifetime = 1440 +session.referer_check = +session.cache_limiter = nocache +session.cache_expire = 180 +session.use_trans_sid = 0 +session.hash_function = 0 +session.hash_bits_per_character = 5 +url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" + + +[MSSQL] +mssql.allow_persistent = On +mssql.max_persistent = -1 +mssql.max_links = -1 +mssql.min_error_severity = 10 +mssql.min_message_severity = 10 +mssql.compatibility_mode = Off +mssql.secure_connection = Off + +[Tidy] +tidy.clean_output = Off + +[soap] +soap.wsdl_cache_enabled=1 +soap.wsdl_cache_dir="{$SERVER_PATH}/php/tmp" +soap.wsdl_cache_ttl=86400 +soap.wsdl_cache_limit = 5 + + +[ldap] +ldap.max_links = -1 diff --git a/plugins/php/conf/phpfpm_status.conf b/plugins/php/conf/phpfpm_status.conf new file mode 100644 index 000000000..4da6f2816 --- /dev/null +++ b/plugins/php/conf/phpfpm_status.conf @@ -0,0 +1,5 @@ +location /phpfpm_status_{$PHP_VERSION} { + fastcgi_pass unix:/tmp/php-cgi-{$PHP_VERSION}.sock; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME \$fastcgi_script_name; +} \ No newline at end of file diff --git a/plugins/php/conf/phpinfo.conf b/plugins/php/conf/phpinfo.conf new file mode 100644 index 000000000..577a70ec0 --- /dev/null +++ b/plugins/php/conf/phpinfo.conf @@ -0,0 +1,4 @@ +location /{$PHP_VERSION} { + root {$ROOT_PATH}/phpinfo; + include {$SERVER_PATH}/web_conf/php/conf/enable-php-{$PHP_VERSION}.conf; +} \ No newline at end of file diff --git a/plugins/php/conf/www.conf b/plugins/php/conf/www.conf new file mode 100644 index 000000000..9a7b3fd77 --- /dev/null +++ b/plugins/php/conf/www.conf @@ -0,0 +1,19 @@ +[www] +user = {$PHP_USER} +group = {$PHP_GROUP} + +listen = /tmp/php-cgi-{$PHP_VERSION}.sock +listen.owner = {$PHP_USER} +listen.group = {$PHP_GROUP} +listen.backlog = 4096 + +pm = dynamic +pm.max_children = 30 +pm.start_servers = 5 +pm.min_spare_servers = 5 +pm.max_spare_servers = 20 +pm.status_path = /phpfpm_status_{$PHP_VERSION} +pm.max_requests = 1000 +request_terminate_timeout = 30 +request_slowlog_timeout = 10 +slowlog = {$SERVER_PATH}/php/{$PHP_VERSION}/var/log/www-slow.log \ No newline at end of file diff --git a/plugins/php/ico.png b/plugins/php/ico.png new file mode 100755 index 000000000..59def5702 Binary files /dev/null and b/plugins/php/ico.png differ diff --git a/plugins/php/index.html b/plugins/php/index.html new file mode 100755 index 000000000..4c67f0531 --- /dev/null +++ b/plugins/php/index.html @@ -0,0 +1,67 @@ + + +
                                +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                安装扩展

                                +

                                配置修改

                                +

                                常用功能

                                +

                                配置文件

                                +

                                禁用函数

                                +

                                FPM配置

                                +

                                性能调整

                                +

                                负载状况

                                +

                                会话管理

                                +

                                FPM日志

                                +

                                慢日志

                                +
                                +
                                +
                                +
                                +
                                + +
                                + + \ No newline at end of file diff --git a/plugins/php/index.py b/plugins/php/index.py new file mode 100755 index 000000000..c5c354c22 --- /dev/null +++ b/plugins/php/index.py @@ -0,0 +1,1128 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import shutil + +# reload(sys) +# sys.setdefaultencoding('utf8') + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'php' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(version): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + return '/etc/init.d/' + getPluginName() + version + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':', 1) + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(version): + path = getServerDir() + '/' + version + '/etc/php.ini' + return path + + +def getFpmConfFile(version, pool='www'): + args = getArgs() + if 'pool' in args: + pool = args['pool'] + return getServerDir() + '/' + version + '/etc/php-fpm.d/'+pool+'.conf' + +def getFpmFile(version): + return getServerDir() + '/' + version + '/etc/php-fpm.conf' + + +def status_progress(version): + # ps -ef|grep 'php/81' |grep -v grep | grep -v python | awk '{print $2} + cmd = "ps aux|grep 'php/" + version + \ + "' |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def getPhpSocket(version): + path = getFpmConfFile(version) + content = mw.readFile(path) + rep = r'listen\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def status(version): + ''' + sock文件判断是否启动 + ''' + sock = getPhpSocket(version) + if sock.find(':'): + return status_progress(version) + + if not os.path.exists(sock): + return 'stop' + return 'start' + + +def contentReplace(content, version): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PHP_VERSION}', version) + content = content.replace('{$LOCAL_IP}', mw.getLocalIp()) + content = content.replace('{$SSL_CRT}', mw.getSslCrt()) + + if mw.isAppleSystem(): + # user = mw.execShell( + # "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + content = content.replace('{$PHP_USER}', 'nobody') + content = content.replace('{$PHP_GROUP}', 'nobody') + + rep = r'listen.owner\s*=\s*(.+)\r?\n' + val = ';listen.owner = nobody\n' + content = re.sub(rep, val, content) + + rep = r'listen.group\s*=\s*(.+)\r?\n' + val = ';listen.group = nobody\n' + content = re.sub(rep, val, content) + + rep = r'user\s*=\s*(.+)\r?\n' + val = ';user = nobody\n' + content = re.sub(rep, val, content) + + rep = r'[^\.]group\s*=\s*(.+)\r?\n' + val = ';group = nobody\n' + content = re.sub(rep, val, content) + + else: + content = content.replace('{$PHP_USER}', 'www') + content = content.replace('{$PHP_GROUP}', 'www') + return content + + +def makeOpenrestyConf(): + phpversions = ['00', '52', '53', '54', '55', '56', + '70', '71', '72', '73', '74', '80', '81', '82', '83','84','85'] + + sdir = mw.getServerDir() + + dst_dir = sdir + '/web_conf/php' + if not os.path.exists(dst_dir): + mw.execShell('mkdir -p ' + dst_dir) + + dst_dir_conf = sdir + '/web_conf/php/conf' + if not os.path.exists(dst_dir_conf): + mw.execShell('mkdir -p ' + dst_dir_conf) + + dst_dir_upstream = sdir + '/web_conf/php/upstream' + if not os.path.exists(dst_dir_upstream): + mw.execShell('mkdir -p ' + dst_dir_upstream) + + dst_pathinfo = sdir + '/web_conf/php/pathinfo.conf' + if not os.path.exists(dst_pathinfo): + src_pathinfo = getPluginDir() + '/conf/pathinfo.conf' + shutil.copyfile(src_pathinfo, dst_pathinfo) + + info = getPluginDir() + '/info.json' + content = mw.readFile(info) + content = json.loads(content) + versions = content['versions'] + tpl = getPluginDir() + '/conf/enable-php.conf' + tpl_content = mw.readFile(tpl) + for x in phpversions: + dfile = sdir + '/web_conf/php/conf/enable-php-' + x + '.conf' + if not os.path.exists(dfile): + if x == '00': + mw.writeFile(dfile, '') + else: + content = contentReplace(tpl_content, x) + mw.writeFile(dfile, content) + + upstream_tpl = getPluginDir() + '/conf/enable-php-upstream.conf' + upstream_tpl_content = mw.readFile(upstream_tpl) + for x in phpversions: + dfile = sdir + '/web_conf/php/upstream/enable-php-' + x + '.conf' + if not os.path.exists(dfile): + if x == '00': + mw.writeFile(dfile, '') + else: + content = contentReplace(upstream_tpl_content, x) + mw.writeFile(dfile, content) + + vhost_dir = mw.getServerDir() + '/web_conf/nginx/vhost' + write_php_upstream_conf = mw.getServerDir()+'/web_conf/php/upstream/*.conf;' + if not os.path.exists(vhost_dir): + mw.execShell('mkdir -p ' + vhost_dir) + + vhost_php_upstream = vhost_dir+'/0.php_upstream.conf' + if not os.path.exists(vhost_php_upstream): + mw.writeFile(vhost_php_upstream,'include '+write_php_upstream_conf) + + + +def phpPrependFile(version): + app_start = getServerDir() + '/app_start.php' + if not os.path.exists(app_start): + tpl = getPluginDir() + '/conf/app_start.php' + content = mw.readFile(tpl) + content = contentReplace(content, version) + mw.writeFile(app_start, content) + + +def phpFpmReplace(version): + desc_php_fpm = getServerDir() + '/' + version + '/etc/php-fpm.conf' + if not os.path.exists(desc_php_fpm): + tpl_php_fpm = getPluginDir() + '/conf/php-fpm.conf' + content = mw.readFile(tpl_php_fpm) + content = contentReplace(content, version) + mw.writeFile(desc_php_fpm, content) + else: + if version == '52': + tpl_php_fpm = tpl_php_fpm = getPluginDir() + '/conf/php-fpm-52.conf' + content = mw.readFile(tpl_php_fpm) + mw.writeFile(desc_php_fpm, content) + + + +def phpFpmPoolReplace(version, pool = 'www'): + service_php_fpm_dir = getServerDir() + '/' + version + '/etc/php-fpm.d/' + + if not os.path.exists(service_php_fpm_dir): + os.mkdir(service_php_fpm_dir) + + service_php_fpmwww = service_php_fpm_dir + '/'+pool+'.conf' + if not os.path.exists(service_php_fpmwww): + tpl_php_fpmwww = getPluginDir() + '/conf/'+pool+'.conf' + content = mw.readFile(tpl_php_fpmwww) + content = contentReplace(content, version) + mw.writeFile(service_php_fpmwww, content) + + +def makePhpIni(version): + dst_ini = getConf(version) + if not os.path.exists(dst_ini): + src_ini = getPluginDir() + '/conf/php' + version[0:1] + '.ini' + # shutil.copyfile(s_ini, d_ini) + content = mw.readFile(src_ini) + if version == '52': + content = content + "auto_prepend_file=/www/server/php/app_start.php" + + content = contentReplace(content, version) + mw.writeFile(dst_ini, content) + + +def initReplace(version): + makeOpenrestyConf() + makePhpIni(version) + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + + file_bin = initD_path + '/php' + version + if not os.path.exists(file_bin): + file_tpl = getPluginDir() + '/init.d/php.tpl' + + if version == '52': + file_tpl = getPluginDir() + '/init.d/php52.tpl' + + content = mw.readFile(file_tpl) + content = contentReplace(content, version) + + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + phpPrependFile(version) + phpFpmPoolReplace(version, 'www') + phpFpmPoolReplace(version, 'backup') + phpFpmReplace(version) + + session_path = getServerDir() + '/tmp/session' + if not os.path.exists(session_path): + mw.execShell('mkdir -p ' + session_path) + mw.execShell('chown -R www:www ' + session_path) + + upload_path = getServerDir() + '/tmp/upload' + if not os.path.exists(upload_path): + mw.execShell('mkdir -p ' + upload_path) + mw.execShell('chown -R www:www ' + upload_path) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/php' + version + '.service' + + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/php.service.tpl' + if version == '52': + systemServiceTpl = getPluginDir() + '/init.d/php.service.52.tpl' + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$VERSION}', version) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def phpOp(version, method): + file = initReplace(version) + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service php' + version + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if method == 'stop' or method == 'restart': + mw.execShell(file + ' ' + 'stop') + + data = mw.execShell('systemctl ' + method + ' php' + version) + if data[1] == '': + return 'ok' + return data[1] + + +def start(version): + cmd = 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/www/server/lib/icu/lib:/usr/lib/x86_64-linux-gnu/:/opt/homebrew/lib' + mw.execShell(cmd) + return phpOp(version, 'start') + + +def stop(version): + status = phpOp(version, 'stop') + + if version == '52': + file = initReplace(version) + data = mw.execShell(file + ' ' + 'stop') + if data[1] == '': + return 'ok' + return status + + +def restart(version): + return phpOp(version, 'restart') + + +def reload(version): + if version == '52': + return phpOp(version, 'restart') + return phpOp(version, 'reload') + + +def initdStatus(version): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile(version) + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status php' + version + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(version): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + import shutil + source_bin = initReplace(version) + initd_bin = getInitDFile(version) + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + return 'ok' + + mw.execShell('systemctl enable php' + version) + return 'ok' + + +def initdUinstall(version): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile(version) + os.remove(initd_bin) + return 'ok' + + mw.execShell('systemctl disable php' + version) + return 'ok' + + +def fpmLog(version): + return getServerDir() + '/' + version + '/var/log/php-fpm.log' + + +def fpmSlowLog(version): + return getServerDir() + '/' + version + '/var/log/www-slow.log' + + +def getPhpConf(version): + gets = [ + {'name': 'short_open_tag', 'type': 1, 'ps': '短标签支持'}, + {'name': 'asp_tags', 'type': 1, 'ps': 'ASP标签支持'}, + {'name': 'max_execution_time', 'type': 2, 'ps': '最大脚本运行时间'}, + {'name': 'max_input_time', 'type': 2, 'ps': '最大输入时间'}, + {'name': 'max_input_vars', 'type': 2, 'ps': '最大输入数量'}, + {'name': 'memory_limit', 'type': 2, 'ps': '脚本内存限制'}, + {'name': 'post_max_size', 'type': 2, 'ps': 'POST数据最大尺寸'}, + {'name': 'file_uploads', 'type': 1, 'ps': '是否允许上传文件'}, + {'name': 'upload_max_filesize', 'type': 2, 'ps': '允许上传文件的最大尺寸'}, + {'name': 'max_file_uploads', 'type': 2, 'ps': '允许同时上传文件的最大数量'}, + {'name': 'default_socket_timeout', 'type': 2, 'ps': 'Socket超时时间'}, + {'name': 'error_reporting', 'type': 3, 'ps': '错误级别'}, + {'name': 'display_errors', 'type': 1, 'ps': '是否输出详细错误信息'}, + {'name': 'cgi.fix_pathinfo', 'type': 0, 'ps': '是否开启pathinfo'}, + {'name': 'date.timezone', 'type': 3, 'ps': '时区'} + ] + phpini = mw.readFile(getConf(version)) + result = [] + for g in gets: + rep = g['name'] + r'\s*=\s*([0-9A-Za-z_& ~]+)(\s*;?|\r?\n)' + tmp = re.search(rep, phpini) + if not tmp: + continue + g['value'] = tmp.groups()[0] + result.append(g) + return mw.getJson(result) + + +def submitPhpConf(version): + gets = ['display_errors', 'cgi.fix_pathinfo', 'date.timezone', 'short_open_tag', + 'asp_tags', 'max_execution_time', 'max_input_time', 'max_input_vars', 'memory_limit', + 'post_max_size', 'file_uploads', 'upload_max_filesize', 'max_file_uploads', + 'default_socket_timeout', 'error_reporting'] + args = getArgs() + filename = getServerDir() + '/' + version + '/etc/php.ini' + phpini = mw.readFile(filename) + for g in gets: + if g in args: + rep = g + r'\s*=\s*(.+)\r?\n' + val = g + ' = ' + args[g] + '\n' + phpini = re.sub(rep, val, phpini) + mw.writeFile(filename, phpini) + # mw.execShell(getServerDir() + '/init.d/php' + version + ' reload') + reload(version) + return mw.returnJson(True, '设置成功') + + +def getLimitConf(version): + fileini = getConf(version) + phpini = mw.readFile(fileini) + filefpm = getFpmConfFile(version) + phpfpm = mw.readFile(filefpm) + + # print fileini, filefpm + data = {} + try: + rep = r"upload_max_filesize\s*=\s*([0-9]+)M" + tmp = re.search(rep, phpini).groups() + data['max'] = tmp[0] + except: + data['max'] = '50' + + try: + rep = r"request_terminate_timeout\s*=\s*([0-9]+)\n" + tmp = re.search(rep, phpfpm).groups() + data['maxTime'] = tmp[0] + except: + data['maxTime'] = 0 + + try: + rep = r"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n" + tmp = re.search(rep, phpini).groups() + + if tmp[0] == '1': + data['pathinfo'] = True + else: + data['pathinfo'] = False + except: + data['pathinfo'] = False + + return mw.getJson(data) + + +def setMaxTime(version): + args = getArgs() + data = checkArgs(args, ['time']) + if not data[0]: + return data[1] + + time = args['time'] + if int(time) < 30 or int(time) > 86400: + return mw.returnJson(False, '请填写30-86400间的值!') + + filefpm = getFpmConfFile(version) + conf = mw.readFile(filefpm) + rep = r"request_terminate_timeout\s*=\s*([0-9]+)\n" + conf = re.sub(rep, "request_terminate_timeout = " + time + "\n", conf) + mw.writeFile(filefpm, conf) + + fileini = getServerDir() + "/" + version + "/etc/php.ini" + phpini = mw.readFile(fileini) + rep = r"max_execution_time\s*=\s*([0-9]+)\r?\n" + phpini = re.sub(rep, "max_execution_time = " + time + "\n", phpini) + rep = r"max_input_time\s*=\s*([0-9]+)\r?\n" + phpini = re.sub(rep, "max_input_time = " + time + "\n", phpini) + mw.writeFile(fileini, phpini) + return mw.returnJson(True, '设置成功!') + + +def setMaxSize(version): + args = getArgs() + data = checkArgs(args, ['max']) + if not data[0]: + return data[1] + + maxVal = args['max'] + if int(maxVal) < 2: + return mw.returnJson(False, '上传大小限制不能小于2MB!') + + path = getConf(version) + conf = mw.readFile(path) + rep = r"\nupload_max_filesize\s*=\s*[0-9]+M" + conf = re.sub(rep, u'\nupload_max_filesize = ' + maxVal + 'M', conf) + rep = r"\npost_max_size\s*=\s*[0-9]+M" + conf = re.sub(rep, u'\npost_max_size = ' + maxVal + 'M', conf) + mw.writeFile(path, conf) + + msg = mw.getInfo('设置PHP-{1}最大上传大小为[{2}MB]!', (version, maxVal,)) + mw.writeLog('插件管理[PHP]', msg) + return mw.returnJson(True, '设置成功!') + + +def getFpmConfig(version, pool = 'www'): + args = getArgs() + pool = 'www' + if 'pool' in args: + pool = args['pool'] + + filefpm = getServerDir() + '/' + version + '/etc/php-fpm.d/'+pool+'.conf' + conf = mw.readFile(filefpm) + data = {} + rep = r"\s*pm.max_children\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['max_children'] = tmp[0] + + rep = r"\s*pm.start_servers\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['start_servers'] = tmp[0] + + rep = r"\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['min_spare_servers'] = tmp[0] + + rep = r"\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['max_spare_servers'] = tmp[0] + + rep = r"\s*pm\s*=\s*(\w+)\s*" + tmp = re.search(rep, conf).groups() + data['pm'] = tmp[0] + return mw.getJson(data) + + +def setFpmConfig(version): + args = getArgs() + # if not 'max' in args: + # return 'missing time args!' + + version = args['version'] + max_children = args['max_children'] + start_servers = args['start_servers'] + min_spare_servers = args['min_spare_servers'] + max_spare_servers = args['max_spare_servers'] + pm = args['pm'] + pool = args['pool'] + + + file = getServerDir() + '/' + version + '/etc/php-fpm.d/'+pool+'.conf' + conf = mw.readFile(file) + + rep = r"\s*pm.max_children\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.max_children = " + max_children, conf) + + rep = r"\s*pm.start_servers\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.start_servers = " + start_servers, conf) + + rep = r"\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.min_spare_servers = " + + min_spare_servers, conf) + + rep = r"\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.max_spare_servers = " + + max_spare_servers + "\n", conf) + + rep = r"\s*pm\s*=\s*(\w+)\s*" + conf = re.sub(rep, "\npm = " + pm + "\n", conf) + + mw.writeFile(file, conf) + reload(version) + + msg = mw.getInfo('设置PHP-{1}并发设置,max_children={2},start_servers={3},min_spare_servers={4},max_spare_servers={5}', (version, max_children, + start_servers, min_spare_servers, max_spare_servers,)) + mw.writeLog('插件管理[PHP]', msg) + return mw.returnJson(True, '设置成功!') + + +# def checkFpmStatusFile(version): +# if not mw.isInstalledWeb(): +# return False + +# dfile = getServerDir() + '/nginx/conf/php_status/phpfpm_status_' + version + '.conf' +# if not os.path.exists(dfile): +# tpl = getPluginDir() + '/conf/phpfpm_status.conf' +# content = mw.readFile(tpl) +# content = contentReplace(content, version) +# mw.writeFile(dfile, content) +# mw.restartWeb() +# return True + + +def getFpmAddress(version, pool='www'): + fpm_address = '/tmp/php-cgi-{}.sock'.format(version) + if pool != 'www': + fpm_address = '/tmp/php-cgi-{}.{}.sock'.format(version,pool) + php_fpm_file = getFpmConfFile(version) + try: + content = readFile(php_fpm_file) + tmp = re.findall(r"listen\s*=\s*(.+)", content) + if not tmp: + return fpm_address + if tmp[0].find('sock') != -1: + return fpm_address + if tmp[0].find(':') != -1: + listen_tmp = tmp[0].split(':') + if bind: + fpm_address = (listen_tmp[0], int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(tmp[0])) + return fpm_address + except: + return fpm_address + + +def getFpmStatus(version): + + if version == '52': + return mw.returnJson(False, 'PHP[' + version + ']不支持!!!') + + stat = status(version) + if stat == 'stop': + return mw.returnJson(False, 'PHP[' + version + ']未启动!!!') + + args = getArgs() + pool = 'www' + if 'pool' in args: + pool = args['pool'] + + sock_file = getFpmAddress(version, pool) + uri = '/phpfpm_status_' + version + '?json' + if pool != 'www': + uri = '/phpfpm_status_' + version + '_'+pool+'?json' + try: + sock_data = mw.requestFcgiPHP(sock_file, uri) + except Exception as e: + return mw.returnJson(False, str(e)) + + + result = str(sock_data, encoding='utf-8') + data = json.loads(result) + fTime = time.localtime(int(data['start time'])) + data['start time'] = time.strftime('%Y-%m-%d %H:%M:%S', fTime) + return mw.returnJson(True, "OK", data) + + +def getSessionConf(version): + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(filename) + + rep = r'session.save_handler\s*=\s*([0-9A-Za-z_& ~]+)(\s*;?|\r?\n)' + save_handler = re.search(rep, phpini) + if save_handler: + save_handler = save_handler.group(1) + else: + save_handler = "files" + + reppath = r'\nsession.save_path\s*=\s*"tcp\:\/\/([\d\.]+):(\d+).*\r?\n' + passrep = r'\nsession.save_path\s*=\s*"tcp://[\w\.\?\:]+=(.*)"\r?\n' + memcached = r'\nsession.save_path\s*=\s*"([\d\.]+):(\d+)"' + save_path = re.search(reppath, phpini) + if not save_path: + save_path = re.search(memcached, phpini) + passwd = re.search(passrep, phpini) + port = "" + if passwd: + passwd = passwd.group(1) + else: + passwd = "" + if save_path: + port = save_path.group(2) + save_path = save_path.group(1) + + else: + save_path = "" + + data = {"save_handler": save_handler, "save_path": save_path, + "passwd": passwd, "port": port} + return mw.returnJson(True, 'ok', data) + + +def setSessionConf(version): + + args = getArgs() + + ip = args['ip'] + port = args['port'] + passwd = args['passwd'] + save_handler = args['save_handler'] + + if save_handler != "files": + iprep = r"(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})" + if not re.search(iprep, ip): + return mw.returnJson(False, '请输入正确的IP地址') + + try: + port = int(port) + if port >= 65535 or port < 1: + return mw.returnJson(False, '请输入正确的端口号') + except: + return mw.returnJson(False, '请输入正确的端口号') + prep = r"[\~\`\/\=]" + if re.search(prep, passwd): + return mw.returnJson(False, '请不要输入以下特殊字符 " ~ ` / = "') + + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + phpini = mw.readFile(filename) + + session_tmp = getServerDir() + "/tmp/session" + + rep = r'session.save_handler\s*=\s*(.+)\r?\n' + val = r'session.save_handler = ' + save_handler + '\n' + phpini = re.sub(rep, val, phpini) + + if save_handler == "memcached": + if not re.search("memcached.so", phpini): + return mw.returnJson(False, '请先安装%s扩展' % save_handler) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "%s:%s" \n' % (ip, port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "' + session_tmp + '"', + '\n;session.save_path = "' + session_tmp + '"' + val, phpini) + + if save_handler == "memcache": + if not re.search("memcache.so", phpini): + return mw.returnJson(False, '请先安装%s扩展' % save_handler) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "%s:%s" \n' % (ip, port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "' + session_tmp + '"', + '\n;session.save_path = "' + session_tmp + '"' + val, phpini) + + if save_handler == "redis": + if not re.search("redis.so", phpini): + return mw.returnJson(False, '请先安装%s扩展' % save_handler) + if passwd: + passwd = "?auth=" + passwd + else: + passwd = "" + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "tcp://%s:%s%s"\n' % (ip, port, passwd) + res = re.search(rep, phpini) + if res: + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "' + session_tmp + '"', + '\n;session.save_path = "' + session_tmp + '"' + val, phpini) + + if save_handler == "files": + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "' + session_tmp + '"\n' + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "' + session_tmp + '"', + '\n;session.save_path = "' + session_tmp + '"' + val, phpini) + + mw.writeFile(filename, phpini) + reload(version) + return mw.returnJson(True, '设置成功!') + + +def getSessionCount_Origin(version): + session_tmp = getServerDir() + "/tmp/session" + d = [session_tmp] + count = 0 + for i in d: + if not os.path.exists(i): + mw.execShell('mkdir -p %s' % i) + list = os.listdir(i) + for l in list: + if os.path.isdir(i + "/" + l): + l1 = os.listdir(i + "/" + l) + for ll in l1: + if "sess_" in ll: + count += 1 + continue + if "sess_" in l: + count += 1 + + s = "find /tmp -mtime +1 |grep 'sess_' | wc -l" + old_file = int(mw.execShell(s)[0].split("\n")[0]) + + s = "find " + session_tmp + " -mtime +1 |grep 'sess_'|wc -l" + old_file += int(mw.execShell(s)[0].split("\n")[0]) + return {"total": count, "oldfile": old_file} + + +def getSessionCount(version): + data = getSessionCount_Origin(version) + return mw.returnJson(True, 'ok!', data) + + +def cleanSessionOld(version): + s = "find /tmp -mtime +1 |grep 'sess_'|xargs rm -f" + mw.execShell(s) + + session_tmp = getServerDir() + "/tmp/session" + s = "find " + session_tmp + " -mtime +1 |grep 'sess_' |xargs rm -f" + mw.execShell(s) + old_file_conf = getSessionCount_Origin(version)["oldfile"] + if old_file_conf == 0: + return mw.returnJson(True, '清理成功') + else: + return mw.returnJson(True, '清理失败') + + +def getDisableFunc(version): + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(filename) + data = {} + rep = r"disable_functions\s*=\s{0,1}(.*)\n" + tmp = re.search(rep, phpini).groups() + data['disable_functions'] = tmp[0] + return mw.getJson(data) + + +def setDisableFunc(version): + filename = getConf(version) + if not os.path.exists(filename): + return mw.returnJson(False, '指定PHP版本不存在!') + + args = getArgs() + disable_functions = args['disable_functions'] + + phpini = mw.readFile(filename) + rep = r"disable_functions\s*=\s*.*\n" + phpini = re.sub(rep, 'disable_functions = ' + + disable_functions + "\n", phpini) + + msg = mw.getInfo('修改PHP-{1}的禁用函数为[{2}]', (version, disable_functions,)) + mw.writeLog('插件管理[PHP]', msg) + mw.writeFile(filename, phpini) + reload(version) + return mw.returnJson(True, '设置成功!') + + +def getPhpinfo(version): + stat = status(version) + if stat == 'stop': + return 'PHP[' + version + ']未启动,不可访问!!!' + + sock_file = getFpmAddress(version) + root_dir = mw.getFatherDir() + '/phpinfo' + + mw.execShell("rm -rf " + root_dir) + mw.execShell("mkdir -p " + root_dir) + mw.writeFile(root_dir + '/phpinfo.php', '') + sock_data = mw.requestFcgiPHP(sock_file, '/phpinfo.php', root_dir) + os.system("rm -rf " + root_dir) + phpinfo = str(sock_data, encoding='utf-8') + return phpinfo + + +def get_php_info(args): + return getPhpinfo(args['version']) + + +def libConfCommon(version): + fname = getConf(version) + if not os.path.exists(fname): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(fname) + + libpath = getPluginDir() + '/versions/phplib.conf' + phplib = json.loads(mw.readFile(libpath)) + + libs = [] + tasks = mw.M('tasks').where("status!=?", ('1',)).field('status,name').select() + for lib in phplib: + lib['task'] = '1' + for task in tasks: + tmp = mw.getStrBetween('[', ']', task['name']) + if not tmp: + continue + tmp1 = tmp.split('-') + if tmp1[0].lower() == lib['name'].lower(): + lib['task'] = task['status'] + lib['phpversions'] = [] + lib['phpversions'].append(tmp1[1]) + if phpini.find(lib['check']) == -1: + lib['status'] = False + else: + lib['status'] = True + libs.append(lib) + return libs + + +def get_lib_conf(data): + libs = libConfCommon(data['version']) + # print(libs) + return mw.returnData(True, 'OK!', libs) + + +def getLibConf(version): + libs = libConfCommon(version) + return mw.returnJson(True, 'OK!', libs) + + +def installLib(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + cmd = "cd " + getPluginDir() + "/versions && /bin/bash common.sh " + version + ' install ' + name + install_name = '安装[' + name + '-' + version + ']' + import thisdb + thisdb.addTask(name=install_name,cmd=cmd) + + mw.triggerTask() + return mw.returnJson(True, '已将下载任务添加到队列!') + + +def uninstallLib(version): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + execstr = "cd " + getPluginDir() + "/versions && /bin/bash common.sh " + version + ' uninstall ' + name + + data = mw.execShell(execstr) + # data[0] == '' and + if data[1] == '': + return mw.returnJson(True, '已经卸载成功!') + else: + return mw.returnJson(False, '卸载信息![通道0]:' + data[0] + "[通道0]:" + data[1]) + + +def getConfAppStart(): + pstart = mw.getServerDir() + '/php/app_start.php' + return pstart + +def opcacheBlacklistFile(): + op_bl = mw.getServerDir() + '/php/opcache-blacklist.txt' + return op_bl + + +def installPreInspection(version): + # 仅对PHP52检查 + if version != '52': + return 'ok' + + sys = mw.execShell( + "cat /etc/*-release | grep PRETTY_NAME |awk -F = '{print $2}' | awk -F '\"' '{print $2}'| awk '{print $1}'") + + if sys[1] != '': + return '不支持改系统' + + sys_id = mw.execShell( + "cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F '\"' '{print $2}'") + + sysName = sys[0].strip().lower() + sysId = sys_id[0].strip() + + if sysName == 'ubuntu': + return 'ubuntu已经安装不了' + + if sysName == 'debian' and int(sysId) > 10: + return 'debian10可以安装' + + if sysName == 'centos' and int(sysId) > 8: + return 'centos[{}]不可以安装'.format(sysId) + + if sysName == 'fedora': + sys_id = mw.execShell( + "cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}'") + sysId = sys_id[0].strip() + if int(sysId) > 31: + return 'fedora[{}]不可安装'.format(sysId) + return 'ok' + + +if __name__ == "__main__": + + if len(sys.argv) < 3: + print('missing parameters') + exit(0) + + func = sys.argv[1] + version = sys.argv[2] + + if func == 'status': + print(status(version)) + elif func == 'start': + print(start(version)) + elif func == 'stop': + print(stop(version)) + elif func == 'restart': + print(restart(version)) + elif func == 'reload': + print(reload(version)) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'initd_status': + print(initdStatus(version)) + elif func == 'initd_install': + print(initdInstall(version)) + elif func == 'initd_uninstall': + print(initdUinstall(version)) + elif func == 'fpm_log': + print(fpmLog(version)) + elif func == 'fpm_slow_log': + print(fpmSlowLog(version)) + elif func == 'conf': + print(getConf(version)) + elif func == 'app_start': + print(getConfAppStart()) + elif func == 'opcache_blacklist_file': + print(opcacheBlacklistFile()) + elif func == 'get_php_conf': + print(getPhpConf(version)) + elif func == 'get_fpm_conf_file': + print(getFpmConfFile(version)) + elif func == 'get_fpm_file': + print(getFpmFile(version)) + elif func == 'submit_php_conf': + print(submitPhpConf(version)) + elif func == 'get_limit_conf': + print(getLimitConf(version)) + elif func == 'set_max_time': + print(setMaxTime(version)) + elif func == 'set_max_size': + print(setMaxSize(version)) + elif func == 'get_fpm_conf': + print(getFpmConfig(version)) + elif func == 'set_fpm_conf': + print(setFpmConfig(version)) + elif func == 'get_fpm_status': + print(getFpmStatus(version)) + elif func == 'get_session_conf': + print(getSessionConf(version)) + elif func == 'set_session_conf': + print(setSessionConf(version)) + elif func == 'get_session_count': + print(getSessionCount(version)) + elif func == 'clean_session_old': + print(cleanSessionOld(version)) + elif func == 'get_disable_func': + print(getDisableFunc(version)) + elif func == 'set_disable_func': + print(setDisableFunc(version)) + elif func == 'get_phpinfo': + print(getPhpinfo(version)) + elif func == 'get_lib_conf': + print(getLibConf(version)) + elif func == 'install_lib': + print(installLib(version)) + elif func == 'uninstall_lib': + print(uninstallLib(version)) + else: + print("fail") diff --git a/plugins/php/index_php.py b/plugins/php/index_php.py new file mode 100755 index 000000000..49edd6d0a --- /dev/null +++ b/plugins/php/index_php.py @@ -0,0 +1,176 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import shutil + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'php' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(version): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + return '/etc/init.d/' + getPluginName() + version + + +def getConf(version): + path = getServerDir() + '/' + version + '/etc/php.ini' + return path + + +def getFpmConfFile(version): + return getServerDir() + '/' + version + '/etc/php-fpm.d/www.conf' + + +def status_progress(version): + # ps -ef|grep 'php/81' |grep -v grep | grep -v python | awk '{print $2} + cmd = "ps aux|grep 'php/" + version + \ + "' |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def getPhpSocket(version): + path = getFpmConfFile(version) + content = mw.readFile(path) + rep = r'listen\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def status(version): + ''' + sock文件判断是否启动 + ''' + sock = getPhpSocket(version) + if sock.find(':'): + return status_progress(version) + + if not os.path.exists(sock): + return 'stop' + return 'start' + + +def getFpmAddress(version): + fpm_address = '/tmp/php-cgi-{}.sock'.format(version) + php_fpm_file = getFpmConfFile(version) + try: + content = readFile(php_fpm_file) + tmp = re.findall(r"listen\s*=\s*(.+)", content) + if not tmp: + return fpm_address + if tmp[0].find('sock') != -1: + return fpm_address + if tmp[0].find(':') != -1: + listen_tmp = tmp[0].split(':') + if bind: + fpm_address = (listen_tmp[0], int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(tmp[0])) + return fpm_address + except: + return fpm_address + + +def getPhpinfo(version): + stat = status(version) + if stat == 'stop': + return 'PHP[' + version + ']未启动,不可访问!!!' + + sock_file = getFpmAddress(version) + root_dir = mw.getFatherDir() + '/phpinfo' + + mw.execShell("rm -rf " + root_dir) + mw.execShell("mkdir -p " + root_dir) + mw.writeFile(root_dir + '/phpinfo.php', '') + sock_data = mw.requestFcgiPHP(sock_file, '/phpinfo.php', root_dir) + os.system("rm -rf " + root_dir) + phpinfo = str(sock_data, encoding='utf-8') + return phpinfo + + +def libConfCommon(version): + fname = getConf(version) + if not os.path.exists(fname): + return mw.returnJson(False, '指定PHP版本不存在!') + + phpini = mw.readFile(fname) + + libpath = getPluginDir() + '/versions/phplib.conf' + phplib = json.loads(mw.readFile(libpath)) + + php_dir = getServerDir() + "/" + version + ext_dir = php_dir+"/lib/php/extensions" + + ext_list = os.listdir(ext_dir) + for sodir in ext_list: + if sodir.find("no-debug-non-zts") > -1: + ext_dir += "/"+ sodir + break + + libs = [] + tasks = mw.M('tasks').where("status!=?", ('1',)).field('status,name').select() + for lib in phplib: + lib['task'] = '1' + for task in tasks: + tmp = mw.getStrBetween('[', ']', task['name']) + if not tmp: + continue + tmp1 = tmp.split('-') + if tmp1[0].lower() == lib['name'].lower(): + lib['task'] = task['status'] + lib['phpversions'] = [] + lib['phpversions'].append(tmp1[1]) + + lib['status'] = False + if phpini.find(lib['check']) > -1: + lib['status'] = True + sofile = ext_dir+"/"+lib['check'] + # 自定义,比较特殊的方式 + if os.path.exists(sofile): + if os.path.getsize(sofile) == 7: + lib['status'] = True + libs.append(lib) + return libs + + +def get_php_info(args): + return getPhpinfo(args['version']) + + +def get_lib_conf(data): + libs = libConfCommon(data['version']) + return mw.returnData(True, 'OK!', libs) diff --git a/plugins/php/info.json b/plugins/php/info.json new file mode 100755 index 000000000..0fd7d34e6 --- /dev/null +++ b/plugins/php/info.json @@ -0,0 +1,20 @@ +{ + "sort": 7, + "ps": "PHP是世界上最好的编程语言", + "shell": "install.sh", + "name": "php", + "title": "PHP", + "coexist": true, + "versions": ["53","54","55","56","70","71","72","73","74","80","81","82","83","84","85"], + "updates": ["5.3.29","5.4.45","5.6.36","7.0.30","7.1.33","7.2.32","7.3.20","7.4.30","8.0.30","8.1.29","8.2.27","8.3.15","8.4.2","8.5.0"], + "tip": "soft", + "install_pre_inspection":true, + "checks": "server/php/VERSION/bin/php", + "path": "server/php/VERSION", + "display": 1, + "author": "Zend", + "date": "2017-04-01", + "home": "https://www.php.net", + "type": "PHP语言解释器", + "pid": "1" +} \ No newline at end of file diff --git a/plugins/php/init.d/php.service.52.tpl b/plugins/php/init.d/php.service.52.tpl new file mode 100644 index 000000000..d837568e8 --- /dev/null +++ b/plugins/php/init.d/php.service.52.tpl @@ -0,0 +1,16 @@ +# It's not recommended to modify this file in-place, because it +# will be overwritten during upgrades. If you want to customize, +# the best way is to use the "systemctl edit" command. + +[Unit] +Description=The PHP {$VERSION} FastCGI Process Manager +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/php/init.d/php{$VERSION} start +ExecStop={$SERVER_PATH}/php/init.d/php{$VERSION} stop +PrivateTmp=false + +[Install] +WantedBy=multi-user.target diff --git a/plugins/php/init.d/php.service.tpl b/plugins/php/init.d/php.service.tpl new file mode 100644 index 000000000..a1fe07178 --- /dev/null +++ b/plugins/php/init.d/php.service.tpl @@ -0,0 +1,18 @@ +# It's not recommended to modify this file in-place, because it +# will be overwritten during upgrades. If you want to customize, +# the best way is to use the "systemctl edit" command. +# systemctl daemon-reload + +[Unit] +Description=The PHP {$VERSION} FastCGI Process Manager +After=network.target + +[Service] +Environment="LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/www/server/lib/icu/lib" +PIDFile={$SERVER_PATH}/php/{$VERSION}/var/run/php-fpm.pid +ExecStart={$SERVER_PATH}/php/{$VERSION}/sbin/php-fpm --nodaemonize --fpm-config {$SERVER_PATH}/php/{$VERSION}/etc/php-fpm.conf +ExecReload=/bin/kill -USR2 $MAINPID +PrivateTmp=false + +[Install] +WantedBy=multi-user.target diff --git a/plugins/php/init.d/php.tpl b/plugins/php/init.d/php.tpl new file mode 100644 index 000000000..bbf46fddd --- /dev/null +++ b/plugins/php/init.d/php.tpl @@ -0,0 +1,154 @@ +#! /bin/sh +# chkconfig: 2345 55 25 +# description: PHP Service + +### BEGIN INIT INFO +# Provides: php-fpm +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts php-fpm +# Description: starts the PHP FastCGI Process Manager daemon +### END INIT INFO + +prefix={$SERVER_PATH}/php/{$PHP_VERSION} +exec_prefix=${prefix} + +php_fpm_BIN=${exec_prefix}/sbin/php-fpm +php_fpm_CONF=${prefix}/etc/php-fpm.conf +php_fpm_PID=${prefix}/var/run/php-fpm.pid + + +php_opts="--fpm-config $php_fpm_CONF --pid $php_fpm_PID" + + +wait_for_pid () { + try=0 + + while test $try -lt 35 ; do + + case "$1" in + 'created') + if [ -f "$2" ] ; then + try='' + break + fi + ;; + + 'removed') + if [ ! -f "$2" ] ; then + try='' + break + fi + ;; + esac + + echo -n . + try=`expr $try + 1` + sleep 1 + + done + +} + +case "$1" in + start) + echo -n "Starting php-fpm " + + $php_fpm_BIN --daemonize $php_opts + + if [ "$?" != 0 ] ; then + echo " failed" + exit 1 + fi + + wait_for_pid created $php_fpm_PID + + if [ -n "$try" ] ; then + echo " failed" + exit 1 + else + echo " done" + fi + ;; + + stop) + echo -n "Gracefully shutting down php-fpm " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -QUIT `cat $php_fpm_PID` + + wait_for_pid removed $php_fpm_PID + + if [ -n "$try" ] ; then + echo " failed. Use force-quit" + exit 1 + else + echo " done" + fi + ;; + + status) + if [ ! -r $php_fpm_PID ] ; then + echo "php-fpm is stopped" + exit 0 + fi + + PID=`cat $php_fpm_PID` + if ps -p $PID | grep -q $PID; then + echo "php-fpm (pid $PID) is running..." + else + echo "php-fpm dead but pid file exists" + fi + ;; + + force-quit) + echo -n "Terminating php-fpm " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -TERM `cat $php_fpm_PID` + + wait_for_pid removed $php_fpm_PID + + if [ -n "$try" ] ; then + echo " failed" + exit 1 + else + echo " done" + fi + ;; + + restart) + $0 stop + $0 start + ;; + + reload) + + echo -n "Reload service php-fpm " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -USR2 `cat $php_fpm_PID` + + echo " done" + ;; + + *) + echo "Usage: $0 {start|stop|force-quit|restart|reload|status}" + exit 1 + ;; + +esac diff --git a/plugins/php/init.d/php52.tpl b/plugins/php/init.d/php52.tpl new file mode 100644 index 000000000..73f96406b --- /dev/null +++ b/plugins/php/init.d/php52.tpl @@ -0,0 +1,139 @@ +#! /bin/sh + +php_fpm_BIN=/www/server/php/52/bin/php-cgi +php_fpm_CONF=/www/server/php/52/etc/php-fpm.conf +php_fpm_PID=/www/server/php/52/var/run/php-fpm.pid + + +php_opts="--fpm-config $php_fpm_CONF" + + +wait_for_pid () { + try=0 + + while test $try -lt 35 ; do + + case "$1" in + 'created') + if [ -f "$2" ] ; then + try='' + break + fi + ;; + + 'removed') + if [ ! -f "$2" ] ; then + try='' + break + fi + ;; + esac + + echo -n . + try=`expr $try + 1` + sleep 1 + + done + +} + +case "$1" in + start) + echo -n "Starting php_fpm " + + $php_fpm_BIN --fpm $php_opts + + if [ "$?" != 0 ] ; then + echo " failed" + exit 1 + fi + + wait_for_pid created $php_fpm_PID + + if [ -n "$try" ] ; then + echo " failed" + exit 1 + else + echo " done" + fi + ;; + + stop) + echo -n "Shutting down php_fpm " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -TERM `cat $php_fpm_PID` + + wait_for_pid removed $php_fpm_PID + + if [ -n "$try" ] ; then + echo " failed" + exit 1 + else + echo " done" + fi + ;; + + quit) + echo -n "Gracefully shutting down php_fpm " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -QUIT `cat $php_fpm_PID` + + wait_for_pid removed $php_fpm_PID + + if [ -n "$try" ] ; then + echo " failed" + exit 1 + else + echo " done" + fi + ;; + + restart) + $0 stop + $0 start + ;; + + reload) + + echo -n "Reload service php-fpm " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -USR2 `cat $php_fpm_PID` + + echo " done" + ;; + + logrotate) + + echo -n "Re-opening php-fpm log file " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -USR1 `cat $php_fpm_PID` + + echo " done" + ;; + + *) + echo "Usage: $0 {start|stop|quit|restart|reload|logrotate}" + exit 1 + ;; + +esac diff --git a/plugins/php/install.sh b/plugins/php/install.sh new file mode 100755 index 000000000..21611e98e --- /dev/null +++ b/plugins/php/install.sh @@ -0,0 +1,95 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +# cd /www/server/mdserver-web/plugins/php && bash install.sh install 73 +# cd /www/server/mdserver-web/plugins/php && bash install.sh install 85 +# https://www.php.net/releases + +if id www &> /dev/null ;then + echo "www uid is `id -u www`" + echo "www shell is `grep "^www:" /etc/passwd |cut -d':' -f7 `" +else + groupadd www + useradd -g www -s /sbin/nologin www + # useradd -g www -s /bin/bash www +fi + +action=$1 +type=$2 + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$2 ];then + echo '缺少安装脚本2...' + exit 0 +fi + + +# if [ "${action}" == "install" ] && [ -d $serverPath/php/${type} ];then +# exit 0 +# fi + +if [ "${action}" == "uninstall" ];then + + if [ -f /usr/lib/systemd/system/php${type}.service ] || [ -f /lib/systemd/system/php${type}.service ] ;then + systemctl stop php${type} + systemctl disable php${type} + rm -rf /usr/lib/systemd/system/php${type}.service + rm -rf /lib/systemd/system/php${type}.service + systemctl daemon-reload + fi +fi + +cd ${curPath} && sh -x $curPath/versions/$2/install.sh $1 + + +if [ "${action}" == "install" ] && [ -d ${serverPath}/php/${type} ];then + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/php/index.py start ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/php/index.py initd_install ${type} + + # 安装通用扩展 + echo "install PHP${type} extend start" + + # cd /www/server/mdserver-web/plugins/php/versions/common && bash iconv.sh install 53 + # cd /www/server/mdserver-web/plugins/php/versions/common && bash intl.sh install 73 + # cd /www/server/mdserver-web/plugins/php/versions/common && bash gd.sh install 56 + # cd /www/server/mdserver-web/plugins/php/versions/common && bash openssl.sh install 56 + # cd /www/server/mdserver-web/plugins/php/versions/common && bash fileinfo.sh install 81 + cd ${rootPath}/plugins/php/versions/common && bash curl.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash gd.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash readline.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash iconv.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash exif.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash intl.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash mcrypt.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash openssl.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash bcmath.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash gettext.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash redis.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash memcached.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash pcntl.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash zip.sh install ${type} + cd ${rootPath}/plugins/php/versions/common && bash zlib.sh install ${type} + + echo "install PHP${type} extend end" + + if [ ! -f /usr/local/bin/composer ] && [ "$sysName" != "Darwin" ] ;then + cd /tmp + curl -sS https://getcomposer.org/installer | /www/server/php/${type}/bin/php + mv composer.phar /usr/local/bin/composer + fi +fi + + diff --git a/plugins/php/js/php.js b/plugins/php/js/php.js new file mode 100755 index 000000000..c1bdb81d4 --- /dev/null +++ b/plugins/php/js/php.js @@ -0,0 +1,858 @@ +function phpPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'php'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function phpPostCallback(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'php'; + req_data['func'] = method; + req_data['script']='index_php'; + args['version'] = version; + + + if (typeof(args) == 'string' && args == ''){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +//配置修改 +function phpSetConfig(version) { + phpPost('get_php_conf', version,'',function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '70' + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = '' + break; + case 1: + var selected_1 = (rdata[i].value == 'On') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'Off') ? 'selected' : ''; + ibody = '' + break; + } + mlist += '

                                ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

                                '; + } + var phpCon = '
                                \ + ' + mlist + '\ +
                                \ + \ + \ +
                                \ +
                                ' + $(".soft-man-con").html(phpCon); + }); +} + + +//提交PHP配置 +function submitConf(version) { + var data = { + version: version, + display_errors: $("select[name='display_errors']").val(), + 'cgi.fix_pathinfo': $("select[name='cgi.fix_pathinfo']").val(), + 'date.timezone': $("input[name='date.timezone']").val(), + short_open_tag: $("select[name='short_open_tag']").val(), + asp_tags: $("select[name='asp_tags']").val() || 'On', + safe_mode: $("select[name='safe_mode']").val(), + max_execution_time: $("input[name='max_execution_time']").val(), + max_input_time: $("input[name='max_input_time']").val(), + max_input_vars: $("input[name='max_input_vars']").val(), + memory_limit: $("input[name='memory_limit']").val(), + post_max_size: $("input[name='post_max_size']").val(), + file_uploads: $("select[name='file_uploads']").val(), + upload_max_filesize: $("input[name='upload_max_filesize']").val(), + max_file_uploads: $("input[name='max_file_uploads']").val(), + default_socket_timeout: $("input[name='default_socket_timeout']").val(), + error_reporting: $("input[name='error_reporting']").val() || 'On' + }; + + phpPost('submit_php_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + // console.log(rdata); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +//php超时限制 +function phpCommonFunc(version){ + phpPost('get_limit_conf', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + var con = '

                                \ + 超时限制\ + , 秒\ + \ +

                                '; + + con += '

                                \ + 上传限制\ + ,MB\ + \ +

                                '; + + con += '

                                \ + \ + \ + \ + \ +

                                '; + + $(".soft-man-con").html(con); + }); +} + +//设置超时限制 +function setPHPMaxTime(version) { + var max = $(".phpTimeLimit").val(); + phpPost('set_max_time',version,{'time':max},function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + phpCommonFunc(version); + },{ icon: rdata.status ? 1 : 2 }); + + }); +} +//设置PHP上传限制 +function setPHPMaxSize(version) { + max = $(".phpUploadLimit").val(); + if (max < 2) { + alert(max); + layer.msg('上传大小限制不能小于2M', { icon: 2 }); + return; + } + + phpPost('set_max_size',version,{'max':max},function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function phpPreload(version){ + phpPost('app_start',version,{},function(data){ + onlineEditFile(0, data['data']); + }); +} + +function phpOpcacheBlacklist(version){ + phpPost('opcache_blacklist_file',version,{},function(data){ + onlineEditFile(0, data['data']); + }); +} + + +function phpFpmRoot(version){ + phpPost('get_fpm_file',version,{},function(data){ + onlineEditFile(0, data['data']); + }); +} + +function phpFpmConfigFile(version, func, pool = 'www'){ + var _name = 'php'; + if ( typeof(version) == 'undefined' ){ + version = ''; + } + + var func_name = 'conf'; + if ( typeof(func) != 'undefined' ){ + func_name = func; + } + + var poolSelect = ""; + + var con = '

                                提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!

                                \ + \ + \ + '+poolSelect+'\ +
                                  \ +
                                • 此处为'+ _name + version +'应用池配置文件,若您不了解配置规则,请勿随意修改。
                                • \ +
                                '; + + + var loadT = layer.msg('配置文件路径获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + + var request_data = {name:_name, func:func_name,version:version}; + request_data['args'] = JSON.stringify({'pool':pool}); + + $.post('/plugins/run', request_data, function (data) { + layer.close(loadT); + + try{ + var jdata = $.parseJSON(data.data); + if (!jdata['status']){ + layer.msg(jdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + }catch(err){/*console.log(err);*/} + + $(".soft-man-con").html(con); + + var loadT2 = layer.msg('文件内容获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + var fileName = data.data; + $.post('/files/get_body', 'path=' + fileName, function(rdata) { + layer.close(loadT2); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + $("#textBody").empty().text(rdata.data.data); + $(".CodeMirror").remove(); + var editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName); + } + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").click(function(){ + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName); + }); + },'json'); + + $('select[name="pool"]').change(function(){ + var pool = $(this).val(); + phpFpmConfigFile(version, func, pool); + }); + },'json'); +} + +function getFpmConfig(version, pool = 'www'){ + phpPost('get_fpm_conf', version, {'pool':pool}, function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var limitList = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + var pms = [{ 'name': 'static', 'title': '静态' }, { 'name': 'dynamic', 'title': '动态' },{ 'name': 'ondemand', 'title': '按需' }]; + var pmList = ''; + for (var i = 0; i < pms.length; i++) { + pmList += ''; + } + + var poolHtml = "" + + ""; + + var body = "
                                " + + "

                                应用池[pool]:

                                " + + "

                                并发方案:

                                " + + "

                                运行模式:*PHP-FPM运行模式

                                " + + "

                                max_children:*允许创建的最大子进程数

                                " + + "

                                start_servers: *起始进程数(服务启动后初始进程数量)

                                " + + "

                                min_spare_servers: *最小空闲进程数(清理空闲进程后的保留数量)

                                " + + "

                                max_spare_servers: *最大空闲进程数(当空闲进程达到此值时清理)

                                " + + "
                                " + + "
                                "; + + $(".soft-man-con").html(body); + $("select[name='limit']").change(function() { + var type = $(this).val(); + var max_children = rdata.max_children; + var start_servers = rdata.start_servers; + var min_spare_servers = rdata.min_spare_servers; + var max_spare_servers = rdata.max_spare_servers; + switch (type) { + case '0': + max_children = 2; + start_servers = 1; + min_spare_servers = 1; + max_spare_servers = 2; + break; + case '1': + max_children = 5; + start_servers = 2; + min_spare_servers = 1; + max_spare_servers = 5; + break; + case '2': + max_children = 10; + start_servers = 2; + min_spare_servers = 1; + max_spare_servers = 10; + break; + case '3': + max_children = 30; + start_servers = 5; + min_spare_servers = 5; + max_spare_servers = 20; + break; + case '4': + max_children = 50; + start_servers = 15; + min_spare_servers = 15; + max_spare_servers = 35; + break; + case '5': + max_children = 100; + start_servers = 20; + min_spare_servers = 20; + max_spare_servers = 70; + break; + case '6': + max_children = 200; + start_servers = 25; + min_spare_servers = 25; + max_spare_servers = 150; + break; + case '7': + max_children = 300; + start_servers = 30; + min_spare_servers = 30; + max_spare_servers = 180; + break; + case '8': + max_children = 500; + start_servers = 35; + min_spare_servers = 35; + max_spare_servers = 250; + break; + case '9': + max_children = 2000; + start_servers = 40; + min_spare_servers = 40; + max_spare_servers = 255; + break; + } + + $("input[name='max_children']").val(max_children); + $("input[name='start_servers']").val(start_servers); + $("input[name='min_spare_servers']").val(min_spare_servers); + $("input[name='max_spare_servers']").val(max_spare_servers); + }); + + $('select[name="pool"]').change(function(){ + var pool = $(this).val(); + getFpmConfig(version, pool); + }); + }); +} + +function setFpmConfig(version){ + var max_children = Number($("input[name='max_children']").val()); + var pool = $("select[name='pool']").val(); + var start_servers = Number($("input[name='start_servers']").val()); + var min_spare_servers = Number($("input[name='min_spare_servers']").val()); + var max_spare_servers = Number($("input[name='max_spare_servers']").val()); + var pm = $("select[name='pm']").val(); + + if (max_children < max_spare_servers) { + layer.msg('max_spare_servers 不能大于 max_children', { icon: 2 }); + return; + } + + if (min_spare_servers > start_servers) { + layer.msg('min_spare_servers 不能大于 start_servers', { icon: 2 }); + return; + } + + if (max_spare_servers < min_spare_servers) { + layer.msg('min_spare_servers 不能大于 max_spare_servers', { icon: 2 }); + return; + } + + if (max_children < start_servers) { + layer.msg('start_servers 不能大于 max_children', { icon: 2 }); + return; + } + + if (max_children < 1 || start_servers < 1 || min_spare_servers < 1 || max_spare_servers < 1) { + layer.msg('配置值不能小于1', { icon: 2 }); + return; + } + + var data = { + version:version, + max_children:max_children, + start_servers:start_servers, + min_spare_servers:min_spare_servers, + max_spare_servers:max_spare_servers, + pm:pm, + pool:pool, + }; + phpPost('set_fpm_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +function getFpmStatus(version, pool = 'www'){ + phpPost('get_fpm_status', version, {'pool':pool}, function(ret_data){ + var tmp_data = $.parseJSON(ret_data.data); + if(!tmp_data.status){ + layer.msg(tmp_data.msg, { icon: tmp_data.status ? 1 : 2 }); + return; + } + + var rdata = tmp_data.data; + var php_fpm_status = '动态'; + if (rdata['process manager'] == 'dynamic'){ + php_fpm_status = '动态'; + } else if(rdata['process manager'] == 'static'){ + php_fpm_status = '静态'; + } else if(rdata['process manager'] == 'ondemand'){ + php_fpm_status = '按需'; + } + + var select_pool_www = ''; + var select_pool_backup = ''; + if (pool == 'www'){ + select_pool_www = 'selected'; + } + if (pool == 'backup'){ + select_pool_backup = 'selected'; + } + + var con = "
                                \ +

                                \ + \ +

                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                应用池(pool)" + rdata.pool + "
                                进程管理方式(process manager)" + php_fpm_status + "
                                启动日期(start time)" + rdata['start time'] + "
                                请求数(accepted conn)" + rdata['accepted conn'] + "
                                请求队列(listen queue)" + rdata['listen queue'] + "
                                最大等待队列(max listen queue)" + rdata['max listen queue'] + "
                                socket队列长度(listen queue len)" + rdata['listen queue len'] + "
                                空闲进程数量(idle processes)" + rdata['idle processes'] + "
                                活跃进程数量(active processes)" + rdata['active processes'] + "
                                总进程数量(total processes)" + rdata['total processes'] + "
                                最大活跃进程数量(max active processes)" + rdata['max active processes'] + "
                                到达进程上限次数(max children reached)" + rdata['max children reached'] + "
                                慢请求数量(slow requests)" + rdata['slow requests'] + "
                                \ +
                                "; + $(".soft-man-con").html(con); + $(".get_fpm_status td,.get_fpm_status th").css("padding", "7px"); + + + $('select[name="pool"]').change(function(){ + var pool = $(this).val(); + getFpmStatus(version, pool); + }); + }); +} + + +function getSessionConfig(version){ + phpPost('get_session_conf', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + if(!rdata.status){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + var rdata = rdata.data; + + var cacheList = "" + + "" + + "" + + ""; + + + var info = rdata.save_path.split(":"); + var con = "
                                " + + "

                                存储模式:

                                " + + "

                                IP地址:

                                " + + "

                                端口:

                                " + + "

                                密码:

                                " + + "

                                " + + "
                                \ +
                                  \ +
                                • 若你的站点并发比较高,使用Redis,Memcache能有效提升PHP并发能力
                                • \ +
                                • 若调整Session模式后,网站访问异常,请切换回原来的模式
                                • \ +
                                • 切换Session模式会使在线的用户会话丢失,请在流量小的时候切换
                                • \ +
                                \ +
                                \ +
                                \ +
                                "; + + $(".soft-man-con").html(con); + + if (rdata.save_handler == 'files'){ + $('input[name="ip"]').attr('disabled','disabled'); + $('input[name="port"]').attr('disabled','disabled'); + $('input[name="passwd"]').attr('placeholder','如果没有密码留空'); + $('input[name="passwd"]').attr('disabled','disabled'); + } + + // change event + $("select[name='save_handler']").change(function() { + var type = $(this).val(); + + var passwd = $('input[name="passwd"]').val(); + if (passwd == ""){ + $('input[name="passwd"]').attr('placeholder','如果没有密码留空'); + } + + var ip = $('input[name="ip"]').val(); + if (ip == ""){ + $('input[name="ip"]').val('127.0.0.1'); + } + + switch (type) { + case 'redis': + var port = $('input[name="port"]').val(); + if (port == ""){ + $('input[name="port"]').val('6379'); + } + $('input[name="ip"]').removeAttr('disabled'); + $('input[name="port"]').removeAttr('disabled'); + $('input[name="passwd"]').removeAttr('disabled'); + break; + case 'files': + $('input[name="ip"]').val("").attr('disabled','disabled'); + $('input[name="port"]').val("").attr('disabled','disabled'); + $('input[name="passwd"]').val("").attr('disabled','disabled'); + break; + case 'memcache': + var port = $('input[name="port"]').val(); + if (port == ""){ + $('input[name="port"]').val('11211'); + } + $('input[name="ip"]').removeAttr('disabled'); + $('input[name="port"]').removeAttr('disabled'); + $('input[name="passwd"]').removeAttr('disabled'); + break; + case 'memcached': + var port = $('input[name="port"]').val(); + if (port == ""){ + $('input[name="port"]').val('11211'); + } + $('input[name="ip"]').removeAttr('disabled'); + $('input[name="port"]').removeAttr('disabled'); + $('input[name="passwd"]').removeAttr('disabled'); + break; + } + }); + + //load session stats + phpPost('get_session_count', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + if(!rdata.status){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + var rdata = rdata.data; + + var html_var = "
                                清理Session文件
                                \ +
                                \ +
                                \ +
                                总Session文件数量"+rdata.total+"
                                \ +
                                可清理的Session文件数量"+rdata.oldfile+"
                                \ +
                                \ + "; + + $("#session_clear").html(html_var); + + + $('#clean_func').click(function(){ + phpPost('clean_session_old', version, '', function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + showMsg(rdata.msg,function(){ + getSessionConfig(version); + },{ icon: rdata.status ? 1 : 2 }); + }); + }); + }); + }); + +} + +function setSessionConfig(version){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var passwd = $('input[name="passwd"]').val(); + var save_handler = $("select[name='save_handler']").val(); + var data = { + ip:ip, + port:port, + passwd:passwd, + save_handler:save_handler, + }; + phpPost('set_session_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +//禁用函数 +function disableFunc(version) { + phpPost('get_disable_func', version,'',function(data){ + var rdata = $.parseJSON(data.data); + var disable_functions = rdata.disable_functions.split(','); + var dbody = '' + for (var i = 0; i < disable_functions.length; i++) { + if (disable_functions[i] == '') continue; + dbody += "" + disable_functions[i] + "删除"; + } + + var con = "
                                " + + "" + + "" + + "
                                " + + "
                                " + + "" + + "" + dbody + "" + + "
                                名称操作
                                "; + + con += '
                                  \ +
                                • 在此处可以禁用指定函数的调用,以增强环境安全性!
                                • \ +
                                • 强烈建议禁用如exec,system等危险函数!
                                • \ +
                                '; + + $(".soft-man-con").html(con); + }); +} +//设置禁用函数 +function setDisableFunc(version, act, fs) { + var fsArr = fs.split(','); + if (act == 1) { + var functions = $("#disable_function_val").val(); + for (var i = 0; i < fsArr.length; i++) { + if (functions == fsArr[i]) { + layer.msg(lan.soft.fun_msg, { icon: 5 }); + return; + } + } + fs += ',' + functions; + msg = '添加成功'; + } else { + + fs = ''; + for (var i = 0; i < fsArr.length; i++) { + if (act == fsArr[i]) continue; + fs += fsArr[i] + ',' + } + msg = '删除成功'; + fs = fs.substr(0, fs.length - 1); + } + + var data = { + 'version':version, + 'disable_functions':fs, + }; + + phpPost('set_disable_func', version,data,function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.status ? msg : rdata.msg, function(){ + disableFunc(version); + } ,{ icon: rdata.status ? 1 : 2 }); + }); +} + + +//phpinfo +// function getPhpinfo(version) { +// var con = ''; +// $(".soft-man-con").html(con); +// } + +//获取PHPInfo +function getPHPInfo_old(version) { + phpPost('get_phpinfo', version, '', function(data){ + var rdata = data.data; + layer.open({ + type: 1, + title: "PHP-" + version + "-PHPINFO", + area: ['90%', '90%'], + closeBtn: 2, + shadeClose: true, + content: rdata + }); + }); +} + +function getPHPInfo(version) { + phpPostCallback('get_php_info', version, {}, function(data){ + if (!data.status){ + layer.msg(rdata.msg, { icon: 2 }); + return; + } + + layer.open({ + type: 1, + title: "PHP-" + version + "-PHPINFO", + area: ['70%', '90%'], + closeBtn: 2, + shadeClose: true, + content: data.data.replace('a:link {color: #009; text-decoration: none; background-color: #fff;}', '').replace('a:link {color: #000099; text-decoration: none; background-color: #ffffff;}', '') + }); + }) +} + + +function phpLibConfig(version){ + + // phpPost('get_lib_conf', version, {}, function(rdata){ + // var rdata = $.parseJSON(rdata.data); + // }); + + phpPostCallback('get_lib_conf', version, {}, function(rdata){ + var rdata = rdata.data; + + if (!rdata.status){ + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + + var libs = rdata.data; + var body = ''; + var opt = ''; + + for (var i = 0; i < libs.length; i++) { + if (libs[i].versions.indexOf(version) == -1){ + continue; + } + + if (libs[i]['task'] == '-1' && libs[i].phpversions.indexOf(version) != -1) { + opt = '安装' + } else if (libs[i]['task'] == '0' && libs[i].phpversions.indexOf(version) != -1) { + opt = '等待安装...' + } else if (libs[i].status) { + opt = '卸载' + } else { + opt = '安装' + } + + body += '' + + '' + libs[i].name + '' + + '' + libs[i].type + '' + + '' + libs[i].msg + '' + + '' + + '' + opt + '' + + ''; + } + + + var con = '
                                ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + body + '' + + '
                                名称类型说明状态操作
                                ' + + '
                                ' + + '
                                  \ +
                                • 请按实际需求安装扩展,不要安装不必要的PHP扩展,这会影响PHP执行效率,甚至出现异常
                                • \ +
                                • Redis扩展只允许在1个PHP版本中使用,安装到其它PHP版本请在[软件管理]重装Redis
                                • \ +
                                • opcache/xcache/apc等脚本缓存扩展,请只安装其中1个,否则可能导致您的站点程序异常
                                • \ +
                                • ioncube要在ZendGuardLoader/opcache前安装,否则可能导致您的站点程序异常
                                • \ +
                                '; + $('.soft-man-con').html(con); + }); + +} + +//安装扩展 +function installPHPLib(version, name, title, pathinfo) { + layer.confirm('您真的要安装{1}吗?'.replace('{1}', name), { icon: 3, closeBtn: 2 }, function() { + name = name.toLowerCase(); + var data = "name=" + name + "&version=" + version + "&type=1"; + + phpPost('install_lib', version, data, function(data){ + var rdata = $.parseJSON(data.data); + // layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + showMsg(rdata.msg, function(){ + getTaskCount(); + phpLibConfig(version); + },{ icon: rdata.status ? 1 : 2 }); + + }); + }); +} + +//卸载扩展 +function uninstallPHPLib(version, name, title, pathinfo) { + layer.confirm('您真的要卸载{1}吗?'.replace('{1}', name), { icon: 3, closeBtn: 2 }, function() { + name = name.toLowerCase(); + var data = 'name=' + name + '&version=' + version; + phpPost('uninstall_lib', version, data, function(data){ + var rdata = $.parseJSON(data.data); + // layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + showMsg(rdata.msg, function(){ + getTaskCount(); + phpLibConfig(version); + },{ icon: rdata.status ? 1 : 2 },5000); + + }); + }); +} \ No newline at end of file diff --git a/plugins/php/lib/freetype_new.sh b/plugins/php/lib/freetype_new.sh new file mode 100644 index 000000000..f68ab52b3 --- /dev/null +++ b/plugins/php/lib/freetype_new.sh @@ -0,0 +1,35 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +if [ ! -d ${SERVER_ROOT}/freetype ];then + cd $SOURCE_ROOT + + if [ ! -f $SOURCE_ROOT/freetype-2.12.1.tar.gz ];then + wget -O freetype-2.12.1.tar.gz --no-check-certificate https://download.savannah.gnu.org/releases/freetype/freetype-2.12.1.tar.gz -T 5 + fi + + if [ ! -d $SOURCE_ROOT/freetype-2.12.1 ];then + tar zxvf freetype-2.12.1.tar.gz + cd freetype-2.12.1 + else + cd freetype-2.12.1 + fi + + ./configure --prefix=${SERVER_ROOT}/freetype && make && make install + cd $SOURCE_ROOT && rm -rf freetype-2.12.1 + #rm -rf freetype-2.12.1.tar.gz + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/freetype-2.12.1 + +fi \ No newline at end of file diff --git a/plugins/php/lib/freetype_old.sh b/plugins/php/lib/freetype_old.sh new file mode 100644 index 000000000..e9c559ce2 --- /dev/null +++ b/plugins/php/lib/freetype_old.sh @@ -0,0 +1,34 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +if [ ! -d ${SERVER_ROOT}/freetype_old ];then + cd $SOURCE_ROOT + + if [ ! -f $SOURCE_ROOT/freetype-2.7.1.tar.gz ];then + wget -O freetype-2.7.1.tar.gz --no-check-certificate https://download.savannah.gnu.org/releases/freetype/freetype-2.7.1.tar.gz -T 5 + fi + + if [ ! -d $SOURCE_ROOT/freetype-2.7.1 ];then + tar zxvf freetype-2.7.1.tar.gz + cd freetype-2.7.1 + else + cd freetype-2.7.1 + fi + + ./configure --prefix=${SERVER_ROOT}/freetype_old && make && make install + cd $SOURCE_ROOT && rm -rf freetype-2.7.1 + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/freetype-2.7.1 + #rm -rf freetype-2.7.1.tar.gz +fi \ No newline at end of file diff --git a/plugins/php/lib/icu.sh b/plugins/php/lib/icu.sh new file mode 100644 index 000000000..b635f5ff8 --- /dev/null +++ b/plugins/php/lib/icu.sh @@ -0,0 +1,56 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + HTTP_PREFIX="https://mirror.ghproxy.com/" +fi + +if [ ! -d ${SERVER_ROOT}/icu ];then + + cd ${SOURCE_ROOT} + + if [ "$LOCAL_ADDR" == 'cn' ];then + if [ ! -f ${SOURCE_ROOT}/icu4c-52_2-src.tgz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/icu4c-52_2-src.tgz https://dl.midoks.icu/lib/icu4c-52_2-src.tgz -T 20 + fi + fi + + if [ ! -f ${SOURCE_ROOT}/icu4c-52_2-src.tgz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/icu4c-52_2-src.tgz https://github.com/unicode-org/icu/releases/download/release-52-2/icu4c-52_2-src.tgz + fi + + if [ ! -d ${SERVER_ROOT}/lib/icu/lib ];then + cd ${SOURCE_ROOT} && tar -zxvf icu4c-52_2-src.tgz + + cd ${SOURCE_ROOT}/icu/source + ./runConfigureICU Linux --prefix=${SERVER_ROOT}/icu && make CXXFLAGS="-g -O2 -std=c++11" && make install + + # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/www/server/lib/icu/lib + if [ -d /etc/ld.so.conf.d ];then + echo "/www/server/lib/icu/lib" > /etc/ld.so.conf.d/mw-icu.conf + elif [ -f /etc/ld.so.conf ]; then + echo "/www/server/lib/icu/lib" >> /etc/ld.so.conf + fi + + ldconfig + + cd $SOURCE_ROOT && rm -rf ${SOURCE_ROOT}/icu + fi + +fi \ No newline at end of file diff --git a/plugins/php/lib/imagemagick.sh b/plugins/php/lib/imagemagick.sh new file mode 100644 index 000000000..8e1d074e0 --- /dev/null +++ b/plugins/php/lib/imagemagick.sh @@ -0,0 +1,39 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +igmVersion="7.1.1-15" +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +if [ ! -d ${SERVER_ROOT}/ImageMagick ];then + cd ${SOURCE_ROOT} + if [ ! -f ${SOURCE_ROOT}/ImageMagick-${igmVersion}.tar.gz ];then + wget --no-check-certificate -O ImageMagick-${igmVersion}.tar.gz https://imagemagick.org/archive/ImageMagick-${igmVersion}.tar.gz -T 20 + fi + + tar -zxf ImageMagick-${igmVersion}.tar.gz + cd ImageMagick-${igmVersion} + ./configure --prefix=${SERVER_ROOT}/ImageMagick --disable-openmp + make && make install + + if [ -d /etc/ld.so.conf.d ];then + echo "/www/server/lib/ImageMagick/lib" > /etc/ld.so.conf.d/ImageMagick.conf + elif [ -f /etc/ld.so.conf ]; then + echo "/www/server/lib/ImageMagick/lib" >> /etc/ld.so.conf + fi + + ldconfig + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/ImageMagick-${igmVersion} +fi + + diff --git a/plugins/php/lib/libedit.sh b/plugins/php/lib/libedit.sh new file mode 100644 index 000000000..405ed0f4a --- /dev/null +++ b/plugins/php/lib/libedit.sh @@ -0,0 +1,45 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/php/lib && bash libedit.sh + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + + +# LOCAL_ADDR=common +# cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") +# if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then +# LOCAL_ADDR=cn +# fi + +if [ ! -d ${SERVER_ROOT}/libedit ];then + cd $SOURCE_ROOT + + VERSION="20230828-3.1" + + if [ ! -f ${SOURCE_ROOT}/libedit-${VERSION}.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/libedit-${VERSION}.tar.gz https://thrysoee.dk/editline/libedit-${VERSION}.tar.gz + fi + + if [ ! -d ${SOURCE_ROOT}/libedit-${VERSION} ];then + cd $SOURCE_ROOT && tar -zxvf libedit-${VERSION}.tar.gz + fi + + cd ${SOURCE_ROOT}/libedit-${VERSION} + + ./configure --prefix=${SERVER_ROOT}/libedit && make && make install + + if [ -d $SOURCE_ROOT/libedit-${VERSION} ];then + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/libedit-${VERSION} + fi +fi \ No newline at end of file diff --git a/plugins/php/lib/libiconv.sh b/plugins/php/lib/libiconv.sh new file mode 100644 index 000000000..92d3735c0 --- /dev/null +++ b/plugins/php/lib/libiconv.sh @@ -0,0 +1,50 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/php/lib && bash libiconv.sh + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + HTTP_PREFIX="https://mirror.ghproxy.com/" +fi + +if [ ! -d ${SERVER_ROOT}/libiconv ];then + cd $SOURCE_ROOT + + if [ "$LOCAL_ADDR" == 'cn' ];then + if [ ! -f ${SOURCE_ROOT}/libiconv-1.15.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/libiconv-1.15.tar.gz https://dl.midoks.icu/lib/libiconv-1.15.tar.gz -T 20 + fi + fi + + if [ ! -f ${SOURCE_ROOT}/libiconv-1.15.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/libiconv-1.15.tar.gz https://github.com/midoks/mdserver-web/releases/download/init/libiconv-1.15.tar.gz -T 5 + fi + + if [ ! -d ${SOURCE_ROOT}/libiconv-1.15 ];then + cd $SOURCE_ROOT && tar -zxvf libiconv-1.15.tar.gz + fi + + cd ${SOURCE_ROOT}/libiconv-1.15 + + ./configure --prefix=${SERVER_ROOT}/libiconv --enable-static && make && make install + + if [ -d $SOURCE_ROOT/libiconv-1.15 ];then + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/libiconv-1.15 + fi +fi \ No newline at end of file diff --git a/plugins/php/lib/libmcrypt.sh b/plugins/php/lib/libmcrypt.sh new file mode 100644 index 000000000..57b9e6a48 --- /dev/null +++ b/plugins/php/lib/libmcrypt.sh @@ -0,0 +1,50 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + + +ISFIND="0" +SYS_DIR=(/usr/local /usr) +for S_DIR in ${SYS_DIR[@]}; do + if [ -f $S_DIR/include/mcrypt.h ];then + ISFIND="1" + fi +done + +if [ $ISFIND == "0" ];then + cd $SOURCE_ROOT + if [ ! -f ${SOURCE_ROOT}/libmcrypt-2.5.8.tar.gz ];then + wget --no-check-certificate -O libmcrypt-2.5.8.tar.gz https://sourceforge.net/projects/mcrypt/files/Libmcrypt/2.5.8/libmcrypt-2.5.8.tar.gz -T 20 + fi + + tar -zxvf libmcrypt-2.5.8.tar.gz + cd libmcrypt-2.5.8 + ./configure && make && make install && make clean + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/libmcrypt-2.5.8 +fi + + +# if [ ! -d ${SERVER_ROOT}/libmcrypt ];then + +# cd $SOURCE_ROOT +# if [ ! -f ${SOURCE_ROOT}/libmcrypt-2.5.8.tar.gz ];then +# wget -O libmcrypt-2.5.8.tar.gz --no-check-certificate https://sourceforge.net/projects/mcrypt/files/Libmcrypt/2.5.8/libmcrypt-2.5.8.tar.gz -T 20 +# fi + +# tar -zxvf libmcrypt-2.5.8.tar.gz +# cd libmcrypt-2.5.8 + +# ./configure --prefix=${SERVER_ROOT}/libmcrypt && make && make install +# fi \ No newline at end of file diff --git a/plugins/php/lib/libmemcached.sh b/plugins/php/lib/libmemcached.sh new file mode 100644 index 000000000..8352be814 --- /dev/null +++ b/plugins/php/lib/libmemcached.sh @@ -0,0 +1,46 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +#----------------------------- libmemcached start -------------------------# +# if [ ! -d ${SERVER_ROOT}/libmemcached ];then +# cd ${SOURCE_ROOT} +# if [ ! -f ${SOURCE_ROOT}/libmemcached-1.0.4.tar.gz ];then +# wget -O libmemcached-1.0.4.tar.gz https://launchpad.net/libmemcached/1.0/1.0.4/+download/libmemcached-1.0.4.tar.gz -T 20 +# fi +# tar -zxf libmemcached-1.0.4.tar.gz +# cd libmemcached-1.0.4 +# ./configure --prefix=${SERVER_ROOT}/libmemcached -with-memcached && make && make install +# fi +#----------------------------- libmemcached end -------------------------# + + +#----------------------------- libmemcached start -------------------------# +if [ ! -d ${SERVER_ROOT}/libmemcached ];then + cd ${SOURCE_ROOT} + if [ ! -f ${SOURCE_ROOT}/libmemcached-1.0.18.tar.gz ];then + wget --no-check-certificate -O libmemcached-1.0.18.tar.gz https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz -T 20 + fi + tar -zxf libmemcached-1.0.18.tar.gz + cd libmemcached-1.0.18 + + # sed -i '_bak' "41,52s#opt_servers == false#opt_servers#g" ${SERVER_ROOT}/libmemcached-1.0.18/clients/memflush.cc + sed -i "s#opt_servers == false#\!opt_servers#g" ${SERVER_ROOT}/libmemcached-1.0.18/clients/memflush.cc + # sed -i "s#opt_servers == false#\!opt_servers#g" /www/server/source/lib/libmemcached-1.0.18/clients/memflush.cc + ./configure --prefix=${SERVER_ROOT}/libmemcached -with-memcached && make && make install + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/libmemcached-1.0.18 + +fi +#----------------------------- libmemcached end -------------------------# \ No newline at end of file diff --git a/plugins/php/lib/libsodium.sh b/plugins/php/lib/libsodium.sh new file mode 100644 index 000000000..6c2b389f2 --- /dev/null +++ b/plugins/php/lib/libsodium.sh @@ -0,0 +1,31 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + + +VERSION=1.0.18 +#----------------------------- libsodium start -------------------------# +if [ ! -f /usr/local/lib/libsodium.so ];then + cd ${SOURCE_ROOT} + if [ ! -f ${SOURCE_ROOT}/libsodium-${VERSION}-stable.tar.gz ];then + # wget --no-check-certificate -O libsodium-1.0.18-stable.tar.gz https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable.tar.gz -T 20 + wget --no-check-certificate -O libsodium-${VERSION}-stable.tar.gz https://download.libsodium.org/libsodium/releases/libsodium-${VERSION}-stable.tar.gz -T 20 + fi + tar -zxvf libsodium-${VERSION}-stable.tar.gz + cd libsodium-stable + ./configure && make && make check && make install + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/libsodium-stable +fi +#----------------------------- libsodium end -------------------------# \ No newline at end of file diff --git a/plugins/php/lib/libzip.sh b/plugins/php/lib/libzip.sh new file mode 100644 index 000000000..01d80b248 --- /dev/null +++ b/plugins/php/lib/libzip.sh @@ -0,0 +1,59 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +mkdir -p $SOURCE_ROOT + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + HTTP_PREFIX="https://mirror.ghproxy.com/" +fi +# HTTP_PREFIX="https://" + +if [ ! -d ${SERVER_ROOT}/libzip ];then + + cd $SOURCE_ROOT + + if [ "$LOCAL_ADDR" == 'cn' ];then + if [ ! -f ${SOURCE_ROOT}/libzip-1.3.2.tar.gz ];then + wget --no-check-certificate -O libzip-1.3.2.tar.gz https://dl.midoks.icu/lib/libzip-1.3.2.tar.gz -T 20 + fi + fi + + # if [ ! -f ${SOURCE_ROOT}/libzip-1.3.2.tar.gz ];then + # wget --no-check-certificate -O libzip-1.3.2.tar.gz ${HTTP_PREFIX}github.com/midoks/mdserver-web/releases/download/init/libzip-1.3.2.tar.gz -T 20 + # fi + + if [ ! -f ${SOURCE_ROOT}/libzip-1.3.2.tar.gz ];then + wget --no-check-certificate -O libzip-1.3.2.tar.gz https://github.com/midoks/mdserver-web/releases/download/init/libzip-1.3.2.tar.gz -T 20 + fi + + if [ ! -d ${SOURCE_ROOT}/libzip-1.3.2 ];then + cd $SOURCE_ROOT && tar -zxvf libzip-1.3.2.tar.gz + fi + + cd ${SOURCE_ROOT}/libzip-1.3.2 + + ./configure --prefix=${SERVER_ROOT}/libzip && make && make install + #cd $SOURCE_ROOT + + if [ "$?" == "0" ];then + rm -rf ${SOURCE_ROOT}/libzip-1.3.2 + rm -rf ${SOURCE_ROOT}/libzip-1.3.2.tar.gz + fi + +fi \ No newline at end of file diff --git a/plugins/php/lib/oniguruma.sh b/plugins/php/lib/oniguruma.sh new file mode 100644 index 000000000..33c8ab0fb --- /dev/null +++ b/plugins/php/lib/oniguruma.sh @@ -0,0 +1,36 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +HTTP_PREFIX="https://" +cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + HTTP_PREFIX="https://mirror.ghproxy.com/" +fi + +which onig-config +if [ "$?" != "0" ];then + cd ${SOURCE_ROOT} + if [ ! -f ${SOURCE_ROOT}/oniguruma-6.9.4.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/oniguruma-6.9.4.tar.gz ${HTTP_PREFIX}github.com/kkos/oniguruma/archive/v6.9.4.tar.gz + fi + + if [ ! -d cd ${SOURCE_ROOT}/oniguruma-6.9.4 ];then + cd ${SOURCE_ROOT} && tar -zxvf oniguruma-6.9.4.tar.gz + fi + + cd ${SOURCE_ROOT}/oniguruma-6.9.4 && ./autogen.sh && ./configure --prefix=/usr && make && make install + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/oniguruma-6.9.4 +fi + diff --git a/plugins/php/lib/openssl.sh b/plugins/php/lib/openssl.sh new file mode 100644 index 000000000..b5d635d01 --- /dev/null +++ b/plugins/php/lib/openssl.sh @@ -0,0 +1,30 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +opensslVersion="3.5.2" +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib +mkdir -p $SOURCE_ROOT + +if [ ! -d ${SERVER_ROOT}/openssl ];then + cd ${SOURCE_ROOT} + if [ ! -f ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + tar -zxvf openssl-${opensslVersion}.tar.gz + cd openssl-${opensslVersion} + ./config --prefix=${SERVER_ROOT}/openssl zlib-dynamic shared + make && make install + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/openssl-${opensslVersion} +fi + diff --git a/plugins/php/lib/openssl_10.sh b/plugins/php/lib/openssl_10.sh new file mode 100644 index 000000000..d369d4f25 --- /dev/null +++ b/plugins/php/lib/openssl_10.sh @@ -0,0 +1,60 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +opensslVersion="1.0.2q" +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + HTTP_PREFIX="https://mirror.ghproxy.com/" +fi + +if [ ! -d ${SERVER_ROOT}/openssl10 ];then + cd ${SOURCE_ROOT} + + if [ "$LOCAL_ADDR" == 'cn' ];then + if [ ! -f ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O openssl-${opensslVersion}.tar.gz https://dl.midoks.icu/lib/openssl-${opensslVersion}.tar.gz -T 20 + fi + fi + + # if [ ! -f ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz ];then + # wget --no-check-certificate ${HTTP_PREFIX}/midoks/mdserver-web/releases/download/init/openssl-${opensslVersion}.tar.gz -T 20 + # fi + + if [ ! -f ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O openssl-${opensslVersion}.tar.gz https://github.com/midoks/mdserver-web/releases/download/init/openssl-${opensslVersion}.tar.gz -T 20 + fi + + tar -zxf openssl-${opensslVersion}.tar.gz + cd openssl-${opensslVersion} + ./config --openssldir=${SERVER_ROOT}/openssl10 zlib-dynamic shared + make && make install + + # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/www/server/lib/openssl10/lib + if [ -d /etc/ld.so.conf.d ];then + echo "/www/server/lib/openssl10/lib" > /etc/ld.so.conf.d/openssl10.conf + elif [ -f /etc/ld.so.conf ]; then + echo "/www/server/lib/openssl10/lib" >> /etc/ld.so.conf + fi + + ldconfig + # ldconfig -p | grep openssl + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/openssl-${opensslVersion} +fi + + diff --git a/plugins/php/lib/openssl_11.sh b/plugins/php/lib/openssl_11.sh new file mode 100644 index 000000000..09655ab85 --- /dev/null +++ b/plugins/php/lib/openssl_11.sh @@ -0,0 +1,39 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +opensslVersion="1.1.1p" +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib +mkdir -p $SOURCE_ROOT + +if [ ! -d ${SERVER_ROOT}/openssl11 ];then + cd ${SOURCE_ROOT} + if [ ! -f ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + tar -zxvf openssl-${opensslVersion}.tar.gz + cd openssl-${opensslVersion} + ./config --prefix=${SERVER_ROOT}/openssl11 zlib-dynamic shared + make && make install + + # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/www/server/lib/openssl11/lib + if [ -d /etc/ld.so.conf.d ];then + echo "/www/server/lib/openssl11/lib" > /etc/ld.so.conf.d/openssl11.conf + elif [ -f /etc/ld.so.conf ]; then + echo "/www/server/lib/openssl11/lib" >> /etc/ld.so.conf + fi + + ldconfig + # ldconfig -p | grep openssl + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/openssl-${opensslVersion} +fi + diff --git a/plugins/php/lib/openssl_30.sh b/plugins/php/lib/openssl_30.sh new file mode 100644 index 000000000..9bd748954 --- /dev/null +++ b/plugins/php/lib/openssl_30.sh @@ -0,0 +1,41 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +opensslVersion="3.0.10" +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib +mkdir -p $SOURCE_ROOT + +if [ ! -d ${SERVER_ROOT}/openssl30 ];then + cd ${SOURCE_ROOT} + if [ ! -f ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + tar -zxvf openssl-${opensslVersion}.tar.gz + cd openssl-${opensslVersion} + ./config --prefix=${SERVER_ROOT}/openssl30 zlib-dynamic shared + make && make install + + + # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/www/server/lib/openssl30/lib + # if [ -d /etc/ld.so.conf.d ];then + # echo "/www/server/lib/openssl30/lib64" > /etc/ld.so.conf.d/openssl30.conf + # elif [ -f /etc/ld.so.conf ]; then + # echo "/www/server/lib/openssl30/lib64" >> /etc/ld.so.conf + # fi + + ldconfig + # ldconfig -p | grep openssl + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/openssl-${opensslVersion} +fi + diff --git a/plugins/php/lib/openssl_35.sh b/plugins/php/lib/openssl_35.sh new file mode 100644 index 000000000..7a4e619e9 --- /dev/null +++ b/plugins/php/lib/openssl_35.sh @@ -0,0 +1,41 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +opensslVersion="3.5.2" +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib +mkdir -p $SOURCE_ROOT + +if [ ! -d ${SERVER_ROOT}/openssl35 ];then + cd ${SOURCE_ROOT} + if [ ! -f ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/openssl-${opensslVersion}.tar.gz https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz + fi + tar -zxvf openssl-${opensslVersion}.tar.gz + cd openssl-${opensslVersion} + ./config --prefix=${SERVER_ROOT}/openssl35 zlib-dynamic shared + make && make install + + + # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/www/server/lib/openssl35/lib + # if [ -d /etc/ld.so.conf.d ];then + # echo "/www/server/lib/openssl35/lib64" > /etc/ld.so.conf.d/openssl35.conf + # elif [ -f /etc/ld.so.conf ]; then + # echo "/www/server/lib/openssl35/lib64" >> /etc/ld.so.conf + # fi + + # ldconfig + # ldconfig -p | grep openssl + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/openssl-${opensslVersion} +fi + diff --git a/plugins/php/lib/pcre.sh b/plugins/php/lib/pcre.sh new file mode 100644 index 000000000..96f6033fb --- /dev/null +++ b/plugins/php/lib/pcre.sh @@ -0,0 +1,36 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib +mkdir -p $SOURCE_ROOT + +pcreVersion='8.38' + +if [ ! -d ${SERVER_ROOT}/pcre ];then + cd ${SOURCE_ROOT} + + if [ ! -f ${SOURCE_ROOT}/pcre-${pcreVersion}.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/pcre-${pcreVersion}.tar.gz https://netix.dl.sourceforge.net/project/pcre/pcre/${pcreVersion}/pcre-${pcreVersion}.tar.gz + fi + + + if [ ! -d ${SOURCE_ROOT}/pcre-${pcreVersion} ];then + cd ${SOURCE_ROOT} && tar -zxvf pcre-${pcreVersion}.tar.gz + + fi + + cd ${SOURCE_ROOT}/pcre-${pcreVersion} + ./configure --prefix=${SERVER_ROOT}/pcre + make && make install + + cd $SOURCE_ROOT && rm -rf ${SOURCE_ROOT}/pcre-${pcreVersion} +fi + diff --git a/plugins/php/lib/zlib.sh b/plugins/php/lib/zlib.sh new file mode 100644 index 000000000..239e4d7c4 --- /dev/null +++ b/plugins/php/lib/zlib.sh @@ -0,0 +1,55 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +SERVER_ROOT=$rootPath/lib +SOURCE_ROOT=$rootPath/source/lib + +mkdir -p $SOURCE_ROOT + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + HTTP_PREFIX="https://mirror.ghproxy.com/" +fi +# HTTP_PREFIX="https://" + +if [ ! -d ${SERVER_ROOT}/zlib ];then + + cd $SOURCE_ROOT + + if [ "$LOCAL_ADDR" == 'cn' ];then + if [ ! -f ${SOURCE_ROOT}/${SOURCE_ROOT}/zlib-1.2.11.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/zlib-1.2.11.tar.gz https://dl.midoks.icu/lib/zlib-1.2.11.tar.gz -T 20 + fi + fi + + # if [ ! -f ${SOURCE_ROOT}/zlib-1.2.11.tar.gz ];then + # wget --no-check-certificate -O ${SOURCE_ROOT}/zlib-1.2.11.tar.gz ${HTTP_PREFIX}github.com/madler/zlib/archive/v1.2.11.tar.gz -T 20 + # fi + + if [ ! -f ${SOURCE_ROOT}/zlib-1.2.11.tar.gz ];then + wget --no-check-certificate -O ${SOURCE_ROOT}/zlib-1.2.11.tar.gz https://github.com/madler/zlib/archive/v1.2.11.tar.gz -T 20 + fi + + if [ ! -d ${SOURCE_ROOT}/zlib-1.2.11 ];then + cd $SOURCE_ROOT && tar -zxvf zlib-1.2.11.tar.gz + fi + cd ${SOURCE_ROOT}/zlib-1.2.11 + + ./configure --prefix=${SERVER_ROOT}/zlib && make && make install + + cd $SOURCE_ROOT && rm -rf $SOURCE_ROOT/zlib-1.2.11 + #rm -rf zlib-1.2.11 + #rm -rf zlib-1.2.11.tar.gz +fi \ No newline at end of file diff --git a/plugins/php/versions/52/eaccelerator.sh b/plugins/php/versions/52/eaccelerator.sh new file mode 100755 index 000000000..edeb19619 --- /dev/null +++ b/plugins/php/versions/52/eaccelerator.sh @@ -0,0 +1,115 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# php 5.2.17 + eaccelerator 0.9.5.3 +# php 5.3.24 + eaccelerator 0.9.6.1 +# php 5.4.14 + eaccelerator 1.0 dev + + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=eaccelerator +LIBV=0.9.6 +sysName=`uname` +actionType=$1 +version=$2 + + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tar.gz https://github.com/eaccelerator/eaccelerator/archive/${LIBV}.tar.gz + # wget -O $php_lib/${LIBNAME}-${LIBV}.tar.bz2 http://dl.wdlinux.cn:5180/soft/eaccelerator-0.9.6.1.tar.bz2 + cd $php_lib && tar -zxvf ${LIBNAME}-${LIBV}.tar.gz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + --enable-eaccelerator=shared + make && make install && make clean + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + EA_DIR=/tmp/eaccelerator + mkdir -p $EA_DIR + chmod 777 -R $EA_DIR + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + echo "${LIBNAME}.enable=1" >> $serverPath/php/$version/etc/php.ini + echo "${LIBNAME}.optimizer=1" >> $serverPath/php/$version/etc/php.ini + echo "${LIBNAME}.shm_size=64" >> $serverPath/php/$version/etc/php.ini + echo "${LIBNAME}.cache_dir=${EA_DIR}" >> $serverPath/php/$version/etc/php.ini + echo "${LIBNAME}.allowed_admin_path=/www/wwwroot/you_project_dir" >> $serverPath/php/$version/etc/php.ini + + + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$vphp not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/52/gd.sh b/plugins/php/versions/52/gd.sh new file mode 100755 index 000000000..109e5806e --- /dev/null +++ b/plugins/php/versions/52/gd.sh @@ -0,0 +1,137 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +LIBNAME=gd +LIBV=0 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + + +sysName=`uname` +echo "use system: ${sysName}" + +if [ ${sysName} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' +elif grep -Eqi "Raspbian" /etc/issue || grep -Eq "Raspbian" /etc/*-release; then + OSNAME='raspbian' +else + OSNAME='unknow' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + # cp -frp /usr/lib64/libldap* /usr/lib/ + + # ln -s /usr/lib64/libjpeg.so /usr/lib/libjpeg.so + # ln -s /usr/lib64/libpng.so /usr/lib/libpng.so + + if [ ! -f /usr/lib/libjpeg.so ];then + ln -s /usr/lib64/libjpeg.so /usr/lib/libjpeg.so + fi + + if [ ! -f /usr/lib/libpng.so ];then + ln -s /usr/lib64/libpng.so /usr/lib/libpng.so + fi + + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash install.sh install ${version} + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + --with-gd \ + --with-jpeg-dir=/usr/lib \ + --with-freetype-dir=${serverPath}/lib/freetype_old \ + --enable-gd-native-ttf + + make clean && make && make install && make clean + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/52/install.sh b/plugins/php/versions/52/install.sh new file mode 100755 index 000000000..caee453a0 --- /dev/null +++ b/plugins/php/versions/52/install.sh @@ -0,0 +1,163 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +version=5.2.17 +PHP_VER=52 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.gz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.gz https://museum.php.net/php5/php-${version}.tar.gz + fi + + if [ ! -f $sourcePath/php/php-5.2.17-fpm-0.5.14.diff.gz ]; then + wget --no-check-certificate -O $sourcePath/php/php-5.2.17-fpm-0.5.14.diff.gz http://php-fpm.org/downloads/php-5.2.17-fpm-0.5.14.diff.gz + fi + + + if [ ! -f $sourcePath/php/php-5.2.17-max-input-vars.patch ]; then + wget --no-check-certificate -O $sourcePath/php/php-5.2.17-max-input-vars.patch https://raw.github.com/laruence/laruence.github.com/master/php-5.2-max-input-vars/php-5.2.17-max-input-vars.patch + fi + + if [ ! -f $sourcePath/php/php-5.x.x.patch ]; then + wget --no-check-certificate -O $sourcePath/php/php-5.x.x.patch https://mail.gnome.org/archives/xml/2012-August/txtbgxGXAvz4N.txt + fi + + + cd $sourcePath/php && tar -zxvf $sourcePath/php/php-${version}.tar.gz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} + + + cd $sourcePath/php + gzip -cd php-5.2.17-fpm-0.5.14.diff.gz | patch -d php${PHP_VER} -p1 + cd $sourcePath/php/php${PHP_VER} + patch -p1 < ../php-5.2.17-max-input-vars.patch + patch -p0 -b < ../php-5.x.x.patch + sed -i "s/\!png_check_sig (sig, 8)/png_sig_cmp (sig, 0, 8)/" ext/gd/libgd/gd_png.c +fi + + +if [ -f $serverPath/php/${PHP_VER}/bin/php.dSYM ];then + mv $serverPath/php/${PHP_VER}/bin/php.dSYM $serverPath/php/${PHP_VER}/bin/php +fi + +if [ -f $serverPath/php/${PHP_VER}/sbin/php-fpm.dSYM ];then + mv $serverPath/php/${PHP_VER}/sbin/php-fpm.dSYM $serverPath/php/${PHP_VER}/sbin/php-fpm +fi + + +if [ -f $serverPath/php/${PHP_VER}/bin/php ];then + return +fi + +OPTIONS='--without-iconv' +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-freetype-dir=${serverPath}/lib/freetype" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +if [ "${SYS_ARCH}" == "aarch64" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + + export MYSQL_LIB_DIR=/usr/lib64/mysql + + cd $sourcePath/php/php${PHP_VER} && ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-xml \ + --enable-shared \ + --with-mysql=mysqlnd \ + --enable-embedded-mysqli=shared \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + $OPTIONS \ + --enable-fastcgi \ + --enable-fpm + # ZEND_EXTRA_LIBS='-liconv' + make && make install && make clean +fi + +if [ "$?" != "0" ];then + echo "install fail!!" + rm -rf $sourcePath/php/php${PHP_VER} + exit 2 +fi + + +if [ -f $serverPath/php/${PHP_VER}/bin/php.dSYM ];then + mv $serverPath/php/${PHP_VER}/bin/php.dSYM $serverPath/php/${PHP_VER}/bin/php +fi + +if [ -f $serverPath/php/${PHP_VER}/sbin/php-fpm.dSYM ];then + mv $serverPath/php/${PHP_VER}/sbin/php-fpm.dSYM $serverPath/php/${PHP_VER}/sbin/php-fpm +fi + +if [ ! -d $serverPath/php/${PHP_VER}/lib/php/extensions/no-debug-non-zts-20060613 ]; then + mkdir -p $serverPath/php/${PHP_VER}/lib/php/extensions/no-debug-non-zts-20060613 +fi + +# ps -ef|grep php/52 |grep -v grep |awk '{print $2}'|xargs kill +# /www/server/php/init.d/php52 start +# /www/server/php/52/sbin/php-fpm start +mkdir -p $serverPath/php/${PHP_VER}/var/log +mkdir -p $serverPath/php/${PHP_VER}/var/run + +#------------------------ install end ------------------------------------# +} + + + +Uninstall_php() +{ + $serverPath/php/init.d/php${PHP_VER} stop + rm -rf $serverPath/php/${PHP_VER} + echo "uninstall php-${version} ..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/52/intl.sh b/plugins/php/versions/52/intl.sh new file mode 100755 index 000000000..803fbeba9 --- /dev/null +++ b/plugins/php/versions/52/intl.sh @@ -0,0 +1,100 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=intl +LIBV=3.0.0 +sysName=`uname` +actionType=$1 +version=$2 + + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + cd $rootPath/plugins/php/lib && /bin/bash icu.sh + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + cd ${LIBNAME}-${LIBV} + $serverPath/php/$version/bin/phpize + + # --with-icu-dir=${serverPath}/lib/icu + ./configure --with-php-config=$serverPath/php/$version/bin/php-config + make clean && make && make install && make clean + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$vphp not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/52/memcache.sh b/plugins/php/versions/52/memcache.sh new file mode 100755 index 000000000..f8cd093be --- /dev/null +++ b/plugins/php/versions/52/memcache.sh @@ -0,0 +1,94 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=memcache +LIBV=2.2.7 +sysName=`uname` +actionType=$1 +version=$2 + + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + cd ${LIBNAME}-${LIBV} + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config --enable-memcache --with-zlib-dir + make && make install && make clean + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install memcache, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/52/zendoptimizer.sh b/plugins/php/versions/52/zendoptimizer.sh new file mode 100755 index 000000000..787a3e498 --- /dev/null +++ b/plugins/php/versions/52/zendoptimizer.sh @@ -0,0 +1,104 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=ZendGuardLoader + +sysName=`uname` +actionType=$1 +version=$2 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/ZendOptimizer.so + + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "ZendOptimizer.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ $sysName == 'Darwin' ]; then + wget -O $php_lib/zend-loader-php5.3.tar.gz http://downloads.zend.com/guard/5.5.0/ZendGuardLoader-php-5.3-darwin-i386.tar.gz + else + wget -O $php_lib/ZendOptimizer-3.3.3-linux-glibc23-x86_64.tar.gz http://downloads.zend.com/optimizer/3.3.3/ZendOptimizer-3.3.3-linux-glibc23-x86_64.tar.gz + fi + + cd $php_lib && tar zxvf ZendOptimizer-3.3.3-linux-glibc23-x86_64.tar.gz + cd $php_lib/ZendOptimizer-3.3.3-linux-glibc23-x86_64 + cp $php_lib/ZendOptimizer-3.3.3-linux-glibc23-x86_64/data/5_2_x_comp/ZendOptimizer.so $serverPath/php/$version/lib/php/extensions/no-debug-non-zts-20060613/ + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[Zend ZendGuard Loader]" >> $serverPath/php/$version/etc/php.ini + echo "zend_extension=$serverPath/php/$version/lib/php/extensions/no-debug-non-zts-20060613/ZendOptimizer.so" >> $serverPath/php/$version/etc/php.ini + echo "zend_loader.enable=1" >> $serverPath/php/$version/etc/php.ini + echo "zend_loader.disable_licensing=0" >> $serverPath/php/$version/etc/php.ini + echo "zend_loader.obfuscation_level_support=3" >> $serverPath/php/$version/etc/php.ini + echo "zend_loader.license_path=" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + sed -i $BAK "/ZendOptimizer.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/zend_loader/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[Zend ZendGuard Loader\]/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/53/apc.sh b/plugins/php/versions/53/apc.sh new file mode 100755 index 000000000..a34967930 --- /dev/null +++ b/plugins/php/versions/53/apc.sh @@ -0,0 +1,105 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=apc +LIBV=3.1.9 +sysName=`uname` +actionType=$1 +version=$2 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + _LIBNAME=$(echo $LIBNAME | tr '[a-z]' '[A-Z]') + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${_LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${_LIBNAME}-${LIBV}.tgz + cd $php_lib + tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${_LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS && \ + make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${_LIBNAME}-${LIBV} + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + echo $extFile + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/53/eaccelerator.sh b/plugins/php/versions/53/eaccelerator.sh new file mode 100755 index 000000000..027e70147 --- /dev/null +++ b/plugins/php/versions/53/eaccelerator.sh @@ -0,0 +1,109 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# php 5.2.17 + eaccelerator 0.9.5.3 +# php 5.3.24 + eaccelerator 0.9.6.1 +# php 5.4.14 + eaccelerator 1.0 dev + + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=eaccelerator +LIBV=0.9.6 +sysName=`uname` +actionType=$1 +version=$2 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tar.gz https://github.com/eaccelerator/eaccelerator/archive/${LIBV}.tar.gz + # wget -O $php_lib/${LIBNAME}-${LIBV}.tar.bz2 http://dl.wdlinux.cn:5180/soft/eaccelerator-0.9.6.1.tar.bz2 + cd $php_lib && tar -zxvf ${LIBNAME}-${LIBV}.tar.gz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + $OPTIONS \ + --enable-eaccelerator=shared + make && make install && make clean + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$vphp not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/53/install.sh b/plugins/php/versions/53/install.sh new file mode 100755 index 000000000..feb326607 --- /dev/null +++ b/plugins/php/versions/53/install.sh @@ -0,0 +1,172 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +version=5.3.29 +PHP_VER=53 +md5_file_ok=dcff9c881fe436708c141cfc56358075 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-5.3.29 ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://museum.php.net/php5/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`md5sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + + +if [ -f $serverPath/php/53/bin/php ];then + return +fi + +# OPTIONS="${OPTIONS} --with-freetype-dir=${serverPath}/lib/freetype_old" +# OPTIONS="${OPTIONS} --with-gd --enable-gd-native-ttf" +# OPTIONS="${OPTIONS} --with-jpeg --with-jpeg-dir=/usr/lib" +OPTIONS='--without-iconv' + +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-freetype-dir=${serverPath}/lib/freetype" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +if [ "${SYS_ARCH}" == "aarch64" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" +fi + +if [ ! -d $serverPath/php/${PHP_VER}/bin ];then + cd $sourcePath/php/php${PHP_VER} && ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --enable-mbstring \ + --enable-exif \ + --enable-hash \ + --enable-libxml \ + --enable-simplexml \ + --enable-dom \ + --enable-filter \ + --enable-xml \ + --enable-ftp \ + --enable-soap \ + --enable-posix \ + --enable-sockets \ + --enable-mbstring \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} + echo "安装php-${version}成功" +fi + + +if [ -f $serverPath/php/53/bin/php.dSYM ];then + mv $serverPath/php/53/bin/php.dSYM $serverPath/php/53/bin/php +fi + +if [ -f $serverPath/php/53/sbin/php-fpm.dSYM ];then + mv $serverPath/php/53/sbin/php-fpm.dSYM $serverPath/php/53/sbin/php-fpm +fi + + +if [ -d $serverPath/php/53 ] && [ ! -d $serverPath/php/53/lib/php/extensions/no-debug-non-zts-20090626 ]; then + mkdir -p $serverPath/php/53/lib/php/extensions/no-debug-non-zts-20090626 +fi + +#------------------------ install end ------------------------------------# +} + + + +Uninstall_php() +{ + $serverPath/php/init.d/php53 stop + rm -rf $serverPath/php/53 + echo "uninstall php-5.3.29 ..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/53/intl.sh b/plugins/php/versions/53/intl.sh new file mode 100755 index 000000000..3b8c2f83d --- /dev/null +++ b/plugins/php/versions/53/intl.sh @@ -0,0 +1,108 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=intl +LIBV=3.0.0 +sysName=`uname` +actionType=$1 +version=$2 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + cd ${rootPath}/plugins/php/lib && /bin/bash icu.sh + if [ -d $php_lib/${LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + $OPTIONS \ + --with-icu-dir=${serverPath}/lib/icu + + make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$vphp not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/53/opcache.sh b/plugins/php/versions/53/opcache.sh new file mode 100755 index 000000000..783c64d5f --- /dev/null +++ b/plugins/php/versions/53/opcache.sh @@ -0,0 +1,114 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=opcache +LIBV=7.0.5 +sysName=`uname` +actionType=$1 +version=$2 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/zendopcache-7.0.5 ];then + wget -O $php_lib/zendopcache-7.0.5.tgz http://pecl.php.net/get/zendopcache-7.0.5.tgz + cd $php_lib && tar xvf zendopcache-7.0.5.tgz + fi + cd $php_lib/zendopcache-7.0.5 + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make && make install && make clean + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "zend_extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + echo "opcache.enable=1" >> $serverPath/php/$version/etc/php.ini + echo "opcache.memory_consumption=128" >> $serverPath/php/$version/etc/php.ini + echo "opcache.interned_strings_buffer=8" >> $serverPath/php/$version/etc/php.ini + echo "opcache.max_accelerated_files=4000" >> $serverPath/php/$version/etc/php.ini + echo "opcache.revalidate_freq=60" >> $serverPath/php/$version/etc/php.ini + echo "opcache.fast_shutdown=1" >> $serverPath/php/$version/etc/php.ini + echo "opcache.enable_cli=1" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/53/zendguardloader.sh b/plugins/php/versions/53/zendguardloader.sh new file mode 100755 index 000000000..77baa7a89 --- /dev/null +++ b/plugins/php/versions/53/zendguardloader.sh @@ -0,0 +1,103 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=ZendGuardLoader + +sysName=`uname` +actionType=$1 +version=$2 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ $sysName == 'Darwin' ]; then + wget -O $php_lib/zend-loader-php5.3.tar.gz http://downloads.zend.com/guard/5.5.0/ZendGuardLoader-php-5.3-darwin-i386.tar.gz + else + wget -O $php_lib/zend-loader-php5.3.tar.gz http://downloads.zend.com/guard/5.5.0/ZendGuardLoader-php-5.3-linux-glibc23-x86_64.tar.gz + fi + + cd $php_lib && tar xvf zend-loader-php5.3.tar.gz + cd ZendGuardLoader-php* && cd php-5.3.x + cp ZendGuardLoader.so $serverPath/php/$version/lib/php/extensions/no-debug-non-zts-20090626/ + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[Zend ZendGuard Loader]" >> $serverPath/php/$version/etc/php.ini + echo "zend_extension=$serverPath/php/$version/lib/php/extensions/no-debug-non-zts-20090626/ZendGuardLoader.so" >> $serverPath/php/$version/etc/php.ini + echo "zend_loader.enable=1" >> $serverPath/php/$version/etc/php.ini + echo "zend_loader.disable_licensing=0" >> $serverPath/php/$version/etc/php.ini + echo "zend_loader.obfuscation_level_support=3" >> $serverPath/php/$version/etc/php.ini + echo "zend_loader.license_path=" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + sed -i $BAK "/ZendGuardLoader.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/zend_loader/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[Zend ZendGuard Loader\]/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/54/apc.sh b/plugins/php/versions/54/apc.sh new file mode 100755 index 000000000..5dd169c07 --- /dev/null +++ b/plugins/php/versions/54/apc.sh @@ -0,0 +1,103 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=apc +_LIBNAME=$(echo $LIBNAME | tr '[a-z]' '[A-Z]') +LIBV=3.1.9 +sysName=`uname` +actionType=$1 +version=$2 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${_LIBNAME}-${LIBV} ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${_LIBNAME}-${LIBV}.tgz + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${_LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${_LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + echo $extFile + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/54/install.sh b/plugins/php/versions/54/install.sh new file mode 100755 index 000000000..1c57bd48d --- /dev/null +++ b/plugins/php/versions/54/install.sh @@ -0,0 +1,180 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +version=5.4.45 +PHP_VER=54 +md5_file_ok=ba580e774ed1ab256f22d1fa69a59311 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + + +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.gz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.gz https://museum.php.net/php5/php-${version}.tar.gz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`md5sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -zxvf $sourcePath/php/php-${version}.tar.gz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +OPTIONS='--without-iconv' +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-freetype-dir=${serverPath}/lib/freetype" + # OPTIONS="${OPTIONS} --with-pcre-dir=${serverPath}/lib/pcre" + # OPTIONS="${OPTIONS} --with-external-pcre=${serverPath}/lib/pcre" +else + OPTIONS="${OPTIONS} --with-readline" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +if [ "${SYS_ARCH}" == "aarch64" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" +fi + +if [ "${SYS_ARCH}" == "arm64" ] && [ "$sysName" == "Darwin" ] ;then + # 修复mac arm64架构下php安装 + # 修复不能识别到sys_icache_invalidate + cat ${curPath}/versions/${PHP_VER}/src/ext/pcre/sljitConfigInternal.h > $sourcePath/php/php${PHP_VER}/ext/pcre/pcrelib/sljit/sljitConfigInternal.h + cat ${curPath}/versions/${PHP_VER}/src/reentrancy.c > $sourcePath/php/php${PHP_VER}/main/reentrancy.c +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} && ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --enable-mbstring \ + --enable-sockets \ + --enable-ftp \ + --enable-simplexml \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + + make clean && make -j${cpuCore} + + #debian11,没有生成php54 man + if [ ! -f sapi/cli/php.1 ];then + cp -rf sapi/cli/php.1.in sapi/cli/php.1 + fi + + if [ ! -f sapi/cgi/php-cgi.1 ];then + cp -rf sapi/cgi/php-cgi.1.in sapi/cgi/php-cgi.1 + fi + + if [ ! -f scripts/man1/phpize.1 ];then + cp -rf scripts/man1/phpize.1.in scripts/man1/phpize.1 + fi + + if [ ! -f scripts/man1/php-config.1 ];then + cp -rf scripts/man1/php-config.1.in scripts/man1/php-config.1 + fi + + if [ ! -f ext/phar/phar.1 ];then + cp -rf ext/phar/phar.1.in ext/phar/phar.1 + fi + + if [ ! -f ext/phar/phar.phar.1 ];then + cp -rf ext/phar/phar.phar.1.in ext/phar/phar.phar.1 + fi + + + make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php54 stop + rm -rf $serverPath/php/54 + echo "卸载php-5.4.45 ..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/54/opcache.sh b/plugins/php/versions/54/opcache.sh new file mode 100755 index 000000000..bb46a63df --- /dev/null +++ b/plugins/php/versions/54/opcache.sh @@ -0,0 +1,115 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=opcache +LIBV=7.0.5 +sysName=`uname` +actionType=$1 +version=$2 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/zendopcache-7.0.5 ];then + wget -O $php_lib/zendopcache-7.0.5.tgz http://pecl.php.net/get/zendopcache-7.0.5.tgz + cd $php_lib && tar xvf zendopcache-7.0.5.tgz + fi + cd $php_lib/zendopcache-7.0.5 + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make && make install && make clean + + # cp modules/opcache.la $serverPath/php/${version}/lib/php/extensions/no-debug-non-zts-20100525/ + + cd $php_lib + rm -rf zendopcache-7.0.5 + rm -rf zendopcache-7.0.5.tgz + rm -rf package.xml + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "zend_extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + echo "opcache.enable=1" >> $serverPath/php/$version/etc/php.ini + echo "opcache.memory_consumption=128" >> $serverPath/php/$version/etc/php.ini + echo "opcache.interned_strings_buffer=8" >> $serverPath/php/$version/etc/php.ini + echo "opcache.max_accelerated_files=4000" >> $serverPath/php/$version/etc/php.ini + echo "opcache.revalidate_freq=60" >> $serverPath/php/$version/etc/php.ini + echo "opcache.fast_shutdown=1" >> $serverPath/php/$version/etc/php.ini + echo "opcache.enable_cli=1" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/54/src/ext/pcre/sljitConfigInternal.h b/plugins/php/versions/54/src/ext/pcre/sljitConfigInternal.h new file mode 100644 index 000000000..23c33609d --- /dev/null +++ b/plugins/php/versions/54/src/ext/pcre/sljitConfigInternal.h @@ -0,0 +1,702 @@ +/* + * Stack-less Just-In-Time compiler + * + * Copyright 2009-2012 Zoltan Herczeg (hzmester@freemail.hu). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SLJIT_CONFIG_INTERNAL_H_ +#define _SLJIT_CONFIG_INTERNAL_H_ + +/* + SLJIT defines the following architecture dependent types and macros: + + Types: + sljit_sb, sljit_ub : signed and unsigned 8 bit byte + sljit_sh, sljit_uh : signed and unsigned 16 bit half-word (short) type + sljit_si, sljit_ui : signed and unsigned 32 bit integer type + sljit_sw, sljit_uw : signed and unsigned machine word, enough to store a pointer + sljit_p : unsgined pointer value (usually the same as sljit_uw, but + some 64 bit ABIs may use 32 bit pointers) + sljit_s : single precision floating point value + sljit_d : double precision floating point value + + Macros for feature detection (boolean): + SLJIT_32BIT_ARCHITECTURE : 32 bit architecture + SLJIT_64BIT_ARCHITECTURE : 64 bit architecture + SLJIT_LITTLE_ENDIAN : little endian architecture + SLJIT_BIG_ENDIAN : big endian architecture + SLJIT_UNALIGNED : allows unaligned memory accesses for non-fpu operations (only!) + SLJIT_INDIRECT_CALL : see SLJIT_FUNC_OFFSET() for more information + + Constants: + SLJIT_NUMBER_OF_REGISTERS : number of available registers + SLJIT_NUMBER_OF_SCRATCH_REGISTERS : number of available scratch registers + SLJIT_NUMBER_OF_SAVED_REGISTERS : number of available saved registers + SLJIT_NUMBER_OF_FLOAT_REGISTERS : number of available floating point registers + SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS : number of available floating point scratch registers + SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS : number of available floating point saved registers + SLJIT_WORD_SHIFT : the shift required to apply when accessing a sljit_sw/sljit_uw array by index + SLJIT_DOUBLE_SHIFT : the shift required to apply when accessing + a double precision floating point array by index + SLJIT_SINGLE_SHIFT : the shift required to apply when accessing + a single precision floating point array by index + SLJIT_LOCALS_OFFSET : local space starting offset (SLJIT_SP + SLJIT_LOCALS_OFFSET) + SLJIT_RETURN_ADDRESS_OFFSET : a return instruction always adds this offset to the return address + + Other macros: + SLJIT_CALL : C calling convention define for both calling JIT form C and C callbacks for JIT + SLJIT_W(number) : defining 64 bit constants on 64 bit architectures (compiler independent helper) +*/ + +/*****************/ +/* Sanity check. */ +/*****************/ + +#if !((defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + || (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + || (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + || (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + || (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + || (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED)) +#error "An architecture must be selected" +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + + (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + + (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + + (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + + (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + + (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + + (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + + (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + + (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + + (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + + (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + + (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + + (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + + (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) >= 2 +#error "Multiple architectures are selected" +#endif + +/********************************************************/ +/* Automatic CPU detection (requires compiler support). */ +/********************************************************/ + +#if (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) + +#ifndef _WIN32 + +#if defined(__i386__) || defined(__i386) +#define SLJIT_CONFIG_X86_32 1 +#elif defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(__arm__) || defined(__ARM__) +#ifdef __thumb2__ +#define SLJIT_CONFIG_ARM_THUMB2 1 +#elif defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) +#define SLJIT_CONFIG_ARM_V7 1 +#else +#define SLJIT_CONFIG_ARM_V5 1 +#endif +#elif defined (__aarch64__) +#define SLJIT_CONFIG_ARM_64 1 +#elif defined(__ppc64__) || defined(__powerpc64__) || defined(_ARCH_PPC64) || (defined(_POWER) && defined(__64BIT__)) +#define SLJIT_CONFIG_PPC_64 1 +#elif defined(__ppc__) || defined(__powerpc__) || defined(_ARCH_PPC) || defined(_ARCH_PWR) || defined(_ARCH_PWR2) || defined(_POWER) +#define SLJIT_CONFIG_PPC_32 1 +#elif defined(__mips__) && !defined(_LP64) +#define SLJIT_CONFIG_MIPS_32 1 +#elif defined(__mips64) +#define SLJIT_CONFIG_MIPS_64 1 +#elif defined(__sparc__) || defined(__sparc) +#define SLJIT_CONFIG_SPARC_32 1 +#elif defined(__tilegx__) +#define SLJIT_CONFIG_TILEGX 1 +#else +/* Unsupported architecture */ +#define SLJIT_CONFIG_UNSUPPORTED 1 +#endif + +#else /* !_WIN32 */ + +#if defined(_M_X64) || defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(_ARM_) +#define SLJIT_CONFIG_ARM_V5 1 +#else +#define SLJIT_CONFIG_X86_32 1 +#endif + +#endif /* !WIN32 */ +#endif /* SLJIT_CONFIG_AUTO */ + +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +#undef SLJIT_EXECUTABLE_ALLOCATOR +#endif + +/******************************/ +/* CPU family type detection. */ +/******************************/ + +#if (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) +#define SLJIT_CONFIG_ARM_32 1 +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) +#define SLJIT_CONFIG_X86 1 +#elif (defined SLJIT_CONFIG_ARM_32 && SLJIT_CONFIG_ARM_32) || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) +#define SLJIT_CONFIG_ARM 1 +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_CONFIG_PPC 1 +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) +#define SLJIT_CONFIG_MIPS 1 +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) || (defined SLJIT_CONFIG_SPARC_64 && SLJIT_CONFIG_SPARC_64) +#define SLJIT_CONFIG_SPARC 1 +#endif + +/**********************************/ +/* External function definitions. */ +/**********************************/ + +#if !(defined SLJIT_STD_MACROS_DEFINED && SLJIT_STD_MACROS_DEFINED) + +/* These libraries are needed for the macros below. */ +#include +#include + +#endif /* SLJIT_STD_MACROS_DEFINED */ + +/* General macros: + Note: SLJIT is designed to be independent from them as possible. + + In release mode (SLJIT_DEBUG is not defined) only the following + external functions are needed: +*/ + +#ifndef SLJIT_MALLOC +#define SLJIT_MALLOC(size, allocator_data) malloc(size) +#endif + +#ifndef SLJIT_FREE +#define SLJIT_FREE(ptr, allocator_data) free(ptr) +#endif + +#ifndef SLJIT_MEMMOVE +#define SLJIT_MEMMOVE(dest, src, len) memmove(dest, src, len) +#endif + +#ifndef SLJIT_ZEROMEM +#define SLJIT_ZEROMEM(dest, len) memset(dest, 0, len) +#endif + +/***************************/ +/* Compiler helper macros. */ +/***************************/ + +#if !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) + +#if defined(__GNUC__) && (__GNUC__ >= 3) +#define SLJIT_LIKELY(x) __builtin_expect((x), 1) +#define SLJIT_UNLIKELY(x) __builtin_expect((x), 0) +#else +#define SLJIT_LIKELY(x) (x) +#define SLJIT_UNLIKELY(x) (x) +#endif + +#endif /* !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) */ + +#ifndef SLJIT_INLINE +/* Inline functions. Some old compilers do not support them. */ +#if defined(__SUNPRO_C) && __SUNPRO_C <= 0x510 +#define SLJIT_INLINE +#else +#define SLJIT_INLINE __inline +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_NOINLINE +/* Not inline functions. */ +#if defined(__GNUC__) +#define SLJIT_NOINLINE __attribute__ ((noinline)) +#else +#define SLJIT_NOINLINE +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_CONST +/* Const variables. */ +#define SLJIT_CONST const +#endif + +#ifndef SLJIT_UNUSED_ARG +/* Unused arguments. */ +#define SLJIT_UNUSED_ARG(arg) (void)arg +#endif + +/*********************************/ +/* Type of public API functions. */ +/*********************************/ + +#if (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) +/* Static ABI functions. For all-in-one programs. */ + +#if defined(__GNUC__) +/* Disable unused warnings in gcc. */ +#define SLJIT_API_FUNC_ATTRIBUTE static __attribute__((unused)) +#else +#define SLJIT_API_FUNC_ATTRIBUTE static +#endif + +#else +#define SLJIT_API_FUNC_ATTRIBUTE +#endif /* (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) */ + +/****************************/ +/* Instruction cache flush. */ +/****************************/ + +#ifndef SLJIT_CACHE_FLUSH + +#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) + +/* Not required to implement on archs with unified caches. */ +#define SLJIT_CACHE_FLUSH(from, to) + +#elif defined __APPLE__ + +/* Supported by all macs since Mac OS 10.5. + However, it does not work on non-jailbroken iOS devices, + although the compilation is successful. */ +#include +#define SLJIT_CACHE_FLUSH(from, to) \ + sys_icache_invalidate((char*)(from), (char*)(to) - (char*)(from)) + +#elif defined __ANDROID__ + +/* Android lacks __clear_cache; instead, cacheflush should be used. */ + +#define SLJIT_CACHE_FLUSH(from, to) \ + cacheflush((long)(from), (long)(to), 0) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +/* The __clear_cache() implementation of GCC is a dummy function on PowerPC. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + ppc_cache_flush((from), (to)) + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +/* The __clear_cache() implementation of GCC is a dummy function on Sparc. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + sparc_cache_flush((from), (to)) + +#else + +/* Calls __ARM_NR_cacheflush on ARM-Linux. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + __clear_cache((char*)(from), (char*)(to)) + +#endif + +#endif /* !SLJIT_CACHE_FLUSH */ + +/******************************************************/ +/* Byte/half/int/word/single/double type definitions. */ +/******************************************************/ + +/* 8 bit byte type. */ +typedef unsigned char sljit_ub; +typedef signed char sljit_sb; + +/* 16 bit half-word type. */ +typedef unsigned short int sljit_uh; +typedef signed short int sljit_sh; + +/* 32 bit integer type. */ +typedef unsigned int sljit_ui; +typedef signed int sljit_si; + +/* Machine word type. Enough for storing a pointer. + 32 bit for 32 bit machines. + 64 bit for 64 bit machines. */ +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +/* Just to have something. */ +#define SLJIT_WORD_SHIFT 0 +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#elif !(defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + && !(defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + && !(defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + && !(defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + && !(defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) +#define SLJIT_32BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 2 +typedef unsigned int sljit_uw; +typedef int sljit_sw; +#else +#define SLJIT_64BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 3 +#ifdef _WIN32 +typedef unsigned __int64 sljit_uw; +typedef __int64 sljit_sw; +#else +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#endif +#endif + +typedef sljit_uw sljit_p; + +/* Floating point types. */ +typedef float sljit_s; +typedef double sljit_d; + +/* Shift for pointer sized data. */ +#define SLJIT_POINTER_SHIFT SLJIT_WORD_SHIFT + +/* Shift for double precision sized data. */ +#define SLJIT_DOUBLE_SHIFT 3 +#define SLJIT_SINGLE_SHIFT 2 + +#ifndef SLJIT_W + +/* Defining long constants. */ +#if (defined SLJIT_64BIT_ARCHITECTURE && SLJIT_64BIT_ARCHITECTURE) +#define SLJIT_W(w) (w##ll) +#else +#define SLJIT_W(w) (w) +#endif + +#endif /* !SLJIT_W */ + +/*************************/ +/* Endianness detection. */ +/*************************/ + +#if !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) + +/* These macros are mostly useful for the applications. */ +#if (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) + +#ifdef __LITTLE_ENDIAN__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) + +#ifdef __MIPSEL__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +#define SLJIT_BIG_ENDIAN 1 + +#else +#define SLJIT_LITTLE_ENDIAN 1 +#endif + +#endif /* !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) */ + +/* Sanity check. */ +#if (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && (defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#if !(defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && !(defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#ifndef SLJIT_UNALIGNED + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_UNALIGNED 1 +#endif + +#endif /* !SLJIT_UNALIGNED */ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) +/* Auto detect SSE2 support using CPUID. + On 64 bit x86 cpus, sse2 must be present. */ +#define SLJIT_DETECT_SSE2 1 +#endif + +/*****************************************************************************************/ +/* Calling convention of functions generated by SLJIT or called from the generated code. */ +/*****************************************************************************************/ + +#ifndef SLJIT_CALL + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#if defined(__GNUC__) && !defined(__APPLE__) + +#define SLJIT_CALL __attribute__ ((fastcall)) +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(_MSC_VER) + +#define SLJIT_CALL __fastcall +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(__BORLANDC__) + +#define SLJIT_CALL __msfastcall +#define SLJIT_X86_32_FASTCALL 1 + +#else /* Unknown compiler. */ + +/* The cdecl attribute is the default. */ +#define SLJIT_CALL + +#endif + +#else /* Non x86-32 architectures. */ + +#define SLJIT_CALL + +#endif /* SLJIT_CONFIG_X86_32 */ + +#endif /* !SLJIT_CALL */ + +#ifndef SLJIT_INDIRECT_CALL +#if ((defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) && (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN)) \ + || ((defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) && defined _AIX) +/* It seems certain ppc compilers use an indirect addressing for functions + which makes things complicated. */ +#define SLJIT_INDIRECT_CALL 1 +#endif +#endif /* SLJIT_INDIRECT_CALL */ + +/* The offset which needs to be substracted from the return address to +determine the next executed instruction after return. */ +#ifndef SLJIT_RETURN_ADDRESS_OFFSET +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +#define SLJIT_RETURN_ADDRESS_OFFSET 8 +#else +#define SLJIT_RETURN_ADDRESS_OFFSET 0 +#endif +#endif /* SLJIT_RETURN_ADDRESS_OFFSET */ + +/***************************************************/ +/* Functions of the built-in executable allocator. */ +/***************************************************/ + +#if (defined SLJIT_EXECUTABLE_ALLOCATOR && SLJIT_EXECUTABLE_ALLOCATOR) +SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void); +#define SLJIT_MALLOC_EXEC(size) sljit_malloc_exec(size) +#define SLJIT_FREE_EXEC(ptr) sljit_free_exec(ptr) +#endif + +/**********************************************/ +/* Registers and locals offset determination. */ +/**********************************************/ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#define SLJIT_NUMBER_OF_REGISTERS 10 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#if (defined SLJIT_X86_32_FASTCALL && SLJIT_X86_32_FASTCALL) +#define SLJIT_LOCALS_OFFSET_BASE ((2 + 4) * sizeof(sljit_sw)) +#else +/* Maximum 3 arguments are passed on the stack, +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1 + 4) * sizeof(sljit_sw)) +#endif /* SLJIT_X86_32_FASTCALL */ + +#elif (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) + +#ifndef _WIN64 +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 6 +#define SLJIT_LOCALS_OFFSET_BASE (sizeof(sljit_sw)) +#else +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE ((4 + 2) * sizeof(sljit_sw)) +#endif /* _WIN64 */ + +#elif (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) + +#define SLJIT_NUMBER_OF_REGISTERS 25 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 10 +#define SLJIT_LOCALS_OFFSET_BASE (2 * sizeof(sljit_sw)) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +#define SLJIT_NUMBER_OF_REGISTERS 22 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 17 +#if (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) || (defined _AIX) +#define SLJIT_LOCALS_OFFSET_BASE ((6 + 8) * sizeof(sljit_sw)) +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1) * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE (3 * sizeof(sljit_sw)) +#endif /* SLJIT_CONFIG_PPC_64 || _AIX */ + +#elif (defined SLJIT_CONFIG_MIPS && SLJIT_CONFIG_MIPS) + +#define SLJIT_NUMBER_OF_REGISTERS 17 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#if (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) +#define SLJIT_LOCALS_OFFSET_BASE (4 * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE 0 +#endif + +#elif (defined SLJIT_CONFIG_SPARC && SLJIT_CONFIG_SPARC) + +#define SLJIT_NUMBER_OF_REGISTERS 18 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 14 +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((23 + 1) * sizeof(sljit_sw)) +#endif + +#elif (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) + +#define SLJIT_NUMBER_OF_REGISTERS 0 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 0 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#endif + +#define SLJIT_LOCALS_OFFSET (SLJIT_LOCALS_OFFSET_BASE) + +#define SLJIT_NUMBER_OF_SCRATCH_REGISTERS \ + (SLJIT_NUMBER_OF_REGISTERS - SLJIT_NUMBER_OF_SAVED_REGISTERS) + +#define SLJIT_NUMBER_OF_FLOAT_REGISTERS 6 +#if (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) && (defined _WIN64) +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 1 +#else +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 0 +#endif + +#define SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS \ + (SLJIT_NUMBER_OF_FLOAT_REGISTERS - SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS) + +/*************************************/ +/* Debug and verbose related macros. */ +/*************************************/ + +#if (defined SLJIT_VERBOSE && SLJIT_VERBOSE) +#include +#endif + +#if (defined SLJIT_DEBUG && SLJIT_DEBUG) + +#if !defined(SLJIT_ASSERT) || !defined(SLJIT_ASSERT_STOP) + +/* SLJIT_HALT_PROCESS must halt the process. */ +#ifndef SLJIT_HALT_PROCESS +#include + +#define SLJIT_HALT_PROCESS() \ + abort(); +#endif /* !SLJIT_HALT_PROCESS */ + +#include + +#endif /* !SLJIT_ASSERT || !SLJIT_ASSERT_STOP */ + +/* Feel free to redefine these two macros. */ +#ifndef SLJIT_ASSERT + +#define SLJIT_ASSERT(x) \ + do { \ + if (SLJIT_UNLIKELY(!(x))) { \ + printf("Assertion failed at " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } \ + } while (0) + +#endif /* !SLJIT_ASSERT */ + +#ifndef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT_STOP() \ + do { \ + printf("Should never been reached " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } while (0) + +#endif /* !SLJIT_ASSERT_STOP */ + +#else /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +/* Forcing empty, but valid statements. */ +#undef SLJIT_ASSERT +#undef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT(x) \ + do { } while (0) +#define SLJIT_ASSERT_STOP() \ + do { } while (0) + +#endif /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +#ifndef SLJIT_COMPILE_ASSERT + +/* Should be improved eventually. */ +#define SLJIT_COMPILE_ASSERT(x, description) \ + SLJIT_ASSERT(x) + +#endif /* !SLJIT_COMPILE_ASSERT */ + +#endif diff --git a/plugins/php/versions/54/src/reentrancy.c b/plugins/php/versions/54/src/reentrancy.c new file mode 100644 index 000000000..e7c5d7171 --- /dev/null +++ b/plugins/php/versions/54/src/reentrancy.c @@ -0,0 +1,450 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2014 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include +#include +#include +#ifdef HAVE_DIRENT_H +#include +#endif + +#include "php_reentrancy.h" +#include "ext/standard/php_rand.h" /* for PHP_RAND_MAX */ + +enum { + LOCALTIME_R, + CTIME_R, + ASCTIME_R, + GMTIME_R, + READDIR_R, + NUMBER_OF_LOCKS +}; + +#if defined(PHP_NEED_REENTRANCY) + +#include + +static MUTEX_T reentrant_locks[NUMBER_OF_LOCKS]; + +#define local_lock(x) tsrm_mutex_lock(reentrant_locks[x]) +#define local_unlock(x) tsrm_mutex_unlock(reentrant_locks[x]) + +#else + +#define local_lock(x) +#define local_unlock(x) + +#endif + +#if defined(PHP_IRIX_TIME_R) + +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf) == buf) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf) == buf) + return (buf); + return (NULL); +} + +#endif + +#if defined(PHP_HPUX_TIME_R) + +#define HAVE_LOCALTIME_R 1 +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 +#define HAVE_GMTIME_R 1 + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (localtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (gmtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +#endif + +#if defined(__BEOS__) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + /* Modified according to LibC definition */ + if (((struct tm*)gmtime_r(timep, p_tm)) == p_tm) + return (p_tm); + return (NULL); +} + +#endif /* BEOS */ + +#if !defined(HAVE_POSIX_READDIR_R) + +PHPAPI int php_readdir_r(DIR *dirp, struct dirent *entry, + struct dirent **result) +{ +#if defined(HAVE_OLD_READDIR_R) + int ret = 0; + + /* We cannot rely on the return value of readdir_r + as it differs between various platforms + (HPUX returns 0 on success whereas Solaris returns non-zero) + */ + entry->d_name[0] = '\0'; + readdir_r(dirp, entry, result); + + if (entry->d_name[0] == '\0') { + *result = NULL; + ret = errno; + } else { + *result = entry; + } + return ret; +#else + struct dirent *ptr; + int ret = 0; + + local_lock(READDIR_R); + + errno = 0; + + ptr = readdir(dirp); + + if (!ptr && errno != 0) + ret = errno; + + if (ptr) + memcpy(entry, ptr, sizeof(*ptr)); + + *result = ptr; + + local_unlock(READDIR_R); + + return ret; +#endif +} + +#endif + +#if !defined(HAVE_LOCALTIME_R) && defined(HAVE_LOCALTIME) + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(LOCALTIME_R); + + tmp = localtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(LOCALTIME_R); + + return tmp; +} + +#endif + +#if !defined(HAVE_CTIME_R) && defined(HAVE_CTIME) + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + char *tmp; + + local_lock(CTIME_R); + + tmp = ctime(clock); + strcpy(buf, tmp); + + local_unlock(CTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_ASCTIME_R) && defined(HAVE_ASCTIME) + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + char *tmp; + + local_lock(ASCTIME_R); + + tmp = asctime(tm); + strcpy(buf, tmp); + + local_unlock(ASCTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_GMTIME_R) && defined(HAVE_GMTIME) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(GMTIME_R); + + tmp = gmtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(GMTIME_R); + + return tmp; +} + +#endif + +#if defined(PHP_NEED_REENTRANCY) + +void reentrancy_startup(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + reentrant_locks[i] = tsrm_mutex_alloc(); + } +} + +void reentrancy_shutdown(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + tsrm_mutex_free(reentrant_locks[i]); + } +} + +#endif + +#ifndef HAVE_RAND_R + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Posix rand_r function added May 1999 by Wes Peters . + */ + +#include +#include + +static int +do_rand(unsigned long *ctx) +{ + return ((*ctx = *ctx * 1103515245 + 12345) % ((u_long)PHP_RAND_MAX + 1)); +} + + +PHPAPI int +php_rand_r(unsigned int *ctx) +{ + u_long val = (u_long) *ctx; + *ctx = do_rand(&val); + return (int) *ctx; +} + +#endif + + +#ifndef HAVE_STRTOK_R + +/* + * Copyright (c) 1998 Softweyr LLC. All rights reserved. + * + * strtok_r, from Berkeley strtok + * Oct 13, 1998 by Wes Peters + * + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notices, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * + * This product includes software developed by Softweyr LLC, the + * University of California, Berkeley, and its contributors. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY SOFTWEYR LLC, THE REGENTS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTWEYR LLC, THE + * REGENTS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +PHPAPI char * +php_strtok_r(char *s, const char *delim, char **last) +{ + char *spanp; + int c, sc; + char *tok; + + if (s == NULL && (s = *last) == NULL) + { + return NULL; + } + + /* + * Skip (span) leading delimiters (s += strspn(s, delim), sort of). + */ +cont: + c = *s++; + for (spanp = (char *)delim; (sc = *spanp++) != 0; ) + { + if (c == sc) + { + goto cont; + } + } + + if (c == 0) /* no non-delimiter characters */ + { + *last = NULL; + return NULL; + } + tok = s - 1; + + /* + * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). + * Note that delim must have one NUL; we stop if we see that, too. + */ + for (;;) + { + c = *s++; + spanp = (char *)delim; + do + { + if ((sc = *spanp++) == c) + { + if (c == 0) + { + s = NULL; + } + else + { + char *w = s - 1; + *w = '\0'; + } + *last = s; + return tok; + } + } + while (sc != 0); + } + /* NOTREACHED */ +} + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/plugins/php/versions/54/zendguardloader.sh b/plugins/php/versions/54/zendguardloader.sh new file mode 100755 index 000000000..197a5cbb6 --- /dev/null +++ b/plugins/php/versions/54/zendguardloader.sh @@ -0,0 +1,96 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=ZendGuardLoader + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` + extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ $sysName == 'Darwin' ]; then + wget -O $php_lib/zend-loader-php5.6.tar.gz http://downloads.zend.com/guard/7.0.0/zend-loader-php5.6-darwin10.7-x86_64_update1.tar.gz + else + wget -O $php_lib/ZendGuardLoader-70429-PHP-5.4-linux-glibc23-x86_64.tar.gz http://downloads.zend.com/guard/6.0.0/ZendGuardLoader-70429-PHP-5.4-linux-glibc23-x86_64.tar.gz + fi + + cd $php_lib && tar xvf ZendGuardLoader-70429-PHP-5.4-linux-glibc23-x86_64.tar.gz + cd ZendGuardLoader-70429-PHP-5.4-linux-glibc23-x86_64/php-5.4.x + cp ZendGuardLoader.so $serverPath/php/$version/lib/php/extensions/no-debug-non-zts-20100525/ + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo -e "[Zend ZendGuard Loader]\nzend_extension=ZendGuardLoader.so\nzend_loader.enable=1\nzend_loader.disable_licensing=0\nzend_loader.obfuscation_level_support=3\nzend_loader.license_path=" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + extFile=$serverPath/php/${version}/lib/php/extensions/no-debug-non-zts-20131226/${LIBNAME}.so + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + sed -i $BAK '/ZendGuardLoader.so/d' $serverPath/php/$version/etc/php.ini + sed -i $BAK '/zend_loader/d' $serverPath/php/$version/etc/php.ini + sed -i $BAK '/\[Zend ZendGuard Loader\]/d' $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +actionType=$1 +version=$2 +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/55/install.sh b/plugins/php/versions/55/install.sh new file mode 100755 index 000000000..d5cad76e8 --- /dev/null +++ b/plugins/php/versions/55/install.sh @@ -0,0 +1,154 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +version=5.5.38 +PHP_VER=55 +md5_file_ok=72302e26f153687e2ca922909f927443 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-5.5.38 ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_old.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://museum.php.net/php5/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`md5sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +OPTIONS='--without-iconv' +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-freetype-dir=${serverPath}/lib/freetype" +else + OPTIONS="${OPTIONS} --with-readline" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +if [ "${SYS_ARCH}" == "aarch64" ];then + # 修复aarch64架构下安装 + # /www/server/mdserver-web/plugins/php/versions/56/src/zend_multiply.h > /www/server/source/php/php56/Zend/zend_multiply.h + cat ${curPath}/versions/${PHP_VER}/src/zend_multiply.h > $sourcePath/php/php${PHP_VER}/Zend/zend_multiply.h +fi + +if [ "${SYS_ARCH}" == "arm64" ] && [ "$sysName" == "Darwin" ] ;then + # 修复mac arm64架构下php安装 + # 修复不能识别到sys_icache_invalidate + cat ${curPath}/versions/${PHP_VER}/src/ext/pcre/sljitConfigInternal.h > $sourcePath/php/php${PHP_VER}/ext/pcre/pcrelib/sljit/sljitConfigInternal.h + cat ${curPath}/versions/${PHP_VER}/src/reentrancy.c > $sourcePath/php/php${PHP_VER}/main/reentrancy.c +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} && ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --enable-mbstring \ + --enable-simplexml \ + --enable-sockets \ + --enable-ftp \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} + echo "安装php-${version}成功" +fi + +#------------------------ install end ------------------------------------# +} + + +Uninstall_php() +{ + $serverPath/php/init.d/php55 stop + rm -rf $serverPath/php/55 + echo "卸载php-5.5.38 ..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/55/src/ext/pcre/sljitConfigInternal.h b/plugins/php/versions/55/src/ext/pcre/sljitConfigInternal.h new file mode 100644 index 000000000..23c33609d --- /dev/null +++ b/plugins/php/versions/55/src/ext/pcre/sljitConfigInternal.h @@ -0,0 +1,702 @@ +/* + * Stack-less Just-In-Time compiler + * + * Copyright 2009-2012 Zoltan Herczeg (hzmester@freemail.hu). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SLJIT_CONFIG_INTERNAL_H_ +#define _SLJIT_CONFIG_INTERNAL_H_ + +/* + SLJIT defines the following architecture dependent types and macros: + + Types: + sljit_sb, sljit_ub : signed and unsigned 8 bit byte + sljit_sh, sljit_uh : signed and unsigned 16 bit half-word (short) type + sljit_si, sljit_ui : signed and unsigned 32 bit integer type + sljit_sw, sljit_uw : signed and unsigned machine word, enough to store a pointer + sljit_p : unsgined pointer value (usually the same as sljit_uw, but + some 64 bit ABIs may use 32 bit pointers) + sljit_s : single precision floating point value + sljit_d : double precision floating point value + + Macros for feature detection (boolean): + SLJIT_32BIT_ARCHITECTURE : 32 bit architecture + SLJIT_64BIT_ARCHITECTURE : 64 bit architecture + SLJIT_LITTLE_ENDIAN : little endian architecture + SLJIT_BIG_ENDIAN : big endian architecture + SLJIT_UNALIGNED : allows unaligned memory accesses for non-fpu operations (only!) + SLJIT_INDIRECT_CALL : see SLJIT_FUNC_OFFSET() for more information + + Constants: + SLJIT_NUMBER_OF_REGISTERS : number of available registers + SLJIT_NUMBER_OF_SCRATCH_REGISTERS : number of available scratch registers + SLJIT_NUMBER_OF_SAVED_REGISTERS : number of available saved registers + SLJIT_NUMBER_OF_FLOAT_REGISTERS : number of available floating point registers + SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS : number of available floating point scratch registers + SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS : number of available floating point saved registers + SLJIT_WORD_SHIFT : the shift required to apply when accessing a sljit_sw/sljit_uw array by index + SLJIT_DOUBLE_SHIFT : the shift required to apply when accessing + a double precision floating point array by index + SLJIT_SINGLE_SHIFT : the shift required to apply when accessing + a single precision floating point array by index + SLJIT_LOCALS_OFFSET : local space starting offset (SLJIT_SP + SLJIT_LOCALS_OFFSET) + SLJIT_RETURN_ADDRESS_OFFSET : a return instruction always adds this offset to the return address + + Other macros: + SLJIT_CALL : C calling convention define for both calling JIT form C and C callbacks for JIT + SLJIT_W(number) : defining 64 bit constants on 64 bit architectures (compiler independent helper) +*/ + +/*****************/ +/* Sanity check. */ +/*****************/ + +#if !((defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + || (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + || (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + || (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + || (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + || (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED)) +#error "An architecture must be selected" +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + + (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + + (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + + (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + + (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + + (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + + (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + + (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + + (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + + (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + + (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + + (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + + (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + + (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) >= 2 +#error "Multiple architectures are selected" +#endif + +/********************************************************/ +/* Automatic CPU detection (requires compiler support). */ +/********************************************************/ + +#if (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) + +#ifndef _WIN32 + +#if defined(__i386__) || defined(__i386) +#define SLJIT_CONFIG_X86_32 1 +#elif defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(__arm__) || defined(__ARM__) +#ifdef __thumb2__ +#define SLJIT_CONFIG_ARM_THUMB2 1 +#elif defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) +#define SLJIT_CONFIG_ARM_V7 1 +#else +#define SLJIT_CONFIG_ARM_V5 1 +#endif +#elif defined (__aarch64__) +#define SLJIT_CONFIG_ARM_64 1 +#elif defined(__ppc64__) || defined(__powerpc64__) || defined(_ARCH_PPC64) || (defined(_POWER) && defined(__64BIT__)) +#define SLJIT_CONFIG_PPC_64 1 +#elif defined(__ppc__) || defined(__powerpc__) || defined(_ARCH_PPC) || defined(_ARCH_PWR) || defined(_ARCH_PWR2) || defined(_POWER) +#define SLJIT_CONFIG_PPC_32 1 +#elif defined(__mips__) && !defined(_LP64) +#define SLJIT_CONFIG_MIPS_32 1 +#elif defined(__mips64) +#define SLJIT_CONFIG_MIPS_64 1 +#elif defined(__sparc__) || defined(__sparc) +#define SLJIT_CONFIG_SPARC_32 1 +#elif defined(__tilegx__) +#define SLJIT_CONFIG_TILEGX 1 +#else +/* Unsupported architecture */ +#define SLJIT_CONFIG_UNSUPPORTED 1 +#endif + +#else /* !_WIN32 */ + +#if defined(_M_X64) || defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(_ARM_) +#define SLJIT_CONFIG_ARM_V5 1 +#else +#define SLJIT_CONFIG_X86_32 1 +#endif + +#endif /* !WIN32 */ +#endif /* SLJIT_CONFIG_AUTO */ + +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +#undef SLJIT_EXECUTABLE_ALLOCATOR +#endif + +/******************************/ +/* CPU family type detection. */ +/******************************/ + +#if (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) +#define SLJIT_CONFIG_ARM_32 1 +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) +#define SLJIT_CONFIG_X86 1 +#elif (defined SLJIT_CONFIG_ARM_32 && SLJIT_CONFIG_ARM_32) || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) +#define SLJIT_CONFIG_ARM 1 +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_CONFIG_PPC 1 +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) +#define SLJIT_CONFIG_MIPS 1 +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) || (defined SLJIT_CONFIG_SPARC_64 && SLJIT_CONFIG_SPARC_64) +#define SLJIT_CONFIG_SPARC 1 +#endif + +/**********************************/ +/* External function definitions. */ +/**********************************/ + +#if !(defined SLJIT_STD_MACROS_DEFINED && SLJIT_STD_MACROS_DEFINED) + +/* These libraries are needed for the macros below. */ +#include +#include + +#endif /* SLJIT_STD_MACROS_DEFINED */ + +/* General macros: + Note: SLJIT is designed to be independent from them as possible. + + In release mode (SLJIT_DEBUG is not defined) only the following + external functions are needed: +*/ + +#ifndef SLJIT_MALLOC +#define SLJIT_MALLOC(size, allocator_data) malloc(size) +#endif + +#ifndef SLJIT_FREE +#define SLJIT_FREE(ptr, allocator_data) free(ptr) +#endif + +#ifndef SLJIT_MEMMOVE +#define SLJIT_MEMMOVE(dest, src, len) memmove(dest, src, len) +#endif + +#ifndef SLJIT_ZEROMEM +#define SLJIT_ZEROMEM(dest, len) memset(dest, 0, len) +#endif + +/***************************/ +/* Compiler helper macros. */ +/***************************/ + +#if !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) + +#if defined(__GNUC__) && (__GNUC__ >= 3) +#define SLJIT_LIKELY(x) __builtin_expect((x), 1) +#define SLJIT_UNLIKELY(x) __builtin_expect((x), 0) +#else +#define SLJIT_LIKELY(x) (x) +#define SLJIT_UNLIKELY(x) (x) +#endif + +#endif /* !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) */ + +#ifndef SLJIT_INLINE +/* Inline functions. Some old compilers do not support them. */ +#if defined(__SUNPRO_C) && __SUNPRO_C <= 0x510 +#define SLJIT_INLINE +#else +#define SLJIT_INLINE __inline +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_NOINLINE +/* Not inline functions. */ +#if defined(__GNUC__) +#define SLJIT_NOINLINE __attribute__ ((noinline)) +#else +#define SLJIT_NOINLINE +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_CONST +/* Const variables. */ +#define SLJIT_CONST const +#endif + +#ifndef SLJIT_UNUSED_ARG +/* Unused arguments. */ +#define SLJIT_UNUSED_ARG(arg) (void)arg +#endif + +/*********************************/ +/* Type of public API functions. */ +/*********************************/ + +#if (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) +/* Static ABI functions. For all-in-one programs. */ + +#if defined(__GNUC__) +/* Disable unused warnings in gcc. */ +#define SLJIT_API_FUNC_ATTRIBUTE static __attribute__((unused)) +#else +#define SLJIT_API_FUNC_ATTRIBUTE static +#endif + +#else +#define SLJIT_API_FUNC_ATTRIBUTE +#endif /* (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) */ + +/****************************/ +/* Instruction cache flush. */ +/****************************/ + +#ifndef SLJIT_CACHE_FLUSH + +#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) + +/* Not required to implement on archs with unified caches. */ +#define SLJIT_CACHE_FLUSH(from, to) + +#elif defined __APPLE__ + +/* Supported by all macs since Mac OS 10.5. + However, it does not work on non-jailbroken iOS devices, + although the compilation is successful. */ +#include +#define SLJIT_CACHE_FLUSH(from, to) \ + sys_icache_invalidate((char*)(from), (char*)(to) - (char*)(from)) + +#elif defined __ANDROID__ + +/* Android lacks __clear_cache; instead, cacheflush should be used. */ + +#define SLJIT_CACHE_FLUSH(from, to) \ + cacheflush((long)(from), (long)(to), 0) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +/* The __clear_cache() implementation of GCC is a dummy function on PowerPC. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + ppc_cache_flush((from), (to)) + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +/* The __clear_cache() implementation of GCC is a dummy function on Sparc. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + sparc_cache_flush((from), (to)) + +#else + +/* Calls __ARM_NR_cacheflush on ARM-Linux. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + __clear_cache((char*)(from), (char*)(to)) + +#endif + +#endif /* !SLJIT_CACHE_FLUSH */ + +/******************************************************/ +/* Byte/half/int/word/single/double type definitions. */ +/******************************************************/ + +/* 8 bit byte type. */ +typedef unsigned char sljit_ub; +typedef signed char sljit_sb; + +/* 16 bit half-word type. */ +typedef unsigned short int sljit_uh; +typedef signed short int sljit_sh; + +/* 32 bit integer type. */ +typedef unsigned int sljit_ui; +typedef signed int sljit_si; + +/* Machine word type. Enough for storing a pointer. + 32 bit for 32 bit machines. + 64 bit for 64 bit machines. */ +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +/* Just to have something. */ +#define SLJIT_WORD_SHIFT 0 +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#elif !(defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + && !(defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + && !(defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + && !(defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + && !(defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) +#define SLJIT_32BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 2 +typedef unsigned int sljit_uw; +typedef int sljit_sw; +#else +#define SLJIT_64BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 3 +#ifdef _WIN32 +typedef unsigned __int64 sljit_uw; +typedef __int64 sljit_sw; +#else +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#endif +#endif + +typedef sljit_uw sljit_p; + +/* Floating point types. */ +typedef float sljit_s; +typedef double sljit_d; + +/* Shift for pointer sized data. */ +#define SLJIT_POINTER_SHIFT SLJIT_WORD_SHIFT + +/* Shift for double precision sized data. */ +#define SLJIT_DOUBLE_SHIFT 3 +#define SLJIT_SINGLE_SHIFT 2 + +#ifndef SLJIT_W + +/* Defining long constants. */ +#if (defined SLJIT_64BIT_ARCHITECTURE && SLJIT_64BIT_ARCHITECTURE) +#define SLJIT_W(w) (w##ll) +#else +#define SLJIT_W(w) (w) +#endif + +#endif /* !SLJIT_W */ + +/*************************/ +/* Endianness detection. */ +/*************************/ + +#if !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) + +/* These macros are mostly useful for the applications. */ +#if (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) + +#ifdef __LITTLE_ENDIAN__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) + +#ifdef __MIPSEL__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +#define SLJIT_BIG_ENDIAN 1 + +#else +#define SLJIT_LITTLE_ENDIAN 1 +#endif + +#endif /* !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) */ + +/* Sanity check. */ +#if (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && (defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#if !(defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && !(defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#ifndef SLJIT_UNALIGNED + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_UNALIGNED 1 +#endif + +#endif /* !SLJIT_UNALIGNED */ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) +/* Auto detect SSE2 support using CPUID. + On 64 bit x86 cpus, sse2 must be present. */ +#define SLJIT_DETECT_SSE2 1 +#endif + +/*****************************************************************************************/ +/* Calling convention of functions generated by SLJIT or called from the generated code. */ +/*****************************************************************************************/ + +#ifndef SLJIT_CALL + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#if defined(__GNUC__) && !defined(__APPLE__) + +#define SLJIT_CALL __attribute__ ((fastcall)) +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(_MSC_VER) + +#define SLJIT_CALL __fastcall +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(__BORLANDC__) + +#define SLJIT_CALL __msfastcall +#define SLJIT_X86_32_FASTCALL 1 + +#else /* Unknown compiler. */ + +/* The cdecl attribute is the default. */ +#define SLJIT_CALL + +#endif + +#else /* Non x86-32 architectures. */ + +#define SLJIT_CALL + +#endif /* SLJIT_CONFIG_X86_32 */ + +#endif /* !SLJIT_CALL */ + +#ifndef SLJIT_INDIRECT_CALL +#if ((defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) && (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN)) \ + || ((defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) && defined _AIX) +/* It seems certain ppc compilers use an indirect addressing for functions + which makes things complicated. */ +#define SLJIT_INDIRECT_CALL 1 +#endif +#endif /* SLJIT_INDIRECT_CALL */ + +/* The offset which needs to be substracted from the return address to +determine the next executed instruction after return. */ +#ifndef SLJIT_RETURN_ADDRESS_OFFSET +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +#define SLJIT_RETURN_ADDRESS_OFFSET 8 +#else +#define SLJIT_RETURN_ADDRESS_OFFSET 0 +#endif +#endif /* SLJIT_RETURN_ADDRESS_OFFSET */ + +/***************************************************/ +/* Functions of the built-in executable allocator. */ +/***************************************************/ + +#if (defined SLJIT_EXECUTABLE_ALLOCATOR && SLJIT_EXECUTABLE_ALLOCATOR) +SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void); +#define SLJIT_MALLOC_EXEC(size) sljit_malloc_exec(size) +#define SLJIT_FREE_EXEC(ptr) sljit_free_exec(ptr) +#endif + +/**********************************************/ +/* Registers and locals offset determination. */ +/**********************************************/ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#define SLJIT_NUMBER_OF_REGISTERS 10 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#if (defined SLJIT_X86_32_FASTCALL && SLJIT_X86_32_FASTCALL) +#define SLJIT_LOCALS_OFFSET_BASE ((2 + 4) * sizeof(sljit_sw)) +#else +/* Maximum 3 arguments are passed on the stack, +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1 + 4) * sizeof(sljit_sw)) +#endif /* SLJIT_X86_32_FASTCALL */ + +#elif (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) + +#ifndef _WIN64 +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 6 +#define SLJIT_LOCALS_OFFSET_BASE (sizeof(sljit_sw)) +#else +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE ((4 + 2) * sizeof(sljit_sw)) +#endif /* _WIN64 */ + +#elif (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) + +#define SLJIT_NUMBER_OF_REGISTERS 25 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 10 +#define SLJIT_LOCALS_OFFSET_BASE (2 * sizeof(sljit_sw)) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +#define SLJIT_NUMBER_OF_REGISTERS 22 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 17 +#if (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) || (defined _AIX) +#define SLJIT_LOCALS_OFFSET_BASE ((6 + 8) * sizeof(sljit_sw)) +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1) * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE (3 * sizeof(sljit_sw)) +#endif /* SLJIT_CONFIG_PPC_64 || _AIX */ + +#elif (defined SLJIT_CONFIG_MIPS && SLJIT_CONFIG_MIPS) + +#define SLJIT_NUMBER_OF_REGISTERS 17 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#if (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) +#define SLJIT_LOCALS_OFFSET_BASE (4 * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE 0 +#endif + +#elif (defined SLJIT_CONFIG_SPARC && SLJIT_CONFIG_SPARC) + +#define SLJIT_NUMBER_OF_REGISTERS 18 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 14 +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((23 + 1) * sizeof(sljit_sw)) +#endif + +#elif (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) + +#define SLJIT_NUMBER_OF_REGISTERS 0 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 0 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#endif + +#define SLJIT_LOCALS_OFFSET (SLJIT_LOCALS_OFFSET_BASE) + +#define SLJIT_NUMBER_OF_SCRATCH_REGISTERS \ + (SLJIT_NUMBER_OF_REGISTERS - SLJIT_NUMBER_OF_SAVED_REGISTERS) + +#define SLJIT_NUMBER_OF_FLOAT_REGISTERS 6 +#if (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) && (defined _WIN64) +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 1 +#else +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 0 +#endif + +#define SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS \ + (SLJIT_NUMBER_OF_FLOAT_REGISTERS - SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS) + +/*************************************/ +/* Debug and verbose related macros. */ +/*************************************/ + +#if (defined SLJIT_VERBOSE && SLJIT_VERBOSE) +#include +#endif + +#if (defined SLJIT_DEBUG && SLJIT_DEBUG) + +#if !defined(SLJIT_ASSERT) || !defined(SLJIT_ASSERT_STOP) + +/* SLJIT_HALT_PROCESS must halt the process. */ +#ifndef SLJIT_HALT_PROCESS +#include + +#define SLJIT_HALT_PROCESS() \ + abort(); +#endif /* !SLJIT_HALT_PROCESS */ + +#include + +#endif /* !SLJIT_ASSERT || !SLJIT_ASSERT_STOP */ + +/* Feel free to redefine these two macros. */ +#ifndef SLJIT_ASSERT + +#define SLJIT_ASSERT(x) \ + do { \ + if (SLJIT_UNLIKELY(!(x))) { \ + printf("Assertion failed at " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } \ + } while (0) + +#endif /* !SLJIT_ASSERT */ + +#ifndef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT_STOP() \ + do { \ + printf("Should never been reached " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } while (0) + +#endif /* !SLJIT_ASSERT_STOP */ + +#else /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +/* Forcing empty, but valid statements. */ +#undef SLJIT_ASSERT +#undef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT(x) \ + do { } while (0) +#define SLJIT_ASSERT_STOP() \ + do { } while (0) + +#endif /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +#ifndef SLJIT_COMPILE_ASSERT + +/* Should be improved eventually. */ +#define SLJIT_COMPILE_ASSERT(x, description) \ + SLJIT_ASSERT(x) + +#endif /* !SLJIT_COMPILE_ASSERT */ + +#endif diff --git a/plugins/php/versions/55/src/reentrancy.c b/plugins/php/versions/55/src/reentrancy.c new file mode 100644 index 000000000..e7c5d7171 --- /dev/null +++ b/plugins/php/versions/55/src/reentrancy.c @@ -0,0 +1,450 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2014 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include +#include +#include +#ifdef HAVE_DIRENT_H +#include +#endif + +#include "php_reentrancy.h" +#include "ext/standard/php_rand.h" /* for PHP_RAND_MAX */ + +enum { + LOCALTIME_R, + CTIME_R, + ASCTIME_R, + GMTIME_R, + READDIR_R, + NUMBER_OF_LOCKS +}; + +#if defined(PHP_NEED_REENTRANCY) + +#include + +static MUTEX_T reentrant_locks[NUMBER_OF_LOCKS]; + +#define local_lock(x) tsrm_mutex_lock(reentrant_locks[x]) +#define local_unlock(x) tsrm_mutex_unlock(reentrant_locks[x]) + +#else + +#define local_lock(x) +#define local_unlock(x) + +#endif + +#if defined(PHP_IRIX_TIME_R) + +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf) == buf) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf) == buf) + return (buf); + return (NULL); +} + +#endif + +#if defined(PHP_HPUX_TIME_R) + +#define HAVE_LOCALTIME_R 1 +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 +#define HAVE_GMTIME_R 1 + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (localtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (gmtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +#endif + +#if defined(__BEOS__) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + /* Modified according to LibC definition */ + if (((struct tm*)gmtime_r(timep, p_tm)) == p_tm) + return (p_tm); + return (NULL); +} + +#endif /* BEOS */ + +#if !defined(HAVE_POSIX_READDIR_R) + +PHPAPI int php_readdir_r(DIR *dirp, struct dirent *entry, + struct dirent **result) +{ +#if defined(HAVE_OLD_READDIR_R) + int ret = 0; + + /* We cannot rely on the return value of readdir_r + as it differs between various platforms + (HPUX returns 0 on success whereas Solaris returns non-zero) + */ + entry->d_name[0] = '\0'; + readdir_r(dirp, entry, result); + + if (entry->d_name[0] == '\0') { + *result = NULL; + ret = errno; + } else { + *result = entry; + } + return ret; +#else + struct dirent *ptr; + int ret = 0; + + local_lock(READDIR_R); + + errno = 0; + + ptr = readdir(dirp); + + if (!ptr && errno != 0) + ret = errno; + + if (ptr) + memcpy(entry, ptr, sizeof(*ptr)); + + *result = ptr; + + local_unlock(READDIR_R); + + return ret; +#endif +} + +#endif + +#if !defined(HAVE_LOCALTIME_R) && defined(HAVE_LOCALTIME) + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(LOCALTIME_R); + + tmp = localtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(LOCALTIME_R); + + return tmp; +} + +#endif + +#if !defined(HAVE_CTIME_R) && defined(HAVE_CTIME) + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + char *tmp; + + local_lock(CTIME_R); + + tmp = ctime(clock); + strcpy(buf, tmp); + + local_unlock(CTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_ASCTIME_R) && defined(HAVE_ASCTIME) + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + char *tmp; + + local_lock(ASCTIME_R); + + tmp = asctime(tm); + strcpy(buf, tmp); + + local_unlock(ASCTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_GMTIME_R) && defined(HAVE_GMTIME) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(GMTIME_R); + + tmp = gmtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(GMTIME_R); + + return tmp; +} + +#endif + +#if defined(PHP_NEED_REENTRANCY) + +void reentrancy_startup(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + reentrant_locks[i] = tsrm_mutex_alloc(); + } +} + +void reentrancy_shutdown(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + tsrm_mutex_free(reentrant_locks[i]); + } +} + +#endif + +#ifndef HAVE_RAND_R + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Posix rand_r function added May 1999 by Wes Peters . + */ + +#include +#include + +static int +do_rand(unsigned long *ctx) +{ + return ((*ctx = *ctx * 1103515245 + 12345) % ((u_long)PHP_RAND_MAX + 1)); +} + + +PHPAPI int +php_rand_r(unsigned int *ctx) +{ + u_long val = (u_long) *ctx; + *ctx = do_rand(&val); + return (int) *ctx; +} + +#endif + + +#ifndef HAVE_STRTOK_R + +/* + * Copyright (c) 1998 Softweyr LLC. All rights reserved. + * + * strtok_r, from Berkeley strtok + * Oct 13, 1998 by Wes Peters + * + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notices, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * + * This product includes software developed by Softweyr LLC, the + * University of California, Berkeley, and its contributors. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY SOFTWEYR LLC, THE REGENTS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTWEYR LLC, THE + * REGENTS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +PHPAPI char * +php_strtok_r(char *s, const char *delim, char **last) +{ + char *spanp; + int c, sc; + char *tok; + + if (s == NULL && (s = *last) == NULL) + { + return NULL; + } + + /* + * Skip (span) leading delimiters (s += strspn(s, delim), sort of). + */ +cont: + c = *s++; + for (spanp = (char *)delim; (sc = *spanp++) != 0; ) + { + if (c == sc) + { + goto cont; + } + } + + if (c == 0) /* no non-delimiter characters */ + { + *last = NULL; + return NULL; + } + tok = s - 1; + + /* + * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). + * Note that delim must have one NUL; we stop if we see that, too. + */ + for (;;) + { + c = *s++; + spanp = (char *)delim; + do + { + if ((sc = *spanp++) == c) + { + if (c == 0) + { + s = NULL; + } + else + { + char *w = s - 1; + *w = '\0'; + } + *last = s; + return tok; + } + } + while (sc != 0); + } + /* NOTREACHED */ +} + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/plugins/php/versions/55/src/zend_multiply.h b/plugins/php/versions/55/src/zend_multiply.h new file mode 100644 index 000000000..da96d12c6 --- /dev/null +++ b/plugins/php/versions/55/src/zend_multiply.h @@ -0,0 +1,97 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2015 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Sascha Schumann | + | Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#if defined(__i386__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__ ("imul %3,%0\n" \ + "adc $0,%1" \ + : "=r"(__tmpvar),"=r"(usedval) \ + : "0"(a), "r"(b), "1"(0)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__x86_64__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__ ("imul %3,%0\n" \ + "adc $0,%1" \ + : "=r"(__tmpvar),"=r"(usedval) \ + : "0"(a), "r"(b), "1"(0)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__arm__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__("smull %0, %1, %2, %3\n" \ + "sub %1, %1, %0, asr #31" \ + : "=r"(__tmpvar), "=r"(usedval) \ + : "r"(a), "r"(b)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__aarch64__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__("mul %0, %2, %3\n" \ + "smulh %1, %2, %3\n" \ + "sub %1, %1, %0, asr #63\n" \ + : "=&r"(__tmpvar), "=&r"(usedval) \ + : "r"(a), "r"(b)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif SIZEOF_LONG == 4 && defined(HAVE_ZEND_LONG64) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + zend_long64 __result = (zend_long64) (a) * (zend_long64) (b); \ + if (__result > LONG_MAX || __result < LONG_MIN) { \ + (dval) = (double) __result; \ + (usedval) = 1; \ + } else { \ + (lval) = (long) __result; \ + (usedval) = 0; \ + } \ +} while (0) + +#else + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __lres = (a) * (b); \ + long double __dres = (long double)(a) * (long double)(b); \ + long double __delta = (long double) __lres - __dres; \ + if ( ((usedval) = (( __dres + __delta ) != __dres))) { \ + (dval) = __dres; \ + } else { \ + (lval) = __lres; \ + } \ +} while (0) + +#endif diff --git a/plugins/php/versions/56/install.sh b/plugins/php/versions/56/install.sh new file mode 100755 index 000000000..f89badba9 --- /dev/null +++ b/plugins/php/versions/56/install.sh @@ -0,0 +1,162 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +# cd /www/server/mdserver-web/plugins/php && /bin/bash install.sh install 56 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +version=5.6.40 +PHP_VER=56 +md5_file_ok=c7dde3afb16ce7b761abf2805125d372 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_old.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://museum.php.net/php5/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`md5sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +OPTIONS='--without-iconv' +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-freetype-dir=${serverPath}/lib/freetype" + OPTIONS="${OPTIONS} --with-zlib-dir=$(brew --prefix zlib)" +else + OPTIONS="${OPTIONS} --with-readline" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + + + +if [ "${SYS_ARCH}" == "aarch64" ];then + # 修复aarch64架构下安装 + # /www/server/mdserver-web/plugins/php/versions/56/src/zend_multiply.h > /www/server/source/php/php56/Zend/zend_multiply.h + cat ${curPath}/versions/${PHP_VER}/src/zend_multiply.h > $sourcePath/php/php${PHP_VER}/Zend/zend_multiply.h +fi + + +if [ "${SYS_ARCH}" == "arm64" ] && [ "$sysName" == "Darwin" ] ;then + # 修复mac arm64架构下php安装 + # 修复不能识别到sys_icache_invalidate + cat ${curPath}/versions/${PHP_VER}/src/ext/pcre/sljitConfigInternal.h > $sourcePath/php/php${PHP_VER}/ext/pcre/pcrelib/sljit/sljitConfigInternal.h + cat ${curPath}/versions/${PHP_VER}/src/reentrancy.c > $sourcePath/php/php${PHP_VER}/main/reentrancy.c + cat ${curPath}/versions/${PHP_VER}/src/mkstemp.c > $sourcePath/php/php${PHP_VER}/ext/zip/lib/mkstemp.c +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} && ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/56/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --enable-mbstring \ + --enable-simplexml \ + --enable-ftp \ + --enable-sockets \ + --enable-shmop \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + + + +Uninstall_php() +{ + $serverPath/php/init.d/php56 stop + rm -rf $serverPath/php/56 + echo "卸载php-${version} ..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/56/src/ext/pcre/sljitConfigInternal.h b/plugins/php/versions/56/src/ext/pcre/sljitConfigInternal.h new file mode 100644 index 000000000..23c33609d --- /dev/null +++ b/plugins/php/versions/56/src/ext/pcre/sljitConfigInternal.h @@ -0,0 +1,702 @@ +/* + * Stack-less Just-In-Time compiler + * + * Copyright 2009-2012 Zoltan Herczeg (hzmester@freemail.hu). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SLJIT_CONFIG_INTERNAL_H_ +#define _SLJIT_CONFIG_INTERNAL_H_ + +/* + SLJIT defines the following architecture dependent types and macros: + + Types: + sljit_sb, sljit_ub : signed and unsigned 8 bit byte + sljit_sh, sljit_uh : signed and unsigned 16 bit half-word (short) type + sljit_si, sljit_ui : signed and unsigned 32 bit integer type + sljit_sw, sljit_uw : signed and unsigned machine word, enough to store a pointer + sljit_p : unsgined pointer value (usually the same as sljit_uw, but + some 64 bit ABIs may use 32 bit pointers) + sljit_s : single precision floating point value + sljit_d : double precision floating point value + + Macros for feature detection (boolean): + SLJIT_32BIT_ARCHITECTURE : 32 bit architecture + SLJIT_64BIT_ARCHITECTURE : 64 bit architecture + SLJIT_LITTLE_ENDIAN : little endian architecture + SLJIT_BIG_ENDIAN : big endian architecture + SLJIT_UNALIGNED : allows unaligned memory accesses for non-fpu operations (only!) + SLJIT_INDIRECT_CALL : see SLJIT_FUNC_OFFSET() for more information + + Constants: + SLJIT_NUMBER_OF_REGISTERS : number of available registers + SLJIT_NUMBER_OF_SCRATCH_REGISTERS : number of available scratch registers + SLJIT_NUMBER_OF_SAVED_REGISTERS : number of available saved registers + SLJIT_NUMBER_OF_FLOAT_REGISTERS : number of available floating point registers + SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS : number of available floating point scratch registers + SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS : number of available floating point saved registers + SLJIT_WORD_SHIFT : the shift required to apply when accessing a sljit_sw/sljit_uw array by index + SLJIT_DOUBLE_SHIFT : the shift required to apply when accessing + a double precision floating point array by index + SLJIT_SINGLE_SHIFT : the shift required to apply when accessing + a single precision floating point array by index + SLJIT_LOCALS_OFFSET : local space starting offset (SLJIT_SP + SLJIT_LOCALS_OFFSET) + SLJIT_RETURN_ADDRESS_OFFSET : a return instruction always adds this offset to the return address + + Other macros: + SLJIT_CALL : C calling convention define for both calling JIT form C and C callbacks for JIT + SLJIT_W(number) : defining 64 bit constants on 64 bit architectures (compiler independent helper) +*/ + +/*****************/ +/* Sanity check. */ +/*****************/ + +#if !((defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + || (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + || (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + || (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + || (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + || (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED)) +#error "An architecture must be selected" +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + + (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + + (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + + (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + + (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + + (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + + (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + + (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + + (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + + (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + + (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + + (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + + (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + + (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) >= 2 +#error "Multiple architectures are selected" +#endif + +/********************************************************/ +/* Automatic CPU detection (requires compiler support). */ +/********************************************************/ + +#if (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) + +#ifndef _WIN32 + +#if defined(__i386__) || defined(__i386) +#define SLJIT_CONFIG_X86_32 1 +#elif defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(__arm__) || defined(__ARM__) +#ifdef __thumb2__ +#define SLJIT_CONFIG_ARM_THUMB2 1 +#elif defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) +#define SLJIT_CONFIG_ARM_V7 1 +#else +#define SLJIT_CONFIG_ARM_V5 1 +#endif +#elif defined (__aarch64__) +#define SLJIT_CONFIG_ARM_64 1 +#elif defined(__ppc64__) || defined(__powerpc64__) || defined(_ARCH_PPC64) || (defined(_POWER) && defined(__64BIT__)) +#define SLJIT_CONFIG_PPC_64 1 +#elif defined(__ppc__) || defined(__powerpc__) || defined(_ARCH_PPC) || defined(_ARCH_PWR) || defined(_ARCH_PWR2) || defined(_POWER) +#define SLJIT_CONFIG_PPC_32 1 +#elif defined(__mips__) && !defined(_LP64) +#define SLJIT_CONFIG_MIPS_32 1 +#elif defined(__mips64) +#define SLJIT_CONFIG_MIPS_64 1 +#elif defined(__sparc__) || defined(__sparc) +#define SLJIT_CONFIG_SPARC_32 1 +#elif defined(__tilegx__) +#define SLJIT_CONFIG_TILEGX 1 +#else +/* Unsupported architecture */ +#define SLJIT_CONFIG_UNSUPPORTED 1 +#endif + +#else /* !_WIN32 */ + +#if defined(_M_X64) || defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(_ARM_) +#define SLJIT_CONFIG_ARM_V5 1 +#else +#define SLJIT_CONFIG_X86_32 1 +#endif + +#endif /* !WIN32 */ +#endif /* SLJIT_CONFIG_AUTO */ + +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +#undef SLJIT_EXECUTABLE_ALLOCATOR +#endif + +/******************************/ +/* CPU family type detection. */ +/******************************/ + +#if (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) +#define SLJIT_CONFIG_ARM_32 1 +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) +#define SLJIT_CONFIG_X86 1 +#elif (defined SLJIT_CONFIG_ARM_32 && SLJIT_CONFIG_ARM_32) || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) +#define SLJIT_CONFIG_ARM 1 +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_CONFIG_PPC 1 +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) +#define SLJIT_CONFIG_MIPS 1 +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) || (defined SLJIT_CONFIG_SPARC_64 && SLJIT_CONFIG_SPARC_64) +#define SLJIT_CONFIG_SPARC 1 +#endif + +/**********************************/ +/* External function definitions. */ +/**********************************/ + +#if !(defined SLJIT_STD_MACROS_DEFINED && SLJIT_STD_MACROS_DEFINED) + +/* These libraries are needed for the macros below. */ +#include +#include + +#endif /* SLJIT_STD_MACROS_DEFINED */ + +/* General macros: + Note: SLJIT is designed to be independent from them as possible. + + In release mode (SLJIT_DEBUG is not defined) only the following + external functions are needed: +*/ + +#ifndef SLJIT_MALLOC +#define SLJIT_MALLOC(size, allocator_data) malloc(size) +#endif + +#ifndef SLJIT_FREE +#define SLJIT_FREE(ptr, allocator_data) free(ptr) +#endif + +#ifndef SLJIT_MEMMOVE +#define SLJIT_MEMMOVE(dest, src, len) memmove(dest, src, len) +#endif + +#ifndef SLJIT_ZEROMEM +#define SLJIT_ZEROMEM(dest, len) memset(dest, 0, len) +#endif + +/***************************/ +/* Compiler helper macros. */ +/***************************/ + +#if !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) + +#if defined(__GNUC__) && (__GNUC__ >= 3) +#define SLJIT_LIKELY(x) __builtin_expect((x), 1) +#define SLJIT_UNLIKELY(x) __builtin_expect((x), 0) +#else +#define SLJIT_LIKELY(x) (x) +#define SLJIT_UNLIKELY(x) (x) +#endif + +#endif /* !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) */ + +#ifndef SLJIT_INLINE +/* Inline functions. Some old compilers do not support them. */ +#if defined(__SUNPRO_C) && __SUNPRO_C <= 0x510 +#define SLJIT_INLINE +#else +#define SLJIT_INLINE __inline +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_NOINLINE +/* Not inline functions. */ +#if defined(__GNUC__) +#define SLJIT_NOINLINE __attribute__ ((noinline)) +#else +#define SLJIT_NOINLINE +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_CONST +/* Const variables. */ +#define SLJIT_CONST const +#endif + +#ifndef SLJIT_UNUSED_ARG +/* Unused arguments. */ +#define SLJIT_UNUSED_ARG(arg) (void)arg +#endif + +/*********************************/ +/* Type of public API functions. */ +/*********************************/ + +#if (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) +/* Static ABI functions. For all-in-one programs. */ + +#if defined(__GNUC__) +/* Disable unused warnings in gcc. */ +#define SLJIT_API_FUNC_ATTRIBUTE static __attribute__((unused)) +#else +#define SLJIT_API_FUNC_ATTRIBUTE static +#endif + +#else +#define SLJIT_API_FUNC_ATTRIBUTE +#endif /* (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) */ + +/****************************/ +/* Instruction cache flush. */ +/****************************/ + +#ifndef SLJIT_CACHE_FLUSH + +#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) + +/* Not required to implement on archs with unified caches. */ +#define SLJIT_CACHE_FLUSH(from, to) + +#elif defined __APPLE__ + +/* Supported by all macs since Mac OS 10.5. + However, it does not work on non-jailbroken iOS devices, + although the compilation is successful. */ +#include +#define SLJIT_CACHE_FLUSH(from, to) \ + sys_icache_invalidate((char*)(from), (char*)(to) - (char*)(from)) + +#elif defined __ANDROID__ + +/* Android lacks __clear_cache; instead, cacheflush should be used. */ + +#define SLJIT_CACHE_FLUSH(from, to) \ + cacheflush((long)(from), (long)(to), 0) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +/* The __clear_cache() implementation of GCC is a dummy function on PowerPC. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + ppc_cache_flush((from), (to)) + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +/* The __clear_cache() implementation of GCC is a dummy function on Sparc. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + sparc_cache_flush((from), (to)) + +#else + +/* Calls __ARM_NR_cacheflush on ARM-Linux. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + __clear_cache((char*)(from), (char*)(to)) + +#endif + +#endif /* !SLJIT_CACHE_FLUSH */ + +/******************************************************/ +/* Byte/half/int/word/single/double type definitions. */ +/******************************************************/ + +/* 8 bit byte type. */ +typedef unsigned char sljit_ub; +typedef signed char sljit_sb; + +/* 16 bit half-word type. */ +typedef unsigned short int sljit_uh; +typedef signed short int sljit_sh; + +/* 32 bit integer type. */ +typedef unsigned int sljit_ui; +typedef signed int sljit_si; + +/* Machine word type. Enough for storing a pointer. + 32 bit for 32 bit machines. + 64 bit for 64 bit machines. */ +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +/* Just to have something. */ +#define SLJIT_WORD_SHIFT 0 +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#elif !(defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + && !(defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + && !(defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + && !(defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + && !(defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) +#define SLJIT_32BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 2 +typedef unsigned int sljit_uw; +typedef int sljit_sw; +#else +#define SLJIT_64BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 3 +#ifdef _WIN32 +typedef unsigned __int64 sljit_uw; +typedef __int64 sljit_sw; +#else +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#endif +#endif + +typedef sljit_uw sljit_p; + +/* Floating point types. */ +typedef float sljit_s; +typedef double sljit_d; + +/* Shift for pointer sized data. */ +#define SLJIT_POINTER_SHIFT SLJIT_WORD_SHIFT + +/* Shift for double precision sized data. */ +#define SLJIT_DOUBLE_SHIFT 3 +#define SLJIT_SINGLE_SHIFT 2 + +#ifndef SLJIT_W + +/* Defining long constants. */ +#if (defined SLJIT_64BIT_ARCHITECTURE && SLJIT_64BIT_ARCHITECTURE) +#define SLJIT_W(w) (w##ll) +#else +#define SLJIT_W(w) (w) +#endif + +#endif /* !SLJIT_W */ + +/*************************/ +/* Endianness detection. */ +/*************************/ + +#if !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) + +/* These macros are mostly useful for the applications. */ +#if (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) + +#ifdef __LITTLE_ENDIAN__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) + +#ifdef __MIPSEL__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +#define SLJIT_BIG_ENDIAN 1 + +#else +#define SLJIT_LITTLE_ENDIAN 1 +#endif + +#endif /* !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) */ + +/* Sanity check. */ +#if (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && (defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#if !(defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && !(defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#ifndef SLJIT_UNALIGNED + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_UNALIGNED 1 +#endif + +#endif /* !SLJIT_UNALIGNED */ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) +/* Auto detect SSE2 support using CPUID. + On 64 bit x86 cpus, sse2 must be present. */ +#define SLJIT_DETECT_SSE2 1 +#endif + +/*****************************************************************************************/ +/* Calling convention of functions generated by SLJIT or called from the generated code. */ +/*****************************************************************************************/ + +#ifndef SLJIT_CALL + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#if defined(__GNUC__) && !defined(__APPLE__) + +#define SLJIT_CALL __attribute__ ((fastcall)) +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(_MSC_VER) + +#define SLJIT_CALL __fastcall +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(__BORLANDC__) + +#define SLJIT_CALL __msfastcall +#define SLJIT_X86_32_FASTCALL 1 + +#else /* Unknown compiler. */ + +/* The cdecl attribute is the default. */ +#define SLJIT_CALL + +#endif + +#else /* Non x86-32 architectures. */ + +#define SLJIT_CALL + +#endif /* SLJIT_CONFIG_X86_32 */ + +#endif /* !SLJIT_CALL */ + +#ifndef SLJIT_INDIRECT_CALL +#if ((defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) && (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN)) \ + || ((defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) && defined _AIX) +/* It seems certain ppc compilers use an indirect addressing for functions + which makes things complicated. */ +#define SLJIT_INDIRECT_CALL 1 +#endif +#endif /* SLJIT_INDIRECT_CALL */ + +/* The offset which needs to be substracted from the return address to +determine the next executed instruction after return. */ +#ifndef SLJIT_RETURN_ADDRESS_OFFSET +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +#define SLJIT_RETURN_ADDRESS_OFFSET 8 +#else +#define SLJIT_RETURN_ADDRESS_OFFSET 0 +#endif +#endif /* SLJIT_RETURN_ADDRESS_OFFSET */ + +/***************************************************/ +/* Functions of the built-in executable allocator. */ +/***************************************************/ + +#if (defined SLJIT_EXECUTABLE_ALLOCATOR && SLJIT_EXECUTABLE_ALLOCATOR) +SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void); +#define SLJIT_MALLOC_EXEC(size) sljit_malloc_exec(size) +#define SLJIT_FREE_EXEC(ptr) sljit_free_exec(ptr) +#endif + +/**********************************************/ +/* Registers and locals offset determination. */ +/**********************************************/ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#define SLJIT_NUMBER_OF_REGISTERS 10 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#if (defined SLJIT_X86_32_FASTCALL && SLJIT_X86_32_FASTCALL) +#define SLJIT_LOCALS_OFFSET_BASE ((2 + 4) * sizeof(sljit_sw)) +#else +/* Maximum 3 arguments are passed on the stack, +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1 + 4) * sizeof(sljit_sw)) +#endif /* SLJIT_X86_32_FASTCALL */ + +#elif (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) + +#ifndef _WIN64 +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 6 +#define SLJIT_LOCALS_OFFSET_BASE (sizeof(sljit_sw)) +#else +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE ((4 + 2) * sizeof(sljit_sw)) +#endif /* _WIN64 */ + +#elif (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) + +#define SLJIT_NUMBER_OF_REGISTERS 25 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 10 +#define SLJIT_LOCALS_OFFSET_BASE (2 * sizeof(sljit_sw)) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +#define SLJIT_NUMBER_OF_REGISTERS 22 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 17 +#if (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) || (defined _AIX) +#define SLJIT_LOCALS_OFFSET_BASE ((6 + 8) * sizeof(sljit_sw)) +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1) * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE (3 * sizeof(sljit_sw)) +#endif /* SLJIT_CONFIG_PPC_64 || _AIX */ + +#elif (defined SLJIT_CONFIG_MIPS && SLJIT_CONFIG_MIPS) + +#define SLJIT_NUMBER_OF_REGISTERS 17 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#if (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) +#define SLJIT_LOCALS_OFFSET_BASE (4 * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE 0 +#endif + +#elif (defined SLJIT_CONFIG_SPARC && SLJIT_CONFIG_SPARC) + +#define SLJIT_NUMBER_OF_REGISTERS 18 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 14 +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((23 + 1) * sizeof(sljit_sw)) +#endif + +#elif (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) + +#define SLJIT_NUMBER_OF_REGISTERS 0 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 0 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#endif + +#define SLJIT_LOCALS_OFFSET (SLJIT_LOCALS_OFFSET_BASE) + +#define SLJIT_NUMBER_OF_SCRATCH_REGISTERS \ + (SLJIT_NUMBER_OF_REGISTERS - SLJIT_NUMBER_OF_SAVED_REGISTERS) + +#define SLJIT_NUMBER_OF_FLOAT_REGISTERS 6 +#if (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) && (defined _WIN64) +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 1 +#else +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 0 +#endif + +#define SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS \ + (SLJIT_NUMBER_OF_FLOAT_REGISTERS - SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS) + +/*************************************/ +/* Debug and verbose related macros. */ +/*************************************/ + +#if (defined SLJIT_VERBOSE && SLJIT_VERBOSE) +#include +#endif + +#if (defined SLJIT_DEBUG && SLJIT_DEBUG) + +#if !defined(SLJIT_ASSERT) || !defined(SLJIT_ASSERT_STOP) + +/* SLJIT_HALT_PROCESS must halt the process. */ +#ifndef SLJIT_HALT_PROCESS +#include + +#define SLJIT_HALT_PROCESS() \ + abort(); +#endif /* !SLJIT_HALT_PROCESS */ + +#include + +#endif /* !SLJIT_ASSERT || !SLJIT_ASSERT_STOP */ + +/* Feel free to redefine these two macros. */ +#ifndef SLJIT_ASSERT + +#define SLJIT_ASSERT(x) \ + do { \ + if (SLJIT_UNLIKELY(!(x))) { \ + printf("Assertion failed at " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } \ + } while (0) + +#endif /* !SLJIT_ASSERT */ + +#ifndef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT_STOP() \ + do { \ + printf("Should never been reached " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } while (0) + +#endif /* !SLJIT_ASSERT_STOP */ + +#else /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +/* Forcing empty, but valid statements. */ +#undef SLJIT_ASSERT +#undef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT(x) \ + do { } while (0) +#define SLJIT_ASSERT_STOP() \ + do { } while (0) + +#endif /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +#ifndef SLJIT_COMPILE_ASSERT + +/* Should be improved eventually. */ +#define SLJIT_COMPILE_ASSERT(x, description) \ + SLJIT_ASSERT(x) + +#endif /* !SLJIT_COMPILE_ASSERT */ + +#endif diff --git a/plugins/php/versions/56/src/mkstemp.c b/plugins/php/versions/56/src/mkstemp.c new file mode 100644 index 000000000..d1df8a5ca --- /dev/null +++ b/plugins/php/versions/56/src/mkstemp.c @@ -0,0 +1,153 @@ +/* Adapted from NetBSB libc by Dieter Baron */ + +/* NetBSD: gettemp.c,v 1.13 2003/12/05 00:57:36 uebayasi Exp */ + +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif +#include +#include +#ifndef _WIN32 +#include +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + + +int +_zip_mkstemp(char *path) +{ +#ifdef _WIN32 + int ret; + ret = _creat(_mktemp(path), _S_IREAD|_S_IWRITE); + if (ret == -1) { + return 0; + } else { + return ret; + } +#else + int fd; + char *start, *trv; + struct stat sbuf; + pid_t pid; + + /* To guarantee multiple calls generate unique names even if + the file is not created. 676 different possibilities with 7 + or more X's, 26 with 6 or less. */ + static char xtra[2] = "aa"; + int xcnt = 0; + + pid = getpid(); + + /* Move to end of path and count trailing X's. */ + for (trv = path; *trv; ++trv) + if (*trv == 'X') + xcnt++; + else + xcnt = 0; + + /* Use at least one from xtra. Use 2 if more than 6 X's. */ + if (*(trv - 1) == 'X') + *--trv = xtra[0]; + if (xcnt > 6 && *(trv - 1) == 'X') + *--trv = xtra[1]; + + /* Set remaining X's to pid digits with 0's to the left. */ + while (*--trv == 'X') { + *trv = (pid % 10) + '0'; + pid /= 10; + } + + /* update xtra for next call. */ + if (xtra[0] != 'z') + xtra[0]++; + else { + xtra[0] = 'a'; + if (xtra[1] != 'z') + xtra[1]++; + else + xtra[1] = 'a'; + } + + /* + * check the target directory; if you have six X's and it + * doesn't exist this runs for a *very* long time. + */ + for (start = trv + 1;; --trv) { + if (trv <= path) + break; + if (*trv == '/') { + *trv = '\0'; + if (stat(path, &sbuf)) + return (0); + if (!S_ISDIR(sbuf.st_mode)) { + errno = ENOTDIR; + return (0); + } + *trv = '/'; + break; + } + } + + for (;;) { + if ((fd=open(path, O_CREAT|O_EXCL|O_RDWR|O_BINARY, 0600)) >= 0) + return (fd); + if (errno != EEXIST) + return (0); + + /* tricky little algorithm for backward compatibility */ + for (trv = start;;) { + if (!*trv) + return (0); + if (*trv == 'z') + *trv++ = 'a'; + else { + if (isdigit((unsigned char)*trv)) + *trv = 'a'; + else + ++*trv; + break; + } + } + } + /*NOTREACHED*/ +#endif +} diff --git a/plugins/php/versions/56/src/reentrancy.c b/plugins/php/versions/56/src/reentrancy.c new file mode 100644 index 000000000..e7c5d7171 --- /dev/null +++ b/plugins/php/versions/56/src/reentrancy.c @@ -0,0 +1,450 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2014 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include +#include +#include +#ifdef HAVE_DIRENT_H +#include +#endif + +#include "php_reentrancy.h" +#include "ext/standard/php_rand.h" /* for PHP_RAND_MAX */ + +enum { + LOCALTIME_R, + CTIME_R, + ASCTIME_R, + GMTIME_R, + READDIR_R, + NUMBER_OF_LOCKS +}; + +#if defined(PHP_NEED_REENTRANCY) + +#include + +static MUTEX_T reentrant_locks[NUMBER_OF_LOCKS]; + +#define local_lock(x) tsrm_mutex_lock(reentrant_locks[x]) +#define local_unlock(x) tsrm_mutex_unlock(reentrant_locks[x]) + +#else + +#define local_lock(x) +#define local_unlock(x) + +#endif + +#if defined(PHP_IRIX_TIME_R) + +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf) == buf) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf) == buf) + return (buf); + return (NULL); +} + +#endif + +#if defined(PHP_HPUX_TIME_R) + +#define HAVE_LOCALTIME_R 1 +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 +#define HAVE_GMTIME_R 1 + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (localtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (gmtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +#endif + +#if defined(__BEOS__) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + /* Modified according to LibC definition */ + if (((struct tm*)gmtime_r(timep, p_tm)) == p_tm) + return (p_tm); + return (NULL); +} + +#endif /* BEOS */ + +#if !defined(HAVE_POSIX_READDIR_R) + +PHPAPI int php_readdir_r(DIR *dirp, struct dirent *entry, + struct dirent **result) +{ +#if defined(HAVE_OLD_READDIR_R) + int ret = 0; + + /* We cannot rely on the return value of readdir_r + as it differs between various platforms + (HPUX returns 0 on success whereas Solaris returns non-zero) + */ + entry->d_name[0] = '\0'; + readdir_r(dirp, entry, result); + + if (entry->d_name[0] == '\0') { + *result = NULL; + ret = errno; + } else { + *result = entry; + } + return ret; +#else + struct dirent *ptr; + int ret = 0; + + local_lock(READDIR_R); + + errno = 0; + + ptr = readdir(dirp); + + if (!ptr && errno != 0) + ret = errno; + + if (ptr) + memcpy(entry, ptr, sizeof(*ptr)); + + *result = ptr; + + local_unlock(READDIR_R); + + return ret; +#endif +} + +#endif + +#if !defined(HAVE_LOCALTIME_R) && defined(HAVE_LOCALTIME) + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(LOCALTIME_R); + + tmp = localtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(LOCALTIME_R); + + return tmp; +} + +#endif + +#if !defined(HAVE_CTIME_R) && defined(HAVE_CTIME) + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + char *tmp; + + local_lock(CTIME_R); + + tmp = ctime(clock); + strcpy(buf, tmp); + + local_unlock(CTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_ASCTIME_R) && defined(HAVE_ASCTIME) + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + char *tmp; + + local_lock(ASCTIME_R); + + tmp = asctime(tm); + strcpy(buf, tmp); + + local_unlock(ASCTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_GMTIME_R) && defined(HAVE_GMTIME) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(GMTIME_R); + + tmp = gmtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(GMTIME_R); + + return tmp; +} + +#endif + +#if defined(PHP_NEED_REENTRANCY) + +void reentrancy_startup(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + reentrant_locks[i] = tsrm_mutex_alloc(); + } +} + +void reentrancy_shutdown(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + tsrm_mutex_free(reentrant_locks[i]); + } +} + +#endif + +#ifndef HAVE_RAND_R + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Posix rand_r function added May 1999 by Wes Peters . + */ + +#include +#include + +static int +do_rand(unsigned long *ctx) +{ + return ((*ctx = *ctx * 1103515245 + 12345) % ((u_long)PHP_RAND_MAX + 1)); +} + + +PHPAPI int +php_rand_r(unsigned int *ctx) +{ + u_long val = (u_long) *ctx; + *ctx = do_rand(&val); + return (int) *ctx; +} + +#endif + + +#ifndef HAVE_STRTOK_R + +/* + * Copyright (c) 1998 Softweyr LLC. All rights reserved. + * + * strtok_r, from Berkeley strtok + * Oct 13, 1998 by Wes Peters + * + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notices, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * + * This product includes software developed by Softweyr LLC, the + * University of California, Berkeley, and its contributors. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY SOFTWEYR LLC, THE REGENTS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTWEYR LLC, THE + * REGENTS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +PHPAPI char * +php_strtok_r(char *s, const char *delim, char **last) +{ + char *spanp; + int c, sc; + char *tok; + + if (s == NULL && (s = *last) == NULL) + { + return NULL; + } + + /* + * Skip (span) leading delimiters (s += strspn(s, delim), sort of). + */ +cont: + c = *s++; + for (spanp = (char *)delim; (sc = *spanp++) != 0; ) + { + if (c == sc) + { + goto cont; + } + } + + if (c == 0) /* no non-delimiter characters */ + { + *last = NULL; + return NULL; + } + tok = s - 1; + + /* + * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). + * Note that delim must have one NUL; we stop if we see that, too. + */ + for (;;) + { + c = *s++; + spanp = (char *)delim; + do + { + if ((sc = *spanp++) == c) + { + if (c == 0) + { + s = NULL; + } + else + { + char *w = s - 1; + *w = '\0'; + } + *last = s; + return tok; + } + } + while (sc != 0); + } + /* NOTREACHED */ +} + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/plugins/php/versions/56/src/zend_multiply.h b/plugins/php/versions/56/src/zend_multiply.h new file mode 100644 index 000000000..8723551be --- /dev/null +++ b/plugins/php/versions/56/src/zend_multiply.h @@ -0,0 +1,97 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2016 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Sascha Schumann | + | Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#if defined(__i386__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__ ("imul %3,%0\n" \ + "adc $0,%1" \ + : "=r"(__tmpvar),"=r"(usedval) \ + : "0"(a), "r"(b), "1"(0)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__x86_64__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__ ("imul %3,%0\n" \ + "adc $0,%1" \ + : "=r"(__tmpvar),"=r"(usedval) \ + : "0"(a), "r"(b), "1"(0)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__arm__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__("smull %0, %1, %2, %3\n" \ + "sub %1, %1, %0, asr #31" \ + : "=&r"(__tmpvar), "=&r"(usedval) \ + : "r"(a), "r"(b)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__aarch64__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__("mul %0, %2, %3\n" \ + "smulh %1, %2, %3\n" \ + "sub %1, %1, %0, asr #63\n" \ + : "=&r"(__tmpvar), "=&r"(usedval) \ + : "r"(a), "r"(b)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif SIZEOF_LONG == 4 && defined(HAVE_ZEND_LONG64) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + zend_long64 __result = (zend_long64) (a) * (zend_long64) (b); \ + if (__result > LONG_MAX || __result < LONG_MIN) { \ + (dval) = (double) __result; \ + (usedval) = 1; \ + } else { \ + (lval) = (long) __result; \ + (usedval) = 0; \ + } \ +} while (0) + +#else + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __lres = (a) * (b); \ + long double __dres = (long double)(a) * (long double)(b); \ + long double __delta = (long double) __lres - __dres; \ + if ( ((usedval) = (( __dres + __delta ) != __dres))) { \ + (dval) = __dres; \ + } else { \ + (lval) = __lres; \ + } \ +} while (0) + +#endif diff --git a/plugins/php/versions/56/zendguardloader.sh b/plugins/php/versions/56/zendguardloader.sh new file mode 100755 index 000000000..89b87ae74 --- /dev/null +++ b/plugins/php/versions/56/zendguardloader.sh @@ -0,0 +1,97 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=ZendGuardLoader + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` + extDir=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME} + extFile=${extDir}/${LIBNAME}.so + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ $sysName == 'Darwin' ]; then + wget -O $php_lib/zend-loader-php5.6.tar.gz http://downloads.zend.com/guard/7.0.0/zend-loader-php5.6-darwin10.7-x86_64_update1.tar.gz + else + wget -O $php_lib/zend-loader-php5.6.tar.gz http://downloads.zend.com/guard/7.0.0/zend-loader-php5.6-linux-x86_64_update1.tar.gz + fi + + cd $php_lib && tar xvf zend-loader-php5.6.tar.gz + cd zend-loader-php5.6-* + cp ZendGuardLoader.so ${extDir} + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo -e "[Zend ZendGuard Loader]\nzend_extension=ZendGuardLoader.so\nzend_loader.enable=1\nzend_loader.disable_licensing=0\nzend_loader.obfuscation_level_support=3\nzend_loader.license_path=" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + extFile=$serverPath/php/${version}/lib/php/extensions/no-debug-non-zts-20131226/${LIBNAME}.so + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + sed -i $BAK '/ZendGuardLoader.so/d' $serverPath/php/$version/etc/php.ini + sed -i $BAK '/zend_loader/d' $serverPath/php/$version/etc/php.ini + sed -i $BAK '/\[Zend ZendGuard Loader\]/d' $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +actionType=$1 +version=$2 +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/70/install.sh b/plugins/php/versions/70/install.sh new file mode 100755 index 000000000..f97d34be7 --- /dev/null +++ b/plugins/php/versions/70/install.sh @@ -0,0 +1,165 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +version=7.0.33 +PHP_VER=70 +md5_file_ok=a6d7c355d023301a1a9ec8b4f32a4856 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_old.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://museum.php.net/php7/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`md5sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +OPTIONS='--without-iconv' +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-freetype-dir=${serverPath}/lib/freetype" + # OPTIONS="${OPTIONS} --with-external-pcre=$(brew --prefix pcre2)" +else + OPTIONS="${OPTIONS} --with-readline" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +if [ "${SYS_ARCH}" == "arm64" ] && [ "$sysName" == "Darwin" ] ;then + # 修复mac arm64架构下php安装 + # 修复不能识别到sys_icache_invalidate + cat ${curPath}/versions/${PHP_VER}/src/ext/pcre/sljitConfigInternal.h > $sourcePath/php/php${PHP_VER}/ext/pcre/pcrelib/sljit/sljitConfigInternal.h + cat ${curPath}/versions/${PHP_VER}/src/reentrancy.c > $sourcePath/php/php${PHP_VER}/main/reentrancy.c + cat ${curPath}/versions/${PHP_VER}/src/mkstemp.c > $sourcePath/php/php${PHP_VER}/ext/zip/lib/mkstemp.c +fi + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl@1.0 | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl@1.0)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl10/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} && ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --enable-mbstring \ + --enable-simplexml \ + --enable-ftp \ + --enable-sockets \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + + + +Uninstall_php() +{ + $serverPath/php/init.d/php70 stop + rm -rf $serverPath/php/70 + echo "卸载php-7.0.30 ..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/70/src/ext/pcre/sljitConfigInternal.h b/plugins/php/versions/70/src/ext/pcre/sljitConfigInternal.h new file mode 100644 index 000000000..23c33609d --- /dev/null +++ b/plugins/php/versions/70/src/ext/pcre/sljitConfigInternal.h @@ -0,0 +1,702 @@ +/* + * Stack-less Just-In-Time compiler + * + * Copyright 2009-2012 Zoltan Herczeg (hzmester@freemail.hu). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SLJIT_CONFIG_INTERNAL_H_ +#define _SLJIT_CONFIG_INTERNAL_H_ + +/* + SLJIT defines the following architecture dependent types and macros: + + Types: + sljit_sb, sljit_ub : signed and unsigned 8 bit byte + sljit_sh, sljit_uh : signed and unsigned 16 bit half-word (short) type + sljit_si, sljit_ui : signed and unsigned 32 bit integer type + sljit_sw, sljit_uw : signed and unsigned machine word, enough to store a pointer + sljit_p : unsgined pointer value (usually the same as sljit_uw, but + some 64 bit ABIs may use 32 bit pointers) + sljit_s : single precision floating point value + sljit_d : double precision floating point value + + Macros for feature detection (boolean): + SLJIT_32BIT_ARCHITECTURE : 32 bit architecture + SLJIT_64BIT_ARCHITECTURE : 64 bit architecture + SLJIT_LITTLE_ENDIAN : little endian architecture + SLJIT_BIG_ENDIAN : big endian architecture + SLJIT_UNALIGNED : allows unaligned memory accesses for non-fpu operations (only!) + SLJIT_INDIRECT_CALL : see SLJIT_FUNC_OFFSET() for more information + + Constants: + SLJIT_NUMBER_OF_REGISTERS : number of available registers + SLJIT_NUMBER_OF_SCRATCH_REGISTERS : number of available scratch registers + SLJIT_NUMBER_OF_SAVED_REGISTERS : number of available saved registers + SLJIT_NUMBER_OF_FLOAT_REGISTERS : number of available floating point registers + SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS : number of available floating point scratch registers + SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS : number of available floating point saved registers + SLJIT_WORD_SHIFT : the shift required to apply when accessing a sljit_sw/sljit_uw array by index + SLJIT_DOUBLE_SHIFT : the shift required to apply when accessing + a double precision floating point array by index + SLJIT_SINGLE_SHIFT : the shift required to apply when accessing + a single precision floating point array by index + SLJIT_LOCALS_OFFSET : local space starting offset (SLJIT_SP + SLJIT_LOCALS_OFFSET) + SLJIT_RETURN_ADDRESS_OFFSET : a return instruction always adds this offset to the return address + + Other macros: + SLJIT_CALL : C calling convention define for both calling JIT form C and C callbacks for JIT + SLJIT_W(number) : defining 64 bit constants on 64 bit architectures (compiler independent helper) +*/ + +/*****************/ +/* Sanity check. */ +/*****************/ + +#if !((defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + || (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + || (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + || (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + || (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + || (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED)) +#error "An architecture must be selected" +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + + (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + + (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + + (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + + (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + + (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + + (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + + (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + + (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + + (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + + (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + + (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + + (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + + (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) >= 2 +#error "Multiple architectures are selected" +#endif + +/********************************************************/ +/* Automatic CPU detection (requires compiler support). */ +/********************************************************/ + +#if (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) + +#ifndef _WIN32 + +#if defined(__i386__) || defined(__i386) +#define SLJIT_CONFIG_X86_32 1 +#elif defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(__arm__) || defined(__ARM__) +#ifdef __thumb2__ +#define SLJIT_CONFIG_ARM_THUMB2 1 +#elif defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) +#define SLJIT_CONFIG_ARM_V7 1 +#else +#define SLJIT_CONFIG_ARM_V5 1 +#endif +#elif defined (__aarch64__) +#define SLJIT_CONFIG_ARM_64 1 +#elif defined(__ppc64__) || defined(__powerpc64__) || defined(_ARCH_PPC64) || (defined(_POWER) && defined(__64BIT__)) +#define SLJIT_CONFIG_PPC_64 1 +#elif defined(__ppc__) || defined(__powerpc__) || defined(_ARCH_PPC) || defined(_ARCH_PWR) || defined(_ARCH_PWR2) || defined(_POWER) +#define SLJIT_CONFIG_PPC_32 1 +#elif defined(__mips__) && !defined(_LP64) +#define SLJIT_CONFIG_MIPS_32 1 +#elif defined(__mips64) +#define SLJIT_CONFIG_MIPS_64 1 +#elif defined(__sparc__) || defined(__sparc) +#define SLJIT_CONFIG_SPARC_32 1 +#elif defined(__tilegx__) +#define SLJIT_CONFIG_TILEGX 1 +#else +/* Unsupported architecture */ +#define SLJIT_CONFIG_UNSUPPORTED 1 +#endif + +#else /* !_WIN32 */ + +#if defined(_M_X64) || defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(_ARM_) +#define SLJIT_CONFIG_ARM_V5 1 +#else +#define SLJIT_CONFIG_X86_32 1 +#endif + +#endif /* !WIN32 */ +#endif /* SLJIT_CONFIG_AUTO */ + +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +#undef SLJIT_EXECUTABLE_ALLOCATOR +#endif + +/******************************/ +/* CPU family type detection. */ +/******************************/ + +#if (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) +#define SLJIT_CONFIG_ARM_32 1 +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) +#define SLJIT_CONFIG_X86 1 +#elif (defined SLJIT_CONFIG_ARM_32 && SLJIT_CONFIG_ARM_32) || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) +#define SLJIT_CONFIG_ARM 1 +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_CONFIG_PPC 1 +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) +#define SLJIT_CONFIG_MIPS 1 +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) || (defined SLJIT_CONFIG_SPARC_64 && SLJIT_CONFIG_SPARC_64) +#define SLJIT_CONFIG_SPARC 1 +#endif + +/**********************************/ +/* External function definitions. */ +/**********************************/ + +#if !(defined SLJIT_STD_MACROS_DEFINED && SLJIT_STD_MACROS_DEFINED) + +/* These libraries are needed for the macros below. */ +#include +#include + +#endif /* SLJIT_STD_MACROS_DEFINED */ + +/* General macros: + Note: SLJIT is designed to be independent from them as possible. + + In release mode (SLJIT_DEBUG is not defined) only the following + external functions are needed: +*/ + +#ifndef SLJIT_MALLOC +#define SLJIT_MALLOC(size, allocator_data) malloc(size) +#endif + +#ifndef SLJIT_FREE +#define SLJIT_FREE(ptr, allocator_data) free(ptr) +#endif + +#ifndef SLJIT_MEMMOVE +#define SLJIT_MEMMOVE(dest, src, len) memmove(dest, src, len) +#endif + +#ifndef SLJIT_ZEROMEM +#define SLJIT_ZEROMEM(dest, len) memset(dest, 0, len) +#endif + +/***************************/ +/* Compiler helper macros. */ +/***************************/ + +#if !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) + +#if defined(__GNUC__) && (__GNUC__ >= 3) +#define SLJIT_LIKELY(x) __builtin_expect((x), 1) +#define SLJIT_UNLIKELY(x) __builtin_expect((x), 0) +#else +#define SLJIT_LIKELY(x) (x) +#define SLJIT_UNLIKELY(x) (x) +#endif + +#endif /* !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) */ + +#ifndef SLJIT_INLINE +/* Inline functions. Some old compilers do not support them. */ +#if defined(__SUNPRO_C) && __SUNPRO_C <= 0x510 +#define SLJIT_INLINE +#else +#define SLJIT_INLINE __inline +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_NOINLINE +/* Not inline functions. */ +#if defined(__GNUC__) +#define SLJIT_NOINLINE __attribute__ ((noinline)) +#else +#define SLJIT_NOINLINE +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_CONST +/* Const variables. */ +#define SLJIT_CONST const +#endif + +#ifndef SLJIT_UNUSED_ARG +/* Unused arguments. */ +#define SLJIT_UNUSED_ARG(arg) (void)arg +#endif + +/*********************************/ +/* Type of public API functions. */ +/*********************************/ + +#if (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) +/* Static ABI functions. For all-in-one programs. */ + +#if defined(__GNUC__) +/* Disable unused warnings in gcc. */ +#define SLJIT_API_FUNC_ATTRIBUTE static __attribute__((unused)) +#else +#define SLJIT_API_FUNC_ATTRIBUTE static +#endif + +#else +#define SLJIT_API_FUNC_ATTRIBUTE +#endif /* (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) */ + +/****************************/ +/* Instruction cache flush. */ +/****************************/ + +#ifndef SLJIT_CACHE_FLUSH + +#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) + +/* Not required to implement on archs with unified caches. */ +#define SLJIT_CACHE_FLUSH(from, to) + +#elif defined __APPLE__ + +/* Supported by all macs since Mac OS 10.5. + However, it does not work on non-jailbroken iOS devices, + although the compilation is successful. */ +#include +#define SLJIT_CACHE_FLUSH(from, to) \ + sys_icache_invalidate((char*)(from), (char*)(to) - (char*)(from)) + +#elif defined __ANDROID__ + +/* Android lacks __clear_cache; instead, cacheflush should be used. */ + +#define SLJIT_CACHE_FLUSH(from, to) \ + cacheflush((long)(from), (long)(to), 0) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +/* The __clear_cache() implementation of GCC is a dummy function on PowerPC. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + ppc_cache_flush((from), (to)) + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +/* The __clear_cache() implementation of GCC is a dummy function on Sparc. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + sparc_cache_flush((from), (to)) + +#else + +/* Calls __ARM_NR_cacheflush on ARM-Linux. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + __clear_cache((char*)(from), (char*)(to)) + +#endif + +#endif /* !SLJIT_CACHE_FLUSH */ + +/******************************************************/ +/* Byte/half/int/word/single/double type definitions. */ +/******************************************************/ + +/* 8 bit byte type. */ +typedef unsigned char sljit_ub; +typedef signed char sljit_sb; + +/* 16 bit half-word type. */ +typedef unsigned short int sljit_uh; +typedef signed short int sljit_sh; + +/* 32 bit integer type. */ +typedef unsigned int sljit_ui; +typedef signed int sljit_si; + +/* Machine word type. Enough for storing a pointer. + 32 bit for 32 bit machines. + 64 bit for 64 bit machines. */ +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +/* Just to have something. */ +#define SLJIT_WORD_SHIFT 0 +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#elif !(defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + && !(defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + && !(defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + && !(defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + && !(defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) +#define SLJIT_32BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 2 +typedef unsigned int sljit_uw; +typedef int sljit_sw; +#else +#define SLJIT_64BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 3 +#ifdef _WIN32 +typedef unsigned __int64 sljit_uw; +typedef __int64 sljit_sw; +#else +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#endif +#endif + +typedef sljit_uw sljit_p; + +/* Floating point types. */ +typedef float sljit_s; +typedef double sljit_d; + +/* Shift for pointer sized data. */ +#define SLJIT_POINTER_SHIFT SLJIT_WORD_SHIFT + +/* Shift for double precision sized data. */ +#define SLJIT_DOUBLE_SHIFT 3 +#define SLJIT_SINGLE_SHIFT 2 + +#ifndef SLJIT_W + +/* Defining long constants. */ +#if (defined SLJIT_64BIT_ARCHITECTURE && SLJIT_64BIT_ARCHITECTURE) +#define SLJIT_W(w) (w##ll) +#else +#define SLJIT_W(w) (w) +#endif + +#endif /* !SLJIT_W */ + +/*************************/ +/* Endianness detection. */ +/*************************/ + +#if !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) + +/* These macros are mostly useful for the applications. */ +#if (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) + +#ifdef __LITTLE_ENDIAN__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) + +#ifdef __MIPSEL__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +#define SLJIT_BIG_ENDIAN 1 + +#else +#define SLJIT_LITTLE_ENDIAN 1 +#endif + +#endif /* !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) */ + +/* Sanity check. */ +#if (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && (defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#if !(defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && !(defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#ifndef SLJIT_UNALIGNED + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_UNALIGNED 1 +#endif + +#endif /* !SLJIT_UNALIGNED */ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) +/* Auto detect SSE2 support using CPUID. + On 64 bit x86 cpus, sse2 must be present. */ +#define SLJIT_DETECT_SSE2 1 +#endif + +/*****************************************************************************************/ +/* Calling convention of functions generated by SLJIT or called from the generated code. */ +/*****************************************************************************************/ + +#ifndef SLJIT_CALL + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#if defined(__GNUC__) && !defined(__APPLE__) + +#define SLJIT_CALL __attribute__ ((fastcall)) +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(_MSC_VER) + +#define SLJIT_CALL __fastcall +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(__BORLANDC__) + +#define SLJIT_CALL __msfastcall +#define SLJIT_X86_32_FASTCALL 1 + +#else /* Unknown compiler. */ + +/* The cdecl attribute is the default. */ +#define SLJIT_CALL + +#endif + +#else /* Non x86-32 architectures. */ + +#define SLJIT_CALL + +#endif /* SLJIT_CONFIG_X86_32 */ + +#endif /* !SLJIT_CALL */ + +#ifndef SLJIT_INDIRECT_CALL +#if ((defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) && (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN)) \ + || ((defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) && defined _AIX) +/* It seems certain ppc compilers use an indirect addressing for functions + which makes things complicated. */ +#define SLJIT_INDIRECT_CALL 1 +#endif +#endif /* SLJIT_INDIRECT_CALL */ + +/* The offset which needs to be substracted from the return address to +determine the next executed instruction after return. */ +#ifndef SLJIT_RETURN_ADDRESS_OFFSET +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +#define SLJIT_RETURN_ADDRESS_OFFSET 8 +#else +#define SLJIT_RETURN_ADDRESS_OFFSET 0 +#endif +#endif /* SLJIT_RETURN_ADDRESS_OFFSET */ + +/***************************************************/ +/* Functions of the built-in executable allocator. */ +/***************************************************/ + +#if (defined SLJIT_EXECUTABLE_ALLOCATOR && SLJIT_EXECUTABLE_ALLOCATOR) +SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void); +#define SLJIT_MALLOC_EXEC(size) sljit_malloc_exec(size) +#define SLJIT_FREE_EXEC(ptr) sljit_free_exec(ptr) +#endif + +/**********************************************/ +/* Registers and locals offset determination. */ +/**********************************************/ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#define SLJIT_NUMBER_OF_REGISTERS 10 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#if (defined SLJIT_X86_32_FASTCALL && SLJIT_X86_32_FASTCALL) +#define SLJIT_LOCALS_OFFSET_BASE ((2 + 4) * sizeof(sljit_sw)) +#else +/* Maximum 3 arguments are passed on the stack, +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1 + 4) * sizeof(sljit_sw)) +#endif /* SLJIT_X86_32_FASTCALL */ + +#elif (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) + +#ifndef _WIN64 +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 6 +#define SLJIT_LOCALS_OFFSET_BASE (sizeof(sljit_sw)) +#else +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE ((4 + 2) * sizeof(sljit_sw)) +#endif /* _WIN64 */ + +#elif (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) + +#define SLJIT_NUMBER_OF_REGISTERS 25 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 10 +#define SLJIT_LOCALS_OFFSET_BASE (2 * sizeof(sljit_sw)) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +#define SLJIT_NUMBER_OF_REGISTERS 22 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 17 +#if (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) || (defined _AIX) +#define SLJIT_LOCALS_OFFSET_BASE ((6 + 8) * sizeof(sljit_sw)) +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1) * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE (3 * sizeof(sljit_sw)) +#endif /* SLJIT_CONFIG_PPC_64 || _AIX */ + +#elif (defined SLJIT_CONFIG_MIPS && SLJIT_CONFIG_MIPS) + +#define SLJIT_NUMBER_OF_REGISTERS 17 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#if (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) +#define SLJIT_LOCALS_OFFSET_BASE (4 * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE 0 +#endif + +#elif (defined SLJIT_CONFIG_SPARC && SLJIT_CONFIG_SPARC) + +#define SLJIT_NUMBER_OF_REGISTERS 18 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 14 +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((23 + 1) * sizeof(sljit_sw)) +#endif + +#elif (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) + +#define SLJIT_NUMBER_OF_REGISTERS 0 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 0 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#endif + +#define SLJIT_LOCALS_OFFSET (SLJIT_LOCALS_OFFSET_BASE) + +#define SLJIT_NUMBER_OF_SCRATCH_REGISTERS \ + (SLJIT_NUMBER_OF_REGISTERS - SLJIT_NUMBER_OF_SAVED_REGISTERS) + +#define SLJIT_NUMBER_OF_FLOAT_REGISTERS 6 +#if (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) && (defined _WIN64) +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 1 +#else +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 0 +#endif + +#define SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS \ + (SLJIT_NUMBER_OF_FLOAT_REGISTERS - SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS) + +/*************************************/ +/* Debug and verbose related macros. */ +/*************************************/ + +#if (defined SLJIT_VERBOSE && SLJIT_VERBOSE) +#include +#endif + +#if (defined SLJIT_DEBUG && SLJIT_DEBUG) + +#if !defined(SLJIT_ASSERT) || !defined(SLJIT_ASSERT_STOP) + +/* SLJIT_HALT_PROCESS must halt the process. */ +#ifndef SLJIT_HALT_PROCESS +#include + +#define SLJIT_HALT_PROCESS() \ + abort(); +#endif /* !SLJIT_HALT_PROCESS */ + +#include + +#endif /* !SLJIT_ASSERT || !SLJIT_ASSERT_STOP */ + +/* Feel free to redefine these two macros. */ +#ifndef SLJIT_ASSERT + +#define SLJIT_ASSERT(x) \ + do { \ + if (SLJIT_UNLIKELY(!(x))) { \ + printf("Assertion failed at " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } \ + } while (0) + +#endif /* !SLJIT_ASSERT */ + +#ifndef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT_STOP() \ + do { \ + printf("Should never been reached " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } while (0) + +#endif /* !SLJIT_ASSERT_STOP */ + +#else /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +/* Forcing empty, but valid statements. */ +#undef SLJIT_ASSERT +#undef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT(x) \ + do { } while (0) +#define SLJIT_ASSERT_STOP() \ + do { } while (0) + +#endif /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +#ifndef SLJIT_COMPILE_ASSERT + +/* Should be improved eventually. */ +#define SLJIT_COMPILE_ASSERT(x, description) \ + SLJIT_ASSERT(x) + +#endif /* !SLJIT_COMPILE_ASSERT */ + +#endif diff --git a/plugins/php/versions/70/src/mkstemp.c b/plugins/php/versions/70/src/mkstemp.c new file mode 100644 index 000000000..d1df8a5ca --- /dev/null +++ b/plugins/php/versions/70/src/mkstemp.c @@ -0,0 +1,153 @@ +/* Adapted from NetBSB libc by Dieter Baron */ + +/* NetBSD: gettemp.c,v 1.13 2003/12/05 00:57:36 uebayasi Exp */ + +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif +#include +#include +#ifndef _WIN32 +#include +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + + +int +_zip_mkstemp(char *path) +{ +#ifdef _WIN32 + int ret; + ret = _creat(_mktemp(path), _S_IREAD|_S_IWRITE); + if (ret == -1) { + return 0; + } else { + return ret; + } +#else + int fd; + char *start, *trv; + struct stat sbuf; + pid_t pid; + + /* To guarantee multiple calls generate unique names even if + the file is not created. 676 different possibilities with 7 + or more X's, 26 with 6 or less. */ + static char xtra[2] = "aa"; + int xcnt = 0; + + pid = getpid(); + + /* Move to end of path and count trailing X's. */ + for (trv = path; *trv; ++trv) + if (*trv == 'X') + xcnt++; + else + xcnt = 0; + + /* Use at least one from xtra. Use 2 if more than 6 X's. */ + if (*(trv - 1) == 'X') + *--trv = xtra[0]; + if (xcnt > 6 && *(trv - 1) == 'X') + *--trv = xtra[1]; + + /* Set remaining X's to pid digits with 0's to the left. */ + while (*--trv == 'X') { + *trv = (pid % 10) + '0'; + pid /= 10; + } + + /* update xtra for next call. */ + if (xtra[0] != 'z') + xtra[0]++; + else { + xtra[0] = 'a'; + if (xtra[1] != 'z') + xtra[1]++; + else + xtra[1] = 'a'; + } + + /* + * check the target directory; if you have six X's and it + * doesn't exist this runs for a *very* long time. + */ + for (start = trv + 1;; --trv) { + if (trv <= path) + break; + if (*trv == '/') { + *trv = '\0'; + if (stat(path, &sbuf)) + return (0); + if (!S_ISDIR(sbuf.st_mode)) { + errno = ENOTDIR; + return (0); + } + *trv = '/'; + break; + } + } + + for (;;) { + if ((fd=open(path, O_CREAT|O_EXCL|O_RDWR|O_BINARY, 0600)) >= 0) + return (fd); + if (errno != EEXIST) + return (0); + + /* tricky little algorithm for backward compatibility */ + for (trv = start;;) { + if (!*trv) + return (0); + if (*trv == 'z') + *trv++ = 'a'; + else { + if (isdigit((unsigned char)*trv)) + *trv = 'a'; + else + ++*trv; + break; + } + } + } + /*NOTREACHED*/ +#endif +} diff --git a/plugins/php/versions/70/src/reentrancy.c b/plugins/php/versions/70/src/reentrancy.c new file mode 100644 index 000000000..e7c5d7171 --- /dev/null +++ b/plugins/php/versions/70/src/reentrancy.c @@ -0,0 +1,450 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2014 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include +#include +#include +#ifdef HAVE_DIRENT_H +#include +#endif + +#include "php_reentrancy.h" +#include "ext/standard/php_rand.h" /* for PHP_RAND_MAX */ + +enum { + LOCALTIME_R, + CTIME_R, + ASCTIME_R, + GMTIME_R, + READDIR_R, + NUMBER_OF_LOCKS +}; + +#if defined(PHP_NEED_REENTRANCY) + +#include + +static MUTEX_T reentrant_locks[NUMBER_OF_LOCKS]; + +#define local_lock(x) tsrm_mutex_lock(reentrant_locks[x]) +#define local_unlock(x) tsrm_mutex_unlock(reentrant_locks[x]) + +#else + +#define local_lock(x) +#define local_unlock(x) + +#endif + +#if defined(PHP_IRIX_TIME_R) + +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf) == buf) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf) == buf) + return (buf); + return (NULL); +} + +#endif + +#if defined(PHP_HPUX_TIME_R) + +#define HAVE_LOCALTIME_R 1 +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 +#define HAVE_GMTIME_R 1 + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (localtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (gmtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +#endif + +#if defined(__BEOS__) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + /* Modified according to LibC definition */ + if (((struct tm*)gmtime_r(timep, p_tm)) == p_tm) + return (p_tm); + return (NULL); +} + +#endif /* BEOS */ + +#if !defined(HAVE_POSIX_READDIR_R) + +PHPAPI int php_readdir_r(DIR *dirp, struct dirent *entry, + struct dirent **result) +{ +#if defined(HAVE_OLD_READDIR_R) + int ret = 0; + + /* We cannot rely on the return value of readdir_r + as it differs between various platforms + (HPUX returns 0 on success whereas Solaris returns non-zero) + */ + entry->d_name[0] = '\0'; + readdir_r(dirp, entry, result); + + if (entry->d_name[0] == '\0') { + *result = NULL; + ret = errno; + } else { + *result = entry; + } + return ret; +#else + struct dirent *ptr; + int ret = 0; + + local_lock(READDIR_R); + + errno = 0; + + ptr = readdir(dirp); + + if (!ptr && errno != 0) + ret = errno; + + if (ptr) + memcpy(entry, ptr, sizeof(*ptr)); + + *result = ptr; + + local_unlock(READDIR_R); + + return ret; +#endif +} + +#endif + +#if !defined(HAVE_LOCALTIME_R) && defined(HAVE_LOCALTIME) + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(LOCALTIME_R); + + tmp = localtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(LOCALTIME_R); + + return tmp; +} + +#endif + +#if !defined(HAVE_CTIME_R) && defined(HAVE_CTIME) + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + char *tmp; + + local_lock(CTIME_R); + + tmp = ctime(clock); + strcpy(buf, tmp); + + local_unlock(CTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_ASCTIME_R) && defined(HAVE_ASCTIME) + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + char *tmp; + + local_lock(ASCTIME_R); + + tmp = asctime(tm); + strcpy(buf, tmp); + + local_unlock(ASCTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_GMTIME_R) && defined(HAVE_GMTIME) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(GMTIME_R); + + tmp = gmtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(GMTIME_R); + + return tmp; +} + +#endif + +#if defined(PHP_NEED_REENTRANCY) + +void reentrancy_startup(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + reentrant_locks[i] = tsrm_mutex_alloc(); + } +} + +void reentrancy_shutdown(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + tsrm_mutex_free(reentrant_locks[i]); + } +} + +#endif + +#ifndef HAVE_RAND_R + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Posix rand_r function added May 1999 by Wes Peters . + */ + +#include +#include + +static int +do_rand(unsigned long *ctx) +{ + return ((*ctx = *ctx * 1103515245 + 12345) % ((u_long)PHP_RAND_MAX + 1)); +} + + +PHPAPI int +php_rand_r(unsigned int *ctx) +{ + u_long val = (u_long) *ctx; + *ctx = do_rand(&val); + return (int) *ctx; +} + +#endif + + +#ifndef HAVE_STRTOK_R + +/* + * Copyright (c) 1998 Softweyr LLC. All rights reserved. + * + * strtok_r, from Berkeley strtok + * Oct 13, 1998 by Wes Peters + * + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notices, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * + * This product includes software developed by Softweyr LLC, the + * University of California, Berkeley, and its contributors. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY SOFTWEYR LLC, THE REGENTS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTWEYR LLC, THE + * REGENTS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +PHPAPI char * +php_strtok_r(char *s, const char *delim, char **last) +{ + char *spanp; + int c, sc; + char *tok; + + if (s == NULL && (s = *last) == NULL) + { + return NULL; + } + + /* + * Skip (span) leading delimiters (s += strspn(s, delim), sort of). + */ +cont: + c = *s++; + for (spanp = (char *)delim; (sc = *spanp++) != 0; ) + { + if (c == sc) + { + goto cont; + } + } + + if (c == 0) /* no non-delimiter characters */ + { + *last = NULL; + return NULL; + } + tok = s - 1; + + /* + * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). + * Note that delim must have one NUL; we stop if we see that, too. + */ + for (;;) + { + c = *s++; + spanp = (char *)delim; + do + { + if ((sc = *spanp++) == c) + { + if (c == 0) + { + s = NULL; + } + else + { + char *w = s - 1; + *w = '\0'; + } + *last = s; + return tok; + } + } + while (sc != 0); + } + /* NOTREACHED */ +} + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/plugins/php/versions/70/src/zend_multiply.h b/plugins/php/versions/70/src/zend_multiply.h new file mode 100644 index 000000000..8723551be --- /dev/null +++ b/plugins/php/versions/70/src/zend_multiply.h @@ -0,0 +1,97 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2016 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Sascha Schumann | + | Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#if defined(__i386__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__ ("imul %3,%0\n" \ + "adc $0,%1" \ + : "=r"(__tmpvar),"=r"(usedval) \ + : "0"(a), "r"(b), "1"(0)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__x86_64__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__ ("imul %3,%0\n" \ + "adc $0,%1" \ + : "=r"(__tmpvar),"=r"(usedval) \ + : "0"(a), "r"(b), "1"(0)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__arm__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__("smull %0, %1, %2, %3\n" \ + "sub %1, %1, %0, asr #31" \ + : "=&r"(__tmpvar), "=&r"(usedval) \ + : "r"(a), "r"(b)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__aarch64__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__("mul %0, %2, %3\n" \ + "smulh %1, %2, %3\n" \ + "sub %1, %1, %0, asr #63\n" \ + : "=&r"(__tmpvar), "=&r"(usedval) \ + : "r"(a), "r"(b)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif SIZEOF_LONG == 4 && defined(HAVE_ZEND_LONG64) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + zend_long64 __result = (zend_long64) (a) * (zend_long64) (b); \ + if (__result > LONG_MAX || __result < LONG_MIN) { \ + (dval) = (double) __result; \ + (usedval) = 1; \ + } else { \ + (lval) = (long) __result; \ + (usedval) = 0; \ + } \ +} while (0) + +#else + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __lres = (a) * (b); \ + long double __dres = (long double)(a) * (long double)(b); \ + long double __delta = (long double) __lres - __dres; \ + if ( ((usedval) = (( __dres + __delta ) != __dres))) { \ + (dval) = __dres; \ + } else { \ + (lval) = __lres; \ + } \ +} while (0) + +#endif diff --git a/plugins/php/versions/71/install.sh b/plugins/php/versions/71/install.sh new file mode 100755 index 000000000..e57cb7f5b --- /dev/null +++ b/plugins/php/versions/71/install.sh @@ -0,0 +1,166 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +version=7.1.33 +PHP_VER=71 + +md5_file_ok=b27bb5ce72995bc0ea56276e156be889 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_old.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://museum.php.net/php7/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`md5sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +OPTIONS='' +OPTIONS='--without-iconv' +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-freetype-dir=${serverPath}/lib/freetype" + OPTIONS="${OPTIONS} --with-external-pcre=$(brew --prefix pcre2)" +else + OPTIONS="${OPTIONS} --with-readline" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + + +if [ "${SYS_ARCH}" == "arm64" ] && [ "$sysName" == "Darwin" ] ;then + # 修复mac arm64架构下php安装 + # 修复不能识别到sys_icache_invalidate + cat ${curPath}/versions/${PHP_VER}/src/ext/pcre/sljitConfigInternal.h > $sourcePath/php/php${PHP_VER}/ext/pcre/pcrelib/sljit/sljitConfigInternal.h + cat ${curPath}/versions/${PHP_VER}/src/reentrancy.c > $sourcePath/php/php${PHP_VER}/main/reentrancy.c + cat ${curPath}/versions/${PHP_VER}/src/mkstemp.c > $sourcePath/php/php${PHP_VER}/ext/zip/lib/mkstemp.c +fi + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl@1.0 | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl@1.0)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl10/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} && ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --enable-mbstring \ + --enable-simplexml \ + --enable-ftp \ + --enable-sockets \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php71 stop + rm -rf $serverPath/php/71 + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/71/mcrypt.sh b/plugins/php/versions/71/mcrypt.sh new file mode 100755 index 000000000..0ad94c70d --- /dev/null +++ b/plugins/php/versions/71/mcrypt.sh @@ -0,0 +1,102 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +LIBNAME=mcrypt +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash install.sh install ${version} + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config + make clean && make && make install && make clean + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/71/src/ext/pcre/sljitConfigInternal.h b/plugins/php/versions/71/src/ext/pcre/sljitConfigInternal.h new file mode 100644 index 000000000..cac7426be --- /dev/null +++ b/plugins/php/versions/71/src/ext/pcre/sljitConfigInternal.h @@ -0,0 +1,713 @@ +/* + * Stack-less Just-In-Time compiler + * + * Copyright 2009-2012 Zoltan Herczeg (hzmester@freemail.hu). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SLJIT_CONFIG_INTERNAL_H_ +#define _SLJIT_CONFIG_INTERNAL_H_ + +/* + SLJIT defines the following architecture dependent types and macros: + + Types: + sljit_sb, sljit_ub : signed and unsigned 8 bit byte + sljit_sh, sljit_uh : signed and unsigned 16 bit half-word (short) type + sljit_si, sljit_ui : signed and unsigned 32 bit integer type + sljit_sw, sljit_uw : signed and unsigned machine word, enough to store a pointer + sljit_p : unsgined pointer value (usually the same as sljit_uw, but + some 64 bit ABIs may use 32 bit pointers) + sljit_s : single precision floating point value + sljit_d : double precision floating point value + + Macros for feature detection (boolean): + SLJIT_32BIT_ARCHITECTURE : 32 bit architecture + SLJIT_64BIT_ARCHITECTURE : 64 bit architecture + SLJIT_LITTLE_ENDIAN : little endian architecture + SLJIT_BIG_ENDIAN : big endian architecture + SLJIT_UNALIGNED : allows unaligned memory accesses for non-fpu operations (only!) + SLJIT_INDIRECT_CALL : see SLJIT_FUNC_OFFSET() for more information + + Constants: + SLJIT_NUMBER_OF_REGISTERS : number of available registers + SLJIT_NUMBER_OF_SCRATCH_REGISTERS : number of available scratch registers + SLJIT_NUMBER_OF_SAVED_REGISTERS : number of available saved registers + SLJIT_NUMBER_OF_FLOAT_REGISTERS : number of available floating point registers + SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS : number of available floating point scratch registers + SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS : number of available floating point saved registers + SLJIT_WORD_SHIFT : the shift required to apply when accessing a sljit_sw/sljit_uw array by index + SLJIT_DOUBLE_SHIFT : the shift required to apply when accessing + a double precision floating point array by index + SLJIT_SINGLE_SHIFT : the shift required to apply when accessing + a single precision floating point array by index + SLJIT_LOCALS_OFFSET : local space starting offset (SLJIT_SP + SLJIT_LOCALS_OFFSET) + SLJIT_RETURN_ADDRESS_OFFSET : a return instruction always adds this offset to the return address + + Other macros: + SLJIT_CALL : C calling convention define for both calling JIT form C and C callbacks for JIT + SLJIT_W(number) : defining 64 bit constants on 64 bit architectures (compiler independent helper) +*/ + +/*****************/ +/* Sanity check. */ +/*****************/ + +#if !((defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + || (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + || (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + || (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + || (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + || (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED)) +#error "An architecture must be selected" +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + + (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + + (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) \ + + (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + + (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + + (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + + (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + + (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + + (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) \ + + (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + + (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + + (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) \ + + (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) \ + + (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) >= 2 +#error "Multiple architectures are selected" +#endif + +/********************************************************/ +/* Automatic CPU detection (requires compiler support). */ +/********************************************************/ + +#if (defined SLJIT_CONFIG_AUTO && SLJIT_CONFIG_AUTO) + +#ifndef _WIN32 + +#if defined(__i386__) || defined(__i386) +#define SLJIT_CONFIG_X86_32 1 +#elif defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(__arm__) || defined(__ARM__) +#ifdef __thumb2__ +#define SLJIT_CONFIG_ARM_THUMB2 1 +#elif defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) +#define SLJIT_CONFIG_ARM_V7 1 +#else +#define SLJIT_CONFIG_ARM_V5 1 +#endif +#elif defined (__aarch64__) +#define SLJIT_CONFIG_ARM_64 1 +#elif defined(__ppc64__) || defined(__powerpc64__) || defined(_ARCH_PPC64) || (defined(_POWER) && defined(__64BIT__)) +#define SLJIT_CONFIG_PPC_64 1 +#elif defined(__ppc__) || defined(__powerpc__) || defined(_ARCH_PPC) || defined(_ARCH_PWR) || defined(_ARCH_PWR2) || defined(_POWER) +#define SLJIT_CONFIG_PPC_32 1 +#elif defined(__mips__) && !defined(_LP64) +#define SLJIT_CONFIG_MIPS_32 1 +#elif defined(__mips64) +#define SLJIT_CONFIG_MIPS_64 1 +#elif defined(__sparc__) || defined(__sparc) +#define SLJIT_CONFIG_SPARC_32 1 +#elif defined(__tilegx__) +#define SLJIT_CONFIG_TILEGX 1 +#else +/* Unsupported architecture */ +#define SLJIT_CONFIG_UNSUPPORTED 1 +#endif + +#else /* !_WIN32 */ + +#if defined(_M_X64) || defined(__x86_64__) +#define SLJIT_CONFIG_X86_64 1 +#elif defined(_ARM_) +#define SLJIT_CONFIG_ARM_V5 1 +#else +#define SLJIT_CONFIG_X86_32 1 +#endif + +#endif /* !WIN32 */ +#endif /* SLJIT_CONFIG_AUTO */ + +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +#undef SLJIT_EXECUTABLE_ALLOCATOR +#endif + +/******************************/ +/* CPU family type detection. */ +/******************************/ + +#if (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) +#define SLJIT_CONFIG_ARM_32 1 +#endif + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) +#define SLJIT_CONFIG_X86 1 +#elif (defined SLJIT_CONFIG_ARM_32 && SLJIT_CONFIG_ARM_32) || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) +#define SLJIT_CONFIG_ARM 1 +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_CONFIG_PPC 1 +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) +#define SLJIT_CONFIG_MIPS 1 +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) || (defined SLJIT_CONFIG_SPARC_64 && SLJIT_CONFIG_SPARC_64) +#define SLJIT_CONFIG_SPARC 1 +#endif + +/**********************************/ +/* External function definitions. */ +/**********************************/ + +#if !(defined SLJIT_STD_MACROS_DEFINED && SLJIT_STD_MACROS_DEFINED) + +/* These libraries are needed for the macros below. */ +#include +#include + +#endif /* SLJIT_STD_MACROS_DEFINED */ + +/* General macros: + Note: SLJIT is designed to be independent from them as possible. + + In release mode (SLJIT_DEBUG is not defined) only the following + external functions are needed: +*/ + +#ifndef SLJIT_MALLOC +#define SLJIT_MALLOC(size, allocator_data) malloc(size) +#endif + +#ifndef SLJIT_FREE +#define SLJIT_FREE(ptr, allocator_data) free(ptr) +#endif + +#ifndef SLJIT_MEMMOVE +#define SLJIT_MEMMOVE(dest, src, len) memmove(dest, src, len) +#endif + +#ifndef SLJIT_ZEROMEM +#define SLJIT_ZEROMEM(dest, len) memset(dest, 0, len) +#endif + +/***************************/ +/* Compiler helper macros. */ +/***************************/ + +#if !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) + +#if defined(__GNUC__) && (__GNUC__ >= 3) +#define SLJIT_LIKELY(x) __builtin_expect((x), 1) +#define SLJIT_UNLIKELY(x) __builtin_expect((x), 0) +#else +#define SLJIT_LIKELY(x) (x) +#define SLJIT_UNLIKELY(x) (x) +#endif + +#endif /* !defined(SLJIT_LIKELY) && !defined(SLJIT_UNLIKELY) */ + +#ifndef SLJIT_INLINE +/* Inline functions. Some old compilers do not support them. */ +#if defined(__SUNPRO_C) && __SUNPRO_C <= 0x510 +#define SLJIT_INLINE +#else +#define SLJIT_INLINE __inline +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_NOINLINE +/* Not inline functions. */ +#if defined(__GNUC__) +#define SLJIT_NOINLINE __attribute__ ((noinline)) +#else +#define SLJIT_NOINLINE +#endif +#endif /* !SLJIT_INLINE */ + +#ifndef SLJIT_CONST +/* Const variables. */ +#define SLJIT_CONST const +#endif + +#ifndef SLJIT_UNUSED_ARG +/* Unused arguments. */ +#define SLJIT_UNUSED_ARG(arg) (void)arg +#endif + +/*********************************/ +/* Type of public API functions. */ +/*********************************/ + +#if (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) +/* Static ABI functions. For all-in-one programs. */ + +#if defined(__GNUC__) +/* Disable unused warnings in gcc. */ +#define SLJIT_API_FUNC_ATTRIBUTE static __attribute__((unused)) +#else +#define SLJIT_API_FUNC_ATTRIBUTE static +#endif + +#else +#define SLJIT_API_FUNC_ATTRIBUTE +#endif /* (defined SLJIT_CONFIG_STATIC && SLJIT_CONFIG_STATIC) */ + +/****************************/ +/* Instruction cache flush. */ +/****************************/ + +#ifndef SLJIT_CACHE_FLUSH + +#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) + +/* Not required to implement on archs with unified caches. */ +#define SLJIT_CACHE_FLUSH(from, to) + +#elif defined __APPLE__ + +/* Supported by all macs since Mac OS 10.5. + However, it does not work on non-jailbroken iOS devices, + although the compilation is successful. */ +#include +#define SLJIT_CACHE_FLUSH(from, to) \ + sys_icache_invalidate((char*)(from), (char*)(to) - (char*)(from)) + +#elif defined __ANDROID__ + +/* Android lacks __clear_cache; instead, cacheflush should be used. */ + +#define SLJIT_CACHE_FLUSH(from, to) \ + cacheflush((long)(from), (long)(to), 0) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +/* The __clear_cache() implementation of GCC is a dummy function on PowerPC. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + ppc_cache_flush((from), (to)) + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +/* The __clear_cache() implementation of GCC is a dummy function on Sparc. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + sparc_cache_flush((from), (to)) + +#else + +/* Calls __ARM_NR_cacheflush on ARM-Linux. */ +#define SLJIT_CACHE_FLUSH(from, to) \ + __clear_cache((char*)(from), (char*)(to)) + +#endif + +#endif /* !SLJIT_CACHE_FLUSH */ + +/******************************************************/ +/* Byte/half/int/word/single/double type definitions. */ +/******************************************************/ + +/* 8 bit byte type. */ +typedef unsigned char sljit_ub; +typedef signed char sljit_sb; + +/* 16 bit half-word type. */ +typedef unsigned short int sljit_uh; +typedef signed short int sljit_sh; + +/* 32 bit integer type. */ +typedef unsigned int sljit_ui; +typedef signed int sljit_si; + +/* Machine word type. Enough for storing a pointer. + 32 bit for 32 bit machines. + 64 bit for 64 bit machines. */ +#if (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) +/* Just to have something. */ +#define SLJIT_WORD_SHIFT 0 +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#elif !(defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + && !(defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + && !(defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) \ + && !(defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) \ + && !(defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) +#define SLJIT_32BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 2 +typedef unsigned int sljit_uw; +typedef int sljit_sw; +#else +#define SLJIT_64BIT_ARCHITECTURE 1 +#define SLJIT_WORD_SHIFT 3 +#ifdef _WIN32 +typedef unsigned __int64 sljit_uw; +typedef __int64 sljit_sw; +#else +typedef unsigned long int sljit_uw; +typedef long int sljit_sw; +#endif +#endif + +typedef sljit_uw sljit_p; + +/* Floating point types. */ +typedef float sljit_s; +typedef double sljit_d; + +/* Shift for pointer sized data. */ +#define SLJIT_POINTER_SHIFT SLJIT_WORD_SHIFT + +/* Shift for double precision sized data. */ +#define SLJIT_DOUBLE_SHIFT 3 +#define SLJIT_SINGLE_SHIFT 2 + +#ifndef SLJIT_W + +/* Defining long constants. */ +#if (defined SLJIT_64BIT_ARCHITECTURE && SLJIT_64BIT_ARCHITECTURE) +#define SLJIT_W(w) (w##ll) +#else +#define SLJIT_W(w) (w) +#endif + +#endif /* !SLJIT_W */ + +/*************************/ +/* Endianness detection. */ +/*************************/ + +#if !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) + +/* These macros are mostly useful for the applications. */ +#if (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) + +#ifdef __LITTLE_ENDIAN__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) \ + || (defined SLJIT_CONFIG_MIPS_64 && SLJIT_CONFIG_MIPS_64) + +#ifdef __MIPSEL__ +#define SLJIT_LITTLE_ENDIAN 1 +#else +#define SLJIT_BIG_ENDIAN 1 +#endif + +#elif (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) + +#define SLJIT_BIG_ENDIAN 1 + +#else +#define SLJIT_LITTLE_ENDIAN 1 +#endif + +#endif /* !defined(SLJIT_BIG_ENDIAN) && !defined(SLJIT_LITTLE_ENDIAN) */ + +/* Sanity check. */ +#if (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && (defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#if !(defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN) && !(defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +#error "Exactly one endianness must be selected" +#endif + +#ifndef SLJIT_UNALIGNED + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) \ + || (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) \ + || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) \ + || (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) \ + || (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) \ + || (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) \ + || (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) +#define SLJIT_UNALIGNED 1 +#endif + +#endif /* !SLJIT_UNALIGNED */ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) +/* Auto detect SSE2 support using CPUID. + On 64 bit x86 cpus, sse2 must be present. */ +#define SLJIT_DETECT_SSE2 1 +#endif + +/*****************************************************************************************/ +/* Calling convention of functions generated by SLJIT or called from the generated code. */ +/*****************************************************************************************/ + +#ifndef SLJIT_CALL + +#if (defined SLJIT_USE_CDECL_CALLING_CONVENTION && SLJIT_USE_CDECL_CALLING_CONVENTION) + +/* Force cdecl. */ +#define SLJIT_CALL + +#elif (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#if defined(__GNUC__) && !defined(__APPLE__) + +#define SLJIT_CALL __attribute__ ((fastcall)) +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(_MSC_VER) + +#define SLJIT_CALL __fastcall +#define SLJIT_X86_32_FASTCALL 1 + +#elif defined(__BORLANDC__) + +#define SLJIT_CALL __msfastcall +#define SLJIT_X86_32_FASTCALL 1 + +#else /* Unknown compiler. */ + +/* The cdecl attribute is the default. */ +#define SLJIT_CALL + +#endif + +#else /* Non x86-32 architectures. */ + +#define SLJIT_CALL + +#endif /* SLJIT_CONFIG_X86_32 */ + +#endif /* !SLJIT_CALL */ + +#ifndef SLJIT_INDIRECT_CALL +#if ((defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) && (defined SLJIT_BIG_ENDIAN && SLJIT_BIG_ENDIAN)) \ + || ((defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) && defined _AIX) +/* It seems certain ppc compilers use an indirect addressing for functions + which makes things complicated. */ +#define SLJIT_INDIRECT_CALL 1 +#endif +#endif /* SLJIT_INDIRECT_CALL */ + +/* The offset which needs to be substracted from the return address to +determine the next executed instruction after return. */ +#ifndef SLJIT_RETURN_ADDRESS_OFFSET +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +#define SLJIT_RETURN_ADDRESS_OFFSET 8 +#else +#define SLJIT_RETURN_ADDRESS_OFFSET 0 +#endif +#endif /* SLJIT_RETURN_ADDRESS_OFFSET */ + +/***************************************************/ +/* Functions of the built-in executable allocator. */ +/***************************************************/ + +#if (defined SLJIT_EXECUTABLE_ALLOCATOR && SLJIT_EXECUTABLE_ALLOCATOR) +SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr); +SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void); +#define SLJIT_MALLOC_EXEC(size) sljit_malloc_exec(size) +#define SLJIT_FREE_EXEC(ptr) sljit_free_exec(ptr) +#endif + +/**********************************************/ +/* Registers and locals offset determination. */ +/**********************************************/ + +#if (defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) + +#define SLJIT_NUMBER_OF_REGISTERS 10 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#if (defined SLJIT_X86_32_FASTCALL && SLJIT_X86_32_FASTCALL) +#define SLJIT_LOCALS_OFFSET_BASE ((2 + 4) * sizeof(sljit_sw)) +#else +/* Maximum 3 arguments are passed on the stack, +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1 + 4) * sizeof(sljit_sw)) +#endif /* SLJIT_X86_32_FASTCALL */ + +#elif (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) + +#ifndef _WIN64 +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 6 +#define SLJIT_LOCALS_OFFSET_BASE (sizeof(sljit_sw)) +#else +#define SLJIT_NUMBER_OF_REGISTERS 12 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE ((4 + 2) * sizeof(sljit_sw)) +#endif /* _WIN64 */ + +#elif (defined SLJIT_CONFIG_ARM_V5 && SLJIT_CONFIG_ARM_V5) || (defined SLJIT_CONFIG_ARM_V7 && SLJIT_CONFIG_ARM_V7) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_THUMB2 && SLJIT_CONFIG_ARM_THUMB2) + +#define SLJIT_NUMBER_OF_REGISTERS 11 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 7 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_ARM_64 && SLJIT_CONFIG_ARM_64) + +#define SLJIT_NUMBER_OF_REGISTERS 25 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 10 +#define SLJIT_LOCALS_OFFSET_BASE (2 * sizeof(sljit_sw)) + +#elif (defined SLJIT_CONFIG_PPC && SLJIT_CONFIG_PPC) + +#define SLJIT_NUMBER_OF_REGISTERS 22 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 17 +#if (defined SLJIT_CONFIG_PPC_64 && SLJIT_CONFIG_PPC_64) || (defined _AIX) +#define SLJIT_LOCALS_OFFSET_BASE ((6 + 8) * sizeof(sljit_sw)) +#elif (defined SLJIT_CONFIG_PPC_32 && SLJIT_CONFIG_PPC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((3 + 1) * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE (3 * sizeof(sljit_sw)) +#endif /* SLJIT_CONFIG_PPC_64 || _AIX */ + +#elif (defined SLJIT_CONFIG_MIPS && SLJIT_CONFIG_MIPS) + +#define SLJIT_NUMBER_OF_REGISTERS 17 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 8 +#if (defined SLJIT_CONFIG_MIPS_32 && SLJIT_CONFIG_MIPS_32) +#define SLJIT_LOCALS_OFFSET_BASE (4 * sizeof(sljit_sw)) +#else +#define SLJIT_LOCALS_OFFSET_BASE 0 +#endif + +#elif (defined SLJIT_CONFIG_SPARC && SLJIT_CONFIG_SPARC) + +#define SLJIT_NUMBER_OF_REGISTERS 18 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 14 +#if (defined SLJIT_CONFIG_SPARC_32 && SLJIT_CONFIG_SPARC_32) +/* Add +1 for double alignment. */ +#define SLJIT_LOCALS_OFFSET_BASE ((23 + 1) * sizeof(sljit_sw)) +#endif + +#elif (defined SLJIT_CONFIG_TILEGX && SLJIT_CONFIG_TILEGX) + +#define SLJIT_NUMBER_OF_REGISTERS 10 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 5 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#elif (defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED) + +#define SLJIT_NUMBER_OF_REGISTERS 0 +#define SLJIT_NUMBER_OF_SAVED_REGISTERS 0 +#define SLJIT_LOCALS_OFFSET_BASE 0 + +#endif + +#define SLJIT_LOCALS_OFFSET (SLJIT_LOCALS_OFFSET_BASE) + +#define SLJIT_NUMBER_OF_SCRATCH_REGISTERS \ + (SLJIT_NUMBER_OF_REGISTERS - SLJIT_NUMBER_OF_SAVED_REGISTERS) + +#define SLJIT_NUMBER_OF_FLOAT_REGISTERS 6 +#if (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) && (defined _WIN64) +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 1 +#else +#define SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS 0 +#endif + +#define SLJIT_NUMBER_OF_SCRATCH_FLOAT_REGISTERS \ + (SLJIT_NUMBER_OF_FLOAT_REGISTERS - SLJIT_NUMBER_OF_SAVED_FLOAT_REGISTERS) + +/*************************************/ +/* Debug and verbose related macros. */ +/*************************************/ + +#if (defined SLJIT_VERBOSE && SLJIT_VERBOSE) +#include +#endif + +#if (defined SLJIT_DEBUG && SLJIT_DEBUG) + +#if !defined(SLJIT_ASSERT) || !defined(SLJIT_ASSERT_STOP) + +/* SLJIT_HALT_PROCESS must halt the process. */ +#ifndef SLJIT_HALT_PROCESS +#include + +#define SLJIT_HALT_PROCESS() \ + abort(); +#endif /* !SLJIT_HALT_PROCESS */ + +#include + +#endif /* !SLJIT_ASSERT || !SLJIT_ASSERT_STOP */ + +/* Feel free to redefine these two macros. */ +#ifndef SLJIT_ASSERT + +#define SLJIT_ASSERT(x) \ + do { \ + if (SLJIT_UNLIKELY(!(x))) { \ + printf("Assertion failed at " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } \ + } while (0) + +#endif /* !SLJIT_ASSERT */ + +#ifndef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT_STOP() \ + do { \ + printf("Should never been reached " __FILE__ ":%d\n", __LINE__); \ + SLJIT_HALT_PROCESS(); \ + } while (0) + +#endif /* !SLJIT_ASSERT_STOP */ + +#else /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +/* Forcing empty, but valid statements. */ +#undef SLJIT_ASSERT +#undef SLJIT_ASSERT_STOP + +#define SLJIT_ASSERT(x) \ + do { } while (0) +#define SLJIT_ASSERT_STOP() \ + do { } while (0) + +#endif /* (defined SLJIT_DEBUG && SLJIT_DEBUG) */ + +#ifndef SLJIT_COMPILE_ASSERT + +/* Should be improved eventually. */ +#define SLJIT_COMPILE_ASSERT(x, description) \ + SLJIT_ASSERT(x) + +#endif /* !SLJIT_COMPILE_ASSERT */ + +#endif diff --git a/plugins/php/versions/71/src/mkstemp.c b/plugins/php/versions/71/src/mkstemp.c new file mode 100644 index 000000000..d1df8a5ca --- /dev/null +++ b/plugins/php/versions/71/src/mkstemp.c @@ -0,0 +1,153 @@ +/* Adapted from NetBSB libc by Dieter Baron */ + +/* NetBSD: gettemp.c,v 1.13 2003/12/05 00:57:36 uebayasi Exp */ + +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif +#include +#include +#ifndef _WIN32 +#include +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + + +int +_zip_mkstemp(char *path) +{ +#ifdef _WIN32 + int ret; + ret = _creat(_mktemp(path), _S_IREAD|_S_IWRITE); + if (ret == -1) { + return 0; + } else { + return ret; + } +#else + int fd; + char *start, *trv; + struct stat sbuf; + pid_t pid; + + /* To guarantee multiple calls generate unique names even if + the file is not created. 676 different possibilities with 7 + or more X's, 26 with 6 or less. */ + static char xtra[2] = "aa"; + int xcnt = 0; + + pid = getpid(); + + /* Move to end of path and count trailing X's. */ + for (trv = path; *trv; ++trv) + if (*trv == 'X') + xcnt++; + else + xcnt = 0; + + /* Use at least one from xtra. Use 2 if more than 6 X's. */ + if (*(trv - 1) == 'X') + *--trv = xtra[0]; + if (xcnt > 6 && *(trv - 1) == 'X') + *--trv = xtra[1]; + + /* Set remaining X's to pid digits with 0's to the left. */ + while (*--trv == 'X') { + *trv = (pid % 10) + '0'; + pid /= 10; + } + + /* update xtra for next call. */ + if (xtra[0] != 'z') + xtra[0]++; + else { + xtra[0] = 'a'; + if (xtra[1] != 'z') + xtra[1]++; + else + xtra[1] = 'a'; + } + + /* + * check the target directory; if you have six X's and it + * doesn't exist this runs for a *very* long time. + */ + for (start = trv + 1;; --trv) { + if (trv <= path) + break; + if (*trv == '/') { + *trv = '\0'; + if (stat(path, &sbuf)) + return (0); + if (!S_ISDIR(sbuf.st_mode)) { + errno = ENOTDIR; + return (0); + } + *trv = '/'; + break; + } + } + + for (;;) { + if ((fd=open(path, O_CREAT|O_EXCL|O_RDWR|O_BINARY, 0600)) >= 0) + return (fd); + if (errno != EEXIST) + return (0); + + /* tricky little algorithm for backward compatibility */ + for (trv = start;;) { + if (!*trv) + return (0); + if (*trv == 'z') + *trv++ = 'a'; + else { + if (isdigit((unsigned char)*trv)) + *trv = 'a'; + else + ++*trv; + break; + } + } + } + /*NOTREACHED*/ +#endif +} diff --git a/plugins/php/versions/71/src/reentrancy.c b/plugins/php/versions/71/src/reentrancy.c new file mode 100644 index 000000000..e7c5d7171 --- /dev/null +++ b/plugins/php/versions/71/src/reentrancy.c @@ -0,0 +1,450 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2014 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include +#include +#include +#ifdef HAVE_DIRENT_H +#include +#endif + +#include "php_reentrancy.h" +#include "ext/standard/php_rand.h" /* for PHP_RAND_MAX */ + +enum { + LOCALTIME_R, + CTIME_R, + ASCTIME_R, + GMTIME_R, + READDIR_R, + NUMBER_OF_LOCKS +}; + +#if defined(PHP_NEED_REENTRANCY) + +#include + +static MUTEX_T reentrant_locks[NUMBER_OF_LOCKS]; + +#define local_lock(x) tsrm_mutex_lock(reentrant_locks[x]) +#define local_unlock(x) tsrm_mutex_unlock(reentrant_locks[x]) + +#else + +#define local_lock(x) +#define local_unlock(x) + +#endif + +#if defined(PHP_IRIX_TIME_R) + +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf) == buf) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf) == buf) + return (buf); + return (NULL); +} + +#endif + +#if defined(PHP_HPUX_TIME_R) + +#define HAVE_LOCALTIME_R 1 +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 +#define HAVE_GMTIME_R 1 + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (localtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (gmtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +#endif + +#if defined(__BEOS__) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + /* Modified according to LibC definition */ + if (((struct tm*)gmtime_r(timep, p_tm)) == p_tm) + return (p_tm); + return (NULL); +} + +#endif /* BEOS */ + +#if !defined(HAVE_POSIX_READDIR_R) + +PHPAPI int php_readdir_r(DIR *dirp, struct dirent *entry, + struct dirent **result) +{ +#if defined(HAVE_OLD_READDIR_R) + int ret = 0; + + /* We cannot rely on the return value of readdir_r + as it differs between various platforms + (HPUX returns 0 on success whereas Solaris returns non-zero) + */ + entry->d_name[0] = '\0'; + readdir_r(dirp, entry, result); + + if (entry->d_name[0] == '\0') { + *result = NULL; + ret = errno; + } else { + *result = entry; + } + return ret; +#else + struct dirent *ptr; + int ret = 0; + + local_lock(READDIR_R); + + errno = 0; + + ptr = readdir(dirp); + + if (!ptr && errno != 0) + ret = errno; + + if (ptr) + memcpy(entry, ptr, sizeof(*ptr)); + + *result = ptr; + + local_unlock(READDIR_R); + + return ret; +#endif +} + +#endif + +#if !defined(HAVE_LOCALTIME_R) && defined(HAVE_LOCALTIME) + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(LOCALTIME_R); + + tmp = localtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(LOCALTIME_R); + + return tmp; +} + +#endif + +#if !defined(HAVE_CTIME_R) && defined(HAVE_CTIME) + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + char *tmp; + + local_lock(CTIME_R); + + tmp = ctime(clock); + strcpy(buf, tmp); + + local_unlock(CTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_ASCTIME_R) && defined(HAVE_ASCTIME) + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + char *tmp; + + local_lock(ASCTIME_R); + + tmp = asctime(tm); + strcpy(buf, tmp); + + local_unlock(ASCTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_GMTIME_R) && defined(HAVE_GMTIME) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(GMTIME_R); + + tmp = gmtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(GMTIME_R); + + return tmp; +} + +#endif + +#if defined(PHP_NEED_REENTRANCY) + +void reentrancy_startup(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + reentrant_locks[i] = tsrm_mutex_alloc(); + } +} + +void reentrancy_shutdown(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + tsrm_mutex_free(reentrant_locks[i]); + } +} + +#endif + +#ifndef HAVE_RAND_R + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Posix rand_r function added May 1999 by Wes Peters . + */ + +#include +#include + +static int +do_rand(unsigned long *ctx) +{ + return ((*ctx = *ctx * 1103515245 + 12345) % ((u_long)PHP_RAND_MAX + 1)); +} + + +PHPAPI int +php_rand_r(unsigned int *ctx) +{ + u_long val = (u_long) *ctx; + *ctx = do_rand(&val); + return (int) *ctx; +} + +#endif + + +#ifndef HAVE_STRTOK_R + +/* + * Copyright (c) 1998 Softweyr LLC. All rights reserved. + * + * strtok_r, from Berkeley strtok + * Oct 13, 1998 by Wes Peters + * + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notices, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * + * This product includes software developed by Softweyr LLC, the + * University of California, Berkeley, and its contributors. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY SOFTWEYR LLC, THE REGENTS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTWEYR LLC, THE + * REGENTS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +PHPAPI char * +php_strtok_r(char *s, const char *delim, char **last) +{ + char *spanp; + int c, sc; + char *tok; + + if (s == NULL && (s = *last) == NULL) + { + return NULL; + } + + /* + * Skip (span) leading delimiters (s += strspn(s, delim), sort of). + */ +cont: + c = *s++; + for (spanp = (char *)delim; (sc = *spanp++) != 0; ) + { + if (c == sc) + { + goto cont; + } + } + + if (c == 0) /* no non-delimiter characters */ + { + *last = NULL; + return NULL; + } + tok = s - 1; + + /* + * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). + * Note that delim must have one NUL; we stop if we see that, too. + */ + for (;;) + { + c = *s++; + spanp = (char *)delim; + do + { + if ((sc = *spanp++) == c) + { + if (c == 0) + { + s = NULL; + } + else + { + char *w = s - 1; + *w = '\0'; + } + *last = s; + return tok; + } + } + while (sc != 0); + } + /* NOTREACHED */ +} + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/plugins/php/versions/71/src/zend_multiply.h b/plugins/php/versions/71/src/zend_multiply.h new file mode 100644 index 000000000..8723551be --- /dev/null +++ b/plugins/php/versions/71/src/zend_multiply.h @@ -0,0 +1,97 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2016 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Sascha Schumann | + | Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#if defined(__i386__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__ ("imul %3,%0\n" \ + "adc $0,%1" \ + : "=r"(__tmpvar),"=r"(usedval) \ + : "0"(a), "r"(b), "1"(0)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__x86_64__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__ ("imul %3,%0\n" \ + "adc $0,%1" \ + : "=r"(__tmpvar),"=r"(usedval) \ + : "0"(a), "r"(b), "1"(0)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__arm__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__("smull %0, %1, %2, %3\n" \ + "sub %1, %1, %0, asr #31" \ + : "=&r"(__tmpvar), "=&r"(usedval) \ + : "r"(a), "r"(b)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif defined(__aarch64__) && defined(__GNUC__) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __tmpvar; \ + __asm__("mul %0, %2, %3\n" \ + "smulh %1, %2, %3\n" \ + "sub %1, %1, %0, asr #63\n" \ + : "=&r"(__tmpvar), "=&r"(usedval) \ + : "r"(a), "r"(b)); \ + if (usedval) (dval) = (double) (a) * (double) (b); \ + else (lval) = __tmpvar; \ +} while (0) + +#elif SIZEOF_LONG == 4 && defined(HAVE_ZEND_LONG64) + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + zend_long64 __result = (zend_long64) (a) * (zend_long64) (b); \ + if (__result > LONG_MAX || __result < LONG_MIN) { \ + (dval) = (double) __result; \ + (usedval) = 1; \ + } else { \ + (lval) = (long) __result; \ + (usedval) = 0; \ + } \ +} while (0) + +#else + +#define ZEND_SIGNED_MULTIPLY_LONG(a, b, lval, dval, usedval) do { \ + long __lres = (a) * (b); \ + long double __dres = (long double)(a) * (long double)(b); \ + long double __delta = (long double) __lres - __dres; \ + if ( ((usedval) = (( __dres + __delta ) != __dres))) { \ + (dval) = __dres; \ + } else { \ + (lval) = __lres; \ + } \ +} while (0) + +#endif diff --git a/plugins/php/versions/72/install.sh b/plugins/php/versions/72/install.sh new file mode 100755 index 000000000..5b32cf2a9 --- /dev/null +++ b/plugins/php/versions/72/install.sh @@ -0,0 +1,209 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +echo "use system: ${sysName}" +if [ ${sysName} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + + +version=7.2.31 +PHP_VER=72 +md5_file_ok=968adcb8d0c5652b6e191b025fc8ba41 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_old.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://museum.php.net/php7/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`md5sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +OPTIONS='--without-iconv' +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-curl=$(brew --prefix curl)" + OPTIONS="${OPTIONS} --with-pcre-dir=$(brew --prefix pcre2)" +else + OPTIONS="${OPTIONS} --with-readline" +fi +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + + +RELOAD_CODE(){ + cat ${curPath}/versions/${PHP_VER}/src/reentrancy.c > $sourcePath/php/php${PHP_VER}/main/reentrancy.c + echo "cat ${curPath}/versions/${PHP_VER}/src/reentrancy.c > $sourcePath/php/php${PHP_VER}/main/reentrancy.c" +} + +if [ "${SYS_ARCH}" == "arm64" ];then + # 修复arm64架构下安装 + RELOAD_CODE +fi + +if [ "${OSNAME}" == "debian" ] && [ "${VERSION_ID}" == "13" ];then + RELOAD_CODE +fi + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl@1.0 | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl@1.0)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl10/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} && ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --enable-mbstring \ + --enable-simplexml \ + --enable-sockets \ + --enable-ftp \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php72 stop + rm -rf $serverPath/php/72 + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/72/src/reentrancy.c b/plugins/php/versions/72/src/reentrancy.c new file mode 100644 index 000000000..69096cbb1 --- /dev/null +++ b/plugins/php/versions/72/src/reentrancy.c @@ -0,0 +1,450 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2018 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include +#include +#include +#ifdef HAVE_DIRENT_H +#include +#endif + +#include "php_reentrancy.h" +#include "ext/standard/php_rand.h" /* for PHP_RAND_MAX */ + +enum { + LOCALTIME_R, + CTIME_R, + ASCTIME_R, + GMTIME_R, + READDIR_R, + NUMBER_OF_LOCKS +}; + +#if defined(PHP_NEED_REENTRANCY) + +#include + +static MUTEX_T reentrant_locks[NUMBER_OF_LOCKS]; + +#define local_lock(x) tsrm_mutex_lock(reentrant_locks[x]) +#define local_unlock(x) tsrm_mutex_unlock(reentrant_locks[x]) + +#else + +#define local_lock(x) +#define local_unlock(x) + +#endif + +#if defined(PHP_IRIX_TIME_R) + +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf) == buf) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf) == buf) + return (buf); + return (NULL); +} + +#endif + +#if defined(PHP_HPUX_TIME_R) + +#define HAVE_LOCALTIME_R 1 +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 +#define HAVE_GMTIME_R 1 + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (localtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (gmtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +#endif + +#if defined(__BEOS__) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + /* Modified according to LibC definition */ + if (((struct tm*)gmtime_r(timep, p_tm)) == p_tm) + return (p_tm); + return (NULL); +} + +#endif /* BEOS */ + +#if !defined(HAVE_POSIX_READDIR_R) + +PHPAPI int php_readdir_r(DIR *dirp, struct dirent *entry, + struct dirent **result) +{ +#if defined(HAVE_OLD_READDIR_R) + int ret = 0; + + /* We cannot rely on the return value of readdir_r + as it differs between various platforms + (HPUX returns 0 on success whereas Solaris returns non-zero) + */ + entry->d_name[0] = '\0'; + readdir_r(dirp, entry, result); + + if (entry->d_name[0] == '\0') { + *result = NULL; + ret = errno; + } else { + *result = entry; + } + return ret; +#else + struct dirent *ptr; + int ret = 0; + + local_lock(READDIR_R); + + errno = 0; + + ptr = readdir(dirp); + + if (!ptr && errno != 0) + ret = errno; + + if (ptr) + memcpy(entry, ptr, sizeof(*ptr)); + + *result = ptr; + + local_unlock(READDIR_R); + + return ret; +#endif +} + +#endif + +#if !defined(HAVE_LOCALTIME_R) && defined(HAVE_LOCALTIME) + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(LOCALTIME_R); + + tmp = localtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(LOCALTIME_R); + + return tmp; +} + +#endif + +#if !defined(HAVE_CTIME_R) && defined(HAVE_CTIME) + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + char *tmp; + + local_lock(CTIME_R); + + tmp = ctime(clock); + strcpy(buf, tmp); + + local_unlock(CTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_ASCTIME_R) && defined(HAVE_ASCTIME) + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + char *tmp; + + local_lock(ASCTIME_R); + + tmp = asctime(tm); + strcpy(buf, tmp); + + local_unlock(ASCTIME_R); + + return buf; +} + +#endif + +#if !defined(HAVE_GMTIME_R) && defined(HAVE_GMTIME) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(GMTIME_R); + + tmp = gmtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(GMTIME_R); + + return tmp; +} + +#endif + +#if defined(PHP_NEED_REENTRANCY) + +void reentrancy_startup(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + reentrant_locks[i] = tsrm_mutex_alloc(); + } +} + +void reentrancy_shutdown(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + tsrm_mutex_free(reentrant_locks[i]); + } +} + +#endif + +#ifndef HAVE_RAND_R + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Posix rand_r function added May 1999 by Wes Peters . + */ + +#include +#include + +static int +do_rand(unsigned long *ctx) +{ + return ((*ctx = *ctx * 1103515245 + 12345) % ((u_long)PHP_RAND_MAX + 1)); +} + + +PHPAPI int +php_rand_r(unsigned int *ctx) +{ + u_long val = (u_long) *ctx; + *ctx = do_rand(&val); + return (int) *ctx; +} + +#endif + + +#ifndef HAVE_STRTOK_R + +/* + * Copyright (c) 1998 Softweyr LLC. All rights reserved. + * + * strtok_r, from Berkeley strtok + * Oct 13, 1998 by Wes Peters + * + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notices, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * + * This product includes software developed by Softweyr LLC, the + * University of California, Berkeley, and its contributors. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY SOFTWEYR LLC, THE REGENTS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTWEYR LLC, THE + * REGENTS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +PHPAPI char * +php_strtok_r(char *s, const char *delim, char **last) +{ + char *spanp; + int c, sc; + char *tok; + + if (s == NULL && (s = *last) == NULL) + { + return NULL; + } + + /* + * Skip (span) leading delimiters (s += strspn(s, delim), sort of). + */ +cont: + c = *s++; + for (spanp = (char *)delim; (sc = *spanp++) != 0; ) + { + if (c == sc) + { + goto cont; + } + } + + if (c == 0) /* no non-delimiter characters */ + { + *last = NULL; + return NULL; + } + tok = s - 1; + + /* + * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). + * Note that delim must have one NUL; we stop if we see that, too. + */ + for (;;) + { + c = *s++; + spanp = (char *)delim; + do + { + if ((sc = *spanp++) == c) + { + if (c == 0) + { + s = NULL; + } + else + { + char *w = s - 1; + *w = '\0'; + } + *last = s; + return tok; + } + } + while (sc != 0); + } + /* NOTREACHED */ +} + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/plugins/php/versions/73/install.sh b/plugins/php/versions/73/install.sh new file mode 100755 index 000000000..17e538f7a --- /dev/null +++ b/plugins/php/versions/73/install.sh @@ -0,0 +1,213 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +echo "use system: ${sysName}" +if [ ${sysName} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget unzip +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' + echo y | pacman -Sy unzip +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +else + OSNAME='unknow' +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +version=7.3.33 +PHP_VER=73 +md5_file_ok=eeabb2140c04da85c86389197421f890 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_old.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" != "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://museum.php.net/php7/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`md5sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +# ZIP_OPTION='--enable-zip' +# libzip_version=`pkg-config libzip --modversion` +# if [ "$?" != "0" ] || version_lt "$libzip_version" "0.11.0" ;then +# cd ${rootPath}/plugins/php/lib && /bin/bash libzip.sh +# export PKG_CONFIG_PATH=$serverPath/lib/libzip/lib/pkgconfig +# ZIP_OPTION="--with-libzip=$serverPath/lib/libzip" +# fi + +OPTIONS='--without-iconv' +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-curl=$(brew --prefix curl)" + OPTIONS="${OPTIONS} --with-pcre-dir=$(brew --prefix pcre2)" +else + OPTIONS="${OPTIONS} --with-readline" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + + + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + + +if [ "${OSNAME}" == "debian" ] && [ "${VERSION_ID}" == "13" ];then + # 修复arm64架构下安装 + cat ${curPath}/versions/${PHP_VER}/src/reentrancy.c > $sourcePath/php/php${PHP_VER}/main/reentrancy.c + echo "cat ${curPath}/versions/${PHP_VER}/src/reentrancy.c > $sourcePath/php/php${PHP_VER}/main/reentrancy.c" +fi + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl@1.0 | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl@1.0)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl10/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} + ./configure --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --enable-ftp \ + --enable-sockets \ + --enable-simplexml \ + --enable-mbstring \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + + # make clean && + make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi + +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php73 stop + rm -rf $serverPath/php/73 + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/73/src/reentrancy.c b/plugins/php/versions/73/src/reentrancy.c new file mode 100644 index 000000000..c72d81921 --- /dev/null +++ b/plugins/php/versions/73/src/reentrancy.c @@ -0,0 +1,442 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2018 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann | + +----------------------------------------------------------------------+ + */ + +#include +#include +#include +#ifdef HAVE_DIRENT_H +#include +#endif + +#include "php_reentrancy.h" +#include "ext/standard/php_rand.h" /* for PHP_RAND_MAX */ + +enum { + LOCALTIME_R, + CTIME_R, + ASCTIME_R, + GMTIME_R, + READDIR_R, + NUMBER_OF_LOCKS +}; + +#if defined(PHP_NEED_REENTRANCY) + +#include + +static MUTEX_T reentrant_locks[NUMBER_OF_LOCKS]; + +#define local_lock(x) tsrm_mutex_lock(reentrant_locks[x]) +#define local_unlock(x) tsrm_mutex_unlock(reentrant_locks[x]) + +#else + +#define local_lock(x) +#define local_unlock(x) + +#endif + +#if defined(PHP_IRIX_TIME_R) + +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf) == buf) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf) == buf) + return (buf); + return (NULL); +} + +#endif + +#if defined(PHP_HPUX_TIME_R) + +#define HAVE_LOCALTIME_R 1 +#define HAVE_CTIME_R 1 +#define HAVE_ASCTIME_R 1 +#define HAVE_GMTIME_R 1 + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (localtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + if (ctime_r(clock, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + if (asctime_r(tm, buf, 26) != -1) + return (buf); + return (NULL); +} + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + if (gmtime_r(timep, p_tm) == 0) + return (p_tm); + return (NULL); +} + +#endif + +#if !defined(HAVE_POSIX_READDIR_R) + +PHPAPI int php_readdir_r(DIR *dirp, struct dirent *entry, + struct dirent **result) +{ +#if defined(HAVE_OLD_READDIR_R) + int ret = 0; + + /* We cannot rely on the return value of readdir_r + as it differs between various platforms + (HPUX returns 0 on success whereas Solaris returns non-zero) + */ + entry->d_name[0] = '\0'; + readdir_r(dirp, entry, result); + + if (entry->d_name[0] == '\0') { + *result = NULL; + ret = errno; + } else { + *result = entry; + } + return ret; +#else + struct dirent *ptr; + int ret = 0; + + local_lock(READDIR_R); + + errno = 0; + + ptr = readdir(dirp); + + if (!ptr && errno != 0) + ret = errno; + + if (ptr) + memcpy(entry, ptr, sizeof(*ptr)); + + *result = ptr; + + local_unlock(READDIR_R); + + return ret; +#endif +} + +#endif + +#if !defined(HAVE_LOCALTIME_R) && defined(HAVE_LOCALTIME) + +PHPAPI struct tm *php_localtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(LOCALTIME_R); + + tmp = localtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(LOCALTIME_R); + + return tmp; +} + +#endif + +#if !defined(HAVE_CTIME_R) && defined(HAVE_CTIME) + +PHPAPI char *php_ctime_r(const time_t *clock, char *buf) +{ + char *tmp; + + local_lock(CTIME_R); + + tmp = ctime(clock); + if (tmp) { + strcpy(buf, tmp); + tmp = buf; + } + + local_unlock(CTIME_R); + + return tmp; +} + +#endif + +#if !defined(HAVE_ASCTIME_R) && defined(HAVE_ASCTIME) + +PHPAPI char *php_asctime_r(const struct tm *tm, char *buf) +{ + char *tmp; + + local_lock(ASCTIME_R); + + tmp = asctime(tm); + if (tmp) { + strcpy(buf, tmp); + tmp = buf; + } + + local_unlock(ASCTIME_R); + + return tmp; +} + +#endif + +#if !defined(HAVE_GMTIME_R) && defined(HAVE_GMTIME) + +PHPAPI struct tm *php_gmtime_r(const time_t *const timep, struct tm *p_tm) +{ + struct tm *tmp; + + local_lock(GMTIME_R); + + tmp = gmtime(timep); + if (tmp) { + memcpy(p_tm, tmp, sizeof(struct tm)); + tmp = p_tm; + } + + local_unlock(GMTIME_R); + + return tmp; +} + +#endif + +#if defined(PHP_NEED_REENTRANCY) + +void reentrancy_startup(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + reentrant_locks[i] = tsrm_mutex_alloc(); + } +} + +void reentrancy_shutdown(void) +{ + int i; + + for (i = 0; i < NUMBER_OF_LOCKS; i++) { + tsrm_mutex_free(reentrant_locks[i]); + } +} + +#endif + +#ifndef HAVE_RAND_R + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Posix rand_r function added May 1999 by Wes Peters . + */ + +#include +#include + +static int +do_rand(unsigned long *ctx) +{ + return ((*ctx = *ctx * 1103515245 + 12345) % ((u_long)PHP_RAND_MAX + 1)); +} + + +PHPAPI int +php_rand_r(unsigned int *ctx) +{ + u_long val = (u_long) *ctx; + *ctx = do_rand(&val); + return (int) *ctx; +} + +#endif + + +#ifndef HAVE_STRTOK_R + +/* + * Copyright (c) 1998 Softweyr LLC. All rights reserved. + * + * strtok_r, from Berkeley strtok + * Oct 13, 1998 by Wes Peters + * + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notices, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * + * This product includes software developed by Softweyr LLC, the + * University of California, Berkeley, and its contributors. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY SOFTWEYR LLC, THE REGENTS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTWEYR LLC, THE + * REGENTS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +PHPAPI char * +php_strtok_r(char *s, const char *delim, char **last) +{ + char *spanp; + int c, sc; + char *tok; + + if (s == NULL && (s = *last) == NULL) + { + return NULL; + } + + /* + * Skip (span) leading delimiters (s += strspn(s, delim), sort of). + */ +cont: + c = *s++; + for (spanp = (char *)delim; (sc = *spanp++) != 0; ) + { + if (c == sc) + { + goto cont; + } + } + + if (c == 0) /* no non-delimiter characters */ + { + *last = NULL; + return NULL; + } + tok = s - 1; + + /* + * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). + * Note that delim must have one NUL; we stop if we see that, too. + */ + for (;;) + { + c = *s++; + spanp = (char *)delim; + do + { + if ((sc = *spanp++) == c) + { + if (c == 0) + { + s = NULL; + } + else + { + char *w = s - 1; + *w = '\0'; + } + *last = s; + return tok; + } + } + while (sc != 0); + } + /* NOTREACHED */ +} + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/plugins/php/versions/74/install.sh b/plugins/php/versions/74/install.sh new file mode 100755 index 000000000..f11be5134 --- /dev/null +++ b/plugins/php/versions/74/install.sh @@ -0,0 +1,170 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=7.4.26 +PHP_VER=74 +md5_file_ok=0cbaae3de6c02cf8d7b82843fdfdf53d +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/php/lib && /bin/bash libzip.sh +# cd /www/server/mdserver-web/plugins/php/lib && /bin/bash libzip.sh +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_new.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh +cd ${rootPath}/plugins/php/lib && /bin/bash libzip.sh + +# redat ge 8 +which yum +if [ "$?" == "0" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash oniguruma.sh +fi + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://museum.php.net/php7/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`md5sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + rm -rf $sourcePath/php/php-${version}.tar.xz + echo "reinstall php${version}" + exit 1 +fi + +cd $sourcePath/php/php${PHP_VER} + +OPTIONS='--without-iconv' +# if [ $sysName == 'Darwin' ]; then +# OPTIONS="${OPTIONS} --with-external-pcre=$(brew --prefix pcre2)" +# fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl@1.0 | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl@1.0)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl10/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} && make clean + ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --enable-ftp \ + --enable-mbstring \ + --enable-sockets \ + --enable-simplexml \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} + + echo "安装php-${version}成功" +fi +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php${PHP_VER} stop + rm -rf $serverPath/php/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/80/install.sh b/plugins/php/versions/80/install.sh new file mode 100755 index 000000000..cf579e517 --- /dev/null +++ b/plugins/php/versions/80/install.sh @@ -0,0 +1,169 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.0.30 +PHP_VER=80 +md5_file_ok=216ab305737a5d392107112d618a755dc5df42058226f1670e9db90e77d777d9 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_new.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +# redat ge 8 +which yum +if [ "$?" == "0" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash oniguruma.sh +fi + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://www.php.net/distributions/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`sha256sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +cd $sourcePath/php/php${PHP_VER} + +OPTIONS='--without-iconv' + +if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-external-pcre=$(brew --prefix pcre2)" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + + +# argon_version=`pkg-config libargon2 --modversion` +# if [ "$?" == "0" ];then +# OPTIONS="${OPTIONS} --with-password-argon2" +# fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +# echo "$sourcePath/php/php${PHP_VER}" + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl@1.0 | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl@1.0)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_11.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl11/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} + ./buildconf --force + ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --enable-ftp \ + --enable-mbstring \ + --enable-sockets \ + --enable-simplexml \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php${PHP_VER} stop + rm -rf $serverPath/php/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/81/install.sh b/plugins/php/versions/81/install.sh new file mode 100755 index 000000000..6ef34558b --- /dev/null +++ b/plugins/php/versions/81/install.sh @@ -0,0 +1,161 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.1.33 +PHP_VER=81 +md5_file_ok=9db83bf4590375562bc1a10b353cccbcf9fcfc56c58b7c8fb814e6865bb928d1 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_new.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +# redat ge 8 +which yum +if [ "$?" == "0" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash oniguruma.sh +fi + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://www.php.net/distributions/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`sha256sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +cd $sourcePath/php/php${PHP_VER} + +OPTIONS='--without-iconv' +# if [ $sysName == 'Darwin' ]; then +# OPTIONS="${OPTIONS} --with-curl" +# fi + +argon_version=`pkg-config libargon2 --modversion` +if [ "$?" == "0" ];then + OPTIONS="${OPTIONS} --with-password-argon2" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +echo "$sourcePath/php/php${PHP_VER}" + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl | grep ${BREW_DIR}/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_11.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl11/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} + ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --enable-ftp \ + --enable-mbstring \ + --enable-sockets \ + --enable-simplexml \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php${PHP_VER} stop + rm -rf $serverPath/php/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/82/install.sh b/plugins/php/versions/82/install.sh new file mode 100755 index 000000000..6514f7139 --- /dev/null +++ b/plugins/php/versions/82/install.sh @@ -0,0 +1,162 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.2.30 +PHP_VER=82 +md5_file_ok=bc90523e17af4db46157e75d0c9ef0b9d0030b0514e62c26ba7b513b8c4eb015 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_new.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +# redat ge 8 +which yum +if [ "$?" == "0" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash oniguruma.sh +fi + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://www.php.net/distributions/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`sha256sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +cd $sourcePath/php/php${PHP_VER} + +OPTIONS='--without-iconv' + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +argon_version=`pkg-config libargon2 --modversion` +if [ "$?" == "0" ];then + OPTIONS="${OPTIONS} --with-password-argon2" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl | grep ${BREW_DIR}/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_11.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl11/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +# echo "$sourcePath/php/php${PHP_VER}" +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} + ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --enable-mbstring \ + --enable-ftp \ + --enable-sockets \ + --enable-simplexml \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php${PHP_VER} stop + rm -rf $serverPath/php/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/83/install.sh b/plugins/php/versions/83/install.sh new file mode 100755 index 000000000..1c29a7ca6 --- /dev/null +++ b/plugins/php/versions/83/install.sh @@ -0,0 +1,163 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.3.29 +PHP_VER=83 +md5_file_ok=f7950ca034b15a78f5de9f1b22f4d9bad1dd497114d175cb1672a4ca78077af5 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_new.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +# redat ge 8 +which yum +if [ "$?" == "0" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash oniguruma.sh +fi + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ];then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://www.php.net/distributions/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`sha256sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +cd $sourcePath/php/php${PHP_VER} + +OPTIONS='--without-iconv' + +argon_version=`pkg-config libargon2 --modversion` +if [ "$?" == "0" ];then + OPTIONS="${OPTIONS} --with-password-argon2" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl | grep ${BREW_DIR}/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_11.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl11/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +# echo "$sourcePath/php/php${PHP_VER}" +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} + ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --enable-mbstring \ + --enable-ftp \ + --enable-sockets \ + --enable-simplexml \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php${PHP_VER} stop + rm -rf $serverPath/php/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/84/install.sh b/plugins/php/versions/84/install.sh new file mode 100755 index 000000000..5173ee304 --- /dev/null +++ b/plugins/php/versions/84/install.sh @@ -0,0 +1,173 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.4.16 +PHP_VER=84 +md5_file_ok=f66f8f48db34e9e29f7bfd6901178e9cf4a1b163e6e497716dfcb8f88bcfae30 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_new.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +# redat ge 8 +which yum +if [ "$?" == "0" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash oniguruma.sh +fi + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ];then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://www.php.net/distributions/php-${version}.tar.xz + fi + + #检测文件是否损坏. + if [ -f $sourcePath/php/php-${version}.tar.xz ];then + md5_file=`sha256sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + if [ "${md5_file}" != "${md5_file_ok}" ]; then + echo "PHP${version} 下载文件不完整,重新安装" + rm -rf $sourcePath/php/php-${version}.tar.xz + fi + fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +cd $sourcePath/php/php${PHP_VER} + +OPTIONS='--without-iconv' + +# if [ $sysName == 'Darwin' ]; then +# OPTIONS="${OPTIONS} --with-curl" +# fi + +argon_version=`pkg-config libargon2 --modversion` +if [ "$?" == "0" ];then + OPTIONS="${OPTIONS} --with-password-argon2" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +# OPTIONS="${OPTIONS} --enable-debug" +# OPTIONS="${OPTIONS} --enable-dtrace" + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl | grep ${BREW_DIR}/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + # cd ${rootPath}/plugins/php/lib && /bin/bash openssl_35.sh + # export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl35/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +echo "$sourcePath/php/php${PHP_VER}" + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} + # ./buildconf --force + ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --with-mysqlnd-ssl \ + --enable-mbstring \ + --enable-ftp \ + --enable-sockets \ + --enable-simplexml \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php${PHP_VER} stop + rm -rf $serverPath/php/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/85/install.sh b/plugins/php/versions/85/install.sh new file mode 100755 index 000000000..27e3f28f3 --- /dev/null +++ b/plugins/php/versions/85/install.sh @@ -0,0 +1,175 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +version=8.5.2 +PHP_VER=85 +md5_file_ok=cb75a9b00a2806f7390dd64858ef42a47b443b3475769c8af6af33a18b1381f1 +Install_php() +{ +#------------------------ install start ------------------------------------# +echo "安装php-${version} ..." +mkdir -p $sourcePath/php +mkdir -p $serverPath/php + +cd ${rootPath}/plugins/php/lib && /bin/bash freetype_new.sh +cd ${rootPath}/plugins/php/lib && /bin/bash zlib.sh + +# redat ge 8 +which yum +if [ "$?" == "0" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash oniguruma.sh +fi + +if [ ! -d $sourcePath/php/php${PHP_VER} ];then + + # ----------------------------------------------------------------------- # + # 中国优化安装 + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + LOCAL_ADDR=common + if [ ! -z "$cn" ];then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "cn" ];then + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://mirrors.nju.edu.cn/php/php-${version}.tar.xz + fi + fi + # ----------------------------------------------------------------------- # + + + if [ ! -f $sourcePath/php/php-${version}.tar.xz ];then + wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://www.php.net/distributions/php-${version}.tar.xz + # wget --no-check-certificate -O $sourcePath/php/php-${version}.tar.xz https://downloads.php.net/~edorian/php-${version}.tar.xz + fi + + #检测文件是否损坏. + # if [ -f $sourcePath/php/php-${version}.tar.xz ];then + # md5_file=`sha256sum $sourcePath/php/php-${version}.tar.xz | awk '{print $1}'` + # if [ "${md5_file}" != "${md5_file_ok}" ]; then + # echo "PHP${version} 下载文件不完整,重新安装" + # rm -rf $sourcePath/php/php-${version}.tar.xz + # fi + # fi + + cd $sourcePath/php && tar -Jxf $sourcePath/php/php-${version}.tar.xz + mv $sourcePath/php/php-${version} $sourcePath/php/php${PHP_VER} +fi + +cd $sourcePath/php/php${PHP_VER} + +OPTIONS='--without-iconv' + +# if [ $sysName == 'Darwin' ]; then +# OPTIONS="${OPTIONS} --with-curl" +# fi + +argon_version=`pkg-config libargon2 --modversion` +if [ "$?" == "0" ];then + OPTIONS="${OPTIONS} --with-password-argon2" +fi + +IS_64BIT=`getconf LONG_BIT` +if [ "$IS_64BIT" == "64" ];then + OPTIONS="${OPTIONS} --with-libdir=lib64" +fi + +# ----- cpu start ------ +if [ -z "${cpuCore}" ]; then + cpuCore="1" +fi + +if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` +fi + +MEM_INFO=$(which free > /dev/null && free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') +if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi +else + cpuCore="1" +fi + +if [ "$cpuCore" -gt "2" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` +else + cpuCore="1" +fi +# ----- cpu end ------ + +# OPTIONS="${OPTIONS} --enable-debug" +# OPTIONS="${OPTIONS} --enable-dtrace" + +if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl | grep ${BREW_DIR}/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" +else + echo "lib" + # cd ${rootPath}/plugins/php/lib && /bin/bash openssl_35.sh + # export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl35/lib/pkgconfig + OPTIONS="$OPTIONS --with-openssl" +fi + +echo "$sourcePath/php/php${PHP_VER}" + +if [ ! -d $serverPath/php/${PHP_VER} ];then + cd $sourcePath/php/php${PHP_VER} + # ./buildconf --force + ./configure \ + --prefix=$serverPath/php/${PHP_VER} \ + --exec-prefix=$serverPath/php/${PHP_VER} \ + --with-config-file-path=$serverPath/php/${PHP_VER}/etc \ + --enable-mysqlnd \ + --with-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pdo-mysql=mysqlnd \ + --with-mysqlnd-ssl \ + --enable-mbstring \ + --enable-ftp \ + --enable-sockets \ + --enable-simplexml \ + --enable-soap \ + --enable-posix \ + --enable-sysvmsg \ + --enable-sysvsem \ + --enable-sysvshm \ + --disable-intl \ + --disable-fileinfo \ + $OPTIONS \ + --enable-fpm + make clean && make -j${cpuCore} && make install && make clean + + # rm -rf $sourcePath/php/php${PHP_VER} +fi +#------------------------ install end ------------------------------------# +} + +Uninstall_php() +{ + $serverPath/php/init.d/php${PHP_VER} stop + rm -rf $serverPath/php/${PHP_VER} + echo "卸载php-${version}..." +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_php +else + Uninstall_php +fi diff --git a/plugins/php/versions/all_mac.sh b/plugins/php/versions/all_mac.sh new file mode 100644 index 000000000..cc6997335 --- /dev/null +++ b/plugins/php/versions/all_mac.sh @@ -0,0 +1,70 @@ +#! /bin/sh +export PATH=$PATH:/opt/local/bin:/opt/local/sbin:/opt/local/share/man:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/opt/homebrew/bin +DIR=$(cd "$(dirname "$0")"; pwd) +ROOT_DIR=$(cd "$(dirname "$0")"; pwd) + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/php && bash install.sh install 72 +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/php/versions/common && bash mcrypt.sh install 82 +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/php/lib && bash libmcrypt.sh + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/scripts/quick && bash debug.sh +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/php/versions && /bin/bash all_mac.sh + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/php && bash install.sh install 55 +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/php/versions/common && bash gd.sh install 73 +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/php/versions/common && bash swoole.sh install 54 + + +# PHP_VER=52 +# echo "php${PHP_VER} -- start" +# cmd_ext=$(ls -l $DIR/versions/$PHP_VER/ |awk '{print $9}') +# cd $DIR && /bin/bash install.sh install $PHP_VER +# for ii in $cmd_ext +# do +# if [ "install.sh" == "$ii" ];then +# echo '' > /tmp/t.log +# else +# cd $DIR/versions/$PHP_VER && /bin/bash $ii install $PHP_VER +# fi +# done +# echo "php${PHP_VER} -- end" + + +PHP_VER_LIST=(54 55 56 70 71 72 73 74 80 81 82 83 84 85) +# PHP_VER_LIST=(81) +for PHP_VER in ${PHP_VER_LIST[@]}; do + echo "php${PHP_VER} -- start" + if [ ! -d /Users/midoks/Desktop/mwdev/server/php/${PHP_VER} ];then + cd /Users/midoks/Desktop/mwdev//server/mdserver-web/plugins/php && bash install.sh install ${PHP_VER} + fi + echo "php${PHP_VER} -- end" +done + +cd $DIR +PHP_VER_LIST=(54 55 56 70 71 72 73 74 80 81 82 83 84 85) +# yar +PHP_EXT_LIST=(ZendGuardLoader pdo mysqlnd sqlite3 openssl opcache mcrypt fileinfo \ + exif gd intl pcntl memcache memcached redis imagemagick xdebug \ + swoole yac apc mongo mongodb solr seaslog mbstring iconv) + +for PHP_VER in ${PHP_VER_LIST[@]}; do + echo "php${PHP_VER} -- start" + + if [ ! -d /Users/midoks/Desktop/mwdev/server/php/${PHP_VER} ];then + echo "php${PHP_VER} is not install!" + continue + fi + + NON_ZTS_FILENAME=`ls /Users/midoks/Desktop/mwdev/server/php/${PHP_VER}/lib/php/extensions | grep no-debug-non-zts` + for EXT in ${PHP_EXT_LIST[@]}; do + extFile=/www/server/php/${PHP_VER}/lib/php/extensions/${NON_ZTS_FILENAME}/${EXT}.so + echo "${PHP_VER} ${EXT} start" + if [ ! -f $extFile ];then + bash common.sh $PHP_VER install ${EXT} + fi + echo "${PHP_VER} ${EXT} end" + done + + echo "php${PHP_VER} -- end" +done + diff --git a/plugins/php/versions/all_test.sh b/plugins/php/versions/all_test.sh new file mode 100644 index 000000000..d9ebd21b9 --- /dev/null +++ b/plugins/php/versions/all_test.sh @@ -0,0 +1,68 @@ +#! /bin/sh +export PATH=$PATH:/opt/local/bin:/opt/local/sbin:/opt/local/share/man:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin +export PATH=$PATH:/opt/homebrew/bin + +DIR=$(cd "$(dirname "$0")"; pwd) +ROOT_DIR=$(cd "$(dirname "$0")"; pwd) + +# cd /www/server/mdserver-web/scripts/quick && bash debug.sh +# cd /www/server/mdserver-web/plugins/php/versions && /bin/bash all_test.sh + +# cd /www/server/mdserver-web/plugins/php && bash install.sh install 82 +# cd /www/server/mdserver-web/plugins/php/versions/common && bash gd.sh install 73 +# cd /www/server/mdserver-web/plugins/php/versions/common && bash swoole.sh install 54 + + +# PHP_VER=52 +# echo "php${PHP_VER} -- start" +# cmd_ext=$(ls -l $DIR/versions/$PHP_VER/ |awk '{print $9}') +# cd $DIR && /bin/bash install.sh install $PHP_VER +# for ii in $cmd_ext +# do +# if [ "install.sh" == "$ii" ];then +# echo '' > /tmp/t.log +# else +# cd $DIR/versions/$PHP_VER && /bin/bash $ii install $PHP_VER +# fi +# done +# echo "php${PHP_VER} -- end" + + +PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82 83 84 85) +# PHP_VER_LIST=(81) +for PHP_VER in ${PHP_VER_LIST[@]}; do + echo "php${PHP_VER} -- start" + if [ ! -d /www/server/php/${PHP_VER} ];then + cd /www/server/mdserver-web/plugins/php && bash install.sh install ${PHP_VER} + fi + echo "php${PHP_VER} -- end" +done + +cd $DIR +PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82 83 84 85) +# yar +PHP_EXT_LIST=(ZendGuardLoader pdo mysqlnd sqlite3 openssl opcache mcrypt fileinfo \ + exif gd intl pcntl memcache memcached redis xdebug \ + swoole yac apc mongo mongodb seaslog mbstring iconv event) + +for PHP_VER in ${PHP_VER_LIST[@]}; do + echo "php${PHP_VER} -- start" + + if [ ! -d /www/server/php/${PHP_VER} ];then + echo "php${PHP_VER} is not install!" + continue + fi + + NON_ZTS_FILENAME=`ls /www/server/php/${PHP_VER}/lib/php/extensions | grep no-debug-non-zts` + for EXT in ${PHP_EXT_LIST[@]}; do + extFile=/www/server/php/${PHP_VER}/lib/php/extensions/${NON_ZTS_FILENAME}/${EXT}.so + echo "${PHP_VER} ${EXT} start" + if [ ! -f $extFile ];then + bash common.sh $PHP_VER install ${EXT} + fi + echo "${PHP_VER} ${EXT} end" + done + + echo "php${PHP_VER} -- end" +done + diff --git a/plugins/php/versions/common.sh b/plugins/php/versions/common.sh new file mode 100644 index 000000000..e6d15c554 --- /dev/null +++ b/plugins/php/versions/common.sh @@ -0,0 +1,50 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +version=$1 +action=$2 +extName=$3 + +# echo $1,$2,$3 + +# echo $curPath +# echo $rootPath +# echo $serverPath + +FILE=${curPath}/${version}/${extName}.sh +FILE_COMMON=${curPath}/common/${extName}.sh + + +if [ "$action" == 'install' ];then + + if [ -f $FILE ];then + cd ${curPath}/${version} && bash ${extName}.sh install ${version} + elif [ -f $FILE_COMMON ];then + cd ${curPath}/common && bash ${extName}.sh install ${version} + else + echo 'no such extension' + fi +fi + + +if [ "$action" == 'uninstall' ];then + if [ -f $FILE ];then + cd ${curPath}/${version} && bash ${extName}.sh uninstall ${version} + elif [ -f $FILE_COMMON ];then + cd ${curPath}/common && bash ${extName}.sh uninstall ${version} + else + echo 'no such extension' + fi +fi + +echo "cd ${curPath}/common && bash ${extName}.sh install ${version}" +echo "cd ${curPath}/${version} && bash ${extName}.sh install ${version}" +echo "cd ${curPath}/common && bash ${extName}.sh uninstall ${version}" +echo "cd ${curPath}/${version} && bash ${extName}.sh uninstall ${version}" diff --git a/plugins/php/versions/common/apcu.sh b/plugins/php/versions/common/apcu.sh new file mode 100755 index 000000000..a15080730 --- /dev/null +++ b/plugins/php/versions/common/apcu.sh @@ -0,0 +1,100 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=apcu +# _LIBNAME=$(echo $LIBNAME | tr '[a-z]' '[A-Z]') +LIBV=5.1.22 +sysName=`uname` +actionType=$1 +version=$2 + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/lib/php/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/lib/php/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config + make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + echo $extFile + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/bcmath.sh b/plugins/php/versions/common/bcmath.sh new file mode 100755 index 000000000..341ad1d4c --- /dev/null +++ b/plugins/php/versions/common/bcmath.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +appPath=$(dirname "$curPath") + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=bcmath +LIBV=0 + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +# export PKG_CONFIG_PATH=/www/server/lib/libzip/lib/pkgconfig + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + + if [ "$version" == "83" ];then + CFLAGS="-std=c99" ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + else + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + fi + + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/brotli.sh b/plugins/php/versions/common/brotli.sh new file mode 100755 index 000000000..87141bf8d --- /dev/null +++ b/plugins/php/versions/common/brotli.sh @@ -0,0 +1,114 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=brotli +LIBV=0.15.2 +sysName=`uname` +actionType=$1 +version=$2 + +if [ "$version" -lt "70" ];then + echo "not need" + exit 1 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --enable-brotli \ + --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/curl.sh b/plugins/php/versions/common/curl.sh new file mode 100755 index 000000000..609974209 --- /dev/null +++ b/plugins/php/versions/common/curl.sh @@ -0,0 +1,123 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` + +actionType=$1 +version=$2 + + +LIBNAME=curl +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + if [ "$sysName" == "Darwin" ];then + + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info curl | grep ${BREW_DIR}/Cellar/curl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-curl=$(brew --prefix curl)" + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$LIB_DEPEND_DIR/lib/pkgconfig + fi + + + $serverPath/php/$version/bin/phpize + echo "./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS" + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/event.sh b/plugins/php/versions/common/event.sh new file mode 100644 index 000000000..711b7746f --- /dev/null +++ b/plugins/php/versions/common/event.sh @@ -0,0 +1,122 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=event +LIBV=3.1.4 + + +if [ "$version" -lt "55" ];then + echo 'not need' + exit 0 +fi + +if [ "$version" -lt "83" ];then + LIBV=3.1.4 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[${LIBNAME}\]/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/exif.sh b/plugins/php/versions/common/exif.sh new file mode 100755 index 000000000..eb12a21aa --- /dev/null +++ b/plugins/php/versions/common/exif.sh @@ -0,0 +1,123 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +appPath=$(dirname "$curPath") + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=exif +LIBV=0 + +if [ "$version" == "53" ];then + echo "i wont support it" + exit +fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + + FIND_C99=`cat Makefile|grep c99` + if [ "$FIND_C99" == "" ];then + sed -i $BAK 's/CFLAGS \=/CFLAGS \= -std=c99/g' Makefile + fi + + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/fileinfo.sh b/plugins/php/versions/common/fileinfo.sh new file mode 100755 index 000000000..e0a481d17 --- /dev/null +++ b/plugins/php/versions/common/fileinfo.sh @@ -0,0 +1,142 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=fileinfo +LIBV=0 + +if [ "$version" == "53" ];then + echo "i wont support it" + exit +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +OSNAME=`cat ${rootPath}/data/osname.pl` + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + + + # It is considered as a temporary bug + if [ "$version" == "81" ] || [ "$version" == "82" ];then + bash ${rootPath}/scripts/getos.sh + if [ "$OSNAME" == 'centos' ];then + FILE_softmagic=$sourcePath/php${version}/ext/${LIBNAME}/libmagic/softmagic.c + FIND_UNDEF_STRNDUP=`cat $FILE_softmagic|grep '#undef strndup'` + if [ "$version" -gt "74" ] && [ "$FIND_UNDEF_STRNDUP" == "" ];then + sed -i $BAK "s/char \*strndup/#undef strndup\nchar \*strndup/g" $FILE_softmagic + fi + fi + fi + + FIND_C99=`cat Makefile|grep c99` + if [ "$version" -gt "74" ] && [ "$FIND_C99" == "" ];then + sed -i $BAK 's/CFLAGS \=/CFLAGS \= -std=gnu99/g' Makefile + fi + + if [ "$version" -gt "80" ] && [ "$OSNAME" == 'centos' ];then + sed -i $BAK "s#CFLAGS = -g -O2#CFLAGS = -std=c99 -g#g" $sourcePath/php${version}/ext/${LIBNAME}/Makefile + fi + + + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/gd.sh b/plugins/php/versions/common/gd.sh new file mode 100755 index 000000000..06b275eac --- /dev/null +++ b/plugins/php/versions/common/gd.sh @@ -0,0 +1,135 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +LIBNAME=gd +LIBV=0 + + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +OSNAME_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +if [ "centos" == "$OSNAME" ]; then + if [ "$OSNAME_ID" != "9" ];then + if [ "$version" -lt "74" ];then + bash $curPath/gd_old.sh $1 $2 + exit 0 + fi + fi +else + if [ "$version" -lt "74" ];then + bash $curPath/gd_old.sh $1 $2 + exit 0 + fi +fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + --enable-gd \ + --with-webp \ + --with-xpm \ + --with-jpeg \ + --with-png-dir \ + --with-freetype + #--enable-gd-jis-conv + + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/gd_old.sh b/plugins/php/versions/common/gd_old.sh new file mode 100755 index 000000000..de8a647a5 --- /dev/null +++ b/plugins/php/versions/common/gd_old.sh @@ -0,0 +1,150 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=gd +LIBV=0 + + + +# if [ "$version" -lt "74" ];then +# echo "not need!" +# exit 1 +# fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + + +# cd ${rootPath}/plugins/php/lib && /bin/bash freetype_old.sh +# OPTIONS="${OPTIONS} --with-freetype-dir=${serverPath}/lib/freetype_old" +# OPTIONS="${OPTIONS} --with-gd --enable-gd-native-ttf" +# OPTIONS="${OPTIONS} --with-jpeg --with-jpeg-dir=/usr/lib" + + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + cd ${rootPath}/plugins/php/lib && /bin/bash freetype_old.sh + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + $serverPath/php/$version/bin/phpize + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + if [ "$version" -lt "55" ];then + echo "not need xmp" + else + OPTIONS="$OPTIONS --with-xpm-dir" + fi + + find_ft2=`pkg-config --list-all | grep freetype2` + if [ "$find_ft2" == "" ];then + OPTIONS="$OPTIONS --with-freetype-dir=${serverPath}/lib/freetype_old" + fi + + + #--with-xpm + # =${serverPath}/lib/freetype_old + # =/usr/lib + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + $OPTIONS \ + --with-gd \ + --with-jpeg-dir \ + --with-png-dir \ + --with-webp-dir \ + --with-zlib-dir \ + --enable-gd-jis-conv \ + # --enable-gd-native-ttf + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/gettext.sh b/plugins/php/versions/common/gettext.sh new file mode 100755 index 000000000..857bcaa64 --- /dev/null +++ b/plugins/php/versions/common/gettext.sh @@ -0,0 +1,122 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=gettext +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + if [ "$sysName" == "Darwin" ];then + OPTIONS="$OPTIONS --with-gettext=$(brew --prefix gettext)" + fi + + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + + # PHP52需要,因为52关闭。所有注释掉 + # FIND_C99=`cat Makefile|grep c99` + # if [ "$FIND_C99" == "" ];then + # sed -i $BAK 's/CFLAGS \=/CFLAGS \= -std=c99/g' Makefile + # fi + + make clean && make && make install && make clean + + # if [ -d $sourcePath/php${version} ];then + # cd ${sourcePath} && rm -rf $sourcePath/php${version} + # fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/gmp.sh b/plugins/php/versions/common/gmp.sh new file mode 100755 index 000000000..48b168905 --- /dev/null +++ b/plugins/php/versions/common/gmp.sh @@ -0,0 +1,122 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +appPath=$(dirname "$curPath") + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +LIBNAME=gmp +LIBV=0 + +if [ "$version" == "53" ];then + echo "i wont support it" + exit +fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config + + # FIND_C99=`cat Makefile|grep c99` + # if [ "$FIND_C99" == "" ];then + # sed -i $BAK 's/CFLAGS \=/CFLAGS \= -std=c99/g' Makefile + # fi + + if [ "$sysName" == "Darwin" ];then + OPTIONS="$OPTIONS --with-gmp=$(brew --prefix gmp)" + fi + + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/iconv.sh b/plugins/php/versions/common/iconv.sh new file mode 100755 index 000000000..f0fd1ecd4 --- /dev/null +++ b/plugins/php/versions/common/iconv.sh @@ -0,0 +1,116 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +appPath=$(dirname "$curPath") + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=iconv +LIBV=0 + + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + cd ${rootPath}/plugins/php/lib && /bin/bash libiconv.sh + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + if [ "$sysName" == "Darwin" ];then + OPTIONS="$OPTIONS --with-iconv=${serverPath}/lib/libiconv" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/igbinary.sh b/plugins/php/versions/common/igbinary.sh new file mode 100755 index 000000000..5128c9e81 --- /dev/null +++ b/plugins/php/versions/common/igbinary.sh @@ -0,0 +1,111 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + + +actionType=$1 +version=$2 + +LIBNAME=igbinary +LIBV=3.2.16 + +if [ "$version" -lt "70" ];then + echo "not need" + exit 1 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.use_namespace/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[${LIBNAME}\]/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/imagemagick.sh b/plugins/php/versions/common/imagemagick.sh new file mode 100755 index 000000000..591e1b4b1 --- /dev/null +++ b/plugins/php/versions/common/imagemagick.sh @@ -0,0 +1,124 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=imagick +LIBV=3.8.0 +sysName=`uname` +actionType=$1 +version=$2 + + +if [ "$version" == "53" ];then + echo 'not need' + exit 0 +elif [ "$version" -lt "70" ];then + LIBV=3.6.0 +else + echo 'ok' +fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + # if [ "$version" -gt "74" ];then + # OPTIONS="$OPTIONS --disable-openmp" + # fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/intl.sh b/plugins/php/versions/common/intl.sh new file mode 100755 index 000000000..c3be5072d --- /dev/null +++ b/plugins/php/versions/common/intl.sh @@ -0,0 +1,155 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` + +# _os=`uname` + +# if [ ${_os} == "Darwin" ]; then +# OSNAME='macos' +# elif grep -Eq "openSUSE" /etc/*-release; then +# OSNAME='opensuse' +# elif grep -Eq "FreeBSD" /etc/*-release; then +# OSNAME='freebsd' +# elif grep -Eqi "CentOS" /etc/issue || grep -Eqi "CentOS" /etc/*-release; then +# OSNAME='rhel' +# elif grep -Eqi "Fedora" /etc/issue || grep -Eqi "Fedora" /etc/*-release; then +# OSNAME='rhel' +# elif grep -Eqi "Rocky" /etc/issue || grep -Eqi "Rocky" /etc/*-release; then +# OSNAME='rhel' +# elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eqi "AlmaLinux" /etc/*-release; then +# OSNAME='rhel' +# elif grep -Eqi "Amazon Linux" /etc/issue || grep -Eqi "Amazon Linux" /etc/*-release; then +# OSNAME='amazon' +# elif grep -Eqi "Debian" /etc/issue || grep -Eqi "Debian" /etc/*-release; then +# OSNAME='debian' +# elif grep -Eqi "Ubuntu" /etc/issue || grep -Eqi "Ubuntu" /etc/*-release; then +# OSNAME='ubuntu' +# else +# OSNAME='unknow' +# fi + +actionType=$1 +version=$2 + + +LIBNAME=intl +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +OPTIONS='' +if [ "$version" -lt "74" ];then + + # cd /www/server/mdserver-web/plugins/php/lib && /bin/bash icu.sh + cd ${rootPath}/plugins/php/lib && /bin/bash icu.sh + OPTIONS="--with-icu-dir=${serverPath}/lib/icu" +fi + + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info icu4c | grep ${BREW_DIR}/Cellar/icu4c | cut -d \ -f 1 | awk 'END {print}'` + + OPTIONS="$OPTIONS --with-icu-dir=${serverPath}/lib/icu" + OPTIONS="$OPTIONS --enable-intl" + fi + + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/ioncube.sh b/plugins/php/versions/common/ioncube.sh new file mode 100755 index 000000000..1d6065c7d --- /dev/null +++ b/plugins/php/versions/common/ioncube.sh @@ -0,0 +1,123 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +# support 55-74 + +LIBNAME=ioncube +LIBV=0 +sysName=`uname` +actionType=$1 +version=$2 +IC_VERSION=${version:0:1}.${version:1:2} +ARCH=`uname -m` + +if [ "$version" -gt "82" ];then + echo "not need" + exit 1 +fi + +DEFAULT_ARCH='x86-64' +if [ "$ARCH" == "aarch64" ];then + DEFAULT_ARCH='aarch64' +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -f $php_lib/ioncube_loaders_lin.tar.gz ];then + wget -O $php_lib/ioncube_loaders_lin.tar.gz https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_${DEFAULT_ARCH}.tar.gz + cd $php_lib && tar -zxvf ioncube_loaders_lin.tar.gz + fi + + if [ ! -d $php_lib/ioncube ];then + cd $php_lib && tar -zxvf ioncube_loaders_lin.tar.gz + fi + cd $php_lib/ioncube + + cp -rf $php_lib/ioncube/ioncube_loader_lin_${IC_VERSION}.so $extFile + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + sed -i $BAK "1i\[${LIBNAME}]" $serverPath/php/$version/etc/php.ini + sed -i $BAK "2i\zend_extension=${LIBNAME}.so" $serverPath/php/$version/etc/php.ini + # echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + # echo "zend_extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' + + if [ -d $php_lib/ioncube ];then + rm -rf $php_lib/ioncube + fi +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$vphp not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/ldap.sh b/plugins/php/versions/common/ldap.sh new file mode 100755 index 000000000..bae5dae75 --- /dev/null +++ b/plugins/php/versions/common/ldap.sh @@ -0,0 +1,110 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=ldap +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/lib.md b/plugins/php/versions/common/lib.md new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/php/versions/common/mcrypt.sh b/plugins/php/versions/common/mcrypt.sh new file mode 100755 index 000000000..d23e8d264 --- /dev/null +++ b/plugins/php/versions/common/mcrypt.sh @@ -0,0 +1,133 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=mcrypt +LIBV=1.0.7 + +if [ "$version" -lt "72" ];then + echo "not need" + exit 1 +fi + +if [ "$version" -ge "84" ];then + LIBV=1.0.7 + echo "not need" + exit 1 +fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + cd ${rootPath}/plugins/php/lib && bash libmcrypt.sh + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + if [ "$sysName" == "Darwin" ];then + + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info mcrypt | grep ${BREW_DIR}/Cellar/mcrypt | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-mcrypt=$(brew --prefix mcrypt)" + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$LIB_DEPEND_DIR/lib/pkgconfig + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + # cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.use_namespace/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[${LIBNAME}\]/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/memcache.sh b/plugins/php/versions/common/memcache.sh new file mode 100755 index 000000000..e326d3119 --- /dev/null +++ b/plugins/php/versions/common/memcache.sh @@ -0,0 +1,119 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=memcache +LIBV=2.2.7 +sysName=`uname` +actionType=$1 +version=$2 + +if [ "$version" -gt "56" ] && [ "$version" -lt "80" ];then + LIBV=4.0.5.2 +fi + +if [ "$version" -gt "74" ];then + LIBV=8.0 +fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + $OPTIONS \ + --enable-memcache --with-zlib-dir + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install memcache, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/memcached.sh b/plugins/php/versions/common/memcached.sh new file mode 100755 index 000000000..ee2e4142a --- /dev/null +++ b/plugins/php/versions/common/memcached.sh @@ -0,0 +1,137 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=memcached +LIBV=3.3.0 +sysName=`uname` +actionType=$1 +version=$2 + +if [ "$version" -lt "70" ];then + LIBV=2.2.0 +fi + + +if [ "$version" == "85" ];then + LIBV=3.4.0 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + + if [ "$sysName" == "Darwin" ];then + OPTIONS="$OPTIONS --with-zlib-dir=$(brew --prefix zlib)" + OPTIONS="$OPTIONS --with-libmemcached-dir=$(brew --prefix libmemcached)" + else + pkg-config --exists libmemcached + if [ "$?" != "0" ]; then + cd ${rootPath}/plugins/php/lib && /bin/bash libmemcached.sh + OPTIONS="$OPTIONS --with-libmemcached-dir=${serverPath}/lib/libmemcached" + fi + fi + + # sed -i '_bak' "3237,3238s#ulong#zend_ulong#g" $php_lib/${LIBNAME}-${LIBV}/php_memcached.c + cd $php_lib/${LIBNAME}-${LIBV} + $serverPath/php/$version/bin/phpize + + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + $OPTIONS \ + --enable-memcached \ + --disable-memcached-sasl + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/mongo.sh b/plugins/php/versions/common/mongo.sh new file mode 100755 index 000000000..ca3eb4b44 --- /dev/null +++ b/plugins/php/versions/common/mongo.sh @@ -0,0 +1,119 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=mongo +LIBV=1.6.16 +sysName=`uname` +actionType=$1 +version=$2 + +if [ "$version" -ge "70" ];then + echo "not need" + exit 1 +fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + OPTIONS='' + if [ $sysName == 'Darwin' ]; then + LIB_DEPEND_DIR=`brew info openssl | grep /usr/local/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="--with-openssl-dir=${LIB_DEPEND_DIR}" + fi + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$vphp not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/mongodb.sh b/plugins/php/versions/common/mongodb.sh new file mode 100755 index 000000000..96228c86b --- /dev/null +++ b/plugins/php/versions/common/mongodb.sh @@ -0,0 +1,129 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=mongodb +LIBV=1.13.0 +sysName=`uname` +actionType=$1 +version=$2 + +if [ "$version" -ge '74' ];then + LIBV=1.20.0 +fi + +if [ "$version" == '71' ];then + LIBV=1.11.1 +fi + +if [ "$version" == '70' ];then + LIBV=1.7.5 +fi + +if [ "$version" == '56' ];then + LIBV=1.7.4 +fi + +if [ "$version" == '55' ];then + LIBV=1.5.3 +fi + +if [ "$version" -lt '55' ];then + echo "not need" + exit 1 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$vphp not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/mysql_xdevapi.sh b/plugins/php/versions/common/mysql_xdevapi.sh new file mode 100755 index 000000000..4905f78a1 --- /dev/null +++ b/plugins/php/versions/common/mysql_xdevapi.sh @@ -0,0 +1,110 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=mysql_xdevapi +LIBV=8.0.30 +sysName=`uname` +actionType=$1 +version=$2 + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/${version}/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/opcache.sh b/plugins/php/versions/common/opcache.sh new file mode 100755 index 000000000..678739614 --- /dev/null +++ b/plugins/php/versions/common/opcache.sh @@ -0,0 +1,86 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +LIBNAME=opcache +sysName=`uname` +actionType=$1 +version=$2 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + # OPcache 黑名单文件位置。 + # 黑名单文件为文本文件,包含了不进行预编译优化的文件名,每行一个文件名。 + # 黑名单中的文件名可以使用通配符,也可以使用前缀。 此文件中以分号(;)开头的行将被视为注释。 + OP_BL=${serverPath}/php/opcache-blacklist.txt + if [ ! -f $OP_BL ];then + touch $OP_BL + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[opcache]" >> $serverPath/php/$version/etc/php.ini + echo "zend_extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + echo "opcache.enable=1" >> $serverPath/php/$version/etc/php.ini + echo "opcache.memory_consumption=128" >> $serverPath/php/$version/etc/php.ini + echo "opcache.interned_strings_buffer=8" >> $serverPath/php/$version/etc/php.ini + echo "opcache.max_accelerated_files=4000" >> $serverPath/php/$version/etc/php.ini + echo "opcache.revalidate_freq=60" >> $serverPath/php/$version/etc/php.ini + echo "opcache.fast_shutdown=1" >> $serverPath/php/$version/etc/php.ini + echo "opcache.enable_cli=1" >> $serverPath/php/$version/etc/php.ini + echo "opcache.jit=1205" >> $serverPath/php/$version/etc/php.ini + echo "opcache.jit_buffer_size=64M" >> $serverPath/php/$version/etc/php.ini + echo "opcache.save_comments=0" >> $serverPath/php/$version/etc/php.ini + echo "opcache.blacklist_filename=${OP_BL}" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/openssl.sh b/plugins/php/versions/common/openssl.sh new file mode 100755 index 000000000..2813eff2a --- /dev/null +++ b/plugins/php/versions/common/openssl.sh @@ -0,0 +1,95 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=openssl +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + if [ "$version" -lt "70" ];then + bash $curPath/openssl_low_version.sh $actionType $version + return + fi + + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "locked" > $extFile + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + if [ -f "/etc/ssl/certs/ca-certificates.crt" ];then + echo "openssl.cafile=/etc/ssl/certs/ca-certificates.crt" >> $serverPath/php/$version/etc/php.ini + elif [ -f "/etc/pki/tls/certs/ca-bundle.crt" ];then + echo "openssl.cafile=/etc/pki/tls/certs/ca-bundle.crt" >> $serverPath/php/$version/etc/php.ini + fi + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/openssl_bak.sh b/plugins/php/versions/common/openssl_bak.sh new file mode 100755 index 000000000..b011245cb --- /dev/null +++ b/plugins/php/versions/common/openssl_bak.sh @@ -0,0 +1,177 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=openssl +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + # cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + if [ "$version" -lt "81" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + fi + + if [ "$version" -gt "82" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + fi + + if [ "$sysName" == "Darwin" ] ;then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + if [ ! -f "config.m4" ];then + mv config0.m4 config.m4 + fi + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + # openssl_version=`pkg-config openssl --modversion` + # export PKG_CONFIG_PATH=$serverPath/lib/openssl10/lib/pkgconfig + if [ "$version" -lt "81" ] && [ "$sysName" != "Darwin" ];then + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl10/lib/pkgconfig + fi + + # Darwin + # otool -L /Users/midoks/Desktop/mwdev/server/php/83/bin/php + # lldb /Users/midoks/Desktop/mwdev/server/php/83/bin/php -r 'phpinfo()' + # otool -L /Users/midoks/Desktop/mwdev/server/php/83/lib/php/extensions/no-debug-non-zts-20230831/openssl.so + # ldd /www/server/php/83/bin/php + + if [ "$version" -lt "84" ] && [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl@1.0 | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl@1.0)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" + + echo "$LIB_DEPEND_DIR/lib/pkgconfig" + fi + + if [ "$version" -ge "84" ] && [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info openssl | grep ${BREW_DIR}/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" + fi + + # if [ "$version" -gt "82" ] && [ "$sysName" == "Darwin" ];then + # export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + # fi + + + $serverPath/php/$version/bin/phpize + # --with-openssl + echo "./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS" + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + if [ -f "/etc/ssl/certs/ca-certificates.crt" ];then + echo "openssl.cafile=/etc/ssl/certs/ca-certificates.crt" >> $serverPath/php/$version/etc/php.ini + elif [ -f "/etc/pki/tls/certs/ca-bundle.crt" ];then + echo "openssl.cafile=/etc/pki/tls/certs/ca-bundle.crt" >> $serverPath/php/$version/etc/php.ini + fi + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/openssl_low_version.sh b/plugins/php/versions/common/openssl_low_version.sh new file mode 100755 index 000000000..b011245cb --- /dev/null +++ b/plugins/php/versions/common/openssl_low_version.sh @@ -0,0 +1,177 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=openssl +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + # cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + if [ "$version" -lt "81" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_10.sh + fi + + if [ "$version" -gt "82" ];then + cd ${rootPath}/plugins/php/lib && /bin/bash openssl.sh + fi + + if [ "$sysName" == "Darwin" ] ;then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + if [ ! -f "config.m4" ];then + mv config0.m4 config.m4 + fi + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + # openssl_version=`pkg-config openssl --modversion` + # export PKG_CONFIG_PATH=$serverPath/lib/openssl10/lib/pkgconfig + if [ "$version" -lt "81" ] && [ "$sysName" != "Darwin" ];then + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$serverPath/lib/openssl10/lib/pkgconfig + fi + + # Darwin + # otool -L /Users/midoks/Desktop/mwdev/server/php/83/bin/php + # lldb /Users/midoks/Desktop/mwdev/server/php/83/bin/php -r 'phpinfo()' + # otool -L /Users/midoks/Desktop/mwdev/server/php/83/lib/php/extensions/no-debug-non-zts-20230831/openssl.so + # ldd /www/server/php/83/bin/php + + if [ "$version" -lt "84" ] && [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info openssl@1.0 | grep ${BREW_DIR}/Cellar/openssl@1.0 | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl@1.0)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" + + echo "$LIB_DEPEND_DIR/lib/pkgconfig" + fi + + if [ "$version" -ge "84" ] && [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info openssl | grep ${BREW_DIR}/Cellar/openssl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-openssl=$(brew --prefix openssl)" + export PKG_CONFIG_PATH=$LIB_DEPEND_DIR/lib/pkgconfig + export OPENSSL_CFLAGS="-I${LIB_DEPEND_DIR}/include" + export OPENSSL_LIBS="-L/${LIB_DEPEND_DIR}/lib -lssl -lcrypto -lz" + fi + + # if [ "$version" -gt "82" ] && [ "$sysName" == "Darwin" ];then + # export PKG_CONFIG_PATH=$serverPath/lib/openssl/lib/pkgconfig + # fi + + + $serverPath/php/$version/bin/phpize + # --with-openssl + echo "./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS" + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + if [ -f "/etc/ssl/certs/ca-certificates.crt" ];then + echo "openssl.cafile=/etc/ssl/certs/ca-certificates.crt" >> $serverPath/php/$version/etc/php.ini + elif [ -f "/etc/pki/tls/certs/ca-bundle.crt" ];then + echo "openssl.cafile=/etc/pki/tls/certs/ca-bundle.crt" >> $serverPath/php/$version/etc/php.ini + fi + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/pcntl.sh b/plugins/php/versions/common/pcntl.sh new file mode 100755 index 000000000..8e01f8a0e --- /dev/null +++ b/plugins/php/versions/common/pcntl.sh @@ -0,0 +1,110 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=pcntl +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/pdo_pgsql.sh b/plugins/php/versions/common/pdo_pgsql.sh new file mode 100755 index 000000000..7ff8e289d --- /dev/null +++ b/plugins/php/versions/common/pdo_pgsql.sh @@ -0,0 +1,110 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` + +actionType=$1 +version=$2 + +LIBNAME=pdo_pgsql +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +OPTIONS='' +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/pgsql.sh b/plugins/php/versions/common/pgsql.sh new file mode 100755 index 000000000..86cd59f34 --- /dev/null +++ b/plugins/php/versions/common/pgsql.sh @@ -0,0 +1,110 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` + +actionType=$1 +version=$2 + +LIBNAME=pgsql +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +OPTIONS='' +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/phalcon.sh b/plugins/php/versions/common/phalcon.sh new file mode 100755 index 000000000..b80ddde9f --- /dev/null +++ b/plugins/php/versions/common/phalcon.sh @@ -0,0 +1,129 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=phalcon +LIBV=4.1.2 + +if [ "$version" -lt "73" ];then + echo "not support!" + exit 1 +fi + +if [ "$version" -gt "73" ];then + LIBV=5.2.3 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + while [[ ! -f "$extFile" ]]; + do + echo -e ".\c" + sleep 0.5 + if [ ! -f "$extFile" ];then + echo "ERROR!" + fi + let n+=1 + if [ $n -gt 8 ];then + echo "WAIT " $n "TIMES FAIL!" + return; + fi + done + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/readline.sh b/plugins/php/versions/common/readline.sh new file mode 100755 index 000000000..71c13ad56 --- /dev/null +++ b/plugins/php/versions/common/readline.sh @@ -0,0 +1,122 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +# cd /www/server/mdserver-web/plugins/php/versions/common && bash readline.sh install 81 + +curPath=`pwd` + +appPath=$(dirname "$curPath") + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=readline +LIBV=0 + +if [ "$version" -lt "74" ];then + echo "not need" + exit 0 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + if [ ! -f "config.m4" ];then + mv config0.m4 config.m4 + fi + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + cd ${rootPath}/plugins/php/lib && /bin/bash libedit.sh + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${serverPath}/lib/libedit/lib/pkgconfig + OPTIONS="$OPTIONS --with-libedit=${serverPath}/lib/libedit" + + cd $sourcePath/php${version}/ext/${LIBNAME} + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/redis.sh b/plugins/php/versions/common/redis.sh new file mode 100755 index 000000000..994f83a0f --- /dev/null +++ b/plugins/php/versions/common/redis.sh @@ -0,0 +1,122 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=redis +LIBV=6.3.0 +sysName=`uname` +actionType=$1 +version=$2 + +if [ "$version" == "52" ];then + LIBV=2.2.7 +elif [ "$version" -lt "70" ];then + LIBV=4.2.0 +elif [ "$version" -lt "80" ];then + LIBV=5.3.7 +elif [ "$version" -gt "80" ];then + LIBV=6.3.0 +else + echo 'ok' +fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/${version}/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/seaslog.sh b/plugins/php/versions/common/seaslog.sh new file mode 100755 index 000000000..5ec0288fe --- /dev/null +++ b/plugins/php/versions/common/seaslog.sh @@ -0,0 +1,120 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=SeasLog +_LIBNAME=$(echo $LIBNAME | tr '[A-Z]' '[a-z]') +LIBV=2.2.0 +sysName=`uname` +actionType=$1 +version=$2 + + +if [ "$version" -lt "72" ];then + LIBV=2.0.2 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${_LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${_LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + OPTIONS='' + if [ $sysName == 'Darwin' ]; then + OPTIONS="${OPTIONS} --with-curl=${serverPath}/lib/curl" + fi + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + sleep 1 + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + _LIBNAME=$(echo $LIBNAME | tr '[A-Z]' '[a-z]') + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${_LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${_LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + _LIBNAME=$(echo $LIBNAME | tr '[A-Z]' '[a-z]') + sed -i $BAK "/${_LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${_LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/sg11.sh b/plugins/php/versions/common/sg11.sh new file mode 100644 index 000000000..8bcf13f33 --- /dev/null +++ b/plugins/php/versions/common/sg11.sh @@ -0,0 +1,138 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +# https://www.sourceguardian.com/loaders.html + +# support 52-81 + +LIBNAME=sg11 +LIBV=0 +sysName=`uname` +actionType=$1 +version=$2 +SG_VER=${version:0:1}.${version:1:2} + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + + +Install_lib() +{ + bash ${rootPath}/scripts/getos.sh + OSNAME=`cat ${rootPath}/data/osname.pl` + if [ "$OSNAME" == 'macos' ];then + VERSION_ID=none + else + VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + fi + + echo "${OSNAME}:${VERSION_ID}" + + DEFAULT_OSNAME=linux-x86_64 + SUFFIX_NAME=lin + if [ "$OSNAME" == 'macos' ];then + DEFAULT_OSNAME=macosx + SUFFIX_NAME=dar + fi + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + mkdir -p $php_lib/sg11 + if [ ! -f $php_lib/sg11_loaders.tar.bz2 ];then + curl -sSLo $php_lib/sg11_loaders.tar.bz2 https://www.sourceguardian.com/loaders/download/loaders.tar.bz2 + echo "cd $php_lib && tar -jxvf $php_lib/sg11_loaders.tar.bz2 -C $php_lib/sg11" + cd $php_lib && tar -jxvf $php_lib/sg11_loaders.tar.bz2 -C $php_lib/sg11 + fi + + + if [ ! -d $php_lib/sg11/macosx ];then + cd $php_lib && tar -jxvf $php_lib/sg11_loaders.tar.bz2 -C $php_lib/sg11 + fi + cd $php_lib/sg11 + # echo "mv $php_lib/sg11/${DEFAULT_OSNAME}/ixed.${SG_VER}.lin $extFile" + if [ -f $php_lib/sg11/${DEFAULT_OSNAME}/ixed.${SG_VER}.${SUFFIX_NAME} ];then + cp -rf $php_lib/sg11/${DEFAULT_OSNAME}/ixed.${SG_VER}.${SUFFIX_NAME} $extFile + else + echo 'Not supported temporarily' + exit + fi + + if [ "$OSNAME" == 'macos' ];then + xattr -c * $extFile + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$vphp not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/sodium.sh b/plugins/php/versions/common/sodium.sh new file mode 100755 index 000000000..25cb6bc07 --- /dev/null +++ b/plugins/php/versions/common/sodium.sh @@ -0,0 +1,125 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + + +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=sodium +LIBV=2.0.23 + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ "$sysName" != "Darwin" ];then + cd ${rootPath}/plugins/php/lib && bash libsodium.sh + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/lib${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/lib${LIBNAME}-${LIBV} + + + OPTIONS="" + if [ "$sysName" == "Darwin" ];then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info libsodium | grep ${BREW_DIR}/Cellar/libsodium | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="$OPTIONS --with-sodium=${LIB_DEPEND_DIR}" + else + OPTIONS="$OPTIONS --with-sodium=$serverPath/lib/libsodium" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + if [ -d $php_lib/lib${LIBNAME}-${LIBV} ];then + cd $php_lib && rm -rf $php_lib/lib${LIBNAME}-${LIBV} + fi + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[${LIBNAME}\]/d" $serverPath/php/$version/etc/php.ini + + rm -rf $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/solr.sh b/plugins/php/versions/common/solr.sh new file mode 100755 index 000000000..55dec099d --- /dev/null +++ b/plugins/php/versions/common/solr.sh @@ -0,0 +1,127 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=solr +LIBV=2.8.1 +sysName=`uname` +actionType=$1 +version=$2 + +if [ "$version" -lt "72" ];then + LIBV=2.4.0 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "$isInstall" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + if [ $sysName == 'Darwin' ]; then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info curl | grep ${BREW_DIR}/Cellar/curl | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="${OPTIONS} --with-curl=${LIB_DEPEND_DIR}" + fi + + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + if [ -d $php_lib/lib${LIBNAME}-${LIBV} ];then + cd $php_lib && rm -rf $php_lib/lib${LIBNAME}-${LIBV} + fi + fi + sleep 1 + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + extFile=$extDir${LIBNAME}.so + if [ ! -f "$extFile" ];then + echo "php$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$vphp not install ${LIBNAME}, Plese select other version!" + return + fi + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/swoole.sh b/plugins/php/versions/common/swoole.sh new file mode 100755 index 000000000..6e7475ed5 --- /dev/null +++ b/plugins/php/versions/common/swoole.sh @@ -0,0 +1,148 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=swoole +LIBV=5.1.6 + +if [ "$version" == "85" ];then + echo "not need" + exit 1 +fi + +if [ "$version" -lt "70" ];then + LIBV=1.10.1 +elif [ "$version" == "70" ];then + LIBV=4.3.0 +elif [ "$version" == "71" ];then + LIBV=4.5.2 +elif [ "$version" -le "74" ];then + LIBV=4.8.10 +elif [ "$version" -lt "80" ];then + LIBV=6.0.2 +elif [ "$version" -gt "80" ];then + LIBV=6.0.2 +else + echo 'other?' + exit 0 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + cd ${rootPath}/plugins/php/lib && /bin/bash openssl_11.sh + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + $OPTIONS \ + --enable-openssl \ + --with-openssl-dir=$serverPath/lib/openssl11 \ + --enable-sockets + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + while [[ ! -f "$extFile" ]]; + do + echo -e ".\c" + sleep 0.5 + if [ ! -f "$extFile" ];then + echo "ERROR!" + fi + let n+=1 + if [ $n -gt 8 ];then + echo "WAIT " $n "TIMES FAIL!" + return; + fi + done + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/xdebug.sh b/plugins/php/versions/common/xdebug.sh new file mode 100755 index 000000000..a09ad77a5 --- /dev/null +++ b/plugins/php/versions/common/xdebug.sh @@ -0,0 +1,128 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=xdebug +LIBV=3.4.5 +sysName=`uname` +actionType=$1 +version=$2 + +if [ "$version" == "85" ];then + echo "not need" + exit 1 +fi + + +if [ "$version" -lt "70" ];then + LIBV=2.2.7 +fi + +if [ "$version" -le "80" ];then + LIBV=2.7.0 +fi + +if [ "$version" -gt "80" ];then + LIBV=3.4.5 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + isInstall=`cat $serverPath/php/${version}/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "zend_extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/xhprof.sh b/plugins/php/versions/common/xhprof.sh new file mode 100755 index 000000000..02198ee7a --- /dev/null +++ b/plugins/php/versions/common/xhprof.sh @@ -0,0 +1,119 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +LIBNAME=xhprof +LIBV=2.3.10 +sysName=`uname` +actionType=$1 +version=$2 + +if [ "$version" -lt "70" ];then + LIBV=0.9.4 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV}/extension + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --enable-xhprof \ + --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + if [ ! -f /tmp/xhprof ];then + mkdir -p /tmp/xhprof + chown -R www:www /tmp/xhprof + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + echo "${LIBNAME}.output_dir=/tmp/xhprof" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/yac.sh b/plugins/php/versions/common/yac.sh new file mode 100755 index 000000000..78af0c5b9 --- /dev/null +++ b/plugins/php/versions/common/yac.sh @@ -0,0 +1,113 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +LIBNAME=yac +LIBV=2.3.1 + + +if [ "$version" -lt "70" ];then + echo "not need" + exit 1 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + + +Install_lib() +{ + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "$isInstall" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config + make clean && make && make install && make clean + cd .. + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + echo "${LIBNAME}.use_namespace=1" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php$version 未安装yaf,请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[${LIBNAME}\]/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/yaf.sh b/plugins/php/versions/common/yaf.sh new file mode 100755 index 000000000..5bb1af208 --- /dev/null +++ b/plugins/php/versions/common/yaf.sh @@ -0,0 +1,121 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=yaf +LIBV=3.3.6 + +if [ "$version" -lt "70" ]; then + LIBV=2.3.5 +elif [ "$version" -lt "74" ]; then + LIBV=3.2.5 +elif [ "$version" -lt "80" ]; then + LIBV=3.3.5 +fi + + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + echo "${LIBNAME}.use_namespace=1" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.use_namespace/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[${LIBNAME}\]/d" $serverPath/php/$version/etc/php.ini + + rm -rf $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/yaml.sh b/plugins/php/versions/common/yaml.sh new file mode 100755 index 000000000..9c58704a5 --- /dev/null +++ b/plugins/php/versions/common/yaml.sh @@ -0,0 +1,117 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +sysName=`uname` +LIBNAME=yaml +LIBV=2.2.4 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + if [ $sysName == 'Darwin' ]; then + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + LIB_DEPEND_DIR=`brew info libyaml | grep ${BREW_DIR}/Cellar/libyaml | cut -d \ -f 1 | awk 'END {print}'` + OPTIONS="${OPTIONS} --with-yaml=${LIB_DEPEND_DIR}" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[${LIBNAME}\]/d" $serverPath/php/$version/etc/php.ini + + rm -rf $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/yar.sh b/plugins/php/versions/common/yar.sh new file mode 100755 index 000000000..0b9c90e8b --- /dev/null +++ b/plugins/php/versions/common/yar.sh @@ -0,0 +1,119 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=yar +LIBV=2.3.2 + +if [ "$version" -lt "70" ];then + LIBV=1.2.5 +fi + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + php_lib=$sourcePath/php_lib + mkdir -p $php_lib + if [ ! -d $php_lib/${LIBNAME}-${LIBV} ];then + if [ ! -f $php_lib/${LIBNAME}-${LIBV}.tgz ];then + wget --no-check-certificate -O $php_lib/${LIBNAME}-${LIBV}.tgz http://pecl.php.net/get/${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib && tar xvf ${LIBNAME}-${LIBV}.tgz + fi + cd $php_lib/${LIBNAME}-${LIBV} + + OPTIONS='' + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config \ + $OPTIONS \ + --with-curl=$serverPath/lib/curl + make clean && make && make install && make clean + + cd $php_lib && rm -rf $php_lib/${LIBNAME}-${LIBV} + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return; + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + echo "${LIBNAME}.expose_info=false" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + echo "php-$version not install ${LIBNAME}, Plese select other version!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.use_namespace/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/\[${LIBNAME}\]/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/zip.sh b/plugins/php/versions/common/zip.sh new file mode 100755 index 000000000..1c4021502 --- /dev/null +++ b/plugins/php/versions/common/zip.sh @@ -0,0 +1,127 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } +function version_le() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" == "$1"; } +function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" != "$1"; } +function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } + +curPath=`pwd` +appPath=$(dirname "$curPath") + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php + +actionType=$1 +version=$2 + +LIBNAME=zip +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +if [ ! -d $serverPath/lib/libzip ];then + cd ${rootPath}/plugins/php/lib && /bin/bash libzip.sh +fi + +export PKG_CONFIG_PATH=${serverPath}/lib/libzip/lib/pkgconfig + +# ZIP_OPTION='--with-zip' +# libzip_version=`pkg-config libzip --modversion` +# if version_lt "$libzip_version" "0.11.0" ;then +# cd ${rootPath}/plugins/php/lib && /bin/bash libzip.sh +# export PKG_CONFIG_PATH=$serverPath/lib/libzip/lib/pkgconfig +# ZIP_OPTION="--with-zip=$serverPath/lib/libzip" +# fi + +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config --with-zip + + make clean && make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/common/zlib.sh b/plugins/php/versions/common/zlib.sh new file mode 100755 index 000000000..81291da52 --- /dev/null +++ b/plugins/php/versions/common/zlib.sh @@ -0,0 +1,117 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` + +appPath=$(dirname "$curPath") + +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source/php +SYS_ARCH=`arch` +actionType=$1 +version=$2 + +LIBNAME=zlib +LIBV=0 + +LIB_PATH_NAME=lib/php +if [ -d $serverPath/php/${version}/lib64 ];then + LIB_PATH_NAME=lib64 +fi + +NON_ZTS_FILENAME=`ls $serverPath/php/${version}/${LIB_PATH_NAME}/extensions | grep no-debug-non-zts` +extFile=$serverPath/php/${version}/${LIB_PATH_NAME}/extensions/${NON_ZTS_FILENAME}/${LIBNAME}.so + +sysName=`uname` +if [ "$sysName" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +# --with-zlib-dir=$serverPath/lib/zlib +Install_lib() +{ + + isInstall=`cat $serverPath/php/$version/etc/php.ini|grep "${LIBNAME}.so"` + if [ "${isInstall}" != "" ];then + echo "php-$version 已安装${LIBNAME},请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + + if [ ! -d $sourcePath/php${version}/ext ];then + cd ${rootPath}/plugins/php && /bin/bash ${rootPath}/plugins/php/versions/${version}/install.sh install + fi + + cd $sourcePath/php${version}/ext/${LIBNAME} + + if [ ! -f "config.m4" ];then + mv config0.m4 config.m4 + fi + + OPTIONS="" + if [ "${SYS_ARCH}" == "aarch64" ] && [ "$version" -lt "56" ];then + OPTIONS="$OPTIONS --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu" + fi + + $serverPath/php/$version/bin/phpize + ./configure --with-php-config=$serverPath/php/$version/bin/php-config $OPTIONS + make && make install && make clean + + if [ -d $sourcePath/php${version} ];then + cd ${sourcePath} && rm -rf $sourcePath/php${version} + fi + + fi + + if [ ! -f "$extFile" ];then + echo "ERROR!" + return + fi + + echo "" >> $serverPath/php/$version/etc/php.ini + echo "[${LIBNAME}]" >> $serverPath/php/$version/etc/php.ini + echo "extension=${LIBNAME}.so" >> $serverPath/php/$version/etc/php.ini + + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===========================================================' + echo 'successful!' +} + + +Uninstall_lib() +{ + if [ ! -f "$serverPath/php/$version/bin/php-config" ];then + echo "php-$version 未安装,请选择其它版本!" + return + fi + + if [ ! -f "$extFile" ];then + echo "php-$version 未安装${LIBNAME},请选择其它版本!" + return + fi + + echo $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}.so/d" $serverPath/php/$version/etc/php.ini + sed -i $BAK "/${LIBNAME}/d" $serverPath/php/$version/etc/php.ini + + rm -f $extFile + cd ${curPath} && bash ${rootPath}/plugins/php/versions/lib.sh $version restart + echo '===============================================' + echo 'successful!' +} + + + +if [ "$actionType" == 'install' ];then + Install_lib +elif [ "$actionType" == 'uninstall' ];then + Uninstall_lib +fi \ No newline at end of file diff --git a/plugins/php/versions/lib.sh b/plugins/php/versions/lib.sh new file mode 100644 index 000000000..2e4584a82 --- /dev/null +++ b/plugins/php/versions/lib.sh @@ -0,0 +1,21 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH=$PATH:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +version=$1 +action=$2 + +if [ -f /lib/systemd/system/php${version}.service ];then + systemctl ${action} php${version} +elif [ -f /usr/lib/systemd/system/php${version}.service ]; then + systemctl ${action} php${version} +else + $serverPath/php/init.d/php${version} ${action} +fi \ No newline at end of file diff --git a/plugins/php/versions/phplib.conf b/plugins/php/versions/phplib.conf new file mode 100755 index 000000000..9495df746 --- /dev/null +++ b/plugins/php/versions/phplib.conf @@ -0,0 +1,925 @@ +[ + { + "name": "ZendGuardLoader", + "versions": [ + "53", + "54", + "56" + ], + "type": "脚本解密", + "msg": "用于解密ZendGuard加密脚本!", + "shell": "zend_guard_loader.sh", + "check": "ZendGuardLoader.so" + }, + { + "name": "ZendOptimizer", + "versions": [ + "52" + ], + "type": "脚本解密", + "msg": "用于解密ZendOptimizer加密脚本!", + "shell": "zend_optimizer.sh", + "check": "ZendOptimizer.so" + }, + { + "name": "sg11", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83" + ], + "type": "脚本解密", + "msg": "用于解密SG11加密脚本!", + "shell": "sg11.sh", + "check": "sg11.so" + }, + { + "name": "ionCube", + "versions": [ + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83" + ], + "type": "脚本解密", + "msg": "用于解密ionCube Encoder加密脚本!", + "shell": "ioncube.sh", + "check": "ioncube.so" + }, + { + "name": "sodium", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "加密库", + "msg": "Sodium加密库的包装器", + "shell": "sodium.sh", + "check": "sodium.so" + }, + { + "name": "brotli", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "压缩", + "msg": "压缩算法", + "shell": "brotli.sh", + "check": "brotli.so" + }, + { + "name": "gmp", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "运算库", + "msg": "一个开源的数学运算库", + "shell": "gmp.sh", + "check": "gmp.so" + }, + { + "name": "opcache", + "versions": [ + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "用于加速PHP脚本!", + "shell": "opcache.sh", + "check": "opcache.so" + }, + { + "name": "openssl", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "SSL", + "shell": "openssl.sh", + "check": "openssl.so" + }, + { + "name": "mcrypt", + "versions": [ + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "加密软件", + "shell": "mcrypt.sh", + "check": "mcrypt.so" + }, + { + "name": "ldap", + "versions": [ + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "轻型目录访问协议", + "shell": "ldap.sh", + "check": "ldap.so" + }, + { + "name": "mysql_xdevapi", + "versions": [ + "71", + "72", + "73" + ], + "type": "通用扩展", + "msg": "PHP71-73连接MySQL", + "shell": "mysql_xdevapi.sh", + "check": "mysql_xdevapi.so" + }, + { + "name": "bcmath", + "versions": [ + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "高精度计算!", + "shell": "bcmath.sh", + "check": "bcmath.so" + }, + { + "name": "pcntl", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "用于信号控制!", + "shell": "pcntl.sh", + "check": "pcntl.so" + }, + { + "name": "iconv", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "编码转换!", + "shell": "iconv.sh", + "check": "iconv.so" + }, + { + "name": "fileinfo", + "versions": [ + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于FILE!", + "shell": "fileinfo.sh", + "check": "fileinfo.so" + }, + { + "name": "event", + "versions": [ + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "提供到libevent库的接口", + "shell": "event.sh", + "check": "event.so" + }, + { + "name": "exif", + "versions": [ + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "用于图像文件格式!", + "shell": "exif.sh", + "check": "exif.so" + }, + { + "name": "igbinary", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "序列化扩展!", + "shell": "igbinary.sh", + "check": "igbinary.so" + }, + { + "name": "gettext", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "国际化与本地化!", + "shell": "gettext.sh", + "check": "gettext.so" + }, + { + "name": "gd", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "通用GD库!", + "shell": "gd.sh", + "check": "gd.so" + }, + { + "name": "curl", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "通用网络", + "shell": "curl.sh", + "check": "curl.so" + }, + { + "name": "intl", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "提供国际化支持", + "shell": "intl.sh", + "check": "intl.so" + }, + { + "name": "readline", + "versions": [ + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "命令行的支持", + "shell": "readline.sh", + "check": "readline.so" + }, + { + "name": "memcache", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83" + ], + "type": "缓存器", + "msg": "强大的内容缓存器,不支持集群", + "shell": "memcache.sh", + "check": "memcache.so" + }, + { + "name": "memcached", + "versions": [ + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "强大的内容缓存器,支持集群", + "shell": "memcached.sh", + "check": "memcached.so" + }, + { + "name": "redis", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "更强大的内容缓存器,支持集群", + "shell": "redis.sh", + "check": "redis.so" + }, + { + "name": "apc", + "versions": [ + "53", + "54" + ], + "type": "缓存器", + "msg": "脚本缓存器", + "shell": "apc.sh", + "check": "apc.so" + }, + { + "name": "apcu", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81" + ], + "type": "缓存器", + "msg": "脚本缓存器", + "shell": "apcu.sh", + "check": "apcu" + }, + { + "name": "imagemagick", + "versions": [ + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "比GD更强大的图形库", + "shell": "imagemagick.sh", + "check": "imagick.so" + }, + { + "name": "xdebug", + "versions": [ + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "调试器", + "msg": "不多说,不了解的不要安装", + "shell": "xdebug.sh", + "check": "xdebug.so" + }, + { + "name": "xhprof", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "性能分析", + "msg": "不多说,不了解的不要安装!", + "shell": "xhprof.sh", + "check": "xhprof.so" + }, + { + "name": "Swoole", + "versions": [ + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84" + ], + "type": "通用扩展", + "msg": "异步、并行、高性能网络通信引擎", + "shell": "swoole.sh", + "check": "swoole.so" + }, + { + "name": "eAccelerator", + "versions": [ + "52", + "53" + ], + "type": "缓存器", + "msg": "内容缓存器", + "shell": "eaccelerator.sh", + "check": "eaccelerator.so" + }, + { + "name": "phalcon", + "versions": [ + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "框架", + "msg": "Phalcon是一个全栈PHP框架。", + "shell": "phalcon.sh", + "check": "phalcon.so" + }, + { + "name": "yaf", + "versions": [ + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83" + ], + "type": "框架", + "msg": "Yaf是一个C语言编写的PHP框架", + "shell": "yaf.sh", + "check": "yaf.so" + }, + { + "name": "yaml", + "versions": [ + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "YAML-1.1解析器和发射器", + "shell": "yaml.sh", + "check": "yaml.so" + }, + { + "name": "mongo", + "versions": [ + "53", + "54", + "55", + "56" + ], + "type": "通用扩展", + "msg": "Mongodb数据库连接驱动", + "shell": "mongo.sh", + "check": "mongo.so" + }, + { + "name": "mongodb", + "versions": [ + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "通用扩展", + "msg": "Mongodb数据库连接驱动", + "shell": "mongodb.sh", + "check": "mongodb.so" + }, + { + "name": "yac", + "versions": [ + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "缓存器", + "msg": "高性能无锁共享内存Cache", + "shell": "yac.sh", + "check": "yac.so" + }, + { + "name": "solr", + "versions": [ + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "大数据", + "msg": "SOLR全文搜索服务", + "shell": "solr.sh", + "check": "solr.so" + }, + { + "name": "seaslog", + "versions": [ + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "日志", + "msg": "SeasLog高性能日志记录", + "shell": "seaslog.sh", + "check": "seaslog.so" + }, + { + "name": "zip", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "压缩", + "msg": "压缩组件", + "shell": "zip.sh", + "check": "zip.so" + }, + { + "name": "zlib", + "versions": [ + "52", + "53", + "54", + "55", + "56", + "70", + "71", + "72", + "73", + "74", + "80", + "81", + "82", + "83", + "84", + "85" + ], + "type": "压缩", + "msg": "压缩组件", + "shell": "zlib.sh", + "check": "zlib.so" + } +] \ No newline at end of file diff --git a/plugins/phpldapadmin/conf/config.php b/plugins/phpldapadmin/conf/config.php new file mode 100644 index 000000000..fc868e835 --- /dev/null +++ b/plugins/phpldapadmin/conf/config.php @@ -0,0 +1,661 @@ +custom variable to do so. + * For example, the default for defining the language in config_default.php + * + * $this->default->appearance['language'] = array( + * 'desc'=>'Language', + * 'default'=>'auto'); + * + * to override this, use $config->custom->appearance['language'] = 'en_EN'; + * + * This file is also used to configure your LDAP server connections. + * + * You must specify at least one LDAP server there. You may add + * as many as you like. You can also specify your language, and + * many other options. + * + * NOTE: Commented out values in this file prefixed by //, represent the + * defaults that have been defined in config_default.php. + * Commented out values prefixed by #, dont reflect their default value, you can + * check config_default.php if you want to see what the default is. + * + * DONT change config_default.php, you changes will be lost by the next release + * of PLA. Instead change this file - as it will NOT be replaced by a new + * version of phpLDAPadmin. + */ + +/********************************************* + * Useful important configuration overrides * + *********************************************/ + +/* If you are asked to put PLA in debug mode, this is how you do it: */ +# $config->custom->debug['level'] = 255; +# $config->custom->debug['syslog'] = true; +# $config->custom->debug['file'] = '/tmp/pla_debug.log'; + +/* phpLDAPadmin can encrypt the content of sensitive cookies if you set this + to a big random string. */ +// $config->custom->session['blowfish'] = null; + +/* If your auth_type is http, you can override your HTTP Authentication Realm. */ +// $config->custom->session['http_realm'] = sprintf('%s %s',app_name(),'login'); + +/* The language setting. If you set this to 'auto', phpLDAPadmin will attempt + to determine your language automatically. + If PLA doesnt show (all) strings in your language, then you can do some + translation at http://translations.launchpad.net/phpldapadmin and download + the translation files, replacing those provided with PLA. + (We'll pick up the translations before making the next release too!) */ +// $config->custom->appearance['language'] = 'auto'; + +/* The temporary storage directory where we will put jpegPhoto data + This directory must be readable and writable by your web server. */ +// $config->custom->jpeg['tmpdir'] = '/tmp'; // Example for Unix systems +# $config->custom->jpeg['tmpdir'] = 'c:\\temp'; // Example for Windows systems + +/* Set this to (bool)true if you do NOT want a random salt used when + calling crypt(). Instead, use the first two letters of the user's + password. This is insecure but unfortunately needed for some older + environments. */ +# $config->custom->password['no_random_crypt_salt'] = true; + +/* If you want to restrict password available types (encryption algorithms) + Should be subset of: + array( + ''=>'clear', + 'bcrypt'=>'bcrypt', + 'blowfish'=>'blowfish', + 'crypt'=>'crypt', + 'ext_des'=>'ext_des', + 'md5'=>'md5', + 'k5key'=>'k5key', + 'md5crypt'=>'md5crypt', + 'sha'=>'sha', + 'smd5'=>'smd5', + 'ssha'=>'ssha', + 'sha256'=>'sha256', + 'ssha256'=>'ssha256', + 'sha384'=>'sha384', + 'ssha384'=>'ssha384', + 'sha512'=>'sha512', + 'ssha512'=>'ssha512', + 'sha256crypt'=>'sha256crypt', + 'sha512crypt'=>'sha512crypt', + 'argon2i'=>'argon2i', + 'argon2id'=>'argon2id', + )*/ +# $config->custom->password['available_types'] = array(''=>'clear','md5'=>'md5'); + +/* PHP script timeout control. If php runs longer than this many seconds then + PHP will stop with an Maximum Execution time error. Increase this value from + the default if queries to your LDAP server are slow. The default is either + 30 seconds or the setting of max_exection_time if this is null. */ +// $config->custom->session['timelimit'] = 30; + +/* Our local timezone + This is to make sure that when we ask the system for the current time, we + get the right local time. If this is not set, all time() calculations will + assume UTC if you have not set PHP date.timezone. */ +// $config->custom->appearance['timezone'] = null; +# $config->custom->appearance['timezone'] = 'Australia/Melbourne'; + +/********************************************* + * Commands * + *********************************************/ + +/* Command availability ; if you don't authorize a command the command + links will not be shown and the command action will not be permitted. + For better security, set also ACL in your ldap directory. */ +/* +$config->custom->commands['cmd'] = array( + 'entry_internal_attributes_show' => true, + 'entry_refresh' => true, + 'oslinks' => true, + 'switch_template' => true +); + +$config->custom->commands['script'] = array( + 'add_attr_form' => true, + 'add_oclass_form' => true, + 'add_value_form' => true, + 'collapse' => true, + 'compare' => true, + 'compare_form' => true, + 'copy' => true, + 'copy_form' => true, + 'create' => true, + 'create_confirm' => true, + 'delete' => true, + 'delete_attr' => true, + 'delete_form' => true, + 'draw_tree_node' => true, + 'expand' => true, + 'export' => true, + 'export_form' => true, + 'import' => true, + 'import_form' => true, + 'login' => true, + 'logout' => true, + 'login_form' => true, + 'mass_delete' => true, + 'mass_edit' => true, + 'mass_update' => true, + 'modify_member_form' => true, + 'monitor' => true, + 'purge_cache' => true, + 'query_engine' => true, + 'rename' => true, + 'rename_form' => true, + 'rdelete' => true, + 'refresh' => true, + 'schema' => true, + 'server_info' => true, + 'show_cache' => true, + 'template_engine' => true, + 'update_confirm' => true, + 'update' => true +); +*/ + +/********************************************* + * Appearance * + *********************************************/ + +/* If you want to choose the appearance of the tree, specify a class name which + inherits from the Tree class. */ +// $config->custom->appearance['tree'] = 'AJAXTree'; +# $config->custom->appearance['tree'] = 'HTMLTree'; + +/* Just show your custom templates. */ +// $config->custom->appearance['custom_templates_only'] = false; + +/* Disable the default template. */ +// $config->custom->appearance['disable_default_template'] = false; + +/* Hide the warnings for invalid objectClasses/attributes in templates. */ +// $config->custom->appearance['hide_template_warning'] = false; + +/* Set to true if you would like to hide header and footer parts. */ +// $config->custom->appearance['minimalMode'] = false; + +/* Configure what objects are shown in left hand tree */ +// $config->custom->appearance['tree_filter'] = '(objectclass=*)'; + +/* The height and width of the tree. If these values are not set, then + no tree scroll bars are provided. */ +// $config->custom->appearance['tree_height'] = null; +# $config->custom->appearance['tree_height'] = 600; +// $config->custom->appearance['tree_width'] = null; +# $config->custom->appearance['tree_width'] = 250; + +/* Number of tree command icons to show, 0 = show all icons on 1 row. */ +// $config->custom->appearance['tree_icons'] = 0; +# $config->custom->appearance['tree_icons'] = 4; + +/* Confirm create and update operations, allowing you to review the changes + and optionally skip attributes during the create/update operation. */ +// $config->custom->confirm['create'] = true; +// $config->custom->confirm['update'] = true; + +/* Confirm copy operations, and treat them like create operations. This allows + you to edit the attributes (thus changing any that might conflict with + uniqueness) before creating the new entry. */ +// $config->custom->confirm['copy'] = true; + +/********************************************* + * User-friendly attribute translation * + *********************************************/ + +/* Use this array to map attribute names to user friendly names. For example, if + you don't want to see "facsimileTelephoneNumber" but rather "Fax". */ +// $config->custom->appearance['friendly_attrs'] = array(); +$config->custom->appearance['friendly_attrs'] = array( + 'facsimileTelephoneNumber' => 'Fax', + 'gid' => 'Group', + 'mail' => 'Email', + 'telephoneNumber' => 'Telephone', + 'uid' => 'User Name', + 'userPassword' => 'Password' +); + +/********************************************* + * Hidden attributes * + *********************************************/ + +/* You may want to hide certain attributes from being edited. If you want to + hide attributes from the user, you should use your LDAP servers ACLs. + NOTE: The user must be able to read the hide_attrs_exempt entry to be + excluded. */ +// $config->custom->appearance['hide_attrs'] = array(); +# $config->custom->appearance['hide_attrs'] = array('objectClass'); + +/* Members of this list will be exempt from the hidden attributes. */ +// $config->custom->appearance['hide_attrs_exempt'] = null; +# $config->custom->appearance['hide_attrs_exempt'] = 'cn=PLA UnHide,ou=Groups,c=AU'; + +/********************************************* + * Read-only attributes * + *********************************************/ + +/* You may want to phpLDAPadmin to display certain attributes as read only, + meaning that users will not be presented a form for modifying those + attributes, and they will not be allowed to be modified on the "back-end" + either. You may configure this list here: + NOTE: The user must be able to read the readonly_attrs_exempt entry to be + excluded. */ +// $config->custom->appearance['readonly_attrs'] = array(); + +/* Members of this list will be exempt from the readonly attributes. */ +// $config->custom->appearance['readonly_attrs_exempt'] = null; +# $config->custom->appearance['readonly_attrs_exempt'] = 'cn=PLA ReadWrite,ou=Groups,c=AU'; + +/********************************************* + * Group attributes * + *********************************************/ + +/* Add "modify group members" link to the attribute. */ +// $config->custom->modify_member['groupattr'] = array('member','uniqueMember','memberUid','sudoUser'); + +/* Configure filter for member search. This only applies to "modify group members" feature */ +// $config->custom->modify_member['filter'] = '(objectclass=Person)'; + +/* Attribute that is added to the group member attribute. */ +// $config->custom->modify_member['attr'] = 'dn'; + +/* For Posix attributes */ +// $config->custom->modify_member['posixattr'] = 'uid'; +// $config->custom->modify_member['posixfilter'] = '(uid=*)'; +// $config->custom->modify_member['posixgroupattr'] = 'memberUid'; + +/********************************************* + * Support for attrs display order * + *********************************************/ + +/* Use this array if you want to have your attributes displayed in a specific + order. You can use default attribute names or their fridenly names. + For example, "sn" will be displayed right after "givenName". All the other + attributes that are not specified in this array will be displayed after in + alphabetical order. */ +// $config->custom->appearance['attr_display_order'] = array(); +# $config->custom->appearance['attr_display_order'] = array( +# 'givenName', +# 'sn', +# 'cn', +# 'displayName', +# 'uid', +# 'uidNumber', +# 'gidNumber', +# 'homeDirectory', +# 'mail', +# 'userPassword' +# ); + +/********************************************* + * Define your LDAP servers in this section * + *********************************************/ + +$servers = new Datastore(); + +/* $servers->NewServer('ldap_pla') must be called before each new LDAP server + declaration. */ +$servers->newServer('ldap_pla'); + +/* A convenient name that will appear in the tree viewer and throughout + phpLDAPadmin to identify this LDAP server to users. */ +$servers->setValue('server','name','My LDAP Server'); + +/* Examples: + 'ldap.example.com', + 'ldaps://ldap.example.com/', + 'ldapi://%2fusr%local%2fvar%2frun%2fldapi' + (Unix socket at /usr/local/var/run/ldap) */ +// $servers->setValue('server','host','127.0.0.1'); + +/* The port your LDAP server listens on (no quotes). 389 is standard. */ +// $servers->setValue('server','port',389); + +/* Array of base DNs of your LDAP server. Leave this blank to have phpLDAPadmin + auto-detect it for you. */ +// $servers->setValue('server','base',array('')); + +/* Five options for auth_type: + 1. 'cookie': you will login via a web form, and a client-side cookie will + store your login dn and password. + 2. 'session': same as cookie but your login dn and password are stored on the + web server in a persistent session variable. + 3. 'http': same as session but your login dn and password are retrieved via + HTTP authentication. + 4. 'config': specify your login dn and password here in this config file. No + login will be required to use phpLDAPadmin for this server. + 5. 'sasl': login will be taken from the webserver's kerberos authentication. + Currently only GSSAPI has been tested (using mod_auth_kerb). + 6. 'sasl_external': login will be taken from SASL external mechanism. + + Choose wisely to protect your authentication information appropriately for + your situation. If you choose 'cookie', your cookie contents will be + encrypted using blowfish and the secret your specify above as + session['blowfish']. */ +// $servers->setValue('login','auth_type','session'); + +/* The DN of the user for phpLDAPadmin to bind with. For anonymous binds or + 'cookie','session' or 'sasl' auth_types, LEAVE THE LOGIN_DN AND LOGIN_PASS + BLANK. If you specify a login_attr in conjunction with a cookie or session + auth_type, then you can also specify the bind_id/bind_pass here for searching + the directory for users (ie, if your LDAP server does not allow anonymous + binds. */ +// $servers->setValue('login','bind_id',''); +# $servers->setValue('login','bind_id','cn=Manager,dc=example,dc=com'); + +/* Your LDAP password. If you specified an empty bind_id above, this MUST also + be blank. */ +// $servers->setValue('login','bind_pass',''); +# $servers->setValue('login','bind_pass','secret'); + +/* Use TLS (Transport Layer Security) to connect to the LDAP server. */ +// $servers->setValue('server','tls',false); + +/* TLS Certificate Authority file (overrides ldap.conf, PHP 7.1+) */ +// $servers->setValue('server','tls_cacert',null); +# $servers->setValue('server','tls_cacert','/etc/openldap/certs/ca.crt'); + +/* TLS Certificate Authority hashed directory (overrides ldap.conf, PHP 7.1+) */ +// $servers->setValue('server','tls_cacertdir',null); +# $servers->setValue('server','tls_cacertdir','/etc/openldap/certs'); + +/* TLS Client Certificate file (PHP 7.1+) */ +// $servers->setValue('server','tls_cert',null); +# $servers->setValue('server','tls_cert','/etc/pki/tls/certs/ldap_user.crt'); + +/* TLS Client Certificate Key file (PHP 7.1+) */ +// $servers->setValue('server','tls_key',null); +# $servers->setValue('server','tls_key','/etc/pki/tls/private/ldap_user.key'); + +/************************************ + * SASL Authentication * + ************************************/ + +/* Enable SASL authentication LDAP SASL authentication requires PHP 5.x + configured with --with-ldap-sasl=DIR. If this option is disabled (ie, set to + false), then all other sasl options are ignored. */ +# $servers->setValue('login','auth_type','sasl'); + +/* SASL GSSAPI auth mechanism (requires auth_type of sasl) */ +// $servers->setValue('sasl','mech','GSSAPI'); + +/* SASL PLAIN support... this mech converts simple binds to SASL + PLAIN binds using any auth_type (or other bind_id/pass) as credentials. + NOTE: auth_type must be simple auth compatible (ie not sasl) */ +# $servers->setValue('sasl','mech','PLAIN'); + +/* SASL EXTERNAL support... really a different auth_type */ +# $servers->setValue('login','auth_type','sasl_external'); + +/* SASL authentication realm name */ +// $servers->setValue('sasl','realm',''); +# $servers->setValue('sasl','realm','EXAMPLE.COM'); + +/* SASL authorization ID name + If this option is undefined, authorization id will be computed from bind DN, + using authz_id_regex and authz_id_replacement. */ +// $servers->setValue('sasl','authz_id', null); + +/* SASL authorization id regex and replacement + When authz_id property is not set (default), phpLDAPAdmin will try to + figure out authorization id by itself from bind distinguished name (DN). + + This procedure is done by calling preg_replace() php function in the + following way: + + $authz_id = preg_replace($sasl_authz_id_regex,$sasl_authz_id_replacement, + $bind_dn); + + For info about pcre regexes, see: + - pcre(3), perlre(3) + - http://www.php.net/preg_replace */ +// $servers->setValue('sasl','authz_id_regex',null); +// $servers->setValue('sasl','authz_id_replacement',null); +# $servers->setValue('sasl','authz_id_regex','/^uid=([^,]+)(.+)/i'); +# $servers->setValue('sasl','authz_id_replacement','$1'); + +/* SASL auth security props. + See http://beepcore-tcl.sourceforge.net/tclsasl.html#anchor5 for explanation. */ +// $servers->setValue('sasl','props',null); + +/* Default password hashing algorithm. One of md5, ssha, sha, md5crpyt, smd5, + blowfish, crypt or leave blank for now default algorithm. */ +// $servers->setValue('appearance','pla_password_hash','md5'); + +/* If you specified 'cookie' or 'session' as the auth_type above, you can + optionally specify here an attribute to use when logging in. If you enter + 'uid' and login as 'dsmith', phpLDAPadmin will search for (uid=dsmith) + and log in as that user. + Leave blank or specify 'dn' to use full DN for logging in. Note also that if + your LDAP server requires you to login to perform searches, you can enter the + DN to use when searching in 'bind_id' and 'bind_pass' above. */ +// $servers->setValue('login','attr','dn'); + +/* Base DNs to used for logins. If this value is not set, then the LDAP server + Base DNs are used. */ +// $servers->setValue('login','base',array()); + +/* If 'login,attr' is used above such that phpLDAPadmin will search for your DN + at login, you may restrict the search to a specific objectClasses. EG, set this + to array('posixAccount') or array('inetOrgPerson',..), depending upon your + setup. */ +// $servers->setValue('login','class',array()); + +/* If login_attr was set to 'dn', it is possible to specify a template string to + build the DN from. Use '%s' where user input should be inserted. A user may + still enter the complete DN. In this case the template will not be used. */ +// $servers->setValue('login','bind_dn_template',null); +# $servers->setValue('login','bind_dn_template','cn=%s,ou=people,dc=example,dc=com'); + +/* If you specified something different from 'dn', for example 'uid', as the + login_attr above, you can optionally specify here to fall back to + authentication with dn. + This is useful, when users should be able to log in with their uid, but + the ldap administrator wants to log in with his root-dn, that does not + necessarily have the uid attribute. + When using this feature, login_class is ignored. */ +// $servers->setValue('login','fallback_dn',false); + +/* Specify true If you want phpLDAPadmin to not display or permit any + modification to the LDAP server. */ +// $servers->setValue('server','read_only',false); + +/* Specify false if you do not want phpLDAPadmin to draw the 'Create new' links + in the tree viewer. */ +// $servers->setValue('appearance','show_create',true); + +/* Set to true if you would like to initially open the first level of each tree. */ +// $servers->setValue('appearance','open_tree',false); + +/* Set to true to display authorization ID in place of login dn (PHP 7.2+) */ +// $servers->setValue('appearance','show_authz',false); + +/* This feature allows phpLDAPadmin to automatically determine the next + available uidNumber for a new entry. */ +// $servers->setValue('auto_number','enable',true); + +/* The mechanism to use when finding the next available uidNumber. Two possible + values: 'uidpool' or 'search'. + The 'uidpool' mechanism uses an existing uidPool entry in your LDAP server to + blindly lookup the next available uidNumber. The 'search' mechanism searches + for entries with a uidNumber value and finds the first available uidNumber + (slower). */ +// $servers->setValue('auto_number','mechanism','search'); + +/* The DN of the search base when the 'search' mechanism is used above. */ +# $servers->setValue('auto_number','search_base','ou=People,dc=example,dc=com'); + +/* The minimum number to use when searching for the next available number + (only when 'search' is used for auto_number. */ +// $servers->setValue('auto_number','min',array('uidNumber'=>1000,'gidNumber'=>500)); + +/* If you set this, then phpldapadmin will bind to LDAP with this user ID when + searching for the uidnumber. The idea is, this user id would have full + (readonly) access to uidnumber in your ldap directory (the logged in user + may not), so that you can be guaranteed to get a unique uidnumber for your + directory. */ +// $servers->setValue('auto_number','dn',null); + +/* The password for the dn above. */ +// $servers->setValue('auto_number','pass',null); + +/* Enable anonymous bind login. */ +// $servers->setValue('login','anon_bind',true); + +/* Use customized page with prefix when available. */ +# $servers->setValue('custom','pages_prefix','custom_'); + +/* If you set this, then only these DNs are allowed to log in. This array can + contain individual users, groups or ldap search filter(s). Keep in mind that + the user has not authenticated yet, so this will be an anonymous search to + the LDAP server, so make your ACLs allow these searches to return results! */ +# $servers->setValue('login','allowed_dns',array( +# 'uid=stran,ou=People,dc=example,dc=com', +# '(&(gidNumber=811)(objectClass=groupOfNames))', +# '(|(uidNumber=200)(uidNumber=201))', +# 'cn=callcenter,ou=Group,dc=example,dc=com')); + +/* Set this if you dont want this LDAP server to show in the tree */ +// $servers->setValue('server','visible',true); + +/* Set this if you want to hide the base DNs that dont exist instead of + displaying the message "The base entry doesnt exist, create it?" +// $servers->setValue('server','hide_noaccess_base',false); +# $servers->setValue('server','hide_noaccess_base',true); + +/* This is the time out value in minutes for the server. After as many minutes + of inactivity you will be automatically logged out. If not set, the default + value will be ( session_cache_expire()-1 ) */ +# $servers->setValue('login','timeout',30); + +/* Set this if you want phpldapadmin to perform rename operation on entry which + has children. Certain servers are known to allow it, certain are not. */ +// $servers->setValue('server','branch_rename',false); + +/* If you set this, then phpldapadmin will show these attributes as + internal attributes, even if they are not defined in your schema. */ +// $servers->setValue('server','custom_sys_attrs',array('')); +# $servers->setValue('server','custom_sys_attrs',array('passwordExpirationTime','passwordAllowChangeTime')); + +/* If you set this, then phpldapadmin will show these attributes on + objects, even if they are not defined in your schema. */ +// $servers->setValue('server','custom_attrs',array('')); +# $servers->setValue('server','custom_attrs',array('nsRoleDN','nsRole','nsAccountLock')); + +/* These attributes will be forced to MAY attributes and become option in the + templates. If they are not defined in the templates, then they wont appear + as per normal template processing. You may want to do this because your LDAP + server may automatically calculate a default value. + In Fedora Directory Server using the DNA Plugin one could ignore uidNumber, + gidNumber and sambaSID. */ +// $servers->setValue('server','force_may',array('')); +# $servers->setValue('server','force_may',array('uidNumber','gidNumber','sambaSID')); + +/********************************************* + * Unique attributes * + *********************************************/ + +/* You may want phpLDAPadmin to enforce some attributes to have unique values + (ie: not belong to other entries in your tree. This (together with + 'unique','dn' and 'unique','pass' option will not let updates to + occur with other attributes have the same value. */ +# $servers->setValue('unique','attrs',array('mail','uid','uidNumber')); + +/* If you set this, then phpldapadmin will bind to LDAP with this user ID when + searching for attribute uniqueness. The idea is, this user id would have full + (readonly) access to your ldap directory (the logged in user may not), so + that you can be guaranteed to get a unique uidnumber for your directory. */ +// $servers->setValue('unique','dn',null); + +/* The password for the dn above. */ +// $servers->setValue('unique','pass',null); + +/************************************************************************** + * If you want to configure additional LDAP servers, do so below. * + * Remove the commented lines and use this section as a template for all * + * your other LDAP servers. * + **************************************************************************/ + +/* +$servers->newServer('ldap_pla'); +$servers->setValue('server','name','LDAP Server'); +$servers->setValue('server','host','127.0.0.1'); +$servers->setValue('server','port',389); +$servers->setValue('server','base',array('')); +$servers->setValue('login','auth_type','cookie'); +$servers->setValue('login','bind_id',''); +$servers->setValue('login','bind_pass',''); +$servers->setValue('server','tls',false); + +# SASL auth +$servers->setValue('login','auth_type','sasl'); +$servers->setValue('sasl','mech','GSSAPI'); +$servers->setValue('sasl','realm','EXAMPLE.COM'); +$servers->setValue('sasl','authz_id',null); +$servers->setValue('sasl','authz_id_regex','/^uid=([^,]+)(.+)/i'); +$servers->setValue('sasl','authz_id_replacement','$1'); +$servers->setValue('sasl','props',null); + +$servers->setValue('appearance','pla_password_hash','md5'); +$servers->setValue('login','attr','dn'); +$servers->setValue('login','fallback_dn',false); +$servers->setValue('login','class',null); +$servers->setValue('server','read_only',false); +$servers->setValue('appearance','show_create',true); + +$servers->setValue('auto_number','enable',true); +$servers->setValue('auto_number','mechanism','search'); +$servers->setValue('auto_number','search_base',null); +$servers->setValue('auto_number','min',array('uidNumber'=>1000,'gidNumber'=>500)); +$servers->setValue('auto_number','dn',null); +$servers->setValue('auto_number','pass',null); + +$servers->setValue('login','anon_bind',true); +$servers->setValue('custom','pages_prefix','custom_'); +$servers->setValue('unique','attrs',array('mail','uid','uidNumber')); +$servers->setValue('unique','dn',null); +$servers->setValue('unique','pass',null); + +$servers->setValue('server','visible',true); +$servers->setValue('login','timeout',30); +$servers->setValue('server','branch_rename',false); +$servers->setValue('server','custom_sys_attrs',array('passwordExpirationTime','passwordAllowChangeTime')); +$servers->setValue('server','custom_attrs',array('nsRoleDN','nsRole','nsAccountLock')); +$servers->setValue('server','force_may',array('uidNumber','gidNumber','sambaSID')); +*/ + +$servers->setValue('server','host','127.0.0.1'); +$servers->setValue('server','port',389); +$servers->setValue('login','auth_type','session'); +$servers->setValue('server','base',array('dc=local,dc=com')); +$servers->setValue('login','bind_id','cn=admin,dc=local,dc=com'); + +/*********************************************************************************** + * If you want to configure Google reCAPTCHA on autentication form, do so below. * + * Remove the commented lines and use this section as a template for all * + * reCAPTCHA v2 Generate on https://www.google.com/recaptcha/ * + * * + * IMPORTANT: Select reCAPTCHA v2 on Type of reCAPTCHA * + ***********************************************************************************/ + + +$config->custom->session['reCAPTCHA-enable'] = false; +$config->custom->session['reCAPTCHA-key-site'] = ''; +$config->custom->session['reCAPTCHA-key-server'] = ''; + +?> diff --git a/plugins/phpldapadmin/conf/phpldapadmin.conf b/plugins/phpldapadmin/conf/phpldapadmin.conf new file mode 100755 index 000000000..046fb1967 --- /dev/null +++ b/plugins/phpldapadmin/conf/phpldapadmin.conf @@ -0,0 +1,38 @@ +server +{ + listen 888; + server_name 127.0.0.1; + index index.html index.htm index.php; + root {$SERVER_PATH}/phpldapadmin; + + #error_page 404 /404.html; + include {$PHP_CONF_PATH}/enable-php-{$PHP_VER}.conf; + + #AUTH_START + auth_basic "Authorization"; + auth_basic_user_file {$SERVER_PATH}/phpldapadmin/pma.pass; + #AUTH_END + + location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ + { + expires 30d; + } + + location ~ .*\.(js|css)?$ + { + expires 12h; + } + + location ~ /.*\.(log|pass|json|pl)$ { + deny all; + } + + + location ~ /\. + { + deny all; + } + + access_log {$SERVER_PATH}/phpldapadmin/access.log; + error_log {$SERVER_PATH}/phpldapadmin/error.log; +} \ No newline at end of file diff --git a/plugins/phpldapadmin/ico.png b/plugins/phpldapadmin/ico.png new file mode 100644 index 000000000..8d3130d8b Binary files /dev/null and b/plugins/phpldapadmin/ico.png differ diff --git a/plugins/phpldapadmin/index.html b/plugins/phpldapadmin/index.html new file mode 100755 index 000000000..01cf3286e --- /dev/null +++ b/plugins/phpldapadmin/index.html @@ -0,0 +1,24 @@ +
                                +
                                +
                                +

                                服务

                                +

                                重写模版

                                +

                                主页

                                +

                                PHP版本

                                +

                                安全设置

                                +

                                访问日志

                                +

                                错误日志

                                +

                                配置

                                +
                                +
                                +
                                +
                                +
                                + +
                                + \ No newline at end of file diff --git a/plugins/phpldapadmin/index.py b/plugins/phpldapadmin/index.py new file mode 100755 index 000000000..802971063 --- /dev/null +++ b/plugins/phpldapadmin/index.py @@ -0,0 +1,473 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import thisdb +from utils.site import sites as MwSites + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'phpldapadmin' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + return mw.getServerDir() + '/web_conf/nginx/vhost/phpldapadmin.conf' + + +def getConfInc(): + return getServerDir() + "/" + getCfg()['path'] + '/config/config.php' + + +def getPort(): + file = getConf() + content = mw.readFile(file) + rep = r'listen\s*(.*);' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getHomePage(): + try: + port = getPort() + ip = '127.0.0.1' + if not mw.isAppleSystem(): + ip = mw.getLocalIp() + + cfg = getCfg() + auth = cfg['username']+':'+cfg['password'] + rand_path = cfg['path'] + url = 'http://' + auth + '@' + ip + ':' + port + '/' + rand_path + '/index.php' + return mw.returnJson(True, 'OK', url) + except Exception as e: + return mw.returnJson(False, '插件未启动!') + + +def getPhpVer(expect=74): + php_vers = MwSites.instance().getPhpVersion() + v = php_vers['data'] + is_find = False + for i in range(len(v)): + t = str(v[i]['version']) + if (t == expect): + is_find = True + return str(t) + expect_str = str(expect) + new_ex = expect_str[0:1]+"."+expect_str[1:2] + if t.find(new_ex) > -1: + is_find = True + return str(t) + if not is_find: + if len(v) > 1: + return v[1]['version'] + return v[0]['version'] + return str(expect) + + +def getCachePhpVer(): + cacheFile = getServerDir() + '/php.pl' + v = '' + if os.path.exists(cacheFile): + v = mw.readFile(cacheFile) + else: + v = getPhpVer() + mw.writeFile(cacheFile, v) + return v + + +def contentReplace(content): + service_path = mw.getServerDir() + php_ver = getCachePhpVer() + tmp = mw.execShell('cat /dev/urandom | head -n 32 | md5sum | head -c 16') + blowfish_secret = tmp[0].strip() + # print php_ver + php_conf_dir = mw.getServerDir() + '/web_conf/php/conf' + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PHP_CONF_PATH}', php_conf_dir) + content = content.replace('{$PHP_VER}', php_ver) + content = content.replace('{$BLOWFISH_SECRET}', blowfish_secret) + + cfg = getCfg() + content = content.replace('{$PMA_PATH}', cfg['path']) + + port = cfg["port"] + rep = r'listen\s*(.*);' + content = re.sub(rep, "listen " + port + ';', content) + return content + + +def initCfg(): + cfg = getServerDir() + "/cfg.json" + if not os.path.exists(cfg): + data = {} + data['port'] = '988' + data['path'] = '' + data['username'] = 'admin' + data['password'] = 'admin' + mw.writeFile(cfg, json.dumps(data)) + + +def setCfg(key, val): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + data = json.loads(data) + data[key] = val + mw.writeFile(cfg, json.dumps(data)) + + +def getCfg(): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + data = json.loads(data) + return data + + +def returnCfg(): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + return data + + +def status(): + conf = getConf() + conf_inc = getServerDir() + "/" + getCfg()["path"] + '/config/config.php' + # 两个文件都在,才算启动成功 + if os.path.exists(conf) and os.path.exists(conf_inc): + return 'start' + return 'stop' + + +def __release_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'phpLDAPadmin默认端口', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def __delete_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().delAcceptPortCmd(port, 'tcp') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def openPort(): + conf = getCfg() + port = conf['port'] + for i in [port]: + __release_port(i) + return True + + +def delPort(): + conf = getCfg() + port = conf['port'] + for i in [port]: + __delete_port(i) + return True + + +def start(): + initCfg() + openPort() + + pma_dir = getServerDir() + "/phpldapadmin" + if os.path.exists(pma_dir): + rand_str = mw.getRandomString(6) + rand_str = rand_str.lower() + pma_dir_dst = pma_dir + "_" + rand_str + mw.execShell("mv " + pma_dir + " " + pma_dir_dst) + setCfg('path', 'phpldapadmin_' + rand_str) + + file_tpl = getPluginDir() + '/conf/phpldapadmin.conf' + file_run = getConf() + if not os.path.exists(file_run): + centent = mw.readFile(file_tpl) + centent = contentReplace(centent) + mw.writeFile(file_run, centent) + + pma_path = getServerDir() + '/pma.pass' + if not os.path.exists(pma_path): + username = mw.getRandomString(8) + password = mw.getRandomString(10) + pass_cmd = username + ':' + mw.hasPwd(password) + setCfg('username', username) + setCfg('password', password) + mw.writeFile(pma_path, pass_cmd) + + tmp = getServerDir() + "/" + getCfg()["path"] + '/tmp' + if not os.path.exists(tmp): + os.mkdir(tmp) + mw.execShell("chown -R www:www " + tmp) + + conf_run = getServerDir() + "/" + getCfg()["path"] + '/config/config.php' + if not os.path.exists(conf_run): + conf_tpl = getPluginDir() + '/conf/config.php' + centent = mw.readFile(conf_tpl) + centent = contentReplace(centent) + mw.writeFile(conf_run, centent) + + log_a = accessLog() + log_e = errorLog() + + for i in [log_a, log_e]: + if os.path.exists(i): + cmd = "echo '' > " + i + mw.execShell(cmd) + + mw.restartWeb() + return 'ok' + + +def stop(): + conf = getConf() + if os.path.exists(conf): + os.remove(conf) + delPort() + mw.restartWeb() + return 'ok' + + +def restart(): + return start() + + +def reload(): + file_tpl = getPluginDir() + '/conf/phpldapadmin.conf' + file_run = getConf() + if os.path.exists(file_run): + centent = mw.readFile(file_tpl) + centent = contentReplace(centent) + mw.writeFile(file_run, centent) + return start() + + +def setPhpVer(): + args = getArgs() + + if not 'phpver' in args: + return 'phpver missing' + + cacheFile = getServerDir() + '/php.pl' + mw.writeFile(cacheFile, args['phpver']) + + file_tpl = getPluginDir() + '/conf/phpldapadmin.conf' + file_run = getConf() + + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_run, content) + + mw.restartWeb() + return 'ok' + + +def getSetPhpVer(): + cacheFile = getServerDir() + '/php.pl' + if os.path.exists(cacheFile): + return mw.readFile(cacheFile).strip() + return '' + + +def getPmaOption(): + data = getCfg() + return mw.returnJson(True, 'ok', data) + + +def getPmaPort(): + try: + port = getPort() + return mw.returnJson(True, 'OK', port) + except Exception as e: + # print(e) + return mw.returnJson(False, '插件未启动!') + + +def setPmaPort(): + args = getArgs() + data = checkArgs(args, ['port']) + if not data[0]: + return data[1] + + port = args['port'] + if port == '80': + return mw.returnJson(False, '80端不能使用!') + + file = getConf() + if not os.path.exists(file): + return mw.returnJson(False, '插件未启动!') + content = mw.readFile(file) + rep = r'listen\s*(.*);' + content = re.sub(rep, "listen " + port + ';', content) + mw.writeFile(file, content) + + setCfg("port", port) + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaUsername(): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + + username = args['username'] + setCfg('username', username) + + cfg = getCfg() + pma_path = getServerDir() + '/pma.pass' + username = mw.getRandomString(10) + pass_cmd = cfg['username'] + ':' + mw.hasPwd(cfg['password']) + mw.writeFile(pma_path, pass_cmd) + + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaPassword(): + args = getArgs() + data = checkArgs(args, ['password']) + if not data[0]: + return data[1] + + password = args['password'] + setCfg('password', password) + + cfg = getCfg() + pma_path = getServerDir() + '/pma.pass' + username = mw.getRandomString(10) + pass_cmd = cfg['username'] + ':' + mw.hasPwd(cfg['password']) + mw.writeFile(pma_path, pass_cmd) + + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaPath(): + args = getArgs() + data = checkArgs(args, ['path']) + if not data[0]: + return data[1] + + path = args['path'] + + if len(path) < 5: + return mw.returnJson(False, '不能小于5位!') + + old_path = getServerDir() + "/" + getCfg()['path'] + new_path = getServerDir() + "/" + path + + mw.execShell("mv " + old_path + " " + new_path) + setCfg('path', path) + return mw.returnJson(True, '修改成功!') + + +def accessLog(): + return getServerDir() + '/access.log' + + +def errorLog(): + return getServerDir() + '/error.log' + + +def installVersion(): + return mw.readFile(getServerDir() + '/version.pl') + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'conf': + print(getConf()) + elif func == 'version': + print(installVersion()) + elif func == 'get_cfg': + print(returnCfg()) + elif func == 'config_inc': + print(getConfInc()) + elif func == 'get_home_page': + print(getHomePage()) + elif func == 'set_php_ver': + print(setPhpVer()) + elif func == 'get_set_php_ver': + print(getSetPhpVer()) + elif func == 'get_pma_port': + print(getPmaPort()) + elif func == 'set_pma_port': + print(setPmaPort()) + elif func == 'get_pma_option': + print(getPmaOption()) + elif func == 'set_pma_username': + print(setPmaUsername()) + elif func == 'set_pma_password': + print(setPmaPassword()) + elif func == 'set_pma_path': + print(setPmaPath()) + elif func == 'access_log': + print(accessLog()) + elif func == 'error_log': + print(errorLog()) + else: + print('error') diff --git a/plugins/phpldapadmin/info.json b/plugins/phpldapadmin/info.json new file mode 100755 index 000000000..119efd016 --- /dev/null +++ b/plugins/phpldapadmin/info.json @@ -0,0 +1,15 @@ +{ + "title":"phpLDAPadmin", + "tip":"soft", + "name":"phpldapadmin", + "type":"运行环境", + "ps":"LDAP管理工具", + "versions":["1.2.6.7"], + "shell":"install.sh", + "checks":"server/phpldapadmin", + "path": "server/phpldapadmin", + "author":"leenooks", + "home":"https://github.com/leenooks/phpLDAPadmin", + "date":"2025-1-28", + "pid": "2" +} \ No newline at end of file diff --git a/plugins/phpldapadmin/install.sh b/plugins/phpldapadmin/install.sh new file mode 100755 index 000000000..40a2b0823 --- /dev/null +++ b/plugins/phpldapadmin/install.sh @@ -0,0 +1,90 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/phpldapadmin && bash install.sh install 1.2.6.7 +# cd /www/server/mdserver-web && python3 plugins/phpldapadmin/index.py start + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +if [ "$sys_os" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +sysName=`uname` +echo "use system: ${sysName}" + +if [ "${sysName}" == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' +elif grep -Eqi "Raspbian" /etc/issue || grep -Eq "Raspbian" /etc/*-release; then + OSNAME='raspbian' +else + OSNAME='unknow' +fi + +Install_App() +{ + if [ -d $serverPath/phpldapadmin ];then + exit 0 + fi + + mkdir -p ${serverPath}/phpldapadmin + mkdir -p ${serverPath}/source/phpldapadmin + echo "${1}" > ${serverPath}/phpldapadmin/version.pl + + VER=$1 + + # https://github.com/leenooks/phpLDAPadmin/archive/refs/tags/1.2.6.7.tar.gz + FDIR=phpLDAPadmin-${VER} + FILE=${VER}.tar.gz + DOWNLOAD=https://github.com/leenooks/phpLDAPadmin/archive/refs/tags/${FILE} + + + if [ ! -f $serverPath/source/phpmyadmin/$FILE ];then + wget --no-check-certificate -O $serverPath/source/phpldapadmin/$FILE $DOWNLOAD + fi + + if [ ! -d $serverPath/source/phpldapadmin/$FDIR ];then + cd $serverPath/source/phpldapadmin && tar zxvf $FILE + fi + + cp -r $serverPath/source/phpldapadmin/$FDIR $serverPath/phpldapadmin/ + cd $serverPath/phpldapadmin/ && mv $FDIR phpldapadmin + # rm -rf $serverPath/source/phpldapadmin/$FDIR + + cd ${rootPath} && python3 ${rootPath}/plugins/phpldapadmin/index.py start + echo '安装完成' + +} + +Uninstall_App() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/phpldapadmin/index.py stop + + rm -rf ${serverPath}/phpldapadmin + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App $2 +else + Uninstall_App $2 +fi diff --git a/plugins/phpldapadmin/js/phpldapadmin.js b/plugins/phpldapadmin/js/phpldapadmin.js new file mode 100755 index 000000000..030b55243 --- /dev/null +++ b/plugins/phpldapadmin/js/phpldapadmin.js @@ -0,0 +1,164 @@ +function str2Obj(str){ + var data = {}; + kv = str.split('&'); + for(i in kv){ + v = kv[i].split('='); + data[v[0]] = v[1]; + } + return data; +} + +function pmaPost(method,args,callback){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'phpldapadmin', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function pmaAsyncPost(method,args){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'phpldapadmin', func:method, args:_args}); +} + +function homePage(){ + pmaPost('get_home_page', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + var con = ''; + $(".soft-man-con").html(con); + }); +} + +//phpmyadmin切换php版本 +function phpVer(version) { + + var _version = pmaAsyncPost('get_set_php_ver','') + if (_version['data'] != ''){ + version = _version['data']; + } + + $.post('/site/get_php_version', function(data) { + var rdata = data['data']; + // console.log(rdata); + var body = "
                                PHP版本
                                '; + $(".soft-man-con").html(body); + },'json'); +} + +function phpVerChange(type, msg) { + var phpver = $("#phpver").val(); + pmaPost('set_php_ver', 'phpver='+phpver, function(data){ + if ( data.data == 'ok' ){ + layer.msg('设置成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg('设置失败!',{icon:2,time:2000,shade: [0.3, '#000']}); + } + }); +} + + +//phpmyadmin安全设置 +function safeConf() { + pmaPost('get_pma_option', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + var cfg = rdata.data; + var con = '
                                \ + 访问端口\ + \ + \ +
                                \ +
                                \ + 用户名\ + \ + \ +
                                \ +
                                \ + 密码\ + \ + \ +
                                \ +
                                \ +
                                \ + 路径名\ + \ + \ +
                                '; + $(".soft-man-con").html(con); + }); +} + +function setPmaUsername(){ + var username = $("input[name=username]").val(); + pmaPost('set_pma_username',{'username':username}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function setPmaPassword(){ + var password = $("input[name=password]").val(); + pmaPost('set_pma_password',{'password':password}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function setPmaPath(){ + var path = $("input[name=path]").val(); + pmaPost('set_pma_path',{'path':path}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +//修改phpmyadmin端口 +function setPamPort() { + var pmport = $("#pmport").val(); + if (pmport < 80 || pmport > 65535) { + layer.msg('端口范围不合法!', { icon: 2 }); + return; + } + var data = 'port=' + pmport; + + pmaPost('set_pma_port',data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} \ No newline at end of file diff --git a/plugins/phpmyadmin/conf/config.inc.php b/plugins/phpmyadmin/conf/config.inc.php new file mode 100644 index 000000000..ab6a8250f --- /dev/null +++ b/plugins/phpmyadmin/conf/config.inc.php @@ -0,0 +1,16 @@ + diff --git a/plugins/phpmyadmin/conf/phpmyadmin.conf b/plugins/phpmyadmin/conf/phpmyadmin.conf new file mode 100755 index 000000000..01d734fae --- /dev/null +++ b/plugins/phpmyadmin/conf/phpmyadmin.conf @@ -0,0 +1,38 @@ +server +{ + listen 888; + server_name 127.0.0.1; + index index.html index.htm index.php; + root {$SERVER_PATH}/phpmyadmin; + + #error_page 404 /404.html; + include {$PHP_CONF_PATH}/enable-php-{$PHP_VER}.conf; + + #AUTH_START + auth_basic "Authorization"; + auth_basic_user_file {$SERVER_PATH}/phpmyadmin/pma.pass; + #AUTH_END + + location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ + { + expires 30d; + } + + location ~ .*\.(js|css)?$ + { + expires 12h; + } + + location ~ /.*\.(log|pass|json|pl)$ { + deny all; + } + + + location ~ /\. + { + deny all; + } + + access_log {$SERVER_PATH}/phpmyadmin/access.log; + error_log {$SERVER_PATH}/phpmyadmin/error.log; +} \ No newline at end of file diff --git a/plugins/phpmyadmin/ico.png b/plugins/phpmyadmin/ico.png new file mode 100755 index 000000000..b2700e7a1 Binary files /dev/null and b/plugins/phpmyadmin/ico.png differ diff --git a/plugins/phpmyadmin/index.html b/plugins/phpmyadmin/index.html new file mode 100755 index 000000000..89c6440a9 --- /dev/null +++ b/plugins/phpmyadmin/index.html @@ -0,0 +1,24 @@ +
                                +
                                +
                                +

                                服务

                                +

                                重写模版

                                +

                                主页

                                +

                                PHP版本

                                +

                                安全设置

                                +

                                访问日志

                                +

                                错误日志

                                +

                                配置

                                +
                                +
                                +
                                +
                                +
                                + +
                                + \ No newline at end of file diff --git a/plugins/phpmyadmin/index.py b/plugins/phpmyadmin/index.py new file mode 100755 index 000000000..72c0de66c --- /dev/null +++ b/plugins/phpmyadmin/index.py @@ -0,0 +1,554 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import thisdb +from utils.site import sites as MwSites + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'phpmyadmin' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + return mw.getServerDir() + '/web_conf/nginx/vhost/phpmyadmin.conf' + + +def getConfInc(): + return getServerDir() + "/" + getCfg()['path'] + '/config.inc.php' + + +def getPort(): + file = getConf() + content = mw.readFile(file) + rep = r'listen\s*(.*);' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getHomePage(): + try: + port = getPort() + ip = '127.0.0.1' + if not mw.isAppleSystem(): + ip = mw.getLocalIp() + + cfg = getCfg() + auth = cfg['username']+':'+cfg['password'] + rand_path = cfg['path'] + url = 'http://' + auth + '@' + ip + ':' + port + '/' + rand_path + '/index.php' + return mw.returnJson(True, 'OK', url) + except Exception as e: + return mw.returnJson(False, '插件未启动!') + + +def getPhpVer(expect=55): + php_vers = MwSites.instance().getPhpVersion() + v = php_vers['data'] + is_find = False + for i in range(len(v)): + t = str(v[i]['version']) + if (t == expect): + is_find = True + return str(t) + expect_str = str(expect) + new_ex = expect_str[0:1]+"."+expect_str[1:2] + if t.find(new_ex) > -1: + is_find = True + return str(t) + if not is_find: + if len(v) > 1: + return v[1]['version'] + return v[0]['version'] + return str(expect) + + +def getCachePhpVer(): + cacheFile = getServerDir() + '/php.pl' + v = '' + if os.path.exists(cacheFile): + v = mw.readFile(cacheFile) + else: + v = getPhpVer() + mw.writeFile(cacheFile, v) + return v + + +def contentReplace(content): + service_path = mw.getServerDir() + php_ver = getCachePhpVer() + tmp = mw.execShell( + 'cat /dev/urandom | head -n 32 | md5sum | head -c 16') + blowfish_secret = tmp[0].strip() + # print php_ver + php_conf_dir = mw.getServerDir() + '/web_conf/php/conf' + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PHP_CONF_PATH}', php_conf_dir) + content = content.replace('{$PHP_VER}', php_ver) + content = content.replace('{$BLOWFISH_SECRET}', blowfish_secret) + + cfg = getCfg() + + if cfg['choose'] == "mysql": + content = content.replace('{$CHOOSE_DB}', 'mysql') + content = content.replace('{$CHOOSE_DB_DIR}', 'mysql') + elif cfg['choose'] == "mysql-community": + content = content.replace('{$CHOOSE_DB}', 'mysql-community') + content = content.replace('{$CHOOSE_DB_DIR}', 'mysql-community') + elif cfg['choose'] == "mysql-apt": + content = content.replace('{$CHOOSE_DB}', 'mysql') + content = content.replace('{$CHOOSE_DB_DIR}', 'mysql-apt') + elif cfg['choose'] == "mysql-yum": + content = content.replace('{$CHOOSE_DB}', 'mysql') + content = content.replace('{$CHOOSE_DB_DIR}', 'mysql-yum') + else: + content = content.replace('{$CHOOSE_DB}', 'MariaDB') + content = content.replace('{$CHOOSE_DB_DIR}', 'mariadb') + + content = content.replace('{$PMA_PATH}', cfg['path']) + + port = cfg["port"] + rep = r'listen\s*(.*);' + content = re.sub(rep, "listen " + port + ';', content) + return content + + +def initCfg(): + cfg = getServerDir() + "/cfg.json" + if not os.path.exists(cfg): + data = {} + data['port'] = '888' + data['choose'] = 'mysql' + data['path'] = '' + data['username'] = 'admin' + data['password'] = 'admin' + mw.writeFile(cfg, json.dumps(data)) + + +def setCfg(key, val): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + data = json.loads(data) + data[key] = val + mw.writeFile(cfg, json.dumps(data)) + + +def getCfg(): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + data = json.loads(data) + return data + + +def returnCfg(): + cfg = getServerDir() + "/cfg.json" + data = mw.readFile(cfg) + return data + + +def status(): + conf = getConf() + conf_inc = getServerDir() + "/" + getCfg()["path"] + '/config.inc.php' + # 两个文件都在,才算启动成功 + if os.path.exists(conf) and os.path.exists(conf_inc): + return 'start' + return 'stop' + + +def __release_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'phpMyAdmin默认端口', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def __delete_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().delAcceptPortCmd(port, 'tcp') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def openPort(): + conf = getCfg() + port = conf['port'] + for i in [port]: + __release_port(i) + return True + + +def delPort(): + conf = getCfg() + port = conf['port'] + for i in [port]: + __delete_port(i) + return True + + +def start(): + initCfg() + openPort() + + pma_dir = getServerDir() + "/phpmyadmin" + if os.path.exists(pma_dir): + rand_str = mw.getRandomString(6) + rand_str = rand_str.lower() + pma_dir_dst = pma_dir + "_" + rand_str + mw.execShell("mv " + pma_dir + " " + pma_dir_dst) + setCfg('path', 'phpmyadmin_' + rand_str) + + file_tpl = getPluginDir() + '/conf/phpmyadmin.conf' + file_run = getConf() + if not os.path.exists(file_run): + centent = mw.readFile(file_tpl) + centent = contentReplace(centent) + mw.writeFile(file_run, centent) + + pma_path = getServerDir() + '/pma.pass' + if not os.path.exists(pma_path): + username = mw.getRandomString(8) + password = mw.getRandomString(10) + pass_cmd = username + ':' + mw.hasPwd(password) + setCfg('username', username) + setCfg('password', password) + mw.writeFile(pma_path, pass_cmd) + + tmp = getServerDir() + "/" + getCfg()["path"] + '/tmp' + if not os.path.exists(tmp): + os.mkdir(tmp) + mw.execShell("chown -R www:www " + tmp) + + conf_run = getServerDir() + "/" + getCfg()["path"] + '/config.inc.php' + if not os.path.exists(conf_run): + conf_tpl = getPluginDir() + '/conf/config.inc.php' + centent = mw.readFile(conf_tpl) + centent = contentReplace(centent) + mw.writeFile(conf_run, centent) + + log_a = accessLog() + log_e = errorLog() + + for i in [log_a, log_e]: + if os.path.exists(i): + cmd = "echo '' > " + i + mw.execShell(cmd) + + mw.restartWeb() + return 'ok' + + +def stop(): + conf = getConf() + if os.path.exists(conf): + os.remove(conf) + delPort() + mw.restartWeb() + return 'ok' + + +def restart(): + return start() + + +def reload(): + file_tpl = getPluginDir() + '/conf/phpmyadmin.conf' + file_run = getConf() + if os.path.exists(file_run): + centent = mw.readFile(file_tpl) + centent = contentReplace(centent) + mw.writeFile(file_run, centent) + return start() + + +def setPhpVer(): + args = getArgs() + + if not 'phpver' in args: + return 'phpver missing' + + cacheFile = getServerDir() + '/php.pl' + mw.writeFile(cacheFile, args['phpver']) + + file_tpl = getPluginDir() + '/conf/phpmyadmin.conf' + file_run = getConf() + + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_run, content) + + mw.restartWeb() + return 'ok' + + +def getSetPhpVer(): + cacheFile = getServerDir() + '/php.pl' + if os.path.exists(cacheFile): + return mw.readFile(cacheFile).strip() + return '' + + +def getPmaOption(): + data = getCfg() + return mw.returnJson(True, 'ok', data) + + +def getPmaPort(): + try: + port = getPort() + return mw.returnJson(True, 'OK', port) + except Exception as e: + # print(e) + return mw.returnJson(False, '插件未启动!') + + +def setPmaPort(): + args = getArgs() + data = checkArgs(args, ['port']) + if not data[0]: + return data[1] + + port = args['port'] + if port == '80': + return mw.returnJson(False, '80端不能使用!') + + file = getConf() + if not os.path.exists(file): + return mw.returnJson(False, '插件未启动!') + content = mw.readFile(file) + rep = r'listen\s*(.*);' + content = re.sub(rep, "listen " + port + ';', content) + mw.writeFile(file, content) + + setCfg("port", port) + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaChoose(): + args = getArgs() + data = checkArgs(args, ['choose']) + if not data[0]: + return data[1] + + choose = args['choose'] + setCfg('choose', choose) + + pma_path = getCfg()['path'] + conf_run = getServerDir() + "/" + pma_path + '/config.inc.php' + + conf_tpl = getPluginDir() + '/conf/config.inc.php' + content = mw.readFile(conf_tpl) + content = contentReplace(content) + mw.writeFile(conf_run, content) + + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaUsername(): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + + username = args['username'] + setCfg('username', username) + + cfg = getCfg() + pma_path = getServerDir() + '/pma.pass' + username = mw.getRandomString(10) + pass_cmd = cfg['username'] + ':' + mw.hasPwd(cfg['password']) + mw.writeFile(pma_path, pass_cmd) + + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaPassword(): + args = getArgs() + data = checkArgs(args, ['password']) + if not data[0]: + return data[1] + + password = args['password'] + setCfg('password', password) + + cfg = getCfg() + pma_path = getServerDir() + '/pma.pass' + username = mw.getRandomString(10) + pass_cmd = cfg['username'] + ':' + mw.hasPwd(cfg['password']) + mw.writeFile(pma_path, pass_cmd) + + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def setPmaPath(): + args = getArgs() + data = checkArgs(args, ['path']) + if not data[0]: + return data[1] + + path = args['path'] + + if len(path) < 5: + return mw.returnJson(False, '不能小于5位!') + + old_path = getServerDir() + "/" + getCfg()['path'] + new_path = getServerDir() + "/" + path + + mw.execShell("mv " + old_path + " " + new_path) + setCfg('path', path) + return mw.returnJson(True, '修改成功!') + + +def accessLog(): + return getServerDir() + '/access.log' + + +def errorLog(): + return getServerDir() + '/error.log' + + +def installVersion(): + return mw.readFile(getServerDir() + '/version.pl') + +def pluginsDbSupport(): + data = {} + + data['installed'] = 'no' + install_path = getServerDir() + if not os.path.exists(install_path): + return mw.returnJson(True, 'ok', data) + + data['installed'] = 'ok' + data['status'] = status() + if (data['status'] == 'stop'): + return mw.returnJson(True, 'ok', data) + + data['cfg'] = getCfg() + port = getPort() + ip = '127.0.0.1' + if not mw.isAppleSystem(): + ip = thisdb.getOption('server_ip') + + cfg = data['cfg'] + auth = cfg['username']+':'+cfg['password'] + rand_path = cfg['path'] + home_page = 'http://' + auth + '@' + ip + ':' + port + '/' + rand_path + '/index.php' + + data['home_page'] = home_page + data['version'] = installVersion().strip() + + return mw.returnJson(True, 'ok', data) + +def installPreInspection(): + php_confdir = mw.getServerDir()+'/web_conf/php/conf' + if not os.path.exists(php_confdir): + return "必须先安装一个php版本!" + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'version': + print(installVersion()) + elif func == 'get_cfg': + print(returnCfg()) + elif func == 'config_inc': + print(getConfInc()) + elif func == 'get_home_page': + print(getHomePage()) + elif func == 'set_php_ver': + print(setPhpVer()) + elif func == 'get_set_php_ver': + print(getSetPhpVer()) + elif func == 'get_pma_port': + print(getPmaPort()) + elif func == 'set_pma_port': + print(setPmaPort()) + elif func == 'get_pma_option': + print(getPmaOption()) + elif func == 'set_pma_choose': + print(setPmaChoose()) + elif func == 'set_pma_username': + print(setPmaUsername()) + elif func == 'set_pma_password': + print(setPmaPassword()) + elif func == 'set_pma_path': + print(setPmaPath()) + elif func == 'access_log': + print(accessLog()) + elif func == 'error_log': + print(errorLog()) + elif func == 'plugins_db_support': + print(pluginsDbSupport()) + else: + print('error') diff --git a/plugins/phpmyadmin/info.json b/plugins/phpmyadmin/info.json new file mode 100755 index 000000000..5f021cf76 --- /dev/null +++ b/plugins/phpmyadmin/info.json @@ -0,0 +1,18 @@ +{ + "title":"phpMyAdmin", + "tip":"soft", + "name":"phpmyadmin", + "type":"运行环境", + "ps":"著名Web端MySQL管理工具", + "to_ver":["4.8.4"], + "install_pre_inspection":true, + "versions":["4.4.15","4.9.11","5.2.1"], + "updates":["4.4.15","4.9.11","5.2.1"], + "shell":"install.sh", + "checks":"server/phpmyadmin", + "path": "server/phpmyadmin", + "author":"phpMyAdmin", + "home":"https://www.phpmyadmin.net/", + "date":"2017-11-24", + "pid": "2" +} \ No newline at end of file diff --git a/plugins/phpmyadmin/install.sh b/plugins/phpmyadmin/install.sh new file mode 100755 index 000000000..9d7f1790a --- /dev/null +++ b/plugins/phpmyadmin/install.sh @@ -0,0 +1,89 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/phpmyadmin && bash install.sh install 4.4.15 +# cd /www/server/mdserver-web && python3 plugins/phpmyadmin/index.py start + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +if [ "$sys_os" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +sysName=`uname` +echo "use system: ${sysName}" + +if [ "${sysName}" == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' +elif grep -Eqi "Raspbian" /etc/issue || grep -Eq "Raspbian" /etc/*-release; then + OSNAME='raspbian' +else + OSNAME='unknow' +fi + +Install_phpmyadmin() +{ + if [ -d $serverPath/phpmyadmin ];then + exit 0 + fi + + mkdir -p ${serverPath}/source/phpmyadmin + + VER=$1 + + FDIR=phpMyAdmin-${VER}-all-languages + FILE=phpMyAdmin-${VER}-all-languages.tar.gz + DOWNLOAD=https://files.phpmyadmin.net/phpMyAdmin/${VER}/$FILE + + + if [ ! -f $serverPath/source/phpmyadmin/$FILE ];then + wget --no-check-certificate -O $serverPath/source/phpmyadmin/$FILE $DOWNLOAD + fi + + if [ ! -d $serverPath/source/phpmyadmin/$FDIR ];then + cd $serverPath/source/phpmyadmin && tar zxvf $FILE + fi + + mkdir -p ${serverPath}/phpmyadmin + cp -r $serverPath/source/phpmyadmin/$FDIR $serverPath/phpmyadmin/ + cd $serverPath/phpmyadmin/ && mv $FDIR phpmyadmin + rm -rf $serverPath/source/phpmyadmin/$FDIR + + echo "${1}" > ${serverPath}/phpmyadmin/version.pl + cd ${rootPath} && python3 ${rootPath}/plugins/phpmyadmin/index.py start + + echo '安装完成' +} + +Uninstall_phpmyadmin() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/phpmyadmin/index.py stop + + rm -rf ${serverPath}/phpmyadmin + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_phpmyadmin $2 +else + Uninstall_phpmyadmin $2 +fi diff --git a/plugins/phpmyadmin/js/phpmyadmin.js b/plugins/phpmyadmin/js/phpmyadmin.js new file mode 100755 index 000000000..e81e05efc --- /dev/null +++ b/plugins/phpmyadmin/js/phpmyadmin.js @@ -0,0 +1,184 @@ +function str2Obj(str){ + var data = {}; + kv = str.split('&'); + for(i in kv){ + v = kv[i].split('='); + data[v[0]] = v[1]; + } + return data; +} + +function pmaPost(method,args,callback){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'phpmyadmin', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function pmaAsyncPost(method,args){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'phpmyadmin', func:method, args:_args}); +} + +function homePage(){ + pmaPost('get_home_page', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + var con = ''; + $(".soft-man-con").html(con); + }); +} + +//phpmyadmin切换php版本 +function phpVer(version) { + + var _version = pmaAsyncPost('get_set_php_ver','') + if (_version['data'] != ''){ + version = _version['data']; + } + + $.post('/site/get_php_version', function(data) { + var rdata = data['data']; + // console.log(rdata); + var body = "
                                PHP版本
                                '; + $(".soft-man-con").html(body); + },'json'); +} + +function phpVerChange(type, msg) { + var phpver = $("#phpver").val(); + pmaPost('set_php_ver', 'phpver='+phpver, function(data){ + if ( data.data == 'ok' ){ + layer.msg('设置成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg('设置失败!',{icon:2,time:2000,shade: [0.3, '#000']}); + } + }); +} + + +//phpmyadmin安全设置 +function safeConf() { + pmaPost('get_pma_option', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + var cfg = rdata.data; + var con = '
                                \ + 访问端口\ + \ + \ +
                                \ +
                                \ + 访问切换\ + \ + \ +
                                \ +
                                \ + 用户名\ + \ + \ +
                                \ +
                                \ + 密码\ + \ + \ +
                                \ +
                                \ +
                                \ + 路径名\ + \ + \ +
                                '; + $(".soft-man-con").html(con); + }); +} + + +function setPmaChoose(){ + var choose = $("#access_choose").val(); + pmaPost('set_pma_choose',{'choose':choose}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function setPmaUsername(){ + var username = $("input[name=username]").val(); + pmaPost('set_pma_username',{'username':username}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function setPmaPassword(){ + var password = $("input[name=password]").val(); + pmaPost('set_pma_password',{'password':password}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +function setPmaPath(){ + var path = $("input[name=path]").val(); + pmaPost('set_pma_path',{'path':path}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + +//修改phpmyadmin端口 +function setPamPort() { + var pmport = $("#pmport").val(); + if (pmport < 80 || pmport > 65535) { + layer.msg('端口范围不合法!', { icon: 2 }); + return; + } + var data = 'port=' + pmport; + + pmaPost('set_pma_port',data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} \ No newline at end of file diff --git a/plugins/postgresql/class/pg.py b/plugins/postgresql/class/pg.py new file mode 100755 index 000000000..0bf3b5bef --- /dev/null +++ b/plugins/postgresql/class/pg.py @@ -0,0 +1,207 @@ +# coding: utf-8 + +import re +import os +import sys + +import psycopg2 + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +class ORM: + __DB_PASS = None + __DB_USER = 'postgres' + __DB_PORT = 5432 + __DB_HOST = 'localhost' + __DB_CONN = None + __DB_CUR = None + __DB_ERR = None + __DB_CNF = '/etc/postgresql.cnf' + __DB_SOCKET = '/tmp/.s.PGSQL.5432' + __DB_CHARSET = "utf8" + + __DB_TABLE = "" # 被操作的表名称 + __OPT_WHERE = "" # where条件 + __OPT_LIMIT = "" # limit条件 + __OPT_GROUP = "" # group条件 + __OPT_ORDER = "" # order条件 + __OPT_FIELD = "*" # field条件 + __OPT_PARAM = () # where值 + + def __Conn(self): + '''连接数据库''' + try: + + if os.path.exists(self.__DB_SOCKET): + try: + self.__DB_CONN = psycopg2.connect(database='postgres', + user=self.__DB_USER, + password=self.__DB_PASS, + host=self.__DB_SOCKET, + port=int(self.__DB_PORT)) + except Exception as e: + self.__DB_HOST = '127.0.0.1' + self.__DB_CONN = psycopg2.connect(database='postgres', + user=self.__DB_USER, + password=self.__DB_PASS, + host=self.__DB_SOCKET, + port=int(self.__DB_PORT)) + else: + try: + self.__DB_CONN = psycopg2.connect(database='postgres', + user=self.__DB_USER, + password=self.__DB_PASS, + host=self.__DB_HOST, + port=int(self.__DB_PORT)) + except Exception as e: + self.__DB_HOST = '127.0.0.1' + self.__DB_CONN = psycopg2.connect(database='postgres', + user=self.__DB_USER, + password=self.__DB_PASS, + host=self.__DB_HOST, + port=int(self.__DB_PORT)) + + self.__DB_CONN.autocommit = True + self.__DB_CUR = self.__DB_CONN.cursor() + + return True + except Exception as e: + self.__DB_ERR = e + return False + + def setDbConf(self, conf): + self.__DB_CNF = conf + + def setSocket(self, sock): + self.__DB_SOCKET = sock + + def setCharset(self, charset): + self.__DB_CHARSET = charset + + def setPort(self, port): + self.__DB_PORT = port + + def setHostAddr(self, host): + self.__DB_HOST = host + + def setPwd(self, pwd): + self.__DB_PASS = pwd + + def getPwd(self): + return self.__DB_PASS + + def table(self, table): + # 设置表名 + self.__DB_TABLE = table + return self + + def where(self, where, param=()): + # WHERE条件 + if where: + self.__OPT_WHERE = " WHERE " + where + self.__OPT_PARAM = param + return self + + def andWhere(self, where, param): + # WHERE条件 + if where: + self.__OPT_WHERE = self.__OPT_WHERE + " and " + where + # print(param) + # print(self.__OPT_PARAM) + self.__OPT_PARAM = self.__OPT_PARAM + param + return self + + def order(self, order): + # ORDER条件 + if len(order): + self.__OPT_ORDER = " ORDER BY " + order + else: + self.__OPT_ORDER = "" + return self + + def group(self, group): + if len(group): + self.__OPT_GROUP = " GROUP BY " + group + else: + self.__OPT_GROUP = "" + return self + + def limit(self, limit): + # LIMIT条件 + if len(limit): + self.__OPT_LIMIT = " LIMIT " + limit + else: + self.__OPT_LIMIT = "" + return self + + def field(self, field): + # FIELD条件 + if len(field): + self.__OPT_FIELD = field + return self + + def select(self): + # 查询数据集 + if not self.__Conn(): + return self.__DB_ERR + try: + sql = "SELECT " + self.__OPT_FIELD + " FROM " + self.__DB_TABLE + \ + self.__OPT_WHERE + self.__OPT_GROUP + self.__OPT_ORDER + self.__OPT_LIMIT + # print(sql) + result = self.__DB_CUR.execute(sql, self.__OPT_PARAM) + data = self.__DB_CUR.fetchall() + ret = [] + if self.__OPT_FIELD != "*": + field = self.__OPT_FIELD.split(',') + for row in data: + i = 0 + field_k = {} + for key in field: + field_k[key] = row[i] + i += 1 + ret.append(field_k) + else: + ret = map(list, data) + self.__Close() + return ret + except Exception as ex: + return "error: " + str(ex) + + def execute(self, sql): + # 执行SQL语句返回受影响行 + if not self.__Conn(): + return self.__DB_ERR + try: + result = self.__DB_CUR.execute(sql) + self.__DB_CONN.commit() + self.__Close() + return result + except Exception as ex: + return ex + + def query(self, sql): + # 执行SQL语句返回数据集 + if not self.__Conn(): + return self.__DB_ERR + try: + self.__DB_CUR.execute(sql) + result = self.__DB_CUR.fetchall() + # print(result) + # 将元组转换成列表 + # data = map(list, result) + self.__Close() + return result + except Exception as ex: + return ex + + def __Close(self): + # 关闭连接 + self.__DB_CUR.close() + self.__DB_CONN.close() diff --git a/plugins/postgresql/conf/pg_hba.conf b/plugins/postgresql/conf/pg_hba.conf new file mode 100644 index 000000000..b411ed22f --- /dev/null +++ b/plugins/postgresql/conf/pg_hba.conf @@ -0,0 +1,10 @@ +# PostgreSQL Client Authentication Configuration File + +# TYPE DATABASE USER ADDRESS METHOD +# "local" is for Unix domain socket connections only +local all all trust +host all all 0.0.0.0/0 md5 +host all all ::1/128 md5 +local replication all trust +host replication all 0.0.0.0/0 md5 +host replication all ::1/128 md5 \ No newline at end of file diff --git a/plugins/postgresql/conf/pgsql.sql b/plugins/postgresql/conf/pgsql.sql new file mode 100755 index 000000000..14ef6fb2d --- /dev/null +++ b/plugins/postgresql/conf/pgsql.sql @@ -0,0 +1,43 @@ +CREATE TABLE IF NOT EXISTS `config` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pg_root` TEXT +); + +INSERT INTO `config` (`id`, `pg_root`) VALUES (1, 'admin'); + +CREATE TABLE IF NOT EXISTS `databases` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pid` INTEGER, + `name` TEXT, + `username` TEXT, + `password` TEXT, + `accept` TEXT, + `rw` TEXT DEFAULT 'rw', + `ps` TEXT, + `addtime` TEXT +); +-- ALTER TABLE `databases` ADD COLUMN `rw` TEXT DEFAULT 'rw'; + +CREATE TABLE IF NOT EXISTS `master_replication_user` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` TEXT, + `password` TEXT, + `accept` TEXT, + `ps` TEXT, + `addtime` TEXT +); + +-- 从库配置主库的[ssh private key] +-- drop table `slave_id_rsa`; +CREATE TABLE IF NOT EXISTS `slave_id_rsa` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `ip` TEXT, + `port` TEXT, + `user` TEXT, + `db_user` TEXT, + `id_rsa` TEXT, + `ps` TEXT, + `addtime` TEXT +); + + diff --git a/plugins/postgresql/conf/postgresql.conf b/plugins/postgresql/conf/postgresql.conf new file mode 100644 index 000000000..dd7c5bb76 --- /dev/null +++ b/plugins/postgresql/conf/postgresql.conf @@ -0,0 +1,49 @@ +port = 5432 +listen_addresses='*' +unix_socket_directories='/tmp,{$APP_PATH}' + +max_connections=200 +max_wal_size = 1GB +min_wal_size = 80MB +shared_buffers = 128MB +work_mem = 4MB +effective_cache_size = 4GB +temp_buffers = 8MB +max_prepared_transactions = 0 +max_stack_depth = 2MB +bgwriter_lru_maxpages = 100 +max_worker_processes = 8 +dynamic_shared_memory_type = posix + + +log_timezone = 'Asia/Shanghai' +datestyle = 'iso, ymd' +timezone = 'Asia/Shanghai' +default_text_search_config = 'pg_catalog.simple' + + +# 主配置 +#archive_mode = on +#archive_command = 'test ! -f {$APP_PATH}/archivelog/%f && cp %p {$APP_PATH}/archivelog/%f' +#wal_level = replica +#max_wal_senders = 10 +#wal_sender_timeout = 60s + + +# 从配置 +#hot_standby = on +#primary_conninfo = 'host=192.168.0.100 port=5432 user=replica password=123456' +#max_standby_streaming_delay = 30s +#wal_receiver_status_interval = 10s +#hot_standby_feedback = on +#recovery_target_timeline= 'latest' + + +logging_collector = on +log_destination = 'stderr' +log_directory = '{$APP_PATH}/logs' +log_filename = 'postgresql-%Y-%m-%d.log' +log_statement = all +log_rotation_age = 7d +log_rotation_size = 100MB +log_min_duration_statement = 5000 diff --git a/plugins/postgresql/ico.png b/plugins/postgresql/ico.png new file mode 100644 index 000000000..26e183c2f Binary files /dev/null and b/plugins/postgresql/ico.png differ diff --git a/plugins/postgresql/index.html b/plugins/postgresql/index.html new file mode 100755 index 000000000..19647e423 --- /dev/null +++ b/plugins/postgresql/index.html @@ -0,0 +1,58 @@ + +
                                +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                配置文件

                                +

                                客服端认证

                                +

                                端口

                                +

                                当前状态

                                +

                                性能优化

                                +

                                日志

                                +

                                慢日志

                                +

                                管理列表

                                +

                                主从配置

                                +
                                +
                                +
                                +
                                +
                                + +
                                + + \ No newline at end of file diff --git a/plugins/postgresql/index.py b/plugins/postgresql/index.py new file mode 100755 index 000000000..6ce89e219 --- /dev/null +++ b/plugins/postgresql/index.py @@ -0,0 +1,1652 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + + +# reload(sys) +# sys.setdefaultencoding('utf-8') + +# python3 plugins/postgresql/index.py start 14.4 +# python3 plugins/postgresql/index.py run_info 14.4 +# ps -ef | grep -v grep| grep run_info | awk '{print $2}' | xargs kill -9 +# vi /etc/sysconfig/network-scripts/ifcfg-eth0 + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +if mw.isAppleSystem(): + cmd = 'ls /usr/local/lib/ | grep python | cut -d \\ -f 1 | awk \'END {print}\'' + info = mw.execShell(cmd) + p = "/usr/local/lib/" + info[0].strip() + "/site-packages" + sys.path.append(p) + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'postgresql' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def getBackupDir(): + bk_path = mw.getBackupDir() + "/database/postgresql" + if not os.path.isdir(bk_path): + mw.execShell("mkdir -p {}".format(bk_path)) + return bk_path + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getConf(): + path = getServerDir() + '/data/postgresql.conf' + return path + + +def configTpl(): + + clist = [] + + app_dir = getServerDir() + clist.append(app_dir + "/data/postgresql.conf") + clist.append(app_dir + "/data/pg_hba.conf") + + return mw.getJson(clist) + + +def pgHbaConf(): + return getServerDir() + "/data/pg_hba.conf" + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + return mw.returnJson(True, 'ok', content) + + +def getDbPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(\d*)?' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getSocketFile(): + # sock_name = '.s.PGSQL.' + getDbPort() + sock_name = "" + sock_tmp = '/tmp/' + sock_name + if os.path.exists(sock_tmp): + return sock_tmp + + sock_app = getServerDir() + "/" + sock_name + if os.path.exists(sock_app): + return sock_app + return sock_app + + +def getInitdTpl(version=''): + path = getPluginDir() + '/init.d/postgresql.tpl' + if not os.path.exists(path): + path = getPluginDir() + '/init.d/postgresql.tpl' + return path + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$APP_PATH}', service_path + '/postgresql') + return content + + +def pSqliteDb(dbname='databases', name='pgsql'): + file = getServerDir() + '/' + name + '.db' + if not os.path.exists(file): + conn = mw.M(dbname).dbPos(getServerDir(), name) + csql = mw.readFile(getPluginDir() + '/conf/pgsql.sql') + csql_list = csql.split(';') + for index in range(len(csql_list)): + conn.execute(csql_list[index], ()) + else: + # 现有run + # conn = mw.M(dbname).dbPos(getServerDir(), name) + # csql = mw.readFile(getPluginDir() + '/conf/mysql.sql') + # csql_list = csql.split(';') + # for index in range(len(csql_list)): + # conn.execute(csql_list[index], ()) + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + + +def pgDb(): + + sys.path.append(getPluginDir() + "/class") + import pg + + db = pg.ORM() + + db.setPort(getDbPort()) + db.setPwd(pSqliteDb('config').where('id=?', (1,)).getField('pg_root')) + db.setSocket(getSocketFile()) + return db + + +def initConfig(version=''): + conf_dir = getServerDir() + init_pl = conf_dir + "/init.pl" + if not os.path.exists(init_pl): + mw.writeFile(init_pl, 'ok') + + # postgresql.conf + pg_conf = conf_dir + '/data/postgresql.conf' + tpl = getPluginDir() + '/conf/postgresql.conf' + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(pg_conf, content) + + # pg_hba.conf + tpl = getPluginDir() + '/conf/pg_hba.conf' + pg_hba_conf = conf_dir + '/data/pg_hba.conf' + content = mw.readFile(tpl) + mw.writeFile(pg_hba_conf, content) + + logfile = runLog() + if not os.path.exists(logfile): + mw.writeFile(logfile, '') + + +def initDreplace(version=''): + + conf_dir = getServerDir() + conf_list = [ + conf_dir + "/logs", + conf_dir + "/tmp", + conf_dir + "/archivelog", + ] + for c in conf_list: + if not os.path.exists(c): + os.mkdir(c) + + # systemd + system_dir = mw.systemdCfgDir() + service = system_dir + '/postgresql.service' + if os.path.exists(system_dir) and not os.path.exists(service): + tpl = getPluginDir() + '/init.d/postgresql.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(service, content) + mw.execShell('systemctl daemon-reload') + + if not mw.isAppleSystem(): + mw.execShell('chown -R postgres:postgres ' + getServerDir()) + + initd_path = getServerDir() + '/init.d' + if not os.path.exists(initd_path): + os.mkdir(initd_path) + + file_bin = initd_path + '/' + getPluginName() + if not os.path.exists(file_bin): + tpl = getInitdTpl(version) + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + return file_bin + + +def status(version=''): + data = mw.execShell( + "ps -ef|grep postgres |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + + +def pgCmd(cmd): + return "su - postgres -c \"" + cmd + "\"" + + +def execShellPg(cmd): + return mw.execShell(pgCmd(cmd)) + + +def pGetDbUser(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + return 'postgresql' + + +def initPgData(): + serverdir = getServerDir() + if not os.path.exists(serverdir + '/data'): + cmd = serverdir + '/bin/initdb -D ' + serverdir + "/data" + if not mw.isAppleSystem(): + execShellPg(cmd) + return False + mw.execShell(cmd) + return False + return True + + +def initPgPwd(): + + serverdir = getServerDir() + pwd = mw.getRandomString(16) + + cmd_pass = serverdir + '/bin/createuser -s -r postgres' + + if not mw.isAppleSystem(): + cmd_pass = 'su - postgres -c "' + cmd_pass + '"' + data = mw.execShell(cmd_pass) + + cmd_pass = "echo \"alter user postgres with password '" + pwd + "'\" | " + if not mw.isAppleSystem(): + cmd = serverdir + '/bin/psql -d postgres' + cmd_pass = cmd_pass + ' ' + pgCmd(cmd) + else: + cmd_pass = cmd_pass + serverdir + '/bin/psql -d postgres' + + data = mw.execShell(cmd_pass) + # print(cmd_pass) + # print(data) + + pSqliteDb('config').where('id=?', (1,)).save('pg_root', (pwd,)) + return True + + +def pgOp(version, method): + # import commands + init_file = initDreplace() + cmd = init_file + ' ' + method + # print(cmd) + try: + isInited = initPgData() + initConfig(version) + if not isInited: + if mw.isAppleSystem(): + cmd_init_start = init_file + ' start' + subprocess.Popen(cmd_init_start, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + + time.sleep(6) + else: + mw.execShell('systemctl start postgresql') + + initPgPwd() + + if mw.isAppleSystem(): + cmd_init_stop = init_file + ' stop' + subprocess.Popen(cmd_init_stop, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + time.sleep(3) + else: + mw.execShell('systemctl stop postgresql') + + if mw.isAppleSystem(): + sub = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, + bufsize=4096, stderr=subprocess.PIPE) + sub.wait(5) + else: + mw.execShell('systemctl ' + method + ' postgresql') + return 'ok' + except Exception as e: + # raise + return method + ":" + str(e) + + +def appCMD(version, action): + return pgOp(version, action) + + +def start(version=''): + return appCMD(version, 'start') + + +def stop(version=''): + return appCMD(version, 'stop') + + +def restart(version=''): + return appCMD(version, 'restart') + + +def reload(version=''): + logfile = runLog() + if os.path.exists(logfile): + mw.writeFile(logfile, '') + return appCMD(version, 'reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status postgresql | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable postgresql') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable postgresql') + return 'ok' + + +def getMyDbPos(): + file = getConf() + content = mw.readFile(file) + rep = r'datadir\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getPgPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def setPgPort(): + args = getArgs() + data = checkArgs(args, ['port']) + if not data[0]: + return data[1] + + port = args['port'] + file = getConf() + content = mw.readFile(file) + rep = r"port\s*=\s*([0-9]+)\s*\n" + content = re.sub(rep, 'port = ' + port + '\n', content) + mw.writeFile(file, content) + restart() + return mw.returnJson(True, '编辑成功!') + + +def runInfo(): + + if status(version) == 'stop': + return mw.returnJson(False, 'PG未启动', []) + + db = pgDb() + data_dir = getServerDir() + "/data" + port = getPgPort() + result = {} + + result['uptime'] = mw.execShell( + '''cat {}/postmaster.pid |sed -n 3p '''.format(data_dir))[0] + timestamp = result['uptime'] + time_local = time.localtime(int(timestamp)) + dt = time.strftime("%Y-%m-%d %H:%M:%S", time_local) + result['uptime'] = dt + + result['progress_num'] = mw.execShell( + "ps -ef |grep postgres |wc -l")[0].strip() + result['pid'] = mw.execShell( + '''cat {}/postmaster.pid |sed -n 1p '''.format(data_dir))[0].strip() + res = db.query( + "select count(*) from pg_stat_activity where not pid=pg_backend_pid()") + + # print(res) + result['connections'] = res[0][0] + + res = db.query("select pg_size_pretty(pg_database_size('postgres'))") + result['pg_size'] = res[0][0] + result['pg_mem'] = mw.execShell( + '''cat /proc/%s/status|grep VmRSS|awk -F: '{print $2}' ''' % (result['pid']))[0] + + result['pg_vm_lock'] = mw.execShell( + '''cat /proc/%s/status|grep VmLck|awk -F: '{print $2}' ''' % (result['pid'].strip()))[0] + result['pg_vm_high'] = mw.execShell( + '''cat /proc/%s/status|grep VmHWM|awk -F: '{print $2}' ''' % (result['pid'].strip()))[0] + result['pg_vm_data_size'] = mw.execShell( + '''cat /proc/%s/status|grep VmData|awk -F: '{print $2}' ''' % (result['pid'].strip()))[0] + result['pg_vm_sk_size'] = mw.execShell( + '''cat /proc/%s/status|grep VmStk|awk -F: '{print $2}' ''' % (result['pid'].strip()))[0] + result['pg_vm_code_size'] = mw.execShell( + '''cat /proc/%s/status|grep VmExe|awk -F: '{print $2}' ''' % (result['pid'].strip()))[0] + result['pg_vm_lib_size'] = mw.execShell( + '''cat /proc/%s/status|grep VmLib|awk -F: '{print $2}' ''' % (result['pid'].strip()))[0] + result['pg_vm_swap_size'] = mw.execShell( + '''cat /proc/%s/status|grep VmSwap|awk -F: '{print $2}' ''' % (result['pid'].strip()))[0] + result['pg_vm_page_size'] = mw.execShell( + '''cat /proc/%s/status|grep VmPTE|awk -F: '{print $2}' ''' % (result['pid'].strip()))[0] + result['pg_sigq'] = mw.execShell( + '''cat /proc/%s/status|grep SigQ|awk -F: '{print $2}' ''' % (result['pid'].strip()))[0] + + return mw.getJson(result) + + +def runLog(): + return getServerDir() + "/logs/server.log" + + +def getSlowLog(): + # pgsql慢日志查询 + return getServerDir() + "/logs/" + time.strftime("postgresql-%Y-%m-%d.log") + + +def getUnit(args): + unit = '' + if "GB" in args: + unit = "GB" + elif "MB" in args: + unit = "MB" + elif "KB" in args: + unit = "KB" + elif "kB" in args: + unit = "kB" + return unit + + +def pgDbStatus(): + + data_directory = getServerDir() + "/data" + data = {} + shared_buffers, work_mem, effective_cache_size, maintence_work_mem, max_connections, temp_buffers, max_prepared_transactions, max_stack_depth, bgwriter_lru_maxpages, max_worker_processes, listen_addresses = '', '', '', '', '', '', '', '', '', '', '' + with open("{}/postgresql.conf".format(data_directory)) as f: + for i in f: + if i.strip().startswith("shared_buffers"): + shared_buffers = i.split("=")[1] + elif i.strip().startswith("#shared_buffers"): + shared_buffers = i.split("=")[1] + + shared_buffers_num = re.match( + r'\d+', shared_buffers.strip()).group() if re.match(r'\d+', shared_buffers.strip()) else "" + data['shared_buffers'] = [shared_buffers_num, "MB", + "PG通过shared_buffers和内核和磁盘打交道,通常设置为实际内存的10%."] + + if i.strip().startswith("work_mem"): + work_mem = i.split("=")[1] + elif i.strip().startswith("#work_mem"): + work_mem = i.split("=")[1] + + work_mem_num = re.match( + r'\d+', work_mem.strip()).group() if re.match(r'\d+', work_mem.strip()) else "" + data['work_mem'] = [work_mem_num, "MB", + "增加work_mem有助于提高排序的速度。通常设置为实际RAM的2% -4%."] + + if i.strip().startswith("effective_cache_size"): + effective_cache_size = i.split("=")[1] + elif i.strip().startswith("#effective_cache_size"): + effective_cache_size = i.split("=")[1] + + effective_cache_size_num = re.match(r'\d+', effective_cache_size.strip( + )).group() if re.match(r'\d+', effective_cache_size.strip()) else "" + data['effective_cache_size'] = [effective_cache_size_num, + "GB", "PG能够使用的最大缓存,比如4G的内存,可以设置为3GB."] + + if i.strip().startswith("temp_buffers "): + temp_buffers = i.split("=")[1] + elif i.strip().startswith("#temp_buffers "): + temp_buffers = i.split("=")[1] + + temp_buffers_num = re.match( + r'\d+', temp_buffers.strip()).group() if re.match(r'\d+', temp_buffers.strip()) else "" + data['temp_buffers'] = [temp_buffers_num, "MB", + "设置每个数据库会话使用的临时缓冲区的最大数目,默认是8MB"] + + if i.strip().startswith("max_connections"): + max_connections = i.split("=")[1] + elif i.strip().startswith("#max_connections"): + max_connections = i.split("=")[1] + + max_connections_num = re.match( + r'\d+', max_connections.strip()).group() if re.match(r'\d+', max_connections.strip()) else "" + data['max_connections'] = [max_connections_num, + getUnit(max_connections), "最大连接数"] + + if i.strip().startswith("max_prepared_transactions"): + max_prepared_transactions = i.split("=")[1] + elif i.strip().startswith("#max_prepared_transactions"): + max_prepared_transactions = i.split("=")[1] + + max_prepared_transactions_num = re.match(r'\d+', max_prepared_transactions.strip( + )).group() if re.match(r'\d+', max_prepared_transactions.strip()) else "" + data['max_prepared_transactions'] = [max_prepared_transactions_num, getUnit( + max_prepared_transactions), "设置可以同时处于 prepared 状态的事务的最大数目"] + + if i.strip().startswith("max_stack_depth "): + max_stack_depth = i.split("=")[1] + elif i.strip().startswith("#max_stack_depth "): + max_stack_depth = i.split("=")[1] + + max_stack_depth_num = re.match( + r'\d+', max_stack_depth.strip()).group() if re.match(r'\d+', max_stack_depth.strip()) else "" + data['max_stack_depth'] = [max_stack_depth_num, + "MB", "指定服务器的执行堆栈的最大安全深度,默认是2MB"] + + if i.strip().startswith("bgwriter_lru_maxpages "): + bgwriter_lru_maxpages = i.split("=")[1] + elif i.strip().startswith("#bgwriter_lru_maxpages "): + bgwriter_lru_maxpages = i.split("=")[1] + + bgwriter_lru_maxpages_num = re.match(r'\d+', bgwriter_lru_maxpages.strip( + )).group() if re.match(r'\d+', bgwriter_lru_maxpages.strip()) else "" + data['bgwriter_lru_maxpages'] = [ + bgwriter_lru_maxpages_num, "", "一个周期最多写多少脏页"] + + if i.strip().startswith("max_worker_processes "): + max_worker_processes = i.split("=")[1] + elif i.strip().startswith("#max_worker_processes "): + max_worker_processes = i.split("=")[1] + + max_worker_processes_num = re.match(r'\d+', max_worker_processes.strip( + )).group() if re.match(r'\d+', max_worker_processes.strip()) else "" + data['max_worker_processes'] = [max_worker_processes_num, + "", "如果要使用worker process, 最多可以允许fork 多少个worker进程."] + + # if i.strip().startswith("listen_addresses"): + # listen_addresses = i.split("=")[1] + # elif i.strip().startswith("#listen_addresses"): + # listen_addresses = i.split("=")[1] + + # listen_addresses = re.match(r"\'.*?\'", listen_addresses.strip()).group( + # ) if re.match(r"\'.*?\'", listen_addresses.strip()) else "" + # data['listen_addresses'] = [listen_addresses.replace( + # "'", '').replace("127.0.0.1", 'localhost'), "", "pgsql监听地址"] + + # 返回数据到前端 + data['status'] = True + return mw.getJson(data) + + +def sedConf(name, val): + path = getServerDir() + "/data/postgresql.conf" + content = '' + with open(path) as f: + for i in f: + if i.strip().startswith(name): + i = "{} = {} \n".format(name, val) + content += i + + mw.writeFile(path, content) + return True + + +def pgSetDbStatus(): + ''' + 保存pgsql性能调整信息 + ''' + args = getArgs() + data = checkArgs(args, ['shared_buffers', 'work_mem', 'effective_cache_size', + 'temp_buffers', 'max_connections', 'max_prepared_transactions', + 'max_stack_depth', 'bgwriter_lru_maxpages', 'max_worker_processes']) + if not data[0]: + return data[1] + + for k, v in args.items(): + sedConf(k, v) + + restart() + return mw.returnJson(True, '设置成功!') + + +def setUserPwd(version=''): + args = getArgs() + data = checkArgs(args, ['password', 'name']) + if not data[0]: + return data[1] + + newpwd = args['password'] + username = args['name'] + uid = args['id'] + try: + pdb = pgDb() + psdb = pSqliteDb('databases') + name = psdb.where('id=?', (uid,)).getField('name') + + r = pdb.execute( + "alter user {} with password '{}'".format(username, newpwd)) + + psdb.where("id=?", (uid,)).setField('password', newpwd) + return mw.returnJson(True, mw.getInfo('修改数据库[{1}]密码成功!', (name,))) + except Exception as ex: + return mw.returnJson(False, mw.getInfo('修改数据库[{1}]密码失败[{2}]!', (name, str(ex),))) + + +def getDbBackupListFunc(dbname=''): + bkDir = getBackupDir() + blist = os.listdir(bkDir) + r = [] + + bname = 'db_' + dbname + blen = len(bname) + for x in blist: + fbstr = x[0:blen] + if fbstr == bname: + r.append(x) + return r + + +def setDbBackup(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + scDir = getPluginDir() + '/scripts/backup.py' + cmd = 'python3 ' + scDir + ' database ' + args['name'] + ' 3' + os.system(cmd) + return mw.returnJson(True, 'ok') + +def rootPwd(): + return pSqliteDb('config').where('id=?', (1,)).getField('pg_root') + +def importDbBackup(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + file_path = getBackupDir() + '/' + file + file_path_sql = getBackupDir() + '/' + file.replace('.gz', '') + + if not os.path.exists(file_path_sql): + cmd = 'cd ' + getBackupDir() + ' && gzip -d ' + file + mw.execShell(cmd) + + pwd = pSqliteDb('config').where('id=?', (1,)).getField('pg_root') + + mysql_cmd = mw.getFatherDir() + '/server/mysql/bin/mysql -uroot -p' + pwd + ' ' + name + ' < ' + file_path_sql + + # print(mysql_cmd) + os.system(mysql_cmd) + return mw.returnJson(True, 'ok') + + +def deleteDbBackup(): + args = getArgs() + data = checkArgs(args, ['filename']) + if not data[0]: + return data[1] + + bkDir = getBackupDir() + os.remove(bkDir + '/' + args['filename']) + return mw.returnJson(True, 'ok') + + +def getDbBackupList(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + r = getDbBackupListFunc(args['name']) + bkDir = getBackupDir() + rr = [] + for x in range(0, len(r)): + p = bkDir + '/' + r[x] + data = {} + data['name'] = r[x] + + rsize = os.path.getsize(p) + data['size'] = mw.toSize(rsize) + + t = os.path.getctime(p) + t = time.localtime(t) + + data['time'] = time.strftime('%Y-%m-%d %H:%M:%S', t) + rr.append(data) + + data['file'] = p + + return mw.returnJson(True, 'ok', rr) + + +def getDbList(): + args = getArgs() + page = 1 + page_size = 10 + search = '' + data = {} + if 'page' in args: + page = int(args['page']) + + if 'page_size' in args: + page_size = int(args['page_size']) + + if 'search' in args: + search = args['search'] + + conn = pSqliteDb('databases') + limit = str((page - 1) * page_size) + ',' + str(page_size) + condition = '' + if not search == '': + condition = "name like '%" + search + "%'" + field = 'id,pid,name,username,password,accept,rw,ps,addtime' + clist = conn.where(condition, ()).field( + field).limit(limit).order('id desc').select() + + for x in range(0, len(clist)): + dbname = clist[x]['name'] + blist = getDbBackupListFunc(dbname) + # print(blist) + clist[x]['is_backup'] = False + if len(blist) > 0: + clist[x]['is_backup'] = True + + count = conn.where(condition, ()).count() + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'dbList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + info = {} + info['root_pwd'] = pSqliteDb('config').where( + 'id=?', (1,)).getField('pg_root') + data['info'] = info + + return mw.getJson(data) + + +def syncGetDatabases(): + pdb = pgDb() + psdb = pSqliteDb('databases') + data = pdb.table('pg_database').field( + 'datname').where("datistemplate=false").select() + nameArr = ['postgres'] + n = 0 + + for value in data: + vdb_name = value["datname"] + b = False + for key in nameArr: + if vdb_name == key: + b = True + break + if b: + continue + if psdb.where("name=?", (vdb_name,)).count() > 0: + continue + host = '127.0.0.1/32' + ps = vdb_name + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + if psdb.add('name,username,password,accept,ps,addtime', (vdb_name, vdb_name, '', host, ps, addTime)): + n += 1 + + msg = mw.getInfo('本次共从服务器获取了{1}个数据库!', (str(n),)) + return mw.returnJson(True, msg) + + +def addDb(): + args = getArgs() + data = checkArgs(args, ['password', 'name', 'db_user', 'dataAccess']) + if not data[0]: + return data[1] + + address = '' + if 'address' in args: + address = args['address'].strip() + + dbname = args['name'].strip() + dbuser = args['db_user'].strip() + password = args['password'].strip() + dataAccess = args['dataAccess'].strip() + + listen_ip = "127.0.0.1/32" + if 'listen_ip' in args: + listen_ip = args['listen_ip'].strip() + + if not re.match(r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}/\d+", listen_ip): + return mw.returnJson(False, "你输入的权限不合法,添加失败!") + + # 修改监听所有地址 + if listen_ip not in ["127.0.0.1/32", "localhost", "127.0.0.1"]: + sedConf("listen_addresses", "'*'") + + reg = r"^[\w\.-]+$" + if not re.match(reg, dbname): + return mw.returnJson(False, '数据库名称不能带有特殊符号!') + + checks = ['root', 'mysql', 'test', 'sys', 'postgres', 'postgresql'] + if dbuser in checks or len(dbuser) < 1: + return mw.returnJson(False, '数据库用户名不合法!') + if dbname in checks or len(dbname) < 1: + return mw.returnJson(False, '数据库名称不合法!') + + if len(password) < 1: + password = mw.md5(time.time())[0:8] + + pdb = pgDb() + psdb = pSqliteDb('databases') + + if psdb.where("name=? or username=?", (dbname, dbuser)).count(): + return mw.returnJson(False, '数据库或用户已存在!') + + sql = "select pg_terminate_backend(pid) from pg_stat_activity where DATNAME = 'template1';" + pdb.execute(sql) + r = pdb.execute("create database " + dbname) + # print(r) + r = pdb.execute("create user " + dbuser) + # print(r) + pdb.execute("alter user {} with password '{}'".format(dbuser, password,)) + pdb.execute( + "GRANT ALL PRIVILEGES ON DATABASE {} TO {}".format(dbname, dbuser,)) + + pg_hba = getServerDir() + "/data/pg_hba.conf" + + content = mw.readFile(pg_hba) + content += "\nhost {} {} {} md5".format( + dbname, dbuser, listen_ip) + mw.writeFile(pg_hba, content) + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + psdb.add('pid,name,username,password,accept,ps,addtime', + (0, dbname, dbuser, password, listen_ip, dbname, addTime)) + + restart() + return mw.returnJson(True, '添加成功!') + + +def delDb(): + args = getArgs() + data = checkArgs(args, ['id', 'name']) + if not data[0]: + return data[1] + + did = args['id'] + name = args['name'] + + pdb = pgDb() + psdb = pSqliteDb('databases') + + username = psdb.where('id=?', (did,)).getField('username') + # print(username, len(username)) + if len(username) > 0: + r = pdb.execute("drop user " + str(username)) + # print(r) + + sql = "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE datname='" + \ + name + "' AND pid<>pg_backend_pid();" + + r = pdb.execute(sql) + # print(r) + r = pdb.execute("drop database " + name) + # print(r) + + pg_hba = pgHbaConf() + old_config = mw.readFile(pg_hba) + new_config = re.sub(r'host\s*{}.*'.format(name), '', old_config).strip() + mw.writeFile(pg_hba, new_config) + + psdb.where("id=?", (did,)).delete() + return mw.returnJson(True, '删除成功!') + + +def setDbRw(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'id', 'rw']) + if not data[0]: + return data[1] + + username = args['username'] + uid = args['id'] + rw = args['rw'] + + pdb = pgDb() + psdb = pSqliteDb('databases') + dbname = psdb.where("id=?", (uid,)).getField('name') + + sql = "REVOKE ALL ON database " + dbname + " FROM " + username + pdb.query(sql) + + if rw == 'rw': + sql = "GRANT SELECT, INSERT, UPDATE, DELETE ON database " + dbname + " TO " + username + elif rw == 'r': + sql = "GRANT SELECT ON database " + dbname + " TO " + username + else: + sql = "GRANT all ON database " + dbname + " TO " + username + + r = pdb.execute(sql) + psdb.where("id=?", (uid,)).setField('rw', rw) + return mw.returnJson(True, '切换成功!') + + +def getDbAccess(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + name = args['name'] + psdb = pSqliteDb('databases') + accept = psdb.where("name=?", (name,)).getField('accept') + return mw.returnJson(True, accept) + + +def setDbAccess(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + access = args['access'] + + psdb = pSqliteDb('databases') + + conf = pgHbaConf() + data = mw.readFile(conf) + new_data = re.sub(r'host\s*{}.*'.format(name), '', data).strip() + + new_data += "\nhost {} {} {} md5".format( + name, name, access) + mw.writeFile(conf, new_data) + + psdb.where("name=?", (name,)).setField('accept', access) + return mw.returnJson(True, "设置成功") + + +def pgBack(): + # 备份数据库 + + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + bk_path_upload = getBackupDir() + database = args['name'] + port = getPgPort() + + cmd = '''su - postgres -c "/www/server/pgsql/bin/pg_dump -c {} -p {} "| gzip > {}/{}_{}.gz '''.format( + database, port, bk_path_upload, database, time.strftime("%Y%m%d_%H%M%S")) + + if mw.isAppleSystem(): + cmd = '''{}/bin/pg_dump -c {} -p {} | gzip > {}/{}_{}.gz '''.format( + getServerDir(), database, port, bk_path_upload, database, time.strftime("%Y%m%d_%H%M%S")) + mw.execShell(cmd) + + return mw.returnJson(True, '备份成功!') + + +def pgBackList(): + + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + database = args['name'] + + bk_path_upload = getBackupDir() + file_list = os.listdir(bk_path_upload) + data = [] + for i in file_list: + if i.split("_")[0].startswith(database): + file_path = os.path.join(bk_path_upload, i) + file_info = os.stat(file_path) + create_time = file_info.st_ctime + time_local = time.localtime(int(create_time)) + create_time = time.strftime("%Y-%m-%d %H:%M:%S", time_local) + file_size = file_info.st_size + file_size = mw.toSize(file_size) + data.append({"name": i, "time": create_time, + "size": file_size, "file": file_path}) + + return mw.returnJson(True, 'ok', data) + + +def importDbBackup(): + args = getArgs() + data = checkArgs(args, ['file', 'name']) + if not data[0]: + return data[1] + + file = args['file'] + name = args['name'] + + bk_path_upload = getBackupDir() + + file_path = os.path.join(bk_path_upload, name) + if not os.path.exists(file_path): + return mw.returnJson(False, '备份文件不存在') + + port = getPgPort() + cmd = '''gunzip -c {}|su - postgres -c " /www/server/pgsql/bin/psql -d {} -p {} " '''.format( + file, name, port) + + if mw.isAppleSystem(): + cmd = '''gunzip -c {} | {}/bin/psql -d {} -p {}'''.format( + name, getServerDir(), port) + + # print(cmd) + + mw.execShell(cmd) + return mw.returnJson(True, '恢复数据库成功') + + +def deleteDbBackup(): + args = getArgs() + data = checkArgs(args, ['filename']) + if not data[0]: + return data[1] + + bk_path_upload = getBackupDir() + os.remove(bk_path_upload + '/' + args['filename']) + return mw.returnJson(True, 'ok') + + +############ 主从功能 ###################### +def getMasterStatus(version=''): + + data = {} + data['mode'] = 'classic' + data['status'] = False + data['slave_status'] = False + pg_conf = getServerDir() + "/data/postgresql.conf" + pg_content = mw.readFile(pg_conf) + + if pg_content.find('#archive_mode') > -1: + data['status'] = False + else: + data['status'] = True + + if pg_content.find('#hot_standby') > -1: + data['slave_status'] = False + else: + data['slave_status'] = True + + return mw.returnJson(True, '设置成功', data) + + +def setMasterStatus(version=''): + pg_conf = getServerDir() + "/data/postgresql.conf" + data = mw.readFile(pg_conf) + + if data.find('#archive_mode') > -1: + data = data.replace('#archive_mode', 'archive_mode') + data = data.replace('#archive_command', 'archive_command') + data = data.replace('#wal_level', 'wal_level') + data = data.replace('#max_wal_senders', 'max_wal_senders') + data = data.replace('#wal_sender_timeout', 'wal_sender_timeout') + else: + data = data.replace('archive_mode', '#archive_mode') + data = data.replace('archive_command', '#archive_command') + data = data.replace('wal_level', '#wal_level') + data = data.replace('max_wal_senders', '#max_wal_senders') + data = data.replace('wal_sender_timeout', '#wal_sender_timeout') + + mw.writeFile(pg_conf, data) + restart(version) + return mw.returnJson(True, '设置成功') + + +def setSlaveStatus(version): + pg_conf = getServerDir() + "/data/postgresql.conf" + data = mw.readFile(pg_conf) + if data.find('#hot_standby') > -1: + data = data.replace('#hot_standby', 'hot_standby') + data = data.replace('#primary_conninfo', 'primary_conninfo') + data = data.replace('#max_standby_streaming_delay', + 'max_standby_streaming_delay') + data = data.replace('#wal_receiver_status_interval', + 'wal_receiver_status_interval') + data = data.replace('#hot_standby_feedback', 'hot_standby_feedback') + data = data.replace('#recovery_target_timeline', + 'recovery_target_timeline') + else: + data = data.replace('hot_standby', '#hot_standby') + data = data.replace('primary_conninfo', '#primary_conninfo') + data = data.replace('max_standby_streaming_delay', + '#max_standby_streaming_delay') + data = data.replace('wal_receiver_status_interval', + '#wal_receiver_status_interval') + data = data.replace('hot_standby_feedback', '#hot_standby_feedback') + data = data.replace('#recovery_target_timeline', + 'recovery_target_timeline') + + mw.writeFile(pg_conf, data) + + restart(version) + return mw.returnJson(True, '设置成功') + + +def getSlaveList(version=''): + + db = pgDb() + + res = db.execute('select * from pg_stat_replication') + print(res) + # dlist = db.query('show slave status') + # ret = [] + # for x in range(0, len(dlist)): + # tmp = {} + # tmp['Master_User'] = dlist[x]["Master_User"] + # tmp['Master_Host'] = dlist[x]["Master_Host"] + # tmp['Master_Port'] = dlist[x]["Master_Port"] + # tmp['Master_Log_File'] = dlist[x]["Master_Log_File"] + # tmp['Slave_IO_Running'] = dlist[x]["Slave_IO_Running"] + # tmp['Slave_SQL_Running'] = dlist[x]["Slave_SQL_Running"] + # ret.append(tmp) + data = {} + data['data'] = [] + + return mw.getJson(data) + + +def getSlaveSSHByIp(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_id_rsa', 'pgsql_slave') + data = conn.field('ip,port,db_user,id_rsa').where("ip=?", (ip,)).select() + return mw.returnJson(True, 'ok', data) + + +def getSlaveSSHList(version=''): + args = getArgs() + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + + conn = pSqliteDb('slave_id_rsa', 'pgsql_slave') + limit = str((page - 1) * page_size) + ',' + str(page_size) + + field = 'id,ip,port,db_user,id_rsa,ps,addtime' + clist = conn.field(field).limit(limit).order('id desc').select() + count = conn.count() + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = args['tojs'] + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def addSlaveSSH(version=''): + import base64 + + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + if ip == "": + return mw.returnJson(True, 'ok') + + data = checkArgs(args, ['port', 'id_rsa']) + if not data[0]: + return data[1] + + id_rsa = args['id_rsa'] + port = args['port'] + user = 'root' + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + conn = pSqliteDb('slave_id_rsa', 'pgsql_slave') + data = conn.field('ip,id_rsa').where("ip=?", (ip,)).select() + if len(data) > 0: + res = conn.where("ip=?", (ip,)).save( + 'port,id_rsa', (port, id_rsa,)) + else: + conn.add('ip,port,user,id_rsa,ps,addtime', + (ip, port, user, id_rsa, '', addTime)) + + return mw.returnJson(True, '设置成功!') + + +def delSlaveSSH(version=''): + args = getArgs() + data = checkArgs(args, ['ip']) + if not data[0]: + return data[1] + + ip = args['ip'] + + conn = pSqliteDb('slave_id_rsa', 'pgsql_slave') + conn.where("ip=?", (ip,)).delete() + return mw.returnJson(True, 'ok') + + +def updateSlaveSSH(version=''): + args = getArgs() + data = checkArgs(args, ['ip', 'id_rsa']) + if not data[0]: + return data[1] + + ip = args['ip'] + id_rsa = args['id_rsa'] + conn = pSqliteDb('slave_id_rsa', 'pgsql_slave') + conn.where("ip=?", (ip,)).save('id_rsa', (id_rsa,)) + return mw.returnJson(True, 'ok') + + +def getMasterRepSlaveList(version=''): + args = getArgs() + data = checkArgs(args, ['page', 'page_size']) + if not data[0]: + return data[1] + + page = int(args['page']) + page_size = int(args['page_size']) + data = {} + + conn = pSqliteDb('master_replication_user') + limit = str((page - 1) * page_size) + ',' + str(page_size) + + field = 'id,username,password,accept,ps,addtime' + clist = conn.field(field).limit(limit).order('id desc').select() + count = conn.count() + + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = 'getMasterRepSlaveList' + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.getJson(data) + + +def addMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'password', 'address']) + if not data[0]: + return data[1] + + address = args['address'].strip() + username = args['username'].strip() + password = args['password'].strip() + + if len(password) < 1: + password = mw.md5(time.time())[0:8] + + if not re.match(r"^[\w\.-]+$", username): + return mw.returnJson(False, '用户名不能带有特殊符号!') + checks = ['root', 'mysql', 'test', 'sys', 'panel_logs'] + if username in checks or len(username) < 1: + return mw.returnJson(False, '用户名不合法!') + if password in checks or len(password) < 1: + return mw.returnJson(False, '密码不合法!') + + pdb = pgDb() + psdb = pSqliteDb('master_replication_user') + + if psdb.where("username=?", (username)).count() > 0: + return mw.returnJson(False, '用户已存在!') + + sql = "CREATE ROLE " + username + " login replication password '" + password + "'" + pdb.execute(sql) + + pg_conf = pgHbaConf() + data = mw.readFile(pg_conf) + + data = data + "\nhost replication " + \ + username + " " + address + " md5" + + mw.writeFile(pg_conf, data) + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + psdb.add('username,password,accept,ps,addtime', + (username, password, address, '', addTime)) + return mw.returnJson(True, '添加成功!') + + +def delMasterRepSlaveUser(version=''): + args = getArgs() + data = checkArgs(args, ['username']) + if not data[0]: + return data[1] + + name = args['username'] + + pdb = pgDb() + psdb = pSqliteDb('master_replication_user') + + pdb.execute("drop user " + name) + + pg_hba = pgHbaConf() + old_config = mw.readFile(pg_hba) + new_config = re.sub( + r'host\s*replication\s*{}.*'.format(name), '', old_config).strip() + mw.writeFile(pg_hba, new_config) + + psdb.where("username=?", (args['username'],)).delete() + return mw.returnJson(True, '删除成功!') + + +def getMasterRepSlaveUserCmd(version=''): + args = getArgs() + data = checkArgs(args, ['username', 'db']) + if not data[0]: + return data[1] + + psdb = pSqliteDb('master_replication_user') + mdir = getServerDir() + port = getPgPort() + localIp = mw.getLocalIp() + f = 'username,password' + username = args['username'] + if username == '': + clist = psdb.field(f).limit('1').order('id desc').select() + else: + clist = psdb.field(f).where( + "username=?", (username,)).order('id desc').select() + + if len(clist) == 0: + return mw.returnJson(False, '请添加同步账户!') + + cmd = 'echo "' + clist[0]['password'] + '" | ' + mdir + '/bin/pg_basebackup -Fp --progress -D ' + mdir + \ + '/postgresql/data -h ' + localIp + ' -p ' + port + \ + ' -U ' + clist[0]['username'] + ' --password' + + data = {} + data['cmd'] = cmd + data['info'] = clist + return mw.returnJson(True, 'ok!', data) + + +def slaveSyncCmd(version=''): + data = {} + data['cmd'] = 'cd /www/server/mdserver-web && python3 plugins/postgresql/index.py do_full_sync' + return mw.returnJson(True, 'ok!', data) + + +def writeDbSyncStatus(data): + path = '/tmp/db_async_status.txt' + mw.writeFile(path, json.dumps(data)) + + +def doFullSync(version=''): + + db = pgDb() + + id_rsa_conn = pSqliteDb('slave_id_rsa', 'pgsql_slave') + data = id_rsa_conn.field('ip,port,db_user,id_rsa').find() + + SSH_PRIVATE_KEY = "/tmp/pg_sync_id_rsa.txt" + id_rsa = data['id_rsa'].replace('\\n', '\n') + mw.writeFile(SSH_PRIVATE_KEY, id_rsa) + + ip = data["ip"] + master_port = data['port'] + print("master ip:", ip) + + writeDbSyncStatus({'code': 0, 'msg': '开始同步...', 'progress': 0}) + + import paramiko + paramiko.util.log_to_file('paramiko.log') + ssh = paramiko.SSHClient() + + print(SSH_PRIVATE_KEY) + if not os.path.exists(SSH_PRIVATE_KEY): + writeDbSyncStatus({'code': 0, 'msg': '需要配置SSH......', 'progress': 0}) + return 'fail' + + try: + # ssh.load_system_host_keys() + mw.execShell("chmod 600 " + SSH_PRIVATE_KEY) + key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + print(ip, master_port) + + # pkey=key + # key_filename=SSH_PRIVATE_KEY + ssh.connect(hostname=ip, port=int(master_port), + username='root', pkey=key) + except Exception as e: + print(str(e)) + writeDbSyncStatus( + {'code': 0, 'msg': 'SSH配置错误:' + str(e), 'progress': 0}) + return 'fail' + + writeDbSyncStatus({'code': 0, 'msg': '登录Master成功...', 'progress': 5}) + + print("同步文件", "start") + t = ssh.get_transport() + sftp = paramiko.SFTPClient.from_transport(t) + copy_status = sftp.get( + "/www/server/postgresql/pgsql.db", getServerDir() + "/pgsql.db") + print("同步信息:", copy_status) + print("同步文件", "end") + if copy_status == None: + writeDbSyncStatus({'code': 2, 'msg': '数据库信息同步本地完成...', 'progress': 40}) + + cmd = 'cd /www/server/mdserver-web && python3 plugins/postgresql/index.py get_master_rep_slave_user_cmd {"username":"","db":""}' + stdin, stdout, stderr = ssh.exec_command(cmd) + result = stdout.read() + result = result.decode('utf-8') + cmd_data = json.loads(result) + + print(cmd_data) + username = cmd_data['data']['info'][0]['username'] + password = cmd_data['data']['info'][0]['password'] + + writeDbSyncStatus({'code': 3, 'msg': '数据库获取完成...', 'progress': 45}) + + mdir = getServerDir() + port = getPgPort() + + cmd = mdir + '/bin/pg_basebackup -Fp --progress -D ' + mdir + \ + '/postgresql/data -h ' + ip + ' -p ' + port + \ + ' -U ' + username + ' --password' + print(cmd) + + cmd_tmp = "/tmp/cmd_run.sh" + + cmd_tmp_data = """#!/bin/expect +spawn %s +expect "Password:" + +send "%s\r" + +interact +""" % (cmd, password) + + mw.writeFile(cmd_tmp, cmd_tmp_data) + + os.system("expect " + cmd_tmp) + + writeDbSyncStatus({'code': 6, 'msg': '从库重启完成...', 'progress': 100}) + + os.system("rm -rf " + SSH_PRIVATE_KEY) + os.system("rm -rf " + cmd_tmp) + return True + + +def installPreInspection(version): + return 'ok' + + +def uninstallPreInspection(version): + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + + version = "14.4" + version_pl = getServerDir() + "/version.pl" + if os.path.exists(version_pl): + version = mw.readFile(version_pl).strip() + + if func == 'status': + print(status(version)) + elif func == 'start': + print(start(version)) + elif func == 'stop': + print(stop(version)) + elif func == 'restart': + print(restart(version)) + elif func == 'reload': + print(reload(version)) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection(version)) + elif func == 'conf': + print(getConf()) + elif func == 'pg_hba_conf': + print(pgHbaConf()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'run_info': + print(runInfo()) + elif func == 'run_log': + print(runLog()) + elif func == 'slow_log': + print(getSlowLog()) + elif func == 'db_status': + print(pgDbStatus()) + elif func == 'set_db_status': + print(pgSetDbStatus()) + elif func == 'set_user_pwd': + print(setUserPwd()) + elif func == 'pg_port': + print(getPgPort()) + elif func == 'set_pg_port': + print(setPgPort()) + elif func == 'root_pwd': + print(rootPwd()) + elif func == 'get_db_list': + print(getDbList()) + elif func == 'add_db': + print(addDb()) + elif func == 'del_db': + print(delDb()) + elif func == 'set_db_rw': + print(setDbRw(version)) + elif func == 'get_db_access': + print(getDbAccess()) + elif func == 'set_db_access': + print(setDbAccess()) + elif func == 'pg_back': + print(pgBack()) + elif func == 'pg_back_list': + print(pgBackList()) + elif func == 'import_db_backup': + print(importDbBackup()) + elif func == 'delete_db_backup': + print(deleteDbBackup()) + elif func == 'sync_get_databases': + print(syncGetDatabases()) + elif func == 'add_slave_ssh': + print(addSlaveSSH(version)) + elif func == 'get_slave_list': + print(getSlaveList(version)) + elif func == 'del_slave_ssh': + print(delSlaveSSH(version)) + elif func == 'update_slave_ssh': + print(updateSlaveSSH(version)) + elif func == 'get_slave_ssh_list': + print(getSlaveSSHList(version)) + elif func == 'get_slave_ssh_by_ip': + print(getSlaveSSHByIp(version)) + elif func == 'get_master_status': + print(getMasterStatus(version)) + elif func == 'set_master_status': + print(setMasterStatus(version)) + elif func == 'set_slave_status': + print(setSlaveStatus(version)) + elif func == 'get_master_rep_slave_list': + print(getMasterRepSlaveList(version)) + elif func == 'add_master_rep_slave_user': + print(addMasterRepSlaveUser(version)) + elif func == 'del_master_rep_slave_user': + print(delMasterRepSlaveUser(version)) + elif func == 'get_master_rep_slave_user_cmd': + print(getMasterRepSlaveUserCmd(version)) + elif func == 'slave_sync_cmd': + print(slaveSyncCmd(version)) + elif func == 'do_full_sync': + print(doFullSync(version)) + else: + print('error') diff --git a/plugins/postgresql/info.json b/plugins/postgresql/info.json new file mode 100755 index 000000000..3fcd8c84b --- /dev/null +++ b/plugins/postgresql/info.json @@ -0,0 +1,20 @@ +{ + "hook":["database"], + "sort": 6, + "title":"PostgreSQL", + "tip":"soft", + "name":"postgresql", + "type":"运行环境", + "ps":"功能强大的开源数据库", + "coexist": false, + "install_pre_inspection":true, + "uninstall_pre_inspection":true, + "versions":["14","15","16","17"], + "shell":"install.sh", + "checks":"server/postgresql", + "path":"server/postgresql", + "author":"postgresql", + "home":"https://www.postgresql.org/", + "date":"2022-08-07", + "pid": "2" +} \ No newline at end of file diff --git a/plugins/postgresql/init.d/postgresql.service.tpl b/plugins/postgresql/init.d/postgresql.service.tpl new file mode 100644 index 000000000..1dfaadd73 --- /dev/null +++ b/plugins/postgresql/init.d/postgresql.service.tpl @@ -0,0 +1,16 @@ +[Unit] +Description=PostgreSQL: a powerful open source database +After=network.target + +[Service] +Type=forking +User=postgres +Group=postgres +WorkingDirectory={$APP_PATH} +ExecStart={$APP_PATH}/bin/pg_ctl start -D {$APP_PATH}/data +ExecReload={$APP_PATH}/bin/pg_ctl restart -D {$APP_PATH}/data +ExecStop={$APP_PATH}/bin/pg_ctl stop -D {$APP_PATH}/data +PrivateTmp=false + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/postgresql/init.d/postgresql.tpl b/plugins/postgresql/init.d/postgresql.tpl new file mode 100644 index 000000000..b4b2f7acd --- /dev/null +++ b/plugins/postgresql/init.d/postgresql.tpl @@ -0,0 +1,67 @@ +#!/bin/bash +# chkconfig: 2345 55 25 +# description: PostgreSQL Service + +### BEGIN INIT INFO +# Provides: Midoks +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts PostgreSQL +# Description: starts the PostgreSQL +### END INIT INFO + + +PATH=/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export LC_ALL="en_US.UTF-8" + +MW_PATH={$SERVER_PATH} +PATH=$PATH:$MW_PATH/bin + +if [ -f $MW_PATH/bin/activate ];then + source $MW_PATH/bin/activate +fi + +pg_start() +{ + touch {$APP_PATH}/logs/server.log + {$APP_PATH}/bin/pg_ctl -D {$APP_PATH}/data -l {$APP_PATH}/logs/server.log start +} + + +pg_stop() +{ + {$APP_PATH}/bin/pg_ctl -D {$APP_PATH}/data -l {$APP_PATH}/logs/server.log stop +} + + +pg_status() +{ + isStart=$(ps aux | grep 'postgres'| grep -v grep | grep -v 'postgresql status' | awk '{print $2}') + if [ "$isStart" != '' ];then + echo -e "\033[32mPostgreSQL (pid $isStart) already running\033[0m" + else + echo -e "\033[31mPostgreSQL not running\033[0m" + fi +} + + +pg_reload() +{ + echo '' > {$APP_PATH}/logs/server.log + {$APP_PATH}/bin/pg_ctl -D {$APP_PATH}/data -l {$APP_PATH}/logs/server.log reload +} + + + +case "$1" in + 'start') pg_start;; + 'stop') pg_stop;; + 'reload') pg_reload;; + 'restart') + pg_stop + pg_start;; + 'status') + pg_status;; +esac diff --git a/plugins/postgresql/install.sh b/plugins/postgresql/install.sh new file mode 100755 index 000000000..8d9b534ca --- /dev/null +++ b/plugins/postgresql/install.sh @@ -0,0 +1,50 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# cd /www/server/mdserver-web/plugins/postgresql && bash install.sh install 16 + +action=$1 +type=$2 + +VERSION=(${type//./ }) + +pip install psycopg2-binary +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate + pip install psycopg2-binary +fi + + +if [ "${2}" == "" ];then + echo '缺少安装脚本...' + exit 0 +fi + +if [ ! -d $curPath/versions/$VERSION ];then + echo '缺少安装脚本2...' + exit 0 +fi + +if [ "${action}" == "uninstall" ];then + if [ -f /usr/lib/systemd/system/postgresql.service ] || [ -f /lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /usr/lib/systemd/system/postgresql.service + rm -rf /lib/systemd/system/postgresql.service + systemctl daemon-reload + fi +fi + +sh -x $curPath/versions/$VERSION/install.sh $1 + +if [ "${action}" == "install" ] && [ -d $serverPath/postgresql ];then + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/postgresql/index.py start ${type} + cd ${rootPath} && python3 ${rootPath}/plugins/postgresql/index.py initd_install ${type} +fi diff --git a/plugins/postgresql/js/postgresql.js b/plugins/postgresql/js/postgresql.js new file mode 100755 index 000000000..5d2edf8c6 --- /dev/null +++ b/plugins/postgresql/js/postgresql.js @@ -0,0 +1,1332 @@ +function str2Obj(str){ + var data = {}; + kv = str.split('&'); + for(i in kv){ + v = kv[i].split('='); + data[v[0]] = v[1]; + } + return data; +} + +function randomStr(b) { + b = b || 32; + var c = "AaBbCcDdEeFfGHhiJjKkLMmNnPpRSrTsWtXwYxZyz"; + var a = c.length; + var d = ""; + for(i = 0; i < b; i++) { + d += c.charAt(Math.floor(Math.random() * a)) + } + return d +} + +function myPost(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'postgresql', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myPostN(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + $.post('/plugins/run', {name:'postgresql', func:method, args:_args}, function(data) { + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + return syncPost('/plugins/run', {name:'mysql', func:method, args:_args}); +} + +function runInfo(){ + myPost('run_info','',function(data){ + + var rdata = $.parseJSON(data.data); + if (typeof(rdata['status']) != 'undefined'){ + layer.msg(rdata['msg'],{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var con = '
                                \ + \ + \ + \ + \ + \ +
                                启动时间' + rdata.uptime + '进程数' +rdata.progress_num+ '
                                总连接次数' + rdata.connections + 'PID' +rdata.pid+ '
                                占用空间' + rdata.pg_size + '占用内存' +rdata.pg_mem+ '
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                表进程已经锁住的物理内存的大小' + rdata.pg_vm_lock + '
                                数据库分配到物理内存的峰值' + rdata.pg_vm_high + '
                                进程数据段的大小' + rdata.pg_vm_data_size + '
                                进程堆栈段的大小' + rdata.pg_vm_sk_size + '
                                进程代码的大小' + rdata.pg_vm_code_size + '
                                进程所使用LIB库的大小' + rdata.pg_vm_lib_size + '
                                进程占用Swap的大小' + rdata.pg_vm_swap_size + '
                                占用的页表的大小' + rdata.pg_vm_page_size + '
                                当前待处理信号的个数' + rdata.pg_sigq + '
                                '; + $(".soft-man-con").html(con); + }); +} + + +function pgPort(){ + myPost('pg_port','',function(data){ + var con = '
                                \ +
                                \ + \ + \ +
                                '; + $(".soft-man-con").html(con); + + $('#btn_change_port').click(function(){ + var port = $("input[name='port']").val(); + myPost('set_pg_port','port='+port,function(data){ + var rdata = $.parseJSON(data.data); + if (rdata.status){ + layer.msg('修改成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); + }); + }); +} + + +//数据库配置状态 +function pgPerfOpt() { + //获取MySQL配置 + myPost('db_status','',function(data){ + var rdata = $.parseJSON(data.data); + var html_p = ''; + for (i in rdata){ + if (i != 'status' ){ + var v = rdata[i]; + html_p += '

                                '+i+''+v[1] +', ' + v[2] + '

                                ' + } + } + + var memCon = '
                                '+html_p + +'
                                \ +
                                \ + \ +
                                \ +
                                ' + +'
                                ' + + $(".soft-man-con").html(memCon); + + $("#pg_conf").change(function (e) { +   e.preventDefault(); + var data = {}; + $('#pg_conf p input').each(function (index, element) { + data[$(this).attr('name')] = $(this).val() + ($(this).attr('unit') || ''); + }) + // console.log(data); + myPost('set_db_status', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + },{ icon: rdata.status ? 1 : 2 }); + }); + return false; + }); + }); +} + +function reBootPgSqld(){ + pluginOpService('postgresql','restart',''); +} + +//设置PG配置参数 +function setPgConf() { + return false; +} + +function syncGetDatabase(){ + myPost('sync_get_databases', null, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function syncToDatabase(type){ + var data = []; + $('input[type="checkbox"].check:checked').each(function () { + if (!isNaN($(this).val())) data.push($(this).val()); + }); + var postData = 'type='+type+'&ids='+JSON.stringify(data); + myPost('sync_to_databases', postData, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + showMsg(rdata.msg,function(){ + dbList(); + },{ icon: rdata.status ? 1 : 2 }); + }); +} + +function setRootPwd(type, pwd){ + if (type==1){ + var data = $("#mod_pwd").serialize(); + myPost('set_root_pwd', data, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + showMsg(rdata.msg,function(){ + dbList(); + $('.layui-layer-close1').click(); + },{icon: rdata.status ? 1 : 2}); + }); + return; + } + + var index = layer.open({ + type: 1, + area: '500px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["取消","提交"], + content: "
                                \ +
                                \ + postgres密码\ +
                                \ +
                                \ +
                                ", + yes:function(){ + setRootPwd(1); + } + }); +} + +function showHidePass(obj){ + var a = "glyphicon-eye-open"; + var b = "glyphicon-eye-close"; + + if($(obj).hasClass(a)){ + $(obj).removeClass(a).addClass(b); + $(obj).prev().text($(obj).prev().attr('data-pw')) + } + else{ + $(obj).removeClass(b).addClass(a); + $(obj).prev().text('***'); + } +} + +function checkSelect(){ + setTimeout(function () { + var num = $('input[type="checkbox"].check:checked').length; + // console.log(num); + if (num == 1) { + $('button[batch="true"]').hide(); + $('button[batch="false"]').show(); + }else if (num>1){ + $('button[batch="true"]').show(); + $('button[batch="false"]').show(); + }else{ + $('button[batch="true"]').hide(); + $('button[batch="false"]').hide(); + } + },5) +} + +function setDbRw(id,username,val){ + myPost('set_db_rw',{id:id,username:username,rw:val}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function(){ + dbList(); + },{icon:rdata.status ? 1 : 5,shade: [0.3, '#000']}, 2000); + }); +} + +function setDbAccess(name){ + myPost('get_db_access','name='+name, function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,shade: [0.3, '#000']}); + return; + } + + layer.open({ + type: 1, + area: '500px', + title: '设置数据库权限', + closeBtn: 1, + shift: 5, + btn:["提交","取消"], + shadeClose: true, + content: "
                                \ +
                                \ + 访问权限\ +
                                \ + \ +
                                \ +
                                \ +
                                ", + success:function(){ + if (rdata.msg == '127.0.0.1/32'){ + $('select[name="dataAccess"]').find("option[value='127.0.0.1/32']").attr("selected",true); + } else if (rdata.msg == '0.0.0.0/0'){ + $('select[name="dataAccess"]').find('option[value="0.0.0.0/0"]').attr("selected",true); + } else { + $('select[name="dataAccess"]').find('option[value="ip"]').attr("selected",true); + $('select[name="dataAccess"]').after(""); + } + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('input[name="address"]').remove() + } + }); + }, + yes:function(index){ + var data = $("#set_db_access").serialize(); + data = decodeURIComponent(data); + var dataObj = str2Obj(data); + if(!dataObj['access']){ + dataObj['access'] = dataObj['dataAccess']; + if ( dataObj['dataAccess'] == 'ip'){ + if (dataObj['address']==''){ + layer.msg('IP地址不能空!',{icon:2,shade: [0.3, '#000']}); + return; + } + dataObj['access'] = dataObj['address']; + } + } + dataObj['name'] = name; + // console.log(dataObj); + myPost('set_db_access', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); + + }); +} + +function setDbPass(id, username, password){ + + var index = layer.open({ + type: 1, + area: '500px', + title: '修改数据库密码', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                                \ +
                                \ + 用户名\ +
                                \ +
                                \ +
                                \ + 密码\ +
                                \ +
                                \ + \ +
                                ", + yes:function(index){ + var data = $("#mod_pwd").serialize(); + myPost('set_user_pwd', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + dbList(); + },{icon: rdata.status ? 1 : 2}); + }); + } + }); +} + +function addDatabase(type,layer_index){ + if (type == 1){ + var data = $("#add_db").serialize(); + data = decodeURIComponent(data); + var dataObj = str2Obj(data); + if(!dataObj['address']){ + dataObj['address'] = dataObj['dataAccess']; + } + + var ip_segment = $('[name="dataAccess"]').val(); + if ($('[name="dataAccess"]').val() == 'ip'){ + dataObj['listen_ip'] = ip_segment; + } + myPost('add_db', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(layer_index); + if (rdata.status){ + dbList(); + } + },{icon: rdata.status ? 1 : 2},2000); + }); + return; + } + + layer.open({ + type: 1, + area: '450px', + title: '添加数据库', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","取消"], + content: "
                                \ +
                                \ + 数据库名\ +
                                \ + \ +
                                \ +
                                \ +
                                用户名
                                \ +
                                \ + 密码\ +
                                \ +
                                \ +
                                \ +
                                \ + 访问权限\ +
                                \ + \ + \ +
                                \ +
                                \ +
                                ", + success:function(){ + + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='db_user']").val(v); + }); + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $('input[name="ip_segment"]').show(); + } else { + $('input[name="ip_segment"]').hide(); + + } + }); + }, + yes:function(index){ + addDatabase(1,index); + } + }); + +} + +function delDb(id, name){ + safeMessage('删除['+name+']','您真的要删除['+name+']吗?',function(){ + var data='id='+id+'&name='+name + myPost('del_db', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + dbList(); + $('.layui-layer-close1').click(); + },{icon: rdata.status ? 1 : 2}, 600); + }); + }); +} + +function delDbBatch(){ + var arr = []; + $('input[type="checkbox"].check:checked').each(function () { + var _val = $(this).val(); + var _name = $(this).parent().next().text(); + if (!isNaN(_val)) { + arr.push({'id':_val,'name':_name}); + } + }); + + safeMessage('批量删除数据库','您共选择了[2]个数据库,删除后将无法恢复,真的要删除吗?',function(){ + var i = 0; + $(arr).each(function(){ + var data = myAsyncPost('del_db', this); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + } + i++; + }); + + var msg = '成功删除['+i+']个数据库!'; + showMsg(msg,function(){ + dbList(); + },{icon: 1}, 600); + }); +} + + +function setDbPs(id, name, obj) { + var _span = $(obj); + var _input = $(""); + _span.hide().after(_input); + _input.focus(); + _input.blur(function(){ + $(this).remove(); + var ps = _input.val(); + _span.text(ps).show(); + var data = {name:name,id:id,ps:ps}; + myPost('set_db_ps', data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + }); + _input.keyup(function(){ + if(event.keyCode == 13){ + _input.trigger('blur'); + } + }); +} + +function delBackup(filename,name){ + myPost('delete_db_backup',{filename:filename},function(){ + layer.msg('执行成功!'); + setTimeout(function(){ + $('.layui-layer-close2').click(); + setBackup(name); + },2000); + }); +} + +function downloadBackup(file){ + window.open('/files/download?filename='+encodeURIComponent(file)); +} + +function importBackup(file,name){ + myPost('import_db_backup',{file:file,name:name}, function(data){ + // console.log(data); + layer.msg('执行成功!'); + }); +} + +function setBackup(db_name,obj){ + myPost('pg_back_list', {name:db_name}, function(data){ + + var rdata = $.parseJSON(data.data); + console.log(rdata); + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + tbody += '\ + ' + rdata.data[i]['name'] + '\ + ' + rdata.data[i]['size'] + '\ + ' + rdata.data[i]['time'] + '\ + \ + 导入 | \ + 下载 | \ + 删除\ + \ + '; + } + + var s = layer.open({ + type: 1, + title: "数据库备份详情", + area: ['600px', '280px'], + closeBtn: 2, + shadeClose: false, + content: '
                                \ +
                                \ + \ +
                                \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + ' + tbody + '\ +
                                文件名称文件大小备份时间操作
                                \ +
                                \ +
                                \ +
                                ' + }); + + $('#btn_backup').click(function(){ + myPost('pg_back',{name:db_name}, function(data){ + layer.msg('执行成功!'); + + setTimeout(function(){ + layer.close(s); + setBackup(db_name,obj); + },2000); + }); + }); + }); +} + + +function dbList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + myPost('get_db_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list +=''; + list += '' + rdata.data[i]['name'] +''; + list += '' + rdata.data[i]['username'] +''; + list += '' + + '***' + + ''+ + ''+ + ''; + + + list += ''+rdata.data[i]['ps']+''; + list += ''; + + list += ''+(rdata.data[i]['is_backup']?'备份':'未备份') +' | '; + + var rw = ''; + var rw_change = 'all'; + if (typeof(rdata.data[i]['rw'])!='undefined'){ + var rw_val = '读写'; + if (rdata.data[i]['rw'] == 'all'){ + rw_val = "所有"; + rw_change = 'rw'; + } else if (rdata.data[i]['rw'] == 'rw'){ + rw_val = "读写"; + rw_change = 'r'; + } else if (rdata.data[i]['rw'] == 'r'){ + rw_val = "只读"; + rw_change = 'all'; + } + rw = ''+rw_val+' | '; + } + + + list += '权限 | ' + + rw + + '改密 | ' + + '删除' + + ''; + list += ''; + } + + // + var con = '
                                \ + \ + \ + \ + \ + \ +
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                数据库名用户名密码备注操作
                                \ +
                                \ +
                                \ +
                                \ + 从服务器获取\ +
                                \ +
                                \ +
                                '; + + con += ''; + + $(".soft-man-con").html(con); + $('#databasePage').html(rdata.page); + + readerTableChecked(); + }); +} +///////////////////////////////// 主从 ///////////////////////// + + +function setDbMaster(name){ + myPost('set_db_master', {name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + masterOrSlaveConf(); + }, 2000); + }); +} + + +function setDbSlave(name){ + myPost('set_db_slave', {name:name}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + masterOrSlaveConf(); + }, 2000); + }); +} + + +function repeatMSPwd(a) { + $("#MyPassword").val(randomStr(a)) +} + +function addMasterRepSlaveUser(){ + layer.open({ + type: 1, + area: '500px', + title: '添加同步账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","取消"], + content: "
                                \ +
                                用户名
                                \ +
                                \ + 密码\ +
                                \ + \ + \ +
                                \ +
                                \ +
                                \ + 网段\ +
                                \ + \ +
                                \ +
                                \ + \ +
                                ", + success:function(){ + $("input[name='name']").keyup(function(){ + var v = $(this).val(); + $("input[name='db_user']").val(v); + $("input[name='ps']").val(v); + }); + + $('select[name="dataAccess"]').change(function(){ + var v = $(this).val(); + if (v == 'ip'){ + $(this).after(""); + } else { + $('#dataAccess_subid').remove(); + } + }); + }, + yes:function(index){ + var data = $("#add_master").serialize(); + data = decodeURIComponent(data); + var dataObj = str2Obj(data); + if(!dataObj['address']){ + dataObj['address'] = dataObj['dataAccess']; + } + + myPost('add_master_rep_slave_user', dataObj, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + layer.close(index); + if (rdata.status){ + getMasterRepSlaveList(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); +} + + + +function updateMasterRepSlaveUser(username){ + + var index = layer.open({ + type: 1, + area: '500px', + title: '更新账户', + closeBtn: 1, + shift: 5, + shadeClose: true, + content: "
                                \ +
                                用户名
                                \ +
                                \ + 密码\ +
                                \ +
                                \ + \ +
                                \ + \ +
                                \ +
                                ", + }); + + $('#submit_update_master').click(function(){ + var data = $("#update_master").serialize(); + data = decodeURIComponent(data); + var dataObj = str2Obj(data); + myPost('update_master_rep_slave_user', data, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getMasterRepSlaveList(); + } + $('.layui-layer-close1').click(); + },{icon: rdata.status ? 1 : 2},600); + }); + }); +} + +function getMasterRepSlaveUserCmd(username, db=''){ + myPost('get_master_rep_slave_user_cmd', {username:username,db:db}, function(data){ + var rdata = $.parseJSON(data.data); + + if (!rdata['status']){ + layer.msg(rdata['msg']); + return; + } + + var cmd = rdata.data['cmd']; + + var loadOpen = layer.open({ + type: 1, + title: '同步命令', + area: '500px', + content:"
                                \ +
                                "+cmd+"
                                \ +
                                \ + \ +
                                \ +
                                ", + }); + + + copyPass(cmd); + $('.class-copy-cmd').click(function(){ + copyPass(cmd); + }); + }); +} + +function delMasterRepSlaveUser(username){ + myPost('del_master_rep_slave_user', {username:username}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg); + + $('.layui-layer-close1').click(); + + setTimeout(function(){ + getMasterRepSlaveList(); + },1000); + }); +} + +function getMasterRepSlaveList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + myPost('get_master_rep_slave_list', _data, function(data){ + // console.log(data); + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e){ + console.log(e); + } + var list = ''; + // console.log(rdata['data']); + var user_list = rdata['data']; + for (i in user_list) { + // console.log(i); + var name = user_list[i]['username']; + list += ''+name+'\ + '+user_list[i]['password']+'\ + \ + 删除 | \ + 从库同步命令 \ + \ + '; + } + + $('#get_master_rep_slave_list_page tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + +function getMasterRepSlaveListPage(){ + var page = '
                                '; + page += '
                                添加同步账户
                                '; + + var loadOpen = layer.open({ + type: 1, + title: '同步账户列表', + area: '500px', + content:"
                                \ +
                                \ +
                                \ + \ + \ +
                                用户名密码操作
                                \ + "+page +"\ +
                                \ +
                                ", + success:function(){ + getMasterRepSlaveList(); + } + }); +} + + +function deleteSlave(){ + myPost('delete_slave', {}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata['msg'], function(){ + masterOrSlaveConf(); + },{},3000); + }); +} + + +function getFullSyncStatus(db){ + var timeId = null; + + var btn = '
                                开始
                                '; + var loadOpen = layer.open({ + type: 1, + title: '全量同步['+db+']', + area: '500px', + content:"
                                \ +
                                \ + \ +
                                \ +
                                0%
                                \ +
                                \ +
                                \ + "+btn+"\ +
                                ", + cancel: function(){ + clearInterval(timeId); + } + }); + + function fullSync(db,begin){ + + myPostN('full_sync', {db:db,begin:begin}, function(data){ + var rdata = $.parseJSON(data.data); + $('#full_msg').text(rdata['msg']); + $('.progress-bar').css('width',rdata['progress']+'%'); + $('.progress-bar').text(rdata['progress']+'%'); + + if (rdata['code']==6 ||rdata['code']<0){ + layer.msg(rdata['msg']); + clearInterval(timeId); + $("#begin_full_sync").attr('data-status','init'); + } + }); + } + + $('#begin_full_sync').click(function(){ + var val = $(this).attr('data-status'); + if (val == 'init'){ + fullSync(db,1); + timeId = setInterval(function(){ + fullSync(db,0); + }, 1000); + $(this).attr('data-status','starting'); + } else { + layer.msg("正在同步中.."); + } + }); +} + +function addSlaveSSH(ip=''){ + + myPost('get_slave_ssh_by_ip', {ip:ip}, function(rdata){ + + var rdata = $.parseJSON(rdata.data); + + var ip = '127.0.0.1'; + var port = "22"; + var id_rsa = ''; + var db_user =''; + + if (rdata.data.length>0){ + ip = rdata.data[0]['ip']; + port = rdata.data[0]['port']; + id_rsa = rdata.data[0]['id_rsa']; + db_user = rdata.data[0]['db_user']; + } + + layer.open({ + type: 1, + area: ['500px','450px'], + title: '添加SSH', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["确认","取消"], + content: "
                                \ +
                                IP
                                \ +
                                端口
                                \ +
                                \ + ID_RSA\ +
                                \ +
                                \ + \ +
                                ", + success:function(){ + $('textarea[name="id_rsa"]').html(id_rsa); + }, + yes:function(index){ + var ip = $('input[name="ip"]').val(); + var port = $('input[name="port"]').val(); + var db_user = $('input[name="db_user"]').val(); + var id_rsa = $('textarea[name="id_rsa"]').val(); + + var data = {ip:ip,port:port,id_rsa:id_rsa,db_user:db_user}; + myPost('add_slave_ssh', data, function(data){ + layer.close(index); + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg,function(){ + if (rdata.status){ + getSlaveSSHPage(); + } + },{icon: rdata.status ? 1 : 2},600); + }); + } + }); + }); +} + + +function delSlaveSSH(ip){ + myPost('del_slave_ssh', {ip:ip}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg, {icon: rdata.status ? 1 : 2}); + getSlaveSSHPage(); + }); +} + +function getSlaveSSHPage(page=1){ + var _data = {}; + _data['page'] = page; + _data['page_size'] = 5; + _data['tojs'] ='getSlaveSSHPage'; + myPost('get_slave_ssh_list', _data, function(data){ + var layerId = null; + var rdata = []; + try { + rdata = $.parseJSON(data.data); + } catch(e) { + console.log(e); + } + var list = ''; + var ssh_list = rdata['data']; + for (i in ssh_list) { + var ip = ssh_list[i]['ip']; + var port = ssh_list[i]['port']; + + var id_rsa = '未设置'; + if ( ssh_list[i]['port'] != ''){ + id_rsa = '已设置'; + } + + var db_user = '未设置'; + if ( ssh_list[i]['db_user'] != ''){ + db_user = ssh_list[i]['db_user']; + } + + list += ''+ip+'\ + '+port+'\ + '+id_rsa+'\ + \ + 修改 | \ + 删除\ + \ + '; + } + + $('.get-slave-ssh-list tbody').html(list); + $('.dataTables_paginate_4').html(rdata['page']); + }); +} + + +function getSlaveSSHList(page=1){ + + var page = '
                                '; + page += '
                                添加SSH
                                '; + + layerId = layer.open({ + type: 1, + title: 'SSH列表', + area: '500px', + content:"
                                \ +
                                \ +
                                \ + \ + \ +
                                IPPORTSSH操作
                                \ + "+page +"\ +
                                \ +
                                ", + success:function(){ + getSlaveSSHPage(1); + } + }); +} + +function handlerRun(){ + myPostN('get_slave_sync_cmd', {}, function(data){ + var rdata = $.parseJSON(data.data); + var cmd = rdata['data']; + var loadOpen = layer.open({ + type: 1, + title: '手动执行', + area: '500px', + content:"
                                \ +
                                "+cmd+"
                                \ +
                                \ + \ +
                                \ +
                                ", + }); + copyPass(cmd); + $('.class-copy-cmd').click(function(){ + copyPass(cmd); + }); + }); +} + +function slaveSyncCmd(){ + myPost('slave_sync_cmd', {}, function(data){ + var rdata = $.parseJSON(data.data); + + if (!rdata['status']){ + layer.msg(rdata['msg']); + return; + } + + var cmd = rdata.data['cmd']; + + var loadOpen = layer.open({ + type: 1, + title: '同步命令', + area: '500px', + content:"
                                \ +
                                "+cmd+"
                                \ +
                                \ + \ +
                                \ +
                                ", + }); + + copyPass(cmd); + $('.class-copy-cmd').click(function(){ + copyPass(cmd); + }); + }); +} + +function masterOrSlaveConf(version=''){ + + function getAsyncMasterDbList(){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + + myPost('get_slave_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + var list = ''; + for(i in rdata.data){ + + var v = rdata.data[i]; + var status = "异常"; + if (v['Slave_SQL_Running'] == 'Yes' && v['Slave_IO_Running'] == 'Yes'){ + status = "正常"; + } + + list += ''; + list += '' + rdata.data[i]['Master_Host'] +''; + list += '' + rdata.data[i]['Master_Port'] +''; + list += '' + rdata.data[i]['Master_User'] +''; + list += '' + rdata.data[i]['Master_Log_File'] +''; + list += '' + rdata.data[i]['Slave_IO_Running'] +''; + list += '' + rdata.data[i]['Slave_SQL_Running'] +''; + list += '' + status +''; + list += '' + + '删除' + + ''; + list += ''; + } + + var con = '
                                \ +
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                主[服务]端口用户日志IOSQL状态操作
                                \ +
                                \ +
                                '; + + //
                                \ + //
                                \ + // 添加\ + //
                                + $(".table_slave_status_list").html(con); + }); + } + + function getMasterStatus(){ + myPost('get_master_status', '', function(data){ + var rdata = $.parseJSON(data.data); + // console.log('mode:',rdata.data); + var rdata = rdata.data; + var limitCon = '\ +

                                \ + 主从同步模式\ + \ +

                                \ +
                                \ +

                                \ + Master[主]配置\ + \ + \ +

                                \ +
                                \ + \ +

                                \ + Slave[从]配置\ + \ + \ + \ +

                                \ +
                                \ + \ +
                                \ + \ +
                                \ + '; + $(".soft-man-con").html(limitCon); + + //设置主服务器配置 + $(".btn-master").click(function () { + myPost('set_master_status', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + getMasterStatus(); + }, 3000); + }); + }); + + $(".btn-slave").click(function () { + myPost('set_slave_status', 'close=change', function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + setTimeout(function(){ + getMasterStatus(); + }, 3000); + }); + }); + + if (rdata.slave_status){ + getAsyncMasterDbList(); + } + }); + } + getMasterStatus(); +} diff --git a/plugins/postgresql/versions/14/install.sh b/plugins/postgresql/versions/14/install.sh new file mode 100755 index 000000000..54aa854eb --- /dev/null +++ b/plugins/postgresql/versions/14/install.sh @@ -0,0 +1,128 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +#https://www.postgresql.org/ftp/source/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +postgreDir=${serverPath}/source/postgresql + +VERSION=14.18 + +# su - postgres -c "/www/server/postgresql/bin/pg_ctl start -D /www/server/postgresql/data" + +Install_App() +{ + mkdir -p ${postgreDir} + echo '正在安装脚本文件...' + + if id postgres &> /dev/null ;then + echo "postgres uid is `id -u postgres`" + echo "postgres shell is `grep "^postgres:" /etc/passwd |cut -d':' -f7 `" + else + groupadd postgres + useradd -g postgres postgres + fi + + if [ ! -d /home/postgres ];then + mkdir -p /home/postgres + fi + + # if [ "$sysName" != "Darwin" ];then + # mkdir -p /var/log/mariadb + # touch /var/log/mariadb/mariadb.log + # fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + # for stable installation + if [ "$cpuCore" -gt "1" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + fi + # ----- cpu end ------ + + if [ ! -f ${postgreDir}/postgresql-${VERSION}.tar.bz2 ];then + wget --no-check-certificate -O ${postgreDir}/postgresql-${VERSION}.tar.bz2 --tries=3 https://ftp.postgresql.org/pub/source/v${VERSION}/postgresql-${VERSION}.tar.bz2 + fi + + if [ ! -d ${postgreDir}/postgresql-${VERSION} ];then + cd ${postgreDir} && tar -jxvf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi + + + if [ ! -d $serverPath/postgresql ];then + cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl + # --with-pgport=33206 + + echo "cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl" + # --with-pgport=33206 + make -j${cpuCore} && make install && make clean + fi + + if [ -d $serverPath/postgresql ];then + echo "${VERSION}" > $serverPath/postgresql/version.pl + echo '安装postgresql成功' + else + echo '安装postgresql失败' + rm -rf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /usr/lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f $serverPath/postgresql/initd/postgresql ];then + $serverPath/postgresql/initd/postgresql stop + fi + + if [ -d $serverPath/postgresql ];then + rm -rf $serverPath/postgresql + fi + + echo '卸载[postgresql]完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/postgresql/versions/15/install.sh b/plugins/postgresql/versions/15/install.sh new file mode 100755 index 000000000..936de20ea --- /dev/null +++ b/plugins/postgresql/versions/15/install.sh @@ -0,0 +1,127 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +#https://www.postgresql.org/ftp/source/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +postgreDir=${serverPath}/source/postgresql +VERSION=15.13 + +# su - postgres -c "/www/server/postgresql/bin/pg_ctl start -D /www/server/postgresql/data" + +Install_App() +{ + mkdir -p ${postgreDir} + echo '正在安装脚本文件...' + + if id postgres &> /dev/null ;then + echo "postgres uid is `id -u postgres`" + echo "postgres shell is `grep "^postgres:" /etc/passwd |cut -d':' -f7 `" + else + groupadd postgres + useradd -g postgres postgres + fi + + if [ ! -d /home/postgres ];then + mkdir -p /home/postgres + fi + + # if [ "$sysName" != "Darwin" ];then + # mkdir -p /var/log/mariadb + # touch /var/log/mariadb/mariadb.log + # fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + # for stable installation + if [ "$cpuCore" -gt "1" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + fi + # ----- cpu end ------ + + if [ ! -f ${postgreDir}/postgresql-${VERSION}.tar.bz2 ];then + wget --no-check-certificate -O ${postgreDir}/postgresql-${VERSION}.tar.bz2 --tries=3 https://ftp.postgresql.org/pub/source/v${VERSION}/postgresql-${VERSION}.tar.bz2 + fi + + if [ ! -d ${postgreDir}/postgresql-${VERSION} ];then + cd ${postgreDir} && tar -jxvf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi + + + if [ ! -d $serverPath/postgresql ];then + cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl + # --with-pgport=33206 + + echo "cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl" + # --with-pgport=33206 + make -j${cpuCore} && make install && make clean + fi + + if [ -d $serverPath/postgresql ];then + echo "${VERSION}" > $serverPath/postgresql/version.pl + echo '安装postgresql成功' + else + echo '安装postgresql失败' + rm -rf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /usr/lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f $serverPath/postgresql/initd/postgresql ];then + $serverPath/postgresql/initd/postgresql stop + fi + + if [ -d $serverPath/postgresql ];then + rm -rf $serverPath/postgresql + fi + + echo '卸载[postgresql]完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/postgresql/versions/16/install.sh b/plugins/postgresql/versions/16/install.sh new file mode 100755 index 000000000..fea8b3986 --- /dev/null +++ b/plugins/postgresql/versions/16/install.sh @@ -0,0 +1,128 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +#https://www.postgresql.org/ftp/source/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +postgreDir=${serverPath}/source/postgresql + +VERSION=16.9 + +# su - postgres -c "/www/server/postgresql/bin/pg_ctl start -D /www/server/postgresql/data" + +Install_App() +{ + mkdir -p ${postgreDir} + echo '正在安装脚本文件...' + + if id postgres &> /dev/null ;then + echo "postgres uid is `id -u postgres`" + echo "postgres shell is `grep "^postgres:" /etc/passwd |cut -d':' -f7 `" + else + groupadd postgres + useradd -g postgres postgres + fi + + if [ ! -d /home/postgres ];then + mkdir -p /home/postgres + fi + + # if [ "$sysName" != "Darwin" ];then + # mkdir -p /var/log/mariadb + # touch /var/log/mariadb/mariadb.log + # fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + # for stable installation + if [ "$cpuCore" -gt "1" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + fi + # ----- cpu end ------ + + if [ ! -f ${postgreDir}/postgresql-${VERSION}.tar.bz2 ];then + wget --no-check-certificate -O ${postgreDir}/postgresql-${VERSION}.tar.bz2 --tries=3 https://ftp.postgresql.org/pub/source/v${VERSION}/postgresql-${VERSION}.tar.bz2 + fi + + if [ ! -d ${postgreDir}/postgresql-${VERSION} ];then + cd ${postgreDir} && tar -jxvf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi + + + if [ ! -d $serverPath/postgresql ];then + cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl + # --with-pgport=33206 + + echo "cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl" + # --with-pgport=33206 + make -j${cpuCore} && make install && make clean + fi + + if [ -d $serverPath/postgresql ];then + echo "${VERSION}" > $serverPath/postgresql/version.pl + echo '安装postgresql成功' + else + echo '安装postgresql失败' + rm -rf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /usr/lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f $serverPath/postgresql/initd/postgresql ];then + $serverPath/postgresql/initd/postgresql stop + fi + + if [ -d $serverPath/postgresql ];then + rm -rf $serverPath/postgresql + fi + + echo '卸载[postgresql]完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/postgresql/versions/17/install.sh b/plugins/postgresql/versions/17/install.sh new file mode 100755 index 000000000..b9c9de79a --- /dev/null +++ b/plugins/postgresql/versions/17/install.sh @@ -0,0 +1,128 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +#https://www.postgresql.org/ftp/source/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +postgreDir=${serverPath}/source/postgresql + +VERSION=17.5 + +# su - postgres -c "/www/server/postgresql/bin/pg_ctl start -D /www/server/postgresql/data" + +Install_App() +{ + mkdir -p ${postgreDir} + echo '正在安装脚本文件...' + + if id postgres &> /dev/null ;then + echo "postgres uid is `id -u postgres`" + echo "postgres shell is `grep "^postgres:" /etc/passwd |cut -d':' -f7 `" + else + groupadd postgres + useradd -g postgres postgres + fi + + if [ ! -d /home/postgres ];then + mkdir -p /home/postgres + fi + + # if [ "$sysName" != "Darwin" ];then + # mkdir -p /var/log/mariadb + # touch /var/log/mariadb/mariadb.log + # fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + # for stable installation + if [ "$cpuCore" -gt "1" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + fi + # ----- cpu end ------ + + if [ ! -f ${postgreDir}/postgresql-${VERSION}.tar.bz2 ];then + wget --no-check-certificate -O ${postgreDir}/postgresql-${VERSION}.tar.bz2 --tries=3 https://ftp.postgresql.org/pub/source/v${VERSION}/postgresql-${VERSION}.tar.bz2 + fi + + if [ ! -d ${postgreDir}/postgresql-${VERSION} ];then + cd ${postgreDir} && tar -jxvf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi + + + if [ ! -d $serverPath/postgresql ];then + cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl + # --with-pgport=33206 + + echo "cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl" + # --with-pgport=33206 + make -j${cpuCore} && make install && make clean + fi + + if [ -d $serverPath/postgresql ];then + echo "${VERSION}" > $serverPath/postgresql/version.pl + echo '安装postgresql成功' + else + echo '安装postgresql失败' + rm -rf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /usr/lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f $serverPath/postgresql/initd/postgresql ];then + $serverPath/postgresql/initd/postgresql stop + fi + + if [ -d $serverPath/postgresql ];then + rm -rf $serverPath/postgresql + fi + + echo '卸载[postgresql]完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/postgresql/versions/18/install.sh b/plugins/postgresql/versions/18/install.sh new file mode 100755 index 000000000..e0d484cc1 --- /dev/null +++ b/plugins/postgresql/versions/18/install.sh @@ -0,0 +1,128 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +#https://www.postgresql.org/ftp/source/ + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + +postgreDir=${serverPath}/source/postgresql + +VERSION=18.3 + +# su - postgres -c "/www/server/postgresql/bin/pg_ctl start -D /www/server/postgresql/data" + +Install_App() +{ + mkdir -p ${postgreDir} + echo '正在安装脚本文件...' + + if id postgres &> /dev/null ;then + echo "postgres uid is `id -u postgres`" + echo "postgres shell is `grep "^postgres:" /etc/passwd |cut -d':' -f7 `" + else + groupadd postgres + useradd -g postgres postgres + fi + + if [ ! -d /home/postgres ];then + mkdir -p /home/postgres + fi + + # if [ "$sysName" != "Darwin" ];then + # mkdir -p /var/log/mariadb + # touch /var/log/mariadb/mariadb.log + # fi + + # ----- cpu start ------ + if [ -z "${cpuCore}" ]; then + cpuCore="1" + fi + + if [ -f /proc/cpuinfo ];then + cpuCore=`cat /proc/cpuinfo | grep "processor" | wc -l` + fi + + MEM_INFO=$(free -m|grep Mem|awk '{printf("%.f",($2)/1024)}') + if [ "${cpuCore}" != "1" ] && [ "${MEM_INFO}" != "0" ];then + if [ "${cpuCore}" -gt "${MEM_INFO}" ];then + cpuCore="${MEM_INFO}" + fi + else + cpuCore="1" + fi + + # for stable installation + if [ "$cpuCore" -gt "1" ];then + cpuCore=`echo "$cpuCore" | awk '{printf("%.f",($1)*0.8)}'` + fi + # ----- cpu end ------ + + if [ ! -f ${postgreDir}/postgresql-${VERSION}.tar.bz2 ];then + wget --no-check-certificate -O ${postgreDir}/postgresql-${VERSION}.tar.bz2 --tries=3 https://ftp.postgresql.org/pub/source/v${VERSION}/postgresql-${VERSION}.tar.bz2 + fi + + if [ ! -d ${postgreDir}/postgresql-${VERSION} ];then + cd ${postgreDir} && tar -jxvf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi + + + if [ ! -d $serverPath/postgresql ];then + cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl + # --with-pgport=33206 + + echo "cd ${postgreDir}/postgresql-${VERSION} && ./configure \ + --prefix=$serverPath/postgresql \ + --with-openssl" + # --with-pgport=33206 + make -j${cpuCore} && make install && make clean + fi + + if [ -d $serverPath/postgresql ];then + echo "${VERSION}" > $serverPath/postgresql/version.pl + echo '安装postgresql成功' + else + echo '安装postgresql失败' + rm -rf ${postgreDir}/postgresql-${VERSION}.tar.bz2 + fi +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /usr/lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/postgresql.service ];then + systemctl stop postgresql + systemctl disable postgresql + rm -rf /lib/systemd/system/postgresql.service + systemctl daemon-reload + fi + + if [ -f $serverPath/postgresql/initd/postgresql ];then + $serverPath/postgresql/initd/postgresql stop + fi + + if [ -d $serverPath/postgresql ];then + rm -rf $serverPath/postgresql + fi + + echo '卸载[postgresql]完成' +} + +action=$1 +if [ "${1}" == "install" ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/prometheus/ico.png b/plugins/prometheus/ico.png new file mode 100644 index 000000000..3ed0b3ba0 Binary files /dev/null and b/plugins/prometheus/ico.png differ diff --git a/plugins/prometheus/index.html b/plugins/prometheus/index.html new file mode 100755 index 000000000..4cada1a7c --- /dev/null +++ b/plugins/prometheus/index.html @@ -0,0 +1,30 @@ + + +
                                +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                常用功能

                                +

                                配置

                                +

                                相关说明

                                + +
                                +
                                +
                                +
                                +
                                +
                                + \ No newline at end of file diff --git a/plugins/prometheus/index.py b/plugins/prometheus/index.py new file mode 100755 index 000000000..bbf0e9db0 --- /dev/null +++ b/plugins/prometheus/index.py @@ -0,0 +1,230 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'prometheus' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/prometheus.yml" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + cmd = "ps aux|grep prometheus |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + +def getInstallVerion(): + version_pl = getServerDir() + "/version.pl" + version = mw.readFile(version_pl).strip() + return version + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + return content + + +def openPort(): + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort('3000', 'prometheus', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + return True + + +def initDreplace(): + # 初始化OP配置 + init_file = getServerDir() + '/init.pl' + if not os.path.exists(init_file): + # openPort() + mw.writeFile(init_file, 'ok') + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return True + + +def gOp(method): + initDreplace() + + data = mw.execShell('systemctl ' + method + ' '+getPluginName()) + mw.execShell('systemctl ' + method + ' '+getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return gOp('start') + +def stop(): + return gOp('stop') + +def restart(): + return gOp('restart') + +def reload(): + return gOp('reload') + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + shell_cmd = 'systemctl status prometheus|grep loaded|grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + data = mw.execShell('systemctl enable prometheus') + if data[1] != '': + return data[1] + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + data = mw.execShell('systemctl disable prometheus') + if data[1] != '': + return data[1] + return 'ok' + +def prometheusUrl(): + ip = mw.getLocalIp() + return 'http://'+ip+':'+"9090" + +def installPreInspection(): + return 'ok' + + +def uninstallPreInspection(): + return 'ok' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'prometheus_url': + print(prometheusUrl()) + else: + print('error') diff --git a/plugins/prometheus/info.json b/plugins/prometheus/info.json new file mode 100755 index 000000000..01fd38a09 --- /dev/null +++ b/plugins/prometheus/info.json @@ -0,0 +1,17 @@ +{ + "sort": 7, + "ps": "监控系统与时间序列数据库", + "name": "prometheus", + "title": "prometheus", + "shell": "install.sh", + "versions":["3.5.0"], + "tip": "soft", + "checks": "server/prometheus", + "path": "server/prometheus", + "display": 1, + "author": "midoks", + "date": "2025-08-02", + "home": "https://prometheus.io/download/", + "type": 0, + "pid": "5" +} diff --git a/plugins/prometheus/init.d/prometheus.service.tpl b/plugins/prometheus/init.d/prometheus.service.tpl new file mode 100644 index 000000000..c84941fb8 --- /dev/null +++ b/plugins/prometheus/init.d/prometheus.service.tpl @@ -0,0 +1,18 @@ +[Unit] +Description=Prometheus +Documentation=https://prometheus.io/docs/introduction/overview/ +Wants=network-online.target +After=network-online.target +[Service] +Type=simple +ExecReload=/bin/kill -HUP $MAINPID +ExecStart={$SERVER_PATH}/prometheus/prometheus \ + --config.file={$SERVER_PATH}/prometheus/prometheus.yml \ + --storage.tsdb.path={$SERVER_PATH}/prometheus/data \ + --web.console.templates={$SERVER_PATH}/prometheus/consoles \ + --web.console.libraries={$SERVER_PATH}/prometheus/console_libraries \ + --web.listen-address=0.0.0.0:9090 +SyslogIdentifier=prometheus +Restart=always +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/prometheus/install.sh b/plugins/prometheus/install.sh new file mode 100755 index 000000000..c321da9aa --- /dev/null +++ b/plugins/prometheus/install.sh @@ -0,0 +1,85 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://www.cnblogs.com/n00dle/p/16916044.html +# cd /www/server/mdserver-web/plugins/prometheus && /bin/bash install.sh install 3.5.0 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/prometheus/index.py start + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +sysArch=`arch` +sysName=`uname` +echo "use system: ${sysName}" + +OSNAME=`bash ${rootPath}/scripts/getos.sh` +if [ "" == "$OSNAME" ];then + OSNAME=`cat ${rootPath}/data/osname.pl` +fi + +if [ "macos" == "$OSNAME" ];then + echo "不支持Macox" + exit +fi + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +# if id prometheus &> /dev/null ;then +# echo "prometheus uid is `id -u prometheus`" +# echo "prometheus shell is `grep "^prometheus:" /etc/passwd |cut -d':' -f7 `" +# else +# groupadd prometheus +# useradd -g prometheus -s /bin/bash prometheus +# fi + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/prometheus + + mkdir -p $serverPath/prometheus + echo "${VERSION}" > $serverPath/prometheus/version.pl + + shell_file=${curPath}/versions/${VERSION}/linux.sh + + if [ -f $shell_file ];then + bash -x $shell_file install ${VERSION} + else + echo '不支持...' + exit 1 + fi + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/prometheus/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/prometheus/index.py initd_install + + echo 'Prometheus安装完成' +} + +Uninstall_App() +{ + shell_file=${curPath}/versions/${VERSION}/linux.sh + if [ -f $shell_file ];then + bash -x $shell_file uninstall ${VERSION} + fi + + cd ${rootPath} && python3 ${rootPath}/plugins/prometheus/index.py stop + cd ${rootPath} && python3 ${rootPath}/plugins/prometheus/index.py initd_uninstall + + rm -rf $serverPath/prometheus + echo 'Prometheus卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/prometheus/js/prometheus.js b/plugins/prometheus/js/prometheus.js new file mode 100755 index 000000000..c9c9218c8 --- /dev/null +++ b/plugins/prometheus/js/prometheus.js @@ -0,0 +1,96 @@ +function gPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'prometheus'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function gPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'prometheus'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function gCommonFunc(){ + var con = '

                                \ + \ +

                                '; + + $(".soft-man-con").html(con); + + $('#prometheus_url').click(function(){ + gPost('prometheus_url', '', {}, function(rdata){ + layer.open({ + title: "prometheus连接", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                                \ +
                                \ +
                                '+rdata.data+'
                                \ +
                                \ +
                                ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); + }); +} + + +function gReadme(){ + var readme = '
                                  '; + readme += '
                                • https://prometheus.io/download
                                • '; + readme += '
                                '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/prometheus/versions/3.5.0/linux.sh b/plugins/prometheus/versions/3.5.0/linux.sh new file mode 100644 index 000000000..79fc102eb --- /dev/null +++ b/plugins/prometheus/versions/3.5.0/linux.sh @@ -0,0 +1,61 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +VERSION=$2 + +sysArch=`arch` +sysName=`uname` + +ARCH_NAME=amd64 +if [ "$sysArch" == "arm64" ];then + ARCH_NAME=arm64 +elif [ "$sysArch" == "x86_64" ]; then + ARCH_NAME=amd64 +elif [ "$sysArch" == "aarch64" ]; then + ARCH_NAME=aarch64 +fi + +FILE_TGZ=prometheus-${VERSION}.linux-${ARCH_NAME}.tar.gz + +# 检查是否通 +Install_App() +{ + SourceDir=$serverPath/source/prometheus + InstallDir=$serverPath/prometheus + mkdir -p ${SourceDir} + mkdir -p ${InstallDir}/bin + + if [ ! -f ${SourceDir}/${FILE_TGZ} ];then + wget --no-check-certificate -O ${SourceDir}/${FILE_TGZ} https://github.com/prometheus/prometheus/releases/download/v${VERSION}/${FILE_TGZ} + fi + + + + if [ ! -d $InstallDir/bin/prometheus ];then + cd ${SourceDir} && tar -zxvf ${FILE_TGZ} + cd ${SourceDir}/prometheus-${VERSION}.linux-${ARCH_NAME} + cp -rf ./* $InstallDir + fi +} + +Uninstall_App() +{ + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/pureftp/conf/ftps.sql b/plugins/pureftp/conf/ftps.sql new file mode 100755 index 000000000..9db502897 --- /dev/null +++ b/plugins/pureftp/conf/ftps.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS `ftps` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pid` INTEGER, + `name` TEXT, + `password` TEXT, + `path` TEXT, + `status` TEXT, + `ps` TEXT, + `addtime` TEXT +); \ No newline at end of file diff --git a/plugins/pureftp/conf/pure-ftpd.conf b/plugins/pureftp/conf/pure-ftpd.conf new file mode 100644 index 000000000..d130c8aeb --- /dev/null +++ b/plugins/pureftp/conf/pure-ftpd.conf @@ -0,0 +1,32 @@ +ChrootEveryone yes +BrokenClientsCompatibility no +MaxClientsNumber 50 +Daemonize yes +MaxClientsPerIP 8 +VerboseLog no +DisplayDotFiles yes +AnonymousOnly no +NoAnonymous no +SyslogFacility ftp +DontResolve yes +MaxIdleTime 15 +LimitRecursion 10000 8 +AnonymousCanCreateDirs no +MaxLoad 4 +AntiWarez yes +Bind 0.0.0.0,21 +Umask 133:022 +MinUID 100 +PassivePortRange 39000 40000 +AllowUserFXP no +AllowAnonymousFXP no +ProhibitDotFilesWrite no +ProhibitDotFilesRead no +AutoRename no +AnonymousCantUpload no +MaxDiskUsage 99 +CustomerProof yes +PIDFile {$SERVER_PATH}/pureftp/etc/pure-ftpd.pid +PureDB {$SERVER_PATH}/pureftp/etc/pureftpd.pdb + +VerboseLog yes \ No newline at end of file diff --git a/plugins/pureftp/ico.png b/plugins/pureftp/ico.png new file mode 100644 index 000000000..bd64461a3 Binary files /dev/null and b/plugins/pureftp/ico.png differ diff --git a/plugins/pureftp/index.html b/plugins/pureftp/index.html new file mode 100755 index 000000000..c3f111311 --- /dev/null +++ b/plugins/pureftp/index.html @@ -0,0 +1,21 @@ +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                配置修改

                                +

                                管理列表

                                +
                                + +
                                +
                                +
                                +
                                + +
                                + \ No newline at end of file diff --git a/plugins/pureftp/index.py b/plugins/pureftp/index.py new file mode 100755 index 000000000..0dc6a00e3 --- /dev/null +++ b/plugins/pureftp/index.py @@ -0,0 +1,491 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import shutil + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'pureftp' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/etc/pure-ftpd.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/pure-ftpd.tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + cmd = "ps -ef|grep pure-ftpd |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + return content + + +def ftp_release_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'pure-ftpd', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def openFtpPort(): + for i in ["21", "39000:40000"]: + ftp_release_port(i) + return True + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + openFtpPort() + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + pureSbinConfig = getServerDir() + "/sbin/pure-config.pl" + if not os.path.exists(pureSbinConfig): + pureTplConfig = getPluginDir() + "/init.d/pure-config.pl" + content = mw.readFile(pureTplConfig) + content = contentReplace(content) + mw.writeFile(pureSbinConfig, content) + mw.execShell('chmod +x ' + pureSbinConfig) + + pureFtpdConfig = getServerDir() + "/etc/pure-ftpd.conf" + pureFtpdConfigBak = getServerDir() + "/etc/pure-ftpd.bak.conf" + pureFtpdConfigTpl = getPluginDir() + "/conf/pure-ftpd.conf" + + if not os.path.exists(pureFtpdConfigBak) or not os.path.exists(pureFtpdConfig): + if os.path.exists(pureFtpdConfig): + shutil.copyfile(pureFtpdConfig, pureFtpdConfigBak) + content = mw.readFile(pureFtpdConfigTpl) + content = contentReplace(content) + mw.writeFile(pureFtpdConfig, content) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/pureftp.service' + systemServiceTpl = getPluginDir() + '/init.d/pureftp.service.tpl' + + if os.path.exists(systemDir): + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def pfOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' pureftp') + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return pfOp('start') + + +def stop(): + return pfOp('stop') + + +def restart(): + return pfOp('restart') + + +def reload(): + return pfOp('reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status pureftp | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable pureftp') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable pureftp') + return 'ok' + + +def pftpDB(): + file = getServerDir() + '/ftps.db' + if not os.path.exists(file): + conn = mw.M('ftps').dbPos(getServerDir(), 'ftps') + csql = mw.readFile(getPluginDir() + '/conf/ftps.sql') + csql_list = csql.split(';') + for index in range(len(csql_list)): + conn.execute(csql_list[index], ()) + else: + conn = mw.M('ftps').dbPos(getServerDir(), 'ftps') + return conn + + +def pftpUser(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + return 'www' + + +def pftpAdd(username, password, path): + user = pftpUser() + + if not os.path.exists(path): + os.makedirs(path) + if mw.isAppleSystem(): + # pass + os.system('chown ' + user + '.staff ' + path) + else: + os.system('chown www.www ' + path) + + cmd = getServerDir() + '/bin/pure-pw useradd ' + username + ' -u ' + user + ' -d ' + \ + path + '< 65535: + return '端口范围不正确!' + file = file = getServerDir() + '/etc/pure-ftpd.conf' + conf = mw.readFile(file) + rep = r"\n#?\s*Bind\s+[0-9]+\.[0-9]+\.[0-9]+\.+[0-9]+,([0-9]+)" + # preg_match(rep,conf,tmp) + conf = re.sub(rep, "\nBind 0.0.0.0," + port, conf) + mw.writeFile(file, conf) + restart() + return 'ok' + except Exception as ex: + return str(ex) + + +def stopPort(): + args = getArgs() + if not 'id' in args: + return 'id missing' + + if not 'username' in args: + return 'username missing' + + if not 'status' in args: + return 'status missing' + + data = pftpStop(args['username']) + pftpReload() + conn = pftpDB() + conn.where('id=?', (int(args['id']),)).save( + 'status', (args['status'],)) + + if data[1] == '': + return 'ok' + return data[0] + + +def startPort(): + args = getArgs() + if not 'id' in args: + return 'id missing' + + if not 'username' in args: + return 'username missing' + + if not 'status' in args: + return 'status missing' + + data = pftpStart(args['username']) + pftpReload() + conn = pftpDB() + conn.where('id=?', (int(args['id']),)).save( + 'status', (args['status'],)) + + if data[1] == '': + return 'ok' + return data[0] + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(getConf()) + elif func == 'get_www_dir': + print(getWwwDir()) + elif func == 'get_ftp_list': + print(getFtpList()) + elif func == 'add_ftp': + print(addFtp()) + elif func == 'del_ftp': + print(delFtp()) + elif func == 'mod_ftp': + print(modFtp()) + elif func == 'mod_ftp_port': + print(modFtpPort()) + elif func == 'stop_ftp': + print(stopPort()) + elif func == 'start_ftp': + print(startPort()) + else: + print('error') diff --git a/plugins/pureftp/info.json b/plugins/pureftp/info.json new file mode 100755 index 000000000..6130a9cb1 --- /dev/null +++ b/plugins/pureftp/info.json @@ -0,0 +1,15 @@ +{ + "title":"PureFtpd", + "tip":"soft", + "name":"pureftp", + "ps":"一款免费FTP服务器软件", + "versions": "1.0.49", + "shell":"install.sh", + "checks":"server/pureftp", + "path":"server/pureftp", + "author":"mdserver-web", + "home":"https://github.com/midoks/mdserver-web", + "date":"2018-11-30", + "dev_update":"2022-6-18", + "pid":"3" +} \ No newline at end of file diff --git a/plugins/pureftp/init.d/pure-config.pl b/plugins/pureftp/init.d/pure-config.pl new file mode 100644 index 000000000..7d722b7a6 --- /dev/null +++ b/plugins/pureftp/init.d/pure-config.pl @@ -0,0 +1,128 @@ +#! /usr/bin/perl + +# (C) 2001-2013 Aristotle Pagaltzis +# derived from code (C) 2001-2002 Frank Denis and Matthias Andree + +use strict; + +my ($conffile, @flg) = @ARGV; + +my $PUREFTPD; +-x && ($PUREFTPD=$_, last) for qw( + {$SERVER_PATH}/pureftp/sbin/pure-ftpd + /www/server/pureftp/sbin/pure-ftpd + /www/server/pureftpd/sbin/pure-ftpd + /www/server/sbin/pure-ftpd + /usr/sbin/pure-ftpd +); + +my %simple_switch_for = ( + IPV4Only => "-4", + IPV6Only => "-6", + ChrootEveryone => "-A", + BrokenClientsCompatibility => "-b", + Daemonize => "-B", + VerboseLog => "-d", + DisplayDotFiles => "-D", + AnonymousOnly => "-e", + NoAnonymous => "-E", + DontResolve => "-H", + AnonymousCanCreateDirs => "-M", + NATmode => "-N", + CallUploadScript => "-o", + AntiWarez => "-s", + AllowUserFXP => "-w", + AllowAnonymousFXP => "-W", + ProhibitDotFilesWrite => "-x", + ProhibitDotFilesRead => "-X", + AllowDotFiles => "-z", + AutoRename => "-r", + AnonymousCantUpload => "-i", + LogPID => "-1", + NoChmod => "-R", + KeepAllFiles => "-K", + CreateHomeDir => "-j", + NoRename => "-G", + CustomerProof => "-Z", + NoTruncate => "-0", +); + +my %string_switch_for = ( + FileSystemCharset => "-8", + ClientCharset => "-9", + SyslogFacility => "-f", + FortunesFile => "-F", + ForcePassiveIP => "-P", + Bind => "-S", + AnonymousBandwidth => "-t", + UserBandwidth => "-T", + TrustedIP => "-V", + AltLog => "-O", + PIDFile => "-g", + TLSCipherSuite => "-J", + CertFile => "-2", +); + +my %numeric_switch_for = ( + MaxIdleTime => "-I", + MaxDiskUsage => "-k", + TrustedGID => "-a", + MaxClientsNumber => "-c", + MaxClientsPerIP => "-C", + MaxLoad => "-m", + MinUID => "-u", + TLS => "-Y", +); + +my %numpairb_switch_for = ( + LimitRecursion => "-L", + PassivePortRange => "-p", + AnonymousRatio => "-q", + UserRatio => "-Q", +); + +my %numpairc_switch_for = ( + Umask => "-U", + Quota => "-n", + PerUserLimits => "-y", +); + +my %auth_method_for = ( + LDAPConfigFile => "ldap", + MySQLConfigFile => "mysql", + PGSQLConfigFile => "pgsql", + PureDB => "puredb", + ExtAuth => "extauth", +); + +my $simple_switch = qr/(@{[join "|", keys %simple_switch_for ]})\s+yes/i; +my $string_switch = qr/(@{[join "|", keys %string_switch_for ]})\s+(\S+)/i; +my $numeric_switch = qr/(@{[join "|", keys %numeric_switch_for ]})\s+(\d+)/i; +my $numpairb_switch = qr/(@{[join "|", keys %numpairb_switch_for ]})\s+(\d+)\s+(\d+)/i; +my $numpairc_switch = qr/(@{[join "|", keys %numpairc_switch_for ]})\s+(\d+):(\d+)/i; +my $auth_method = qr/(@{[join "|", keys %auth_method_for ]})\s+(\S+)/i; + +die "Usage: pure-config.pl [extra options]\n" + unless defined $conffile; + +open CONF, "< $conffile" or die "Can't open $conffile: $!\n"; + +!/^\s*(?:$|#)/ and (chomp, push @flg, + /$simple_switch/i ? ($simple_switch_for{$1}) : + /$string_switch/i ? ($string_switch_for{$1} . $2) : + /$numeric_switch/i ? ($numeric_switch_for{$1} . $2) : + /$numpairb_switch/i ? ($numpairb_switch_for{$1} . "$2:$3") : + /$numpairc_switch/i ? ($numpairc_switch_for{$1} . "$2:$3") : + /$auth_method/i ? ("-l" . "$auth_method_for{$1}:$2") : + /UnixAuthentication\s+yes/i ? ("-l" . "unix") : + /PAMAuthentication\s+yes/i ? ("-l" . "pam") : + () +) while ; + +close CONF; + +if (-t STDOUT) { + print "Running: $PUREFTPD ", join(" ", @flg), "\n"; +} +exec { $PUREFTPD } ($PUREFTPD, @flg) or die "cannot exec $PUREFTPD: $!"; + diff --git a/plugins/pureftp/init.d/pure-ftpd.tpl b/plugins/pureftp/init.d/pure-ftpd.tpl new file mode 100644 index 000000000..af51ca29d --- /dev/null +++ b/plugins/pureftp/init.d/pure-ftpd.tpl @@ -0,0 +1,78 @@ +#!/bin/bash +# +# chkconfig: 2345 85 15 +# description: Pure-FTPd is an FTP server daemon based upon Troll-FTPd +# processname: pure-ftpd + +### BEGIN INIT INFO +# Provides: pureftpd +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts pureftpd server +# Description: starts pureftpd server +### END INIT INFO + +# Pure-FTPd Settings +PURE_PERL="{$SERVER_PATH}/pureftp/sbin/pure-config.pl" +PURE_CONF="{$SERVER_PATH}/pureftp/etc/pure-ftpd.conf" +PURE_PID="{$SERVER_PATH}/pureftp/etc/pure-ftpd.pid" +RETVAL=0 +prog="Pure-FTPd" + +start() { + echo -n $"Starting $prog... " + $PURE_PERL $PURE_CONF --daemonize + if [ "$?" = 0 ] ; then + echo " done" + else + echo " failed" + fi +} + +stop() { + echo -n $"Stopping $prog... " + if [ ! -e $PURE_PID ]; then + echo -n $"$prog is not running." + exit 1 + fi + kill `cat $PURE_PID` + if [ "$?" = 0 ] ; then + echo " done" + else + echo " failed" + fi +} + +restart(){ + echo $"Restarting $prog..." + $0 stop + sleep 2 + $0 start +} + +status(){ + if [ -e $PURE_PID ]; then + echo $"$prog is running." + else + echo $"$prog is not running." + fi +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + status) + status + ;; + *) + echo $"Usage: $0 {start|stop|restart}" +esac diff --git a/plugins/pureftp/init.d/pureftp.service.tpl b/plugins/pureftp/init.d/pureftp.service.tpl new file mode 100644 index 000000000..7d632eae8 --- /dev/null +++ b/plugins/pureftp/init.d/pureftp.service.tpl @@ -0,0 +1,15 @@ +[Unit] +Description=Pure-FTPd is a fast, production-quality, standard-conformant FTP server +After=network.target + + + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/pureftp/sbin/pure-ftpd {$SERVER_PATH}/pureftp/etc/pure-ftpd.conf +ExecStop=/bin/kill -HUP $MAINPID +ExecReload=/bin/kill -USR2 $MAINPID +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/pureftp/install.sh b/plugins/pureftp/install.sh new file mode 100755 index 000000000..874c46668 --- /dev/null +++ b/plugins/pureftp/install.sh @@ -0,0 +1,137 @@ +#!/bin/bash +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +sysName=`uname` +echo "use system: ${sysName}" + +if [ ${sysName} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' +elif grep -Eqi "Raspbian" /etc/issue || grep -Eq "Raspbian" /etc/*-release; then + OSNAME='raspbian' +else + OSNAME='unknow' +fi + + + + +Install_pureftp() +{ + if id ftp &> /dev/null ;then + echo "ftp UID is `id -u ftp`" + echo "ftp Shell is `grep "^ftp:" /etc/passwd |cut -d':' -f7 `" + else + groupadd ftp + useradd -g ftp -s /sbin/nologin ftp + fi + + # mkdir -p ${serverPath}/pureftp + mkdir -p ${serverPath}/source/pureftp + + # https://github.com/jedisct1/pure-ftpd/releases/download/1.0.49/pure-ftpd-1.0.49.tar.gz + # https://download.pureftpd.org/pub/pure-ftpd/releases/pure-ftpd-1.0.49.tar.gz + + + VER=$1 + DOWNLOAD=https://github.com/jedisct1/pure-ftpd/releases/download/${VER}/pure-ftpd-${VER}.tar.gz + # DOWNLOAD=https://download.pureftpd.org/pub/pure-ftpd/releases/pure-ftpd-${VER}.tar.gz + + # curl -sSLo pure-ftpd-1.0.49.tar.gz https://download.pureftpd.org/pub/pure-ftpd/releases/pure-ftpd-1.0.49.tar.gz + if [ ! -f $serverPath/source/pureftp/pure-ftpd-${VER}.tar.gz ];then + # wget --no-check-certificate -O $serverPath/source/pureftp/pure-ftpd-${VER}.tar.gz $DOWNLOAD + curl -sSLo $serverPath/source/pureftp/pure-ftpd-${VER}.tar.gz $DOWNLOAD + fi + + #检测文件是否损坏. + md5_ok=451879495ba61c1d7dcfca8dd231119f + if [ -f $serverPath/source/pureftp/pure-ftpd-${VER}.tar.gz ];then + md5_check=`md5sum $serverPath/source/pureftp/pure-ftpd-${VER}.tar.gz | awk '{print $1}'` + if [ "${md5_ok}" == "${md5_check}" ]; then + echo "pure-ftpd file check ok" + fi + fi + + # Last Download Method + if [ ! -f $serverPath/source/pureftp/pure-ftpd-${VER}.tar.gz ];then + wget --no-check-certificate -O $serverPath/source/pureftp/pure-ftpd-${VER}.tar.gz https://dl.midoks.icu/soft/ftp/pure-ftpd-${VER}.tar.gz -T 3 + fi + + if [ ! -d $serverPath/source/pureftp/pure-ftpd-${VER} ];then + cd $serverPath/source/pureftp && tar zxvf pure-ftpd-${VER}.tar.gz + fi + + cd $serverPath/source/pureftp/pure-ftpd-${VER} && ./configure --prefix=${serverPath}/pureftp \ +   CFLAGS=-O2 \ + --with-puredb \ + --with-quotas \ + --with-cookie \ + --with-virtualhosts \ + --with-diraliases \ + --with-sysquotas \ + --with-ratios \ + --with-altlog \ + --with-paranoidmsg \ + --with-shadow \ + --with-welcomemsg \ + --with-throttling \ + --with-uploadscript \ + --with-language=english \ + --with-rfc2640 \ + --with-ftpwho \ + --with-tls && make && make install && make clean + + if [ -d ${serverPath}/pureftp ];then + echo "${1}" > ${serverPath}/pureftp/version.pl + echo '安装完成' + + cd ${rootPath} && python3 ${rootPath}/plugins/pureftp/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/pureftp/index.py initd_install + else + echo '安装失败' + fi +} + +Uninstall_pureftp() +{ + if [ -f /usr/lib/systemd/system/pureftp.service ];then + systemctl stop pureftp + systemctl disable pureftp + rm -rf /usr/lib/systemd/system/pureftp.service + systemctl daemon-reload + fi + + if [ -f $serverPath/pureftp/initd/pureftp ];then + $serverPath/pureftp/initd/pureftp stop + fi + + rm -rf ${serverPath}/pureftp + userdel ftp + groupdel ftp + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_pureftp $2 +else + Uninstall_pureftp $2 +fi diff --git a/plugins/pureftp/js/ftp.js b/plugins/pureftp/js/ftp.js new file mode 100755 index 000000000..0ddee424a --- /dev/null +++ b/plugins/pureftp/js/ftp.js @@ -0,0 +1,308 @@ + +function ftpPost(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'pureftp', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function ftpAsyncPost(method,args){ + return syncPost('/plugins/run', + {name:'pureftp', func:method, args:JSON.stringify(args)} + ); +} + +function ftpListFind(){ + var search = $('#ftp_find_user').val(); + if (search==''){ + layer.msg('搜索字符不能为空!',{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + ftpList(1, search); +} + +function ftpList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + ftpPost('get_ftp_list', _data, function(data){ + + var rdata = $.parseJSON(data.data); + // console.log(rdata); + content = '

                                当前FTP地址为:ftp://'+rdata['info']['ip']+':'+rdata['info']['port']+'

                                '; + content += '
                                '; + content += '
                                '; + + content += '
                                '; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + content += ''; + + content += ''; + + ulist = rdata.data; + for (i in ulist){ + // console.log(ulist[i]); + status = '已停用'; + if (ulist[i]['status'] == '1'){ + status = '已启用'; + } + content += ''+ + ''+ + '' + + '' + + '' + + ''; + } + + content += ''; + content += '
                                用户名密码状态根目录备注操作(添加|端口)
                                '+ulist[i]['name']+''+ulist[i]['password']+''+status+''+ulist[i]['path']+''+ulist[i]['ps']+'改密 | ' + + '删除
                                '; + + page = ''; + + content += page; + + $(".soft-man-con").html(content); + }); +} + + +/** + *添加FTP帐户 + */ +function addFtp() { + + var data = ftpAsyncPost('get_www_dir'); + var defaultPath = data.data; + var indexFtp = layer.open({ + type: 1, + area: '500px', + title: '添加FTP帐户', + closeBtn: 2, + shift: 5, + shadeClose: false, + btn: ['提交','关闭'], + content: "
                                \ +
                                \ + 用户名\ +
                                \ +
                                \ +
                                \ + 密码\ +
                                \ +
                                \ +
                                \ + 根目录\ +

                                "+lan.ftp.add_path_ps+"

                                \ +
                                \ + \ +
                                ", + yes:function(index,layero){ + var loadT = layer.load({shade: true,shadeClose: false}); + var data = $("#ftpAdd").serialize(); + ftpPost('add_ftp', data, function(rdata){ + layer.close(loadT); + layer.close(indexFtp); + if (rdata.data == 'ok'){ + layer.msg('添加成功!', {icon: 1,time:3000}); + } else { + layer.msg(rdata.data, {icon: 5,time:3000}); + } + + setTimeout(function(){ftpList();},2000); + }); + return true; + }, + }); + + $("#ftpUser").keyup(function(){ + var ftpName = $(this).val(); + $("#inputPath").val(defaultPath+'/'+ftpName); + $("#ftp_ps").val(ftpName); + }); +} + + +/** + * 删除FTP帐户 + * @param {Number} id + * @param {String} ftp_username 欲被删除的用户名 + * @return {bool} + */ +function ftpDelete(id,ftp_username){ + safeMessage(lan.public.del+"["+ftp_username+"]",lan.get('confirm_del',[ftp_username]),function(){ + layer.msg(lan.public.the_del,{icon:16,time:0,shade: [0.3, '#000']}); + var data='&id='+id+'&username='+ftp_username; + + ftpPost('del_ftp', data, function(data){ + layer.msg('删除成功!', {icon: 1}); + ftpList(); + }) + }); +} + +function modFtpPort(type, port){ + var index = layer.open({ + type: 1, + skin: 'demo-class', + area: '500px', + title: '修改FTP帐户端口', + content: "
                                \ +
                                \ + 默认端口\ +
                                \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                ", + }); + + $('#ftp_port_close').click(function(){ + $('.layui-layer-close1').click(); + }); + + $('#ftp_port_submit').click(function(){ + var port = $('#ftpPort').val(); + data = 'port='+port + ftpPost('mod_ftp_port', data,function(data){ + ftpList(); + if (data.data == 'ok'){ + layer.msg('修改成功!', {icon: 1}); + } else { + layer.msg(data.data, {icon: 2}); + } + $('.layui-layer-close1').click(); + }); + }); + +} + + +function ftpModPwd(id,name,password){ + var index = layer.open({ + type: 1, + skin: 'demo-class', + area: '500px', + title: '修改FTP帐户密码', + content: "
                                \ +
                                \ + 用户名\ +
                                \ +
                                \ + \ +
                                \ + 密码\ +
                                \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                ", + }); + + + $('#ftp_mod_close').click(function(){ + $('.layui-layer-close1').click(); + }); + + $('#ftp_mod_submit').click(function(){ + pwd = $('#MyPassword').val(); + data='id='+id+'&name='+name+'&password='+pwd + ftpPost('mod_ftp', data,function(data){ + ftpList(); + if (data.data == 'ok'){ + layer.msg('修改成功!', {icon: 1}); + } + $('.layui-layer-close1').click(); + }); + }); +} + + +/** + * 停止FTP帐号 + * @param {Number} id FTP的ID + * @param {String} username FTP用户名 + */ +function ftpStop(id, username) { + layer.confirm('您真的要停止{1}的FTP吗?'.replace('{1}',username), { + title: 'FTP帐户',icon:3, + closeBtn:2 + }, function(index) { + if (index > 0) { + var loadT = layer.load({shade: true,shadeClose: false}); + var data='id=' + id + '&username=' + username + '&status=0'; + ftpPost('stop_ftp', data, function(data){ + layer.close(loadT); + if (data.data == 'ok'){ + showMsg('启动成功!', function(){ + ftpList(); + },{icon: 1}); + } else { + layer.msg(data.data, {icon: 2}); + } + }); + } + $('.layui-layer-close1').click(); + }); +} + +/** + * 启动FTP帐号 + * @param {Number} id FTP的ID + * @param {String} username FTP用户名 + */ +function ftpStart(id, username) { + var loadT = layer.load({shade: true,shadeClose: false}); + var data='id=' + id + '&username=' + username + '&status=1'; + ftpPost('start_ftp', data, function(data){ + layer.close(loadT); + if (data.data == 'ok'){ + showMsg('启动成功!', function(){ + ftpList(); + },{icon: 1}); + } else { + layer.msg(data.data, {icon: 2}); + } + + }); +} + diff --git a/plugins/redis/config/redis.conf b/plugins/redis/config/redis.conf new file mode 100644 index 000000000..e630018a6 --- /dev/null +++ b/plugins/redis/config/redis.conf @@ -0,0 +1,90 @@ +daemonize yes +pidfile {$SERVER_PATH}/redis/redis.pid + +bind 127.0.0.1 +port 6379 +requirepass {$REDIS_PASS} + +timeout 3 +tcp-keepalive 0 + +loglevel notice + +logfile {$SERVER_PATH}/redis/data/redis.log +databases 16 + +################################ SNAPSHOTTING ################################# + +save 900 1000 +save 300 10000 +save 60 1000000 +stop-writes-on-bgsave-error no +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir {$SERVER_PATH}/redis/data/ + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 218mb +#maxmemory-policy volatile-ttl +maxmemory-policy allkeys-lru + +############################## APPEND ONLY MODE ############################### + +# appendonly no + +# appendfsync always +# appendfsync everysec +# appendfsync no + +# appendfilename "appendonly.aof" + +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing yes + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/redis/ico.png b/plugins/redis/ico.png new file mode 100755 index 000000000..804758d8e Binary files /dev/null and b/plugins/redis/ico.png differ diff --git a/plugins/redis/index.html b/plugins/redis/index.html new file mode 100755 index 000000000..3b3944cf5 --- /dev/null +++ b/plugins/redis/index.html @@ -0,0 +1,36 @@ + + +
                                +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                配置修改

                                + +

                                性能调整

                                +

                                负载状态

                                +

                                复制状态

                                +

                                集群状态

                                +

                                集群节点

                                +

                                运行日志

                                +

                                相关说明

                                + +
                                +
                                +
                                +
                                +
                                +
                                + \ No newline at end of file diff --git a/plugins/redis/index.py b/plugins/redis/index.py new file mode 100755 index 000000000..08d2dbba0 --- /dev/null +++ b/plugins/redis/index.py @@ -0,0 +1,574 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'redis' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/redis.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + "/config/redis.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + pid_file = getPidFile() + if not os.path.exists(pid_file): + return 'stop' + + # data = mw.execShell( + # "ps aux|grep redis |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + + # if data[0] == '': + # return 'stop' + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/'+getPluginName()) + content = content.replace('{$REDIS_PASS}', mw.getRandomString(10)) + return content + + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # log + dataLog = getServerDir() + '/data' + if not os.path.exists(dataLog): + mw.execShell('mkdir -p ' + dataLog) + mw.execShell('chmod +x ' + file_bin) + + # config replace + dst_conf = getConf() + dst_conf_init = getServerDir() + '/init.pl' + if not os.path.exists(dst_conf_init): + conf_content = mw.readFile(getConfTpl()) + conf_content = conf_content.replace('{$SERVER_PATH}', service_path) + conf_content = conf_content.replace('{$REDIS_PASS}', mw.getRandomString(10)) + + mw.writeFile(dst_conf, conf_content) + mw.writeFile(dst_conf_init, 'ok') + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + service_path = mw.getServerDir() + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def redisOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return redisOp('start') + + +def stop(): + return redisOp('stop') + + +def restart(): + status = redisOp('restart') + + log_file = runLog() + mw.execShell("echo '' > " + log_file) + return status + + +def reload(): + return redisOp('reload') + + +def getPort(): + conf = getConf() + content = mw.readFile(conf) + + rep = r"^(port)\s*([.0-9A-Za-z_& ~]+)" + tmp = re.search(rep, content, re.M) + if tmp: + return tmp.groups()[1] + + return '6379' + + +def getRedisCmd(): + requirepass = "" + conf = getConf() + content = mw.readFile(conf) + rep = r"^(requirepass)\s*([.0-9A-Za-z_& ~]+)" + tmp = re.search(rep, content, re.M) + if tmp: + requirepass = tmp.groups()[1] + + default_ip = '127.0.0.1' + port = getPort() + # findDebian = mw.execShell('cat /etc/issue |grep Debian') + # if findDebian[0] != '': + # default_ip = mw.getLocalIp() + cmd = getServerDir() + "/bin/redis-cli -h " + default_ip + ' -p ' + port + " " + + if requirepass != "": + cmd = getServerDir() + '/bin/redis-cli -h ' + default_ip + ' -p ' + port + ' -a "' + requirepass + '" ' + + return cmd + +def runInfo(): + s = status() + if s == 'stop': + return mw.returnJson(False, '未启动') + + + cmd = getRedisCmd() + cmd = cmd + 'info' + + # print(cmd) + data = mw.execShell(cmd)[0] + # print(data) + res = [ + 'tcp_port', + 'uptime_in_days', # 已运行天数 + 'connected_clients', # 连接的客户端数量 + 'used_memory', # Redis已分配的内存总量 + 'used_memory_rss', # Redis占用的系统内存总量 + 'used_memory_peak', # Redis所用内存的高峰值 + 'mem_fragmentation_ratio', # 内存碎片比率 + 'total_connections_received', # 运行以来连接过的客户端的总数量 + 'total_commands_processed', # 运行以来执行过的命令的总数量 + 'instantaneous_ops_per_sec', # 服务器每秒钟执行的命令数量 + 'keyspace_hits', # 查找数据库键成功的次数 + 'keyspace_misses', # 查找数据库键失败的次数 + 'latest_fork_usec' # 最近一次 fork() 操作耗费的毫秒数 + ] + data = data.split("\n") + result = {} + for d in data: + if len(d) < 3: + continue + t = d.strip().split(':') + if not t[0] in res: + continue + result[t[0]] = t[1] + return mw.getJson(result) + +def infoReplication(): + # 复制信息 + s = status() + if s == 'stop': + return mw.returnJson(False, '未启动') + + cmd = getRedisCmd() + cmd = cmd + 'info replication' + + # print(cmd) + data = mw.execShell(cmd)[0] + # print(data) + res = [ + #slave + 'role',#角色 + 'master_host', # 连接主库HOST + 'master_port', # 连接主库PORT + 'master_link_status', # 连接主库状态 + 'master_last_io_seconds_ago', # 上次同步时间 + 'master_sync_in_progress', # 正在同步中 + 'slave_read_repl_offset', # 从库读取复制位置 + 'slave_repl_offset', # 从库复制位置 + 'slave_priority', # 从库同步优先级 + 'slave_read_only', # 从库是否仅读 + 'replica_announced', # 已复制副本 + 'connected_slaves', # 连接从库数量 + 'master_failover_state', # 主库故障状态 + 'master_replid', # 主库复制ID + 'master_repl_offset', # 主库复制位置 + 'second_repl_offset', # 主库复制位置时间 + 'repl_backlog_active', # 复制状态 + 'repl_backlog_size', # 复制大小 + 'repl_backlog_first_byte_offset', # 第一个字节偏移量 + 'repl_backlog_histlen', # backlog中数据的长度 + ] + + data = data.split("\n") + result = {} + for d in data: + if len(d) < 3: + continue + t = d.strip().split(':') + if not t[0] in res: + continue + result[t[0]] = t[1] + + if 'role' in result and result['role'] == 'master': + connected_slaves = int(result['connected_slaves']) + slave_l = [] + for x in range(connected_slaves): + slave_l.append('slave'+str(x)) + + for d in data: + if len(d) < 3: + continue + t = d.strip().split(':') + if not t[0] in slave_l: + continue + result[t[0]] = t[1] + + return mw.getJson(result) + + +def clusterInfo(): + #集群信息 + # https://redis.io/commands/cluster-info/ + s = status() + if s == 'stop': + return mw.returnJson(False, '未启动') + + cmd = getRedisCmd() + cmd = cmd + 'cluster info' + + # print(cmd) + data = mw.execShell(cmd)[0] + # print(data) + + res = [ + 'cluster_state',#状态 + 'cluster_slots_assigned', # 被分配的槽 + 'cluster_slots_ok', # 被分配的槽状态 + 'cluster_slots_pfail', # 连接主库状态 + 'cluster_slots_fail', # 失败的槽 + 'cluster_known_nodes', # 知道的节点 + 'cluster_size', # 大小 + 'cluster_current_epoch', # + 'cluster_my_epoch', # + 'cluster_stats_messages_sent', # 发送 + 'cluster_stats_messages_received', # 接受 + 'total_cluster_links_buffer_limit_exceeded', # + ] + + data = data.split("\n") + result = {} + for d in data: + if len(d) < 3: + continue + t = d.strip().split(':') + if not t[0] in res: + continue + result[t[0]] = t[1] + + return mw.getJson(result) + +def clusterNodes(): + s = status() + if s == 'stop': + return mw.returnJson(False, '未启动') + + cmd = getRedisCmd() + cmd = cmd + 'cluster nodes' + + # print(cmd) + data = mw.execShell(cmd)[0] + # print(data) + + data = data.strip().split("\n") + return mw.getJson(data) + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + return getServerDir() + '/data/redis.log' + + +def getRedisConfInfo(): + conf = getConf() + + gets = [ + {'name': 'bind', 'type': 2, 'ps': '绑定IP(修改绑定IP可能会存在安全隐患)','must_show':1}, + {'name': 'port', 'type': 2, 'ps': '绑定端口','must_show':1}, + {'name': 'timeout', 'type': 2, 'ps': '空闲链接超时时间,0表示不断开','must_show':1}, + {'name': 'maxclients', 'type': 2, 'ps': '最大连接数','must_show':1}, + {'name': 'databases', 'type': 2, 'ps': '数据库数量','must_show':1}, + {'name': 'requirepass', 'type': 2, 'ps': 'redis密码,留空代表没有设置密码','must_show':1}, + {'name': 'maxmemory', 'type': 2, 'ps': 'MB,最大使用内存,0表示不限制','must_show':1}, + {'name': 'slaveof', 'type': 2, 'ps': '同步主库地址','must_show':0}, + {'name': 'masterauth', 'type': 2, 'ps': '同步主库密码', 'must_show':0} + ] + content = mw.readFile(conf) + + result = [] + for g in gets: + rep = r"^(" + g['name'] + r')\s*([.0-9A-Za-z_& ~]+)' + tmp = re.search(rep, content, re.M) + if not tmp: + if g['must_show'] == 0: + continue + + g['value'] = '' + result.append(g) + continue + g['value'] = tmp.groups()[1] + if g['name'] == 'maxmemory': + g['value'] = g['value'].strip("mb") + result.append(g) + + return result + + +def getRedisConf(): + data = getRedisConfInfo() + return mw.getJson(data) + + +def submitRedisConf(): + gets = ['bind', 'port', 'timeout', 'maxclients', + 'databases', 'requirepass', 'maxmemory','slaveof','masterauth'] + args = getArgs() + conf = getConf() + content = mw.readFile(conf) + for g in gets: + if g in args: + rep = g + r'\s*([.0-9A-Za-z_& ~]+)' + val = g + ' ' + args[g] + + if g == 'maxmemory': + val = g + ' ' + args[g] + "mb" + + if g == 'requirepass' and args[g] == '': + content = re.sub('requirepass', '#requirepass', content) + if g == 'requirepass' and args[g] != '': + content = re.sub('#requirepass', 'requirepass', content) + content = re.sub(rep, val, content) + + if g != 'requirepass': + content = re.sub(rep, val, content) + mw.writeFile(conf, content) + reload() + return mw.returnJson(True, '设置成功') + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'info_replication': + print(infoReplication()) + elif func == 'cluster_info': + print(clusterInfo()) + elif func == 'cluster_nodes': + print(clusterNodes()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'get_redis_conf': + print(getRedisConf()) + elif func == 'submit_redis_conf': + print(submitRedisConf()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + else: + print('error') diff --git a/plugins/redis/info.json b/plugins/redis/info.json new file mode 100755 index 000000000..76b9a9a39 --- /dev/null +++ b/plugins/redis/info.json @@ -0,0 +1,17 @@ +{ + "sort":4, + "ps": "一个高性能的KV数据库", + "name": "redis", + "title": "Redis", + "shell": "install.sh", + "versions":["4.0.14","5.0.14","6.0.20","6.2.16","7.0.15","7.2.9", "7.4.7", "8.0.5", "8.2.3"], + "tip": "soft", + "checks": "server/redis", + "path": "server/redis", + "display": 1, + "author": "redis", + "date": "2017-04-01", + "home": "https://redis.io", + "type": 0, + "pid": "2" +} diff --git a/plugins/redis/init.d/redis.service.tpl b/plugins/redis/init.d/redis.service.tpl new file mode 100644 index 000000000..8e8eb5693 --- /dev/null +++ b/plugins/redis/init.d/redis.service.tpl @@ -0,0 +1,12 @@ +[Unit] +Description=Redis In-Memory Data Store +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/redis/bin/redis-server {$SERVER_PATH}/redis/redis.conf +ExecReload=/bin/kill -USR2 $MAINPID +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/redis/init.d/redis.tpl b/plugins/redis/init.d/redis.tpl new file mode 100644 index 000000000..fb4f76e46 --- /dev/null +++ b/plugins/redis/init.d/redis.tpl @@ -0,0 +1,77 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: Redis Service + +### BEGIN INIT INFO +# Provides: Redis +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts Redis +# Description: starts the MDW-Web +### END INIT INFO + +# Simple Redis init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +CONF="{$SERVER_PATH}/redis/redis.conf" +REDISPORT=$(cat $CONF |grep port|grep -v '#'|awk '{print $2}') +REDISPASS=$(cat $CONF |grep requirepass|grep -v '#'|awk '{print $2}') +if [ "$REDISPASS" != "" ];then + REDISPASS=" -a $REDISPASS" +fi +EXEC={$SERVER_PATH}/redis/bin/redis-server +CLIEXEC="{$SERVER_PATH}/redis/bin/redis-cli -p $REDISPORT$REDISPASS" +PIDFILE={$SERVER_PATH}/redis/redis.pid + +echo $REDISPASS +echo $REDISPORT +echo $CLIEXEC + +mkdir -p {$SERVER_PATH}/redis/data + +redis_start(){ + if [ -f $PIDFILE ];then + kill -9 `cat $PIDFILE` + fi + + echo "Starting Redis server..." + nohup $EXEC $CONF >> {$SERVER_PATH}/redis/logs.pl 2>&1 & +} +redis_stop(){ + if [ ! -f $PIDFILE ] + then + echo "$PIDFILE does not exist, process is not running" + else + PID=$(cat $PIDFILE) + echo "Stopping ..." + $CLIEXEC shutdown save 2>/dev/null + while [ -x /proc/${PID} ] + do + echo "Waiting for Redis to shutdown ..." + sleep 1 + done + echo "Redis stopped" + rm -rf $PIDFILE + fi +} + + +case "$1" in + start) + redis_start + ;; + stop) + redis_stop + ;; + restart|reload) + redis_stop + sleep 0.3 + redis_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/redis/install.sh b/plugins/redis/install.sh new file mode 100755 index 000000000..681e88717 --- /dev/null +++ b/plugins/redis/install.sh @@ -0,0 +1,96 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# https://www.cnblogs.com/zlonger/p/16177595.html +# https://www.cnblogs.com/BNTang/articles/15841688.html + +# ps -ef|grep redis |grep -v grep | awk '{print $2}' | xargs kill + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/redis && bash install.sh install 7.2.2 + +# cmd查看| info replication +# /Users/midoks/Desktop/mwdev/server/redis/bin/redis-cli -h 127.0.0.1 -p 6399 +# /www/server/redis/bin/redis-cli -h 127.0.0.1 -p 6399 + +VERSION=$2 + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/source/redis + + FILE_TGZ=redis-${VERSION}.tar.gz + REDIS_DIR=$serverPath/source/redis + + if [ ! -f $REDIS_DIR/${FILE_TGZ} ];then + wget --no-check-certificate -O $REDIS_DIR/${FILE_TGZ} https://download.redis.io/releases/${FILE_TGZ} + fi + + cd $REDIS_DIR && tar -zxvf ${FILE_TGZ} + + CMD_MAKE=`which gmake` + if [ "$?" == "0" ];then + cd redis-${VERSION} && gmake PREFIX=$serverPath/redis install + else + cd redis-${VERSION} && make PREFIX=$serverPath/redis install + fi + + if [ -d $serverPath/redis ];then + mkdir -p $serverPath/redis/data + sed '/^ *#/d' redis.conf > $serverPath/redis/redis.conf + + echo "${VERSION}" > $serverPath/redis/version.pl + echo '安装完成' + + cd ${rootPath} && python3 ${rootPath}/plugins/redis/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/redis/index.py initd_install + + else + echo '安装失败!' + fi + + if [ -d ${REDIS_DIR}/redis-${VERSION} ];then + rm -rf ${REDIS_DIR}/redis-${VERSION} + fi +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/redis.service ];then + systemctl stop redis + systemctl disable redis + rm -rf /usr/lib/systemd/system/redis.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/redis.service ];then + systemctl stop redis + systemctl disable redis + rm -rf /lib/systemd/system/redis.service + systemctl daemon-reload + fi + + if [ -f $serverPath/redis/initd/redis ];then + $serverPath/redis/initd/redis stop + fi + + if [ -d $serverPath/redis ];then + rm -rf $serverPath/redis + fi + + echo "卸载redis成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/redis/js/redis.js b/plugins/redis/js/redis.js new file mode 100755 index 000000000..1697e96aa --- /dev/null +++ b/plugins/redis/js/redis.js @@ -0,0 +1,297 @@ +function redisPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'redis'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function redisPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'redis'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +//redis状态 start +function redisStatus(version) { + + redisPost('run_info',version, {},function(data){ + var rdata = $.parseJSON(data.data); + + if ('status' in rdata && !rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + hit = (parseInt(rdata.keyspace_hits) / (parseInt(rdata.keyspace_hits) + parseInt(rdata.keyspace_misses)) * 100).toFixed(2); + var con = '
                                \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                字段当前值说明
                                uptime_in_days' + rdata.uptime_in_days + '已运行天数
                                tcp_port' + rdata.tcp_port + '当前监听端口
                                connected_clients' + rdata.connected_clients + '连接的客户端数量
                                used_memory_rss' + toSize(rdata.used_memory_rss) + 'Redis当前占用的系统内存总量
                                used_memory' + toSize(rdata.used_memory) + 'Redis当前已分配的内存总量
                                used_memory_peak' + toSize(rdata.used_memory_peak) + 'Redis历史分配内存的峰值
                                mem_fragmentation_ratio' + rdata.mem_fragmentation_ratio + '%内存碎片比率
                                total_connections_received' + rdata.total_connections_received + '运行以来连接过的客户端的总数量
                                total_commands_processed' + rdata.total_commands_processed + '运行以来执行过的命令的总数量
                                instantaneous_ops_per_sec' + rdata.instantaneous_ops_per_sec + '服务器每秒钟执行的命令数量
                                keyspace_hits' + rdata.keyspace_hits + '查找数据库键成功的次数
                                keyspace_misses' + rdata.keyspace_misses + '查找数据库键失败的次数
                                hit' + hit + '%查找数据库键命中率
                                latest_fork_usec' + rdata.latest_fork_usec + '最近一次 fork() 操作耗费的微秒数
                                '; + $(".soft-man-con").html(con); + }); +} + +function replStatus(version){ + redisPost('info_replication', version, {},function(data){ + var rdata = $.parseJSON(data.data); + + if ('status' in rdata && !rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var kv = { + 'role':'角色', + 'master_host':'连接主库HOST', + 'master_port':'连接主库PORT', + 'master_link_status':'连接主库状态', + 'master_last_io_seconds_ago':'上次同步时间', + 'master_sync_in_progress':'正在同步中', + 'slave_read_repl_offset':'从库读取复制位置', + 'slave_repl_offset':'从库复制位置', + 'slave_read_only':'从库是否仅读', + 'replica_announced':'已复制副本', + 'connected_slaves':'连接数量', + 'master_failover_state':'主库故障状态', + 'master_replid':'主库复制ID', + 'master_repl_offset':'主库复制位置', + 'repl_backlog_size':'backlog复制大小', + 'second_repl_offset':'复制位置时间', + 'repl_backlog_first_byte_offset':'第一个字节偏移量', + 'repl_backlog_histlen':'backlog中数据的长度', + 'repl_backlog_active':'开启复制缓冲区', + 'slave_priority':'同步优先级', + } + + var tbody_text = ''; + for (k in rdata){ + if (k == 'master_replid'){ + tbody_text += ''+k+'' + rdata[k] + ''+kv[k]+''; + } else{ + + if (k.substring(0,5) == 'slave' && !isNaN(k.substring(5))){ + tbody_text += ''+k+'' + rdata[k] + '从库配置信息'; + } else{ + tbody_text += ''+k+'' + rdata[k] + ''+kv[k]+''; + } + + + } + } + + var con = '
                                \ + \ + \ + '+tbody_text+'\ +
                                字段当前值说明
                                '; + $(".soft-man-con").html(con); + }); +} + +function clusterStatus(version){ + redisPost('cluster_info', version, {},function(data){ + var rdata = $.parseJSON(data.data); + + if ('status' in rdata && !rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var kv = { + 'cluster_state':'集群状态', + 'cluster_slots_assigned':'被分配的槽', + 'cluster_slots_ok':'被分配的槽状态', + 'cluster_known_nodes':'知道的节点', + 'cluster_size':'大小', + 'cluster_stats_messages_sent':'发送', + 'cluster_stats_messages_received':'接收', + 'cluster_current_epoch':'集群当前epoch', + 'cluster_my_epoch':'当前我的epoch', + 'cluster_slots_pfail':'处于PFAIL状态的槽数', + 'cluster_slots_fail':'处于FAIL状态的槽数', + 'total_cluster_links_buffer_limit_exceeded':'超出缓冲区总数', + } + + var tbody_text = ''; + for (k in rdata){ + var desc = k; + if (k in kv){ + desc = kv[k]; + } + + if (k == 'master_replid'){ + tbody_text += ''+k+'' + rdata[k] + ''+desc+''; + } else{ + tbody_text += ''+k+'' + rdata[k] + ''+desc+''; + } + } + + if (tbody_text == ''){ + tbody_text += '无数据/未设置集群'; + } + + var con = '
                                \ + \ + \ + '+tbody_text+'\ +
                                字段当前值说明
                                '; + $(".soft-man-con").html(con); + }); +} + +function clusterNodes(version){ + redisPost('cluster_nodes', version, {},function(data){ + var rdata = $.parseJSON(data.data); + + if ('status' in rdata && !rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + // console.log(rdata); + var tbody_text = ''; + for (k in rdata){ + tbody_text += ''+ rdata[k] +''; + } + + if (tbody_text == ''){ + tbody_text += '无数据/未设置集群'; + } + + var con = '
                                \ + \ + \ + '+tbody_text+'\ +
                                节点信息
                                '; + $(".soft-man-con").html(con); + }); +} + +//redis状态 end + +//配置修改 +function getRedisConfig(version) { + redisPost('get_redis_conf', version,'',function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '70' + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = '' + break; + case 1: + var selected_1 = (rdata[i].value == 'On') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'Off') ? 'selected' : ''; + ibody = '' + break; + } + mlist += '

                                ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

                                ' + } + var con = '
                                ' + mlist + '\ +
                                \ +
                                \ +
                                ' + $(".soft-man-con").html(con); + }); +} + +//提交配置 +function submitConf(version) { + var data = { + version: version, + bind: $("input[name='bind']").val(), + 'port': $("input[name='port']").val(), + 'timeout': $("input[name='timeout']").val(), + maxclients: $("input[name='maxclients']").val(), + databases: $("input[name='databases']").val(), + requirepass: $("input[name='requirepass']").val(), + maxmemory: $("input[name='maxmemory']").val(), + }; + + redisPost('submit_redis_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +function redisReadme(){ + var cmd_01 = '/www/server/redis/bin/redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 --cluster-replicas 0'; + var cmd_02 = '/www/server/redis/bin/redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster-replicas 1'; + + + var readme = '
                                  '; + readme += '
                                • 集群创建1
                                • '; + readme += '
                                • '+cmd_01+'
                                • '; + readme += '
                                • 集群创建2
                                • '; + readme += '
                                • '+cmd_02+'
                                • '; + readme += '
                                '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/redis/tpl/redis_cluster.conf b/plugins/redis/tpl/redis_cluster.conf new file mode 100644 index 000000000..8cf018c78 --- /dev/null +++ b/plugins/redis/tpl/redis_cluster.conf @@ -0,0 +1,94 @@ +daemonize yes +pidfile {$SERVER_PATH}/redis/redis.pid + +loglevel notice +logfile {$SERVER_PATH}/redis/data/redis.log +databases 16 + +timeout 0 +tcp-keepalive 0 + +bind 127.0.0.1 +port 6379 + +################################ SNAPSHOTTING ################################# + +save 900 100 +save 300 1000 +save 60 1000000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir {$SERVER_PATH}/redis/data/ + +################################# CLUSTER ################################# + + +protected-mode no +cluster-enabled yes +cluster-config-file nodes_{$REDIS_PASS}.conf +cluster-require-full-coverage no +cluster-node-timeout 15000 + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 218mb +maxmemory-policy volatile-ttl + +############################## APPEND ONLY MODE ############################### + + +#appendonly no +# appendfsync always +#appendfsync everysec +# appendfsync no +#no-appendfsync-on-rewrite no + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing yes + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/redis/tpl/redis_simple.conf b/plugins/redis/tpl/redis_simple.conf new file mode 100644 index 000000000..f8d5ce16d --- /dev/null +++ b/plugins/redis/tpl/redis_simple.conf @@ -0,0 +1,87 @@ +daemonize yes +pidfile {$SERVER_PATH}/redis/redis.pid + +bind 127.0.0.1 +port 6379 +requirepass {$REDIS_PASS} + +timeout 0 +tcp-keepalive 0 + +loglevel notice + +logfile {$SERVER_PATH}/redis/data/redis.log +databases 16 + +################################ SNAPSHOTTING ################################# + +save 900 100 +save 300 1000 +save 60 1000000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir {$SERVER_PATH}/redis/data/ + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 218mb +maxmemory-policy volatile-ttl + +############################## APPEND ONLY MODE ############################### + + +#appendonly no +# appendfsync always +#appendfsync everysec +# appendfsync no +#no-appendfsync-on-rewrite no + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing yes + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/redis/tpl/redis_slave.conf b/plugins/redis/tpl/redis_slave.conf new file mode 100644 index 000000000..9acff6985 --- /dev/null +++ b/plugins/redis/tpl/redis_slave.conf @@ -0,0 +1,91 @@ +daemonize yes +pidfile {$SERVER_PATH}/redis/redis.pid + +bind 127.0.0.1 +port 6379 +requirepass {$REDIS_PASS} + +timeout 0 +tcp-keepalive 0 + +loglevel notice + +logfile {$SERVER_PATH}/redis/data/redis.log +databases 16 + +################################ SNAPSHOTTING ################################# + +save 900 100 +save 300 1000 +save 60 1000000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir {$SERVER_PATH}/redis/data/ + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +# 填写主库信息 +#slaveof 127.0.0.1 6379 +#masterauth 123123 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 0mb +maxmemory-policy volatile-ttl + +############################## APPEND ONLY MODE ############################### + + +#appendonly no +# appendfsync always +#appendfsync everysec +# appendfsync no +#no-appendfsync-on-rewrite no + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing yes + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/redis/tpl/redis_slave_mem.conf b/plugins/redis/tpl/redis_slave_mem.conf new file mode 100644 index 000000000..bbe35527f --- /dev/null +++ b/plugins/redis/tpl/redis_slave_mem.conf @@ -0,0 +1,75 @@ +daemonize yes +pidfile {$SERVER_PATH}/redis/redis.pid + +loglevel notice +logfile {$SERVER_PATH}/redis/data/redis.log +databases 16 + +timeout 0 +tcp-keepalive 0 + +bind 127.0.0.1 +port 6379 +requirepass {$REDIS_PASS} + +################################ SNAPSHOTTING ################################# + +save "" +stop-writes-on-bgsave-error no + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +# 填写主库信息 +#slaveof 127.0.0.1 6379 +#masterauth 123123 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 218mb +maxmemory-policy allkeys-lru + +############################## APPEND ONLY MODE ############################### + + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing no + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/rsyncd/conf/config.json b/plugins/rsyncd/conf/config.json new file mode 100644 index 000000000..be3fd10a8 --- /dev/null +++ b/plugins/rsyncd/conf/config.json @@ -0,0 +1,26 @@ +{ + "receive":{ + "default":{ + "uid": "root", + "use chroot": "no", + "dont compress": "*.gz *.tgz *.zip *.z *.Z *.rpm *.deb *.bz2 *.mp4 *.avi *.swf *.rar", + "hosts allow": "", + "max connections": 200, + "gid": "root", + "timeout": 600, + "pid file": "/var/run/rsyncd.pid", + "log file": "/var/log/rsyncd.log", + "port": 873 + }, + "list":[] + }, + "send":{ + "default":{ + "logfile": "/www/server/rsyncd/lsyncd.log", + "inotifyMode": "CloseWrite", + "maxProcesses": 8, + "statusFile": "/www/server/rsyncd/lsyncd.status" + }, + "list":[] + } +} \ No newline at end of file diff --git a/plugins/rsyncd/conf/lsyncd.conf b/plugins/rsyncd/conf/lsyncd.conf new file mode 100644 index 000000000..b2ed2416f --- /dev/null +++ b/plugins/rsyncd/conf/lsyncd.conf @@ -0,0 +1,6 @@ +settings { + logfile = "{$SERVER_PATH}/rsyncd/lsyncd.log", + inotifyMode = "CloseWrite", + maxProcesses = 8, + statusFile = "{$SERVER_PATH}/rsyncd/lsyncd.status" +} \ No newline at end of file diff --git a/plugins/rsyncd/conf/rsyncd.conf b/plugins/rsyncd/conf/rsyncd.conf new file mode 100644 index 000000000..43d17f270 --- /dev/null +++ b/plugins/rsyncd/conf/rsyncd.conf @@ -0,0 +1,8 @@ +uid = www +gid = www +use chroot = no +max connections = 100 +log file = /var/log/rsyncd.log +pid file = /var/run/rsyncd.pid +list = false +hosts allow = * \ No newline at end of file diff --git a/plugins/rsyncd/ico.png b/plugins/rsyncd/ico.png new file mode 100644 index 000000000..8dd3748bd Binary files /dev/null and b/plugins/rsyncd/ico.png differ diff --git a/plugins/rsyncd/index.html b/plugins/rsyncd/index.html new file mode 100755 index 000000000..21a803d7b --- /dev/null +++ b/plugins/rsyncd/index.html @@ -0,0 +1,95 @@ + +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                发送配置

                                +

                                接收配置

                                +

                                说明

                                +
                                +
                                +
                                +
                                +
                                +
                                + + \ No newline at end of file diff --git a/plugins/rsyncd/index.py b/plugins/rsyncd/index.py new file mode 100755 index 000000000..5967c5a2e --- /dev/null +++ b/plugins/rsyncd/index.py @@ -0,0 +1,997 @@ +# coding: utf-8 + +import time +import random +import os +import json +import re +import sys + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'rsyncd' + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$SERVER_PATH}', service_path) + return content + + +def status(): + data = mw.execShell( + "ps -ef|grep rsync |grep -v grep | grep -v python | awk '{print $2}'") + if data[0] == '': + return 'stop' + + # data = mw.execShell( + # "ps -ef|grep lsyncd |grep -v grep | grep -v python | awk '{print $2}'") + # if data[0] == '': + # return 'stop' + + return 'start' + + +def appConf(): + return getServerDir() + '/rsyncd.conf' + + +def appAuthPwd(name): + nameDir = getServerDir() + '/receive/' + name + if not os.path.exists(nameDir): + mw.execShell("mkdir -p " + nameDir) + return nameDir + '/auth.db' + + +def getLog(): + conf_path = appConf() + conf = mw.readFile(conf_path) + rep = r'log file\s*=\s*(.*)' + tmp = re.search(rep, conf) + if not tmp: + return '' + return tmp.groups()[0] + + +def getLsyncdLog(): + path = getServerDir() + "/lsyncd.conf" + conf = mw.readFile(path) + rep = r'logfile\s*=\s*\"(.*)\"' + tmp = re.search(rep, conf) + if not tmp: + return '' + return tmp.groups()[0] + + +def __release_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'RSYNC同步', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + + +def openPort(): + for i in ["873"]: + __release_port(i) + return True + + +def initDReceive(): + # conf + conf_path = appConf() + conf_tpl_path = getPluginDir() + '/conf/rsyncd.conf' + if not os.path.exists(conf_path): + content = mw.readFile(conf_tpl_path) + mw.writeFile(conf_path, content) + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + + file_bin = initD_path + '/' + getPluginName() + file_tpl = getInitDTpl() + # print(file_bin, file_tpl) + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + lock_file = getServerDir() + "/installed_rsyncd.pl" + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/rsyncd.service' + systemServiceTpl = getPluginDir() + '/init.d/rsyncd.service.tpl' + if not os.path.exists(lock_file): + + rsync_bin = mw.execShell('which rsync')[0].strip() + if rsync_bin == '': + print('rsync missing!') + exit(0) + + service_path = mw.getServerDir() + se = mw.readFile(systemServiceTpl) + se = se.replace('{$SERVER_PATH}', service_path) + se = se.replace('{$RSYNC_BIN}', rsync_bin) + mw.writeFile(systemService, se) + mw.execShell('systemctl daemon-reload') + + mw.writeFile(lock_file, "ok") + openPort() + + rlog = getLog() + if os.path.exists(rlog): + mw.writeFile(rlog, '') + return file_bin + + +def initDSend(): + + service_path = mw.getServerDir() + + conf_path = getServerDir() + '/lsyncd.conf' + conf_tpl_path = getPluginDir() + '/conf/lsyncd.conf' + if not os.path.exists(conf_path): + content = mw.readFile(conf_tpl_path) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(conf_path, content) + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + + # initd replace + file_bin = initD_path + '/lsyncd' + file_tpl = getPluginDir() + "/init.d/lsyncd.tpl" + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + lock_file = getServerDir() + "/installed.pl" + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/lsyncd.service' + systemServiceTpl = getPluginDir() + '/init.d/lsyncd.service.tpl' + if not os.path.exists(lock_file): + lsyncd_bin = mw.execShell('which lsyncd')[0].strip() + if lsyncd_bin == '': + print('lsyncd missing!') + exit(0) + + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$LSYNCD_BIN}', lsyncd_bin) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + mw.writeFile(lock_file, "ok") + + lslog = getLsyncdLog() + if os.path.exists(lslog): + mw.writeFile(lslog, '') + + return file_bin + + +def getDefaultConf(): + path = getServerDir() + "/config.json" + data = mw.readFile(path) + data = json.loads(data) + return data + + +def setDefaultConf(data): + path = getServerDir() + "/config.json" + mw.writeFile(path, json.dumps(data)) + return True + + +def initConfigJson(): + path = getServerDir() + "/config.json" + tpl = getPluginDir() + "/conf/config.json" + if not os.path.exists(path): + data = mw.readFile(tpl) + data = json.loads(data) + mw.writeFile(path, json.dumps(data)) + + +def initDreplace(): + + initDSend() + + # conf + file_bin = initDReceive() + initConfigJson() + + return file_bin + + +def rsyncOp(method): + file = initDreplace() + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' rsyncd') + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return 'fail' + + +def lsyncdOp(method): + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' lsyncd') + if data[1] == '': + return 'ok' + return 'fail' + return 'fail' + +def start(): + status = rsyncOp('start') + lsyncdOp('start') + return status + +def stop(): + status = rsyncOp('stop') + lsyncdOp('stop') + return status + +def restart(): + status = rsyncOp('restart') + lsyncdOp('restart') + return status + +def reload(): + status = rsyncOp('reload') + lsyncdOp('reload') + return status + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status rsyncd | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + + shell_cmd = 'systemctl status lsyncd | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable lsyncd') + mw.execShell('systemctl enable rsyncd') + return 'ok' + + +def initdUinstall(): + if not app_debug: + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl diable lsyncd') + mw.execShell('systemctl diable rsyncd') + return 'ok' + + +def getRecListData(): + path = appConf() + content = mw.readFile(path) + + flist = re.findall("\\[(.*)\\]", content) + + flist_len = len(flist) + ret_list = [] + for i in range(flist_len): + tmp = {} + tmp['name'] = flist[i] + n = i + 1 + reg = '' + if n == flist_len: + reg = r'\[' + flist[i] + r'\](.*)\[?' + else: + reg = r'\[' + flist[i] + r'\](.*)\[' + flist[n] + r'\]' + + t1 = re.search(reg, content, re.S) + if t1: + args = t1.groups()[0] + # print('args start', args, 'args_end') + t2 = re.findall(r'\s*(.*)\s*\=\s*?(.*)?', args, re.M | re.I) + for i in range(len(t2)): + tmp[t2[i][0].strip()] = t2[i][1].strip() + ret_list.append(tmp) + + return ret_list + + +def getRecListDataBy(name): + l = getRecListData() + for x in range(len(l)): + if name == l[x]["name"]: + return l[x] + + +def getRecList(): + ret_list = getRecListData() + return mw.returnJson(True, 'ok', ret_list) + + +def addRec(): + args = getArgs() + data = checkArgs(args, ['name', 'path', 'pwd', 'ps']) + if not data[0]: + return data[1] + + args_name = args['name'] + args_pwd = args['pwd'] + args_path = args['path'] + args_ps = args['ps'] + + if not mw.isAppleSystem(): + if os.path.exists(args_path): + import utils.file as utils_file + info = utils_file.getAccess(args_path) + file_chown = info['chown'] + if file_chown != 'www': + return mw.returnJson(False, '建议手动执行命令: chown -R www:www '+ args_path) + else: + os.system("mkdir -p " + args_path + " &") + os.system("chown -R www:www " + args_path + " &") + os.system("chmod -R 755 " + args_path + " &") + + delRecBy(args_name) + + auth_path = appAuthPwd(args_name) + pwd_content = args_name + ':' + args_pwd + "\n" + mw.writeFile(auth_path, pwd_content) + mw.execShell("chmod 600 " + auth_path) + + path = appConf() + content = mw.readFile(path) + + con = "\n\n" + '[' + args_name + ']' + "\n" + con += 'path = ' + args_path + "\n" + con += 'comment = ' + args_ps + "\n" + con += 'auth users = ' + args_name + "\n" + con += 'ignore errors' + "\n" + con += 'secrets file = ' + auth_path + "\n" + con += 'read only = false' + + content = content.strip() + "\n" + con + mw.writeFile(path, content) + return mw.returnJson(True, '添加成功') + + +def getRec(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + + if name == "": + tmp = {} + tmp["name"] = "" + tmp["comment"] = "" + tmp["path"] = mw.getWwwDir() + tmp["pwd"] = mw.getRandomString(16) + return mw.returnJson(True, 'OK', tmp) + + data = getRecListDataBy(name) + + content = mw.readFile(data['secrets file']) + pwd = content.strip().split(":") + data['pwd'] = pwd[1] + return mw.returnJson(True, 'OK', data) + + +def delRecBy(name): + try: + path = appConf() + content = mw.readFile(path) + + reclist = getRecListData() + ret_list_len = len(reclist) + is_end = False + next_name = '' + for x in range(ret_list_len): + tmp = reclist[x] + if tmp['name'] == name: + + secrets_file = tmp['secrets file'] + tp = os.path.dirname(secrets_file) + if os.path.exists(tp): + mw.execShell("rm -rf " + tp) + + if x + 1 == ret_list_len: + is_end = True + else: + next_name = reclist[x + 1]['name'] + reg = '' + if is_end: + reg = r'\[' + name + r'\]\s*(.*)' + else: + reg = r'\\[' + name + r'\]\s*(.*)\s*\[' + next_name + r'\]' + + conre = re.search(reg, content, re.S) + content = content.replace("[" + name + "]\n" + conre.groups()[0], '') + mw.writeFile(path, content) + except Exception as e: + return False + return True + + +def delRec(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + name = args['name'] + ok = delRecBy(name) + if ok: + return mw.returnJson(True, '删除成功!') + return mw.returnJson(False, '删除失败!') + + +def cmdRecSecretKey(): + import base64 + + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + info = getRecListDataBy(name) + + secrets_file = info['secrets file'] + content = mw.readFile(info['secrets file']) + pwd = content.strip().split(":") + + m = {"A": info['name'], "B": pwd[1], "C": "873"} + m = json.dumps(m) + m = m.encode("utf-8") + m = base64.b64encode(m) + cmd = m.decode("utf-8") + return mw.returnJson(True, 'OK!', cmd) + + +def cmdRecCmd(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + info = getRecListDataBy(name) + ip = mw.getLocalIp() + + content = mw.readFile(info['secrets file']) + pwd = content.strip().split(":") + + tmp_name = '/tmp/' + name + '.pass' + + cmd = 'echo "' + pwd[1] + '" > ' + tmp_name + '
                                ' + cmd += 'chmod 600 ' + tmp_name + '
                                ' + cmd += 'rsync -arv --password-file=' + tmp_name + \ + ' --progress --delete /project ' + name + '@' + ip + '::' + name + return mw.returnJson(True, 'OK!', cmd) + + +# ----------------------------- rsyncdSend start ------------------------- + +def lsyncdReload(): + data = mw.execShell( + "ps -ef|grep lsyncd |grep -v grep | grep -v python | awk '{print $2}'") + if data[0] == '': + mw.execShell('systemctl start lsyncd') + else: + mw.execShell('systemctl restart lsyncd') + + +def makeLsyncdConf(data): + # print(data) + + lsyncd_data = data['send'] + lsyncd_setting = lsyncd_data['default'] + + content = "settings {\n" + for x in lsyncd_setting: + v = lsyncd_setting[x] + # print(v, type(v)) + if type(v) == str: + content += "\t" + x + ' = "' + v + "\",\n" + elif type(v) == int: + content += "\t" + x + ' = ' + str(v) + ",\n" + content += "}\n\n" + + lsyncd_list = lsyncd_data['list'] + + rsync_bin = mw.execShell('which rsync')[0].strip() + send_dir = getServerDir() + "/send" + + if len(lsyncd_list) > 0: + for x in range(len(lsyncd_list)): + + t = lsyncd_list[x] + name_dir = send_dir + "/" + t["name"] + if not os.path.exists(name_dir): + mw.execShell("mkdir -p " + name_dir) + + cmd_exclude = name_dir + "/exclude" + cmd_exclude_txt = "" + for x in t['exclude']: + cmd_exclude_txt += x + "\n" + mw.writeFile(cmd_exclude, cmd_exclude_txt) + cmd_pass = name_dir + "/pass" + if os.path.exists(cmd_pass): + mw.execShell("chmod 755 " + cmd_pass) + mw.writeFile(cmd_pass, t['password']) + mw.execShell("chmod 600 " + cmd_pass) + + delete_ok = ' ' + if t['delete'] == "true": + delete_ok = ' --delete ' + + remote_addr = t['name'] + '@' + t['ip'] + "::" + t['name'] + cmd = rsync_bin + " -avzP " + "--port=" + str(t['rsync']['port']) + " --bwlimit=" + t['rsync'][ + 'bwlimit'] + delete_ok + " --exclude-from=" + cmd_exclude + " --password-file=" + cmd_pass + " " + t["path"] + " " + remote_addr + mw.writeFile(name_dir + "/cmd", cmd) + mw.execShell("cmod +x " + name_dir + "/cmd") + + if t['realtime'] == "false": + continue + + # print(x, t) + content += "sync {\n" + content += "\tdefault.rsync,\n" + content += "\tsource = \"" + t['path'] + "\",\n" + content += "\ttarget = \"" + remote_addr + "\",\n" + content += "\tdelete = " + t['delete'] + ",\n" + content += "\tdelay = " + t['delay'] + ",\n" + content += "\tinit = false,\n" + + exclude_str = json.dumps(t['exclude']) + exclude_str = exclude_str.replace("[", "{") + exclude_str = exclude_str.replace("]", "}") + # print(exclude_str) + content += "\texclude = " + exclude_str + ",\n" + + # rsync + content += "\trsync = {\n" + content += "\t\tbinary = \"" + rsync_bin + "\",\n" + content += "\t\tarchive = true,\n" + content += "\t\tverbose = true,\n" + content += "\t\tcompress = " + t['rsync']['compress'] + ",\n" + content += "\t\tpassword_file = \"" + cmd_pass + "\",\n" + + content += "\t\t_extra = {\"--bwlimit=" + t['rsync'][ + 'bwlimit'] + "\", \"--port=" + str(t['rsync']['port']) + "\"},\n" + + content += "\t}\n" + content += "}\n" + + path = getServerDir() + "/lsyncd.conf" + mw.writeFile(path, content) + + lsyncdReload() + + import tool_task + tool_task.createBgTask(lsyncd_list) + + +def lsyncdListFindIp(slist, ip): + for x in range(len(slist)): + if slist[x]["ip"] == ip: + return (True, x) + return (False, -1) + + +def lsyncdListFindName(slist, name): + for x in range(len(slist)): + if slist[x]["name"] == name: + return (True, x) + return (False, -1) + + +def lsyncdList(): + data = getDefaultConf() + send = data['send'] + return mw.returnJson(True, "设置成功!", send) + + +def lsyncdGet(): + import base64 + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + data = getDefaultConf() + + slist = data['send']["list"] + res = lsyncdListFindName(slist, name) + + rsync = { + 'bwlimit': "1024", + "compress": "true", + "archive": "true", + "verbose": "true" + } + + info = { + "secret_key": '', + "ip": '', + "path": mw.getServerDir(), + 'rsync': rsync, + 'realtime': "true", + 'delete': "false", + } + if res[0]: + list_index = res[1] + info = slist[list_index] + m = {"A": info['name'], "B": info["password"], "C": "873"} + m = json.dumps(m) + m = m.encode("utf-8") + m = base64.b64encode(m) + info['secret_key'] = m.decode("utf-8") + return mw.returnJson(True, "OK", info) + + +def lsyncdDelete(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + name = args['name'] + data = getDefaultConf() + slist = data['send']["list"] + res = lsyncdListFindName(slist, name) + retdata = {} + if res[0]: + list_index = res[1] + slist.pop(list_index) + + data['send']["list"] = slist + setDefaultConf(data) + makeLsyncdConf(data) + return mw.returnJson(True, "OK") + + +def lsyncdAdd(): + import base64 + + args = getArgs() + data = checkArgs(args, ['ip', 'conn_type', 'path', 'delay', 'period', 'bwlimit']) + if not data[0]: + return data[1] + + ip = args['ip'] + path = args['path'] + + if not mw.isAppleSystem(): + if os.path.exists(path): + import utils.file as utils_file + info = utils_file.getAccess(path) + file_chown = info['chown'] + if file_chown != 'www': + return mw.returnJson(False, '建议手动执行命令: chown -R www:www '+ args_path) + else: + os.system("mkdir -p " + path + " &") + os.system("chown -R www:www " + path + " &") + os.system("chmod -R 755 " + path + " &") + + conn_type = args['conn_type'] + + delete = args['delete'] + realtime = args['realtime'] + delay = args['delay'] + bwlimit = args['bwlimit'] + compress = args['compress'] + period = args['period'] + + hour = args['hour'] + minute = args['minute'] + minute_n = args['minute-n'] + + info = { + "ip": ip, + "path": path, + "delete": delete, + "realtime": realtime, + 'delay': delay, + "conn_type": conn_type, + "period": period, + "hour": hour, + "minute": minute, + "minute-n": minute_n, + } + + if conn_type == "key": + + secret_key_check = checkArgs(args, ['secret_key']) + if not secret_key_check[0]: + return secret_key_check[1] + + secret_key = args['secret_key'] + try: + m = base64.b64decode(secret_key) + m = json.loads(m) + info['name'] = m['A'] + info['password'] = m['B'] + info['port'] = m['C'] + except Exception as e: + return mw.returnJson(False, "接收密钥格式错误!") + else: + data = checkArgs(args, ['sname', 'password']) + if not data[0]: + return data[1] + + info['name'] = args['sname'] + info['password'] = args['password'] + info['port'] = args['port'] + + rsync = { + 'bwlimit': bwlimit, + "port": info['port'], + "compress": compress, + "archive": "true", + "verbose": "true" + } + + info['rsync'] = rsync + + data = getDefaultConf() + + slist = data['send']["list"] + res = lsyncdListFindName(slist, info['name']) + + if not 'exclude' in info: + if res[0]: + info["exclude"] = slist[res[1]]['exclude'] + else: + info["exclude"] = [ + "/**.upload.tmp", "**/*.log", "**/*.tmp", + "**/*.temp", ".git", ".gitignore", ".user.ini", + ] + + if res[0]: + list_index = res[1] + slist[list_index] = info + else: + slist.append(info) + + data['send']["list"] = slist + + setDefaultConf(data) + makeLsyncdConf(data) + return mw.returnJson(True, "设置成功!") + + +def lsyncdRun(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + send_dir = getServerDir() + "/send" + name = args['name'] + app_dir = send_dir + "/" + name + + cmd = "bash " + app_dir + "/cmd >> " + app_dir + "/run.log" + " 2>&1 &" + mw.execShell(cmd) + return mw.returnJson(True, "执行成功!") + + +def lsyncdConfLog(): + logs_path = getServerDir() + "/lsyncd.log" + return logs_path + + +def lsyncdLog(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + send_dir = getServerDir() + "/send" + name = args['name'] + app_dir = send_dir + "/" + name + return app_dir + "/run.log" + + +def lsyncdGetExclude(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + data = getDefaultConf() + slist = data['send']["list"] + res = lsyncdListFindName(slist, args['name']) + i = res[1] + info = slist[i] + return mw.returnJson(True, "OK!", info['exclude']) + + +def lsyncdRemoveExclude(): + args = getArgs() + data = checkArgs(args, ['name', 'exclude']) + if not data[0]: + return data[1] + + exclude = args['exclude'] + + data = getDefaultConf() + slist = data['send']["list"] + res = lsyncdListFindName(slist, args['name']) + i = res[1] + info = slist[i] + + exclude_list = info['exclude'] + exclude_pop_key = -1 + for x in range(len(exclude_list)): + if exclude_list[x] == exclude: + exclude_pop_key = x + + if exclude_pop_key > -1: + exclude_list.pop(exclude_pop_key) + + data['send']["list"][i]['exclude'] = exclude_list + setDefaultConf(data) + makeLsyncdConf(data) + return mw.returnJson(True, "OK!", exclude_list) + + +def lsyncdAddExclude(): + args = getArgs() + data = checkArgs(args, ['name', 'exclude']) + if not data[0]: + return data[1] + + exclude = args['exclude'] + + data = getDefaultConf() + slist = data['send']["list"] + res = lsyncdListFindName(slist, args['name']) + i = res[1] + info = slist[i] + + exclude_list = info['exclude'] + exclude_list.append(exclude) + + data['send']["list"][i]['exclude'] = exclude_list + setDefaultConf(data) + makeLsyncdConf(data) + return mw.returnJson(True, "OK!", exclude_list) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(appConf()) + elif func == 'run_log': + print(getLog()) + elif func == 'rec_list': + print(getRecList()) + elif func == 'add_rec': + print(addRec()) + elif func == 'del_rec': + print(delRec()) + elif func == 'get_rec': + print(getRec()) + elif func == 'cmd_rec_secret_key': + print(cmdRecSecretKey()) + elif func == 'cmd_rec_cmd': + print(cmdRecCmd()) + elif func == 'lsyncd_list': + print(lsyncdList()) + elif func == 'lsyncd_add': + print(lsyncdAdd()) + elif func == 'lsyncd_get': + print(lsyncdGet()) + elif func == 'lsyncd_delete': + print(lsyncdDelete()) + elif func == 'lsyncd_run': + print(lsyncdRun()) + elif func == 'lsyncd_log': + print(lsyncdLog()) + elif func == 'lsyncd_conf_log': + print(lsyncdConfLog()) + elif func == 'lsyncd_get_exclude': + print(lsyncdGetExclude()) + elif func == 'lsyncd_remove_exclude': + print(lsyncdRemoveExclude()) + elif func == 'lsyncd_add_exclude': + print(lsyncdAddExclude()) + else: + print('error') diff --git a/plugins/rsyncd/info.json b/plugins/rsyncd/info.json new file mode 100755 index 000000000..1f8b746e8 --- /dev/null +++ b/plugins/rsyncd/info.json @@ -0,0 +1,16 @@ +{ + "sort":2, + "title":"rsyncd", + "tip":"soft", + "name":"rsyncd", + "type":"软件", + "ps":"rsyncd同步助手", + "versions":"2.0", + "shell":"install.sh", + "checks":"server/rsyncd", + "path": "server/rsyncd", + "author":"samba", + "home":"https://rsync.samba.org/", + "date":"2019-03-05", + "pid":"4" +} \ No newline at end of file diff --git a/plugins/rsyncd/init.d/lsyncd.service.tpl b/plugins/rsyncd/init.d/lsyncd.service.tpl new file mode 100755 index 000000000..7c6a03c60 --- /dev/null +++ b/plugins/rsyncd/init.d/lsyncd.service.tpl @@ -0,0 +1,10 @@ +[Unit] +Description=Lightweight inotify based sync daemon +ConditionPathExists={$SERVER_PATH}/rsyncd/lsyncd.conf + +[Service] +Type=simple +ExecStart={$LSYNCD_BIN} -nodaemon {$SERVER_PATH}/rsyncd/lsyncd.conf + +[Install] +WantedBy=multi-user.target diff --git a/plugins/rsyncd/init.d/lsyncd.tpl b/plugins/rsyncd/init.d/lsyncd.tpl new file mode 100755 index 000000000..a5ccdcad6 --- /dev/null +++ b/plugins/rsyncd/init.d/lsyncd.tpl @@ -0,0 +1,108 @@ +#!/bin/bash +# +# chkconfig: - 85 15 +# description: Lightweight inotify based sync daemon +# +# processname: lsyncd +# config: /etc/lsyncd.conf +# config: /etc/sysconfig/lsyncd +# pidfile: /var/run/lsyncd.pid +# Source function library + +if [ -f /etc/init.d/functions ];then + . /etc/init.d/functions +fi + +if [ -f /lib/lsb/init-functions ];then + . /lib/lsb/init-functions +fi + +# Source networking configuration. +if [ -f /etc/sysconfig/network ];then + . /etc/sysconfig/network +fi + +LSYNCD_OPTIONS="-pidfile /var/run/lsyncd.pid {$SERVER_PATH}/rsyncd/lsyncd.conf" +if [ -e /etc/sysconfig/lsyncd ]; then + . /etc/sysconfig/lsyncd +fi +RETVAL=0 +prog="lsyncd" +thelock=/var/lock/subsys/lsyncd +LSYNCD_USER=root + +start() { + [ -f {$SERVER_PATH}/rsyncd/lsyncd.conf ] || exit 6 + echo -n $"Starting $prog: " + if [ $UID -ne 0 ]; then + RETVAL=1 + failure + else + nohup /usr/bin/lsyncd $LSYNCD_OPTIONS > /dev/null & + RETVAL=$? + [ $RETVAL -eq 0 ] && touch $thelock + fi; + echo + return $RETVAL +} + +stop() { + echo -n $"Stopping $prog: " + if [ $UID -ne 0 ]; then + RETVAL=1 + failure + else + killproc lsyncd + RETVAL=$? + [ $RETVAL -eq 0 ] && rm -f $thelock + fi; + echo + return $RETVAL +} + +reload(){ + echo -n $"Reloading $prog: " + killproc lsyncd -HUP + RETVAL=$? + echo + return $RETVAL +} + +restart(){ + stop + start +} + +condrestart(){ + [ -e $thelock ] && restart + return 0 +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + + restart) + restart + ;; + reload) + reload + ;; + condrestart) + condrestart + ;; + + status) + status lsyncd + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|reload}" + RETVAL=1 +esac + +exit $RETVAL diff --git a/plugins/rsyncd/init.d/rsyncd.service.tpl b/plugins/rsyncd/init.d/rsyncd.service.tpl new file mode 100644 index 000000000..72dd0efc9 --- /dev/null +++ b/plugins/rsyncd/init.d/rsyncd.service.tpl @@ -0,0 +1,9 @@ +[Unit] +Description=fast remote file copy program daemon +ConditionPathExists={$SERVER_PATH}/rsyncd/rsyncd.conf + +[Service] +ExecStart={$RSYNC_BIN} --config={$SERVER_PATH}/rsyncd/rsyncd.conf --daemon --no-detach + +[Install] +WantedBy=multi-user.target diff --git a/plugins/rsyncd/init.d/rsyncd.tpl b/plugins/rsyncd/init.d/rsyncd.tpl new file mode 100755 index 000000000..2ee441060 --- /dev/null +++ b/plugins/rsyncd/init.d/rsyncd.tpl @@ -0,0 +1,70 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: rsyncd Service + +### BEGIN INIT INFO +# Provides: rsyncd +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts rsyncd +# Description: starts the rsyncd +### END INIT INFO + +ROOT_PATH={$SERVER_PATH} + +p_start(){ + isStart=$(ps -ef | grep rsync | grep 'daemon' | grep -v grep | grep -v python | awk '{print $2}') + if [ "$isStart" == '' ];then + echo -e "Starting rsync... \c" + if [ -f /var/run/rsyncd.pid ]; then + rm -rf /var/run/rsyncd.pid + fi + /usr/bin/rsync --daemon --config=/etc/rsyncd.conf + sleep 0.3 + isStart=$(ps -ef | grep rsync | grep 'daemon' | grep -v grep | grep -v python | awk '{print $2}') + if [ "$isStart" == '' ];then + echo -e "\033[31mError: rsyncd service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "Starting rsyncd(pid $isStart) already running" + fi +} + +p_stop(){ + echo -e "Stopping rsyncd... \c"; + pids=$(ps -ef | grep rsync | grep 'daemon' | grep -v grep | grep -v python | awk '{print $2}') + arr=($pids) + + for p in ${arr[@]} + do + kill -9 $p + done + + if [ -f /var/run/rsyncd.pid ]; then + rm -rf /var/run/rsyncd.pid + fi + echo -e "\033[32mdone\033[0m" +} + + +case "$1" in + start) + p_start + ;; + stop) + p_stop + ;; + restart|reload) + p_stop + sleep 0.3 + p_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/rsyncd/install.sh b/plugins/rsyncd/install.sh new file mode 100755 index 000000000..a8a13d86a --- /dev/null +++ b/plugins/rsyncd/install.sh @@ -0,0 +1,94 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` + + +#获取信息和版本 +# bash /www/server/mdsever-web/scripts/getos.sh +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` + + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +if id www &> /dev/null ;then + echo "www uid is `id -u www`" + echo "www shell is `grep "^www:" /etc/passwd |cut -d':' -f7 `" +else + groupadd www + useradd -g www -s /bin/bash www +fi + +# echo $OSNAME + +Install_rsyncd() +{ + echo '正在安装脚本文件...' + + + if [ "$OSNAME" == "debian" ] || [ "$OSNAME" == "ubuntu" ];then + apt install -y rsync + apt install -y lsyncd + elif [[ "$OSNAME" == "arch" ]]; then + echo y | pacman -Sy rsync + echo y | pacman -Sy lsyncd + elif [[ "$OSNAME" == "macos" ]]; then + # brew install rsync + # brew install lsyncd + echo "ok" + else + yum install -y rsync + yum install -y lsyncd + fi + + mkdir -p $serverPath/rsyncd + mkdir -p $serverPath/rsyncd/receive + mkdir -p $serverPath/rsyncd/send + + echo '2.0' > $serverPath/rsyncd/version.pl + echo '安装完成' + cd ${rootPath} && python3 ${rootPath}/plugins/rsyncd/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/rsyncd/index.py initd_install +} + +Uninstall_rsyncd() +{ + if [ -f /usr/lib/systemd/system/rsyncd.service ] || [ -f /lib/systemd/system/rsyncd.service ];then + systemctl stop rsyncd + systemctl disable rsyncd + rm -rf /usr/lib/systemd/system/rsyncd.service + rm -rf /lib/systemd/system/rsyncd.service + systemctl daemon-reload + fi + + if [ -f /usr/lib/systemd/system/lsyncd.service ] || [ -f /lib/systemd/system/lsyncd.service ];then + systemctl stop lsyncd + systemctl disable lsyncd + rm -rf /usr/lib/systemd/system/lsyncd.service + rm -rf /lib/systemd/system/lsyncd.service + systemctl daemon-reload + fi + + if [ -f $serverPath/rsyncd/initd/rsyncd ];then + $serverPath/rsyncd/initd/rsyncd stop + fi + + rm -rf $serverPath/rsyncd + echo "卸载完成" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_rsyncd +else + Uninstall_rsyncd +fi diff --git a/plugins/rsyncd/js/base64.js b/plugins/rsyncd/js/base64.js new file mode 100644 index 000000000..4a5caa2a7 --- /dev/null +++ b/plugins/rsyncd/js/base64.js @@ -0,0 +1,149 @@ +//base64.js +function base64_encode(str) { + var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var out, i, len; + var c1, c2, c3; + + len = str.length; + i = 0; + out = ""; + while(i < len) { + c1 = str.charCodeAt(i++) & 0xff; + if(i == len) + { + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt((c1 & 0x3) << 4); + out += "=="; + break; + } + c2 = str.charCodeAt(i++); + if(i == len) + { + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); + out += base64EncodeChars.charAt((c2 & 0xF) << 2); + out += "="; + break; + } + c3 = str.charCodeAt(i++); + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); + out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)); + out += base64EncodeChars.charAt(c3 & 0x3F); + } + return out; +} + +function base64_decode(str) { + var base64DecodeChars = new Array( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1); + var c1, c2, c3, c4; + var i, len, out; + + len = str.length; + i = 0; + out = ""; + while(i < len) { + /* c1 */ + do { + c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; + } while(i < len && c1 == -1); + if(c1 == -1) + break; + + /* c2 */ + do { + c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; + } while(i < len && c2 == -1); + if(c2 == -1) + break; + + out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); + + /* c3 */ + do { + c3 = str.charCodeAt(i++) & 0xff; + if(c3 == 61) + return out; + c3 = base64DecodeChars[c3]; + } while(i < len && c3 == -1); + if(c3 == -1) + break; + + out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); + + /* c4 */ + do { + c4 = str.charCodeAt(i++) & 0xff; + if(c4 == 61) + return out; + c4 = base64DecodeChars[c4]; + } while(i < len && c4 == -1); + if(c4 == -1) + break; + out += String.fromCharCode(((c3 & 0x03) << 6) | c4); + } + return out; +} + +function utf16to8(str) { + var out, i, len, c; + + out = ""; + len = str.length; + for(i = 0; i < len; i++) { + c = str.charCodeAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) { + out += str.charAt(i); + } else if (c > 0x07FF) { + out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); + out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); + out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); + } else { + out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); + out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); + } + } + return out; +} + +function utf8to16(str) { + var out, i, len, c; + var char2, char3; + + out = ""; + len = str.length; + i = 0; + while(i < len) { + c = str.charCodeAt(i++); + switch(c >> 4) + { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + // 0xxxxxxx + out += str.charAt(i-1); + break; + case 12: case 13: + // 110x xxxx 10xx xxxx + char2 = str.charCodeAt(i++); + out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); + break; + case 14: + // 1110 xxxx 10xx xxxx 10xx xxxx + char2 = str.charCodeAt(i++); + char3 = str.charCodeAt(i++); + out += String.fromCharCode(((c & 0x0F) << 12) | + ((char2 & 0x3F) << 6) | + ((char3 & 0x3F) << 0)); + break; + } + } + + return out; +} \ No newline at end of file diff --git a/plugins/rsyncd/js/rsyncd.js b/plugins/rsyncd/js/rsyncd.js new file mode 100755 index 000000000..f29c7af07 --- /dev/null +++ b/plugins/rsyncd/js/rsyncd.js @@ -0,0 +1,741 @@ +function rsPost(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'rsyncd', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +///////////////// ----------------- 发送配置 ---------------- ////////////// + +function createSendTask(name = ''){ + var args = {}; + args["name"] = name; + rsPost('lsyncd_get', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var data = rdata.data; + console.log(data); + + var layerName = '创建'; + if (name!=''){ + layerName = '编辑'; + } + + var compress_true = ""; + var compress_false = ""; + if (data['rsync']['compress'] == 'true'){ + compress_true = "selected"; + compress_false = ""; + } else { + compress_true = ""; + compress_false = "selected"; + } + + + var delete_true = ""; + var delete_false = ""; + if (data['delete'] == 'false'){ + delete_true = "selected"; + delete_false = ""; + } else { + delete_true = ""; + delete_false = "selected"; + } + + + var realtime_true = ""; + var realtime_false = ""; + if (data['realtime'] == 'true'){ + realtime_true = "selected"; + realtime_false = ""; + } else { + realtime_true = ""; + realtime_false = "selected"; + } + + + var period_day = ""; + var period_minute_n = ""; + if (data['period'] == 'day'){ + period_day = "selected"; + period_minute_n = ""; + } else { + period_day = ""; + period_minute_n = "selected"; + } + + var bwlimit = "1024"; + if ('rsync' in data){ + bwlimit = data['rsync']['bwlimit']; + } + + var delay = "3"; + if ('delay' in data){ + delay = data['delay']; + } + + var layerID = layer.open({ + type: 1, + area: ['600px','500px'], + title: layerName+"发送任务", + closeBtn: 1, + shift: 0, + shadeClose: false, + btn: ['提交','取消'], + content:"
                                \ +
                                \ + 服务器IP\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 同步目录\ +
                                \ + \ + ?\ +
                                \ +
                                \ +
                                \ + 同步方式\ +
                                \ + \ + ?\ + 同步周期\ + \ +
                                \ +
                                \ + \ +
                                \ + 限速\ +
                                \ + KB\ + ?\ + 延迟 秒\ + ?\ +
                                \ +
                                \ +
                                \ + 连接方式\ +
                                \ + \ + 压缩传输\ + \ + ?\ +
                                \ +
                                \ +
                                \ + 接收密钥\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 用户名\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 密码\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 端口\ +
                                \ + \ +
                                \ +
                                \ +
                                  \ +
                                \ +
                                ", + success:function(){ + $('[data-toggle="tooltip"]').tooltip(); + + $(".conn-user").hide(); + $("select[name='conn_type']").change(function(){ + if($(this).val() == 'key'){ + $(".conn-user").hide(); + $(".conn-key").show(); + }else{ + $(".conn-user").show(); + $(".conn-key").hide(); + } + }); + + + var selVal = $('.synchronization option:selected').val(); + if (selVal == "false"){ + $('#period').show(); + }else{ + $('#period').hide(); + $('.hour input,.minute input').val('0'); + $('.minute-n input').val('1'); + } + $('.synchronization').change(function(event) { + var selVal = $('.synchronization option:selected').val(); + if (selVal == "false"){ + $('#period').show(); + }else{ + $('#period').hide(); + $('.hour input,.minute input').val('0'); + $('.minute-n input').val('1'); + } + }); + + $("select[name='delete']").change(function(){ + if($(this).val() == 'true'){ + var mpath = $('input[name="path"]').val(); + var msg = '
                                警告:您选择了完全同步,将会使本机同步与目标机器指定目录的文件保持一致,' + +'
                                请确认目录设置是否有误,一但设置错误,可能导致目标机器的目录文件被删除!
                                ' + +'

                                注意: 同步程序将本机目录:' + +mpath+'的所有数据同步到目标服务器,若目标服务器的同步目录存在其它文件将被删除!

                                已了解风险,请按确定继续
                                '; + + layer.confirm(msg,{title:'数据安全风险警告',icon:2,closeBtn: 1,shift: 5, + btn2:function(){ + setTimeout(function(){$($("select[name='delete']").children("option")[0]).prop('selected',true);},100); + } + }); + } + }); + + + var selVal = $('#period select option:selected').val(); + if (selVal == 'day'){ + $('.hour,.minute').show(); + if ($('.hour input').val() == ''){ + $('.hour input,.minute input').val('0'); + } + $('.minute-n').hide(); + }else{ + $('.hour,.minute').hide(); + $('.minute-n').show(); + if ($('.minute-n input').val() == ''){ + $('.minute-n input').val('1'); + } + } + $('#period').change(function(event) { + var selVal = $('#period select option:selected').val(); + if (selVal == 'day'){ + $('.hour,.minute').show(); + if ($('.hour input').val() == ''){ + $('.hour input,.minute input').val('0'); + } + $('.minute-n').hide(); + }else{ + $('.hour,.minute').hide(); + $('.minute-n').show(); + if ($('.minute-n input').val() == ''){ + $('.minute-n input').val('1'); + } + } + }); + }, + yes:function(index){ + var args = {}; + var conn_type = $("select[name='conn_type']").val(); + + if(conn_type == 'key'){ + if ( $('textarea[name="secret_key"]').val() != ''){ + args['secret_key'] = $('textarea[name="secret_key"]').val(); + } else { + layer.msg('请输入接收密钥!'); + return false; + } + } else { + args['sname'] = $("input[name='u_user']").val(); + args['password'] = $("input[name='u_pass']").val(); + var port = Number($("input[name='u_port']").val()); + args['port'] = port; + if (!args['sname'] || !args['password'] || !args['port']){ + layer.msg('请输入帐号、密码、端口信息'); + return false; + } + } + + if ($('input[name="ip"]').val() == ''){ + layer.msg('请输入服务器IP地址!'); + return false; + } + + args['sname'] = $("input[name='u_user']").val(); + args['password'] = $("input[name='u_pass']").val(); + var port = Number($("input[name='u_port']").val()); + args['port'] = port; + + + args['ip'] = $('input[name="ip"]').val(); + args['path'] = $('input[name="path"]').val(); + args['delete'] = $('select[name="delete"]').val(); + args['realtime'] = $('select[name="realtime"]').val(); + args['delay'] = $('input[name="delay"]').val(); + + args['bwlimit'] = $('input[name="bwlimit"]').val(); + args['conn_type'] = $('select[name="conn_type"]').val(); + args['compress'] = $('select[name="compress"]').val(); + + args['period'] = $('select[name="period"]').val(); + args['hour'] = $('input[name="hour"]').val(); + args['minute'] = $('input[name="minute"]').val(); + args['minute-n'] = $('input[name="minute-n"]').val(); + + rsPost('lsyncd_add', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2,time:2000,shade: [0.3, '#000']}); + + if (rdata.status){ + setTimeout(function(){ + layer.close(index); + lsyncdSend(); + },2000); + return; + } + }); + return true; + } + }); + }); +} + +function lsyncdDelete(name){ + safeMessage('删除['+name+']', '您真的要删除['+name+']吗?', function(){ + var args = {}; + args['name'] = name; + rsPost('lsyncd_delete', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2,time:2000,shade: [0.3, '#000']}); + setTimeout(function(){lsyncdSend();},2000); + }); + }); +} + + +function lsyncdRun(name){ + var args = {}; + args["name"] = name; + rsPost('lsyncd_run', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2,time:2000,shade: [0.3, '#000']}); + }); +} + +function lsyncdLog(name){ + var args = {}; + args["name"] = name; + pluginStandAloneLogs("rsyncd", '', "lsyncd_log", JSON.stringify(args)); +} + + +function lsyncdExclude(name){ + layer.open({ + type:1, + title:'过滤器', + area: '400px', + shadeClose:false, + closeBtn:2, + content:'
                                \ +
                                \ +
                                \ + 排除的文件和目录\ + \ + \ +
                                \ +
                                \ +
                                \ +
                                \ +
                                \ +
                                \ +
                                  \ +
                                • 排除的文件和目录是指当前目录下不需要同步的目录或者文件
                                • \ +
                                • 如果规则以斜线 /开头,则从头开始要匹配全部
                                • \ +
                                • 如果规则以 /结尾,则要匹配监控路径的末尾
                                • \ +
                                • ? 匹配任何字符,但不包括/
                                • \ +
                                • * 匹配0或多个字符,但不包括/
                                • \ +
                                • ** 匹配0或多个字符,可以是/
                                • \ +
                                \ +
                                \ +
                                ' + }); + + function getIncludeExclude(mName){ + loadT = layer.msg('正在获取数据...',{icon:16,time:0,shade: [0.3, '#000']}); + rsPost('lsyncd_get_exclude',{"name":mName}, function(rdata) { + layer.close(loadT); + + var rdata = $.parseJSON(rdata.data); + var res = rdata.data; + + var list='' + for (var i = 0; i < res.length; i++) { + list += ''+ res[i] +'删除'; + } + $('.lsyncd_exclude .BlockList tbody').empty().append(list); + }); + } + getIncludeExclude(name); + + + function addArgs(name,exclude){ + loadT = layer.msg('正在添加...',{icon:16,time:0,shade: [0.3, '#000']}); + rsPost('lsyncd_add_exclude', {name:name,exclude:exclude}, function(res){ + layer.close(loadT); + + console.log('addArgs:',res); + + if (res.status){ + getIncludeExclude(name); + $('.lsyncd_exclude input').val(''); + layer.msg(res.msg); + }else{ + layer.msg(res.msg); + } + }); + } + $('.addList').click(function(event) { + var val = $(this).prev().val(); + if(val == ''){ + layer.msg('当前输入内容为空,请输入'); + return false; + } + addArgs(name,val); + }); + $('.lsyncd_exclude input').keyup(function(event){ + if (event.which == 13){ + var val = $(this).val(); + if(val == ''){ + layer.msg('当前输入内容为空,请输入'); + return false; + } + addArgs(name,val); + } + }); + + + $('.lsyncd_exclude').on('click', '.delList', function(event) { + loadT = layer.msg('正在删除...',{icon:16,time:0,shade: [0.3, '#000']}); + var val = $(this).parent().prev().text(); + rsPost('lsyncd_remove_exclude',{"name":name,exclude:val}, function(rdata) { + layer.close(loadT); + + console.log(rdata) + var rdata = $.parseJSON(rdata.data); + var res = rdata.data; + + var list='' + for (var i = 0; i < res.length; i++) { + list += ''+ res[i] +'删除'; + } + $('.lsyncd_exclude .BlockList tbody').empty().append(list); + }); + }); +} + +function lsyncdConfLog(){ + pluginRollingLogs("rsyncd","","lsyncd_conf_log");; +} + +function lsyncdSend(){ + rsPost('lsyncd_list', '', function(data){ + var rdata = $.parseJSON(data.data); + console.log(rdata); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:rdata.status?1:2,time:2000,shade: [0.3, '#000']}); + return; + } + var list = rdata.data.list; + var con = ''; + + con += '
                                \ + \ + \ +
                                '; + + con += '
                                '; + con += ''; + con += ''; + con += ''; + con += ''; + con += ''; + con += ''; + con += ''; + con += ''; + + con += ''; + + + + for (var i = 0; i < list.length; i++) { + var mode = '增量'; + if (list[i]['delete'] == 'true'){ + mode = '完全'; + } else { + mode = '增量'; + } + + var period = "实时"; + if (list[i]['realtime'] == 'true'){ + period = '实时'; + } else { + period = '定时'; + } + + con += ''+ + '' + + '' + + '' + + '' + + '' + + '\ + '; + } + + con += ''; + con += '
                                名称(标识)源目录同步到模式周期操作
                                ' + list[i]['name']+'' + list[i]['path']+'' + list[i]['ip']+":"+list[i]['name']+'' + mode+'' + period +'\ + 同步\ + | 手动日志\ + | 过滤器\ + | 编辑\ + | 删除\ +
                                '; + + $(".soft-man-con").html(con); + }); +} + + + +///////////////// ----------------- 接收配置 ---------------- ////////////// +function rsyncdConf(){ + rsPost('conf', {}, function(rdata){ + rpath = rdata['data']; + if (rdata['status']){ + onlineEditFile(0, rpath); + } else { + layer.msg(rdata.msg,{icon:1,time:2000,shade: [0.3, '#000']}); + } + }); +} + +function rsyncdLog(){ + pluginRollingLogs("rsyncd","","run_log"); +} + + +function rsyncdReceive(){ + rsPost('rec_list', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:rdata.status?1:2,time:2000,shade: [0.3, '#000']}); + return; + } + // console.log(rdata); + var list = rdata.data; + var con = ''; + + con += '
                                \ + \ + \ +
                                '; + + con += '
                                '; + con += ''; + con += ''; + con += ''; + con += ''; + con += ''; + con += ''; + + con += ''; + + //编辑 + for (var i = 0; i < list.length; i++) { + con += ''+ + '' + + '' + + '' + + '\ + '; + } + + con += ''; + con += '
                                服务名路径备注操作(添加)
                                ' + list[i]['name']+'' + list[i]['path']+'' + list[i]['comment']+'\ + 命令\ + | 密钥\ + | 编辑\ + | 删除
                                '; + + $(".soft-man-con").html(con); + }); +} + + +function addReceive(name = ""){ + rsPost('get_rec',{"name":name},function(rdata) { + var rdata = $.parseJSON(rdata.data); + var data = rdata.data; + + var readonly = ""; + if (name !=""){ + readonly = 'readonly="readonly"' + } + + var loadOpen = layer.open({ + type: 1, + title: '创建接收', + area: '400px', + btn:['确认','取消'], + content:"
                                \ +
                                \ + 项目名\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + 密钥\ +
                                \ + \ + \ +
                                \ +
                                \ +
                                \ + 同步到\ +
                                \ + \ + \ +
                                \ +
                                \ +
                                \ + 备注\ +
                                \ + \ +
                                \ +
                                \ +
                                ", + success:function(layero, index){}, + yes:function(){ + var args = {}; + args['name'] = $('#name').val(); + args['pwd'] = $('#MyPassword').val(); + args['path'] = $('#inputPath').val(); + args['ps'] = $('#ps').val(); + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + rsPost('add_rec', args, function(data){ + var rdata = $.parseJSON(data.data); + if (rdata['status']){ + layer.close(loadOpen); + layer.msg(rdata.msg,{icon:rdata.status?1:2,time:2000,shade: [0.3, '#000']}); + setTimeout(function(){rsyncdReceive();},2000); + } else { + layer.msg(rdata.msg,{icon:rdata.status?1:2,time:10000,shade: [0.3, '#000']}); + } + }); + } + }); + }) +} + + +function delReceive(name){ + safeMessage('删除['+name+']', '您真的要删除['+name+']吗?', function(){ + var _data = {}; + _data['name'] = name; + rsPost('del_rec', _data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2,time:2000,shade: [0.3, '#000']}); + setTimeout(function(){rsyncdReceive();},2000); + }); + }); +} + +function cmdRecSecretKey(name){ + var _data = {}; + _data['name'] = name; + rsPost('cmd_rec_secret_key', _data, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + type: 1, + title: '接收密钥', + area: '400px', + content:"
                                " + }); + }); +} + +function cmdRecCmd(name){ + var _data = {}; + _data['name'] = name; + rsPost('cmd_rec_cmd', _data, function(data){ + var rdata = $.parseJSON(data.data); + layer.open({ + type: 1, + title: '接收命令例子', + area: '400px', + content:"
                                "+rdata.data+"
                                " + }); + }); +} + + +function rsRead(){ + var readme = '
                                  '; + readme += '
                                • 如需将其他服务器数据同步到本地服务器,请在接受配置中 "创建接收任务"
                                • '; + readme += '
                                • 如果开启防火墙,需要放行873端口
                                • '; + readme += '
                                '; + + $('.soft-man-con').html(readme); +} \ No newline at end of file diff --git a/plugins/rsyncd/tool_task.py b/plugins/rsyncd/tool_task.py new file mode 100644 index 000000000..e38a29626 --- /dev/null +++ b/plugins/rsyncd/tool_task.py @@ -0,0 +1,149 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'rsyncd' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getTaskConf(): + conf = getServerDir() + "/task_config.json" + return conf + + +def getConfigData(): + conf = getTaskConf() + if os.path.exists(conf): + return json.loads(mw.readFile(conf)) + return [] + + +def getConfigTpl(): + tpl = { + "name": "", + "task_id": -1, + } + return tpl + + +def createBgTask(data): + removeBgTask() + for d in data: + if d['realtime'] == "false": + createBgTaskByName(d['name'], d) + + +def createBgTaskByName(name, args): + cfg = getConfigTpl() + _name = "[勿删]同步插件定时任务[" + name + "]" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in cfg.keys() and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where("id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + print("计划任务已经存在!") + return True + + period = args['period'] + _hour = '' + _minute = '' + _where1 = '' + _type_day = "day" + if period == 'day': + _type_day = 'day' + _hour = args['hour'] + _minute = args['minute'] + elif period == 'minute-n': + _type_day = 'minute-n' + _where1 = args['minute-n'] + _minute = '' + + cmd = ''' +rname=%s +plugin_path=%s +logs_file=$plugin_path/send/${rname}/run.log +''' % (name, getServerDir()) + cmd += 'echo "★【`date +"%Y-%m-%d %H:%M:%S"`】 STSRT" >> $logs_file' + "\n" + cmd += 'echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" >> $logs_file' + "\n" + cmd += 'bash $plugin_path/send/${rname}/cmd >> $logs_file 2>&1' + "\n" + cmd += 'echo "【`date +"%Y-%m-%d %H:%M:%S"`】 END★" >> $logs_file' + "\n" + cmd += 'echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" >> $logs_file' + "\n" + + params = { + 'name': _name, + 'type': _type_day, + 'week': "", + 'where1': _where1, + 'hour': _hour, + 'minute': _minute, + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + cfg["task_id"] = task_id + cfg["name"] = name + + _dd = getConfigData() + _dd.append(cfg) + mw.writeFile(getTaskConf(), json.dumps(_dd)) + + +def removeBgTask(): + cfg_list = getConfigData() + for x in range(len(cfg_list)): + cfg = cfg_list[x] + if "task_id" in cfg.keys() and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where("id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data[0]: + cfg["task_id"] = -1 + cfg_list[x] = cfg + mw.writeFile(getTaskConf(), '[]') + return True + return False + + +if __name__ == "__main__": + if len(sys.argv) > 1: + action = sys.argv[1] + if action == "remove": + removeBgTask() + elif action == "add": + createBgTask() diff --git a/plugins/simpleping/ico.png b/plugins/simpleping/ico.png new file mode 100644 index 000000000..348efe3f4 Binary files /dev/null and b/plugins/simpleping/ico.png differ diff --git a/plugins/simpleping/index.html b/plugins/simpleping/index.html new file mode 100755 index 000000000..83d25d8f9 --- /dev/null +++ b/plugins/simpleping/index.html @@ -0,0 +1,25 @@ +
                                +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                配置修改

                                +

                                连通性

                                +

                                MySQL

                                +

                                相关说明

                                + +
                                +
                                +
                                +
                                +
                                +
                                + + \ No newline at end of file diff --git a/plugins/simpleping/index.py b/plugins/simpleping/index.py new file mode 100755 index 000000000..6fc8e92fa --- /dev/null +++ b/plugins/simpleping/index.py @@ -0,0 +1,305 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getHomeDir(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + else: + return '/root' + + +def getRunUser(): + if mw.isAppleSystem(): + user = mw.execShell( + "who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return user + else: + return 'root' + +__SR = '''#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export USER=%s +export HOME=%s && ''' % ( getRunUser(), getHomeDir()) + +def getPluginName(): + return 'simpleping' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/conf/app.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + data = mw.execShell( + "ps aux|grep simpleping |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + + if data[0] == '': + return 'stop' + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/simpleping') + return content + + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # print(file_bin) + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + # systemctl start simpleping.service + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def appOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(__SR + file + ' ' + method) + # print(data) + return 'ok' + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return appOp('start') + + +def stop(): + return appOp('stop') + + +def restart(): + status = appOp('restart') + return status + + +def reload(): + return redisOp('reload') + + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + return getServerDir() + '/data/log.pl' + + +def ipList(): + config = getServerDir() + '/conf/app.conf' + content = mw.readFile(config) + rep = r'ip\s*=\s*(.*)' + tmp = re.search(rep, content) + if not tmp: + return '' + ip = tmp.groups()[0] + ips = ip.split(",") + return mw.returnJson(True, 'OK', ips) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(getConf()) + elif func == 'ip_list': + print(ipList()) + else: + print('error') diff --git a/plugins/simpleping/info.json b/plugins/simpleping/info.json new file mode 100755 index 000000000..ae3d31e55 --- /dev/null +++ b/plugins/simpleping/info.json @@ -0,0 +1,17 @@ +{ + "sort": 999, + "ps": "简单Ping服务,检查连通性", + "name": "simpleping", + "title": "SimplePing", + "shell": "install.sh", + "versions":["1.0"], + "tip": "soft", + "checks": "server/simpleping", + "path": "server/simpleping", + "display": 1, + "author": "midoks", + "date": "2024-07-10", + "home": "https://github.com/midoks/simpleping", + "type": 0, + "pid": "5" +} diff --git a/plugins/simpleping/init.d/simpleping.service.tpl b/plugins/simpleping/init.d/simpleping.service.tpl new file mode 100644 index 000000000..f7d3d3411 --- /dev/null +++ b/plugins/simpleping/init.d/simpleping.service.tpl @@ -0,0 +1,21 @@ +[Unit] +Description=SimplePing Server +After=network.service +After=syslog.target + +[Service] +User=root +Group=root +Type=simple +WorkingDirectory={$SERVER_PATH}/simpleping +ExecStart={$SERVER_PATH}/simpleping/simpleping service +ExecReload=/bin/kill -USR2 $MAINPID +PermissionsStartOnly=true +LimitNOFILE=5000 +Restart=on-failure +RestartSec=10 +RestartPreventExitStatus=1 +PrivateTmp=false + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/simpleping/init.d/simpleping.tpl b/plugins/simpleping/init.d/simpleping.tpl new file mode 100644 index 000000000..682ae56bc --- /dev/null +++ b/plugins/simpleping/init.d/simpleping.tpl @@ -0,0 +1,46 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: SimplePing Service + +### BEGIN INIT INFO +# Provides: SimplePing +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts SimplePing +# Description: starts the MDW-Web +### END INIT INFO + +# Simple SimplePing init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +app_start(){ + echo "Starting SimplePing server..." + cd {$SERVER_PATH}/simpleping + ./simpleping service >> {$SERVER_PATH}/simpleping/logs.pl 2>&1 & +} + +app_stop(){ + echo "SimplePing stopped" + ps -ef| grep simpleping | grep -v grep | grep -v python | grep -v sh | awk '{print $2}'| xargs kill +} + + +case "$1" in + start) + app_start + ;; + stop) + app_stop + ;; + restart|reload) + app_stop + sleep 0.3 + app_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/simpleping/install.sh b/plugins/simpleping/install.sh new file mode 100755 index 000000000..7a39c14de --- /dev/null +++ b/plugins/simpleping/install.sh @@ -0,0 +1,85 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +ARCH=`uname -m` +sysName=`uname` +VERSION=$2 + +# 开启可以PING +# sysctl -w net.ipv4.ping_group_range="0 2147483647" + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/source/simpleping + + name=linux + if [ "$sysName" == "Darwin" ];then + name="darwin" + else + sysctl -w net.ipv4.ping_group_range="0 2147483647" + fi + + if [ "$ARCH" == "x86_64" ];then + ARCH="amd64" + fi + + FILE_TGZ=simpleping_${name}_${ARCH}.tar.gz + APP_DIR=$serverPath/source/simpleping + + # https://github.com/midoks/simpleping/releases/download/1.0/simpleping_linux_amd64.tar.gz + if [ ! -f $APP_DIR/${FILE_TGZ} ];then + wget -O $APP_DIR/${FILE_TGZ} https://github.com/midoks/simpleping/releases/download/2.0/${FILE_TGZ} + fi + + mkdir -p $serverPath/simpleping + cd $APP_DIR && tar -zxvf ${FILE_TGZ} -C $serverPath/simpleping + echo "${VERSION}" > $serverPath/simpleping/version.pl + + cd ${rootPath} && python3 ${rootPath}/plugins/simpleping/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/simpleping/index.py initd_install + + echo '安装SimplePing成功!' + +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/simpleping.service ];then + systemctl stop simpleping + systemctl disable simpleping + rm -rf /usr/lib/systemd/system/simpleping.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/simpleping.service ];then + systemctl stop simpleping + systemctl disable simpleping + rm -rf /lib/systemd/system/simpleping.service + systemctl daemon-reload + fi + + if [ -f $serverPath/simpleping/initd/simpleping ];then + $serverPath/simpleping/initd/simpleping stop + fi + + if [ -d $serverPath/simpleping ];then + rm -rf $serverPath/simpleping + fi + + echo "卸载SimplePing成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/simpleping/js/simpleping.js b/plugins/simpleping/js/simpleping.js new file mode 100755 index 000000000..9c89ec66a --- /dev/null +++ b/plugins/simpleping/js/simpleping.js @@ -0,0 +1,531 @@ + +function pingPost(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'simpleping'; + req_data['func'] = method; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function pingPostCallbak(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'simpleping'; + req_data['script'] = 'simpleping_index'; + req_data['func'] = method; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function pingPostCallbakN(method, args, callback){ + var req_data = {}; + req_data['name'] = 'simpleping'; + req_data['script'] = 'simpleping_index'; + req_data['func'] = method; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function appReadme(){ + + var readme = '
                                  '; + readme += '
                                • 简单说明
                                • '; + readme += '
                                • 主要是检查内网连通性
                                • '; + readme += '
                                '; + $('.soft-man-con').html(readme); +} + +var chartPing; +var chartPingData = []; +var posTimer; + +// 把Unix时间戳转化成普通日期 +function toCommonTime(unix_time){ + var unixTimestamp = new Date(unix_time*1000); + var commonTime = unixTimestamp.toLocaleString(); + return commonTime; +} + +function getToday(){ + var mydate = new Date(); + var str = "" + mydate.getFullYear() + "/"; + str += (mydate.getMonth()+1) + "/"; + str += mydate.getDate(); + return str; +} + +//定义周期时间 +function getBeforeDate(n){ + var n = n; + var d = new Date(); + var year = d.getFullYear(); + var mon=d.getMonth()+1; + var day=d.getDate(); + if(day <= n){ + if(mon>1) { + mon = mon-1; + } else { + year = year-1; + mon = 12; + } + } + d.setDate(d.getDate()-n); + year = d.getFullYear(); + mon=d.getMonth()+1; + day=d.getDate(); + s = year+"/"+(mon<10?('0'+mon):mon)+"/"+(day<10?('0'+day):day); + return s; +} + + +function pingDataGraphPosData(){ + + var isOk = document.getElementById('pingview'); + if (!isOk){ + clearInterval(posTimer); + } + + if (chartPingData.length>0){ + var dlen = chartPingData.length; + last_pos = chartPingData[dlen-1]; + // console.log(start,end); + var cur_ip = $('select[name="ip_list"]').val(); + pingPostCallbakN('pingData', {'type':'pos', 'pos':last_pos['created_unix'],"ip":cur_ip}, function(data){ + var tmp_data = data.data; + for (x in tmp_data){ + chartPingData.push(tmp_data[x]); + } + pingDataGraphRender(); + }); + + } +} + + +function pingDataGraphData(day){ + + var now = (new Date().getTime())/1000; + if(day==0){ + var start = (new Date(getToday() + " 00:00:01").getTime())/1000; + start = Math.round(start); + var end = Math.round(now); + } + if(day==1){ + var start = (new Date(getBeforeDate(day) + " 00:00:01").getTime())/1000; + var end = (new Date(getBeforeDate(day) + " 23:59:59").getTime())/1000; + start = Math.round(start); + end = Math.round(end); + } else { + var start = (new Date(getBeforeDate(day) + " 00:00:01").getTime())/1000; + start = Math.round(start); + var end = Math.round(now); + } + + var cur_ip = $('select[name="ip_list"]').val(); + // console.log(start,end); + pingPostCallbak('pingData', {'type':'range', 'start':start, 'end':end, 'ip':cur_ip}, function(data){ + chartPingData = data.data; + pingDataGraphRender(); + + if (day!=1){ + clearInterval(posTimer); + posTimer = setInterval(function() { + pingDataGraphPosData(); + }, 3000); + } + }); +} + +function pingDataGraphRender(){ + var xData = []; + var yData = []; + var rdata = chartPingData; + for(var i = 0; i < rdata.length; i++){ + xData.push(toCommonTime(rdata[i].created_unix)); + yData.push(rdata[i].speed/1000000); + } + var option = { + tooltip: { + trigger: 'axis', + axisPointer: { type: 'cross' }, + formatter: '{b}
                                {a}: {c}' + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xData, + axisLine:{ lineStyle:{ color:"#666"} } + }, + yAxis: { + type: 'value', + name: "PING延迟(ms)", + // boundaryGap: [0, '100%'], + // min:0, + // max: 100, + splitLine:{ lineStyle:{ color:"#ddd" } }, + axisLine:{ lineStyle:{ color:"#666" } } + }, + series: [ + { + name:'PING', + type:'line', + smooth:true, + symbol: 'none', + sampling: 'average', + itemStyle: { normal: { color: 'rgb(0, 153, 238)' } }, + data: yData + } + ] + }; + chartPing.setOption(option); +} + +function pingIpList(){ + pingPost('ip_list', {}, function(data){ + var rdata = $.parseJSON(data.data); + var ips = rdata.data; + + var option = ''; + option += ''; + + for (var i = 0; i < ips.length; i++) { + option += ''; + } + $('select[name="ip_list"]').html(option); + + + $('select[name="ip_list"]').change(function(){ + chartPingData = []; + pingDataGraphData(0); + }); + + pingDataGraphData(0); + }); + +} + +// console.log('pingDataGraph'); +function pingDataGraph(){ + var tpl = '\ +
                                \ +
                                \ +
                                \ +
                                \ +

                                连通性

                                \ +
                                \ + \ + 昨天\ + 今天\ + 最近7天\ +
                                \ +
                                \ +
                                \ +
                                \ +
                                \ +
                                '; + $('.soft-man-con').html(tpl); + + $('.searcTime span').click(function(e){ + $('.searcTime span').removeClass('on'); + $(this).addClass('on'); + }); + + chartPing = echarts.init(document.getElementById('pingview')); + + var option = { + tooltip: { + trigger: 'axis', + axisPointer: { type: 'cross' }, + formatter: '{b}
                                {a}: {c}' + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: [], + axisLine:{ lineStyle:{ color:"#666"} } + }, + yAxis: { + type: 'value', + name: "PING延迟(ms)", + splitLine:{ lineStyle:{ color:"#ddd" } }, + axisLine:{ lineStyle:{ color:"#666" } } + }, + dataZoom: [{ + type: 'inside', + start: 0, + end: 100, + zoomLock:true + }, { + start: 0, + end: 100, + handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z', + handleSize: '80%', + handleStyle: { + color: '#fff', + shadowBlur: 3, + shadowColor: 'rgba(0, 0, 0, 0.6)', + shadowOffsetX: 2, + shadowOffsetY: 2 + } + }], + series: [ + { + name:'PING', + type:'line', + smooth:true, + symbol: 'none', + sampling: 'average', + itemStyle: { normal: { color: 'rgb(0, 153, 238)' } }, + data: [] + } + ] + }; + chartPing.setOption(option); + window.addEventListener("resize",function(){ + chartPing.resize(); + }); + + pingIpList(); +} + + + + + +// -------------------------------------------------------------------------------------------------------------- +// MYSQL PING +// -------------------------------------------------------------------------------------------------------------- + +var chartMySQLPingData = []; +var chartMySQLPing; +var posMySQLTimer; + +function pingMySQLDataGraphPosData(){ + + var isOk = document.getElementById('mysqlview'); + if (!isOk){ + clearInterval(posMySQLTimer); + } + + if (chartMySQLPingData.length>0){ + var dlen = chartMySQLPingData.length; + last_pos = chartMySQLPingData[dlen-1]; + // console.log(start,end); + pingPostCallbakN('pingMySQLData', {'type':'pos', 'pos':last_pos['created_unix']}, function(data){ + var tmp_data = data.data; + for (x in tmp_data){ + chartMySQLPingData.push(tmp_data[x]); + } + pingDataMySQLGraphRender(); + }); + + } +} + +function pingDataMySQLGraphRender(){ + var xData = []; + var yData = []; + var rdata = chartMySQLPingData; + for(var i = 0; i < rdata.length; i++){ + xData.push(toCommonTime(rdata[i].created_unix)); + yData.push(rdata[i].value); + } + var option = { + tooltip: { + trigger: 'axis', + axisPointer: { type: 'cross' }, + formatter: '{b}
                                {a}: {c}' + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xData, + axisLine:{ lineStyle:{ color:"#666"} } + }, + yAxis: { + type: 'value', + name: "MySQL同步延迟", + splitLine:{ lineStyle:{ color:"#ddd" } }, + axisLine:{ lineStyle:{ color:"#666" } } + }, + series: [ + { + name:'同步延迟', + type:'line', + smooth:true, + symbol: 'none', + sampling: 'average', + itemStyle: { normal: { color: 'rgb(0, 153, 238)' } }, + data: yData + } + ] + }; + chartMySQLPing.setOption(option); +} + +function pingMySQLDataGraphData(day){ + + var now = (new Date().getTime())/1000; + if(day==0){ + var start = (new Date(getToday() + " 00:00:01").getTime())/1000; + start = Math.round(start); + var end = Math.round(now); + } + if(day==1){ + var start = (new Date(getBeforeDate(day) + " 00:00:01").getTime())/1000; + var end = (new Date(getBeforeDate(day) + " 23:59:59").getTime())/1000; + start = Math.round(start); + end = Math.round(end); + } else { + var start = (new Date(getBeforeDate(day) + " 00:00:01").getTime())/1000; + start = Math.round(start); + var end = Math.round(now); + } + + var cur_ip = $('select[name="ip_list"]').val(); + // console.log(start,end); + pingPostCallbak('pingMySQLData', {'type':'range', 'start':start, 'end':end, 'ip':cur_ip}, function(data){ + chartMySQLPingData = data.data; + pingDataMySQLGraphRender(); + if (day!=1){ + clearInterval(posMySQLTimer); + posMySQLTimer = setInterval(function() { + pingMySQLDataGraphPosData(); + }, 3000); + } + }); +} + + +function pingMySQLDataGraph(){ + var tpl = '\ +
                                \ +
                                \ +
                                \ +
                                \ +

                                MySQL检查

                                \ +
                                \ + 昨天\ + 今天\ + 最近7天\ +
                                \ +
                                \ +
                                \ +
                                \ +
                                \ +
                                '; + $('.soft-man-con').html(tpl); + + $('.searcTime span').click(function(e){ + $('.searcTime span').removeClass('on'); + $(this).addClass('on'); + }); + + chartMySQLPing = echarts.init(document.getElementById('mysqlview')); + + var option = { + tooltip: { + trigger: 'axis', + axisPointer: { type: 'cross' }, + formatter: '{b}
                                {a}: {c}' + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: [], + axisLine:{ lineStyle:{ color:"#666"} } + }, + yAxis: { + type: 'value', + name: "MySQL延迟", + splitLine:{ lineStyle:{ color:"#ddd" } }, + axisLine:{ lineStyle:{ color:"#666" } } + }, + dataZoom: [{ + type: 'inside', + start: 0, + end: 100, + zoomLock:true + }, { + start: 0, + end: 100, + handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z', + handleSize: '80%', + handleStyle: { + color: '#fff', + shadowBlur: 3, + shadowColor: 'rgba(0, 0, 0, 0.6)', + shadowOffsetX: 2, + shadowOffsetY: 2 + } + }], + series: [ + { + name:'延迟', + type:'line', + smooth:true, + symbol: 'none', + sampling: 'average', + itemStyle: { normal: { color: 'rgb(0, 153, 238)' } }, + data: [] + } + ] + }; + chartMySQLPing.setOption(option); + window.addEventListener("resize",function(){ + chartMySQLPing.resize(); + }); + + pingMySQLDataGraphData(0); +} + + diff --git a/plugins/simpleping/simpleping_index.py b/plugins/simpleping/simpleping_index.py new file mode 100755 index 000000000..4d6292d81 --- /dev/null +++ b/plugins/simpleping/simpleping_index.py @@ -0,0 +1,72 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import shutil + +sys.path.append(os.getcwd() + "/class/core") +import mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + +def getPluginName(): + return 'simpleping' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + +def pingData(args = ()): + # print(args) + conn = mw.M('sp_ping').dbPos(getServerDir()+'/data', 'simpleping', 'db3') + field = 'id,speed,created_unix' + conn = conn.field(field) + data = [] + atype = args['type'] + + if not 'ip' in args: + ip = 'all' + else: + ip = args['ip'] + + if atype == 'pos': + if 'pos' in args: + pos = args['pos'] + conn.where('created_unix>?',(pos,)).limit("3000") + if ip != 'all': + conn.andWhere('ip=?',(ip,)) + elif atype == 'range': + start = args['start'] + end = args['end'] + conn.where('created_unix>=? and created_unix<=?',(start,end,)).limit("1000") + if ip != 'all': + conn.andWhere('ip=?',(ip,)) + + data = conn.select() + return data + + +def pingMySQLData(args = ()): + conn = mw.M('sp_mysql_ping').dbPos(getServerDir()+'/data', 'simpleping', 'db3') + field = 'id,value,created_unix' + conn = conn.field(field) + data = [] + atype = args['type'] + if atype == 'pos': + pos = args['pos'] + data = conn.where('created_unix>?',(pos,)).limit("3000").select() + elif atype == 'range': + start = args['start'] + end = args['end'] + data = conn.where('created_unix>=? and created_unix<=?',(start,end)).limit("1000").select() + return data + diff --git a/plugins/sphinx/class/sphinx_make.py b/plugins/sphinx/class/sphinx_make.py new file mode 100644 index 000000000..239064fc5 --- /dev/null +++ b/plugins/sphinx/class/sphinx_make.py @@ -0,0 +1,490 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import subprocess +import re +import json + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +def getServerDir(): + return mw.getServerDir() + '/mysql' + +def getPluginDir(): + return mw.getPluginDir() + '/mysql' + +def getConf(): + path = getServerDir() + '/etc/my.cnf' + return path + +def getDbPort(): + file = getConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getSocketFile(): + file = getConf() + content = mw.readFile(file) + rep = r'socket\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def pSqliteDb(dbname='databases'): + file = getServerDir() + '/mysql.db' + name = 'mysql' + + conn = mw.M(dbname).dbPos(getServerDir(), name) + return conn + +def pMysqlDb(): + # pymysql + db = mw.getMyORM() + + db.setPort(getDbPort()) + db.setSocket(getSocketFile()) + # db.setCharset("utf8") + db.setPwd(pSqliteDb('config').where('id=?', (1,)).getField('mysql_root')) + return db + +class sphinxMake(): + + pdb = None + psdb = None + + pkey_name_cache = {} + delta = 'sph_counter' + ver = '' + + + def __init__(self): + self.pdb = pMysqlDb() + + def setDeltaName(self, name): + self.delta = name + return True + + def setVersion(self, ver): + self.ver = ver + + def createSql(self, db): + conf = ''' +CREATE TABLE IF NOT EXISTS `{$DB_NAME}`.`{$TABLE_NAME}` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `table` varchar(200) NOT NULL, + `max_id` bigint(20) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `table_uniq` (`table`), + KEY `table` (`table`) +) ENGINE=InnoDB AUTO_INCREMENT=1 CHARSET=utf8mb4; +''' + conf = conf.replace("{$TABLE_NAME}", self.delta) + conf = conf.replace("{$DB_NAME}", db) + return conf + + def eqVerField(self, field): + ver = self.ver.replace(".1",'') + if float(ver) >= 3.6: + if field == 'sql_attr_timestamp': + return 'attr_bigint' + + if field == 'sql_attr_bigint': + return 'attr_bigint' + + if field == 'sql_attr_float': + return 'attr_float' + + if field == 'sql_field_string': + return 'field_string' + + if float(ver) >= 3.3: + if field == 'sql_attr_timestamp': + return 'sql_attr_bigint' + + return field + + def pathVerName(self): + ver = self.ver.replace(".1",'') + # if float(ver) >= 3.6: + # return 'datadir' + return 'path' + + def getTablePk(self, db, table): + key = db+'_'+table + if key in self.pkey_name_cache: + return self.pkey_name_cache[key] + + # SHOW INDEX FROM bbs.bbs_ucenter_vars WHERE Key_name = 'PRIMARY' + pkey_sql = "SHOW INDEX FROM {}.{} WHERE Key_name = 'PRIMARY';".format(db,table,); + pkey_data = self.pdb.query(pkey_sql) + + # print(db, table) + # print(pkey_data) + key = '' + if len(pkey_data) == 1: + pkey_name = pkey_data[0]['Column_name'] + sql = "select COLUMN_NAME,DATA_TYPE from information_schema.COLUMNS where `TABLE_SCHEMA`='{}' and `TABLE_NAME` = '{}' and `COLUMN_NAME`='{}';" + sql = sql.format(db,table,pkey_name,) + # print(sql) + fields = self.pdb.query(sql) + + if len(fields) == 1: + # print(fields[0]['DATA_TYPE']) + if mw.inArray(['bigint','smallint','tinyint','int','mediumint'], fields[0]['DATA_TYPE']): + key = pkey_name + return key + + + def getTableFieldStr(self, db, table): + sql = "select COLUMN_NAME,DATA_TYPE from information_schema.COLUMNS where `TABLE_SCHEMA`='{}' and `TABLE_NAME` = '{}';" + sql = sql.format(db,table,) + fields = self.pdb.query(sql) + + field_str = '' + for x in range(len(fields)): + field_str += '`'+fields[x]['COLUMN_NAME']+'`,' + + field_str = field_str.strip(',') + return field_str + + def makeSphinxHeader(self): + conf = ''' +indexer +{ + mem_limit = 128M +} + +searchd +{ + listen = 9312 + listen = 9306:mysql41 + log = {$server_dir}/sphinx/index/searchd.log + query_log = {$server_dir}/sphinx/index/query.log + read_timeout = 5 + max_children = 0 + pid_file = {$server_dir}/sphinx/index/searchd.pid + seamless_rotate = 1 + preopen_indexes = 1 + unlink_old = 1 + #workers = threads # for RT to work + binlog_path = {$server_dir}/sphinx/index/binlog +} + ''' + conf = conf.replace("{$server_dir}", mw.getServerDir()) + return conf + + def makeSphinxDbSourceRangeSql(self, db, table): + pkey_name = self.getTablePk(db,table) + sql = "SELECT min("+pkey_name+"), max("+pkey_name+") FROM "+table + return sql + + def makeSphinxDbSourceQuerySql(self, db, table): + pkey_name = self.getTablePk(db,table) + field_str = self.getTableFieldStr(db,table) + # print(field_str) + if pkey_name == 'id': + sql = "SELECT " + field_str + " FROM " + table + " where id >= $start AND id <= $end" + else: + sql = "SELECT `"+pkey_name+'` as `id`,' + field_str + " FROM " + table + " where "+pkey_name+" >= $start AND "+pkey_name+" <= $end" + return sql + + + def makeSphinxDbSourceDeltaRange(self, db, table): + pkey_name = self.getTablePk(db,table) + conf = "SELECT (SELECT max_id FROM `{$SPH_TABLE}` where `table`='{$TABLE_NAME}') as min, (SELECT max({$PK_NAME}) FROM {$TABLE_NAME}) as max" + conf = conf.replace("{$DB_NAME}", db) + conf = conf.replace("{$TABLE_NAME}", table) + conf = conf.replace("{$SPH_TABLE}", self.delta) + conf = conf.replace("{$PK_NAME}", pkey_name) + return conf + + def makeSphinxDbSourcePost(self, db, table): + pkey_name = self.getTablePk(db,table) + conf = "sql_query_post = UPDATE {$SPH_TABLE} SET max_id=(SELECT MAX({$PK_NAME}) FROM {$TABLE_NAME}) where `table`='{$TABLE_NAME}'" + # conf = "REPLACE INTO {$SPH_TABLE} (`table`,`max_id`) VALUES ('{$TABLE_NAME}',(SELECT MAX({$PK_NAME}) FROM {$TABLE_NAME}))" + conf = conf.replace("{$DB_NAME}", db) + conf = conf.replace("{$TABLE_NAME}", table) + conf = conf.replace("{$SPH_TABLE}", self.delta) + conf = conf.replace("{$PK_NAME}", pkey_name) + return conf + + def makeSphinxDbSourceDelta(self, db, table): + conf = ''' +source {$DB_NAME}_{$TABLE_NAME}_delta:{$DB_NAME}_{$TABLE_NAME} +{ + sql_query_pre = SET NAMES utf8 + sql_query_range = {$DELTA_RANGE} + sql_query = {$DELTA_QUERY} + {$DELTA_UPDATE} + +{$SPH_FIELD} +} + +index {$DB_NAME}_{$TABLE_NAME}_delta:{$DB_NAME}_{$TABLE_NAME} +{ + source = {$DB_NAME}_{$TABLE_NAME}_delta + {$PATH_NAME} = {$server_dir}/sphinx/index/db/{$DB_NAME}.{$TABLE_NAME}/delta + + html_strip = 1 + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F + +{$SPH_FIELD_INDEX} +} +'''; + conf = conf.replace("{$server_dir}", mw.getServerDir()) + conf = conf.replace("{$PATH_NAME}", self.pathVerName()) + + conf = conf.replace("{$DB_NAME}", db) + conf = conf.replace("{$TABLE_NAME}", table) + + delta_range = self.makeSphinxDbSourceDeltaRange(db, table) + conf = conf.replace("{$DELTA_RANGE}", delta_range) + + delta_query = self.makeSphinxDbSourceQuerySql(db, table) + conf = conf.replace("{$DELTA_QUERY}", delta_query) + + delta_update = self.makeSphinxDbSourcePost(db, table) + conf = conf.replace("{$DELTA_UPDATE}", delta_update) + + + sph_field = self.makeSqlToSphinxTable(db, table) + conf = self.makeSphinxDbFieldRepalce(conf, sph_field) + + return conf; + + def makeSphinxDbSource(self, db, table, create_sphinx_table = False): + db_info = pSqliteDb('databases').field('username,password').where('name=?', (db,)).find() + port = getDbPort() + + conf = ''' +source {$DB_NAME}_{$TABLE_NAME} +{ + type = mysql + sql_host = 127.0.0.1 + sql_user = {$DB_USER} + sql_pass = {$DB_PASS} + sql_db = {$DB_NAME} + sql_port = {$DB_PORT} + + sql_query_pre = SET NAMES utf8 + + {$UPDATE} + + sql_query_range = {$DB_RANGE_SQL} + sql_range_step = 1000 + + sql_query = {$DB_QUERY_SQL} + +{$SPH_FIELD} +} + +index {$DB_NAME}_{$TABLE_NAME} +{ + source = {$DB_NAME}_{$TABLE_NAME} + {$PATH_NAME} = {$server_dir}/sphinx/index/db/{$DB_NAME}.{$TABLE_NAME}/index + + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F + +{$SPH_FIELD_INDEX} +} + ''' + conf = conf.replace("{$server_dir}", mw.getServerDir()) + conf = conf.replace("{$PATH_NAME}", self.pathVerName()) + + conf = conf.replace("{$DB_NAME}", db) + conf = conf.replace("{$TABLE_NAME}", table) + conf = conf.replace("{$DB_USER}", db_info['username']) + conf = conf.replace("{$DB_PASS}", db_info['password']) + conf = conf.replace("{$DB_PORT}", port) + + range_sql = self.makeSphinxDbSourceRangeSql(db, table) + conf = conf.replace("{$DB_RANGE_SQL}", range_sql) + + query_sql = self.makeSphinxDbSourceQuerySql(db, table) + conf = conf.replace("{$DB_QUERY_SQL}", query_sql) + + sph_field = self.makeSqlToSphinxTable(db, table) + # conf = conf.replace("{$SPH_FIELD}", sph_field) + + + conf = self.makeSphinxDbFieldRepalce(conf, sph_field) + + if create_sphinx_table: + update = self.makeSphinxDbSourcePost(db, table) + conf = conf.replace("{$UPDATE}", update) + else: + conf = conf.replace("{$UPDATE}", '') + + if create_sphinx_table: + sph_sql = self.createSql(db) + self.pdb.query(sph_sql) + sql_find = "select * from {}.{} where `table`='{}'".format(db,self.delta,table) + find_data = self.pdb.query(sql_find) + if len(find_data) == 0: + insert_sql = "insert into `{}`.`{}`(`table`,`max_id`) values ('{}',{}) ".format(db,self.delta,table,0) + # print(insert_sql) + self.pdb.execute(insert_sql) + conf += self.makeSphinxDbSourceDelta(db,table) + + # print(ver) + # print(conf) + + return conf + + def makeSphinxDbFieldRepalce(self, content, sph_field): + ver = self.ver.replace(".1",'') + ver = float(ver) + if ver >= 3.6: + content = content.replace("{$SPH_FIELD}", '') + content = content.replace("{$SPH_FIELD_INDEX}", '') + else: + content = content.replace("{$SPH_FIELD}", sph_field) + content = content.replace("{$SPH_FIELD_INDEX}", '') + + return content + + + def makeSqlToSphinxDb(self, db, table = [], is_delta = False): + conf = '' + + + for tn in table: + pkey_name = self.getTablePk(db,tn) + if pkey_name == '': + continue + conf += self.makeSphinxDbSource(db, tn,is_delta) + + if len(table) == 0: + tables = self.pdb.query("show tables in "+ db) + for x in range(len(tables)): + key = 'Tables_in_'+db + table_name = tables[x][key] + pkey_name = self.getTablePk(db, table_name, is_delta) + if pkey_name == '': + continue + + if self.makeSqlToSphinxTableIsHaveFulltext(db, table_name): + conf += self.makeSphinxDbSource(db, table_name) + return conf + + def makeSqlToSphinxTableIsHaveFulltext(self, db, table): + sql = "select COLUMN_NAME,DATA_TYPE from information_schema.COLUMNS where `TABLE_SCHEMA`='{}' and `TABLE_NAME` = '{}';" + sql = sql.format(db,table,) + cols = self.pdb.query(sql) + cols_len = len(cols) + + for x in range(cols_len): + data_type = cols[x]['DATA_TYPE'] + column_name = cols[x]['COLUMN_NAME'] + + if mw.inArray(['varchar'], data_type): + return True + if mw.inArray(['text','mediumtext','tinytext','longtext'], data_type): + return True + return False + + def makeSqlToSphinxTable(self,db,table): + pkey_name = self.getTablePk(db,table) + sql = "select COLUMN_NAME,DATA_TYPE from information_schema.COLUMNS where `TABLE_SCHEMA`='{}' and `TABLE_NAME` = '{}';" + sql = sql.format(db,table,) + cols = self.pdb.query(sql) + cols_len = len(cols) + conf = '' + run_pos = 0 + for x in range(cols_len): + data_type = cols[x]['DATA_TYPE'] + column_name = cols[x]['COLUMN_NAME'] + # print(column_name+":"+data_type) + + # if mw.inArray(['tinyint'], data_type): + # conf += 'sql_attr_bool = '+ column_name + "\n" + + if pkey_name == column_name: + # run_pos += 1 + # conf += '\tsql_attr_bigint = '+column_name+"\n" + continue + + if mw.inArray(['enum'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_string')+' = '+ column_name + "\n" + continue + + if mw.inArray(['decimal'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_float')+' = '+ column_name + "\n" + continue + + if mw.inArray(['bigint','smallint','tinyint','int','mediumint'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_bigint')+' = '+ column_name + "\n" + continue + + + if mw.inArray(['float'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_float')+' = '+ column_name + "\n" + continue + + if mw.inArray(['char'], data_type): + conf += '\t'+self.eqVerField('sql_attr_string')+' = '+ column_name + "\n" + continue + + if mw.inArray(['varchar'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_field_string')+' = '+ column_name + "\n" + continue + + if mw.inArray(['text','mediumtext','tinytext','longtext'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_field_string')+' = '+ column_name + "\n" + continue + + if mw.inArray(['datetime','date'], data_type): + run_pos += 1 + conf += '\t'+self.eqVerField('sql_attr_timestamp')+' = '+ column_name + "\n" + continue + + return conf + + def checkDbName(self, db): + filter_db = ['information_schema','performance_schema','sys','mysql'] + if db in filter_db: + return False + return True + + def makeSqlToSphinx(self, db, tables = [], is_delta = False): + conf = '' + conf += self.makeSphinxHeader() + conf += self.makeSqlToSphinxDb(db, tables, is_delta) + return conf + + def makeSqlToSphinxAll(self): + filter_db = ['information_schema','performance_schema','sys','mysql'] + + dblist = self.pdb.query('show databases') + + conf = '' + conf += self.makeSphinxHeader() + + # conf += makeSqlToSphinxDb(pdb, 'bbs') + for x in range(len(dblist)): + dbname = dblist[x]['Database'] + if mw.inArray(filter_db, dbname): + continue + conf += self.makeSqlToSphinxDb(dbname) + return conf + + diff --git a/plugins/sphinx/class/sphinxapi.py b/plugins/sphinx/class/sphinxapi.py new file mode 100644 index 000000000..88f5afde4 --- /dev/null +++ b/plugins/sphinx/class/sphinxapi.py @@ -0,0 +1,1256 @@ +# +# $Id$ +# +# Python version of Sphinx searchd client (Python API) +# +# Copyright (c) 2006, Mike Osadnik +# Copyright (c) 2006-2016, Andrew Aksyonoff +# Copyright (c) 2008-2016, Sphinx Technologies Inc +# All rights reserved +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Library General Public License. You should +# have received a copy of the LGPL license along with this program; if you +# did not, you can find it at http://www.gnu.org/ +# +# WARNING!!! +# +# As of 2015, we strongly recommend to use either SphinxQL or REST APIs +# rather than the native SphinxAPI. +# +# While both the native SphinxAPI protocol and the existing APIs will +# continue to exist, and perhaps should not even break (too much), exposing +# all the new features via multiple different native API implementations +# is too much of a support complication for us. +# +# That said, you're welcome to overtake the maintenance of any given +# official API, and remove this warning ;) +# + +from __future__ import print_function +import sys +import select +import socket +import re +from struct import * + +if sys.version_info > (3,): + long = int + text_type = str +else: + text_type = unicode + +# known searchd commands +SEARCHD_COMMAND_SEARCH = 0 +SEARCHD_COMMAND_EXCERPT = 1 +SEARCHD_COMMAND_UPDATE = 2 +SEARCHD_COMMAND_KEYWORDS = 3 +SEARCHD_COMMAND_PERSIST = 4 +SEARCHD_COMMAND_STATUS = 5 +SEARCHD_COMMAND_FLUSHATTRS = 7 + +# current client-side command implementation versions +VER_COMMAND_SEARCH = 0x120 +VER_COMMAND_EXCERPT = 0x104 +VER_COMMAND_UPDATE = 0x103 +VER_COMMAND_KEYWORDS = 0x100 +VER_COMMAND_STATUS = 0x101 +VER_COMMAND_FLUSHATTRS = 0x100 + +# known searchd status codes +SEARCHD_OK = 0 +SEARCHD_ERROR = 1 +SEARCHD_RETRY = 2 +SEARCHD_WARNING = 3 + +# known ranking modes (extended2 mode only) +SPH_RANK_PROXIMITY_BM15 = 0 # default mode, phrase proximity major factor and BM15 minor one +SPH_RANK_BM15 = 1 # statistical mode, BM15 ranking only (faster but worse quality) +SPH_RANK_NONE = 2 # no ranking, all matches get a weight of 1 +SPH_RANK_WORDCOUNT = 3 # simple word-count weighting, rank is a weighted sum of per-field keyword occurence counts +SPH_RANK_PROXIMITY = 4 +SPH_RANK_MATCHANY = 5 +SPH_RANK_FIELDMASK = 6 +SPH_RANK_SPH04 = 7 +SPH_RANK_EXPR = 8 +SPH_RANK_TOTAL = 9 + +# aliases; to be retired +SPH_RANK_PROXIMITY_BM25 = 0 +SPH_RANK_BM25 = 1 + +# known sort modes +SPH_SORT_RELEVANCE = 0 +SPH_SORT_ATTR_DESC = 1 +SPH_SORT_ATTR_ASC = 2 +SPH_SORT_TIME_SEGMENTS = 3 +SPH_SORT_EXTENDED = 4 + +# known filter types +SPH_FILTER_VALUES = 0 +SPH_FILTER_RANGE = 1 +SPH_FILTER_FLOATRANGE = 2 +SPH_FILTER_STRING = 3 +SPH_FILTER_STRING_LIST = 6 + +# known attribute types +SPH_ATTR_NONE = 0 +SPH_ATTR_INTEGER = 1 +SPH_ATTR_TIMESTAMP = 2 +SPH_ATTR_ORDINAL = 3 +SPH_ATTR_BOOL = 4 +SPH_ATTR_FLOAT = 5 +SPH_ATTR_BIGINT = 6 +SPH_ATTR_STRING = 7 +SPH_ATTR_FACTORS = 1001 +SPH_ATTR_MULTI = long(0X40000001) +SPH_ATTR_MULTI64 = long(0X40000002) + +SPH_ATTR_TYPES = (SPH_ATTR_NONE, + SPH_ATTR_INTEGER, + SPH_ATTR_TIMESTAMP, + SPH_ATTR_ORDINAL, + SPH_ATTR_BOOL, + SPH_ATTR_FLOAT, + SPH_ATTR_BIGINT, + SPH_ATTR_STRING, + SPH_ATTR_MULTI, + SPH_ATTR_MULTI64) + +# known grouping functions +SPH_GROUPBY_DAY = 0 +SPH_GROUPBY_WEEK = 1 +SPH_GROUPBY_MONTH = 2 +SPH_GROUPBY_YEAR = 3 +SPH_GROUPBY_ATTR = 4 +SPH_GROUPBY_ATTRPAIR = 5 + + +class SphinxClient: + def __init__ (self): + """ + Create a new client object, and fill defaults. + """ + self._host = 'localhost' # searchd host (default is "localhost") + self._port = 9312 # searchd port (default is 9312) + self._path = None # searchd unix-domain socket path + self._socket = None + self._offset = 0 # how much records to seek from result-set start (default is 0) + self._limit = 20 # how much records to return from result-set starting at offset (default is 20) + self._weights = [] # per-field weights (default is 1 for all fields) + self._sort = SPH_SORT_RELEVANCE # match sorting mode (default is SPH_SORT_RELEVANCE) + self._sortby = bytearray() # attribute to sort by (defualt is "") + self._min_id = 0 # min ID to match (default is 0) + self._max_id = 0 # max ID to match (default is UINT_MAX) + self._filters = [] # search filters + self._groupby = bytearray() # group-by attribute name + self._groupfunc = SPH_GROUPBY_DAY # group-by function (to pre-process group-by attribute value with) + self._groupsort = str_bytes('@group desc') # group-by sorting clause (to sort groups in result set with) + self._groupdistinct = bytearray() # group-by count-distinct attribute + self._maxmatches = 1000 # max matches to retrieve + self._cutoff = 0 # cutoff to stop searching at + self._retrycount = 0 # distributed retry count + self._retrydelay = 0 # distributed retry delay + self._indexweights = {} # per-index weights + self._ranker = SPH_RANK_PROXIMITY_BM15 # ranking mode + self._rankexpr = bytearray() # ranking expression for SPH_RANK_EXPR + self._maxquerytime = 0 # max query time, milliseconds (default is 0, do not limit) + self._timeout = 1.0 # connection timeout + self._fieldweights = {} # per-field-name weights + self._select = str_bytes('*') # select-list (attributes or expressions, with optional aliases) + self._query_flags = SetBit ( 0, 6, True ) # default idf=tfidf_normalized + self._predictedtime = 0 # per-query max_predicted_time + self._outerorderby = bytearray() # outer match sort by + self._outeroffset = 0 # outer offset + self._outerlimit = 0 # outer limit + self._hasouter = False # sub-select enabled + self._tokenfilterlibrary = bytearray() # token_filter plugin library name + self._tokenfiltername = bytearray() # token_filter plugin name + self._tokenfilteropts = bytearray() # token_filter plugin options + + self._error = '' # last error message + self._warning = '' # last warning message + self._reqs = [] # requests array for multi-query + + def __del__ (self): + if self._socket: + self._socket.close() + + + def GetLastError (self): + """ + Get last error message (string). + """ + return self._error + + + def GetLastWarning (self): + """ + Get last warning message (string). + """ + return self._warning + + + def SetServer (self, host, port = None): + """ + Set searchd server host and port. + """ + assert(isinstance(host, str)) + if host.startswith('/'): + self._path = host + return + elif host.startswith('unix://'): + self._path = host[7:] + return + self._host = host + if isinstance(port, int): + assert(port>0 and port<65536) + self._port = port + self._path = None + + def SetConnectTimeout ( self, timeout ): + """ + Set connection timeout ( float second ) + """ + assert (isinstance(timeout, float)) + # set timeout to 0 make connaection non-blocking that is wrong so timeout got clipped to reasonable minimum + self._timeout = max ( 0.001, timeout ) + + def _Connect (self): + """ + INTERNAL METHOD, DO NOT CALL. Connects to searchd server. + """ + if self._socket: + # we have a socket, but is it still alive? + sr, sw, _ = select.select ( [self._socket], [self._socket], [], 0 ) + + # this is how alive socket should look + if len(sr)==0 and len(sw)==1: + return self._socket + + # oops, looks like it was closed, lets reopen + self._socket.close() + self._socket = None + + try: + if self._path: + af = socket.AF_UNIX + addr = self._path + desc = self._path + else: + af = socket.AF_INET + addr = ( self._host, self._port ) + desc = '%s;%s' % addr + sock = socket.socket ( af, socket.SOCK_STREAM ) + sock.settimeout ( self._timeout ) + sock.connect ( addr ) + except socket.error as msg: + if sock: + sock.close() + self._error = 'connection to %s failed (%s)' % ( desc, msg ) + return + + v = unpack('>L', sock.recv(4))[0] + if v<1: + sock.close() + self._error = 'expected searchd protocol version, got %s' % v + return + + # all ok, send my version + sock.send(pack('>L', 1)) + return sock + + + def _GetResponse (self, sock, client_ver): + """ + INTERNAL METHOD, DO NOT CALL. Gets and checks response packet from searchd server. + """ + (status, ver, length) = unpack('>2HL', sock.recv(8)) + response = bytearray() + left = length + while left>0: + chunk = sock.recv(left) + if chunk: + response += chunk + left -= len(chunk) + else: + break + + if not self._socket: + sock.close() + + # check response + read = len(response) + if not response or read!=length: + if length: + self._error = 'failed to read searchd response (status=%s, ver=%s, len=%s, read=%s)' \ + % (status, ver, length, read) + else: + self._error = 'received zero-sized searchd response' + return None + + # check status + if status==SEARCHD_WARNING: + wend = 4 + unpack ( '>L', response[0:4] )[0] + self._warning = bytes_str(response[4:wend]) + return response[wend:] + + if status==SEARCHD_ERROR: + self._error = 'searchd error: ' + bytes_str(response[4:]) + return None + + if status==SEARCHD_RETRY: + self._error = 'temporary searchd error: ' + bytes_str(response[4:]) + return None + + if status!=SEARCHD_OK: + self._error = 'unknown status code %d' % status + return None + + # check version + if ver>8, ver&0xff, client_ver>>8, client_ver&0xff) + + return response + + + def _Send ( self, sock, req ): + """ + INTERNAL METHOD, DO NOT CALL. send request to searchd server. + """ + total = 0 + while True: + sent = sock.send ( req[total:] ) + if sent<=0: + break + + total = total + sent + + return total + + + def SetLimits (self, offset, limit, maxmatches=0, cutoff=0): + """ + Set offset and count into result set, and optionally set max-matches and cutoff limits. + """ + assert ( type(offset) in [int,long] and 0<=offset<16777216 ) + assert ( type(limit) in [int,long] and 0=0) + self._offset = offset + self._limit = limit + if maxmatches>0: + self._maxmatches = maxmatches + if cutoff>=0: + self._cutoff = cutoff + + + def SetMaxQueryTime (self, maxquerytime): + """ + Set maximum query time, in milliseconds, per-index. 0 means 'do not limit'. + """ + assert(isinstance(maxquerytime,int) and maxquerytime>0) + self._maxquerytime = maxquerytime + + + def SetRankingMode ( self, ranker, rankexpr='' ): + """ + Set ranking mode. + """ + assert(ranker>=0 and ranker=0) + assert(isinstance(delay,int) and delay>=0) + self._retrycount = count + self._retrydelay = delay + + + def SetSelect (self, select): + assert(isinstance(select, (str,text_type))) + self._select = str_bytes(select) + + + def SetQueryFlag ( self, name, value ): + known_names = [ "reverse_scan", "sort_method", "max_predicted_time", "boolean_simplify", "idf", "global_idf" ] + flags = { "reverse_scan":[0, 1], "sort_method":["pq", "kbuffer"],"max_predicted_time":[0], "boolean_simplify":[True, False], "idf":["normalized", "plain", "tfidf_normalized", "tfidf_unnormalized"], "global_idf":[True, False] } + assert ( name in known_names ) + assert ( value in flags[name] or ( name=="max_predicted_time" and isinstance(value, (int, long)) and value>=0)) + + if name=="reverse_scan": + self._query_flags = SetBit ( self._query_flags, 0, value==1 ) + if name=="sort_method": + self._query_flags = SetBit ( self._query_flags, 1, value=="kbuffer" ) + if name=="max_predicted_time": + self._query_flags = SetBit ( self._query_flags, 2, value>0 ) + self._predictedtime = int(value) + if name=="boolean_simplify": + self._query_flags= SetBit ( self._query_flags, 3, value ) + if name=="idf" and ( value=="plain" or value=="normalized" ) : + self._query_flags = SetBit ( self._query_flags, 4, value=="plain" ) + if name=="global_idf": + self._query_flags= SetBit ( self._query_flags, 5, value ) + if name=="idf" and ( value=="tfidf_normalized" or value=="tfidf_unnormalized" ) : + self._query_flags = SetBit ( self._query_flags, 6, value=="tfidf_normalized" ) + + def SetOuterSelect ( self, orderby, offset, limit ): + assert(isinstance(orderby, (str,text_type))) + assert(isinstance(offset, (int, long))) + assert(isinstance(limit, (int, long))) + assert ( offset>=0 ) + assert ( limit>0 ) + + self._outerorderby = str_bytes(orderby) + self._outeroffset = offset + self._outerlimit = limit + self._hasouter = True + + def SetTokenFilter ( self, library, name, opts='' ): + assert(isinstance(library, str)) + assert(isinstance(name, str)) + assert(isinstance(opts, str)) + + self._tokenfilterlibrary = str_bytes(library) + self._tokenfiltername = str_bytes(name) + self._tokenfilteropts = str_bytes(opts) + + + def ResetFilters (self): + """ + Clear all filters (for multi-queries). + """ + self._filters = [] + + + def ResetGroupBy (self): + """ + Clear groupby settings (for multi-queries). + """ + self._groupby = bytearray() + self._groupfunc = SPH_GROUPBY_DAY + self._groupsort = str_bytes('@group desc') + self._groupdistinct = bytearray() + + def ResetQueryFlag (self): + self._query_flags = SetBit ( 0, 6, True ) # default idf=tfidf_normalized + self._predictedtime = 0 + + def ResetOuterSelect (self): + self._outerorderby = bytearray() + self._outeroffset = 0 + self._outerlimit = 0 + self._hasouter = False + + def Query (self, query, index='*', comment=''): + """ + Connect to searchd server and run given search query. + Returns None on failure; result set hash on success (see documentation for details). + """ + assert(len(self._reqs)==0) + self.AddQuery(query,index,comment) + results = self.RunQueries() + self._reqs = [] # we won't re-run erroneous batch + + if not results or len(results)==0: + return None + self._error = results[0]['error'] + self._warning = results[0]['warning'] + if results[0]['status'] == SEARCHD_ERROR: + return None + return results[0] + + + def AddQuery (self, query, index='*', comment=''): + """ + Add query to batch. + """ + # build request + # 6 == match_mode extended2 + req = bytearray() + req.extend(pack('>5L', self._query_flags, self._offset, self._limit, 6, self._ranker)) + if self._ranker==SPH_RANK_EXPR: + req.extend(pack('>L', len(self._rankexpr))) + req.extend(self._rankexpr) + req.extend(pack('>L', self._sort)) + req.extend(pack('>L', len(self._sortby))) + req.extend(self._sortby) + + query = str_bytes(query) + assert(isinstance(query,bytearray)) + + req.extend(pack('>L', len(query))) + req.extend(query) + + req.extend(pack('>L', len(self._weights))) + for w in self._weights: + req.extend(pack('>L', w)) + index = str_bytes(index) + assert(isinstance(index,bytearray)) + req.extend(pack('>L', len(index))) + req.extend(index) + req.extend(pack('>L',1)) # id64 range marker + req.extend(pack('>Q', self._min_id)) + req.extend(pack('>Q', self._max_id)) + + # filters + req.extend ( pack ( '>L', len(self._filters) ) ) + for f in self._filters: + attr = str_bytes(f['attr']) + req.extend ( pack ( '>L', len(f['attr'])) + attr) + filtertype = f['type'] + req.extend ( pack ( '>L', filtertype)) + if filtertype == SPH_FILTER_VALUES: + req.extend ( pack ('>L', len(f['values']))) + for val in f['values']: + req.extend ( pack ('>q', val)) + elif filtertype == SPH_FILTER_RANGE: + req.extend ( pack ('>2q', f['min'], f['max'])) + elif filtertype == SPH_FILTER_FLOATRANGE: + req.extend ( pack ('>2f', f['min'], f['max'])) + elif filtertype == SPH_FILTER_STRING: + val = str_bytes(f['value']) + req.extend ( pack ( '>L', len(val) ) ) + req.extend ( val ) + elif filtertype == SPH_FILTER_STRING_LIST: + req.extend ( pack ('>L', len(f['values']))) + for sval in f['values']: + val = str_bytes( sval ) + req.extend ( pack ( '>L', len(val) ) ) + req.extend(val) + req.extend ( pack ( '>L', f['exclude'] ) ) + + # group-by, max-matches, group-sort + req.extend ( pack ( '>2L', self._groupfunc, len(self._groupby) ) ) + req.extend ( self._groupby ) + req.extend ( pack ( '>2L', self._maxmatches, len(self._groupsort) ) ) + req.extend ( self._groupsort ) + req.extend ( pack ( '>LLL', self._cutoff, self._retrycount, self._retrydelay)) + req.extend ( pack ( '>L', len(self._groupdistinct))) + req.extend ( self._groupdistinct) + + # geoanchor point + req.extend ( pack ('>L', 0) ) + + # per-index weights + req.extend ( pack ('>L',len(self._indexweights))) + for indx,weight in list(self._indexweights.items()): + indx = str_bytes(indx) + req.extend ( pack ('>L',len(indx)) + indx + pack ('>L',weight)) + + # max query time + req.extend ( pack ('>L', self._maxquerytime) ) + + # per-field weights + req.extend ( pack ('>L',len(self._fieldweights) ) ) + for field,weight in list(self._fieldweights.items()): + field = str_bytes(field) + req.extend ( pack ('>L',len(field)) + field + pack ('>L',weight) ) + + # comment + comment = str_bytes(comment) + req.extend ( pack('>L',len(comment)) + comment ) + + # attribute overrides + req.extend ( pack('>L', 0) ) + + # select-list + req.extend ( pack('>L', len(self._select)) ) + req.extend ( self._select ) + if self._predictedtime>0: + req.extend ( pack('>L', self._predictedtime ) ) + + # outer + req.extend ( pack('>L',len(self._outerorderby)) + self._outerorderby ) + req.extend ( pack ( '>2L', self._outeroffset, self._outerlimit ) ) + if self._hasouter: + req.extend ( pack('>L', 1) ) + else: + req.extend ( pack('>L', 0) ) + + # token_filter + req.extend ( pack('>L',len(self._tokenfilterlibrary)) + self._tokenfilterlibrary ) + req.extend ( pack('>L',len(self._tokenfiltername)) + self._tokenfiltername ) + req.extend ( pack('>L',len(self._tokenfilteropts)) + self._tokenfilteropts ) + + # send query, get response + + self._reqs.append(req) + return + + + def RunQueries (self): + """ + Run queries batch. + Returns None on network IO failure; or an array of result set hashes on success. + """ + if len(self._reqs)==0: + self._error = 'no queries defined, issue AddQuery() first' + return None + + sock = self._Connect() + if not sock: + return None + + req = bytearray() + for r in self._reqs: + req.extend(r) + length = len(req)+8 + req_all = bytearray() + req_all.extend(pack('>HHLLL', SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, length, 0, len(self._reqs))) + req_all.extend(req) + self._Send ( sock, req_all ) + + response = self._GetResponse(sock, VER_COMMAND_SEARCH) + if not response: + return None + + nreqs = len(self._reqs) + + # parse response + max_ = len(response) + p = 0 + + results = [] + for i in range(0,nreqs,1): + result = {} + results.append(result) + + result['error'] = '' + result['warning'] = '' + status = unpack('>L', response[p:p+4])[0] + p += 4 + result['status'] = status + if status != SEARCHD_OK: + length = unpack('>L', response[p:p+4])[0] + p += 4 + message = bytes_str(response[p:p+length]) + p += length + + if status == SEARCHD_WARNING: + result['warning'] = message + else: + result['error'] = message + continue + + # read schema + fields = [] + attrs = [] + + nfields = unpack('>L', response[p:p+4])[0] + p += 4 + while nfields>0 and pL', response[p:p+4])[0] + p += 4 + fields.append(bytes_str(response[p:p+length])) + p += length + + result['fields'] = fields + + nattrs = unpack('>L', response[p:p+4])[0] + p += 4 + while nattrs>0 and pL', response[p:p+4])[0] + p += 4 + attr = bytes_str(response[p:p+length]) + p += length + type_ = unpack('>L', response[p:p+4])[0] + p += 4 + attrs.append([attr,type_]) + + result['attrs'] = attrs + + # read match count + count = unpack('>L', response[p:p+4])[0] + p += 4 + id64 = unpack('>L', response[p:p+4])[0] + p += 4 + + # read matches + result['matches'] = [] + while count>0 and pQL', response[p:p+12]) + p += 12 + else: + doc, weight = unpack('>2L', response[p:p+8]) + p += 8 + + match = { 'id':doc, 'weight':weight, 'attrs':{} } + for i in range(len(attrs)): + if attrs[i][1] == SPH_ATTR_FLOAT: + match['attrs'][attrs[i][0]] = unpack('>f', response[p:p+4])[0] + elif attrs[i][1] == SPH_ATTR_BIGINT: + match['attrs'][attrs[i][0]] = unpack('>q', response[p:p+8])[0] + p += 4 + elif attrs[i][1] == SPH_ATTR_STRING: + slen = unpack('>L', response[p:p+4])[0] + p += 4 + match['attrs'][attrs[i][0]] = '' + if slen>0: + match['attrs'][attrs[i][0]] = bytes_str(response[p:p+slen]) + p += slen-4 + elif attrs[i][1] == SPH_ATTR_FACTORS: + slen = unpack('>L', response[p:p+4])[0] + p += 4 + match['attrs'][attrs[i][0]] = '' + if slen>0: + match['attrs'][attrs[i][0]] = response[p:p+slen-4] + p += slen-4 + p -= 4 + elif attrs[i][1] == SPH_ATTR_MULTI: + match['attrs'][attrs[i][0]] = [] + nvals = unpack('>L', response[p:p+4])[0] + p += 4 + for n in range(0,nvals,1): + match['attrs'][attrs[i][0]].append(unpack('>L', response[p:p+4])[0]) + p += 4 + p -= 4 + elif attrs[i][1] == SPH_ATTR_MULTI64: + match['attrs'][attrs[i][0]] = [] + nvals = unpack('>L', response[p:p+4])[0] + nvals = nvals/2 + p += 4 + for n in range(0,nvals,1): + match['attrs'][attrs[i][0]].append(unpack('>q', response[p:p+8])[0]) + p += 8 + p -= 4 + else: + match['attrs'][attrs[i][0]] = unpack('>L', response[p:p+4])[0] + p += 4 + + result['matches'].append ( match ) + + result['total'], result['total_found'], result['time'], words = unpack('>4L', response[p:p+16]) + + result['time'] = '%.3f' % (result['time']/1000.0) + p += 16 + + result['words'] = [] + while words>0: + words -= 1 + length = unpack('>L', response[p:p+4])[0] + p += 4 + word = bytes_str(response[p:p+length]) + p += length + docs, hits = unpack('>2L', response[p:p+8]) + p += 8 + + result['words'].append({'word':word, 'docs':docs, 'hits':hits}) + + self._reqs = [] + return results + + + def BuildExcerpts (self, docs, index, words, opts=None): + """ + Connect to searchd server and generate exceprts from given documents. + """ + if not opts: + opts = {} + + assert(isinstance(docs, list)) + assert(isinstance(index, (str,text_type))) + assert(isinstance(words, (str,text_type))) + assert(isinstance(opts, dict)) + + sock = self._Connect() + + if not sock: + return None + + # fixup options + opts.setdefault('before_match', '') + opts.setdefault('after_match', '') + opts.setdefault('chunk_separator', ' ... ') + opts.setdefault('html_strip_mode', 'index') + opts.setdefault('limit', 256) + opts.setdefault('limit_passages', 0) + opts.setdefault('limit_words', 0) + opts.setdefault('around', 5) + opts.setdefault('start_passage_id', 1) + opts.setdefault('passage_boundary', 'none') + + # build request + # v.1.0 req + + flags = 1 # (remove spaces) + if opts.get('exact_phrase'): flags |= 2 + if opts.get('single_passage'): flags |= 4 + if opts.get('use_boundaries'): flags |= 8 + if opts.get('weight_order'): flags |= 16 + if opts.get('query_mode'): flags |= 32 + if opts.get('force_all_words'): flags |= 64 + if opts.get('load_files'): flags |= 128 + if opts.get('allow_empty'): flags |= 256 + if opts.get('emit_zones'): flags |= 512 + if opts.get('load_files_scattered'): flags |= 1024 + + # mode=0, flags + req = bytearray() + req.extend(pack('>2L', 0, flags)) + + # req index + index = str_bytes(index) + req.extend(pack('>L', len(index))) + req.extend(index) + + # req words + words = str_bytes(words) + req.extend(pack('>L', len(words))) + req.extend(words) + + # options + opts_before_match = str_bytes(opts['before_match']) + req.extend(pack('>L', len(opts_before_match))) + req.extend(opts_before_match) + + opts_after_match = str_bytes(opts['after_match']) + req.extend(pack('>L', len(opts_after_match))) + req.extend(opts_after_match) + + opts_chunk_separator = str_bytes(opts['chunk_separator']) + req.extend(pack('>L', len(opts_chunk_separator))) + req.extend(opts_chunk_separator) + + req.extend(pack('>L', int(opts['limit']))) + req.extend(pack('>L', int(opts['around']))) + + req.extend(pack('>L', int(opts['limit_passages']))) + req.extend(pack('>L', int(opts['limit_words']))) + req.extend(pack('>L', int(opts['start_passage_id']))) + opts_html_strip_mode = str_bytes(opts['html_strip_mode']) + req.extend(pack('>L', len(opts_html_strip_mode))) + req.extend(opts_html_strip_mode) + opts_passage_boundary = str_bytes(opts['passage_boundary']) + req.extend(pack('>L', len(opts_passage_boundary))) + req.extend(opts_passage_boundary) + + # documents + req.extend(pack('>L', len(docs))) + for doc in docs: + doc = str_bytes(doc) + req.extend(pack('>L', len(doc))) + req.extend(doc) + + # send query, get response + length = len(req) + + # add header + req_head = bytearray() + req_head.extend(pack('>2HL', SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, length)) + req_all = req_head + req + self._Send ( sock, req_all ) + + response = self._GetResponse(sock, VER_COMMAND_EXCERPT ) + if not response: + return [] + + # parse response + pos = 0 + res = [] + rlen = len(response) + + for i in range(len(docs)): + length = unpack('>L', response[pos:pos+4])[0] + pos += 4 + + if pos+length > rlen: + self._error = 'incomplete reply' + return [] + + res.append(bytes_str(response[pos:pos+length])) + pos += length + + return res + + + def UpdateAttributes ( self, index, attrs, values, mva=False, ignorenonexistent=False ): + """ + Update given attribute values on given documents in given indexes. + Returns amount of updated documents (0 or more) on success, or -1 on failure. + + 'attrs' must be a list of strings. + 'values' must be a dict with int key (document ID) and list of int values (new attribute values). + optional boolean parameter 'mva' points that there is update of MVA attributes. + In this case the 'values' must be a dict with int key (document ID) and list of lists of int values + (new MVA attribute values). + Optional boolean parameter 'ignorenonexistent' points that the update will silently ignore any warnings about + trying to update a column which is not exists in current index schema. + + Example: + res = cl.UpdateAttributes ( 'test1', [ 'group_id', 'date_added' ], { 2:[123,1000000000], 4:[456,1234567890] } ) + """ + assert ( isinstance ( index, str ) ) + assert ( isinstance ( attrs, list ) ) + assert ( isinstance ( values, dict ) ) + for attr in attrs: + assert ( isinstance ( attr, str ) ) + for docid, entry in list(values.items()): + AssertUInt32(docid) + assert ( isinstance ( entry, list ) ) + assert ( len(attrs)==len(entry) ) + for val in entry: + if mva: + assert ( isinstance ( val, list ) ) + for vals in val: + AssertInt32(vals) + else: + AssertInt32(val) + + # build request + req = bytearray() + index = str_bytes(index) + req.extend( pack('>L',len(index)) + index ) + + req.extend ( pack('>L',len(attrs)) ) + ignore_absent = 0 + if ignorenonexistent: ignore_absent = 1 + req.extend ( pack('>L', ignore_absent ) ) + mva_attr = 0 + if mva: mva_attr = 1 + for attr in attrs: + attr = str_bytes(attr) + req.extend ( pack('>L',len(attr)) + attr ) + req.extend ( pack('>L', mva_attr ) ) + + req.extend ( pack('>L',len(values)) ) + for docid, entry in list(values.items()): + req.extend ( pack('>Q',docid) ) + for val in entry: + val_len = val + if mva: val_len = len ( val ) + req.extend ( pack('>L',val_len ) ) + if mva: + for vals in val: + req.extend ( pack ('>L',vals) ) + + # connect, send query, get response + sock = self._Connect() + if not sock: + return None + + length = len(req) + req_all = bytearray() + req_all.extend( pack ( '>2HL', SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, length ) ) + req_all.extend( req ) + self._Send ( sock, req_all ) + + response = self._GetResponse ( sock, VER_COMMAND_UPDATE ) + if not response: + return -1 + + # parse response + updated = unpack ( '>L', response[0:4] )[0] + return updated + + + def BuildKeywords ( self, query, index, hits ): + """ + Connect to searchd server, and generate keywords list for a given query. + Returns None on failure, or a list of keywords on success. + """ + assert ( isinstance ( query, str ) ) + assert ( isinstance ( index, str ) ) + assert ( isinstance ( hits, int ) ) + + # build request + req = bytearray() + query = str_bytes(query) + req.extend(pack ( '>L', len(query) ) + query) + index = str_bytes(index) + req.extend ( pack ( '>L', len(index) ) + index ) + req.extend ( pack ( '>L', hits ) ) + + # connect, send query, get response + sock = self._Connect() + if not sock: + return None + + length = len(req) + req_all = bytearray() + req_all.extend(pack ( '>2HL', SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, length )) + req_all.extend(req) + self._Send ( sock, req_all ) + + response = self._GetResponse ( sock, VER_COMMAND_KEYWORDS ) + if not response: + return None + + # parse response + res = [] + + nwords = unpack ( '>L', response[0:4] )[0] + p = 4 + max_ = len(response) + + while nwords>0 and pL', response[p:p+4] )[0] + p += 4 + tokenized = response[p:p+length] + p += length + + length = unpack ( '>L', response[p:p+4] )[0] + p += 4 + normalized = response[p:p+length] + p += length + + entry = { 'tokenized':bytes_str(tokenized), 'normalized':bytes_str(normalized) } + if hits: + entry['docs'], entry['hits'] = unpack ( '>2L', response[p:p+8] ) + p += 8 + + res.append ( entry ) + + if nwords>0 or p>max_: + self._error = 'incomplete reply' + return None + + return res + + def Status ( self, session=False ): + """ + Get the status + """ + + # connect, send query, get response + sock = self._Connect() + if not sock: + return None + + sess = 1 + if session: + sess = 0 + + req = pack ( '>2HLL', SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, sess ) + self._Send ( sock, req ) + + response = self._GetResponse ( sock, VER_COMMAND_STATUS ) + if not response: + return None + + # parse response + res = [] + + p = 8 + max_ = len(response) + + while pL', response[p:p+4] )[0] + k = response[p+4:p+length+4] + p += 4+length + length = unpack ( '>L', response[p:p+4] )[0] + v = response[p+4:p+length+4] + p += 4+length + res += [[bytes_str(k), bytes_str(v)]] + + return res + + ### persistent connections + + def Open(self): + if self._socket: + self._error = 'already connected' + return None + + server = self._Connect() + if not server: + return None + + # command, command version = 0, body length = 4, body = 1 + request = pack ( '>hhII', SEARCHD_COMMAND_PERSIST, 0, 4, 1 ) + self._Send ( server, request ) + + self._socket = server + return True + + def Close(self): + if not self._socket: + self._error = 'not connected' + return + self._socket.close() + self._socket = None + + def EscapeString(self, string): + return re.sub(r"([=\(\)|\-!@~\"&/\\\^\$\=\<])", r"\\\1", string) + + + def FlushAttributes(self): + sock = self._Connect() + if not sock: + return -1 + + request = pack ( '>hhI', SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ) # cmd, ver, bodylen + self._Send ( sock, request ) + + response = self._GetResponse ( sock, VER_COMMAND_FLUSHATTRS ) + if not response or len(response)!=4: + self._error = 'unexpected response length' + return -1 + + tag = unpack ( '>L', response[0:4] )[0] + return tag + +def AssertInt32 ( value ): + assert(isinstance(value, (int, long))) + assert(value>=-2**32-1 and value<=2**32-1) + +def AssertUInt32 ( value ): + assert(isinstance(value, (int, long))) + assert(value>=0 and value<=2**32-1) + +def SetBit ( flag, bit, on ): + if on: + flag += ( 1< (3,): + def str_bytes(x): + return bytearray(x, 'utf-8') +else: + def str_bytes(x): + if isinstance(x,unicode): + return bytearray(x, 'utf-8') + else: + return bytearray(x) + +def bytes_str(x): + assert (isinstance(x, bytearray)) + return x.decode('utf-8') + +# +# $Id$ +# diff --git a/plugins/sphinx/conf/sphinx.conf b/plugins/sphinx/conf/sphinx.conf new file mode 100755 index 000000000..de9e5f270 --- /dev/null +++ b/plugins/sphinx/conf/sphinx.conf @@ -0,0 +1,28 @@ +# +# Minimal Sphinx configuration sample (clean, simple, functional) +# + + +searchd +{ + listen = 9312 + listen = 9306:mysql41 + log = {$SERVER_APP}/index/searchd.log + query_log = {$SERVER_APP}/index/query.log + pid_file = {$SERVER_APP}/index/searchd.pid + #workers = threads # for RT to work + binlog_path = {$SERVER_APP}/index/binlog + read_timeout = 5 + max_children = 0 + seamless_rotate = 1 + preopen_indexes = 1 + unlink_old = 1 +} + +index mydocs +{ + type = rt + path = {$SERVER_APP}/bin/doc + rt_field = title + rt_attr_json = j +} \ No newline at end of file diff --git a/plugins/sphinx/ico.png b/plugins/sphinx/ico.png new file mode 100644 index 000000000..b80d938b7 Binary files /dev/null and b/plugins/sphinx/ico.png differ diff --git a/plugins/sphinx/index.html b/plugins/sphinx/index.html new file mode 100755 index 000000000..b62232b49 --- /dev/null +++ b/plugins/sphinx/index.html @@ -0,0 +1,36 @@ + + +
                                +
                                +
                                +

                                服务

                                +

                                自启动

                                +

                                配置修改

                                +

                                运行日志

                                +

                                查询日志

                                +

                                运行状态

                                +

                                常用功能

                                +

                                说明

                                +
                                +
                                +
                                +
                                +
                                +
                                + \ No newline at end of file diff --git a/plugins/sphinx/index.py b/plugins/sphinx/index.py new file mode 100755 index 000000000..2e8e8ca7f --- /dev/null +++ b/plugins/sphinx/index.py @@ -0,0 +1,484 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import string +import subprocess + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + +def getPluginName(): + return 'sphinx' + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + +sys.path.append(getPluginDir() +"/class") + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConfTpl(): + path = getPluginDir() + "/conf/sphinx.conf" + return path + + +def getConf(): + path = getServerDir() + "/sphinx.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/'+getPluginName()) + return content + + +def status(): + data = mw.execShell( + "ps -ef|grep sphinx |grep -v grep | grep -v mdserver-web | awk '{print $2}'") + # print(data) + if data[0] == '': + return 'stop' + return 'start' + + +def mkdirAll(): + content = mw.readFile(getConf()) + rep = r'path\s*=\s*(.*)' + p = re.compile(rep) + tmp = p.findall(content) + + for x in tmp: + if x.find('binlog') != -1: + mw.execShell('mkdir -p ' + x) + else: + mw.execShell('mkdir -p ' + os.path.dirname(x)) + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = contentReplace(content) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # config replace + conf_bin = getConf() + if not os.path.exists(conf_bin): + conf_content = mw.readFile(getConfTpl()) + conf_content = contentReplace(conf_content) + mw.writeFile(getServerDir() + '/sphinx.conf', conf_content) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/sphinx.service' + systemServiceTpl = getPluginDir() + '/init.d/sphinx.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + mkdirAll() + return file_bin + + +def checkIndexSph(): + content = mw.readFile(getConf()) + rep = r'path\s*=\s*(.*)' + p = re.compile(rep) + tmp = p.findall(content) + for x in tmp: + if x.find('binlog') != -1: + continue + else: + p = x + '.sph' + if os.path.exists(p): + return False + return True + + +def sphOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + import tool_cron + tool_cron.createBgTask() + return sphOp('start') + + +def stop(): + import tool_cron + tool_cron.removeBgTask() + return sphOp('stop') + + +def restart(): + return sphOp('restart') + + +def reload(): + return sphOp('reload') + + +def rebuild(): + file = initDreplace() + cmd = file + ' rebuild' + data = mw.execShell(cmd) + if data[0].find('successfully')<0: + return data[0].replace("\n","
                                ") + return 'ok' + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status sphinx | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable sphinx') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable sphinx') + return 'ok' + + +def runLog(): + path = getConf() + content = mw.readFile(path) + rep = r'log\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0] + + +def getPort(): + path = getConf() + content = mw.readFile(path) + rep = r'listen\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0] + + +def queryLog(): + path = getConf() + content = mw.readFile(path) + rep = r'query_log\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0] + + +def runStatus(): + s = status() + if s != 'start': + return mw.returnJson(False, '没有启动程序') + + sys.path.append(getPluginDir() + "/class") + import sphinxapi + + sh = sphinxapi.SphinxClient() + port = getPort() + sh.SetServer('127.0.0.1', port) + info_status = sh.Status() + + rData = {} + for x in range(len(info_status)): + rData[info_status[x][0]] = info_status[x][1] + + return mw.returnJson(True, 'ok', rData) + + +def sphinxConfParse(): + file = getConf() + bin_dir = getServerDir() + content = mw.readFile(file) + rep = r'index\s(.*)' + sindex = re.findall(rep, content) + indexlen = len(sindex) + cmd = {} + cmd['cmd'] = bin_dir + '/bin/bin/indexer -c ' + bin_dir + '/sphinx.conf' + + cmd['index'] = [] + cmd_index = [] + cmd_delta = [] + if indexlen > 0: + for x in range(indexlen): + name = sindex[x].strip() + if name == '': + continue + if name.find(':') != -1: + cmd_delta.append(name.strip()) + else: + cmd_index.append(name.strip()) + + # print(cmd_index) + # print(cmd_delta) + + for ci in cmd_index: + val = {} + val['index'] = ci + + for cd in cmd_delta: + cd = cd.replace(" ", '') + if cd.find(":"+ci) > -1: + val['delta'] = cd.split(":")[0].strip() + break + + cmd['index'].append(val) + return cmd + + +def sphinxCmd(): + data = sphinxConfParse() + if 'index' in data: + return mw.returnJson(True, 'ok', data) + else: + return mw.returnJson(False, 'no index') + +def makeDbToSphinxTest(): + conf_file = getConf() + import sphinx_make + sph_make = sphinx_make.sphinxMake() + conf = sph_make.makeSqlToSphinxAll() + + mw.writeFile(conf_file,conf) + print(conf) + # makeSqlToSphinxTable() + return True + +def makeDbToSphinx(): + args = getArgs() + check = checkArgs(args, ['db','tables','is_delta','is_cover']) + if not check[0]: + return check[1] + + db = args['db'] + tables = args['tables'] + is_delta = args['is_delta'] + is_cover = args['is_cover'] + + if is_cover != 'yes': + return mw.returnJson(False,'暂时仅支持覆盖!') + + sph_file = getConf() + + import sphinx_make + sph_make = sphinx_make.sphinxMake() + + version_pl = getServerDir() + "/version.pl" + if os.path.exists(version_pl): + version = mw.readFile(version_pl).strip() + sph_make.setVersion(version) + + if not sph_make.checkDbName(db): + return mw.returnJson(False,'保留数据库名称,不可用!') + is_delta_bool = False + if is_delta == 'yes': + is_delta_bool = True + if is_cover == 'yes': + tables = tables.split(',') + content = sph_make.makeSqlToSphinx(db, tables, is_delta_bool) + mw.writeFile(sph_file,content) + mkdirAll() + return mw.returnJson(True,'设置成功!') + + return mw.returnJson(True,'测试中') + + +# 全量更新 +def updateAll(): + data = sphinxConfParse() + cmd = data['cmd'] + if not 'index' in data: + return '无更新' + index = data['index'] + + for x in range(len(index)): + cmd_index = cmd + ' ' + index[x]['index'] + ' --rotate' + print(cmd_index) + os.system(cmd_index) + return '' + +#增量更新 +def updateDelta(): + data = sphinxConfParse() + cmd = data['cmd'] + if not 'index' in data: + return '无更新' + index = data['index'] + + for x in range(len(index)): + if 'delta' in index[x]: + cmd_index = cmd + ' ' + index[x]['delta'] + ' --rotate' + print(cmd_index) + os.system(cmd_index) + + cmd_index_merge = cmd + ' --merge ' + index[x]['index'] + ' ' + index[x]['delta'] + ' --rotate' + print(cmd_index_merge) + os.system(cmd_index_merge) + else: + print(index[x]['index'],'no delta') + + return '' + +def installPreInspection(version): + data = mw.execShell('arch') + if data[0].strip().startswith('aarch'): + return '不支持aarch架构' + return 'ok' + +if __name__ == "__main__": + version = "3.1.1" + version_pl = getServerDir() + "/version.pl" + if os.path.exists(version_pl): + version = mw.readFile(version_pl).strip() + + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'rebuild': + print(rebuild()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection(version)) + elif func == 'conf': + print(getConf()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'run_log': + print(runLog()) + elif func == 'query_log': + print(queryLog()) + elif func == 'run_status': + print(runStatus()) + elif func == 'sphinx_cmd': + print(sphinxCmd()) + elif func == 'db_to_sphinx': + print(makeDbToSphinx()) + elif func == 'update_all': + print(updateAll()) + elif func == 'update_delta': + print(updateDelta()) + else: + print('error') diff --git a/plugins/sphinx/info.json b/plugins/sphinx/info.json new file mode 100755 index 000000000..b99d3164d --- /dev/null +++ b/plugins/sphinx/info.json @@ -0,0 +1,20 @@ +{ + "sort": 7, + "ps": "简单高效的全文搜索引擎", + "name": "sphinx", + "title": "sphinx", + "shell": "install.sh", + "versions":["3.1.1","3.2.1","3.3.1","3.4.1","3.5.1","3.6.1","3.7.1","3.8.1"], + "tip": "soft", + "install_pre_inspection":true, + "checks": "server/sphinx", + "path": "server/sphinx", + "display": 1, + "author": "midoks", + "date": "2017-04-01", + "home": "http://sphinxsearch.com/", + "doc1": "http://sphinxsearch.com/docs/sphinx3.html", + "doc2": "http://sphinxsearch.com/docs/manual-2.3.2.html", + "type": 0, + "pid": "2" +} \ No newline at end of file diff --git a/plugins/sphinx/init.d/sphinx.service.tpl b/plugins/sphinx/init.d/sphinx.service.tpl new file mode 100644 index 000000000..5524cef02 --- /dev/null +++ b/plugins/sphinx/init.d/sphinx.service.tpl @@ -0,0 +1,12 @@ +[Unit] +Description=Open Source Search Server +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/sphinx/bin/bin/searchd -c {$SERVER_PATH}/sphinx/sphinx.conf +ExecReload=/bin/kill -USR2 $MAINPID +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/sphinx/init.d/sphinx.tpl b/plugins/sphinx/init.d/sphinx.tpl new file mode 100644 index 000000000..8de34003c --- /dev/null +++ b/plugins/sphinx/init.d/sphinx.tpl @@ -0,0 +1,82 @@ +#! /bin/bash +# +# searchd: sphinx Daemon +# +# chkconfig: - 90 25 +# description: sphinx Daemon +# +### BEGIN INIT INFO +# Provides: sphinx +# Required-Start: $syslog +# Required-Stop: $syslog +# Should-Start: $local_fs +# Should-Stop: $local_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: sphinx - Document Index Daemon +# Description: sphinx - Document Index Daemon +### END INIT INFO + +APP_PATH={$SERVER_APP} +APP_CONF={$SERVER_APP}/sphinx.conf +prog="sphinx" + +start () { + echo -n $"Starting $prog: " + ${APP_PATH}/bin/bin/searchd -c ${APP_CONF} + if [ "$?" != 0 ] ; then + echo " failed" + exit 1 + else + echo " done" + fi +} + +rebuild () { + ${APP_PATH}/bin/bin/indexer -c ${APP_CONF} --all --rotate +} + + +stop () { + echo -n $"Stopping $prog: " + if [ ! -e ${APP_PATH}/index/searchd.pid ]; then + echo -n $"$prog is not running." + exit 1 + fi + kill `cat ${APP_PATH}/index/searchd.pid` + if [ "$?" != 0 ] ; then + echo " failed" + exit 1 + else + rm -f ${APP_PATH}/index/searchd.pid + echo " done" + fi +} + +restart () { + $0 stop + sleep 2 + $0 start +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart|reload) + restart + ;; + rebuild) + rebuild + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|reload}" + exit 1 + ;; +esac + +exit $? \ No newline at end of file diff --git a/plugins/sphinx/install.sh b/plugins/sphinx/install.sh new file mode 100755 index 000000000..7e4ee305f --- /dev/null +++ b/plugins/sphinx/install.sh @@ -0,0 +1,171 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sysName=`uname` +sysArch=`arch` + + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/sphinx/index.py rebuild +# cd /www/server/mdserver-web/plugins/sphinx && bash install.sh install 3.6.1 +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/sphinx/index.py db_to_sphinx && /www/server/sphinx/bin/bin/indexer -c /www/server/sphinx/sphinx.conf --all --rotate +# /Users/midoks/Desktop/mwdev/server/sphinx/bin/bin/indexer /Users/midoks/Desktop/mwdev/server/sphinx/sphinx.conf --all --rotate + +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/sphinx/index.py sphinx_cmd + +# /Users/midoks/Desktop/mwdev/server/sphinx/bin/bin/indexer /Users/midoks/Desktop/mwdev/server/sphinx/sphinx.conf --all --rotate + +# cd /www/server/mdserver-web && source bin/activate && python3 plugins/sphinx/index.py start +bash ${rootPath}/scripts/getos.sh +# echo "bash ${rootPath}/scripts/getos.sh" +OSNAME="macos" +if [ -f ${rootPath}/data/osname.pl ];then + OSNAME=`cat ${rootPath}/data/osname.pl` +fi + +if [ "${OSNAME}" == "centos" ] || + [ "${OSNAME}" == "fedora" ] || + [ "${OSNAME}" == "alma" ]; then + yum install -y postgresql-libs unixODBC +fi + +# http://sphinxsearch.com/files/sphinx-3.7.1-da9f8a4-linux-amd64.tar.gz + +VERSION=$2 + +# echo $VERSION + +if [ "$VERSION" == "3.1.1" ];then + VERSION_NUM=${VERSION}-612d99f +elif [ "$VERSION" == "3.2.1" ]; then + VERSION_NUM=${VERSION}-f152e0b +elif [ "$VERSION" == "3.3.1" ]; then + VERSION_NUM=${VERSION}-b72d67b +elif [ "$VERSION" == "3.4.1" ]; then + VERSION_NUM=${VERSION}-efbcc65 +elif [ "$VERSION" == "3.5.1" ]; then + VERSION_NUM=${VERSION}-82c60cb +elif [ "$VERSION" == "3.6.1" ]; then + VERSION_NUM=${VERSION}-c9dbeda +elif [ "$VERSION" == "3.7.1" ]; then + VERSION_NUM=${VERSION}-da9f8a4 +elif [ "$VERSION" == "3.8.1" ]; then + VERSION_NUM=${VERSION}-d25e0bb +fi + +# echo $VERSION_NUM + +Install_sphinx() +{ + echo '正在安装Sphinx...' + mkdir -p $serverPath/sphinx + + SPHINX_DIR=${serverPath}/source/sphinx + mkdir -p $SPHINX_DIR + + SPH_NAME=amd64 + if [ "$sysArch" == "arm64" ];then + SPH_NAME=amd64 + elif [ "$sysArch" == "x86_64" ]; then + SPH_NAME=amd64 + elif [ "$sysArch" == "aarch64" ]; then + SPH_NAME=aarch64 + fi + + if [ "$sysName" == "Darwin" ] && [ "$VERSION" == "3.7.1" ];then + SPH_NAME=aarch64 + fi + + SPH_SYSNAME=linux + if [ $sysName == 'Darwin' ]; then + SPH_SYSNAME=darwin + elif [ "$sysName" == "aarch64" ]; then + SPH_NAME=aarch64 + elif [ "$sysName" == "freebsd" ]; then + SPH_NAME=freebsd + fi + + if [ "$SPH_SYSNAME" == "linux" ];then + glibc_ver=`ldd --version | grep libc | awk -F ')' '{print $2}'|awk '{gsub(/^\s+|\s+$/, "");print}'` + if [ "$VERSION" == "3.7.1" ] && [ `echo "2.29 > $glibc_ver " | bc` -eq 1 ];then + SPH_NAME=${SPH_NAME}-glibc2.17 + fi + if [ "$VERSION" == "3.6.1" ] && [ `echo "2.29 > $glibc_ver " | bc` -eq 1 ];then + SPH_NAME=${SPH_NAME}-glibc2.17 + fi + fi + + + FILE_NAME=sphinx-${VERSION_NUM}-${SPH_SYSNAME}-${SPH_NAME} + FILE_TGZ=${FILE_NAME}.tar.gz + + echo $FILE_TGZ + # curl -sSLo ${SPHINX_DIR}/${FILE_TGZ} http://sphinxsearch.com/files/${FILE_TGZ} + if [ ! -f ${SPHINX_DIR}/${FILE_TGZ} ];then + wget --no-check-certificate -O ${SPHINX_DIR}/${FILE_TGZ} http://sphinxsearch.com/files/${FILE_TGZ} + fi + + cd ${SPHINX_DIR} && tar -zxvf ${FILE_TGZ} + + if [ "$?" == "0" ];then + mkdir -p $SPHINX_DIR + cp -rf ${SPHINX_DIR}/sphinx-${VERSION}/ $serverPath/sphinx/bin + fi + + if [ -d $serverPath/sphinx ];then + echo "${VERSION}" > $serverPath/sphinx/version.pl + echo '安装Sphinx完成' + cd ${rootPath} && python3 ${rootPath}/plugins/sphinx/index.py start + + if [ $sysName != 'Darwin' ]; then + cd ${rootPath} && python3 ${rootPath}/plugins/sphinx/index.py initd_install + fi + fi + + if [ -d ${SPHINX_DIR}/sphinx-${VERSION} ];then + rm -rf ${SPHINX_DIR}/sphinx-${VERSION} + fi +} + +Uninstall_sphinx() +{ + if [ -f /usr/lib/systemd/system/sphinx.service ] || [ -f /lib/systemd/system/sphinx.service ];then + systemctl stop sphinx + systemctl disable sphinx + + if [ -f /usr/lib/systemd/system/sphinx.service ];then + rm -rf /usr/lib/systemd/system/sphinx.service + fi + + if [ -f /lib/systemd/system/sphinx.service ];then + rm -rf /lib/systemd/system/sphinx.service + fi + systemctl daemon-reload + fi + + if [ -f $serverPath/sphinx/initd/sphinx ];then + $serverPath/sphinx/initd/sphinx stop + fi + + if [ -d $serverPath/sphinx ];then + echo "rm -rf $serverPath/sphinx" + rm -rf $serverPath/sphinx + fi + + echo "卸载sphinx成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_sphinx +else + Uninstall_sphinx +fi diff --git a/plugins/sphinx/js/sphinx.js b/plugins/sphinx/js/sphinx.js new file mode 100755 index 000000000..1fd4aa637 --- /dev/null +++ b/plugins/sphinx/js/sphinx.js @@ -0,0 +1,311 @@ +function spPostMin(method, args, callback){ + + var req_data = {}; + req_data['name'] = 'sphinx'; + req_data['func'] = method; + + if (typeof(args) != 'undefined' && args!=''){ + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function myPost(method, args, callback, title){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + $.post('/plugins/run', {name:'mysql', func:method, args:_args}, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function spPost(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + spPostMin(method,args,function(data){ + layer.close(loadT); + if(typeof(callback) == 'function'){ + callback(data); + } + }); +} + +function commonFunc(){ + var con = ''; + con += '   '; + $(".soft-man-con").html(con); +} + +function autoMakeConf(){ + var xm_db_list; + + var con = '
                                  '; + con += '
                                • 如果数据量比较大,第一次启动会失败!(可通过手动建立索引)
                                • '; + con += '
                                • 以下内容,需手动加入计划任务。
                                • '; + layer.open({ + type: 1, + area: ['380px','350px'], + title: '自动创建配置', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["提交","关闭"], + content: "
                                  \ +
                                  \ + 选择数据库\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + 选择表\ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + 是否增量\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + 是否覆盖配置\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                    \ +
                                  • 具体配置,仍须手动修改!!!
                                  • \ +
                                  • 增量索引,需要有更新权限,主从分离时,需要主库配置
                                  • \ +
                                  \ +
                                  \ + ", + + success:function(l,i){ + $(l).find('.layui-layer-content').css('overflow','visible'); + + xm_db_list = xmSelect.render({ + el: '#table', + repeat: false, + toolbar: {show: true}, + data: [], + }); + + myPost('get_db_list', {"page":1,"page_size":20}, function(data){ + var rdata = $.parseJSON(data.data); + var dblist = rdata.data; + + var db_html = ''; + for (var i = 0; i < dblist.length; i++) { + db_html += ""; + } + + if (dblist.length > 0){ + initDbSelect(dblist[0]['name']); + } + $('select[name="dbname"]').html(db_html); + }); + + $('select[name="dbname"]').change(function(){ + var db = $('select[name="dbname"]').val(); + initDbSelect(db); + }); + + }, + yes:function(index){ + var args = {} + args['db'] = $('select[name="dbname"]').val(); + args['is_delta'] = $('select[name="is_delta"]').val(); + args['is_cover'] = $('select[name="is_cover"]').val(); + args['tables'] = xm_db_list.getValue('value').join(','); + // console.log(args); + spPost('db_to_sphinx', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + // console.log(rdata); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(index); + confirmRebuildIndex(); + } + },{icon: rdata.status ? 1 : 2}, 2000); + }); + } + + }); + + function initDbSelect(db){ + if (db == ''){ + return; + } + getDbInfo(db, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var tables = rdata.tables; + + var idx_db = []; + for (var i = 0; i < tables.length; i++) { + var t = {}; + t['name'] = tables[i]['table_name']; + t['value'] = tables[i]['table_name']; + idx_db.push(t); + } + xm_db_list = xmSelect.render({el: '#table', filterable: true,repeat: false,toolbar: {show: true},data: idx_db,}); + }); + } + + function getDbInfo(db_name, callback){ + myPost('get_db_info', {name:db_name}, function(data){ + callback(data); + }); + } +} + +function rebuildIndex(){ + spPost('rebuild', '', function(data){ + if (data.data == 'ok'){ + layer.msg('重建成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg(data.data,{icon:2,time:10000,shade: [0.3, '#000']}); + } + }); +} + +function confirmRebuildIndex(){ + layer.confirm("是否重建索引?", {icon:3,closeBtn: 1} , function(){ + rebuildIndex(); + }); +} + + +function tryRebuildIndex(){ + layer.confirm("修改配置后,是否尝试重建索引!", {icon:3,closeBtn: 1} , function(){ + rebuildIndex(); + }); +} + + +function secToTime(s) { + var t; + if(s > -1){ + var hour = Math.floor(s/3600); + var min = Math.floor(s/60) % 60; + var sec = s % 60; + if(hour < 10) { + t = '0'+ hour + ":"; + } else { + t = hour + ":"; + } + + if(min < 10){t += "0";} + t += min + ":"; + if(sec < 10){t += "0";} + t += sec.toFixed(2); + } + return t; +} + + +function runStatus(){ + spPost('run_status', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata['status']){ + layer.msg(rdata['msg'],{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + var idata = rdata.data; + + var tbody = ''; + for (var i in idata) { + tbody += ''+i+'' + idata[i] + ''+i+''; + } + + var con = '
                                  \ + \ + \ + \ + \ + \ + \ +
                                  运行时间' + secToTime(idata.uptime) + '每秒查询' + parseInt(parseInt(idata.queries) / parseInt(idata.uptime)) + '
                                  总连接次数' + idata.connections + 'work_queue_length' +idata.work_queue_length + '
                                  agent_connect' + idata.agent_connect+ 'workers_active' + idata.workers_active + '
                                  agent_retry' + idata.agent_retry + 'workers_total' + idata.workers_total + '
                                  \ + \ + \ + \ + '+tbody+'\ + \ +
                                  \ +
                                  '; + + $(".soft-man-con").html(con); + }); +} + +function readme(){ + spPost('sphinx_cmd', '', function(data){ + + var rdata = $.parseJSON(data.data); + if (!rdata['status']){ + layer.msg(rdata['msg'],{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + + // console.log(rdata['data']); + var con = '
                                    '; + + con += '
                                  • 如果数据量比较大,第一次启动会失败!(可通过手动建立索引)
                                  • '; + con += '
                                  • 以下内容,需手动加入计划任务。
                                  • '; + + con += '
                                  • 全量:' + rdata['data']['cmd'] + ' --all --rotate
                                  • '; + + //主索引 + for (var i = 0; i < rdata['data']['index'].length; i++) { + var index_kv = rdata['data']['index'][i]; + var index = index_kv['index']; + // console.log(index); + con += '
                                  • 主索引 :' + rdata['data']['cmd'] + ' '+ index +' --rotate
                                  • '; + if (typeof(index_kv['delta']) != 'undefined'){ + var delta = index_kv['delta']; + con += '
                                  • 增量索引 :' + rdata['data']['cmd'] + ' '+ delta +' --rotate
                                  • '; + con += '
                                  • 合并索引 :' + rdata['data']['cmd'] + ' --merge '+ index + ' ' + delta +' --rotate
                                  • '; + } + } + con += '
                                  '; + + $(".soft-man-con").html(con); + }); + +} + diff --git a/plugins/sphinx/tool_cron.py b/plugins/sphinx/tool_cron.py new file mode 100644 index 000000000..7f7aeccc7 --- /dev/null +++ b/plugins/sphinx/tool_cron.py @@ -0,0 +1,211 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'sphinx' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getTaskConf(): + conf = getServerDir() + "/cron_config.json" + return conf + +def getTaskDeltaConf(): + conf = getServerDir() + "/cron_delta_config.json" + return conf + +def getConfigData(): + conf = getTaskConf() + if os.path.exists(conf): + return json.loads(mw.readFile(getTaskConf())) + return { + "task_id": -1, + "period": "day-n", + "where1": "1", + "hour": "0", + "minute": "15", + } + +def getConfigDeltaData(): + conf = getTaskDeltaConf() + if os.path.exists(conf): + return json.loads(mw.readFile(getTaskDeltaConf())) + return { + "task_id": -1, + "period": "minute-n", + "where1": "3", + "hour": "0", + "minute": "0", + } + + +def createBgTask(): + removeBgTask() + removeDeltaBgTask() + + createBgTaskByName(getPluginName()) + createBgTaskDeltaByName(getPluginName()) + return True + + +def createBgTaskByName(name): + args = getConfigData() + _name = "[勿删]Sphinx全量更新[" + name + "]" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in args and args["task_id"] > 0: + res = mw.M("crontab").field("id, name").where("id=?", (args["task_id"],)).find() + if res and res["id"] == args["task_id"]: + print("计划任务已经存在!") + return True + + mw_dir = mw.getPanelDir() + cmd = ''' +mw_dir=%s +rname=%s +plugin_path=%s +script_path=%s +logs_file=$plugin_path/${rname}.log +''' % (mw_dir, name, getServerDir(), getPluginDir()) + cmd += 'echo "★【`date +"%Y-%m-%d %H:%M:%S"`】 STSRT★" >> $logs_file' + "\n" + cmd += 'echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" >> $logs_file' + "\n" + cmd += 'echo "python3 $script_path/index.py update_all"' + "\n" + cmd += 'cd $mw_dir && python3 $script_path/index.py update_all' + "\n" + cmd += 'echo "【`date +"%Y-%m-%d %H:%M:%S"`】 END★" >> $logs_file' + "\n" + cmd += 'echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" >> $logs_file' + "\n" + + params = { + 'name': _name, + 'type': args['period'], + 'week': "", + 'where1': args['where1'], + 'hour': args['hour'], + 'minute': args['minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + args["task_id"] = task_id + args["name"] = name + mw.writeFile(getTaskConf(), json.dumps(args)) + +def createBgTaskDeltaByName(name): + args = getConfigDeltaData() + _name = "[勿删]Sphinx增量更新[" + name + "]" + res = mw.M("crontab").field("id, name").where("name=?", (_name,)).find() + if res: + return True + + if "task_id" in args and args["task_id"] > 0: + res = mw.M("crontab").field("id, name").where("id=?", (args["task_id"],)).find() + if res and res["id"] == args["task_id"]: + print("计划任务已经存在!") + return True + + mw_dir = mw.getPanelDir() + cmd = ''' +mw_dir=%s +rname=%s +plugin_path=%s +script_path=%s +logs_file=$plugin_path/${rname}.log +''' % (mw_dir, name, getServerDir(), getPluginDir()) + cmd += 'echo "★【`date +"%Y-%m-%d %H:%M:%S"`】 STSRT★" >> $logs_file' + "\n" + cmd += 'echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" >> $logs_file' + "\n" + cmd += 'echo "python3 $script_path/index.py update_delta"' + "\n" + cmd += 'cd $mw_dir && python3 $script_path/index.py update_delta' + "\n" + cmd += 'echo "【`date +"%Y-%m-%d %H:%M:%S"`】 END★" >> $logs_file' + "\n" + cmd += 'echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" >> $logs_file' + "\n" + + params = { + 'name': _name, + 'type': args['period'], + 'week': "", + 'where1': args['where1'], + 'hour': args['hour'], + 'minute': args['minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + args["task_id"] = task_id + args["name"] = name + mw.writeFile(getTaskConf(), json.dumps(args)) + + +def removeBgTask(): + cfg = getConfigData() + if "task_id" in cfg and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data['status']: + cfg["task_id"] = -1 + mw.writeFile(getTaskConf(), json.dumps(cfg)) + return True + return False + +def removeDeltaBgTask(): + cfg = getConfigDeltaData() + if "task_id" in cfg and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data['status']: + cfg["task_id"] = -1 + mw.writeFile(getTaskDeltaConf(), json.dumps(cfg)) + return True + return False + + +if __name__ == "__main__": + if len(sys.argv) > 1: + action = sys.argv[1] + if action == "remove": + removeBgTask() + removeDeltaBgTask() + elif action == "add": + createBgTask() diff --git a/plugins/sphinx/tpl/discuz.conf b/plugins/sphinx/tpl/discuz.conf new file mode 100644 index 000000000..ab2ba35fe --- /dev/null +++ b/plugins/sphinx/tpl/discuz.conf @@ -0,0 +1,127 @@ +# +# Discuz Sphinx Config File +# + +indexer +{ + mem_limit = 32M + write_buffer = 64M +} + +searchd +{ + listen = 9312 + listen = 9306:mysql41 + log = {$SERVER_APP}/index/searchd.log + query_log = {$SERVER_APP}/index/query.log + read_timeout = 5 + max_children = 0 + pid_file = {$SERVER_APP}/index/searchd.pid + seamless_rotate = 1 + preopen_indexes = 1 + unlink_old = 1 + #workers = threads # for RT to work + binlog_path = {$SERVER_APP}/index/binlog +} + + +source pre_forum_thread +{ + type = mysql + sql_host = 127.0.0.1 + sql_user = bbs + sql_pass = bbs + sql_db = bbs + sql_port = 3306 + sql_query_pre = SET NAMES UTF8 + sql_query_pre = SET SESSION query_cache_type=OFF + sql_query_pre = REPLACE INTO bbs_common_sphinxcounter SELECT 1, MAX(tid) FROM bbs_forum_thread + sql_query = SELECT t.tid as id,t.tid,t.subject,t.digest,t.displayorder,t.authorid,t.lastpost,t.special \ + FROM bbs_forum_thread AS t WHERE t.tid>=$start AND t.tid<=$end + sql_query_range = SELECT (SELECT MIN(tid) FROM bbs_forum_thread),maxid FROM bbs_common_sphinxcounter WHERE indexid=1 + + sql_range_step = 5000 + sql_attr_uint = tid + + sql_attr_uint = digest + sql_attr_uint = displayorder + sql_attr_uint = authorid + sql_attr_uint = special + sql_attr_timestamp =lastpost +} + +index pre_forum_thread +{ + source = pre_forum_thread + path = {$SERVER_APP}/index/db/pre_forum_thread/index + + min_word_len = 2 + html_strip = 1 + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F +} + + +# threads_minute +source pre_forum_thread_minute : pre_forum_thread +{ + sql_query_pre = SET NAMES UTF8 + sql_query_pre = SET SESSION query_cache_type=OFF + sql_query_range = SELECT maxid-1,(SELECT MAX(tid) FROM bbs_forum_thread) FROM bbs_common_sphinxcounter WHERE indexid=1 +} + +# threads_minute +index pre_forum_thread_minute : pre_forum_thread +{ + source = pre_forum_thread_minute + path = {$SERVER_APP}/index/db/pre_forum_thread/pre_forum_thread_minute +} + +#posts +source pre_forum_post : pre_forum_thread +{ + type = mysql + sql_query_pre = + sql_query_pre = SET NAMES UTF8 + sql_query_pre = SET SESSION query_cache_type=OFF + sql_query_pre = REPLACE INTO bbs_common_sphinxcounter SELECT 2, MAX(pid) FROM bbs_forum_post + sql_query = SELECT p.pid AS id,p.tid,p.subject,p.message,t.digest,t.displayorder,t.authorid,t.lastpost,t.special \ + FROM bbs_forum_post AS p LEFT JOIN bbs_forum_thread AS t USING(tid) where p.pid >=$start and p.pid <=$end \ + AND p.first=1 + sql_query_range = SELECT (SELECT MIN(pid) FROM bbs_forum_post),maxid FROM bbs_common_sphinxcounter WHERE indexid=2 + sql_range_step = 5000 + sql_attr_uint = tid + sql_attr_uint = digest + sql_attr_uint= displayorder + sql_attr_uint = authorid + sql_attr_uint = special + sql_attr_timestamp = lastpost +} + +#posts +index pre_forum_post +{ + source = pre_forum_post + path = {$SERVER_APP}/index/db/pre_forum_thread/pre_forum_post + + min_word_len = 2 + html_strip = 1 + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F +} + +#pre_forum_post_minute +source pre_forum_post_minute : pre_forum_post +{ + sql_query_pre = SET NAMES UTF8 + sql_query_pre = SET SESSION query_cache_type=OFF + sql_query_range = SELECT maxid-1,(SELECT MAX(pid) FROM bbs_forum_post) FROM bbs_common_sphinxcounter WHERE indexid=2 +} + +#pre_forum_post_minute +index pre_forum_post_minute : pre_forum_post +{ + source = pre_forum_thread + path = {$SERVER_APP}/index/db/pre_forum_thread/pre_forum_post_minute +} + diff --git a/plugins/sphinx/tpl/maccms.conf b/plugins/sphinx/tpl/maccms.conf new file mode 100644 index 000000000..ad70ae6f8 --- /dev/null +++ b/plugins/sphinx/tpl/maccms.conf @@ -0,0 +1,95 @@ +# +# Maccms Sphinx Config File +# + +# 创建统计表 +# CREATE TABLE `sph_counter` ( `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT, `counter_id` bigint(20) unsigned NOT NULL,`max_doc_id` bigint(20) unsigned NOT NULL,PRIMARY KEY (`id`)) + +indexer +{ + mem_limit = 32M + write_buffer = 64M +} + +searchd +{ + listen = 9312 + listen = 9306:mysql41 + log = {$SERVER_APP}/index/searchd.log + query_log = {$SERVER_APP}/index/query.log + read_timeout = 5 + max_children = 0 + pid_file = {$SERVER_APP}/index/searchd.pid + seamless_rotate = 1 + preopen_indexes = 1 + unlink_old = 1 + #workers = threads # for RT to work + binlog_path = {$SERVER_APP}/index/binlog +} + + +# -------- maccms 全量更新 start -------- + +source mac_vod +{ + type = mysql + sql_host = 127.0.0.1 + sql_user = maccms + sql_pass = maccms + sql_db = maccms + sql_port = 3306 + sql_query_range = SELECT min(vod_id), max(vod_id) FROM mac_vod + sql_query_pre = SET NAMES UTF8 + sql_query_pre = SET SESSION query_cache_type=OFF + sql_query = SELECT vod_id as id, vod_name, vod_tag, vod_class, vod_actor, vod_director, vod_time,vod_time_add FROM mac_vod WHERE vod_status=1 and vod_id>=$start AND vod_id<=$end + sql_range_step = 1000 + + + sql_attr_uint = vod_id + sql_attr_timestamp = vod_time + sql_attr_timestamp = vod_time_add + sql_field_string = vod_name + + sql_query_post = UPDATE sph_counter SET max_doc_id=(SELECT MAX(vod_id) FROM mac_vod) where counter_id=1 +} + +index mac_vod +{ + source = mac_vod + path = {$SERVER_APP}/index/db/maccms/index + + min_word_len = 2 + html_strip = 1 + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F +} + +# -------- maccms 全量更新 end -------- + + +# -------- maccms 增量更新 start -------- + + +source mac_vod_delta : mac_vod +{ + sql_query_pre = SET NAMES utf8 + + sql_query_range = SELECT (SELECT max_doc_id FROM `sph_counter` where counter_id=1) as min, (SELECT max(vod_id) FROM mac_vod) as max + + sql_query = SELECT vod_id as id, vod_name, vod_tag, vod_class, vod_actor, vod_director, vod_time,vod_time_add FROM mac_vod WHERE vod_status=1 and vod_id>=$start AND vod_id<=$end + + sql_query_post = UPDATE sph_counter SET max_doc_id=(SELECT MAX(vod_id) FROM mac_vod) where counter_id=1 +} + +index mac_vod_delta : mac_vod +{ + source = mac_vod_delta + path = {$SERVER_APP}/index/db/maccms_delta/index + + min_word_len = 2 + html_strip = 1 + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F +} + +# -------- maccms 增量更新 end -------- \ No newline at end of file diff --git a/plugins/sphinx/tpl/none.conf b/plugins/sphinx/tpl/none.conf new file mode 100755 index 000000000..de9e5f270 --- /dev/null +++ b/plugins/sphinx/tpl/none.conf @@ -0,0 +1,28 @@ +# +# Minimal Sphinx configuration sample (clean, simple, functional) +# + + +searchd +{ + listen = 9312 + listen = 9306:mysql41 + log = {$SERVER_APP}/index/searchd.log + query_log = {$SERVER_APP}/index/query.log + pid_file = {$SERVER_APP}/index/searchd.pid + #workers = threads # for RT to work + binlog_path = {$SERVER_APP}/index/binlog + read_timeout = 5 + max_children = 0 + seamless_rotate = 1 + preopen_indexes = 1 + unlink_old = 1 +} + +index mydocs +{ + type = rt + path = {$SERVER_APP}/bin/doc + rt_field = title + rt_attr_json = j +} \ No newline at end of file diff --git a/plugins/sphinx/tpl/qbittorrent.conf b/plugins/sphinx/tpl/qbittorrent.conf new file mode 100755 index 000000000..46f61331d --- /dev/null +++ b/plugins/sphinx/tpl/qbittorrent.conf @@ -0,0 +1,54 @@ +# +# Minimal Sphinx configuration sample (clean, simple, functional) +# + +indexer +{ + mem_limit = 32M +} + +searchd +{ + listen = 9312 + listen = 9306:mysql41 + log = {$SERVER_APP}/index/searchd.log + query_log = {$SERVER_APP}/index/query.log + read_timeout = 5 + max_children = 0 + pid_file = {$SERVER_APP}/index/searchd.pid + seamless_rotate = 1 + preopen_indexes = 1 + unlink_old = 1 + #workers = threads # for RT to work + binlog_path = {$SERVER_APP}/binlog +} + +source qbittorrent +{ + type = mysql + + sql_host = 127.0.0.1 + sql_user = qbittorrent + sql_pass = qbittorrent + sql_db = qbittorrent + sql_port = 3306 # optional, default is 3306 + + sql_query_range = SELECT min(id), max(id) FROM pl_hash_list + sql_range_step = 1000 + + sql_query_pre = SET NAMES utf8 + sql_query = SELECT id, name, UNIX_TIMESTAMP(create_time) AS create_time \ + FROM pl_hash_list where status=0 and id >= $start AND id <= $end + + sql_attr_timestamp = create_time +} + + +index qbittorrent +{ + source = qbittorrent + path = {$SERVER_APP}/index/db/qbittorrent/index + + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F +} diff --git a/plugins/sphinx/tpl/simdht.conf b/plugins/sphinx/tpl/simdht.conf new file mode 100755 index 000000000..51d5b26ea --- /dev/null +++ b/plugins/sphinx/tpl/simdht.conf @@ -0,0 +1,59 @@ +# +# Minimal Sphinx configuration sample (clean, simple, functional) +# + +indexer +{ + mem_limit = 32M +} + +searchd +{ + listen = 9312 + listen = 9306:mysql41 + log = {$SERVER_APP}/index/searchd.log + query_log = {$SERVER_APP}/index/query.log + read_timeout = 5 + max_children = 0 + pid_file = {$SERVER_APP}/index/searchd.pid + seamless_rotate = 1 + preopen_indexes = 1 + unlink_old = 1 + #workers = threads # for RT to work + binlog_path = {$SERVER_APP}/index/binlog +} + +source search_hash +{ + type = mysql + + sql_host = 127.0.0.1 + sql_user = ssbc + sql_pass = ssbc + sql_db = ssbc + sql_port = 3306 # optional, default is 3306 + + sql_query_range = SELECT min(id), max(id) FROM search_hash + sql_range_step = 1000 + + sql_query_pre = SET NAMES utf8 + sql_query = \ + SELECT id, name, CRC32(category) AS category, length, UNIX_TIMESTAMP(create_time) AS create_time, UNIX_TIMESTAMP(last_seen) AS last_seen\ + FROM search_hash where id >= $start AND id <= $end AND is_has=1 + + sql_attr_bigint = length + sql_attr_timestamp = create_time + sql_attr_timestamp = last_seen + sql_attr_uint = category + +} + + +index search_hash +{ + source = search_hash + path = {$SERVER_APP}/index/db/search_hash/index + + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F +} diff --git a/plugins/sphinx/tpl/simdht_delta.conf b/plugins/sphinx/tpl/simdht_delta.conf new file mode 100755 index 000000000..e20eed842 --- /dev/null +++ b/plugins/sphinx/tpl/simdht_delta.conf @@ -0,0 +1,79 @@ +# +# Minimal Sphinx configuration sample (clean, simple, functional) +# + +indexer +{ + mem_limit = 128M +} + +searchd +{ + listen = 9312 + listen = 9306:mysql41 + log = {$SERVER_APP}/index/searchd.log + query_log = {$SERVER_APP}/index/query.log + read_timeout = 5 + max_children = 0 + pid_file = {$SERVER_APP}/index/searchd.pid + seamless_rotate = 1 + preopen_indexes = 1 + unlink_old = 1 + #workers = threads # for RT to work + binlog_path = {$SERVER_APP}/index/binlog +} + +source search_hash +{ + type = mysql + + sql_host = 127.0.0.1 + sql_user = ssbc + sql_pass = ssbc + sql_db = ssbc + sql_port = 3306 # optional, default is 3306 + + sql_query_pre = UPDATE sph_counter SET max_doc_id=(SELECT MAX(id) FROM search_hash) where counter_id=1 + + sql_query_range = SELECT min(id), max(id) FROM search_hash + sql_range_step = 1000 + + sql_query_pre = SET NAMES utf8 + sql_query = SELECT * from ( SELECT s1.id id, s1.name name, s1.category category, s1.length length, UNIX_TIMESTAMP(s1.create_time) as create_time, UNIX_TIMESTAMP(s1.last_seen) as last_seen, s2.file_list file_list, s1.info_hash info_hash FROM search_hash s1 left join search_filelist s2 on s1.info_hash=s2.info_hash where s1.is_has=1 and s1.id >= $start AND s1.id <= $end ) as s WHERE s.file_list is not null + + sql_attr_bigint = length + sql_attr_timestamp = create_time + sql_attr_timestamp = last_seen + sql_attr_uint = category +} + +source search_hash_delta : search_hash +{ + sql_query_range = SELECT (SELECT max_doc_id FROM `sph_counter` where counter_id=1) as min, (SELECT max(id) FROM search_hash) as max + sql_query_pre = SET NAMES utf8 + sql_query = SELECT * from ( SELECT s1.id id, s1.name name, s1.category category, s1.length length, UNIX_TIMESTAMP(s1.create_time) as create_time, UNIX_TIMESTAMP(s1.last_seen) as last_seen, s2.file_list file_list, s1.info_hash info_hash FROM search_hash s1 left join search_filelist s2 on s1.info_hash=s2.info_hash where s1.is_has=1 and s1.id >= $start AND s1.id <= $end ) as s WHERE s.file_list is not null + sql_attr_bigint = length + sql_attr_timestamp = create_time + sql_attr_timestamp = last_seen + sql_attr_uint = category + sql_query_post = UPDATE sph_counter SET max_doc_id=(SELECT MAX(id) FROM search_hash) where counter_id=1 +} + + +index search_hash +{ + source = search_hash + path = {$SERVER_APP}/index/db/search_hash/index + + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F +} + +index search_hash_delta : search_hash +{ + source = search_hash_delta + path = {$SERVER_APP}/index/db/search_hash_delta/index + + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F +} diff --git a/plugins/sphinx/tpl/video.conf b/plugins/sphinx/tpl/video.conf new file mode 100755 index 000000000..2a93c22e5 --- /dev/null +++ b/plugins/sphinx/tpl/video.conf @@ -0,0 +1,58 @@ +# +# Minimal Sphinx configuration sample (clean, simple, functional) +# + +indexer +{ + mem_limit = 32M +} + +searchd +{ + listen = 9312 + listen = 9306:mysql41 + log = {$SERVER_APP}/index/searchd.log + query_log = {$SERVER_APP}/index/query.log + read_timeout = 5 + max_children = 0 + pid_file = {$SERVER_APP}/index/searchd.pid + seamless_rotate = 1 + preopen_indexes = 1 + unlink_old = 1 + #workers = threads # for RT to work + binlog_path = {$SERVER_APP}/binlog +} + +source sp_video +{ + type = mysql + + sql_host = 127.0.0.1 + sql_user = video_spider + sql_pass = video_spider + sql_db = video_spider + sql_port = 3306 # optional, default is 3306 + + sql_query_range = SELECT min(id), max(id) FROM sp_video + sql_range_step = 1000 + + sql_query_pre = SET NAMES utf8 + sql_query = SELECT id, name, UNIX_TIMESTAMP(create_time) AS create_time, UNIX_TIMESTAMP(update_time) AS update_time \ + FROM sp_video where id >= $start AND id <= $end + + sql_attr_bigint = length + sql_attr_timestamp = create_time + sql_attr_timestamp = update_time + sql_attr_uint = category + +} + + +index sp_video +{ + source = sp_video + path = {$SERVER_APP}/index/db/sp_video/index + + ngram_len = 1 + ngram_chars = U+3000..U+2FA1F +} diff --git a/plugins/supervisor/conf/supervisor.conf b/plugins/supervisor/conf/supervisor.conf new file mode 100644 index 000000000..d8df8fdd2 --- /dev/null +++ b/plugins/supervisor/conf/supervisor.conf @@ -0,0 +1,170 @@ +; Sample supervisor config file. +; +; For more information on the config file, please see: +; http://supervisord.org/configuration.html +; +; Notes: +; - Shell expansion ("~" or "$HOME") is not supported. Environment +; variables can be expanded using this syntax: "%(ENV_HOME)s". +; - Quotes around values are not supported, except in the case of +; the environment= options as shown below. +; - Comments must have a leading space: "a=b ;comment" not "a=b;comment". +; - Command will be truncated if it looks like a config file comment, e.g. +; "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ". +; +; Warning: +; Paths throughout this example file use /tmp because it is available on most +; systems. You will likely need to change these to locations more appropriate +; for your system. Some systems periodically delete older files in /tmp. +; Notably, if the socket file defined in the [unix_http_server] section below +; is deleted, supervisorctl will be unable to connect to supervisord. + +[unix_http_server] +file={$SERVER_PATH}/supervisor/run/supervisor.sock +;chmod=0700 ; socket file mode (default 0700) +;chown=nobody:nogroup ; socket file uid:gid owner +;username=user ; default is no username (open server) +;password=123 ; default is no password (open server) + +; Security Warning: +; The inet HTTP server is not enabled by default. The inet HTTP server is +; enabled by uncommenting the [inet_http_server] section below. The inet +; HTTP server is intended for use within a trusted environment only. It +; should only be bound to localhost or only accessible from within an +; isolated, trusted network. The inet HTTP server does not support any +; form of encryption. The inet HTTP server does not use authentication +; by default (see the username= and password= options to add authentication). +; Never expose the inet HTTP server to the public internet. + +;[inet_http_server] ; inet (TCP) server disabled by default +;port=127.0.0.1:9001 ; ip_address:port specifier, *:port for all iface +;username=user ; default is no username (open server) +;password=123 ; default is no password (open server) + +[supervisord] +logfile={$SERVER_PATH}/supervisor/log/supervisor.log +logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB +logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 +loglevel=info ; log level; default info; others: debug,warn,trace +pidfile={$SERVER_PATH}/supervisor/run/supervisor.pid +nodaemon=false ; start in foreground if true; default false +silent=false ; no logs to stdout if true; default false +minfds=1024 ; min. avail startup file descriptors; default 1024 +minprocs=200 ; min. avail process descriptors;default 200 +;umask=022 ; process file creation umask; default 022 +user={$OS_USER} ; setuid to this UNIX account at startup; recommended if root +;identifier=supervisor ; supervisord identifier, default is 'supervisor' +;directory=/tmp ; default is not to cd during start +;nocleanup=true ; don't clean up tempfiles at start; default false +;childlogdir=/tmp ; 'AUTO' child log dir, default $TEMP +;environment=KEY="value" ; key value pairs to add to environment +;strip_ansi=false ; strip ansi escape codes in logs; def. false + +; The rpcinterface:supervisor section must remain in the config file for +; RPC (supervisorctl/web interface) to work. Additional interfaces may be +; added by defining them in separate [rpcinterface:x] sections. + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +; The supervisorctl section configures how supervisorctl will connect to +; supervisord. configure it match the settings in either the unix_http_server +; or inet_http_server section. + +[supervisorctl] +serverurl=unix:///{$SERVER_PATH}/supervisor/run/supervisor.sock +;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket +;username=chris ; should be same as in [*_http_server] if set +;password=123 ; should be same as in [*_http_server] if set +;prompt=mysupervisor ; cmd line prompt (default "supervisor") +;history_file=~/.sc_history ; use readline history if available + +; The sample program section below shows all possible program subsection values. +; Create one or more 'real' program: sections to be able to control them under +; supervisor. + +;[program:theprogramname] +;command=/bin/cat ; the program (relative uses PATH, can take args) +;process_name=%(program_name)s ; process_name expr (default %(program_name)s) +;numprocs=1 ; number of processes copies to start (def 1) +;directory=/tmp ; directory to cwd to before exec (def no cwd) +;umask=022 ; umask for process (default None) +;priority=999 ; the relative start priority (default 999) +;autostart=true ; start at supervisord start (default: true) +;startsecs=1 ; # of secs prog must stay up to be running (def. 1) +;startretries=3 ; max # of serial start failures when starting (default 3) +;autorestart=unexpected ; when to restart if exited after running (def: unexpected) +;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0) +;stopsignal=QUIT ; signal used to kill process (default TERM) +;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) +;stopasgroup=false ; send stop signal to the UNIX process group (default false) +;killasgroup=false ; SIGKILL the UNIX process group (def false) +;user=chrism ; setuid to this UNIX account to run the program +;redirect_stderr=true ; redirect proc stderr to stdout (default false) +;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO +;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) +;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) +;stdout_events_enabled=false ; emit events on stdout writes (default false) +;stdout_syslog=false ; send stdout to syslog with process name (default false) +;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO +;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) +;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) +;stderr_events_enabled=false ; emit events on stderr writes (default false) +;stderr_syslog=false ; send stderr to syslog with process name (default false) +;environment=A="1",B="2" ; process environment additions (def no adds) +;serverurl=AUTO ; override serverurl computation (childutils) + +; The sample eventlistener section below shows all possible eventlistener +; subsection values. Create one or more 'real' eventlistener: sections to be +; able to handle event notifications sent by supervisord. + +;[eventlistener:theeventlistenername] +;command=/bin/eventlistener ; the program (relative uses PATH, can take args) +;process_name=%(program_name)s ; process_name expr (default %(program_name)s) +;numprocs=1 ; number of processes copies to start (def 1) +;events=EVENT ; event notif. types to subscribe to (req'd) +;buffer_size=10 ; event buffer queue size (default 10) +;directory=/tmp ; directory to cwd to before exec (def no cwd) +;umask=022 ; umask for process (default None) +;priority=-1 ; the relative start priority (default -1) +;autostart=true ; start at supervisord start (default: true) +;startsecs=1 ; # of secs prog must stay up to be running (def. 1) +;startretries=3 ; max # of serial start failures when starting (default 3) +;autorestart=unexpected ; autorestart if exited after running (def: unexpected) +;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0) +;stopsignal=QUIT ; signal used to kill process (default TERM) +;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) +;stopasgroup=false ; send stop signal to the UNIX process group (default false) +;killasgroup=false ; SIGKILL the UNIX process group (def false) +;user=chrism ; setuid to this UNIX account to run the program +;redirect_stderr=false ; redirect_stderr=true is not allowed for eventlisteners +;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO +;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) +;stdout_events_enabled=false ; emit events on stdout writes (default false) +;stdout_syslog=false ; send stdout to syslog with process name (default false) +;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO +;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) +;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) +;stderr_events_enabled=false ; emit events on stderr writes (default false) +;stderr_syslog=false ; send stderr to syslog with process name (default false) +;environment=A="1",B="2" ; process environment additions +;serverurl=AUTO ; override serverurl computation (childutils) + +; The sample group section below shows all possible group values. Create one +; or more 'real' group: sections to create "heterogeneous" process groups. + +;[group:thegroupname] +;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions +;priority=999 ; the relative start priority (default 999) + +; The [include] section can just contain the "files" setting. This +; setting can list multiple files (separated by whitespace or +; newlines). It can also contain wildcards. The filenames are +; interpreted as relative to this file. Included files *cannot* +; include files themselves. + +[include] +files = {$SERVER_PATH}/supervisor/conf.d/*.ini diff --git a/plugins/supervisor/ico.png b/plugins/supervisor/ico.png new file mode 100644 index 000000000..229e38752 Binary files /dev/null and b/plugins/supervisor/ico.png differ diff --git a/plugins/supervisor/index.html b/plugins/supervisor/index.html new file mode 100755 index 000000000..4300e73a2 --- /dev/null +++ b/plugins/supervisor/index.html @@ -0,0 +1,31 @@ +
                                  +
                                  +
                                  +
                                  +

                                  服务

                                  +

                                  自启动

                                  +

                                  进程管理

                                  +

                                  主配置

                                  +

                                  主日志

                                  +

                                  子配置

                                  + +

                                  子日志

                                  +
                                  +
                                  +
                                  +
                                  +
                                  + +
                                  + + + + \ No newline at end of file diff --git a/plugins/supervisor/index.py b/plugins/supervisor/index.py new file mode 100755 index 000000000..1a9d69c6a --- /dev/null +++ b/plugins/supervisor/index.py @@ -0,0 +1,652 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'supervisor' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/supervisor.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + "/conf/supervisor.conf" + return path + + +def getSubConfDir(): + return getServerDir() + "/conf.d" + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':', 1) + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + data = mw.execShell( + "ps -ef|grep supervisor | grep -v grep | grep -v index.py | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + + +def initDreplace(): + + confD = getServerDir() + "/conf.d" + conf = getServerDir() + "/supervisor.conf" + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/supervisor.service' + systemServiceTpl = getPluginDir() + '/init.d/supervisor.service' + + service_path = mw.getServerDir() + + if not os.path.exists(confD): + os.mkdir(confD) + + if not os.path.exists(conf): + # config replace + user = 'root' + if mw.isAppleSystem(): + cmd = "who | sed -n '2, 1p' |awk '{print $1}'" + user = mw.execShell(cmd)[0].strip() + + conf_content = mw.readFile(getConfTpl()) + conf_content = conf_content.replace('{$SERVER_PATH}', service_path) + conf_content = conf_content.replace('{$OS_USER}', user) + mw.writeFile(conf, conf_content) + + if os.path.exists(systemDir) and not os.path.exists(systemService): + activate_file = mw.getPanelDir() + '/bin/activate' + if os.path.exists(activate_file): + supervisord_bin = mw.execShell( + 'source ' + activate_file + '&& which supervisord')[0].strip() + else: + supervisord_bin = mw.execShell('which supervisord')[0].strip() + + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + se_content = se_content.replace('{$SUP_BIN}', supervisord_bin) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return True + + +def supOp(method): + initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' supervisor') + if data[1] == '': + return 'ok' + return data[1] + + if method in ('reload', 'restart'): + return 'ok' + + cmd = 'supervisord -c ' + getServerDir() + '/supervisor.conf' + if method == 'stop': + cmd = "ps -ef|grep supervisor | grep -v grep | grep -v index.py | awk '{print $2}'|xargs kill" + data = mw.execShell(cmd) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return supOp('start') + + +def stop(): + return supOp('stop') + + +def restart(): + return supOp('restart') + + +def reload(): + return supOp('reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status supervisor | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable supervisor') + return 'ok' + + +def initdUinstall(): + if not app_debug: + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl diable supervisor') + return 'ok' + + +def getSupList(): + data = {} + + statusFile = getServerDir() + "/status.txt" + supCtl = 'supervisorctl -c ' + getServerDir() + "/supervisor.conf" + cmd = "%s update; %s status > %s" % (supCtl, supCtl, statusFile) + mw.execShell(cmd) + + with open(statusFile, "r") as fr: + lines = fr.readlines() + + array_list = [] + process_list = [] + for r in lines: + array = r.split() + if array: + d = dict() + program = array[0].split(':')[0] + if program in process_list: + continue + process_list.append(program) + d["program"] = program + d["runStatus"] = array[1] + if array[1] == "RUNNING": + d["status"] = "1" + d["pid"] = array[3][:-1] + else: + d["status"] = "0" + d["pid"] = "" + file = getServerDir() + '/conf.d/' + program + ".ini" + if not os.path.exists(file): + continue + with open(file, "r") as fr: + infos = fr.readlines() + for line in infos: + if "command=" in line.strip(): + # d["command"] = line.strip().split('=')[1] + d["command"] = "子配置查看" + if "user=" in line.strip(): + d["user"] = line.strip().split('=')[1] + if "priority=" in line.strip(): + d["priority"] = line.strip().split('=')[1] + if "numprocs=" in line.strip(): + d["numprocs"] = line.strip().split('=')[1] + array_list.append(d) + + # print(array_list) + data = {} + data['data'] = array_list + return mw.getJson(data) + +def confDList(): + confd_dir = getServerDir() + '/conf.d' + clist = os.listdir(confd_dir) + array_list = [] + for x in range(len(clist)): + t = {} + t['name'] = clist[x] + array_list.append(t) + + data = {} + data['data'] = array_list + return mw.getJson(data) + + +def confDlistTraceLog(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + confd_dir = getServerDir() + '/conf.d/' + args['name'] + content = mw.readFile(confd_dir) + rep = r'stdout_logfile\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def confDlistErrorLog(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + + confd_dir = getServerDir() + '/conf.d/' + args['name'] + content = mw.readFile(confd_dir) + rep = r'stderr_logfile\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getUserListData(): + user = getServerDir() + "/user.txt" + if not os.path.isfile(user): + os.system(r"touch {}".format(user)) + res = mw.execShell("cat /etc/passwd > " + user) + with open(user, "r") as fr: + users = fr.readlines() + fr.close() + os.remove(user) + + user_list = [] + special = ["bin", "daemon", "adm", "lp", "shutdown", "halt", "mail", "operator", "games", + "avahi-autoipd", "systemd-bus-proxy", "systemd-network", "dbus", "polkitd", "tss", "ntp"] + for u in users: + user = re.split(':', u)[0] + if user[0] == '#': + continue + if user in special: + continue + user_list.append(user) + + if mw.isAppleSystem(): + cmd = "who | sed -n '2, 1p' |awk '{print $1}'" + user = mw.execShell(cmd)[0].strip() + user_list.append(user) + return user_list + + +def getUserList(): + user_list = getUserListData() + return mw.getJson(user_list) + + +def addJob(): + args = getArgs() + data = checkArgs(args, ['name', 'user', 'path', 'command', 'numprocs']) + if not data[0]: + return data[1] + + program = args['name'] + command = args['command'] + path = args['path'] + numprocs = args['numprocs'] + user = args['user'] + + log_dir = getServerDir() + '/log/' + + w_body = "" + w_body += "[program:" + program + "]" + "\n" + w_body += "command=" + command + "\n" + w_body += "directory=" + path + "\n" + w_body += "autorestart=true" + "\n" + w_body += "startsecs=3" + "\n" + w_body += "startretries=3" + "\n" + w_body += "stdout_logfile=" + log_dir + program + ".out.log" + "\n" + w_body += "stderr_logfile=" + log_dir + program + ".err.log" + "\n" + w_body += "stdout_logfile_maxbytes=1MB" + "\n" + w_body += "stderr_logfile_maxbytes=1MB" + "\n" + w_body += "user=" + user + "\n" + w_body += "priority=999" + "\n" + w_body += "numprocs={0}".format(numprocs) + "\n" + w_body += "process_name=%(program_name)s_%(process_num)02d" + # _%(process_num)02d + + dstFile = getSubConfDir() + "/" + program + '.ini' + + mw.writeFile(dstFile, w_body) + + return mw.returnJson(True, '增加守护进程成功!') + + +def startJob(): + args = getArgs() + data = checkArgs(args, ['name', 'status']) + if not data[0]: + return data[1] + + supCtl = 'supervisorctl -c ' + getServerDir() + "/supervisor.conf" + + name = args['name'] + status = args['status'] + + action = "启动" + cmd = supCtl + " start " + name + ":" + if status == 'start': + action = "停止" + cmd = supCtl + " stop " + name + ":" + # print(cmd) + data = mw.execShell(cmd) + # print(data) + + if data[1] != '': + return mw.returnJson(False, action + '[' + name + ']失败!') + return mw.returnJson(True, action + '[' + name + ']成功!') + + +def restartJob(): + args = getArgs() + data = checkArgs(args, ['name', 'status']) + if not data[0]: + return data[1] + + supCtl = 'supervisorctl -c ' + getServerDir() + "/supervisor.conf" + + name = args['name'] + status = args['status'] + + cmd = supCtl + " stop " + name + ":" + data = mw.execShell(cmd) + cmd = supCtl + " start " + name + ":" + data = mw.execShell(cmd) + + if data[1] != '': + return mw.returnJson(False, '[' + name + ']重启失败!') + return mw.returnJson(True, '[' + name + ']重启成功!') + + +def delJob(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + name = args['name'] + + supCtl = 'supervisorctl' + log_dir = getServerDir() + '/log/' + + result = mw.execShell("{0} stop ".format(supCtl) + name + ":") + program = getServerDir() + "/conf.d/" + name + ".ini" + + # 删除日志文件 + outlog = log_dir + name + ".out.log" + if os.path.isfile(outlog): + os.remove(outlog) + errlog = log_dir + name + ".err.log" + if os.path.isfile(errlog): + os.remove(errlog) + + # 删除ini文件 + if os.path.isfile(program): + os.remove(program) + result = mw.execShell( + "{0} update".format(supCtl)) + return mw.returnJson(True, '删除守护进程成功!') + else: + result = mw.execShell( + "{0} update".format(supCtl)) + return mw.returnJson(False, '该守护进程不存在!') + + +def updateJob(): + args = getArgs() + data = checkArgs(args, ["name", 'user', 'numprocs', 'priority']) + if not data[0]: + return data[1] + user = args['user'] + numprocs = args['numprocs'] + priority = args['priority'] + name = args['name'] + programFile = getServerDir() + "/conf.d/" + name + ".ini" + + mess = {} + infos = [] + with open(programFile, "r") as fr: + infos = fr.readlines() + + for line in infos: + if "command=" in line.strip(): + mess["command"] = line.strip().split('=')[1] + if "directory=" in line.strip(): + mess["path"] = line.strip().split('=')[1] + + # print(mess) + log_file_name = getServerDir() + '/log/' + name + + w_body = "" + w_body += "[program:" + name + "]" + "\n" + w_body += "command=" + mess["command"] + "\n" + w_body += "directory=" + mess["path"] + "\n" + w_body += "autorestart=true" + "\n" + w_body += "startsecs=3" + "\n" + w_body += "startretries=3" + "\n" + w_body += "stdout_logfile=" + log_file_name + ".out.log" + "\n" + w_body += "stderr_logfile=" + log_file_name + ".err.log" + "\n" + w_body += "stdout_logfile_maxbytes=2MB" + "\n" + w_body += "stderr_logfile_maxbytes=2MB" + "\n" + w_body += "user=" + user + "\n" + w_body += "priority=" + priority + "\n" + w_body += "numprocs={0}".format(numprocs) + "\n" + w_body += "process_name=%(program_name)s_%(process_num)02d" + + mw.writeFile(programFile, w_body) + + return mw.returnJson(True, '修改守护进程成功!') + + +def getJobInfo(): + args = getArgs() + data = checkArgs(args, ['name']) + if not data[0]: + return data[1] + name = args['name'] + + mess = {} + infos = [] + info = {} + program = getServerDir() + "/conf.d/" + name + ".ini" + with open(program, "r") as fr: + infos = fr.readlines() + mess = {} + for line in infos: + if "user=" in line.strip(): + mess["user"] = line.strip().split('=')[1] + if "numprocs=" in line.strip(): + mess["numprocs"] = line.strip().split('=')[1] + if "priority=" in line.strip(): + mess["priority"] = line.strip().split('=')[1] + userlist = getUserListData() + info["userlist"] = userlist + info["daemoninfo"] = mess + return mw.getJson(info) + + +def configTpl(): + path = getServerDir() + '/conf.d' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + if one.endswith(".ini"): + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + return mw.returnJson(True, 'ok', content) + + +def readConfigLogTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + file_log = args['file'] + line_log = args['line'] + + with open(file_log, "r") as fr: + infos = fr.readlines() + + stdout_logfile = '' + for line in infos: + if "stdout_logfile=" in line.strip(): + stdout_logfile = line.strip().split('=')[1] + + if stdout_logfile != '': + data = mw.getLastLine(stdout_logfile, int(line_log)) + return mw.returnJson(True, 'OK', data) + return mw.returnJson(False, 'OK', '') + + +def readConfigLogErrorTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + file_log = args['file'] + line_log = args['line'] + + with open(file_log, "r") as fr: + infos = fr.readlines() + + stderr_logfile = '' + for line in infos: + if "stderr_logfile=" in line.strip(): + stderr_logfile = line.strip().split('=')[1] + + if stderr_logfile != '': + data = mw.getLastLine(stderr_logfile, int(line_log)) + return mw.returnJson(True, 'OK', data) + return mw.returnJson(False, 'OK', '') + + +def supClearLog(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + file_log = args['file'] + + with open(file_log, "r") as fr: + infos = fr.readlines() + + stdout_logfile = '' + stderr_logfile = '' + for line in infos: + if "stdout_logfile=" in line.strip(): + stdout_logfile = line.strip().split('=')[1] + if "stderr_logfile=" in line.strip(): + stderr_logfile = line.strip().split('=')[1] + + cmd_stdout = "echo '' > " + stdout_logfile + cmd_stderr = "echo '' > " + stderr_logfile + mw.execShell(cmd_stdout) + mw.execShell(cmd_stderr) + return mw.returnJson(True, '清空成功') + + +def runLog(): + return getServerDir() + '/log/supervisor.log' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'read_config_log_tpl': + print(readConfigLogTpl()) + elif func == 'read_config_log_error_tpl': + print(readConfigLogErrorTpl()) + elif func == 'sup_clear_log': + print(supClearLog()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'get_user_list': + print(getUserList()) + elif func == 'get_sup_list': + print(getSupList()) + elif func == 'confd_list': + print(confDList()) + elif func == 'confd_list_trace_log': + print(confDlistTraceLog()) + elif func == 'confd_list_error_log': + print(confDlistErrorLog()) + elif func == 'add_job': + print(addJob()) + elif func == 'start_job': + print(startJob()) + elif func == 'restart_job': + print(restartJob()) + elif func == 'del_job': + print(delJob()) + elif func == 'update_job': + print(updateJob()) + elif func == 'get_job_info': + print(getJobInfo()) + else: + print('error') diff --git a/plugins/supervisor/info.json b/plugins/supervisor/info.json new file mode 100755 index 000000000..7b6d71416 --- /dev/null +++ b/plugins/supervisor/info.json @@ -0,0 +1,18 @@ +{ + "sort": 1, + "ps": "一个Python开发的通用的进程管理程序", + "name": "supervisor", + "title": "supervisor", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "checks": "server/supervisor", + "path": "server/supervisor", + "display": 1, + "author": "python", + "date": "2022-06-15", + "home": "https://pypi.org/project/supervisor/", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/supervisor/init.d/supervisor.service b/plugins/supervisor/init.d/supervisor.service new file mode 100644 index 000000000..1da932a35 --- /dev/null +++ b/plugins/supervisor/init.d/supervisor.service @@ -0,0 +1,14 @@ +[Unit] +Description=supervisor server daemon +After=network.target + +[Service] +Type=forking +ExecStart={$SUP_BIN} -c {$SERVER_PATH}/supervisor/supervisor.conf +ExecStop=kill -s TERM $MAINPID +ExecReload=kill -s HUP $MAINPID +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/supervisor/install.sh b/plugins/supervisor/install.sh new file mode 100755 index 000000000..96ece61ac --- /dev/null +++ b/plugins/supervisor/install.sh @@ -0,0 +1,73 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +# /www/server/mdserver-web/bin/supervisorctl -c /www/server/supervisor/supervisor.conf +# cmd | status + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +OSNAME_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +Install_app() +{ + echo '正在安装[supervisor]...' + mkdir -p $serverPath/source + mkdir -p $serverPath/supervisor + mkdir -p $serverPath/supervisor/log + mkdir -p $serverPath/supervisor/run + + echo 'supervisor install...' + if [ "centos" == "$OSNAME" ] || [ "fedora" == "$OSNAME" ];then + pip install supervisor + elif [ "ubuntu" == "$OSNAME" ] || [ "debian" == "$OSNAME" ] ;then + pip install supervisor + else + pip install supervisor + # brew install supervisor + fi + + echo "${VERSION}" > $serverPath/supervisor/version.pl + + cd ${rootPath} && python3 plugins/supervisor/index.py start + cd ${rootPath} && python3 plugins/supervisor/index.py initd_install + + echo '安装完成[supervisor]' +} + +Uninstall_app() +{ + + if [ -f /usr/lib/systemd/system/supervisor.service ] || [ -f /lib/systemd/system/supervisor.service ];then + systemctl stop supervisor + systemctl disable supervisor + rm -rf /usr/lib/systemd/system/supervisor.service + rm -rf /lib/systemd/system/supervisor.service + systemctl daemon-reload + fi + + pip uninstall supervisor -y + + rm -rf $serverPath/supervisor + + echo "卸载完成[supervisor]" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app +else + Uninstall_app +fi diff --git a/plugins/supervisor/js/supervisor.js b/plugins/supervisor/js/supervisor.js new file mode 100755 index 000000000..a3780da0f --- /dev/null +++ b/plugins/supervisor/js/supervisor.js @@ -0,0 +1,624 @@ + +function myPost(method,args,callback, title){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(str2Obj(args)); + } else { + _args = JSON.stringify(args); + } + + var _title = '正在获取...'; + if (typeof(title) != 'undefined'){ + _title = title; + } + + var loadT = layer.msg(_title, { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'supervisor', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + + +function supList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + myPost('get_sup_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list += '' + rdata.data[i]['program'] +''; + list += '' + rdata.data[i]['command'] +''; + list += '' + rdata.data[i]['user'] +''; + list += '' + rdata.data[i]['pid'] +''; + list += '' + rdata.data[i]['numprocs'] +''; + list += '' + rdata.data[i]['priority'] +''; + + sup_status = 'start' + sup_status_desc = 'start' + if (rdata.data[i]['runStatus'] == 'RUNNING' ){ + sup_status = 'start' + sup_status_desc = '已启动' + } else{ + sup_status = 'stop' + sup_status_desc = '已停止' + } + + list += ''+sup_status_desc+''; + + list += '' + rdata.data[i]['runStatus'] +''; + + list += '\ + '+sup_status_desc+' | ' + + '重启 | ' + + '修改 | ' + + '删除' + + ''; + + list += ''; + } + + if(rdata.data.length==0){ + list = "当前没有数据"; + } + + var con = '
                                  \ + \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                  名称启动命令启动用户进程ID进程数量优先级进程管理状态操作
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  '; + + con += '
                                  \ + supervisord 常见进程状态详细如下:\ + 1:STOPPED:该进程已停止。 2:STOPPING:由于停止请求,该进程正在停止。\ + 3:RUNNING:该进程正在运行。 4:STARTING:该进程由于启动请求而开始。\ + 5:FATAL:该进程无法成功启动。\ + 6:BACKOFF:该进程进入“ 启动”状态,但随后退出的速度太快而无法移至“ 运行”状态。\ +
                                  ' + + $(".soft-man-con").html(con); + $('#databasePage').html(rdata.page); + }); +} + + +function startOrStop(name,status){ + myPost('start_job',{'name':name,'status':status}, function(data){ + var rdata = $.parseJSON(data.data); + showMsg(rdata.msg, function(){ + supList(1,10); + },{icon:rdata.status?1:2}, rdata.status ? 2000 : 10000); + }); +} + +function restartJob(name,status){ + myPost('restart_job',{'name':name,'status':status}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + setTimeout(function(){ + supList(1,10); + },2000); + }); +} + +function updateJob(name){ + myPost('get_job_info',{'name':name},function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var defaultPath = $("#defaultPath").html(); + var ulist = "
                                  启动用户
                                  "; + layer.open({ + type: 1, + area: '500px', + title: '修改守护进程', + closeBtn: 2, + shift: 0, + shadeClose: false, + btn: ['确定', '取消'], + content: "
                                  \ +
                                  \ + 名称\ +
                                  \ + \ +
                                  \ +
                                  \ + "+ulist+"\ +
                                  \ + 进程数量\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + 启动优先级\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  ", + yes: function(index, layero){ + // console.log(index,layero); + + var options = ['name','user','numprocs','priority']; + var opval = {}; + for(var i in options){ + opval[options[i]] = $('[name="'+ options[i] +'"]').val(); + if(opval[options[i]] == ''){ + if(options[i] == 'user'){ + layer.msg('启动用户不能为空'); + return false; + }else if(options[i] == 'priority'){ + layer.msg('启动优先级不能为空'); + return false; + }else if(options[i] == 'numprocs'){ + layer.msg('进程数量不能为空!'); + return false; + } + } + } + + var numprocs = $('[name=numprocs]').val(); + if (!(/(^[1-9]\d*$)/.test(numprocs))) { + layer.msg('进程数量请输入正整数') + return false; +   } + + myPost("update_job", opval, function(data){ + rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + rdata.status ? layer.close(index):''; + supList(1,10); + }) + return; + } + }); + }); +} + + +//卸载软件 +function delJob(name) { + layer.confirm(msgTpl('是否删除守护进程[{1}]?', [name]), { icon: 3, closeBtn: 2 }, function() { + /////////////////////////////////////// + var data = {'name': name}; + var loadT = layer.msg('正在处理,请稍候...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + myPost('del_job', data, function(rdata){ + layer.close(loadT) + + rdata = $.parseJSON(rdata.data); + supList(1,10); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + /////////////////////////////////////// + }); +} + +//添加站点 +function supAdd() { + myPost('get_user_list',{},function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata); + + var defaultPath = $("#defaultPath").html(); + var ulist = "
                                  启动用户
                                  "; + layer.open({ + type: 1, + area: '500px', + title: '添加守护进程', + closeBtn: 2, + shift: 0, + shadeClose: false, + btn: ['确定', '取消'], + content: "
                                  \ +
                                  \ + 名称\ +
                                  \ + \ +
                                  \ +
                                  \ + "+ulist+"\ +
                                  \ + 运行目录\ +
                                  \ + \ + \ +
                                  \ +
                                  \ +
                                  \ + 启动命令\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + 进程数量\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                    \ +
                                  • 注意:填写进程名称请使用英文,暂不支持中文!
                                  • \ +
                                  • 如果启动命令里面有文件,请填写文件的绝对路径!
                                  • \ +
                                  • 进程数量默认值为1,如果值为大于1的整数,则相当于多进程!
                                  • \ +
                                  \ +
                                  ", + yes: function(index, layero){ + // console.log(index,layero); + + var options = ['name','user','path','command','numprocs']; + var opval = {}; + for(var i in options){ + opval[options[i]] = $('[name="'+ options[i] +'"]').val(); + if(opval[options[i]] == ''){ + if(options[i] == 'name'){ + layer.msg('进程名称不能为空'); + return false; + }else if(options[i] == 'user'){ + layer.msg('启动用户不能为空'); + return false; + }else if(options[i] == 'path'){ + layer.msg('运行目录不能为空'); + return false; + }else if(options[i] == 'command'){ + layer.msg('启动命令不能为空'); + return false; + }else if(options[i] == 'numprocs'){ + layer.msg('进程数量不能为空!'); + return false; + } + } + } + + var numprocs = $('[name=numprocs]').val(); + if (!(/(^[1-9]\d*$)/.test(numprocs))) { + layer.msg('进程数量请输入正整数') + return false; +   } + + myPost("add_job", opval, function(data){ + rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + rdata.status ? layer.close(index):''; + supList(1,10); + }) + return; + } + }); + }); + +} + + +//supervisor +function supConfigTpl(_name, version, func, config_tpl_func, read_config_tpl_func){ + if ( typeof(version) == 'undefined' ){ + version = ''; + } + + var func_name = 'conf'; + if ( typeof(func) != 'undefined' ){ + func_name = func; + } + + var _config_tpl_func = 'config_tpl'; + if ( typeof(config_tpl_func) != 'undefined' ){ + _config_tpl_func = config_tpl_func; + } + + var _read_config_tpl_func = 'read_config_tpl'; + if ( typeof(read_config_tpl_func) != 'undefined' ){ + _read_config_tpl_func = read_config_tpl_func; + } + + + var con = '

                                  提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!

                                  \ + \ + \ + \ +
                                    \ +
                                  • 此处为'+ _name + version +'配置文件,若您不了解配置规则,请勿随意修改。
                                  • \ +
                                  '; + $(".soft-man-con").html(con); + + function getFileName(file){ + var list = file.split('/'); + var f = list[list.length-1]; + return f + } + + var fileName = ''; + $.post('/plugins/run',{name:_name, func:_config_tpl_func,version:version}, function(data){ + var rdata = $.parseJSON(data.data); + for (var i = 0; i < rdata.length; i++) { + $('#config_tpl').append(''); + } + + + $("#textBody").empty().text(''); + $(".CodeMirror").remove(); + var editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() {} + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + + $('#config_tpl').change(function(){ + var selected = $(this).val(); + if (selected != '0'){ + fileName = selected; + var loadT = layer.msg('配置获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + + var _args = JSON.stringify({file:selected}); + $.post('/plugins/run', {name:_name, func:_read_config_tpl_func,version:version,args:_args}, function(data){ + layer.close(loadT); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + $("#textBody").empty().html(rdata.data); + $(".CodeMirror").remove(); + var editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName); + } + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").unbind('click'); + $("#onlineEditFileBtn").click(function(){ + $("#textBody").html(editor.getValue()); + pluginConfigSave(fileName); + }); + },'json'); + } + }); + + },'json'); + +} + + +//保存 +function supConfigSave(fileName) { + var data = encodeURIComponent($("#textBody").val()); + var encoding = 'utf-8'; + var loadT = layer.msg('保存中...', {icon: 16,time: 0}); + $.post('/files/save_body', 'data=' + data + '&path=' + fileName + '&encoding=' + encoding, function(rdata) { + layer.close(loadT); + layer.msg(rdata.msg, {icon: rdata.status ? 1 : 2}); + },'json'); +} + + + +function supLogs(_name, config_tpl_func, read_config_tpl_func,line){ + + var file_line = 100; + if ( typeof(line) != 'undefined' ){ + file_line = line; + } + + var _config_tpl_func = 'config_tpl'; + if ( typeof(config_tpl_func) != 'undefined' ){ + _config_tpl_func = config_tpl_func; + } + + var _read_config_tpl_func = 'read_config_tpl'; + if ( typeof(read_config_tpl_func) != 'undefined' ){ + _read_config_tpl_func = read_config_tpl_func; + } + + function getFileName(file){ + var list = file.split('/'); + var f = list[list.length-1]; + return f + } + + var con = '
                                  \ + \ + \ +
                                  '; + con += ''; + $(".soft-man-con").html(con); + var ob = document.getElementById('info_log'); + ob.scrollTop = ob.scrollHeight; + + function clearLog(file){ + $('#sup_clear_log').click(function(){ + myPost('sup_clear_log', {'file':file}, function (data) { + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2,time:2000,shade: [0.3, '#000']}); + }); + }); + } + + function errorLog(file,file_line){ + $('#sup_error_log').click(function(){ + var _args = JSON.stringify({file:file,line:file_line}); + $.post('/plugins/run', {name:_name, func:'read_config_log_error_tpl',args:_args}, function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + $("#info_log").empty().html(rdata.data); + },'json'); + }); + } + + var loadT = layer.msg('日志路径获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/plugins/run', {name:_name, func:_config_tpl_func},function (data) { + layer.close(loadT); + + var rdata = $.parseJSON(data.data); + for (var i = 0; i < rdata.length; i++) { + $('#config_tpl').append(''); + } + + $('#config_tpl').change(function(){ + /// + var selected = $(this).val(); + if (selected == '0'){ + return; + } + + fileName = selected; + var loadT = layer.msg('日志获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + + var _args = JSON.stringify({file:selected,line:file_line}); + $.post('/plugins/run', {name:_name, func:_read_config_tpl_func,args:_args}, function(data){ + layer.close(loadT); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + $("#info_log").empty().html(rdata.data); + },'json'); + + clearLog(selected); + errorLog(selected,file_line); + /// + }); + + },'json'); +} + + +function confdListTraceLog(name){ + var args = {}; + args["name"] = name; + pluginRollingLogs("supervisor", '', "confd_list_trace_log", JSON.stringify(args), 21); +} + +function confdListErrLog(name){ + var args = {}; + args["name"] = name; + pluginRollingLogs("supervisor", '', "confd_list_error_log", JSON.stringify(args), 21); +} + +function confdList(page, search){ + var _data = {}; + if (typeof(page) =='undefined'){ + var page = 1; + } + + _data['page'] = page; + _data['page_size'] = 10; + if(typeof(search) != 'undefined'){ + _data['search'] = search; + } + + myPost('confd_list', _data, function(data){ + var rdata = $.parseJSON(data.data); + // console.log(rdata.data); + var list = ''; + for(i in rdata.data){ + list += ''; + list += '' + rdata.data[i]['name'] +''; + + list += '\ + 日志跟踪 | ' + + '查看错误日志' + + ''; + + list += ''; + } + + if( rdata.data.length == 0 ){ + list = "当前没有数据"; + } + + var con = '
                                  \ + \ +
                                  \ +
                                  \ + \ + \ + \ + \ + '+ list +'
                                  名称操作
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  '; + + con += '
                                  \ + 方便查看日志\ +
                                  ' + + $(".soft-man-con").html(con); + $('#databasePage').html(rdata.page); + }); +} + diff --git a/plugins/swap/ico.png b/plugins/swap/ico.png new file mode 100644 index 000000000..4ff64f09e Binary files /dev/null and b/plugins/swap/ico.png differ diff --git a/plugins/swap/index.html b/plugins/swap/index.html new file mode 100755 index 000000000..0ea986471 --- /dev/null +++ b/plugins/swap/index.html @@ -0,0 +1,20 @@ +
                                  +
                                  +
                                  +

                                  服务

                                  +

                                  自启动

                                  +

                                  配置调整

                                  +

                                  说明

                                  +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + + \ No newline at end of file diff --git a/plugins/swap/index.py b/plugins/swap/index.py new file mode 100755 index 000000000..db79fa596 --- /dev/null +++ b/plugins/swap/index.py @@ -0,0 +1,226 @@ +# coding:utf-8 + +import sys +import io +import os +import time + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'swap' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + data = mw.execShell("free -m|grep Swap|awk '{print $2}'") + if data[0].strip() == '0': + return 'stop' + return 'start' + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace( + '{$SERVER_PATH}', getServerDir() + '/swapfile') + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/swap.service' + systemServiceTpl = getPluginDir() + '/init.d/swap.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + swapon_bin = mw.execShell('which swapon')[0].strip() + swapoff_bin = mw.execShell('which swapoff')[0].strip() + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SWAPON_BIN}', swapon_bin) + content = content.replace('{$SWAPOFF_BIN}', swapoff_bin) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def swapOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' swap') + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return 'fail' + + +def start(): + return swapOp('start') + + +def stop(): + return swapOp('stop') + + +def restart(): + return swapOp('restart') + + +def reload(): + return 'ok' + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status swap | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable swap') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable swap') + return 'ok' + + +def swapStatus(): + sfile = getServerDir() + '/swapfile' + + if os.path.exists(sfile): + size = os.path.getsize(sfile) / 1024 / 1024 + else: + size = '218' + data = {'size': size} + return mw.returnJson(True, "ok", data) + + +def changeSwap(): + args = getArgs() + data = checkArgs(args, ['size']) + if not data[0]: + return data[1] + + size = args['size'] + swapOp('stop') + + gsdir = getServerDir() + + cmd = 'dd if=/dev/zero of=' + gsdir + '/swapfile bs=1M count=' + size + cmd += ' && mkswap ' + gsdir + '/swapfile && chmod 600 ' + gsdir + '/swapfile' + msg = mw.execShell(cmd) + swapOp('start') + + return mw.returnJson(True, "修改成功:\n" + msg[0]) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'conf': + print(getConf()) + elif func == "swap_status": + print(swapStatus()) + elif func == "change_swap": + print(changeSwap()) + else: + print('error') diff --git a/plugins/swap/info.json b/plugins/swap/info.json new file mode 100755 index 000000000..a1f0d1ea0 --- /dev/null +++ b/plugins/swap/info.json @@ -0,0 +1,17 @@ +{ + "sort": 6, + "ps": "Linux虚拟内存", + "name": "swap", + "title": "swap", + "shell": "install.sh", + "versions":"1.1", + "tip": "soft", + "checks": "server/swap", + "path": "server/swap", + "display": 1, + "author": "swap", + "date": "2021-01-29", + "home": "swap", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/swap/init.d/swap.service.tpl b/plugins/swap/init.d/swap.service.tpl new file mode 100644 index 000000000..6765e6c55 --- /dev/null +++ b/plugins/swap/init.d/swap.service.tpl @@ -0,0 +1,14 @@ + +[Unit] +Description=Swap Process Manager +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/swap/init.d/swap start +ExecStop={$SERVER_PATH}/swap/init.d/swap stop +RemainAfterExit=yes + + +[Install] +WantedBy=multi-user.target diff --git a/plugins/swap/init.d/swap.tpl b/plugins/swap/init.d/swap.tpl new file mode 100644 index 000000000..86bcb6d57 --- /dev/null +++ b/plugins/swap/init.d/swap.tpl @@ -0,0 +1,55 @@ +#!/bin/bash +# chkconfig: 2345 55 25 +# description: MW Cloud Service + +### BEGIN INIT INFO +# Provides: bt +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts mw +# Description: starts the mw +### END INIT INFO + + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + +app_file={$SERVER_PATH} + +app_start(){ + isStart=`free -m|grep Swap|awk '{print $2}'` + if [ "$isStart" == '0' ];then + echo -e "Starting swap... \c" + swapon $app_file + echo -e "\033[32mdone\033[0m" + else + echo "Starting swap already running" + fi +} + +app_stop() +{ + echo -e "Stopping swap... \c"; + swapoff $app_file + echo -e "\033[32mdone\033[0m" +} + +app_status() +{ + isStart=`free -m|grep Swap|awk '{print $2}'` + if [ "$isStart" == '0' ];then + echo -e "\033[32mswap already running\033[0m" + else + echo -e "\033[31mswap not running\033[0m" + fi +} + +case "$1" in + 'start') app_start;; + 'stop') app_stop;; + 'restart'|'reload') + app_stop + app_start;; + 'status') app_status;; +esac \ No newline at end of file diff --git a/plugins/swap/install.sh b/plugins/swap/install.sh new file mode 100755 index 000000000..1f698fd13 --- /dev/null +++ b/plugins/swap/install.sh @@ -0,0 +1,68 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +SYSOS=`uname` + +VERSION=$2 + +# cd /www/server/mdserver-web/plugins/swap && /bin/bash install.sh install 1.1 + +Install_swap() +{ + if [ -d $serverPath/swap ];then + exit 0 + fi + + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/swap + echo "${VERSION}" > $serverPath/swap/version.pl + + if [ "$sysName" == "Darwin" ];then + pass + else + dd if=/dev/zero of=$serverPath/swap/swapfile bs=1M count=1024 + chmod 600 $serverPath/swap/swapfile + mkswap $serverPath/swap/swapfile + swapon $serverPath/swap/swapfile + fi + + echo '安装完成' + + cd ${rootPath} && python3 ${rootPath}/plugins/swap/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/swap/index.py initd_install +} + +Uninstall_swap() +{ + swapoff $serverPath/swap/swapfile + + + if [ -f /usr/lib/systemd/system/swap.service ] || [ -f /lib/systemd/system/swap.service ];then + systemctl stop swap + systemctl disable swap + rm -rf /usr/lib/systemd/system/swap.service + rm -rf /lib/systemd/system/swap.service + systemctl daemon-reload + fi + + if [ -f $serverPath/swap/initd/swap ];then + $serverPath/swap/initd/swap stop + fi + + rm -rf $serverPath/swap + + echo "Uninstall_swap" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_swap +else + Uninstall_swap +fi diff --git a/plugins/swap/js/swap.js b/plugins/swap/js/swap.js new file mode 100755 index 000000000..a78e2db07 --- /dev/null +++ b/plugins/swap/js/swap.js @@ -0,0 +1,81 @@ + + +function swapPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'swap'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function swapStatus() { + swapPost('swap_status', '', {}, function(data){ + var rdata = $.parseJSON(data.data); + var size = rdata.data['size']; + + var spCon = '
                                  \ +
                                  最大使用交换分区: \ + \ + 当前: MB\ +
                                  \ +

                                  修改MB

                                  \ +
                                  \ +
                                  '; + + $(".soft-man-con").html(spCon); + + $(".conf_p select[name='swap_set']").change(function() { + var swap_size = $(this).val(); + if (swap_size.indexOf('GB')>-1){ + swap_size = parseInt(swap_size)*1024; + } else{ + swap_size = parseInt(swap_size); + } + $("input[name='cur_size']").val(swap_size); + $("input[name='size']").val(swap_size); + }); + }); +} + +function submitSwap(){ + var size = $("input[name='size']").val(); + swapPost('change_swap', '',{"size":size}, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 5 }); + }); +} + +function readme(){ + var readme = '
                                    '; + readme += '
                                  • dd if=/dev/zero of=/www/server/swap/swapfile bs=1M count=2048
                                  • '; + readme += '
                                  • mkswap /www/server/swap/swapfile
                                  • '; + readme += '
                                  '; + $('.soft-man-con').html(readme); +} diff --git a/plugins/sys-opt/ico.png b/plugins/sys-opt/ico.png new file mode 100644 index 000000000..d262d40dc Binary files /dev/null and b/plugins/sys-opt/ico.png differ diff --git a/plugins/sys-opt/index.html b/plugins/sys-opt/index.html new file mode 100755 index 000000000..e230b24c3 --- /dev/null +++ b/plugins/sys-opt/index.html @@ -0,0 +1,26 @@ +
                                  +
                                  +
                                  +

                                  服务

                                  +

                                  系统配置

                                  +

                                  安全日志

                                  +

                                  常用日志

                                  +

                                  任务日志

                                  +

                                  HOSTS

                                  +

                                  RESOLV

                                  +

                                  系统日志

                                  +

                                  说明

                                  +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + + \ No newline at end of file diff --git a/plugins/sys-opt/index.py b/plugins/sys-opt/index.py new file mode 100755 index 000000000..661b57afc --- /dev/null +++ b/plugins/sys-opt/index.py @@ -0,0 +1,172 @@ +# coding: utf-8 + +import time +import random +import os +import json +import re +import sys + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +def getPluginName(): + return 'sys-opt' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/sys-opt') + return content + + +def status(): + return 'start' + + +def start(): + mw.execShell('sysctl -p') + return "ok" + + +def stop(): + mw.execShell('sysctl -p') + return 'ok' + + +def restart(): + mw.execShell('sysctl -p') + return 'ok' + + +def reload(): + mw.execShell('sysctl -p') + return 'ok' + + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + + +def sysConf(): + return '/etc/sysctl.conf' + + +def secRunLog(): + if os.path.exists('/var/log/auth.log'): + return '/var/log/auth.log' + if os.path.exists('/var/log/messages'): + return '/var/log/messages' + return '/var/log/secure' + + +def msgRunLog(): + if os.path.exists('/var/log/kern.log'): + return '/var/log/kern.log' + return '/var/log/messages' + + +def cronRunLog(): + if os.path.exists('/var/log/syslog.log'): + return '/var/log/syslog.log' + if os.path.exists('/var/log/syslog'): + return '/var/log/syslog' + return '/var/log/cron' + + +def systemRunLog(): + return '/var/log/syslog' + +def showHosts(): + return '/etc/hosts' + +def showResolv(): + return '/etc/resolv.conf' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'conf': + print(sysConf()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + elif func == 'sec_run_log': + print(secRunLog()) + elif func == 'msg_run_log': + print(msgRunLog()) + elif func == 'cron_run_log': + print(cronRunLog()) + elif func == 'sys_run_log': + print(systemRunLog()) + elif func == 'hosts': + print(showHosts()) + elif func == 'resolv': + print(showResolv()) + else: + print('err') diff --git a/plugins/sys-opt/info.json b/plugins/sys-opt/info.json new file mode 100755 index 000000000..b9257f105 --- /dev/null +++ b/plugins/sys-opt/info.json @@ -0,0 +1,16 @@ +{ + "sort":1, + "title":"系统优化", + "tip":"soft", + "name":"sys-opt", + "type":"扩展", + "ps":"仅Linux系统优化", + "versions":"1.0", + "shell":"install.sh", + "checks":"server/sys-opt", + "path": "server/sys-opt", + "author":"midoks", + "home":"", + "date":"2018-12-20", + "pid":"5" +} \ No newline at end of file diff --git a/plugins/sys-opt/install.sh b/plugins/sys-opt/install.sh new file mode 100755 index 000000000..03e7c82ff --- /dev/null +++ b/plugins/sys-opt/install.sh @@ -0,0 +1,31 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +Install_sysopt() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/sys-opt + echo '1.0' > $serverPath/sys-opt/version.pl + echo '安装完成' + +} + +Uninstall_sysopt() +{ + rm -rf $serverPath/sys-opt + echo "卸载完成" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_sysopt +else + Uninstall_sysopt +fi diff --git a/plugins/sys-opt/js/sys-opt.js b/plugins/sys-opt/js/sys-opt.js new file mode 100755 index 000000000..b582d21a6 --- /dev/null +++ b/plugins/sys-opt/js/sys-opt.js @@ -0,0 +1,9 @@ + + +function pRead(){ + var readme = '
                                    '; + readme += '
                                  • 修改后,点击重启按钮
                                  • '; + readme += '
                                  '; + + $('.soft-man-con').html(readme); +} \ No newline at end of file diff --git a/plugins/sys-opt/tpl/linux.conf b/plugins/sys-opt/tpl/linux.conf new file mode 100755 index 000000000..f074f6516 --- /dev/null +++ b/plugins/sys-opt/tpl/linux.conf @@ -0,0 +1,63 @@ +# sysctl -w fs.file-max=6553560 +fs.file-max = 6553560 + +net.ipv4.ip_forward = 0 +net.ipv4.conf.default.rp_filter = 1 +net.ipv4.conf.default.accept_source_route = 0 +kernel.sysrq = 0 +kernel.core_uses_pid = 1 +net.ipv4.tcp_syncookies = 1 +kernel.msgmnb = 65536 +kernel.msgmax = 65536 +kernel.shmmax = 68719476736 +kernel.shmall = 4294967296 +net.ipv4.tcp_max_tw_buckets = 6000 +net.ipv4.tcp_sack = 1 +net.ipv4.tcp_window_scaling = 1 +net.ipv4.tcp_rmem = 4096 87380 4194304 +net.ipv4.tcp_wmem = 4096 16384 4194304 +net.core.wmem_default = 8388608 +net.core.rmem_default = 8388608 +net.core.rmem_max = 16777216 +net.core.wmem_max = 16777216 +net.core.netdev_max_backlog = 262144 +net.core.somaxconn = 262144 +net.ipv4.tcp_max_orphans = 3276800 +net.ipv4.tcp_max_syn_backlog = 262144 +net.ipv4.tcp_timestamps = 0 +net.ipv4.tcp_synack_retries = 1 +net.ipv4.tcp_syn_retries = 1 + +net.ipv4.tcp_tw_reuse = 1 +net.ipv4.tcp_mem = 94500000 915000000 927000000 +net.ipv4.tcp_fin_timeout = 1 +net.ipv4.tcp_keepalive_time = 30 +net.ipv4.ip_local_port_range = 1024 65001 + + +# vm opt +vm.dirty_ratio = 40 +vm.dirty_background_ratio = 10 +vm.swappiness = 10 +vm.vfs_cache_pressure = 500 +vm.overcommit_memory=1 +vm.max_map_count = 262144 + +# error:table full, dropping packet. +net.nf_conntrack_max = 25000000 +net.netfilter.nf_conntrack_max = 25000000 +net.netfilter.ip_conntrack_tcp_timeout_established = 30 +net.netfilter.nf_conntrack_tcp_timeout_close_wait = 10 +net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 5 +net.netfilter.nf_conntrack_tcp_timeout_time_wait = 5 + +#net.ipv4.tcp_tw_recycle = 1 +#net.ipv4.ip_conntrack_max = 81920 +#net.ipv4.netfilter.ip_conntrack_tcp_timeout_established = 1500 + +#lvs +#net.ipv4.conf.ens33.arp_ignore=1 +#net.ipv4.conf.ens33.arp_announce=2 +#net.ipv4.conf.all.arp_ignore=1 +#net.ipv4.conf.all.arp_announce=2 +#net.ipv4.ip_forward=1 \ No newline at end of file diff --git a/plugins/sys-opt/tpl/linux_notes.conf b/plugins/sys-opt/tpl/linux_notes.conf new file mode 100755 index 000000000..c43e244aa --- /dev/null +++ b/plugins/sys-opt/tpl/linux_notes.conf @@ -0,0 +1,74 @@ +#表示开启SYN Cookies.当出现SYN等待队列溢出时,启用cookies来处理, +#可防范少量SYN攻击,默认为0,表示关闭; +net.ipv4.tcp_syncookies = 1 + +#表示SYN队列的长度,默认为1024,加大队列长度为8192, +#可以容纳更多等待连接的网络连接数; +net.ipv4.tcp_max_syn_backlog = 65536 + +#时间戳可以避免序列号的卷绕。一个1Gbps的链路肯定会遇到以前用过的序列号, +#时间戳能够让内核接受这种"异常"的数据包.这里需要将其关掉; +net.ipv4.tcp_timestamps = 0 + +#tcp_synack_retries 显示或设定 Linux 核心在回应 SYN 要求时会尝试多少次重新发送初始 SYN,ACK 封包后才决定放弃。 +#这是所谓的三段交握 (threeway handshake) 的第二个步骤。即是说系统会尝试多少次去建立由远端启始的 TCP 连线。 +#tcp_synack_retries 的值必须为正整数,并不能超过 255。因为每一次重新发送封包都会耗费约 30 至 40 秒去等待才决定尝试下一次重新发送或决定放弃。 +#tcp_synack_retries 的缺省值为 5,即每一个连线要在约 180 秒 (3 分钟) 后才确定逾时。 +net.ipv4.tcp_synack_retries = 2 + +#在内核放弃建立连接之前发送SYN包的数量. +net.ipv4.tcp_syn_retries = 2 + +#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭; +net.ipv4.tcp_tw_recycle = 1 +#net.ipv4.tcp_tw_len = 1 + +#表示开启重用.允许将TIME-WAIT sockets重新用于新的TCP连接, +#默认为0,表示关闭; +net.ipv4.tcp_tw_reuse = 1 + +#同样有3个值,意思是: +#net.ipv4.tcp_mem[0]:低于此值,TCP没有内存压力. +#net.ipv4.tcp_mem[1]:在此值下,进入内存压力阶段. +#net.ipv4.tcp_mem[2]:高于此值,TCP拒绝分配socket. +#上述内存单位是页,而不是字节。可参考的优化值是:786432 1048576 1572864 +net.ipv4.tcp_mem = 94500000 91500000 92700000 + +#系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。 +#如果超过这个数字,孤儿连接将即刻被复位并打印出警告信息。 +#这个限制仅仅是为了防止简单的DoS攻击,不能过分依靠它或者人为地减小这个值, +#更应该增加这个值(如果增加了内存之后); +net.ipv4.tcp_max_orphans = 3276800 + +#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间; +net.ipv4.tcp_fin_timeout = 30 + +#表示当keepalive起用的时候,TCP发送keepalive消息的频度, +#缺省是2小时,改为20分钟 +net.ipv4.tcp_keeplive_time = 1200 + +#表示用于向外连接的端口范围.缺省情况下很小:32768到61000,改为1024到65000; +net.ipv4.ip_local_port_range = 1024 65535 + +#表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字, +#TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000,改为 5000. +#对于Apache、Nginx等服务器.上几行的参数可以很好地减少TIME_WAIT套接字数量; +net.ipv4.tcp_max_tw_buckets = 5000 + +####################################### + +#每个网络接口接收数据包的速率比内核处理这些包的速率快时, +#允许送到队列的数据包的最大数目; +net.core.netdev_max_backlog = 32768 + +#web应用中listen函数的backlog默认会给我们内核参数的net.core.somaxconn +#限制到128而nginx定义的NGX_LISTEN_BACKLOG默认为511,所以有必要调整这个值。 +net.core.somaxconn = 32768 + +#最大socket读buffer,可参考的优化值:873200 +net.core.wmem_default = 8388608 +net.core.rmem_default = 8388608 +net.core.rmem_max = 16777216 + +#最大socket写buffer,可参考的优化值:873200 +net.core.wmem_max = 16777216 \ No newline at end of file diff --git a/plugins/system_safe/LICENSE b/plugins/system_safe/LICENSE new file mode 100644 index 000000000..137069b82 --- /dev/null +++ b/plugins/system_safe/LICENSE @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/plugins/system_safe/conf/config.json b/plugins/system_safe/conf/config.json new file mode 100755 index 000000000..4dcd92c66 --- /dev/null +++ b/plugins/system_safe/conf/config.json @@ -0,0 +1,960 @@ +{ + "open": false, + "service": { + "open": true, + "name": "服务加固", + "ps": "保护系统服务,开启后将无法添加和删除服务,部分软件无法安装!", + "paths": [ + { + "path": "/etc/rc.d", + "chattr": "i", + "s_mode": 509, + "d_mode": 509 + }, + { + "path": "/etc/rc.d/init.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc.d/rc0.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc.d/rc1.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc.d/rc2.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc.d/rc3.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc.d/rc4.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc.d/rc5.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc.d/rc6.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc.d/rc.local", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/init.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc0.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc1.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc2.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc3.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc4.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc5.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rc6.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/rcS.d", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/etc/systemd", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + }, + { + "path": "/lib/systemd", + "chattr": "i", + "s_mode": 493, + "d_mode": 493 + } + ] + }, + "home": { + "open": true, + "name": "环境变量加固", + "ps": "保护用户环境变量不被修改,开启后无法自定义用户环境变量!", + "paths": [ + { + "path": "/root/.bash_history", + "chattr": "a", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/root/.bashrc", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/root/.bash_logout", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/root/.bash_profile", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/root/.profile", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/etc/profile", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/etc/bash.bashrc", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/etc/sysctl.conf", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/etc/sysctl.d", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + } + ] + }, + "user": { + "open": true, + "name": "用户加固", + "ps": "保护用户,开启后将无法添加删除用户和修改用户密码!", + "paths": [ + { + "path": "/etc/passwd", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/etc/group", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/usr/sbin/groupadd", + "chattr": "i", + "s_mode": 488, + "d_mode": 420 + }, + { + "path": "/usr/sbin/useradd", + "chattr": "i", + "s_mode": 488, + "d_mode": 420 + }, + { + "path": "/usr/bin/passwd", + "chattr": "i", + "s_mode": 509, + "d_mode": 420 + } + ] + }, + "bin": { + "open": true, + "name": "关键目录加固", + "ps": "保护系统关键文件不被修改替换!", + "paths": [ + { + "path": "/usr/bin", + "chattr": "i", + "s_mode": 365, + "d_mode": 365 + }, + { + "path": "/usr/sbin", + "chattr": "i", + "s_mode": 365, + "d_mode": 365 + }, + { + "path": "/etc/bashrc", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/usr/local/bin", + "chattr": "i", + "s_mode": 365, + "d_mode": 365 + }, + { + "path": "/usr/local/sbin", + "chattr": "i", + "s_mode": 365, + "d_mode": 365 + }, + { + "path": "/sbin", + "chattr": "i", + "s_mode": 365, + "d_mode": 365 + }, + { + "path": "/bin", + "chattr": "i", + "s_mode": 365, + "d_mode": 365 + } + ] + }, + "cron": { + "open": true, + "name": "计划任务加固", + "ps": "保护计划任务不被篡改,开启后无法添加删除计划任务!", + "paths": [ + { + "path": "/usr/bin/crontab", + "chattr": "i", + "s_mode": 509, + "d_mode": 420 + }, + { + "path": "/etc/crontab", + "chattr": "i", + "s_mode": 420, + "d_mode": 420 + }, + { + "path": "/etc/cron.d", + "chattr": "i", + "s_mode": 509, + "d_mode": 509 + }, + { + "path": "/etc/cron.daily", + "chattr": "i", + "s_mode": 509, + "d_mode": 420 + }, + { + "path": "/etc/cron.hourly", + "chattr": "i", + "s_mode": 509, + "d_mode": 420 + }, + { + "path": "/etc/cron.monthly", + "chattr": "i", + "s_mode": 509, + "d_mode": 420 + }, + { + "path": "/etc/cron.weekly", + "chattr": "i", + "s_mode": 509, + "d_mode": 420 + }, + { + "path": "/etc/anacrontab", + "chattr": "i", + "s_mode": 384, + "d_mode": 384 + }, + { + "path": "/var/spool/cron", + "chattr": "i", + "s_mode": 448, + "d_mode": 448 + }, + { + "path": "/var/spool/cron/root", + "chattr": "i", + "s_mode": 384, + "d_mode": 384 + }, + { + "path": "/var/spool/anacron", + "chattr": "i", + "s_mode": 509, + "d_mode": 509 + } + ] + }, + "ssh": { + "open": true, + "name": "SSH服务加固", + "ps": "保护SSH不被暴力破解,记录用户登录日志", + "cycle": 120, + "limit": 3600, + "limit_count": 3 + }, + "process": { + "open": true, + "name": "异常进程监控", + "ps": "监控服务器进程列表,发现异常的进程后立即结束", + "process_white": [ + "pip", + "pip3", + "yum", + "apt-get", + "apt", + "redis-cli", + "memcached", + "sshd", + "vm", + "vim", + "htop", + "top", + "sh", + "bash", + "zip", + "gzip", + "rsync", + "tar", + "unzip", + "rar", + "unrar", + "php", + "composer", + "pkill", + "mongo", + "mongod", + "php-fpm", + "php-fpm5.6", + "php-fpm7.0", + "php-fpm7.1", + "php-fpm7.2", + "php-fpm7.3", + "php-fpm7.4", + "php-fpm8.1", + "php-fpm8.2", + "nginx", + "httpd", + "lsof", + "ps", + "vi", + "vim", + "redis-server", + "mysqld", + "mysqld_safe", + "mysql", + "pure-ftpd", + "sparse_dd", + "stunnel", + "squeezed", + "vncterm", + "awk", + "ruby", + "postgres", + "mpathalert", + "vncterm", + "multipathd", + "fe", + "elasticsyslog", + "syslogd", + "v6d", + "xapi", + "screen", + "runsvdir", + "svlogd", + "java", + "udevd", + "ntpd", + "irqbalance", + "qmgr", + "wpa_supplicant", + "mysqld_safe", + "sftp-server", + "lvmetad", + "gitlab-web", + "pure-ftpd", + "auditd", + "master", + "dbus-daemon", + "tapdisk", + "init", + "ksoftirqd", + "kworker", + "kmpathd", + "kmpath_handlerd", + "python", + "kdmflush", + "bioset", + "crond", + "kthreadd", + "migration", + "rcu_sched", + "kjournald", + "gcc", + "gcc++", + "nginx", + "mysqld", + "php-cgi", + "login", + "firewalld", + "iptables", + "systemd", + "network", + "dhclient", + "systemd-journald", + "chrony", + "chronyd", + "chronyc", + "NetworkManager", + "systemd-logind", + "systemd-udevd", + "polkitd", + "tuned", + "rsyslogd", + "AliYunDunUpdate", + "AliYunDun", + "du", + "sendmail", + "gunicorn", + "python2", + "python3", + "python34", + "python2.6", + "runsv", + "dd", + "mkfs", + "fdisk", + "systemd-resolve", + "rpm", + "cc1", + "cc1plus", + "as", + "acme.sh", + "cc", + "du", + "grep", + "awk", + "sed", + "cut", + "free", + "df", + "firewall-cmd", + "ufw", + "echo", + "ls", + "cp", + "mv", + "rm", + "diff", + "ln", + "cat", + "more", + "wtmp", + "date", + "e2fsck", + "gunzip", + "ifconfig", + "ip", + "route", + "ping", + "scp", + "telnet", + "cd", + "comm", + "touch", + "man", + "w", + "who", + "last", + "clock", + "uname", + "su", + "uptime", + "vmstat", + "mount", + "umount", + "mkdir", + "mkswap", + "swapon", + "e2fsck", + "tune2fs", + "groupadd", + "useradd", + "passwd", + "userdel", + "chown", + "chmod", + "chgrp", + "id", + "mail", + "gzip", + "bzip2", + "bunzip2", + "networking", + "ssh", + "find", + "locate", + "slocate", + "dir", + "vdir", + "pwd", + "rename", + "rmdir", + "updatedb", + "whereis", + "which", + "compress", + "ar", + "arj", + "gzexe", + "bzip2", + "cksum", + "cmp", + "col", + "colrm", + "csplit", + "diff3", + "emacs", + "join", + "head", + "sort", + "wc", + "uniq", + "tail", + "sum", + "ulimit", + "pkill", + "kill", + "killall", + "pidof", + "pstree", + "watch", + "pgrep", + "dumpe2fs", + "mke2fs", + "sync", + "lsyncd", + "tune2fs", + "basename", + "file", + "locate/slocate", + "ls/dir/vdir", + "bzcat", + "bzip2recover", + "bzless/bzmore", + "cpio", + "dump", + "lha", + "resotre", + "unarj", + "uncompress", + "zcat", + "zforce", + "zipinfo", + "znew", + "diffstat", + "ed", + "ex", + "expand", + "fmt", + "fold", + "grep/egrep/fgrep", + "ispell", + "jed", + "joe", + "less", + "look", + "od", + "paste", + "pico", + "spell", + "split", + "tac", + "tee", + "tr", + "unexpand", + "alias", + "bg", + "bind", + "declare", + "dirs", + "enable", + "eval", + "exec", + "exit", + "export", + "fc", + "fg", + "hash", + "history", + "jobs", + "logout", + "popd", + "pushd", + "set", + "shopt", + "umask", + "unalias", + "unset", + "accept", + "cancel", + "disable", + "lp", + "lpadmin", + "lpc", + "lpq", + "lpr", + "lprm", + "lpstat", + "pr", + "reject", + "bc", + "cal", + "clear", + "consoletype", + "ctrlaltdel", + "dircolors", + "eject", + "halt", + "hostid", + "hwclock", + "info", + "md5sum", + "letsencrypt", + "mandb", + "mesg", + "mtools", + "mtoolstest", + "poweroff", + "reboot", + "shutdown", + "sleep", + "stat", + "talk", + "wall", + "whatis", + "whoami", + "write", + "cmsgoagent.linu", + "yes", + "chfn", + "chsh", + "finger", + "gpasswd", + "groupdel", + "groupmod", + "groups", + "grpck", + "grpconv", + "grpunconv", + "logname", + "pwck", + "pwconv", + "dd", + "mariadbd", + "pwunconv", + "usermod", + "users", + "nice", + "nohup", + "renice", + "badblocks", + "blockdev", + "chattr", + "convertquota", + "e2image", + "e2label", + "edquota", + "fail2ban-server", + "findfs", + "fsck", + "grub", + "hdparm", + "lilo", + "lsattr", + "mkbootdisk", + "mkinitrd", + "mkisofs", + "mknod", + "mktemp", + "parted", + "quota", + "quotacheck", + "xargs", + "bcm-agent", + "bcm-si", + "quotaoff", + "quotaon", + "quotastat", + "repquota", + "swapoff", + "depmod", + "dmesg", + "insmod", + "iostat", + "ipcs", + "kernelversion", + "lsmod", + "modinfo", + "modprobe", + "mpstat", + "rmmod", + "sar", + "slabtop", + "sysctl", + "tload", + "startx", + "xauth", + "xhost", + "xinit", + "xlsatoms", + "xlsclients", + "xlsfonts", + "xset", + "chroot", + "nmap", + "ntfs-3g", + "sftp", + "slogin", + "sudo", + "awk/gawk", + "expr", + "gdb", + "ldd", + "make", + "nm", + "perl", + "test", + "arch", + "at", + "atq", + "atrm", + "batch", + "chkconfig", + "crontab", + "lastb", + "logrotate", + "logsave", + "logwatch", + "lsusb", + "patch", + "runlevel", + "service", + "telinit", + "dnsdomainname", + "domainname", + "hostname", + "ifcfg", + "ifdown", + "ifup", + "stmpd", + "nisdomainname", + "ypdomainname", + "arp", + "arping", + "arpwatch", + "dig", + "elinks", + "elm", + "ftp", + "host", + "ipcalc", + "lynx", + "ncftp", + "netstat", + "nslookup", + "pine", + "dhclient-script", + "rsh", + "tftp", + "tracepath", + "traceroute", + "wget", + "arptables", + "iptables-save", + "iptables-restore", + "tcpdump", + "ab", + "apachectl", + "exportfs", + "htdigest", + "htpasswd", + "mailq", + "mysqladmin", + "msqldump", + "mysqlimport", + "mysqlshow", + "nfsstat", + "showmount", + "smbclient", + "smbmount", + "smbpasswd", + "squid", + "pickup", + "local", + "localedef", + "bounce", + "smtp", + "smtpd", + "trivial-rewrite", + "xe-daemon", + "cleanup", + "nm-dispatcher", + "lsinitrd", + "barad_agent", + "stop.sh", + "YDService", + "agetty", + "systemctl", + "AliSecureCheckA", + "containerd", + "containerd-shim", + "certbot-auto", + "oneav", + "oneavd", + "oneav_service_m", + "s3fs", + "bosfs", + "cosfs", + "flock", + "raidcheck", + "run-parts", + "man-db.cron", + "YDLive", + "sgagent" + ], + "process_white_rule": [ + "vif", + "node", + "pm2", + "npm", + "yarn", + "qemu", + "scsi_eh", + "xcp", + "xen", + "docker", + "yunsuo", + "scsi", + "jbd2", + "ext4", + "xfs", + "aliyun", + "kswapd", + "PM2", + "yunsuo", + "mkfs", + "Ali", + "watchdog", + "kworker", + "ksoftirqd", + "migration", + "mysql", + "/www/server/", + "cmsgoagent", + "python", + "python3", + "ifdown", + "node", + "sd-pam" + ], + "process_exclude": [ + "php-fpm", + "git", + "mysqld", + "mongod", + "dockerd", + "docker-containerd", + "memcached", + "jsvc", + "jsvc.exec", + "nginx", + "node", + "pm2", + "npm", + "yarn", + "httpd", + "gunicorn", + "configure", + "make", + "curl", + "wget", + "anacron", + "mysqldump", + "node", + "php", + "mysql", + "netstat", + "redis", + "postfix" + ] + } +} \ No newline at end of file diff --git a/plugins/system_safe/ico.png b/plugins/system_safe/ico.png new file mode 100644 index 000000000..e31c1b3bf Binary files /dev/null and b/plugins/system_safe/ico.png differ diff --git a/plugins/system_safe/index.html b/plugins/system_safe/index.html new file mode 100755 index 000000000..16a31b904 --- /dev/null +++ b/plugins/system_safe/index.html @@ -0,0 +1,24 @@ +
                                  +
                                  +
                                  +

                                  服务

                                  +

                                  自启动

                                  +

                                  防护配置

                                  +

                                  封锁IP

                                  +

                                  日志审计

                                  +

                                  操作日志

                                  +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + + + \ No newline at end of file diff --git a/plugins/system_safe/info.json b/plugins/system_safe/info.json new file mode 100755 index 000000000..52fe791ea --- /dev/null +++ b/plugins/system_safe/info.json @@ -0,0 +1,20 @@ +{ + "sort": 8, + "ps": "提供灵活的系统加固功能,防止系统被植入木马,支持服务器日志审计功能", + "name": "system_safe", + "title": "系统加固", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "icon":"ico.png", + "checks": "server/system_safe", + "path": "server/system_safe", + "display": 1, + "author": "midoks", + "date": "2022-01-12", + "home": "", + "type": 0, + "pid": "4" + +} \ No newline at end of file diff --git a/plugins/system_safe/init.d/system_safe.service.tpl b/plugins/system_safe/init.d/system_safe.service.tpl new file mode 100644 index 000000000..707d60eb6 --- /dev/null +++ b/plugins/system_safe/init.d/system_safe.service.tpl @@ -0,0 +1,15 @@ +[Unit] +Description=system_safe server daemon +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/init.d/system_safe start +ExecStop={$SERVER_PATH}/init.d/system_safe stop +ExecReload={$SERVER_PATH}/init.d/system_safe reload +ExecRestart={$SERVER_PATH}/init.d/system_safe restart +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/system_safe/init.d/system_safe.tpl b/plugins/system_safe/init.d/system_safe.tpl new file mode 100644 index 000000000..ae460a4b7 --- /dev/null +++ b/plugins/system_safe/init.d/system_safe.tpl @@ -0,0 +1,94 @@ +#!/bin/bash +# chkconfig: 2345 55 25 +# description:system_safe + +### BEGIN INIT INFO +# Provides: system_safe +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts system_safe +# Description: starts the system_safe +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +mw_path={$SERVER_PATH} +rootPath=$(dirname "$mw_path") +PATH=$PATH:$mw_path/bin + +if [ -f $rootPath/mdserver-web/bin/activate ];then + source $rootPath/mdserver-web/bin/activate +fi + +sys_start() +{ + isStart=$(ps aux |grep -E "(system_safe)"|grep -v grep|grep -v '/bin/bash'|grep -v 'system_safe/system_safe.py start' | grep -v 'system_safe/system_safe.py reload' | grep -v 'system_safe/system_safe.py restart' | grep -v systemctl | grep -v '/bin/sh' | awk '{print $2}'|xargs) + if [ "$isStart" == '' ];then + echo -e "Starting system_safe service... \c" + cd $rootPath/mdserver-web + nohup python3 plugins/system_safe/system_safe.py bg_start &> $mw_path/service.log & + sleep 0.5 + isStart=$(ps aux |grep -E "(system_safe)"|grep -v grep|awk '{print $2}'|xargs) + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo '------------------------------------------------------' + cat $mw_path/service.log + echo '------------------------------------------------------' + echo -e "\033[31mError: system_safe service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "Starting system_safe service (pid $isStart) already running" + fi +} + +sys_stop() +{ + echo -e "Stopping system_safe service... \c"; + pids=$(ps aux |grep -E "(system_safe)"|grep -v grep|grep -v '/bin/bash'|grep -v systemctl | grep -v 'system_safe/system_safe.py bg_stop'|grep -v 'system_safe/system_safe.py stop' | grep -v 'system_safe/system_safe.py reload' | grep -v 'system_safe/system_safe.py restart' |awk '{print $2}'|xargs) + arr=($pids) + for p in ${arr[@]} + do + kill -9 $p + done + cd $rootPath/mdserver-web + python3 plugins/system_safe/system_safe.py bg_stop + echo -e "\033[32mdone\033[0m" +} + +sys_status() +{ + isStart=$(ps aux |grep -E "(system_safe)"|grep -v grep|grep -v systemctl|awk '{print $2}'|xargs) + if [ "$isStart" != '' ];then + echo -e "\033[32msystem_safe service (pid $isStart) already running\033[0m" + else + echo -e "\033[31msystem_safe service not running\033[0m" + fi +} + +case "$1" in + 'start') + sys_start + ;; + 'stop') + sys_stop + ;; + 'restart') + sys_stop + sleep 0.2 + sys_start + ;; + 'reload') + sys_stop + sleep 0.2 + sys_start + ;; + 'status') + sys_status + ;; + *) + echo "Usage: systemctl {start|stop|restart|reload} system_safe" + ;; +esac \ No newline at end of file diff --git a/plugins/system_safe/install.sh b/plugins/system_safe/install.sh new file mode 100755 index 000000000..8140c6f18 --- /dev/null +++ b/plugins/system_safe/install.sh @@ -0,0 +1,48 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +SYSOS=`uname` +VERSION=$2 +APP_NAME=system_safe + +# cd /www/server/mdserver-web && python3 plugins/system_safe/system_safe.py stop 1.0 + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/system_safe + echo "$VERSION" > $serverPath/system_safe/version.pl + echo 'install complete' + + #初始化 + cd ${serverPath}/mdserver-web && python3 plugins/system_safe/system_safe.py start $VERSION + cd ${serverPath}/mdserver-web && python3 plugins/system_safe/system_safe.py initd_install $VERSION +} + +Uninstall_App() +{ + + if [ -f /usr/lib/systemd/system/${APP_NAME}.service ] || [ -f /lib/systemd/system/${APP_NAME}.service ] ;then + systemctl stop ${APP_NAME} + systemctl disable ${APP_NAME} + rm -rf /usr/lib/systemd/system/${APP_NAME}.service + rm -rf /lib/systemd/system/${APP_NAME}.service + systemctl daemon-reload + fi + + rm -rf $serverPath/system_safe + echo "uninstall completed" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/system_safe/js/app.js b/plugins/system_safe/js/app.js new file mode 100644 index 000000000..04aa5e5a9 --- /dev/null +++ b/plugins/system_safe/js/app.js @@ -0,0 +1,606 @@ +function ssPost(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + + + var req_data = {}; + req_data['name'] = 'system_safe'; + req_data['script'] = 'system_safe'; + req_data['func'] = method; + req_data['args'] = _args; + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function ssAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'system_safe', func:method, args:_args}); +} + +function ssPostCallbak(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'system_safe'; + req_data['func'] = method; + req_data['script'] = 'system_safe'; + args['version'] = '1.0'; + + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function getSafeConfigPathList(tag){ + ssPost('get_safe_data', {tag:tag}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var slist = rdata.data.paths; + + var body = ''; + for (var i = 0; i < slist.length; i++) { + body += ""; + body += ""+slist[i]['path']+""; + + if (slist[i]['chattr'] == 'i'){ + body += "只读"; + } else if (slist[i]['chattr'] == 'a'){ + body += "追加"; + } else{ + body += "只读"; + } + + if (rdata.msg['open']){ + body += ""+slist[i]['d_mode']+""; + body += "已保护"; + } else { + body += ""+slist[i]['s_mode']+""; + body += "未保护"; + } + body += "删除"; + body += ""; + } + + $('#safe-file-table tbody').html(body); + $('.safe_path_delete').click(function(){ + var id = $(this).attr('row'); + ssPost('del_safe_path',{tag:tag,index:id}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + getSafeConfigPathList(tag); + },{icon:rdata.status?1:2,shade: [0.3, '#000']},2000); + }); + }); + }); +} + +function setSafeConfigPath(tag, alist){ + + layer.open({ + type: 1, + area: ['700px','535px'], + title: '配置【'+alist['name']+'】', + content: "
                                  \ +
                                  \ + \ + \ + \ + \ + \ +
                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                  路径模式权限状态操作
                                  \ +
                                  \ +
                                  \ +
                                    \ +
                                  • 【只读】无法修改、创建、删除文件和目录
                                  • \ +
                                  • 【追加】只能追加内容,不能删除或修改原有内容
                                  • \ +
                                  • 【权限】设置文件或目录在受保护状态下的权限(非继承),关闭保护后权限自动还原
                                  • \ +
                                  • 【如何填写权限】请填写Linux权限代号,如:644、755、600、555等,如果不填写,则使用文件原来的权限
                                  • \ +
                                  \ +
                                  ", + success:function(){ + + $('.add_safe_config').click(function(){ + var path = $('input[name="s_path"]').val(); + var chattr = $('select[name="chattr"]').val(); + var d_mode = $('input[name="d_mode"]').val(); + ssPost('add_safe_path',{tag:tag,path:path,chattr:chattr,d_mode:d_mode}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + getSafeConfigPathList(tag); + },{icon:rdata.status?1:2,shade: [0.3, '#000']},2000); + }); + }); + } + }); + getSafeConfigPathList(tag); + + +} + +function setSafeConfigSsh(tag, alist){ + + + function setSafeConfigSshData(){ + ssPost('get_ssh_data', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var info = rdata.data; + + $('input[name="s_cycle"]').val(info['cycle']); + $('input[name="s_limit_count"]').val(info['limit_count']); + $('input[name="s_limit"]').val(info['limit']); + }); + } + layer.open({ + type: 1, + area: ['700px','210px'], + title: '配置【'+alist['name']+'】', + content: "
                                  \ +
                                  \ + 在(检测周期)\ + \ + 秒内,登录错误(检测阈值)\ + \ + 次,封锁(封锁时间)\ + \ + \ +
                                  \ +
                                    \ +
                                  • 触发以上策略后,客户端IP将被封锁一段时间
                                  • \ +
                                  • 请在面板日志或操作日志中查看封锁记录
                                  • \ +
                                  • 请在面板日志或操作日志中查看SSH成功登录的记录
                                  • \ +
                                  \ +
                                  ", + success:function(){ + setSafeConfigSshData(); + + $('.save_safe_ssh').click(function(){ + var cycle = $('input[name="s_cycle"]').val(); + var limit_count = $('input[name="s_limit_count"]').val(); + var limit = $('input[name="s_limit"]').val(); + ssPost('save_safe_ssh',{cycle:cycle,limit_count:limit_count,limit:limit}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + setSafeConfigSshData(); + },{icon:rdata.status?1:2,shade: [0.3, '#000']},2000); + }); + }); + } + }); + +} + +function setSafeConfigProcessList(tag){ + ssPost('get_process_data', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var slist = rdata.data.process_white; + + var body = ''; + for (var i = 0; i < slist.length; i++) { + body += "\ + "+slist[i]+"\ + 删除\ + "; + } + + $('#safe-file-table tbody').html(body); + $('.safe_path_delete').click(function(){ + var id = $(this).attr('row'); + ssPost('del_safe_proccess_name',{tag:tag,index:id}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + setSafeConfigProcessList(tag); + },{icon:rdata.status?1:2,shade: [0.3, '#000']},2000); + }); + }); + }); +} + +function setSafeConfigProcess(tag, alist){ + layer.open({ + type: 1, + area: ['700px','535px'], + title: '配置【进程白名单】', + content: "
                                  \ +
                                  \ + \ + \ +
                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                  进程名称操作
                                  \ +
                                  \ +
                                  \ +
                                    \ +
                                  • 【进程名称】请填写完整的进程名称,如: mysqld
                                  • \ +
                                  • 【说明】在白名单列表中的进程将不再检测范围,建议将常用软件的进程添加进白名单
                                  • \ +
                                  \ +
                                  ", + success:function(){ + $('.add_process_white').click(function(){ + var process_name = $('input[name="s_name"]').val(); + ssPost('add_process_white',{process_name:process_name}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + setSafeConfigProcessList(tag); + },{icon:rdata.status?1:2,shade: [0.3, '#000']},2000); + }); + }); + } + }); + + setSafeConfigProcessList(tag); + + +} + +function setSafeConfig(tag, alist){ + + if ($.inArray(tag, ['bin', 'service', 'home', 'user', 'bin', 'cron'])>-1){ + setSafeConfigPath(tag,alist); + } + + if ($.inArray(tag, ['ssh'])>-1){ + setSafeConfigSsh(tag,alist); + } + + if ($.inArray(tag, ['process'])>-1){ + setSafeConfigProcess(tag,alist); + } +} + +//设置安全状态 +function setSafeStatus(obj,tag){ + var o = $(obj).prev(); + var status = $(o).prop('checked'); + ssPost('set_safe_status', {tag:tag,status:!status}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + $(o).prop('checked',status); + return; + } + layer.msg("配置成功!",{icon:1,time:2000,shade: [0.3, '#000']}); + }); +} + +function ssConfigList(){ + + ssPost('conf',{},function(rdata){ + var rdata = $.parseJSON(rdata.data); + var libs = rdata.data; + // console.log(libs); + var body = ''; + for (var i in libs) { + + if (i == 'open'){ + continue; + } + + var checked = ''; + if (libs[i].open){ + checked = 'checked'; + } + + body += '' + + '' + libs[i].name + '' + + '' + libs[i].ps + '' + + '\ +
                                  \ + \ + \ +
                                  \ + '+ + '配置' + + ''; + } + + var con = '
                                  ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + body + '' + + '
                                  名称描述状态操作
                                  ' + + '
                                  ' + + '
                                    \ +
                                  • 开启系统加固功能后,一些如软件安装等敏感操作将被禁止
                                  • \ +
                                  • 开启【SSH服务加固】之后,用户登录SSH的行为将受到监控,若连续多次登录失败,将采取封IP措施
                                  • \ +
                                  • 【异常进程监控】与【SSH服务加固】会占用一定服务器开销
                                  • \ +
                                  • 【注意】如果您需要安装软件或插件,请先将系统加固关闭!
                                  • \ +
                                  '; + + $('.soft-man-con').html(con); + + $('#system_safe_list .service_switch').click(function(){ + var val = $(this).prev().attr('id'); + val = val.replace('sys_service_',''); + setSafeStatus(this,val); + }); + + $('.set_safe_config').click(function(){ + var name = $(this).attr('id'); + // console.log(name); + name = name.replace('conf_sys_service_',''); + setSafeConfig(name, libs[name]); + }); + + }); + +} + +function ssOpLogList(p){ + ssPost('op_log',{p:p},function(rdata){ + var rdata = $.parseJSON(rdata.data); + var plist = rdata.data.data; + + var body = ''; + for (var i = 0; i < plist.length; i++) { + body += "\ + " + plist[i].addtime + "\ + " + plist[i].log + "\ + "; + } + + $("#system_safe_log_list tbody").html(body); + $("#system_safe_log_page").html(rdata.data.page); + }); +} + +function ssOpLog(){ + var con = '
                                  \ + \ + \ + \ +
                                  时间详情
                                  \ +
                                  \ +
                                  '; + $('.soft-man-con').html(con); + ssOpLogList(1); +} + + +function ssLogAuditList(){ + ssPost('get_sys_logfiles',{},function(rdata){ + var rdata = $.parseJSON(rdata.data); + var plist = rdata.data; + var option = ''; + // console.log(plist); + for (var i = 0; i < plist.length; i++) { + option += ''; + } + + $("#system_safe_log_audit").html(option); + var log_name = $('#system_safe_log_audit option:first').val(); + ssLogAuditFile(log_name); + + $('#system_safe_log_audit').change(function(){ + var log_name = $('#system_safe_log_audit option:selected').val(); + ssLogAuditFile(log_name); + }); + }); +} + +function ssLogAuditFileRenderString(data){ + + var tbody = ''; + $("#system_safe_log_audit_list").html(tbody); + var ob = document.getElementById('system_safe_log_audit_str'); + ob.scrollTop = ob.scrollHeight; + + $("#system_safe_log_audit_str").html(data); +} + +function ssLogAuditFileRenderObject(plist){ + + var pre_html = '\ + \ + \ +
                                  时间角色事件
                                  '; + $("#system_safe_log_audit_list").html(pre_html); + + if (plist.length>0){ + var tmp = plist[0]; + // console.log(tmp); + var thead = ''; + tbody += '' + for (var i in tmp) { + tbody+=''+ i + ''; + } + tbody += ''; + $("#system_safe_log_audit_list thead").html(tbody); + } + + + var tbody = ''; + for (var i = 0; i < plist.length; i++) { + tbody += ''; + for (var vv in plist[i]) { + tbody+= ''+ plist[i][vv] + '' + } + tbody += ''; + } + + $("#system_safe_log_audit_list tbody").html(tbody); +} + +function ssLogAuditFile(log_name){ + // ssPost('get_sys_log',{log_name:log_name},function(rdata){ + // try{ + // var rdata = $.parseJSON(rdata.data); + // if (typeof(rdata.data) == 'object'){ + + // if (!rdata.status){ + // layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + // return; + // } + // var plist = rdata.data; + // ssLogAuditFileRenderObject(plist); + // } + // }catch(e){ + // if (typeof(rdata.data) == 'string'){ + // ssLogAuditFileRenderString(rdata.data); + // } + // } + // }); + + ssPostCallbak('get_sys_log',{log_name:log_name},function(rdata){ + try{ + var rdata = $.parseJSON(rdata.data); + if (typeof(rdata.data) == 'object'){ + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + var plist = rdata.data; + ssLogAuditFileRenderObject(plist); + } + }catch(e){ + if (typeof(rdata.data) == 'string'){ + ssLogAuditFileRenderString(rdata.data); + } + } + }); +} + +function ssLogAudit(){ + var con = '\ +
                                  '; + $('.soft-man-con').html(con); + ssLogAuditList(); +} + +function ssLockAddressList(){ + ssPost('get_ssh_limit_info',{},function(rdata){ + var rdata = $.parseJSON(rdata.data); + var libs = rdata.data; + var tbody = ''; + for (var i in libs) { + var end_time = libs[i].end; + var time_title = '手动解封'; + if (end_time != '0'){ + time_title = '自动解封时间:'+end_time; + } + tbody += '' + + '' + libs[i].address + '' + + ''+time_title+'' + + '立即解封' + + ''; + } + + $('#system_lock_address tbody').html(tbody); + $('#system_lock_address .remove_ssh_limit').click(function(){ + var address = $(this).attr('ip'); + ssPost('remove_ssh_limit', {ip:address}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + ssLockAddressList(); + },{icon:rdata.status?1:2,shade: [0.3, '#000']},2000); + }); + }); + }); +} + +function ssLockAddress(){ + + var con = '
                                  \ + \ + \ +
                                  \ +
                                  ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
                                  IP解封时间操作
                                  ' + + '
                                  ' + + '
                                    \ +
                                  • 【封锁IP】此处封锁的IP仅针对SSH服务,即被封锁的IP将无法连接SSH
                                  • \ +
                                  • 【添加】手动添加的IP封锁只能手动解封!
                                  • \ +
                                  '; + + $('.soft-man-con').html(con); + + $('.add_lock_address').click(function(){ + var address = $('input[name="s_address"]').val(); + ssPost('add_ssh_limit', {ip:address}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + ssLockAddressList(); + },{icon:rdata.status?1:2,shade: [0.3, '#000']},2000); + }); + }); + ssLockAddressList(); +} \ No newline at end of file diff --git a/plugins/system_safe/system_safe.py b/plugins/system_safe/system_safe.py new file mode 100755 index 000000000..62efb4e06 --- /dev/null +++ b/plugins/system_safe/system_safe.py @@ -0,0 +1,1145 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json +import re +import psutil +from datetime import datetime + +sys.dont_write_bytecode = True + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +class App: + + __deny = '/etc/hosts.deny' + __allow = '/etc/hosts.allow' + __state = {True: '开启', False: '关闭'} + __months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', + 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Sept': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'} + __name = '系统加固' + __deny_list = None + + __config = None + __log_file = None + __last_ssh_time = 0 + __last_ssh_size = 0 + + def __init__(self): + if mw.isAppleSystem(): + self.__deny = self.getServerDir() + '/hosts.deny' + self.__allow = self.getServerDir() + '/hosts.allow' + + def getPluginName(self): + return 'system_safe' + + def getPluginDir(self): + return mw.getPluginDir() + '/' + self.getPluginName() + + def getServerDir(self): + return mw.getServerDir() + '/' + self.getPluginName() + + def getInitDFile(self): + if app_debug: + return '/tmp/' + self.getPluginName() + return '/etc/init.d/' + self.getPluginName() + + def getInitDTpl(self): + path = self.getPluginDir() + "/init.d/" + self.getPluginName() + ".tpl" + return path + + def getArgs(self): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + def checkArgs(self, data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + def getServerConfPath(self): + return self.getServerDir() + "/config.json" + + def getConf(self): + cpath = self.getServerConfPath() + cpath_tpl = self.getPluginDir() + "/conf/config.json" + + if not os.path.exists(cpath): + t_data = mw.readFile(cpath_tpl) + t_data = json.loads(t_data) + t = json.dumps(t_data) + mw.writeFile(cpath, t) + return t_data + + t_data = mw.readFile(cpath) + t_data = json.loads(t_data) + return t_data + + def writeConf(self, data): + cpath = self.getServerConfPath() + tmp_conf = json.dumps(data) + mw.writeFile(cpath, tmp_conf) + + def initDreplace(self): + + file_tpl = self.getInitDTpl() + service_path = self.getServerDir() + + initD_path = service_path + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + + # init.d + file_bin = initD_path + '/' + self.getPluginName() + if not os.path.exists(file_bin): + # initd replace + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + # /usr/lib/systemd/system + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/system_safe.service' + systemServiceTpl = self.getPluginDir() + '/init.d/system_safe.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + def ssOp(self, method): + file = self.initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + + ' ' + self.getPluginName()) + if data[1] == '': + return 'ok' + return 'fail' + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return 'fail' + + # def status(self): + # data = self.getConf() + # if not data['open']: + # return 'stop' + # return 'start' + + def status(self): + data = mw.execShell( + 'ps -ef|grep "system_safe/system_safe.py bg_start" | grep -v grep | awk \'{print $2}\'') + if data[0] == '': + return 'stop' + return 'start' + + def start(self): + return self.ssOp('start') + + def writeLog(self, msg): + mw.writeDbLog(self.__name, msg) + + def getDenyList(self): + deny_file = self.getServerDir() + '/deny.json' + if not os.path.exists(deny_file): + mw.writeFile(deny_file, '{}') + self.__deny_list = json.loads(mw.readFile(deny_file)) + + # 存deny_list + def saveDeayList(self): + deny_file = self.getServerDir() + '/deny.json' + mw.writeFile(deny_file, json.dumps(self.__deny_list)) + + def get_ssh_limit(self): + data = self.getSshLimit() + return mw.returnJson(True, 'ok!', data) + + # 获取当前SSH禁止IP + def getSshLimit(self): + denyConf = mw.readFile(self.__deny) + if not denyConf: + mw.writeFile(self.__deny, '') + return [] + data = re.findall( + r"sshd:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):deny", denyConf) + return data + + def get_ssh_limit_info(self): + data = self.getSshLimitInfo() + return mw.returnJson(True, 'ok!', data) + + # 获取deny信息 + def getSshLimitInfo(self): + self.getDenyList() + conf_list = self.getSshLimit() + data = [] + for c_ip in conf_list: + tmp = {} + tmp['address'] = c_ip + tmp['end'] = 0 + if c_ip in self.__deny_list: + tmp['end'] = mw.getDataFromInt(self.__deny_list[c_ip]) + data.append(tmp) + return data + + def add_ssh_limit(self): + args = self.getArgs() + check = self.checkArgs(args, ['ip']) + if not check[0]: + return check[1] + ip = args['ip'] + return self.addSshLimit(ip) + + # 添加SSH目标IP + def addSshLimit(self, ip=None): + if ip == None: + ip = self.ip + + if ip in self.getSshLimit(): + return mw.returnJson(True, '指定IP黑名单已存在!') + denyConf = mw.readFile(self.__deny).strip() + while denyConf[-1:] == "\n" or denyConf[-1:] == " ": + denyConf = denyConf[:-1] + denyConf += "\nsshd:" + ip + ":deny\n" + mw.writeFile(self.__deny, denyConf) + if ip in self.getSshLimit(): + msg = u'添加IP[%s]到SSH-IP黑名单' % ip + self.writeLog(msg) + self.getDenyList() + if not ip in self.__deny_list: + self.__deny_list[ip] = 0 + self.saveDeayList() + return mw.returnJson(True, '添加成功!') + return mw.returnJson(False, '添加失败!') + + def remove_ssh_limit(self): + args = self.getArgs() + check = self.checkArgs(args, ['ip']) + if not check[0]: + return check[1] + ip = args['ip'] + return self.removeSshLimit(ip) + + # 删除IP黑名单 + def removeSshLimit(self, ip=None): + if ip == None: + ip = self.ip + + if not self.__deny_list: + self.getDenyList() + if ip in self.__deny_list: + del(self.__deny_list[ip]) + self.saveDeayList() + if not ip in self.getSshLimit(): + return mw.returnJson(True, '指定IP黑名单不存在!') + + denyConf = mw.readFile(self.__deny).strip() + while denyConf[-1:] == "\n" or denyConf[-1:] == " ": + denyConf = denyConf[:-1] + denyConf = re.sub("\n?sshd:" + ip + ":deny\n?", "\n", denyConf) + mw.writeFile(self.__deny, denyConf + "\n") + + msg = '从SSH-IP黑名单中解封[%s]' % ip + self.writeLog(msg) + return mw.returnJson(True, '解封成功!') + + def sshLoginTask(self): + if not self.__log_file: + log_file = '/var/log/secure' + if not os.path.exists(log_file): + log_file = '/var/log/auth.log' + if not os.path.exists(log_file): + return + self.__log_file = log_file + + if not self.__log_file: + return + + log_size = os.path.getsize(self.__log_file) + if self.__last_ssh_size == log_size: + return + self.__last_ssh_size = log_size + try: + config = self.__config + ssh_config = self.__config['ssh'] + line_num = ssh_config['limit_count'] * 20 + secure_logs = mw.getLastLine( + self.__log_file, line_num).split('\n') + + total_time = '/dev/shm/ssh_total_time.pl' + if not os.path.exists(total_time): + mw.writeFile(total_time, str(int(time.time()))) + + last_total_time = int(mw.readFile(total_time)) + now_total_time = int(time.time()) + + # print("last_total_time:", last_total_time) + # print("now_total_time:", now_total_time) + + self.getDenyList() + deny_list = list(self.__deny_list.keys()) + for i in range(len(deny_list)): + c_ip = deny_list[i] + if self.__deny_list[c_ip] > now_total_time or self.__deny_list[c_ip] == 0: + continue + self.ip = c_ip + self.removeSshLimit(None) + ip_total = {} + for log in secure_logs: + if log.find('Failed password for') != -1: + login_time = self.__to_date( + re.search(r'^\w+\s+\d+\s+\d+:\d+:\d+', log).group()) + if now_total_time - login_time >= ssh_config['cycle']: + continue + + client_ip = re.search(r'(\d+\.)+\d+', log).group() + if client_ip in self.__deny_list: + continue + if not client_ip in ip_total: + ip_total[client_ip] = 0 + ip_total[client_ip] += 1 + if ip_total[client_ip] <= ssh_config['limit_count']: + continue + self.__deny_list[ + client_ip] = now_total_time + ssh_config['limit'] + + self.saveDeayList() + self.ip = client_ip + + self.addSshLimit(None) + self.writeLog("[%s]在[%s]秒内连续[%s]次登录SSH失败,封锁[%s]秒" % ( + client_ip, ssh_config['cycle'], ssh_config['limit_count'], ssh_config['limit'])) + elif log.find('Accepted p') != -1: + login_time = self.__to_date( + re.search(r'^\w+\s+\d+\s+\d+:\d+:\d+', log).group()) + if login_time < last_total_time: + continue + client_ip = re.search(r'(\d+\.)+\d+', log).group() + login_user = re.findall(r"(\w+)\s+from", log)[0] + self.writeLog("用户[%s]成功登录服务器,用户IP:[%s],登录时间:[%s]" % ( + login_user, client_ip, time.strftime('%Y-%m-%d %X', time.localtime(login_time)))) + mw.writeFile(total_time, str(int(time.time()))) + except Exception as e: + print(mw.getTracebackInfo()) + return 'success' + + __limit = 30 + __vmsize = 1048576 * 100 + __wlist = None + __wslist = None + __elist = None + __last_pid_count = 0 + __last_return_time = 0 # 上次返回结果的时间 + __return_timeout = 360 + + def getSysProcess(self, get): + # 是否直接从属性中获取 + stime = time.time() + if stime - self.__last_return_time < self.__return_timeout: + if self.__sys_process: + return True + self.__last_return_time = stime + + # 本地是否存在系统进程白名单文件 + config_file = self.getServerDir() + '/sys_process.json' + is_force = False + if not os.path.exists(config_file): + mw.writeFile(config_file, json.dumps([])) + is_force = True + + data = json.loads(mw.readFile(config_file)) + self.__sys_process = data + return True + + # 取进程白名单列表 + def getProcessWhite(self, get): + data = self.getConf() + return data['process']['process_white'] + + # 取进程关键词 + def getProcessRule(self, get): + data = self.getConf() + return data['process']['process_white_rule'] + + # 取进程排除名单 + def getProcessExclude(self, get): + data = self.getConf() + return data['process']['process_exclude'] + + # 检查白名单 + def checkWhite(self, name): + if not self.__elist: + self.__elist = self.getProcessExclude(None) + if not self.__wlist: + self.__wlist = self.getProcessWhite(None) + if not self.__wslist: + self.__wslist = self.getProcessRule(None) + self.getSysProcess(None) + if name in ['mw', 'dnf', 'yum', + 'apt', 'apt-get', 'grep', + 'awk', 'python', 'node', 'php', 'mysqld', + 'httpd', 'openresty', 'wget', 'curl', 'openssl', + 'rspamd', 'supervisord', 'tlsmgr']: + return True + if name in self.__elist: + return True + if name in self.__sys_process: + return True + if name in self.__wlist: + return True + for key in self.__wslist: + if name.find(key) != -1: + return True + return False + + def checkMainProccess(self, pid): + if pid < 1100: + return + fname = '/proc/' + str(pid) + '/comm' + if not os.path.exists(fname): + return + name = mw.readFile(fname).strip() + is_num_name = re.match(r"^\d+$", name) + if not is_num_name: + if self.checkWhite(name): + return + try: + p = psutil.Process(pid) + percent = p.cpu_percent(interval=0.1) + vm = p.memory_info().vms + if percent > self.__limit or vm > self.__vmsize: + cmdline = ' '.join(p.cmdline()) + if cmdline.find('/www/server/cron') != -1: + return + if cmdline.find('/www/server') != -1: + return + if name.find('kworker') != -1 or name.find('mw_') == 0: + return + p.kill() + self.writeLog("已强制结束异常进程:[%s],PID:[%s],CPU:[%s],CMD:[%s]" % ( + name, pid, percent, cmdline)) + except: + print(mw.getTracebackInfo()) + return + + def checkMain(self): + pids = psutil.pids() + pid_count = len(pids) + if self.__last_pid_count == pid_count: + return + self.__last_pid_count = pid_count + + try: + for pid in pids: + self.checkMainProccess(pid) + except Exception as e: + print(mw.getTracebackInfo()) + + def processTask(self): + + if not self.__config: + self.__config = self.getConf() + if not self.__config: + return + + self.setOpen(1) + is_open = 0 + while True: + if self.__config['ssh']['open']: + is_open += 1 + self.sshLoginTask() + if self.__config['process']['open']: + is_open += 1 + self.checkMain() + + if is_open > 60: + self.__config = self.getConf() + is_open = 0 + time.sleep(1) + + def bg_start(self): + try: + self.processTask() + except Exception as e: + print(mw.getTracebackInfo()) + self.bg_stop() + self.writeLog('【{}】系统加固监控进程启动异常关闭'.format(mw.getDate())) + return 'ok' + + def bg_stop(self): + try: + self.setOpen(0) + except Exception as e: + print(mw.getTracebackInfo()) + print('【{}】系统加固监控进程停止异常关闭'.format(mw.getDate())) + return '' + + def stop(self): + return self.ssOp('stop') + + def restart(self): + return self.ssOp('restart') + + def reload(self): + return self.ssOp('reload') + + def initd_status(self): + if mw.isAppleSystem(): + return "Apple Computer does not support" + shell_cmd = 'systemctl status %s | grep loaded | grep "enabled;"' % ( + self.getPluginName()) + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + def initd_install(self): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + self.getPluginName()) + return 'ok' + + def initd_uninstall(self): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + self.getPluginName()) + return 'ok' + + def __lock_path(self, info): + try: + if not os.path.exists(info['path']): + return False + if info['d_mode']: + os.chmod(info['path'], info['d_mode']) + if info['chattr']: + cmd = "chattr -R +%s %s" % (info['chattr'], info['path']) + mw.execShell(cmd) + return True + except Exception as e: + + return False + + def __unlock_path(self, info): + try: + if not os.path.exists(info['path']): + return False + if info['chattr']: + cmd = "chattr -R -%s %s" % (info['chattr'], info['path']) + mw.execShell(cmd) + + if info['s_mode']: + os.chmod(info['path'], info['s_mode']) + return True + except Exception as e: + mw.getTracebackInfo() + return False + + def __set_safe_state(self, paths, lock=False): + for path_info in paths: + if lock: + self.__lock_path(path_info) + else: + self.__unlock_path(path_info) + return True + + # 转换时间格式 + def __to_date(self, date_str): + tmp = re.split(r'\s+', date_str) + s_date = str(datetime.now().year) + '-' + \ + self.__months.get(tmp[0]) + '-' + tmp[1] + ' ' + tmp[2] + time_array = time.strptime(s_date, "%Y-%m-%d %H:%M:%S") + time_stamp = int(time.mktime(time_array)) + return time_stamp + + def conf(self): + data = self.getConf() + return mw.returnJson(True, 'ok', data) + + def setOpen(self, is_open=-1): + cpath = self.getServerConfPath() + data = self.getConf() + if is_open != -1: + if is_open == 0: + data['open'] = False + else: + data['open'] = True + + for s_name in data.keys(): + if type(data[s_name]) == bool: + continue + if not 'name' in data[s_name]: + continue + if not 'paths' in data[s_name]: + continue + s_name_status = False + if data['open']: + s_name_status = True + # print(data[s_name]['paths'], data[s_name]['open']) + self.__set_safe_state(data[s_name]['paths'], s_name_status) + msg = '已[%s]系统加固功能' % self.__state[data['open']] + self.writeLog(msg) + self.writeConf(data) + + def set_safe_status(self): + args = self.getArgs() + data = self.checkArgs(args, ['tag', 'status']) + if not data[0]: + return data[1] + + tag = args['tag'] + status = args['status'] + + # 转换格式 + if status == 'false': + status = False + if status == 'true': + status = True + + if not tag in ['bin', 'service', 'home', 'user', 'bin', 'cron', 'ssh', 'process']: + return mw.returnJson(False, '不存在此配置[{}]!'.format(tag)) + + data = self.getConf() + data[tag]['open'] = status + self.writeConf(data) + return mw.returnJson(True, '设置成功') + + # 取文件或目录锁定状态 + def __get_path_state(self, path): + if not os.path.exists(path): + return 'i' + if os.path.isfile(path): + shell_cmd = "lsattr %s|awk '{print $1}'" % path + else: + shell_cmd = "lsattr {}/ |grep '{}$'|awk '{{print $1}}'".format( + os.path.dirname(path), path) + result = mw.execShell(shell_cmd)[0] + if result.find('-i-') != -1: + return 'i' + if result.find('-a-') != -1: + return 'a' + return False + + # 遍历当前防护状态 + def __list_safe_state(self, paths): + result = [] + for i in range(len(paths)): + if not os.path.exists(paths[i]['path']): + continue + if os.path.islink(paths[i]['path']): + continue + mstate = self.__get_path_state(paths[i]['path']) + paths[i]['state'] = mstate == paths[i]['chattr'] + paths[i]['s_mode'] = oct(paths[i]['s_mode']) + paths[i]['d_mode'] = oct(paths[i]['d_mode']) + result.append(paths[i]) + return result + + def get_safe_data(self): + + args = self.getArgs() + check = self.checkArgs(args, ['tag']) + if not check[0]: + return check[1] + + tag = args['tag'] + if not tag in ['bin', 'service', 'home', 'user', 'bin', 'cron']: + return mw.returnJson(False, '不存在此配置[{}]!'.format(tag)) + + cpath = self.getServerConfPath() + data = self.getConf() + tmp = data[tag] + tmp['paths'] = self.__list_safe_state(tmp['paths']) + return mw.returnJson(True, {'open': data['open']}, tmp) + + def get_ssh_data(self): + data = self.getConf() + tmp = data['ssh'] + return mw.returnJson(True, {'open': data['open']}, tmp) + + def get_process_data(self): + data = self.getConf() + tmp = data['process'] + return mw.returnJson(True, {'open': data['open']}, tmp) + + # 添加防护对象 + def add_safe_path(self): + args = self.getArgs() + check = self.checkArgs(args, ['tag', 'path', 'chattr', 'd_mode']) + if not check[0]: + return check[1] + + path = args['path'] + tag = args['tag'] + chattr = args['chattr'] + d_mode = args['d_mode'] + if path[-1] == '/': + path = path[:-1] + if not os.path.exists(path): + return mw.returnJson(False, '指定文件或目录不存在!') + data = self.getConf() + + for m_path in data[tag]['paths']: + if path == m_path['path']: + return mw.returnJson(False, '指定文件或目录已经添加过了!') + + path_info = {} + path_info['path'] = path + path_info['chattr'] = chattr + path_info['s_mode'] = int(oct(os.stat(path).st_mode)[-3:], 8) + if d_mode != '': + path_info['d_mode'] = int(d_mode, 8) + else: + path_info['d_mode'] = path_info['s_mode'] + + data[tag]['paths'].insert(0, path_info) + if 'paths' in data[tag]: + mw.execShell('chattr -R -%s %s' % + (path_info['chattr'], path_info['path'])) + if data['open']: + self.__set_safe_state([path_info], data[tag]['open']) + msg = '添加防护对象[%s]到[%s]' % (path, data[tag]['name']) + self.writeLog(msg) + self.writeConf(data) + return mw.returnJson(True, msg) + + def save_safe_ssh(self): + + args = self.getArgs() + check = self.checkArgs(args, ['cycle', 'limit', 'limit_count']) + if not check[0]: + return check[1] + + cycle = int(args['cycle']) + limit = int(args['limit']) + limit_count = int(args['limit_count']) + + if cycle > limit: + return mw.returnJson(False, '封锁时间不能小于检测周期!') + if cycle < 30 or cycle > 1800: + return mw.returnJson(False, '检测周期的值必需在30 - 1800秒之间!') + if limit < 60: + return mw.returnJson(False, '封锁时间不能小于60秒') + if limit_count < 3 or limit_count > 100: + return mw.returnJson(False, '检测阈值必需在3 - 100秒之间!') + data = self.getConf() + data['ssh']['cycle'] = cycle + data['ssh']['limit'] = limit + data['ssh']['limit_count'] = limit_count + self.writeConf(data) + msg = '修改SSH策略: 在[%s]秒内,登录错误[%s]次,封锁[%s]秒' % ( + data['ssh']['cycle'], data['ssh']['limit_count'], data['ssh']['limit']) + self.writeLog(msg) + self.restart() + return mw.returnJson(True, '配置已保存!') + + # 添加进程白名单 + def add_process_white(self): + args = self.getArgs() + check = self.checkArgs(args, ['process_name']) + if not check[0]: + return check[1] + + data = self.getConf() + process_name = args['process_name'] + if process_name in data['process']['process_white']: + return mw.returnJson(False, '指定进程名已在白名单') + data['process']['process_white'].insert(0, process_name) + self.writeConf(data) + msg = '添加进程名[%s]到进程白名单' % process_name + self.writeLog(msg) + self.restart() + return mw.returnJson(True, msg) + + def del_safe_proccess_name(self): + args = self.getArgs() + check = self.checkArgs(args, ['tag', 'index']) + if not check[0]: + return check[1] + + tag = args['tag'] + index = int(args['index']) + + cpath = self.getServerConfPath() + data = self.getConf() + + del(data[tag]['process_white'][index]) + t = json.dumps(data) + mw.writeFile(cpath, t) + return mw.returnJson(True, '删除成功') + + def del_safe_path(self): + args = self.getArgs() + check = self.checkArgs(args, ['tag', 'index']) + if not check[0]: + return check[1] + + tag = args['tag'] + index = int(args['index']) + + cpath = self.getServerConfPath() + data = self.getConf() + + del(data[tag]['paths'][index]) + t = json.dumps(data) + mw.writeFile(cpath, t) + return mw.returnJson(True, '删除成功') + + def get_log_title(self, log_name): + log_name = log_name.replace('.1', '') + if log_name in ['auth.log', 'secure'] or log_name.find('auth.') == 0: + return '授权日志' + if log_name in ['dmesg'] or log_name.find('dmesg') == 0: + return '内核缓冲区日志' + if log_name in ['syslog'] or log_name.find('syslog') == 0: + return '系统警告/错误日志' + if log_name in ['btmp']: + return '失败的登录记录' + if log_name in ['utmp', 'wtmp']: + return '登录和重启记录' + if log_name in ['lastlog']: + return '用户最后登录' + if log_name in ['yum.log']: + return 'yum包管理器日志' + if log_name in ['anaconda.log']: + return 'Anaconda日志' + if log_name in ['dpkg.log']: + return 'dpkg日志' + if log_name in ['daemon.log']: + return '系统后台守护进程日志' + if log_name in ['boot.log']: + return '启动日志' + if log_name in ['kern.log']: + return '内核日志' + if log_name in ['maillog', 'mail.log']: + return '邮件日志' + if log_name.find('Xorg') == 0: + return 'Xorg日志' + if log_name in ['cron.log']: + return '定时任务日志' + if log_name in ['alternatives.log']: + return '更新替代信息' + if log_name in ['debug']: + return '调试信息' + if log_name.find('apt') == 0: + return 'apt-get相关日志' + if log_name.find('installer') == 0: + return '系统安装相关日志' + if log_name in ['messages']: + return '综合日志' + return '{}日志'.format(log_name.split('.')[0]) + + def get_sys_logfiles(self): + log_dir = '/var/log' + log_files = [] + for log_file in os.listdir(log_dir): + + log_suffix = log_file.split('.')[-1:] + if log_suffix[0] in ['gz', 'xz', 'bz2', 'asl']: + continue + + if log_file in ['.', '..', 'faillog', 'fontconfig.log', 'unattended-upgrades', 'tallylog']: + continue + + # print(log_suffix) + filename = os.path.join(log_dir, log_file) + if os.path.isfile(filename): + file_size = os.path.getsize(filename) + if not file_size: + continue + + tmp = { + 'name': log_file, + 'size': file_size, + 'log_file': filename, + 'title': self.get_log_title(log_file), + 'uptime': os.path.getmtime(filename) + } + + log_files.append(tmp) + else: + for next_name in os.listdir(filename): + if next_name[-3:] in ['.gz', '.xz']: + continue + next_file = os.path.join(filename, next_name) + if not os.path.isfile(next_file): + continue + file_size = os.path.getsize(next_file) + if not file_size: + continue + log_name = '{}/{}'.format(log_file, next_name) + tmp = { + 'name': log_name, + 'size': file_size, + 'log_file': next_file, + 'title': self.get_log_title(log_name), + 'uptime': os.path.getmtime(next_file) + } + log_files.append(tmp) + log_files = sorted(log_files, key=lambda x: x['name'], reverse=True) + return mw.returnJson(True, 'ok', log_files) + + def get_last(self, log_name): + # 获取日志 + cmd = '''LANG=en_US.UTF-8 last -n 200 -x -f {} |grep -v 127.0.0.1|grep -v " begins"'''.format( + '/var/log/' + log_name) + result = mw.execShell(cmd) + lastlog_list = [] + for _line in result[0].split("\n"): + if not _line: + continue + tmp = {} + sp_arr = _line.split() + tmp['用户'] = sp_arr[0] + if sp_arr[0] == 'runlevel': + tmp['来源'] = sp_arr[4] + tmp['端口'] = ' '.join(sp_arr[1:4]) + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[5:])) + ' ' + ' '.join(sp_arr[-2:]) + elif sp_arr[0] in ['reboot', 'shutdown']: + tmp['来源'] = sp_arr[3] + tmp['端口'] = ' '.join(sp_arr[1:3]) + if sp_arr[-3] == '-': + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[4:])) + ' ' + ' '.join(sp_arr[-3:]) + else: + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[4:])) + ' ' + ' '.join(sp_arr[-2:]) + elif sp_arr[1] in ['tty1', 'tty', 'tty2', 'tty3', 'hvc0', 'hvc1', 'hvc2'] or len(sp_arr) == 9: + tmp['来源'] = '' + tmp['端口'] = sp_arr[1] + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[2:])) + ' ' + ' '.join(sp_arr[-3:]) + else: + tmp['来源'] = sp_arr[2] + tmp['端口'] = sp_arr[1] + tmp['时间'] = self.__to_date3( + ' '.join(sp_arr[3:])) + ' ' + ' '.join(sp_arr[-3:]) + + # tmp['_line'] = _line + lastlog_list.append(tmp) + # lastlog_list = sorted(lastlog_list,key=lambda x:x['时间'],reverse=True) + return mw.returnJson(True, 'ok!', lastlog_list) + + def get_lastlog(self): + cmd = '''LANG=en_US.UTF-8 lastlog|grep -v Username''' + result = mw.execShell(cmd) + lastlog_list = [] + for _line in result[0].split("\n"): + if not _line: + continue + tmp = {} + sp_arr = _line.split() + tmp['用户'] = sp_arr[0] + # tmp['_line'] = _line + if _line.find('Never logged in') != -1: + tmp['最后登录时间'] = '0' + tmp['最后登录来源'] = '-' + tmp['最后登录端口'] = '-' + lastlog_list.append(tmp) + continue + tmp['最后登录来源'] = sp_arr[2] + tmp['最后登录端口'] = sp_arr[1] + tmp['最后登录时间'] = self.__to_date2(' '.join(sp_arr[3:])) + + lastlog_list.append(tmp) + lastlog_list = sorted(lastlog_list, key=lambda x: x[ + '最后登录时间'], reverse=True) + for i in range(len(lastlog_list)): + if lastlog_list[i]['最后登录时间'] == '0': + lastlog_list[i]['最后登录时间'] = '从未登录过' + return mw.returnJson(True, 'ok!', lastlog_list) + + def get_sys_log_with_name(self, log_name): + if log_name in ['wtmp', 'btmp', 'utmp'] or log_name.find('wtmp') == 0 or log_name.find('btmp') == 0 or log_name.find('utmp') == 0: + return self.get_last(log_name) + + if log_name.find('lastlog') == 0: + return self.get_lastlog() + + if log_name.find('sa/sa') == 0: + if log_name.find('sa/sar') == -1: + return mw.execShell("sar -f /var/log/{}".format(log_name))[0] + log_dir = '/var/log' + log_file = log_dir + '/' + log_name + if not os.path.exists(log_file): + return mw.returnJson(False, '日志文件不存在!') + result = mw.getLastLine(log_file, 100) + log_list = [] + is_string = True + for _line in result.split("\n"): + if not _line.strip(): + continue + if log_name.find('sa/sa') == -1: + if _line[:3] in self.__months: + _msg = _line[16:] + _tmp = _msg.split(": ") + _act = '' + if len(_tmp) > 1: + _act = _tmp[0] + _msg = _tmp[1] + else: + _msg = _tmp[0] + _line = { + "时间": self.__to_date4(_line[:16].strip()), + "角色": _act, + "事件": _msg + } + is_string = False + elif _line[:2] in ['19', '20', '21', '22', '23', '24']: + _msg = _line[19:] + _tmp = _msg.split(" ") + _act = _tmp[1] + _msg = ' '.join(_tmp[2:]) + _line = { + "时间": _line[:19].strip(), + "角色": _act, + "事件": _msg + } + is_string = False + elif log_name.find('alternatives') == 0: + _tmp = _line.split(": ") + _last = _tmp[0].split(" ") + _act = _last[0] + _msg = ' '.join(_tmp[1:]) + _line = { + "时间": ' '.join(_last[1:]).strip(), + "角色": _act, + "事件": _msg + } + is_string = False + else: + if not is_string: + if type(_line) != dict: + continue + + log_list.append(_line) + try: + # if len(log_list) > 1: + # if type(log_list[0]) != type(log_list[1]): + # del(log_list[0]) + # log_list = sorted(log_list,key=lambda x:x['时间'],reverse=True) + # return log_list + + _string = [] + _dict = [] + _list = [] + for _line in log_list: + if isinstance(_line, str): + _string.append(_line.strip()) + elif isinstance(_line, dict): + _dict.append(_line) + elif isinstance(_line, list): + _list.append(_line) + else: + continue + _str_len = len(_string) + _dict_len = len(_dict) + _list_len = len(_list) + if _str_len > _dict_len + _list_len: + return "\n".join(_string) + elif _dict_len > _str_len + _list_len: + return mw.returnJson(True, 'ok!', _dict) + else: + return mw.returnJson(True, 'ok!', _list) + + except: + data = '\n'.join(log_list) + return mw.returnJson(True, 'ok!', data) + + def get_sys_log(self): + args = self.getArgs() + check = self.checkArgs(args, ['log_name']) + if not check[0]: + return check[1] + + log_name = args['log_name'] + return self.get_sys_log_with_name(log_name) + + def __to_date2(self, date_str): + tmp = date_str.split() + s_date = str(tmp[-1]) + '-' + self.__months.get(tmp[1], + tmp[1]) + '-' + tmp[2] + ' ' + tmp[3] + return s_date + + def __to_date3(self, date_str): + tmp = date_str.split() + s_date = str(datetime.now().year) + '-' + \ + self.__months.get(tmp[1], tmp[1]) + '-' + tmp[2] + ' ' + tmp[3] + return s_date + + def __to_date4(self, date_str): + tmp = date_str.split() + s_date = str(datetime.now().year) + '-' + \ + self.__months.get(tmp[0], tmp[0]) + '-' + tmp[1] + ' ' + tmp[2] + return s_date + + def op_log(self): + args = self.getArgs() + check = self.checkArgs(args, ['p']) + if not check[0]: + return check[1] + + p = int(args['p']) + limit = 10 + start = (p - 1) * limit + + _list = mw.M('logs').field( + 'id,type,log,addtime').where('type=?', (self.__name,)).limit(str(start) + ',' + str(limit)).order('id desc').select() + data = {} + data['data'] = _list + count = mw.M('logs').where('type=?', (self.__name,)).count() + _page = {} + _page['count'] = count + _page['tojs'] = 'ssOpLogList' + _page['p'] = p + data['page'] = mw.getPage(_page) + return mw.returnJson(True, 'ok', data) + + +def get_sys_log(args): + classApp = App() + data = classApp.get_sys_log_with_name(args['log_name']) + return data + +if __name__ == "__main__": + func = sys.argv[1] + classApp = App() + try: + data = eval("classApp." + func + "()") + print(data) + except Exception as e: + print(mw.getTracebackInfo()) diff --git a/plugins/tamper_proof_py/conf/config.json b/plugins/tamper_proof_py/conf/config.json new file mode 100644 index 000000000..8a372031a --- /dev/null +++ b/plugins/tamper_proof_py/conf/config.json @@ -0,0 +1,5 @@ +{ + "open": true, + "excludePath": [ "cache", "threadcache", "log", "logs", "config", "runtime", "temp","tmp","caches","tmps"], + "protectExt": [ "php", "html", "htm", "shtml", "tpl", "js", "css", "jsp", "do" ] +} \ No newline at end of file diff --git a/plugins/tamper_proof_py/ico.png b/plugins/tamper_proof_py/ico.png new file mode 100644 index 000000000..3dede4e91 Binary files /dev/null and b/plugins/tamper_proof_py/ico.png differ diff --git a/plugins/tamper_proof_py/index.html b/plugins/tamper_proof_py/index.html new file mode 100755 index 000000000..cb5de195f --- /dev/null +++ b/plugins/tamper_proof_py/index.html @@ -0,0 +1,1163 @@ + +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + \ No newline at end of file diff --git a/plugins/tamper_proof_py/index.py b/plugins/tamper_proof_py/index.py new file mode 100755 index 000000000..59d538d4b --- /dev/null +++ b/plugins/tamper_proof_py/index.py @@ -0,0 +1,629 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json +import re +import psutil +from datetime import datetime + +sys.dont_write_bytecode = True +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.site import sites as MwSites + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +class App: + + __total = 'total.json' + __sites = [] + + def __init__(self): + pass + + def getPluginName(self): + return 'tamper_proof_py' + + def getPluginDir(self): + return mw.getPluginDir() + '/' + self.getPluginName() + + def getServerDir(self): + return mw.getServerDir() + '/' + self.getPluginName() + + def getInitDFile(self): + if app_debug: + return '/tmp/' + self.getPluginName() + return '/etc/init.d/' + self.getPluginName() + + def getInitDTpl(self): + path = self.getPluginDir() + "/init.d/" + self.getPluginName() + ".tpl" + return path + + def getArgs(self): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + def checkArgs(self, data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + def getTotal(self, siteName=None, day=None): + defaultTotal = {"total": 0, "delete": 0, + "create": 0, "modify": 0, "move": 0} + if siteName: + total = {} + total_path = self.getServerDir() + '/sites/' + siteName + '/' + self.__total + if not os.path.exists(total_path): + total['site'] = defaultTotal + else: + total_data = mw.readFile(total_path) + if total_data['site']: + total['site'] = json.loads(total_data['site']) + else: + total['site'] = defaultTotal + + if not day: + day = time.strftime("%Y-%m-%d", time.localtime()) + total_day_path = self.getServerDir() + '/sites/' + siteName + '/day/total.json' + if not os.path.exists(total_day_path): + total['day'] = defaultTotal + else: + total['day'] = mw.readFile(total_day_path) + if total['day']: + total['day'] = json.loads(total['day']) + else: + total['day'] = defaultTotal + else: + filename = self.getServerDir() + '/sites/' + self.__total + if os.path.exists(filename): + total = json.loads(mw.readFile(filename)) + else: + total = defaultTotal + return total + + def getSites(self): + sites_path = self.getServerDir() + '/sites.json' + t = mw.readFile(sites_path) + if not os.path.exists(sites_path) or not t: + mw.writeFile(sites_path, '[]') + data = json.loads(mw.readFile(sites_path)) + + is_write = False + rm_keys = ['lock', 'bak_open'] + for i in data: + i_keys = i.keys() + if not 'open' in i_keys: + i['open'] = False + for o in rm_keys: + if o in i_keys: + if i[o]: + i['open'] = True + i.pop(o) + is_write = True + if is_write: + mw.writeFile(sites_path, json.dumps(data)) + + self.__sites = data + return data + + def writeSites(self, data): + mw.writeFile(self.getServerDir() + '/sites.json', json.dumps(data)) + # mw.ExecShell('/etc/init.d/bt_tamper_proof reload') + + def __getFind(self, siteName): + data = self.getSites() + for siteInfo in data: + if siteName == siteInfo['siteName']: + return siteInfo + return None + + def writeLog(self, log): + mw.writeLog('防篡改程序', log) + + def saveSiteConfig(self, siteInfo): + data = self.getSites() + for i in range(len(data)): + if data[i]['siteName'] != siteInfo['siteName']: + continue + data[i] = siteInfo + break + self.writeSites(data) + + def syncSites(self): + data = self.getSites() + sites = mw.M('sites').field('name,path').select() + + config_path = self.getPluginDir() + '/conf/config.json' + config = json.loads(mw.readFile(config_path)) + names = [] + n = 0 + + # print(config) + for siteTmp in sites: + names.append(siteTmp['name']) + siteInfo = self.__getFind(siteTmp['name']) + if siteInfo: + if siteInfo['path'] != siteTmp['path']: + siteInfo['path'] = siteTmp['path'] + self.saveSiteConfig(siteInfo) + data = self.getSites() + continue + siteInfo = {} + siteInfo['siteName'] = siteTmp['name'] + siteInfo['path'] = siteTmp['path'] + siteInfo['open'] = False + siteInfo['excludePath'] = config['excludePath'] + siteInfo['protectExt'] = config['protectExt'] + data.append(siteInfo) + n += 1 + + newData = [] + for siteInfoTmp in data: + if siteInfoTmp['siteName'] in names: + newData.append(siteInfoTmp) + else: + mw.execShell("rm -rf " + self.getServerDir() + + '/sites/' + siteInfoTmp['siteName']) + n += 1 + if n > 0: + self.writeSites(newData) + self.__sites = None + + def initDreplace(self): + file_tpl = self.getInitDTpl() + service_path = self.getServerDir() + + initD_path = service_path + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + + # init.d + file_bin = initD_path + '/' + self.getPluginName() + if not os.path.exists(file_bin): + # initd replace + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + # /usr/lib/systemd/system + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/tamper_proof_py.service' + systemServiceTpl = self.getPluginDir() + '/init.d/tamper_proof_py.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + def getDays(self, path): + days = [] + if not os.path.exists(path): + os.makedirs(path) + for dirname in os.listdir(path): + if dirname == '..' or dirname == '.' or dirname == 'total.json': + continue + if not os.path.isdir(path + '/' + dirname): + continue + days.append(dirname) + days = sorted(days, reverse=True) + return days + + def status(self): + ''' + 状态 + ''' + initd_file = self.getServerDir() + '/init.d/' + self.getPluginName() + if not os.path.exists(initd_file): + return 'stop' + cmd = initd_file + ' status|grep already' + data = mw.execShell(cmd) + if data[0] != '': + return 'start' + return 'stop' + + def tpOp(self, method): + file = self.initDreplace() + if not mw.isAppleSystem(): + cmd = 'systemctl ' + method + ' ' + self.getPluginName() + data = mw.execShell(cmd) + if data[1] == '': + return mw.returnJson(True, '操作成功') + return mw.returnJson(False, '操作失败') + + cmd = file + ' ' + method + data = mw.execShell(cmd) + if data[1] == '': + return mw.returnJson(True, '操作成功') + return mw.returnJson(False, '操作失败') + + def start(self): + return self.tpOp('start') + + def restart(self): + return self.tpOp('restart') + + def service_admin(self): + if mw.isAppleSystem(): + return mw.returnJson(False, '仅支持Linux!') + + args = self.getArgs() + check = self.checkArgs(args, ['serviceStatus']) + if not check[0]: + return check[1] + + method = args['serviceStatus'] + return self.tpOp(method) + + def initd_status(self): + if mw.isAppleSystem(): + return "Apple Computer does not support" + shell_cmd = 'systemctl status %s | grep loaded | grep "enabled;"' % ( + self.getPluginName()) + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + def initd_install(self): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + self.getPluginName()) + return 'ok' + + def initd_uninstall(self): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + self.getPluginName()) + return 'ok' + + def set_site_status(self): + args = self.getArgs() + check = self.checkArgs(args, ['siteName']) + if not check[0]: + return check[1] + + siteName = args['siteName'] + siteInfo = self.__getFind(siteName) + if not siteInfo: + return mw.returnJson(False, '指定站点不存在!') + try: + siteInfo['open'] = not siteInfo['open'] + except: + siteInfo['open'] = not siteInfo['open'] + + m_logs = {True: '开启', False: '关闭'} + self.writeLog('%s站点[%s]防篡改保护' % (m_logs[siteInfo['open']], siteInfo['siteName'])) + self.siteReload(siteInfo) + self.saveSiteConfig(siteInfo) + self.restart() + return mw.returnJson(True, '设置成功!') + + def get_run_logs(self): + log_file = self.getServerDir() + '/service.log' + return mw.returnJson(True, mw.getLastLine(log_file, 200)) + + # 取文件指定尾行数 + def getNumLines(self, path, num, p=1): + pyVersion = sys.version_info[0] + try: + import cgi + if not os.path.exists(path): + return "" + start_line = (p - 1) * num + count = start_line + num + fp = open(path, 'rb') + buf = "" + fp.seek(-1, 2) + if fp.read(1) == "\n": + fp.seek(-1, 2) + data = [] + b = True + n = 0 + for i in range(count): + while True: + newline_pos = str.rfind(str(buf), "\n") + pos = fp.tell() + if newline_pos != -1: + if n >= start_line: + line = buf[newline_pos + 1:] + try: + data.append(json.loads(cgi.escape(line))) + except: + pass + buf = buf[:newline_pos] + n += 1 + break + else: + if pos == 0: + b = False + break + to_read = min(4096, pos) + fp.seek(-to_read, 1) + t_buf = fp.read(to_read) + if pyVersion == 3: + if type(t_buf) == bytes: + t_buf = t_buf.decode('utf-8') + buf = t_buf + buf + fp.seek(-to_read, 1) + if pos - to_read == 0: + buf = "\n" + buf + if not b: + break + fp.close() + except: + return [] + if len(data) >= 2000: + arr = [] + for d in data: + arr.insert(0, json.dumps(d)) + mw.writeFile(path, "\n".join(arr)) + return data + + def get_safe_logs(self): + + args = self.getArgs() + check = self.checkArgs(args, ['siteName']) + if not check[0]: + return check[1] + + siteName = args['siteName'] + + data = {} + path = self.getPluginDir() + '/sites/' + siteName + '/day' + data['days'] = self.getDays(path) + + if not data['days']: + data['logs'] = [] + else: + p = 1 + if hasattr(args, 'p'): + p = args['p'] + + day = data['days'][0] + if hasattr(args, 'day'): + day = args['day'] + data['get_day'] = day + logs_path = path + '/' + day + '/logs.json' + data['logs'] = self.getNumLines(logs_path, 2000, int(p)) + return mw.returnJson(True, 'ok', data) + + def get_site_find(self): + args = self.getArgs() + check = self.checkArgs(args, ['siteName']) + if not check[0]: + return check[1] + + siteName = args['siteName'] + data = self.__getFind(siteName) + return mw.returnJson(True, 'ok', data) + + def siteReload(self, siteInfo): + cmd = "python3 {} {}".format( + mw.getPluginDir() + '/tamper_proof_service.py unlock', siteInfo['path']) + mw.execShell(cmd) + tip_file = mw.getServerDir() + '/tips/' + siteInfo['siteName'] + '.pl' + if os.path.exists(tip_file): + os.remove(tip_file) + + def remove_protect_ext(self): + args = self.getArgs() + check = self.checkArgs(args, ['siteName', 'protectExt']) + if not check[0]: + return check[1] + + siteName = args['siteName'] + protectExt = args['protectExt'].strip() + + siteInfo = self.__getFind(siteName) + + if not siteInfo: + return mw.returnJson(False, '指定站点不存在!') + if not protectExt: + return mw.returnJson(False, '被删除的保护列表不能为空') + + for protectExt in protectExt.split(','): + if not protectExt in siteInfo['protectExt']: + continue + siteInfo['protectExt'].remove(protectExt) + self.writeLog('站点[%s]从受保护列表中删除[.%s]' % (siteInfo['siteName'], protectExt)) + self.siteReload(siteInfo) + self.saveSiteConfig(siteInfo) + return mw.returnJson(True, '删除成功!') + + def add_protect_ext(self): + args = self.getArgs() + check = self.checkArgs(args, ['siteName', 'protectExt']) + if not check[0]: + return check[1] + + siteName = args['siteName'] + protectExt = args['protectExt'].strip() + + siteInfo = self.__getFind(siteName) + if not siteInfo: + return mw.returnJson(False, '指定站点不存在!') + protectExt = protectExt.lower() + for protectExt in protectExt.split("\n"): + if protectExt[0] == '/': + if os.path.isdir(protectExt): + continue + if protectExt in siteInfo['protectExt']: + continue + siteInfo['protectExt'].insert(0, protectExt) + self.writeLog('站点[%s]添加文件类型或文件名[.%s]到受保护列表' % + (siteInfo['siteName'], protectExt)) + self.siteReload(siteInfo) + self.saveSiteConfig(siteInfo) + return mw.returnJson(True, '添加成功!') + + def add_excloud(self): + args = self.getArgs() + check = self.checkArgs(args, ['siteName', 'excludePath']) + if not check[0]: + return check[1] + + siteName = args['siteName'] + excludePath = args['excludePath'].strip() + siteInfo = self.__getFind(siteName) + if not siteInfo: + return mw.returnJson(False, '指定站点不存在!') + + if not excludePath: + return mw.returnJson(False, '排除内容不能为空') + + for excludePath in excludePath.split('\n'): + if not excludePath: + continue + if excludePath.find('/') != -1: + if not os.path.exists(excludePath): + continue + excludePath = excludePath.lower() + if excludePath[-1] == '/': + excludePath = excludePath[:-1] + if excludePath in siteInfo['excludePath']: + continue + siteInfo['excludePath'].insert(0, excludePath) + self.writeLog('站点[%s]添加排除目录名[%s]到排除列表' % + (siteInfo['siteName'], excludePath)) + + self.siteReload(siteInfo) + self.saveSiteConfig(siteInfo) + return mw.returnJson(True, '添加成功!') + + def remove_excloud(self): + args = self.getArgs() + check = self.checkArgs(args, ['siteName', 'excludePath']) + if not check[0]: + return check[1] + + siteName = args['siteName'] + siteInfo = self.__getFind(siteName) + excludePath = args['excludePath'].strip() + if excludePath == '': + return mw.returnJson(False, '排除文件或目录不能为空') + if not siteInfo: + return mw.returnJson(False, '指定站点不存在!') + + for excludePath in excludePath.split(','): + if not excludePath: + continue + if not excludePath in siteInfo['excludePath']: + continue + siteInfo['excludePath'].remove(excludePath) + self.writeLog('站点[%s]从排除列表中删除目录名[%s]' % + (siteInfo['siteName'], excludePath)) + self.siteReload(siteInfo) + self.saveSiteConfig(siteInfo) + return mw.returnJson(True, '删除成功!') + + def sim_test(self): + args = self.getArgs() + check = self.checkArgs(args, ['path']) + if not check[0]: + return check[1] + + path = args['path'].strip() + if not os.path.exists(path): + return mw.returnJson(False, "此目录不存在") + + # 判断是否安装php + php_version = MwSites.instance().getPhpVersion() + if not php_version['data']: + return mw.returnJson(False, "未安装PHP测试失败") + + php_path = '/www/server/php/' + php_version['data'][1]['version'] + '/bin/php' + php_name = path + "/" + str(int(time.time())) + ".php" + if os.path.exists(php_name): + mw.execShell("rm -rf %s" % php_name) + # 写入 + cmd = php_path + \ + " -r \"file_put_contents('{}','{}');\"".format(php_name, php_name) + mw.execShell(cmd) + time.sleep(0.5) + if os.path.exists(php_name): + if os.path.exists(php_name): + mw.execShell("rm -rf %s" % php_name) + return mw.returnJson(False, "拦截失败,可能未开启防篡改") + return mw.returnJson(True, "拦截成功") + + def set_site_status_all(self): + args = self.getArgs() + check = self.checkArgs(args, ['siteNames', 'siteState']) + if not check[0]: + return check[1] + + sites = self.getSites() + siteState = True if args['siteState'] == '1' else False + siteNames = json.loads(args['siteNames']) + m_logs = {True: '开启', False: '关闭'} + for i in range(len(sites)): + if sites[i]['siteName'] in siteNames: + sites[i]['open'] = siteState + self.writeLog('%s站点[%s]防篡改保护' % (m_logs[siteState], sites[i]['siteName'])) + self.writeSites(sites) + return mw.returnJson(True, '批量设置成功') + + def get_index(self): + self.syncSites() + args = self.getArgs() + day = None + if 'day' in args: + day = args['day'] + + ser_status = self.status() + ser_status_bool = False + if ser_status == 'start': + ser_status_bool = True + data = {} + data['open'] = ser_status_bool + data['total'] = self.getTotal() + data['sites'] = self.getSites() + for i in range(len(data['sites'])): + data['sites'][i]['total'] = self.getTotal( + data['sites'][i]['siteName'], day) + return mw.returnJson(True, 'ok', data) + + def get_speed(self): + print("12") + + +if __name__ == "__main__": + func = sys.argv[1] + classApp = App() + try: + data = eval("classApp." + func + "()") + print(data) + except Exception as e: + print(mw.getTracebackInfo()) diff --git a/plugins/tamper_proof_py/info.json b/plugins/tamper_proof_py/info.json new file mode 100644 index 000000000..29c3b599a --- /dev/null +++ b/plugins/tamper_proof_py/info.json @@ -0,0 +1,15 @@ +{ + "title": "网站防篡改程序PY", + "tip": "lib", + "name": "tamper_proof_py", + "type": "soft", + "ps": "事件型防篡改程序,可有效保护网站重要文件不被木马篡改[Python]", + "versions": "1.0", + "shell": "install.sh", + "checks": "server/tamper_proof_py", + "path": "server/tamper_proof_py", + "author": "midoks", + "home": "", + "date":"2022-01-18", + "pid":"4" +} \ No newline at end of file diff --git a/plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl b/plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl new file mode 100644 index 000000000..ee7907398 --- /dev/null +++ b/plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl @@ -0,0 +1,14 @@ +[Unit] +Description=tamper_proof_py server daemon +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/init.d/tamper_proof_py start +ExecStop={$SERVER_PATH}/init.d/tamper_proof_py stop +ExecReload={$SERVER_PATH}/init.d/tamper_proof_py reload +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/tamper_proof_py/init.d/tamper_proof_py.tpl b/plugins/tamper_proof_py/init.d/tamper_proof_py.tpl new file mode 100644 index 000000000..ae38bab4c --- /dev/null +++ b/plugins/tamper_proof_py/init.d/tamper_proof_py.tpl @@ -0,0 +1,95 @@ +#!/bin/bash +# chkconfig: 2345 55 25 +# description:tamper_proof_service + +### BEGIN INIT INFO +# Provides: tamper_proof_service +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts tamper_proof_service +# Description: starts the tamper_proof_service +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +mw_path={$SERVER_PATH} +rootPath=$(dirname "$mw_path") +PATH=$PATH:$mw_path/bin + +if [ -f $rootPath/mdserver-web/bin/activate ];then + source $rootPath/mdserver-web/bin/activate +fi + +# cd /www/server/mdserver-web && python3 plugins/tamper_proof_py/tamper_proof_service.py start +sys_start() +{ + isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep |grep -v 'tamper_proof_py/tamper_proof_service.py start' | grep -v 'tamper_proof_py/tamper_proof_service.py reload' | grep -v 'tamper_proof_py/tamper_proof_service.py restart' | grep -v systemctl | grep -v '/bin/sh' | grep -v '/bin/bash' | awk '{print $2}'|xargs) + if [ "$isStart" == '' ];then + echo -e "Starting tamper_proof_service... \c" + cd $rootPath/mdserver-web + nohup python3 plugins/tamper_proof_py/tamper_proof_service.py start &> $mw_path/service.log & + sleep 0.5 + isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|awk '{print $2}'|xargs) + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo '------------------------------------------------------' + cat $mw_path/service.log + echo '------------------------------------------------------' + echo -e "\033[31mError: tamper_proof_service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "Starting tamper_proof_service (pid $isStart) already running" + fi +} + +sys_stop() +{ + echo -e "Stopping tamper_proof_service... \c"; + pids=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|grep -v '/bin/bash'|grep -v systemctl | grep -v 'tamper_proof_py/tamper_proof_service.py stop' | grep -v 'tamper_proof_py/tamper_proof_service.py reload' | grep -v 'tamper_proof_py/tamper_proof_service.py restart' |awk '{print $2}'|xargs) + arr=($pids) + for p in ${arr[@]} + do + kill -9 $p + done + cd $rootPath/mdserver-web + python3 plugins/tamper_proof_py/tamper_proof_service.py stop + echo -e "\033[32mdone\033[0m" +} + +sys_status() +{ + isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|grep -v "init.d/tamper_proof_py"|grep -v systemctl|awk '{print $2}'|xargs) + if [ "$isStart" != '' ];then + echo -e "\033[32mtamper_proof_service (pid $isStart) already running\033[0m" + else + echo -e "\033[32mtamper_proof_service not running\033[0m" + fi +} + +case "$1" in + 'start') + sys_start + ;; + 'stop') + sys_stop + ;; + 'restart') + sys_stop + sleep 0.2 + sys_start + ;; + 'reload') + sys_stop + sleep 0.2 + sys_start + ;; + 'status') + sys_status + ;; + *) + echo "Usage: systemctl {start|stop|restart|reload} tamper_proof_service" + ;; +esac \ No newline at end of file diff --git a/plugins/tamper_proof_py/install.sh b/plugins/tamper_proof_py/install.sh new file mode 100755 index 000000000..ee9c89e6e --- /dev/null +++ b/plugins/tamper_proof_py/install.sh @@ -0,0 +1,58 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# pip install pyinotify +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi +pip install pyinotify + +# cd /www/server/mdserver-web/plugins/tamper_proof_py && bash install.sh install 1.0 + +# cd /www/server/mdserver-web && python3 plugins/tamper_proof_py/index.py start 1.0 +# cd /www/server/mdserver-web && python3 plugins/tamper_proof_py/index.py service_admin {"serviceStatus":"start"} +# systemctl start tamper_proof_py +# systemctl status tamper_proof_py + +SYSOS=`uname` +VERSION=$2 +APP_NAME=tamper_proof_py + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/tamper_proof_py + echo "$VERSION" > $serverPath/tamper_proof_py/version.pl + echo 'install complete' + + #初始化 + cd ${serverPath}/mdserver-web && python3 plugins/tamper_proof_py/index.py start $VERSION + cd ${serverPath}/mdserver-web && python3 plugins/tamper_proof_py/index.py initd_install $VERSION +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/${APP_NAME}.service ] || [ -f /lib/systemd/system/${APP_NAME}.service ] ;then + systemctl stop ${APP_NAME} + systemctl disable ${APP_NAME} + rm -rf /usr/lib/systemd/system/${APP_NAME}.service + rm -rf /lib/systemd/system/${APP_NAME}.service + systemctl daemon-reload + fi + + rm -rf $serverPath/tamper_proof_py + echo "uninstall completed" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/tamper_proof_py/tamper_proof_service.py b/plugins/tamper_proof_py/tamper_proof_service.py new file mode 100644 index 000000000..7f5fa4f25 --- /dev/null +++ b/plugins/tamper_proof_py/tamper_proof_service.py @@ -0,0 +1,552 @@ +# coding=utf-8 + +# +-------------------------------------------------------------------- +# | 事件型防篡改 +# +-------------------------------------------------------------------- +import sys +import os +import pyinotify +import json +import shutil +import time +import psutil +import threading +import datetime + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +class MyEventHandler(pyinotify.ProcessEvent): + _PLUGIN_PATH = '/www/server/tamper_proof_py' + _CONFIG = '/config.json' + _SITES = '/sites.json' + _SITES_DATA = None + _CONFIG_DATA = None + _DONE_FILE = None + bakcupChirdPath = [] + + def __init__(self): + self._PLUGIN_PATH = self.getServerDir() + + def getPluginName(self): + return 'tamper_proof_py' + + def getPluginDir(self): + return mw.getPluginDir() + '/' + self.getPluginName() + + def getServerDir(self): + return mw.getServerDir() + '/' + self.getPluginName() + + def rmdir(self, filename): + try: + shutil.rmtree(filename) + except: + pass + + def check_site_logs(self, Stiename, datetime_time): + ret = [] + cur_month = datetime_time.month + cur_day = datetime_time.day + cur_year = datetime_time.year + cur_hour = datetime_time.hour + cur_minute = datetime_time.minute + cur_second = int(datetime_time.second) + months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07', + 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'} + logs_data = self.get_site_logs(Stiename) + if not logs_data: + return False + for i2 in logs_data: + try: + i = i2.split() + # 判断状态码是否为200 + if int(i[8]) not in [200, 500]: + continue + # 判断是否为POST + day_time = i[3].split('/')[0].split('[')[1] + if int(cur_day) != int(day_time): + continue + month_time = i[3].split('/')[1] + if int(months[month_time]) != int(cur_month): + continue + year_time = i[3].split('/')[2].split(':')[0] + if int(year_time) != int(cur_year): + continue + hour_time = i[3].split('/')[2].split(':')[1] + if int(hour_time) != int(cur_hour): + continue + minute_time = i[3].split('/')[2].split(':')[2] + if int(minute_time) != int(cur_minute): + continue + second_time = int(i[3].split('/')[2].split(':')[3]) + if cur_second - second_time > 10: + continue + ret.append(i2) + except: + continue + ret2 = [] + if len(ret) > 20: + for i2 in logs_data: + try: + i = i2.split() + if i[6] != 'POST': + continue + if int(i[8]) not in [200, 500]: + continue + # 判断是否为POST + day_time = i[3].split('/')[0].split('[')[1] + if int(cur_day) != int(day_time): + continue + month_time = i[3].split('/')[1] + if int(months[month_time]) != int(cur_month): + continue + year_time = i[3].split('/')[2].split(':')[0] + if int(year_time) != int(cur_year): + continue + hour_time = i[3].split('/')[2].split(':')[1] + if int(hour_time) != int(cur_hour): + continue + minute_time = i[3].split('/')[2].split(':')[2] + if int(minute_time) != int(cur_minute): + continue + ret2.append(i2) + except: + continue + if ret2: + ret = ret2 + if len(ret) > 20: + return ret[0:20] + return ret + + def get_site_logs(self, site_name): + try: + pythonV = sys.version_info[0] + path = '/www/wwwlogs/' + site_name + '.log' + num = 500 + if not os.path.exists(path): + return [] + p = 1 + start_line = (p - 1) * num + count = start_line + num + fp = open(path, 'rb') + buf = "" + try: + fp.seek(-1, 2) + except: + return [] + if fp.read(1) == "\n": + fp.seek(-1, 2) + data = [] + b = True + n = 0 + c = 0 + while c < count: + while True: + newline_pos = str.rfind(buf, "\n") + pos = fp.tell() + if newline_pos != -1: + if n >= start_line: + line = buf[newline_pos + 1:] + if line: + try: + data.append(line) + except: + c -= 1 + n -= 1 + pass + else: + c -= 1 + n -= 1 + buf = buf[:newline_pos] + n += 1 + c += 1 + break + else: + if pos == 0: + b = False + break + to_read = min(4096, pos) + fp.seek(-to_read, 1) + t_buf = fp.read(to_read) + if pythonV == 3: + t_buf = t_buf.decode('utf-8', errors="ignore") + buf = t_buf + buf + fp.seek(-to_read, 1) + if pos - to_read == 0: + buf = "\n" + buf + if not b: + break + fp.close() + except: + data = [] + return data + + def process_IN_CREATE(self, event): + siteInfo = self.get_SITE_CONFIG(event.pathname) + if not self.check_FILE(event, siteInfo, True): + return False + self._DONE_FILE = event.pathname + if event.dir: + if os.path.exists(event.pathname): + self.rmdir(event.pathname) + self.write_LOG('create', siteInfo[ + 'siteName'], event.pathname, datetime.datetime.now()) + + else: + if os.path.exists(event.pathname): + try: + src_path = os.path.dirname(event.pathname) + os.system("chattr -a {}".format(src_path)) + os.remove(event.pathname) + os.system("chattr +a {}".format(src_path)) + self.write_LOG('create', siteInfo[ + 'siteName'], event.pathname, datetime.datetime.now()) + except: + pass + + def process_IN_MOVED_TO(self, event): + # 检查是否受保护 + siteInfo = self.get_SITE_CONFIG(event.pathname) + if not self.check_FILE(event, siteInfo): + return False + + if not getattr(event, 'src_pathname', None): + if os.path.isdir(event.pathname): + self.rmdir(event.pathname) + else: + os.remove(event.pathname) + self.write_LOG('move', siteInfo[ + 'siteName'], '未知 -> ' + event.pathname) + return True + + # 是否为标记文件 + if event.src_pathname == self._DONE_FILE: + return False + + if not os.path.exists(event.src_pathname): + # 标记 + self._DONE_FILE = event.pathname + # 还原 + os.renames(event.pathname, event.src_pathname) + + # 记录日志 + self.write_LOG('move', siteInfo['siteName'], + event.src_pathname + ' -> ' + event.pathname) + + def check_FILE(self, event, siteInfo, create=False): + if not siteInfo: + return False + if self.exclude_PATH(event.pathname): + return False + if event.dir and create: + return True + if not event.dir: + if not self.protect_EXT(event.pathname): + return False + return True + + def protect_EXT(self, pathname): + if pathname.find('.') == -1: + return False + extName = pathname.split('.')[-1].lower() + siteData = self.get_SITE_CONFIG(pathname) + if siteData: + if extName in siteData['protectExt']: + return True + return False + + def exclude_PATH(self, pathname): + if pathname.find('/') == -1: + return False + siteData = self.get_SITE_CONFIG(pathname) + return self.exclude_PATH_OF_SITE(pathname, siteData['excludePath']) + + def exclude_PATH_OF_SITE(self, pathname, excludePath): + pathname = pathname.lower() + dirNames = pathname.split('/') + if excludePath: + if pathname in excludePath: + return True + if pathname + '/' in excludePath: + return True + for ePath in excludePath: + if ePath in dirNames: + return True + if pathname.find(ePath) == 0: + return True + return False + + def get_SITE_CONFIG(self, pathname): + if not self._SITES_DATA: + self._SITES_DATA = json.loads( + mw.readFile(self._PLUGIN_PATH + self._SITES)) + for site in self._SITES_DATA: + length = len(site['path']) + if len(pathname) < length: + continue + if site['path'] != pathname[:length]: + continue + return site + return None + + def get_CONFIG(self): + if self._CONFIG_DATA: + return self._CONFIG_DATA + self._CONFIG_DATA = json.loads( + mw.readFile(self._PLUGIN_PATH + self._CONFIG)) + + def list_DIR(self, path, siteInfo): # path 站点路径 + if not os.path.exists(path): + return + lock_files = [] + lock_dirs = [] + explode_a = ['log', 'logs', 'cache', 'templates', 'template', 'upload', 'img', + 'image', 'images', 'public', 'static', 'js', 'css', 'tmp', 'temp', 'update', 'data'] + for name in os.listdir(path): + try: + filename = "{}/{}".format(path, name).replace('//', '/') + lower_name = name.lower() + lower_filename = filename.lower() + if os.path.isdir(filename): # 是否为目录 + if lower_name in siteInfo['excludePath']: + continue # 是否为排除的文件名 + # 是否为排除目录 + if not self.exclude_PATH_OF_SITE(filename, siteInfo['excludePath']): + if not lower_name in explode_a: # 是否为固定不锁定目录 + lock_dirs.append('"' + name + '"') + self.list_DIR(filename, siteInfo) + continue + + # 是否为受保护的文件名或文件全路径 + if not lower_name in siteInfo['protectExt'] and not lower_filename in siteInfo['protectExt']: + if not self.get_EXT_NAME(lower_name) in siteInfo['protectExt']: + continue # 是否为受保护文件类型 + + if lower_filename in siteInfo['excludePath']: + continue # 是否为排除文件 + if lower_name in siteInfo['excludePath']: + continue # 是否为排除的文件名 + lock_files.append('"' + name + '"') + except: + print(mw.getTracebackInfo()) + if lock_files: + self.thread_exec(lock_files, path, 'i') + if lock_dirs: + self.thread_exec(lock_dirs, path, 'a') + + _thread_count = 0 + _thread_max = 2 * psutil.cpu_count() + + def thread_exec(self, file_list, cwd, i='i'): + while self._thread_count > self._thread_max: + time.sleep(0.1) + + self._thread_count += 1 + cmd = "cd {} && chattr +{} {} > /dev/null".format( + cwd, i, ' '.join(file_list)) + p = threading.Thread(target=self.run_thread, args=(cmd,)) + p.start() + + def run_thread(self, cmd): + os.system(cmd) + self._thread_count -= 1 + + def get_EXT_NAME(self, fileName): + return fileName.split('.')[-1] + + def write_LOG(self, eventType, siteName, pathname, datetime): + # 获取网站时间的top100 记录 + site_log = '/www/wwwlogs/%s.log' % siteName + logs_data = [] + if os.path.exists(site_log): + logs_data = self.check_site_logs(siteName, datetime) + dateDay = time.strftime("%Y-%m-%d", time.localtime()) + logPath = self._PLUGIN_PATH + '/sites/' + \ + siteName + '/day/' + dateDay + if not os.path.exists(logPath): + os.makedirs(logPath) + logFile = os.path.join(logPath, 'logs.json') + logVar = [int(time.time()), eventType, pathname, logs_data] + fp = open(logFile, 'a+') + fp.write(json.dumps(logVar) + "\n") + fp.close() + logFiles = [ + logPath + '/total.json', + self._PLUGIN_PATH + '/sites/' + siteName + '/day/total.json', + self._PLUGIN_PATH + '/sites/total.json' + ] + + for totalLogFile in logFiles: + if not os.path.exists(totalLogFile): + totalData = {"total": 0, "delete": 0, + "create": 0, "modify": 0, "move": 0} + else: + dataTmp = mw.readFile(totalLogFile) + if dataTmp: + totalData = json.loads(dataTmp) + else: + totalData = {"total": 0, "delete": 0, + "create": 0, "modify": 0, "move": 0} + + totalData['total'] += 1 + totalData[eventType] += 1 + mw.writeFile(totalLogFile, json.dumps(totalData)) + + # 设置.user.ini + def set_user_ini(self, path, up=0): + os.chdir(path) + useriniPath = path + '/.user.ini' + if os.path.exists(useriniPath): + os.system('chattr +i ' + useriniPath) + for p1 in os.listdir(path): + try: + npath = path + '/' + p1 + if not os.path.isdir(npath): + continue + useriniPath = npath + '/.user.ini' + if os.path.exists(useriniPath): + os.system('chattr +i ' + useriniPath) + if up < 3: + self.set_user_ini(npath, up + 1) + except: + continue + return True + + def unlock(self, path): + os.system('chattr -R -i {} &> /dev/null'.format(path)) + os.system('chattr -R -a {} &> /dev/null'.format(path)) + self.set_user_ini(path) + + def close(self, close_reload=False): + # 解除锁定 + sites = self.get_sites() + print("") + print("=" * 60) + print("{}Disabling anti tampering, please wait...".format(mw.formatDate())) + print("-" * 60) + for siteInfo in sites: + tip = self._PLUGIN_PATH + '/tips/' + siteInfo['siteName'] + '.pl' + if not siteInfo['open'] and not os.path.exists(tip): + continue + if close_reload and siteInfo['open']: + continue + if sys.version_info[0] == 2: + print("【{}】|-Unlock website[{}]".format(mw.formatDate(), siteInfo['siteName'])), + else: + os.system("echo -e '{}|-Unlock website[{}]\c'".format(mw.formatDate(), siteInfo['siteName'])) + #print("【{}】|-解锁网站[{}]".format(mw.format_date(),siteInfo['siteName']),end=" ") + self.unlock(siteInfo['path']) + if os.path.exists(tip): + os.remove(tip) + print("\t=> complete") + print("-" * 60) + print('|-Anti tampering has been turned off') + print("=" * 60) + print(">>>>>>>>>>END<<<<<<<<<<") + + # 获取网站配置列表 + def get_sites(self): + siteconf = self._PLUGIN_PATH + '/sites.json' + d = mw.readFile(siteconf) + if not os.path.exists(siteconf) or not d: + mw.writeFile(siteconf, "[]") + data = json.loads(mw.readFile(siteconf)) + + # 处理多余字段开始 >>>>>>>>>> + is_write = False + rm_keys = ['lock', 'bak_open'] + for i in data: + i_keys = i.keys() + if not 'open' in i_keys: + i['open'] = False + for o in rm_keys: + if o in i_keys: + if i[o]: + i['open'] = True + i.pop(o) + is_write = True + if is_write: + mw.writeFile(siteconf, json.dumps(data)) + # 处理多余字段结束 <<<<<<<<<<<<< + return data + + def __enter__(self): + self.close() + + def __exit__(self, a, b, c): + self.close() + + +def run(): + # 初始化inotify对像 + event = MyEventHandler() + watchManager = pyinotify.WatchManager() + starttime = time.time() + mode = pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO + + # 处理网站属性 + sites = event.get_sites() + print("=" * 60) + print("{} Starting anti tampering, please wait...".format(mw.formatDate())) + print("-" * 60) + tip_path = event._PLUGIN_PATH + '/tips/' + if not os.path.exists(tip_path): + os.makedirs(tip_path) + speed_file = event._PLUGIN_PATH + '/speed.pl' + for siteInfo in sites: + s = time.time() + tip = tip_path + siteInfo['siteName'] + '.pl' + if not siteInfo['open']: + continue + if sys.version_info[0] == 2: + print("{}|-website[{}]".format(mw.formatDate(),siteInfo['siteName'])), + else: + os.system("echo -e '{}|-website[{}]\c'".format(mw.formatDate(), siteInfo['siteName'])) + # print("【{}】|-网站[{}]".format(public.format_date(),siteInfo['siteName']),end=" ") + mw.writeFile(speed_file, "Processing website[{}]please wait a moment...".format( + siteInfo['siteName'])) + if not os.path.exists(tip): + event.list_DIR(siteInfo['path'], siteInfo) + try: + watchManager.add_watch(siteInfo['path'], mode, auto_add=True, rec=True) + except: + print(mw.getTracebackInfo()) + tout = round(time.time() - s, 2) + mw.writeFile(tip, '1') + print("\t\t=> Completed, time-consuming {}s".format(tout)) + + # 启动服务 + endtime = round(time.time() - starttime, 2) + mw.writeLog('防篡改程序', "The website anti tampering service has been successfully started,[%s]s" % endtime) + notifier = pyinotify.Notifier(watchManager, event) + print("-" * 60) + print('|-Anti tampering service has been started') + print("=" * 60) + end_tips = ">>>>>>>>>>END<<<<<<<<<<" + print(end_tips) + mw.writeFile(speed_file, end_tips) + notifier.loop() + + +if __name__ == '__main__': + if len(sys.argv) > 1: + if 'stop' in sys.argv: + event = MyEventHandler() + event.close() + elif 'start' in sys.argv: + run() + elif 'unlock' in sys.argv: + event = MyEventHandler() + event.unlock(sys.argv[2]) + elif 'reload' in sys.argv: + event = MyEventHandler() + event.close(True) + else: + print('error') + else: + run() diff --git a/plugins/task_manager/ico.png b/plugins/task_manager/ico.png new file mode 100644 index 000000000..60fa27d66 Binary files /dev/null and b/plugins/task_manager/ico.png differ diff --git a/plugins/task_manager/index.html b/plugins/task_manager/index.html new file mode 100755 index 000000000..12445cd4c --- /dev/null +++ b/plugins/task_manager/index.html @@ -0,0 +1,553 @@ + + +
                                  +
                                  +
                                  + 进程 + 启动项 + 服务 + 网络 + 用户 + 计划任务 + 会话 +
                                  +
                                  + +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + +
                                  +
                                  +
                                  +
                                  +
                                  + +
                                  +
                                  + CPU + 内存 + 磁盘 +
                                  + +
                                  +
                                  +
                                  +
                                  + +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + +
                                  +
                                  + +
                                  +
                                  + + + + diff --git a/plugins/task_manager/index.py b/plugins/task_manager/index.py new file mode 100755 index 000000000..90173fb97 --- /dev/null +++ b/plugins/task_manager/index.py @@ -0,0 +1,66 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'task_manager' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def status(): + return 'start' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + else: + print('error') diff --git a/plugins/task_manager/info.json b/plugins/task_manager/info.json new file mode 100755 index 000000000..4a9f7c981 --- /dev/null +++ b/plugins/task_manager/info.json @@ -0,0 +1,17 @@ +{ + "sort": 5, + "ps": "轻松管理进程、流量监控、启动项、用户、服务、计划任务、会话", + "name": "task_manager", + "title": "任务管理器", + "shell": "install.sh", + "versions":"1.0", + "tip": "soft", + "checks": "server/task_manager", + "path": "server/task_manager", + "display": 1, + "author": "task_manager", + "date": "2024-06-01", + "home": "task_manager", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/task_manager/install.sh b/plugins/task_manager/install.sh new file mode 100755 index 000000000..896b1af89 --- /dev/null +++ b/plugins/task_manager/install.sh @@ -0,0 +1,54 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +# python3 plugins/task_manager/task_manager_index.py +# /www/server/mdserver-web/bin/python3 /www/server/mdserver-web/plugins/task_manager/process_network_total.py +# ps -ef|grep process_network_total| grep -v grep | awk '{print $2}' | xargs kill -9 + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/task_manager + + if [ -f /usr/bin/apt ]; then + apt install libpcap-dev -y + fi + + if [ -f /usr/bin/yum ]; then + yum install libpcap-devel -y + fi + + if [ -f /usr/bin/dnf ]; then + dnf install libpcap-devel -y + fi + + pip3 install pypcap + + echo "$VERSION" > $serverPath/task_manager/version.pl + echo "安装任务管理器成功" +} + +Uninstall_App() +{ + rm -rf $serverPath/task_manager + echo "卸载任务管理器成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/task_manager/js/task_manager.js b/plugins/task_manager/js/task_manager.js new file mode 100644 index 000000000..16eab7e58 --- /dev/null +++ b/plugins/task_manager/js/task_manager.js @@ -0,0 +1,1117 @@ + +function tmPostCallback(method, args, callback, version='1.0'){ + var req_data = {}; + req_data['name'] = 'task_manager'; + req_data['func'] = method; + req_data['script']='task_manager_index'; + args['version'] = version; + + if (typeof(args) == 'string' && args == ''){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +var tab_name = 'p_list'; +var search_val = ''; +var select_pid = undefined; // 当前选中进程的id +var realProcess = []; // 进程数组 +var originProcess = []; // 当前进程展示的列表 +var wrapPid = {}; // 展开的父进程 +var TaskProcessLayerIndex = ''; // 进程详情弹窗的index +var canScroll2TaskMangerPossess = true; // 是否可以定位到选中的进程 + +$('.t-mana .man-menu-sub span').click(function () { + $(this).siblings().removeClass("on"); + tab_name = $(this).attr('class'); + $(this).addClass("on") + // console.log(tab_name); + if (tab_name == 'p_resource') { + $('.resource-panel').addClass('resource-panel-show') + $('.taskdivtable').addClass('divtable-hide') + $('.ts-line').addClass('divtable-hide') + } else if (tab_name !== 'p_resource on') { + $('.resource-panel').removeClass('resource-panel-show') + $('.taskdivtable').removeClass('divtable-hide') + $('.ts-line').removeClass('divtable-hide') + } + search_val = $('.search-bar .search_input').val('') + get_list_bytab(false); +}); + +$('.search-bar .search_input').on({ + 'keyup': function (e) { + if (e.keyCode == 13) { + get_list_bytab(); + } + }, + 'blur': function () { + get_list_bytab(true); + }, +}); + +$('.search-bar .glyphicon').click(function (e) { + get_list_bytab(); +}); + +var process_list_s = 0; +get_process_list(); + +function setTableHead(data) { + $.each(Object.keys(data.meter_head), function (index, item) { + if (!$('.' + item).hasClass('disabled')) { + if (data.meter_head[item]) { + $('.' + item).addClass('active'); + } else { + $('.' + item).removeClass('active'); + } + } + }); + + $('#change_thead').bind("contextmenu", function () { + return false; + }) + $(".plug_menu").bind("contextmenu", function () { + return false; + }) + $('#change_thead').mousedown(function (e) { + e.preventDefault(); + if (e.which == 3) { // 1 = 鼠标左键; 2 = 鼠标中键; 3 = 鼠标右键 + var menu = $('.setting_ul'); + var offset = $(this).offset(); + var x = e.pageX - offset.left; + var y = e.pageY - offset.top; + var width = menu.outerWidth(); + $(".plug_menu").removeAttr('style'); + $(".setting_ul").removeClass('undisplay'); + if ($('#change_thead').width() - x < width) { + $(".plug_menu").css({ + position: 'absolute', + left: x - width + 150 + "px", + top: y + 50 + "px", + }).show(); + } else { + $(".plug_menu").css({ + position: 'absolute', + left: x + width + "px", + top: y + 50 + "px", + }).show(); + } + } + $(".setting_ul").show(); + }); +} + + +$('.layui-layer').not($('.setting_ul_li')).click(function () { + $(".plug_menu").hide() + $(".setting_ul").hide(); +}); +var isProcessing = false; // 设置标志 + +$('.setting_ul_li').off('click').click(function (e) { + var that = $(this); + e.stopPropagation(); + // 检查标志 + if (isProcessing) { + return; + } + if (!$(this).hasClass('disabled')) { + isProcessing = true; // 点击事件被触发,设置标志 + var name = $(this).attr("name"); + clearInterval(process_list_s); + + tmPostCallback('set_meter_head',{meter_head_name: name}, function(data){ + isProcessing = false; // 操作完成,清除标志 + if (!data) { + layer.msg('设置失败'); + } + that.toggleClass('active'); + get_process_list(null, null, false); + clearInterval(process_list_s); + process_list_s = setInterval(function () { + if ($(".t-mana").length == 0) { + clearInterval(process_list_s); + process_list_s = 0; + console.log('进程列表轮询任务已停止'); + } + get_process_list(null, null, true); + }, 3000); + }); + } +}); + +$('.setting_btn').on('click',function (e) { + e.stopPropagation(); + var offset = $('.man-menu-sub').offset(); + var x = e.pageX - offset.left; + var y = e.pageY - offset.top; + var width = $('.man-menu-sub').outerWidth(); + if ($('.man-menu-sub').width() - x < width) { + $(".plug_menu").css({ + position: 'absolute', + right: "14.5px", + top:"40px", + }).toggle(); + $('.setting_ul').addClass('undisplay') + } + $(".setting_ul").toggle(); +}); + +if (process_list_s === 0) { + process_list_s = setInterval(function () { + if ($(".t-mana").length == 0) { + clearInterval(process_list_s); + process_list_s = 0; + console.log('进程列表轮询任务已停止'); + } + get_process_list(null, null, true); + }, 3000); +} + +function get_list_bytab(isblur) { + if (isblur && $('.search-bar .search_input').val() === search_val) { + return; + } + search_val = $('.search-bar .search_input').val(); + get_process_list(); + + switch (tab_name) { + case 'p_list': + $('.table_config').show(); + $('.search-bar span').addClass('r56'); + select_pid = ''; + wrapPid = {}; + canScroll2TaskMangerPossess = true; + get_process_list(); + break; + case 'p_resource': + $('.table_config').hide(); + $('.search-bar span').removeClass('r56'); + get_resource_list(); + break; + case 'p_run': + $('.table_config').hide(); + $('.search-bar span').removeClass('r56'); + get_run_list(); + break; + case 'p_service': + $('.table_config').hide(); + $('.search-bar span').removeClass('r56'); + get_service_list(); + break; + case 'p_network': + $('.table_config').hide(); + $('.search-bar span').removeClass('r56'); + get_network_list(); + break; + case 'p_user': + $('.table_config').hide(); + $('.search-bar span').removeClass('r56'); + get_user_list(); + break; + case 'p_cron': + $('.table_config').hide(); + $('.search-bar span').removeClass('r56'); + get_cron_list(); + break; + case 'p_session': + $('.table_config').hide(); + $('.search-bar span').removeClass('r56'); + get_who_list(); + break; + } +} + +function get_process_list(sortx, reverse, rx) { + if ($('.t-mana .man-menu-sub .on').attr('class') != 'p_list on') return; + var cookie_key = 'task_process_sort'; + var s_tmp = getCookie(cookie_key); + if (sortx == undefined || sortx == null) { + if (s_tmp) { + sortx_arr = s_tmp.split('|'); + sortx = sortx_arr[0]; + reverse = sortx_arr[1]; + } else { + sortx = 'cpu_percent'; + } + } + + res_list = {True: 'False',False: 'True'}; + setCookie(cookie_key, sortx + '|' + reverse); + if (!rx) { + var loadT = layer.msg('正在获取进程列表..', {icon: 16, time: 0, shade: [0.3, '#000']}) + } + + tmPostCallback('get_process_list', {sortx: sortx,reverse: reverse,search:search_val}, function(rdata){ + // console.log(rdata); + if (!rx) layer.close(loadT); + if ($('.t-mana .man-menu-sub .on').attr('class') != 'p_list on') return; + if (rdata.status === false) { + layer.closeAll(); + layer.msg(rdata.msg, {icon: 2}); + return; + } + var data = rdata.data; + + var year = new Date().getFullYear(); + realProcess = []; + originProcess = []; + var list = data.process_list; + for (var i = 0; i < list.length; i++) { + list[i].haschild = false; + if (list[i].children && list[i].children.length > 0) { + list[i].haschild = true; + } + originProcess.push(list[i]); + realProcess.push(list[i]); + } + var selectline = buildRealProcess(); + var tbody_tr = createProcessTable(true, data); + var tbody = '\ + \ + 应用名称\ + PID\ + 线程\ + 用户\ + CPU\ + 内存\ + io读\ + io写\ + 上行\ + 下行\ + 连接\ + 状态\ + 操作\ + \ + \ + ' + tbody_tr + ''; + $("#TaskManagement").html(tbody); + var topMsg = '
                                  \ +

                                  CPU:' + data.info.cpu + '%

                                  内存:' + toSize(data.info.mem) + '

                                  \ +

                                  负载(load average)

                                  ' + data.info.load_average[1] + ', ' + data.info.load_average[5] + ', ' + data.info.load_average[15] + '

                                  \ +

                                  进程数:' + data.process_list.length + '

                                  磁盘:' + toSize(data.info.disk) + '

                                  \ +
                                  '; + $("#load_average").html(topMsg).show(); + $(".pro_" + sortx).append(''); + $(".table-cont").css("height", "500px"); + scropll2selectPossess(selectline); + show_task(); + setTableHead(data); + if(getCookie('table_config_tip')=='false'||!getCookie('table_config_tip')){ + layer.tips('点击可设置表头', '.setting_btn', { + tips: [1, '#20a53a'], + time: 3000 + }); + setCookie('table_config_tip',true); + } + // 清除掉之前绑定的滚动事件 + // $("#table-cont").unbind('scroll'); + // 重新绑定滚动事件 + // $('#table-cont').scroll(task_manager_possess_scroll()); + }); +} + +function task_manager_possess_scroll() { + var timer = null; + var set2selected = null; + // 滚动节流 + return function () { + if (timer !== null) return; + timer = setTimeout(function () { + timer = null; + canScroll2TaskMangerPossess = false; + if (set2selected !== null) clearTimeout(set2selected) + // 最后一次滚动后2秒内不再滚动,才能滚动到选中的行 + set2selected = setTimeout(function () { + canScroll2TaskMangerPossess = true; + }, 2000); + }, 500); + } +} + +function scropll2selectPossess(selectline) { + if (canScroll2TaskMangerPossess && selectline !== -1) { + var top = $('#table-cont')[0].scrollTop; + // if(selectline > 2) + if (selectline * 38 > top + 500 || selectline * 38 < top) { + $('#table-cont')[0].scrollTo(0, (selectline - 2) * 38, 'smooth'); + } + } +} + +function show_process_child(pid) { + wrapPid[pid + ''] = true; + // buildRealProcess() + // createProcessTable() +} + +function colp_process_child(pid) { + // select_pid = pid+''; + wrapPid[pid + ''] = false; + // buildRealProcess() + // createProcessTable() +} + +function click_process_tr(e, pid, fpid) { + select_pid = pid + ''; + if (e.target.innerText === '结束' && fpid) { + select_pid = fpid + ''; + } + var selectline = buildRealProcess() + createProcessTable() + scropll2selectPossess(selectline) +} + +// 构建真实进程列表 +function buildRealProcess() { + realProcess = []; + var len = originProcess.length; + for (var i = 0; i < len; i++) { + realProcess.push(originProcess[i]); + if (wrapPid[originProcess[i].pid + '']) { + // console.log(originProcess[i]); + if (!originProcess[i].children) { + wrapPid[originProcess[i].pid + ''] = false; + continue; + } + var childSelected = false; + for (var j = 0; j < originProcess[i].children.length; j++) { + originProcess[i].children[j].fpid = originProcess[i].pid + '' + originProcess[i].children[j].ischild = true + originProcess[i].children[j].isselect = false + if (originProcess[i].pid + '' === select_pid) { + originProcess[i].children[j].isselect = true + } + if (originProcess[i].children[j].pid + '' === select_pid) { + var childSelected = true; + } + realProcess.push(originProcess[i].children[j]); + // 显示选中的父子进程 + } + if (childSelected) { + for (var k = 0; k <= originProcess[i].children.length; k++) { + realProcess.pop() + } + originProcess[i].isselect = true + realProcess.push(originProcess[i]) + for (var l = 0; l < originProcess[i].children.length; l++) { + originProcess[i].children[l].isselect = true + realProcess.push(originProcess[i].children[l]); + } + } + } + } + for (var i = 0; i < realProcess.length; i++) { + if (realProcess[i].pid + '' === select_pid) { + return i// 返回选中的下标 + } + } + return -1 +} + +// 生成进程表格内容 +function createProcessTable(getboday, data) { + var tbody_tr = ''; + for (var i = 0; i < realProcess.length; i++) { + if (realProcess[i].status == '活动') realProcess[i].status = '活动'; + var colp = realProcess[i].haschild ? '\ + \ + ' : ''; + if (wrapPid[realProcess[i].pid + '']) { + colp = '\ + \ + '; + } + var childNums = realProcess[i].haschild ? '(' + realProcess[i].children.length + ')' : ''; + var namewidth = "max-width:120px;" + if (realProcess[i].ischild) { + namewidth = "max-width:80px;" + } + if (realProcess[i].haschild) { + namewidth = "max-width:100px;" + } + var processName = '' + realProcess[i].ps + ''; + var isProcessChild = realProcess[i].ischild ? 'process-child' : ''; + var childStyle = realProcess[i].ischild ? 'width:100px' : ''; + var selected = realProcess[i].pid + '' === select_pid || realProcess[i].isselect ? 'class="process-select"' : ''; + var selected_one = realProcess[i].pid + '' === select_pid ? 'style="background-color:#F6F6F6;"' : ''; + var kill_process = realProcess[i].haschild ? 'kill_process_all' : 'kill_process'; + + var tbody_td = ''; + if ('io_read_bytes' in realProcess[i]){ + tbody_td += '' + toSize(realProcess[i].io_read_speed).replace(' ', '') + ''; + tbody_td += '' + toSize(realProcess[i].io_write_speed).replace(' ', '') + ''; + + tbody_td += '' + toSize(realProcess[i].up).replace(' ', '') + ''; + tbody_td += '' + toSize(realProcess[i].down).replace(' ', '') + ''; + } + + tbody_tr += '\ + \ + ' + colp + '\ + \ + ' + processName + '\ + ' + childNums + '\ + \ + ' + realProcess[i].pid + '\ + ' + realProcess[i].threads + '\ + ' + realProcess[i].user + '\ + ' + realProcess[i].cpu_percent + '%\ + ' + toSize(realProcess[i].memory_used).replace(' ', '') + '\ + '+tbody_td+'\ + ' + realProcess[i].connects + '\ + ' + realProcess[i].status + '\ + 结束\ + '; + } + if (getboday) return tbody_tr; + $("#TaskManagement tbody").html(tbody_tr); +} + +// 获取资源列表 +function get_resource_list() { + // console.log('get_resource_list----------'); + var url = '/plugin?action=a&name=task_manager&s=cpu_status' + // url = 'plugin?action=a&name=task_manager&s=get_disk_staus' + + var res_list = { + True: 'False', + False: 'True' + } + var reverse = 'True' + $.post(url, function (data) { + console.log(data); + realProcess = [] + originProcess = [] + var list = data.process_list + for (var i = 0; i < list.length; i++) { + list[i].haschild = false + if (list[i].children && list[i].children.length > 0) { + list[i].haschild = true + } + originProcess.push(list[i]) + realProcess.push(list[i]) + } + // console.log('realllll'); + + buildRealProcess() + var tbody_tr = createProcessTable(true); + var tbody = '\ + \ + 应用名称\ + PID\ + 线程\ + 用户\ + CPU\ + 内存\ + io读\ + io写\ + 上行\ + 下行\ + 连接\ + 状态\ + 操作\ + \ + \ + ' + tbody_tr + ''; + $('#taskResourceTable').html(tbody); + $(".table-cont").css("height", "220px"); + }) + + $("#load_average").html('') +} + +//查看计划任务列表 +function get_cron_list() { + var loadT = layer.msg('获取计划任务列表..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('get_cron_list', {search:search_val}, function(rdata){ + layer.close(loadT); + + var rdata = rdata.data; + var tbody_tr = ''; + for (var i = 0; i < rdata.length; i++) { + tbody_tr += '\ + ' + rdata[i].cycle + '\ + ' + rdata[i].exe + '\ + ' + rdata[i].ps + '\ + 删除\ + '; + } + var tbody = '\ + \ + 周期\ + 执行\ + 描述\ + 操作\ + \ + \ + ' + tbody_tr + ''; + $("#TaskManagement").html(tbody); + var topMsg = ''; + $("#load_average").html(topMsg).hide(); + $(".table-cont").css("height", "597px"); + show_task(); + }); +} + + +//查看网络状态 +function get_network_list(rflush) { + var loadT = layer.msg(lan.public.the_get, {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('get_network_list', {search:search_val}, function(rdata){ + layer.close(loadT); + if (rdata.data['is_mac']){ + tbody_tr += "mac无法使用"; + tbody = "\ + \ + " + lan.index.net_protocol + "\ + " + lan.index.net_address_dst + "\ + " + lan.index.net_address_src + "\ + " + lan.index.net_address_status + "\ + " + lan.index.net_process + "\ + " + lan.index.net_process_pid + "\ + \ + \ + " + tbody_tr + ""; + $("#TaskManagement").html(tbody); + show_task(); + return; + } + + var rdata = rdata.data; + + var tbody_tr = ""; + for (var i = 0; i < rdata.list.length; i++) { + tbody_tr += "" + + "" + rdata.list[i].type + "" + + "" + rdata.list[i].laddr[0] + ":" + rdata.list[i].laddr[1] + "" + + "" + (rdata.list[i].raddr.length > 1 ? "" + rdata.list[i].raddr[0] + ":" + rdata.list[i].raddr[1] : 'NONE') + "" + + "" + rdata.list[i].status + "" + + "" + rdata.list[i].process + "" + + "" + rdata.list[i].pid + "" + + ""; + } + + tbody = "\ + \ + " + lan.index.net_protocol + "\ + " + lan.index.net_address_dst + "\ + " + lan.index.net_address_src + "\ + " + lan.index.net_address_status + "\ + " + lan.index.net_process + "\ + " + lan.index.net_process_pid + "\ + \ + \ + " + tbody_tr + ""; + + $("#TaskManagement").html(tbody); + var topMsg = '
                                  \ +
                                  \ +

                                  总发送:' + toSize(rdata.state.upTotal) + '

                                  \ +

                                  总接收:' + toSize(rdata.state.downTotal) + '

                                  \ +
                                  \ +
                                  \ +

                                  上行:' + toSize(rdata.state.up) + '

                                  \ +

                                  下行:' + toSize(rdata.state.down) + '

                                  \ +
                                  \ +
                                  \ +

                                  总发包:' + to_max(rdata.state.upPackets) + '

                                  \ +

                                  总收包:' + to_max(rdata.state.downPackets) + '

                                  \ +
                                  \ +
                                  \ +

                                  包发送/秒:' + to_max(rdata.state.upPackets_s) + '

                                  \ +

                                  包接收/秒:' + to_max(rdata.state.downPackets_s) + '

                                  \ +
                                  \ +
                                  '; + $("#load_average").html(topMsg).show(); + $(".table-cont").css("height", "500px"); + show_task(); + }); +} + +function to_max(num) { + if (num > 10000) { + num = num / 10000; + if (num > 10000) { + num = num / 10000; + return num.toFixed(5) + ' 亿'; + } + return num.toFixed(5) + ' 万'; + } + return num; +} + +//获取会话列表 +function get_who_list() { + var loadT = layer.msg('正在获取用户会话列表..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('get_who', {search:search_val}, function(data){ + layer.close(loadT); + var rdata = data.data; + var tbody_tr = ''; + for (var i = 0; i < rdata.length; i++) { + tbody_tr += '\ + ' + rdata[i].user + '\ + ' + rdata[i].pts + '\ + ' + rdata[i].ip + '\ + ' + rdata[i].date + '\ + 强制断开\ + '; + } + var tbody = '\ + \ + 用户\ + PTS\ + 登陆IP\ + 登陆时间\ + 操作\ + \ + \ + ' + tbody_tr + ''; + $("#TaskManagement").html(tbody); + var topMsg = ''; + $("#load_average").html(topMsg).hide(); + $(".table-cont").css("height", "597px"); + show_task(); + }); +} + +//获取启动列表 +function get_run_list() { + var loadT = layer.msg('正在获取启动项列表..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('get_run_list', {search:search_val}, function(rdata){ + layer.close(loadT); + if (rdata.data['is_mac']){ + tbody_tr += "mac无法使用"; + var tbody = '\ + \ + 名称\ + 启动路径\ + 文件大小\ + 文件权限\ + 描述\ + 操作\ + \ + \ + ' + tbody_tr + ''; + $("#TaskManagement").html(tbody); + return; + } + + var rdata = rdata.data; + var tbody_tr = ''; + for (var i = 0; i < rdata.run_list.length; i++) { + tbody_tr += '\ + ' + rdata.run_list[i].name + '\ + ' + rdata.run_list[i].srcfile + '\ + ' + toSize(rdata.run_list[i].size) + '\ + ' + rdata.run_list[i].access + '\ + ' + rdata.run_list[i].ps + '\ + 编辑\ + '; + } + var tbody = '\ + \ + 名称\ + 启动路径\ + 文件大小\ + 文件权限\ + 描述\ + 操作\ + \ + \ + ' + tbody_tr + ''; + $("#TaskManagement").html(tbody); + var topMsg = '
                                  当前运行级别: level-' + rdata.run_level + '
                                  '; + $("#load_average").html(topMsg).show(); + $(".table-cont").css("height", "500px"); + show_task(); + }); +} + +//获取服务列表 +function get_service_list() { + var loadT = layer.msg('正在获取服务列表..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('get_service_list', {search:search_val}, function(rdata){ + layer.close(loadT); + if (rdata.data['is_mac']){ + tbody_tr += "mac无法使用"; + var tbody = '\ + \ + 名称\ + Level-0\ + Level-1\ + Level-2\ + Level-3\ + Level-4\ + Level-5\ + Level-6\ + 描述\ + 操作\ + \ + \ + ' + tbody_tr + ''; + $("#TaskManagement").html(tbody); + return; + } + + var rdata = rdata.data; + + var tbody_tr = ''; + for (var i = 0; i < rdata.serviceList.length; i++) { + tbody_tr += '\ + ' + rdata.serviceList[i].name + '\ + ' + rdata.serviceList[i].runlevel_0 + '\ + ' + rdata.serviceList[i].runlevel_1 + '\ + ' + rdata.serviceList[i].runlevel_2 + '\ + ' + rdata.serviceList[i].runlevel_3 + '\ + ' + rdata.serviceList[i].runlevel_4 + '\ + ' + rdata.serviceList[i].runlevel_5 + '\ + ' + rdata.serviceList[i].runlevel_6 + '\ + ' + rdata.serviceList[i].ps + '\ + 删除\ + '; + } + var tbody = '\ + \ + 名称\ + Level-0\ + Level-1\ + Level-2\ + Level-3\ + Level-4\ + Level-5\ + Level-6\ + 描述\ + 操作\ + \ + \ + ' + tbody_tr + ''; + $("#TaskManagement").html(tbody); + var topMsg = '
                                  当前运行级别: level-' + rdata.runlevel + '
                                  '; + $("#load_average").html(topMsg).show(); + $(".table-cont").css("height", "500px"); + show_task(); + }); +} + +//取用户列表 +function get_user_list() { + var loadT = layer.msg('正在获取用户列表..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('get_user_list', {search:search_val}, function(data){ + layer.close(loadT); + var rdata = data.data; + var tbody_tr = ''; + for (var i = 0; i < rdata.length; i++) { + tbody_tr += '\ + ' + rdata[i].username + '\ + ' + rdata[i].home + '\ + ' + rdata[i].group + '\ + ' + rdata[i].uid + '\ + ' + rdata[i].gid + '\ + ' + rdata[i].login_shell + '\ + ' + rdata[i].ps + '\ + 删除\ + '; + } + var tbody = '\ + \ + 用户名\ + home\ + 用户组\ + uid\ + gid\ + 登陆脚本\ + 描述\ + 操作\ + \ + \ + ' + tbody_tr + ''; + $("#TaskManagement").html(tbody); + var topMsg = ''; + $("#load_average").html(topMsg).hide(); + $(".table-cont").css("height", "597px"); + show_task(); + }); +} + +//删除用户 +function userdel(user) { + safeMessage('删除用户【' + user + '】', '删除后可能导致您的环境无法正常运行,继续吗?', function () { + var loadT = layer.msg('正在删除用户[' + user + ']..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('remove_user', {user:user}, function(rdata){ + layer.close(loadT); + var rdata = rdata.data; + showMsg(rdata.msg, function(){ + if (rdata.status) { + get_user_list(); + } + },{icon: rdata.status ? 1 : 2}); + }); + }); +} + +//结束进程 +function kill_process(pid, fpid) { + if (fpid) { + select_pid = fpid; + } + var w = layer.confirm('您是否要结束 (' + pid + ') 进程?', { + btn: ['结束', '取消'], //按钮 + title: '结束' + pid, + closeBtn: 2 + }, function () { + var loadT = layer.msg('正在结束进程[' + pid + ']..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('kill_process', {pid:pid}, function(data){ + layer.close(loadT); + var rdata = data.data; + layer.msg(rdata.msg, {icon: rdata.status ? 1 : 2}); + if (rdata.status) { + get_process_list(); + } + }); + }, function () { + layer.close(w); + }) +} + +//结束进程树 +function kill_process_all(pid) { + var w = layer.confirm('您是否要结束 (' + pid + ') 进程?', { + btn: ['结束', '取消'], //按钮 + title: '结束' + pid, + closeBtn: 2 + }, function () { + var loadT = layer.msg('正在结束父进程[' + pid + ']..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('kill_process_all', {pid:pid}, function(data){ + layer.close(loadT); + var rdata = data.data; + showMsg(rdata.msg, function(){ + if (rdata.status) { + get_process_list(); + } + },{icon: rdata.status ? 1 : 2}); + }); + }, function () { + layer.close(w); + }); +} + +//打开文件所在位置 +function open_path(path) { + var tmp = path.split('/'); + tmp[tmp.length - 1] = ''; + var path = '/' + tmp.join('/'); + openPath(path); +} + +//删除服务 +function remove_service(serviceName) { + safeMessage('删除服务【' + serviceName + '】', '删除后可能导致您的环境无法正常运行,继续吗?', function () { + var loadT = layer.msg('正在删除服务[' + serviceName + ']..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('remove_service', {serviceName:serviceName}, function(data){ + var rdata = data.data; + layer.close(loadT); + showMsg(rdata.msg, function(){ + if (rdata.status){ + get_service_list(); + } + },{icon: rdata.status ? 1 : 2}) + }); + }); +} + +//在线编辑文件 +function online_edit_file(fileName) { + onlineEditFile(0, fileName); +} + +//删除计划任务 +function remove_cron(index) { + safeMessage('删除计划任务[' + index + ']', '删除后将无法恢复,继续吗?', function () { + var loadT = layer.msg('正在删除计划任务..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('remove_cron', {index:index}, function(rdata){ + layer.close(loadT); + var rdata = rdata.data; + showMsg(rdata.msg, function(){ + if (rdata.status) { + get_cron_list(); + } + },{icon: rdata.status ? 1 : 2}); + }); + }); +} + +//强制断开会话 +function pkill_session(pts) { + safeMessage('强制断开会话[' + pts + ']', '强制断开此会话吗?', function () { + var loadT = layer.msg('正在断开会话..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('pkill_session', {pts:pts}, function(data){ + layer.close(loadT); + + var rdata = data.data; + layer.msg(rdata.msg, {icon: rdata.status ? 1 : 2}); + if (rdata.status){ + get_who_list(); + } + }); + }); +} + +//设置服务启动级别状态 +function set_runlevel_state(runlevel, serviceName) { + var loadT = layer.msg('正在设置服务[' + serviceName + ']..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('set_runlevel_state', {runlevel: runlevel,serviceName: serviceName}, function(data){ + layer.close(loadT); + var rdata = data.data; + showMsg(rdata.msg, function(){ + if (rdata.status) { + get_service_list(); + } + },{icon: rdata.status ? 1 : 2}); + }); +} + +//查看进程详情 +function get_process_info(pid) { + var loadT = layer.msg('正在获取进程信息[' + pid + ']..', {icon: 16, time: 0, shade: [0.3, '#000']}); + tmPostCallback('get_process_info', {pid:pid}, function(data){ + layer.close(loadT); + var rdata = data.data; + fileBody = ''; + for (var i = 0; i < rdata.open_files.length; i++) { + fileBody += '' + rdata.open_files[i].path + '\ + ' + rdata.open_files[i].mode + '\ + ' + rdata.open_files[i].position + '\ + ' + rdata.open_files[i].flags + '\ + ' + rdata.open_files[i].fd + '\ + '; + } + var cbody = '
                                  \ +
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                  名称' + rdata.name + 'PID' + rdata.pid + '状态' + rdata.status + '
                                  父进程' + rdata.pname + '(' + rdata.ppid + ')用户' + rdata.user + '线程' + rdata.threads + '
                                  Socket' + rdata.connects + 'io读' + toSize(rdata.io_read_bytes) + 'io写' + toSize(rdata.io_write_bytes) + '
                                  启动时间' + getLocalTime(rdata.create_time) + '描述' + rdata.ps + '
                                  启动命令' + rdata.comline.join(" ") + '
                                  \ +
                                  \ +

                                  内存

                                  \ +
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                  rss' + toSize(rdata.memory_full.rss) + 'pss' + toSize(rdata.memory_full.pss) + 'uss' + toSize(rdata.memory_full.uss) + '
                                  vms' + toSize(rdata.memory_full.vms) + 'swap' + toSize(rdata.memory_full.swap) + 'shared' + toSize(rdata.memory_full.shared) + '
                                  data' + toSize(rdata.memory_full.data) + 'text' + toSize(rdata.memory_full.text) + 'dirty' + toSize(rdata.memory_full.dirty) + '
                                  \ +
                                  \ +

                                  打开的文件列表

                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + ' + fileBody + '\ +
                                  文件modepositionflagsfd
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + \ + \ +
                                  '; + + TaskProcessLayerIndex = layer.open({ + type: 1, + title: '进程属性[' + rdata.name + '] -- ' + rdata.exe, + area: '750px', + closeBtn: 2, + shadeClose: false, + content: cbody + }); + show_jc_flie(); + }); +} + +//屏蔽指定IP +function dropAddress(address) { + layer.confirm(lan.index.net_doup_ip_msg, {icon: 3, closeBtn: 2}, function () { + loadT = layer.msg(lan.index.net_doup_ip_to, {icon: 16, time: 0, shade: [0.3, '#000']}); + $.post('/firewall/add_drop_address', 'type=address&protocol=tcp&port=' + address + '&ps=手动屏蔽', function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg, {icon: rdata.status ? 1 : 2}); + }); + }); +} + +function show_task() { + $(".ts-line").width($("#TaskManagement").width()); + $("#TaskManagement tbody td").click(function () { + // console.log('---'); + $(this).parents("tr").addClass("active").siblings().removeClass("active"); + }); + var tableCont = document.querySelector('#table-cont'); + //表格固定 + var sct = tableCont.scrollTop; + tableCont.querySelector('thead').style.transform = 'translateY(' + sct + 'px)'; + tableCont.addEventListener('scroll', scrollHandle); +} + +function show_jc_flie() { + var tableJc = document.querySelector('#jc-file-table'); + //文件表格固定 + tableJc.addEventListener('scroll', scrollHandle); +} + +function scrollHandle(e) { + var scrollTop = this.scrollTop; + this.querySelector('thead').style.transform = 'translateY(' + scrollTop + 'px)'; +} \ No newline at end of file diff --git a/plugins/task_manager/process_network_total.py b/plugins/task_manager/process_network_total.py new file mode 100644 index 000000000..d941b64e2 --- /dev/null +++ b/plugins/task_manager/process_network_total.py @@ -0,0 +1,230 @@ +#coding: utf-8 + +import sys +import time +import os +import struct + +os.chdir('/www/server/mdserver-web') +if 'class/' in sys.path: sys.path.insert(0,"class/") +import copy +try: + import pcap +except ImportError: + if os.path.exists('/usr/bin/apt'): + os.system("apt install libpcap-dev -y") + elif os.path.exists('/usr/bin/dnf'): + red_file = '/etc/redhat-release' + if os.path.exists(red_file): + f = open(red_file,'r') + red_body = f.read() + f.close() + if red_body.find('CentOS Linux release 8.') != -1: + rpm_file = '/root/libpcap-1.9.1.rpm' + down_url = "wget -O {} https://repo.almalinux.org/almalinux/8/PowerTools/x86_64/os/Packages/libpcap-devel-1.9.1-5.el8.x86_64.rpm --no-check-certificate -T 10".format(rpm_file) + print(down_url) + os.system(down_url) + os.system("rpm -ivh {}".format(rpm_file)) + if os.path.exists(rpm_file): os.remove(rpm_file) + else: + os.system("dnf install libpcap-devel -y") + else: + os.system("dnf install libpcap-devel -y") + elif os.path.exists('/usr/bin/yum'): + os.system("yum install libpcap-devel -y") + + os.system("pip install pypcap") + try: + import pcap + except ImportError: + print("pypcap module install failed.") + sys.exit() + +class process_network_total: + __pid_file = 'logs/process_network_total.pid' + __inode_list = {} + __net_process_list = {} + __net_process_size = {} + __last_stat = 0 + __last_write_time = 0 + __end_time = 0 + + def start(self,timeout = 0): + stime = time.time() + self.__end_time = timeout + stime + self.__last_stat = stime + try: + p = pcap.pcap() # 监听所有网卡 + p.setfilter('tcp') # 只监听TCP数据包 + for p_time,p_data in p: + self.handle_packet(p_data) + # 过期停止监听 + if timeout > 0: + if p_time > self.__end_time: + self.rm_pid_file() + break + + except: + self.rm_pid_file() + + def handle_packet(self, pcap_data): + # 获取IP协议头 + ip_header = pcap_data[14:34] + # 解析src/dst地址 + src_ip = ip_header[12:16] + dst_ip = ip_header[16:20] + # 解析sport/dport端口 + src_port = pcap_data[34:36] + dst_port = pcap_data[36:38] + + src = src_ip + b':' + src_port + dst = dst_ip + b':' + dst_port + # 计算数据包长度 + pack_size = len(pcap_data) + # 统计进程流量 + self.total_net_process(dst,src,pack_size) + + def total_net_process(self,dst,src,pack_size): + self.get_tcp_stat() + direction = None + mtime = time.time() + if dst in self.__net_process_list: + pid = self.__net_process_list[dst] + direction = 'down' + elif src in self.__net_process_list: + pid = self.__net_process_list[src] + direction = 'up' + else: + if mtime - self.__last_stat > 3: + self.__last_stat = mtime + self.get_tcp_stat(True) + if dst in self.__net_process_list: + pid = self.__net_process_list[dst] + direction = 'down' + elif src in self.__net_process_list: + pid = self.__net_process_list[src] + direction = 'up' + + if not direction: return False + if not pid: return False + if not pid in self.__net_process_size: + self.__net_process_size[pid] = {} + self.__net_process_size[pid]['down'] = 0 + self.__net_process_size[pid]['up'] = 0 + self.__net_process_size[pid]['up_package'] = 0 + self.__net_process_size[pid]['down_package'] = 0 + + self.__net_process_size[pid][direction] += pack_size + self.__net_process_size[pid][direction + '_package'] += 1 + + # 写入到文件 + if mtime - self.__last_write_time > 1: + self.__last_write_time = mtime + self.write_net_process() + + def write_net_process(self): + w_file = '/dev/shm/mw_net_process' + process_size = copy.deepcopy(self.__net_process_size) + net_process = [] + for pid in process_size.keys(): + net_process.append(str(pid) + " " + str(process_size[pid]['down']) + " " + str(process_size[pid]['up']) + " " + str(process_size[pid]['down_package']) + " " + str(process_size[pid]['up_package'])) + + f = open(w_file,'w+',encoding='utf-8') + f.write('\n'.join(net_process)) + f.close() + + def hex_to_ip(self, hex_ip): + hex_ip,hex_port = hex_ip.split(':') + ip = '.'.join([str(int(hex_ip[i:i+2], 16)) for i in range(0, len(hex_ip), 2)][::-1]) + port = int(hex_port, 16) + return ip,port + + def get_tcp_stat(self,force = False): + if not force and self.__net_process_list: return self.__net_process_list + self.__net_process_list = {} + tcp_stat_file = '/proc/net/tcp' + tcp_stat = open(tcp_stat_file, 'rb') + tcp_stat_list = tcp_stat.read().decode('utf-8').split('\n') + tcp_stat.close() + tcp_stat_list = tcp_stat_list[1:] + if force: self.get_process_inodes(force) + for i in tcp_stat_list: + tcp_tmp = i.split() + if len(tcp_tmp) < 10: continue + inode = tcp_tmp[9] + if inode == '0': continue + local_ip,local_port = self.hex_to_ip(tcp_tmp[1]) + if local_ip == '127.0.0.1': continue + remote_ip,remote_port = self.hex_to_ip(tcp_tmp[2]) + if local_ip == remote_ip: continue + if remote_ip == '0.0.0.0': continue + + pid = self.inode_to_pid(inode,force) + if not pid: continue + + key = self.get_ip_pack(local_ip) + b':' + self.get_port_pack(local_port) + self.__net_process_list[key] = pid + return self.__net_process_list + + + def get_port_pack(self,port): + return struct.pack('H',int(port))[::-1] + + def get_ip_pack(self,ip): + ip_arr = ip.split('.') + ip_pack = b'' + for i in ip_arr: + ip_pack += struct.pack('B',int(i)) + return ip_pack + + def inode_to_pid(self,inode,force = False): + inode_list = self.get_process_inodes() + if inode in inode_list: + return inode_list[inode] + return None + + def get_process_inodes(self,force = False): + if not force and self.__inode_list: return self.__inode_list + proc_path = '/proc' + inode_list = {} + for pid in os.listdir(proc_path): + try: + if not pid.isdigit(): continue + inode_path = proc_path + '/' + pid + '/fd' + for fd in os.listdir(inode_path): + try: + fd_file = inode_path + '/' + fd + fd_link = os.readlink(fd_file) + if fd_link.startswith('socket:['): + inode = fd_link[8:-1] + inode_list[inode] = pid + except: + continue + except: + continue + self.__inode_list = inode_list + return inode_list + + def get_process_name(self,pid): + pid_path = '/proc/' + pid + '/comm' + if not os.path.exists(pid_path): return '' + pid_file = open(pid_path, 'rb') + pid_name = pid_file.read().decode('utf-8').strip() + pid_file.close() + return pid_name + + + def write_pid(self): + self_pid = os.getpid() + pid_file = open(self.__pid_file,'w') + pid_file.write(str(self_pid)) + pid_file.close() + + def rm_pid_file(self): + if os.path.exists(self.__pid_file): + os.remove(self.__pid_file) + +if __name__ == '__main__': + p = process_network_total() + p.write_pid() + p.start(600) \ No newline at end of file diff --git a/plugins/task_manager/task_manager_index.py b/plugins/task_manager/task_manager_index.py new file mode 100755 index 000000000..22a44fb91 --- /dev/null +++ b/plugins/task_manager/task_manager_index.py @@ -0,0 +1,1641 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import threading +import psutil +import json + +from typing import List, Dict + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'task_manager' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +class mainClass(object): + + pids = None + + new_info = {} + old_info = {} + new_net_info = {} + old_net_info = {} + panel_pid = None + task_pid = None + __process_net_list = {} + __cpu_time = None + + meter_head = {} + + last_net_process = None + last_net_process_time = 0 + + # lock + _instance_lock = threading.Lock() + is_mac = False + + def __init__(self): + # print("__init__") + self.is_mac = mw.isAppleSystem() + self.old_path = getServerDir()+'/task_old.json' + self.old_net_path = getServerDir()+'/network_old.json' + + self.old_info['cpu_time'] = self.get_cpu_time() + self.old_info['time'] = time.time() + + @classmethod + def instance(cls, *args, **kwargs): + if not hasattr(mainClass, "_instance"): + with mainClass._instance_lock: + if not hasattr(mainClass, "_instance"): + mainClass._instance = mainClass(*args, **kwargs) + return mainClass._instance + + + def get_process_net_list(self): + import copy + w_file = '/dev/shm/mw_net_process' + if not os.path.exists(w_file): return + net_process_body = mw.readFile(w_file) + if not net_process_body: return + net_process = net_process_body.split('\n') + for np in net_process: + if not np: continue + tmp = {} + np_list = np.split() + if len(np_list) < 5: continue + tmp['pid'] = int(np_list[0]) + tmp['down'] = int(np_list[1]) + tmp['up'] = int(np_list[2]) + tmp['down_package'] = int(np_list[3]) + tmp['up_package'] = int(np_list[4]) + self.__process_net_list[tmp['pid']] = tmp + + if time.time() - self.last_net_process_time > 12 or self.last_net_process_time == 0: + self.last_net_process = copy.deepcopy(self.__process_net_list) + self.last_net_process_time = time.time() + + # 获取进程连接数 + def get_connects(self, pid): + connects = 0 + try: + if pid == 1: return connects + tp = '/proc/' + str(pid) + '/fd/' + if not os.path.exists(tp): return connects + for d in os.listdir(tp): + fname = tp + d + if os.path.islink(fname): + l = os.readlink(fname) + if l.find('socket:') != -1: connects += 1 + except: + pass + return connects + + # 获取进程io写 + def get_io_write(self, pid, io_write): + disk_io_write = 0 + if not self.old_info: self.old_info = {} + if not pid in self.old_info: + self.old_info[pid] = {} + self.old_info[pid]['io_write'] = io_write + return disk_io_write + + if not 'io_write' in self.old_info[pid]: + self.old_info[pid]['io_write'] = io_write + return disk_io_write + + io_end = (io_write - self.old_info[pid]['io_write']) + if io_end > 0: + disk_io_write = io_end / (time.time() - self.old_info['time']) + self.old_info[pid]['io_write'] = io_write + if disk_io_write > 0: return int(disk_io_write) + return 0 + + # 获取io读 + def get_io_read(self, pid, io_read): + disk_io_read = 0 + if not pid in self.old_info: + self.old_info[pid] = {} + self.old_info[pid]['io_read'] = io_read + return disk_io_read + + if not 'io_read' in self.old_info[pid]: + self.old_info[pid]['io_read'] = io_read + return disk_io_read + + io_end = (io_read - self.old_info[pid]['io_read']) + if io_end > 0: + disk_io_read = io_end / (time.time() - self.old_info['time']) + self.old_info[pid]['io_read'] = io_read + if disk_io_read > 0: return int(disk_io_read) + return 0 + + def get_mem_info(self, get=None): + mem = psutil.virtual_memory() + if self.is_mac: + memInfo = {'memTotal': mem.total} + memInfo['memRealUsed'] = memInfo['memTotal'] * (mem.percent / 100) + return memInfo['memRealUsed'] + + memInfo = {'memTotal': mem.total, 'memFree': mem.free, 'memBuffers': mem.buffers, 'memCached': mem.cached} + memInfo['memRealUsed'] = memInfo['memTotal'] - memInfo['memFree'] - memInfo['memBuffers'] - memInfo['memCached'] + return memInfo['memRealUsed'] + + # 进程备注,name,pid,启动命令 + def get_process_ps(self, name, pid, p_exe=None, p=None): + processPs = { + 'irqbalance': '系统进程-优化系统性能服务', + 'containerd': 'docker管理服务', + 'qmgr': '系统进程-管理和控制邮件进程', + 'pickup': '系统进程-接收和处理待发送的邮件', + 'cleanup': '系统进程-服务器释放资源进程', + 'trivial-rewrite': '系统进程-邮件重写和转发服务', + 'bt-ipfilter': '宝塔网络的IP过滤器', + 'oneav': '微步木马检测服务', + 'rhsmcertd': '系统进程-验证系统的订阅状态服务', + 'tamperuser': '宝塔-企业级防篡改服务', + 'lvmetad': '系统进程-元数据守护进程', + 'containerd-shim-runc-v2': 'docker容器管理服务', + 'tuned': '系统进程-优化系统服务', + 'chronyd': '系统进程-时间同步服务', + 'auditd': '系统进程-系统安全日志记录服务', + 'gssproxy': '系统进程-身份验证和授权服务', + 'ossfs': '阿里云对象存储挂载服务', + 'cosfs': '腾讯云对象存储挂载服务', + 'obsfs': '华为云对象存储挂载服务', + 's3fs': '对象存储挂载服务', + 'bosfs': '百度云对象存储挂载服务', + 'jsvc': 'tomcat服务', + 'mysqld': 'MySQL服务', + 'php-fpm': 'PHP子进程', + 'php-cgi': 'PHP-CGI进程', + 'nginx': 'Nginx服务', + 'httpd': 'Apache服务', + 'sshd': 'SSH服务', + 'pure-ftpd': 'FTP服务', + 'sftp-server': 'SFTP服务', + 'mysqld_safe': 'MySQL服务', + 'firewalld': '防火墙服务', + 'BT-Panel': '宝塔面板-主进程', + 'BT-Task': '宝塔面板-后台任务进程', + 'NetworkManager': '网络管理服务', + 'svlogd': '日志守护进程', + 'memcached': 'Memcached缓存器', + 'gunicorn': "python项目", + "BTPanel": '宝塔面板', + 'baota_coll': "堡塔云控-主控端", + 'baota_client': "堡塔云控-被控端", + 'node': 'Node.js程序', + 'supervisord': 'Supervisor进程', + 'rsyslogd': 'rsyslog日志服务', + 'crond': '计划任务服务', + 'cron': '计划任务服务', + 'rsync': 'rsync文件同步进程', + 'ntpd': '网络时间同步服务', + 'rpc.mountd': 'NFS网络文件系统挂载服务', + 'sendmail': 'sendmail邮件服务', + 'postfix': 'postfix邮件服务', + 'npm': 'Node.js NPM管理器', + 'PM2': 'Node.js PM2进程管理器', + 'htop': 'htop进程监控软件', + 'btpython': '宝塔面板-独立Python环境进程', + 'btappmanagerd': '宝塔应用管理器插件', + 'dockerd': 'Docker容器管理器', + 'docker-proxy': 'Docker容器管理器', + 'docker-registry': 'Docker容器管理器', + 'docker-distribution': 'Docker容器管理器', + 'docker-network': 'Docker容器管理器', + 'docker-volume': 'Docker容器管理器', + 'docker-swarm': 'Docker容器管理器', + 'docker-systemd': 'Docker容器管理器', + 'docker-containerd': 'Docker容器管理器', + 'docker-containerd-shim': 'Docker容器管理器', + 'docker-runc': 'Docker容器管理器', + 'docker-init': 'Docker容器管理器', + 'docker-init-systemd': 'Docker容器管理器', + 'docker-init-upstart': 'Docker容器管理器', + 'docker-init-sysvinit': 'Docker容器管理器', + 'docker-init-openrc': 'Docker容器管理器', + 'docker-init-runit': 'Docker容器管理器', + 'docker-init-systemd-resolved': 'Docker容器管理器', + 'rpcbind': 'NFS网络文件系统服务', + 'dbus-daemon': 'D-Bus消息总线守护进程', + 'systemd-logind': '登录管理器', + 'systemd-journald': 'Systemd日志管理服务', + 'systemd-udevd': '系统设备管理服务', + 'systemd-timedated': '系统时间日期服务', + 'systemd-timesyncd': '系统时间同步服务', + 'systemd-resolved': '系统DNS解析服务', + 'systemd-hostnamed': '系统主机名服务', + 'systemd-networkd': '系统网络管理服务', + 'systemd-resolvconf': '系统DNS解析服务', + 'systemd-local-resolv': '系统DNS解析服务', + 'systemd-sysctl': '系统系统参数服务', + 'systemd-modules-load': '系统模块加载服务', + 'systemd-modules-restore': '系统模块恢复服务', + 'agetty': 'TTY登陆验证程序', + 'sendmail-mta': 'MTA邮件传送代理', + 'bash': 'bash命令行进程', + '(sd-pam)': '可插入认证模块', + 'polkitd': '授权管理服务', + 'mongod': 'MongoDB数据库服务', + 'mongodb': 'MongoDB数据库服务', + 'mongodb-mms-monitor': 'MongoDB数据库服务', + 'mongodb-mms-backup': 'MongoDB数据库服务', + 'mongodb-mms-restore': 'MongoDB数据库服务', + 'mongodb-mms-agent': 'MongoDB数据库服务', + 'mongodb-mms-analytics': 'MongoDB数据库服务', + 'mongodb-mms-tools': 'MongoDB数据库服务', + 'mongodb-mms-backup-agent': 'MongoDB数据库服务', + 'mongodb-mms-backup-tools': 'MongoDB数据库服务', + 'mongodb-mms-restore-agent': 'MongoDB数据库服务', + 'mongodb-mms-restore-tools': 'MongoDB数据库服务', + 'mongodb-mms-analytics-agent': 'MongoDB数据库服务', + 'mongodb-mms-analytics-tools': 'MongoDB数据库服务', + 'dhclient': 'DHCP协议客户端', + 'dhcpcd': 'DHCP协议客户端', + 'dhcpd': 'DHCP服务器', + 'isc-dhcp-server': 'DHCP服务器', + 'isc-dhcp-server6': 'DHCP服务器', + 'dhcp6c': 'DHCP服务器', + 'dhcpcd': 'DHCP服务器', + 'dhcpd': 'DHCP服务器', + 'avahi-daemon': 'Zeroconf守护进程', + 'login': '登录进程', + 'systemd': '系统管理服务', + 'systemd-sysv': '系统管理服务', + 'systemd-journal-gateway': '系统管理服务', + 'systemd-journal-remote': '系统管理服务', + 'systemd-journal-upload': '系统管理服务', + 'systemd-networkd': '系统网络管理服务', + 'rpc.idmapd': 'NFS网络文件系统相关服务', + 'cupsd': '打印服务', + 'cups-browsed': '打印服务', + 'sh': 'shell进程', + 'php': 'PHP CLI模式进程', + 'blkmapd': 'NFS映射服务', + 'lsyncd': '文件同步服务', + 'sleep': '延迟进程' + } + if p_exe: + # print(name.lower(), pid, p_exe) + # if p: + # cmdline = ' '.join(p.cmdline()).strip() + # print(cmdline) + if name == 'php-fpm': + try: + if self.is_mac: + php_version = p_exe.split('/')[-3][3:] + else: + php_version = p_exe.split('/')[-3] + return 'PHP' + php_version + '进程' + except: + pass + elif name.lower() == 'python' or name.lower() == 'python3': + p_exe_arr = p_exe.split('/') + if p_exe_arr[-1] in ['task.py']: + return 'MW面板-后台任务进程' + elif p_exe_arr[-1] in ['BT-Panel', 'runserver.py']: + return '面板-主进程' + elif p_exe.find('process_network_total') != -1: + return '面板-进程网络监控' + + if p: + cmdline = ' '.join(p.cmdline()).strip() + cmdline_arr = cmdline.split('/') + if cmdline.find('process_network_total') != -1: + return '进程网络监控' + if cmdline_arr[-1] in ['BT-Task', 'task.py']: + return '后台任务进程' + elif cmdline_arr[-1] in ['BT-Panel', 'runserver.py']: + return '主进程' + elif cmdline.find('process_network_total') != -1: + return '进程网络监控' + elif cmdline.find('tamper_proof_service') != -1: + return '网站防篡改' + elif cmdline.find('syssafe') != -1: + return '系统加固' + elif cmdline.find('opwaf') != -1: + return 'WAF防火墙' + elif cmdline.find('acme') != -1: + return 'SSL证书签发' + elif cmdline.find('psync') != -1: + return '面板一键迁移' + elif cmdline.find('mdserver-web/plugins') != -1: + return '面板插件进程' + elif cmdline.find('/www/server/cron/') != -1: + return '面板计划任务' + elif cmdline.find('task.py') != -1: + return 'MW面板-后台任务' + elif cmdline.find('mdserver-web') != -1 and cmdline.find('gunicorn -c setting.py app:app') != -1: + return 'MW面板' + elif name.lower() == 'gunicorn': + if p: + cmdline = ' '.join(p.cmdline()).strip() + if cmdline.find('mdserver-web') != -1 and cmdline.find('gunicorn -c setting.py app:app') != -1: + return 'MW面板' + + elif name == 'nginx': + default_name = 'Nginx' + if p_exe.find('openresty/nginx') != -1: + default_name = 'OpenResty' + + if p.username() == 'www': + return default_name+'子进程' + else: + return default_name+'主进程' + elif name == 'openresty': + if p.username() == 'www': + return 'OpenResty子进程' + return 'OpenResty主进程' + elif name == 'mw': + return 'MW面板-命令' + elif p_exe == '/usr/bin/bash': + cmdline = ' '.join(p.cmdline()).strip() + if cmdline.find('task.py') != -1: + return 'MW面板-后台任务' + if cmdline.find('/www/server/cron/') != -1: + return '面板计划任务' + elif cmdline.find('mdserver-web/plugins') != -1: + return '面板插件进程' + + + if name in processPs: return processPs[name] + + # if self.is_panel_process(pid): return 'MW面板' + + if p_exe: + exe_keys = { + '/www/server/mdserver-web/plugins/': '面板插件', + '/www/server/cron/': '计划任务进程', + 'pm2': 'PM2进程管理器', + 'PM2': 'PM2进程管理器', + 'nvm': 'NVM Node版本管理器', + 'npm': 'NPM Node包管理器' + } + + for k in exe_keys.keys(): + if p_exe.find(k) != -1: return exe_keys[k] + if name.find(k) != -1: return exe_keys[k] + + return name + + def get_process_network(self, pid): + if not self.__process_net_list: + self.get_process_net_list() + if not self.last_net_process_time: return 0, 0, 0, 0 + if not pid in self.__process_net_list: return 0, 0, 0, 0 + + if not pid in self.last_net_process: + return self.__process_net_list[pid]['up'], self.__process_net_list[pid]['up_package'], \ + self.__process_net_list[pid]['down'], self.__process_net_list[pid]['down_package'] + + now_t = time.time() + # print(pid, self.__process_net_list[pid]['up'], self.last_net_process[pid]['up'],time.time(),self.last_net_process_time) + up = int((self.__process_net_list[pid]['up'] - self.last_net_process[pid]['up']) / ( now_t - self.last_net_process_time)) + down = int((self.__process_net_list[pid]['down'] - self.last_net_process[pid]['down']) / ( now_t - self.last_net_process_time)) + up_package = int((self.__process_net_list[pid]['up_package'] - self.last_net_process[pid]['up_package']) / ( now_t - self.last_net_process_time)) + down_package = int((self.__process_net_list[pid]['down_package'] - self.last_net_process[pid]['down_package']) / (now_t - self.last_net_process_time)) + # print('get_process_network', up, up_package, down, down_package) + return up, up_package, down, down_package + + def get_process_cpu_time(self, cpu_times): + cpu_time = 0.00 + for s in cpu_times: cpu_time += s + return cpu_time + + # 获取cpu使用率 + def get_cpu_percent(self, pid, cpu_times, cpu_time): + percent = 0.00 + process_cpu_time = self.get_process_cpu_time(cpu_times) + + if not self.old_info: self.old_info = {} + if not pid in self.old_info: + self.old_info[pid] = {} + self.old_info[pid]['cpu_time'] = process_cpu_time + return percent + + if cpu_time == self.old_info['cpu_time']: + return 0.00 + + percent = round(100.00 * (process_cpu_time - self.old_info[pid]['cpu_time']) / (cpu_time - self.old_info['cpu_time']), 2) + # self.old_info[pid]['cpu_time'] = process_cpu_time + + if percent > 0: return percent + return 0.00 + + # 获取平均负载 + def get_load_average(self): + c = os.getloadavg() + data = {} + data['1'] = round(float(c[0]), 3) # float(c[0]) + data['5'] = round(float(c[1]), 3) # float(c[1]) + data['15'] = round(float(c[2]), 3) # float(c[2]) + return data + + def set_meter_head(self, get): + if not 'meter_head_name' in get: + return False + + meter_head_name = get['meter_head_name'] + meter_head_file = getServerDir()+'/meter_head.json' + try: + self.get_meter_head() + if meter_head_name not in self.meter_head.keys(): + return False + if meter_head_name in ['ps', 'memory_used', 'cpu_percent', 'name']: + return False + self.meter_head[meter_head_name] = not self.meter_head[meter_head_name] + mw.writeFile(meter_head_file, json.dumps(self.meter_head)) + return True + except: + return False + + def get_meter_head(self, get=None): + meter_head_file = getServerDir()+'/meter_head.json' + if os.path.exists(meter_head_file): + self.meter_head = json.loads(mw.readFile(meter_head_file)) + else: + self.meter_head = { + 'name': True, + 'pid': True, + 'cpu_percent': True, + 'down': True, + 'up': True, + 'status': True, + 'threads': True, + 'user': True, + 'ps': True, + 'memory_used': True, + 'io_read_bytes': True, + 'io_write_bytes': True, + 'connects': True + } + mw.writeFile(meter_head_file, json.dumps(self.meter_head)) + return self.meter_head + + # 添加进程查找 + def search_pro(self, data, search): + try: + ldata = [] + for i in data: + if search in i['name'] or search in i['exe'] or search in i['ps'] or search in i[ + 'user'] or search in str(i['pid']) or search in i['status']: + ldata.append(i) + elif 'children' in i: + for k in i['children']: + if search in k['name'] or search in k['exe'] or search in k['ps'] or search in k[ + 'user'] or search in str(k['pid']): + ldata.append(i) + return ldata + except: + print(mw.getTracebackInfo()) + return data + + def get_cpu_time(self): + cpu_times = psutil.cpu_times() + + cpu_time = 0.00 + for s in cpu_times: cpu_time += s + return cpu_time + # return s.user + s.system + s.nice + s.idle + + # 获取python的路径 + def get_python_bin(self): + mw_dir = mw.getServerDir() + '/mdserver-web' + bin_file = mw_dir + '/bin/python3' + if os.path.exists(bin_file): + return bin_file + return '/usr/bin/python3' + + # 检查process_network_total.py是否运行 + def check_process_net_total(self): + mw_dir = mw.getServerDir() + '/mdserver-web' + _pid_file = mw_dir+'/logs/process_network_total.pid' + if os.path.exists(_pid_file): + pid = mw.readFile(_pid_file) + if os.path.exists('/proc/' + pid): return True + + cmd_file = mw_dir+'/plugins/task_manager/process_network_total.py' + python_bin = self.get_python_bin() + _cmd = 'nohup {} {} &> /tmp/net.log &'.format(python_bin, cmd_file) + mw.execShell(_cmd) + + # 进程折叠,将子进程折叠到父进程下,并将使用资源累加。 + def __pro_s_s(self, data: List) -> List: + """ + 将子进程合并到父进程中 + :param data:进程列表 + :return:合并后的进程列表增加children字段 + """ + data1 = [] + children_set = {'childrens': []} + for i in data: + if i['pid'] > 30 and i['ppid'] == 1: + children = self.__get_children(i['pid']) + s4 = time.time() + if children != []: children_set[i['pid']] = children + children_set['childrens'] += children + for i in data: + if i['pid'] in children_set: + i['children'] = [] + for j in data: + if j['pid'] in children_set[i['pid']]: + i['children'].append(j) + i['memory_used'] += j['memory_used'] + i['cpu_percent'] = round(i['cpu_percent'] + j['cpu_percent'], 2) + i['connects'] += j['connects'] + i['threads'] += j['threads'] + + if 'io_write_bytes' in j: + i['io_write_bytes'] += j['io_write_bytes'] + if 'io_read_bytes' in j: + i['io_read_bytes'] += j['io_read_bytes'] + if 'io_write_speed' in j: + i['io_write_speed'] += j['io_write_speed'] + if 'io_read_speed' in j: + i['io_read_speed'] += j['io_read_speed'] + + if 'up' in j: + i['up'] += j['up'] + if 'up_package' in j: + i['up_package'] += j['up_package'] + + if 'down' in j: + i['down'] += j['down'] + if 'down_package' in j: + i['down_package'] += j['down_package'] + data1.append(i) + elif i['pid'] not in children_set['childrens']: + data1.append(i) + return data1 + + def __get_children(self, pid: int) -> List: + try: + p = psutil.Process(pid) # pid为指定进程的进程号 + psutil.process_iter() + children = p.children(recursive=True) # 获取指定进程的所有子进程 + pids = [] + for child in children: + pids.append(child.pid) + return pids + except: + return [] + + # 外部接口,结束进程,pid30以上 + def kill_process(self, get): + pid = int(get['pid']) + if pid < 30: return mw.returnData(False, '不能结束系统关键进程!') + if not pid in psutil.pids(): return mw.returnData(False, '指定进程不存在!') + if not 'killall' in get: + p = psutil.Process(pid) + if self.is_panel_process(pid): return mw.returnData(False, '不能结束面板服务进程') + p.kill() + return mw.returnData(True, '进程已结束') + return self.kill_process_all(pid) + + # 是否为面板进程 + def is_panel_process(self, pid): + if not self.panel_pid: + self.panel_pid = os.getpid() + if pid == self.panel_pid: return True + if not self.task_pid: + try: + self.task_pid = int(mw.execShell("ps aux | grep 'python3 task.py' |grep -v grep|head -n1|awk '{print $2}'")[0]) + except: + self.task_pid = -1 + if pid == self.task_pid: return True + return False + + # 遍历结束pid的子进程 kill_process_all——>引用kill_process_lower + def kill_process_lower(self, pid): + pids = psutil.pids() + for lpid in pids: + if lpid < 30: continue + if self.is_panel_process(lpid): continue + p = psutil.Process(lpid) + ppid = p.ppid() + if ppid == pid: + p.kill() + return self.kill_process_lower(lpid) + return True + + # 结束进程树 kill_process——>引用kill_process_all + def kill_process_all(self, pid): + if pid < 30: return mw.returnData(True, '已结束此进程树!') + if self.is_panel_process(pid): return mw.returnData(False, '不能结束面板服务进程') + try: + if not pid in psutil.pids(): mw.returnData(True, '已结束此进程树!') + p = psutil.Process(pid) + ppid = p.ppid() + name = p.name() + p.kill() + mw.execShell('pkill -9 ' + name) + if name.find('php-') != -1: + mw.execShell("rm -f /tmp/php-cgi-*.sock") + elif name.find('mysql') != -1: + mw.execShell("rm -f /tmp/mysql.sock") + elif name.find('nginx') != -1: + mw.execShell("rm -f /tmp/mysql.sock") + self.kill_process_lower(pid) + if ppid: return self.kill_process_all(ppid) + except: + pass + return mw.returnData(True, '已结束此进程树!') + + + + def get_process_list(self, args = {}): + # https://hellowac.github.io/psutil-doc-zh/processes/process_class/oneshot.html + if self.is_mac: + return self.get_process_list_mac(args) + return self.get_process_list_linux(args) + + def get_process_list_mac(self, args = {}): + self.new_info['cpu_time'] = self.get_cpu_time() + self.new_info['time'] = time.time() + + sortx = 'all' + if 'sortx' in args: sortx = args['sortx'] + + if not 'sortx' in args: + args['sortx'] = 'status' + if args['sortx'] == 'status': res = False + if 'reverse' in args: + if args['reverse'] in ['undefined', 'null']: + args['reverse'] = 'True' + args['sortx'] = 'all' + if not args['reverse'] in ['True', 'False']: args['reverse'] = 'True' + res_list = {'True': True, 'False': False} + res = res_list[args['reverse']] + else: + args['reverse'] = True + if args['reverse'] in ['undefined', 'null']: + args['reverse'] = 'True' + args['sortx'] = 'all' + + info = {} + info['activity'] = 0 + info['cpu'] = 0.00 + info['mem'] = 0 + info['disk'] = 0 + status_ps = {'sleeping': '睡眠', 'running': '活动'} + ppids = psutil.pids() + processList = [] + for pid in ppids: + tmp = {} + try: + try: + p = psutil.Process(pid) + except Exception as e: + continue + + with p.oneshot(): + p_state = p.status() + try: + tmp['exe'] = p.exe() + except Exception as e: + continue + + tmp['name'] = p.name() + tmp['pid'] = pid + tmp['ppid'] = p.ppid() + tmp['create_time'] = int(p.create_time()) + tmp['status'] = p_state + tmp['user'] = p.username() + tmp['connects'] = self.get_connects(pid) + + if p_state == 'running': info['activity'] += 1 + if p_state in status_ps: p_state = status_ps[p_state] + + try: + tmp['threads'] = p.num_threads() + except Exception as e: + continue + + + tmp['ps'] = self.get_process_ps(tmp['name'], pid, tmp['exe'], p) + tmp['up'], tmp['up_package'], tmp['down'], tmp['down_package'] = self.get_process_network(pid) + + + try: + p_cpus = p.cpu_times() + except Exception as e: + continue + + try: + p_mem = p.memory_info() + except Exception as e: + continue + if p_mem.rss == 0: continue + + tmp['memory_used'] = p_mem.rss + tmp['cpu_percent'] = self.get_cpu_percent(str(pid), p_cpus, self.new_info['cpu_time']) + # print(tmp['cpu_percent']) + if tmp['cpu_percent'] > 100: tmp['cpu_percent'] = 0.1 + info['cpu'] += tmp['cpu_percent'] + info['disk'] += 0 + + processList.append(tmp) + del (p) + del (tmp) + except Exception as e: + print("err:", mw.getTracebackInfo()) + continue + + + processList = self.__pro_s_s(processList) + res = True + + if args['sortx'] not in ['all']: + processList = sorted(processList, key=lambda x: x[args['sortx']], reverse=res) + else: + processList = sorted(processList, key=lambda x: [x['cpu_percent'], x['connects'], x['threads'], + x['memory_used']], reverse=res) + + info['load_average'] = self.get_load_average() + data = {} + data['is_mac'] = self.is_mac + data['process_list'] = processList + info['cpu'] = round(info['cpu'], 3) + info['mem'] = self.get_mem_info() + data['info'] = info + if 'search' in args: + if args['search'] != '': + data['process_list'] = self.search_pro(data['process_list'], args['search']) + self.get_meter_head() + data['meter_head'] = self.meter_head + + data['meter_head']['io_read_bytes'] = False + data['meter_head']['io_write_bytes'] = False + data['meter_head']['down'] = False + data['meter_head']['up'] = False + return data + + # 获取进程信息 + def get_process_list_linux(self, get = {}): + self.check_process_net_total() + self.pids = psutil.pids() + processList = [] + if type(self.new_info) != dict: self.new_info = {} + self.new_info['cpu_time'] = self.get_cpu_time() + self.new_info['time'] = time.time() + self.get_process_net_list() + + + if not 'sortx' in get: + get['sortx'] = 'all' + + res = True + if get['sortx'] == 'status': + res = False + + if 'reverse' in get: + if get['reverse'] in ['undefined', 'null']: + get['reverse'] = 'True' + get['sortx'] = 'all' + if not get['reverse'] in ['True', 'False']: get['reverse'] = 'True' + res_list = {'True': True, 'False': False} + res = res_list[get['reverse']] + else: + get['reverse'] = True + if get['reverse'] in ['undefined', 'null']: + get['reverse'] = 'True' + get['sortx'] = 'all' + + info = {} + info['activity'] = 0 + info['cpu'] = 0.00 + info['mem'] = 0 + info['disk'] = 0 + status_ps = {'sleeping': '睡眠', 'running': '活动'} + for pid in self.pids: + tmp = {} + try: + try: + p = psutil.Process(pid) + except Exception as e: + continue + with p.oneshot(): + + try: + p_mem = p.memory_full_info() + except Exception as e: + continue + + if p_mem.rss == 0: continue + pio = p.io_counters() + p_cpus = p.cpu_times() + p_state = p.status() + if p_state == 'running': info['activity'] += 1 + if p_state in status_ps: p_state = status_ps[p_state] + tmp['exe'] = p.exe() + + try: + tmp['name'] = p.name() + except Exception as e: + continue + tmp['pid'] = pid + tmp['ppid'] = p.ppid() + tmp['create_time'] = int(p.create_time()) + tmp['status'] = p_state + tmp['user'] = p.username() + tmp['memory_used'] = p_mem.uss + tmp['cpu_percent'] = self.get_cpu_percent(str(pid), p_cpus, self.new_info['cpu_time']) + if tmp['name'] == 'BT-Panel' and tmp['cpu_percent'] > 1: + tmp['cpu_percent'] = round(tmp['cpu_percent'] % 1, 2) + tmp['io_write_bytes'] = pio.write_bytes + tmp['io_read_bytes'] = pio.read_bytes + tmp['io_write_speed'] = self.get_io_write(str(pid), pio.write_bytes) + tmp['io_read_speed'] = self.get_io_read(str(pid), pio.read_bytes) + tmp['connects'] = self.get_connects(pid) + tmp['threads'] = p.num_threads() + tmp['ps'] = self.get_process_ps(tmp['name'], pid, tmp['exe'], p) + tmp['up'], tmp['up_package'], tmp['down'], tmp['down_package'] = self.get_process_network(pid) + # print(pid,tmp['up'], tmp['up_package'], tmp['down'], tmp['down_package']) + if tmp['cpu_percent'] > 100: tmp['cpu_percent'] = 0.1 + info['cpu'] += tmp['cpu_percent'] + info['disk'] += tmp['io_write_speed'] + tmp['io_read_speed'] + processList.append(tmp) + del (p) + del (tmp) + except Exception as e: + print(mw.getTracebackInfo()) + continue + + processList = self.__pro_s_s(processList) + + if get['sortx'] not in ['all']: + processList = sorted(processList, key=lambda x: x[get['sortx']], reverse=res) + else: + processList = sorted(processList, key=lambda x: [x['cpu_percent'], x['up'], x['down'], x['io_write_speed'], + x['io_read_speed'], x['connects'], x['threads'], + x['memory_used']], reverse=res) + info['load_average'] = self.get_load_average() + data = {} + data['process_list'] = processList + info['cpu'] = round(info['cpu'], 2) + info['mem'] = self.get_mem_info() + data['info'] = info + if 'search' in get: + if get['search'] != '': + data['process_list'] = self.search_pro(data['process_list'], get['search']) + self.get_meter_head() + data['meter_head'] = self.meter_head + return data + + def object_to_dict(self, obj): + result = {} + for name in dir(obj): + value = getattr(obj, name) + if not name.startswith('__') and not callable(value) and not name.startswith('_'): result[name] = value + return result + + def list_to_dict(self, data): + result = [] + for s in data: + result.append(self.object_to_dict(s)) + return result + + # 获取进程的详细信息 + def get_process_info(self, args={}): + pid = int(args['pid']) + try: + p = psutil.Process(pid) + processInfo = {} + + if self.is_mac: + p_mem = self.object_to_dict(p.memory_info()) + else: + p_mem = self.object_to_dict(p.memory_full_info()) + pio = p.io_counters() + processInfo['io_write_bytes'] = pio.write_bytes; + processInfo['io_read_bytes'] = pio.read_bytes; + # p_cpus= p.cpu_times() + processInfo['exe'] = p.exe() + processInfo['name'] = p.name(); + processInfo['pid'] = pid; + processInfo['ppid'] = p.ppid() + processInfo['pname'] = 'sys' + if processInfo['ppid'] != 0: processInfo['pname'] = psutil.Process(processInfo['ppid']).name() + processInfo['comline'] = p.cmdline() + processInfo['create_time'] = int(p.create_time()) + processInfo['open_files'] = self.list_to_dict(p.open_files()) + processInfo['status'] = p.status(); + processInfo['user'] = p.username(); + processInfo['memory_full'] = p_mem + + processInfo['connects'] = self.get_connects(pid) + processInfo['threads'] = p.num_threads() + processInfo['ps'] = self.get_process_ps(processInfo['name'], pid, processInfo['exe'], p) + except Exception as e: + # print(mw.getTracebackInfo()) + return mw.returnData(False, '指定进程已关闭!') + return processInfo + + # 获取用户组处理函数 get_user_list——>引用get_group_name + def get_group_name(self, gid): + for g in self.groupList: + if g['gid'] == gid: return g['group'] + return '' + + # 用户名注释:ps get_user_list——>引用get_user_ps + def get_user_ps(self, name, ps): + userPs = {'www': '面板', 'root': '超级管理员', 'mysql': '用于运行MySQL的用户', + 'mongo': '用于运行MongoDB的用户', + 'git': 'git用户', 'mail': 'mail', 'nginx': '第三方nginx用户', 'postfix': 'postfix邮局用户', + 'lp': '打印服务帐号', + 'daemon': '控制后台进程的系统帐号', 'nobody': '匿名帐户', 'bin': '管理大部分命令的帐号', + 'adm': '管理某些管理文件的帐号', 'smtp': 'smtp邮件'} + if name in userPs: return userPs[name] + if not ps: return name + return ps + + # 获取服务器的组名和id,从/etc/group中读取。存储到self.groupList,get_user_list——>引用get_group_list + def get_group_list(self, get): + tmpList = mw.readFile('/etc/group').split("\n") + groupList = [] + for gl in tmpList: + tmp = gl.split(':') + if len(tmp) < 3: continue + groupInfo = {} + groupInfo['group'] = tmp[0] + groupInfo['gid'] = tmp[2] + groupList.append(groupInfo) + return groupList; + + # 外部接口,删除用户,不能删除系统运行环境用户 + def remove_user(self, get): + if self.is_mac: + return mw.returnData(False, '无法操作!') + users = ['www', 'root', 'mysql', 'shutdown', 'postfix', + 'smmsp', 'sshd', 'systemd-network', 'systemd-bus-proxy', + 'avahi-autoipd', 'mail', 'sync', 'lp', + 'adm', 'bin', 'mailnull', 'ntp', 'daemon', 'sys']; + + if not 'user' in get: + return mw.returnData(False, '缺少参数!') + + if get['user'] in users: return mw.returnData(False, '不能删除系统和环境关键用户!') + + user = get['user'] + + r = mw.execShell("userdel " + user) + if r[1].find('process') != -1: + try: + pid = r[1].split()[-1] + p = psutil.Process(int(pid)) + pname = p.name() + p.kill() + mw.execShell("pkill -9 " + pname) + r = mw.execShell("userdel " + user) + except: + pass + if r[1].find('userdel:') != -1: return mw.returnData(False, r[1]); + return mw.returnData(True, '删除成功!') + + # 获取用户列表 从/etc/passwd文件中读取 + def get_user_list(self, get={}): + tmpList = mw.readFile('/etc/passwd').strip().split("\n") + userList = [] + self.groupList = self.get_group_list(get) + for ul in tmpList: + tmp = ul.split(':') + if len(tmp) < 6: continue + userInfo = {} + userInfo['username'] = tmp[0] + userInfo['uid'] = tmp[2] + userInfo['gid'] = tmp[3] + userInfo['group'] = self.get_group_name(tmp[3]) + userInfo['ps'] = self.get_user_ps(tmp[0], tmp[4]) + userInfo['home'] = tmp[5] + userInfo['login_shell'] = tmp[6] + userList.append(userInfo) + + # print(userList) + if 'search' in get: + if get['search'] != '': + userList = self.search_user(userList, get['search']) + return userList + + # 查询用户 + def search_user(self, data, search): + try: + ldata = [] + for i in data: + if search in i['username'] or search in i['ps'] or search in i['login_shell'] or search in i[ + 'home'] or search in i['group']: + ldata.append(i) + return ldata + except: + mw.writeLog('任务管理', traceback.format_exc()) + return data + + # get_network_list ——>引用get_network + def get_network(self): + try: + networkIo = psutil.net_io_counters()[:4] + self.new_net_info['upTotal'] = networkIo[0] + self.new_net_info['downTotal'] = networkIo[1] + self.new_net_info['upPackets'] = networkIo[2] + self.new_net_info['downPackets'] = networkIo[3] + self.new_net_info['time'] = time.time() + + if not self.old_net_info: self.old_net_info = {} + if not 'upTotal' in self.old_net_info: + time.sleep(0.1) + networkIo = psutil.net_io_counters()[:4] + self.old_net_info['upTotal'] = networkIo[0] + self.old_net_info['downTotal'] = networkIo[1] + self.old_net_info['upPackets'] = networkIo[2] + self.old_net_info['downPackets'] = networkIo[3] + self.old_net_info['time'] = time.time() + + s = self.new_net_info['time'] - self.old_net_info['time'] + networkInfo = {} + networkInfo['upTotal'] = networkIo[0] + networkInfo['downTotal'] = networkIo[1] + networkInfo['up'] = round((float(networkIo[0]) - self.old_net_info['upTotal']) / s, 2) + networkInfo['down'] = round((float(networkIo[1]) - self.old_net_info['downTotal']) / s, 2) + networkInfo['downPackets'] = networkIo[3] + networkInfo['upPackets'] = networkIo[2] + networkInfo['downPackets_s'] = int((networkIo[3] - self.old_net_info['downPackets']) / s) + networkInfo['upPackets_s'] = int((networkIo[2] - self.old_net_info['upPackets']) / s) + return networkInfo + except: + return None + + + # 获取当前网络连接信息 + def get_network_list(self, get = {}): + netstats = psutil.net_connections() + data = {} + data['is_mac'] = False + if self.is_mac: + data['is_mac'] = self.is_mac + return data + + netstats = psutil.net_connections() + networkList = [] + for netstat in netstats: + tmp = {} + if netstat.type == 1: + tmp['type'] = 'tcp' + else: + tmp['type'] = 'udp' + tmp['family'] = netstat.family + tmp['laddr'] = netstat.laddr + tmp['raddr'] = netstat.raddr + tmp['status'] = netstat.status + p = psutil.Process(netstat.pid) + tmp['process'] = p.name() + if tmp['process'] in ['gunicorn']: continue + tmp['pid'] = netstat.pid + networkList.append(tmp) + del (p) + del (tmp) + networkList = sorted(networkList, key=lambda x: x['status'], reverse=True) + + data['list'] = networkList + data['state'] = self.get_network() + if 'search' in get: + if get['search'] != '': + data['list'] = self.search_network(data['list'], get['search']) + return data + + # 查询网络 + def search_network(self, data, search): + try: + ldata = [] + for i in data: + if search in i['process'] or search in i['status'] or search in str(i['pid']) or search in i['laddr'][ + 0] or search in str( + i['laddr'][1]): + ldata.append(i) + continue + if i['raddr'] != 'NONE': + for j in i['raddr']: + if search in str(j): + ldata.append(i) + return ldata + except: + return data + + # 获取当前运行级别 get_service_list ——> 引用get_my_runlevel + def get_my_runlevel(self): + try: + runlevel = mw.execShell('runlevel')[0].split()[1] + except: + runlevel_dict = {"multi-user.target": '3', 'rescue.target': '1', 'poweroff.target': '0', + 'graphical.target': '5', "reboot.target": '6'} + r_tmp = mw.execShell('systemctl get-default')[0].strip() + if r_tmp in runlevel_dict: + runlevel = runlevel_dict[r_tmp] + else: + runlevel = '3' + return runlevel + + # 服务注释 get_service_list——>引用 get_run_ps + def get_run_ps(self, name): + runPs = {'netconsole': '网络控制台日志', 'network': '网络服务', 'jexec': 'JAVA', 'tomcat8': 'Apache Tomcat', + 'tomcat7': 'Apache Tomcat', 'mariadb': 'Mariadb', + 'tomcat9': 'Apache Tomcat', 'tomcat': 'Apache Tomcat', 'memcached': 'Memcached缓存器', + 'php-fpm-53': 'PHP-5.3', 'php-fpm-52': 'PHP-5.2', + 'php-fpm-54': 'PHP-5.4', 'php-fpm-55': 'PHP-5.5', 'php-fpm-56': 'PHP-5.6', 'php-fpm-70': 'PHP-7.0', + 'php-fpm-71': 'PHP-7.1', + 'php-fpm-72': 'PHP-7.2', 'rsync_inotify': 'rsync实时同步', 'pure-ftpd': 'FTP服务', + 'mongodb': 'MongoDB', 'nginx': 'Web服务器(Nginx)', + 'httpd': 'Web服务器(Apache)', 'mw': '面板', 'mysqld': 'MySQL数据库', 'rsynd': 'rsync主服务', + 'php-fpm': 'PHP服务', 'systemd': '系统核心服务', + '/etc/rc.local': '用户自定义启动脚本', '/etc/profile': '全局用户环境变量', + '/etc/inittab': '用于自定义系统运行级别', '/etc/rc.sysinit': '系统初始化时调用的脚本', + 'sshd': 'SSH服务', 'crond': '计划任务服务', 'udev-post': '设备管理系统', 'auditd': '审核守护进程', + 'rsyslog': 'rsyslog服务', 'sendmail': '邮件发送服务', 'blk-availability': 'lvm2相关', + 'local': '用户自定义启动脚本', 'netfs': '网络文件系统', 'lvm2-monitor': 'lvm2相关', + 'xensystem': 'xen云平台相关', 'iptables': 'iptables防火墙', 'ip6tables': 'iptables防火墙 for IPv6', + 'firewalld': 'firewall防火墙'} + if name in runPs: return runPs[name] + return name + + # 清除注释 + def clear_comments(self, body): + bodyTmp = body.split("\n") + bodyR = "" + for tmp in bodyTmp: + if tmp.startswith('#'): continue + if tmp.strip() == '': continue + bodyR += tmp + return bodyR + + # 获取启动项 + def get_run_list(self, get={}): + data = {} + data['is_mac'] = False + if self.is_mac: + data['is_mac'] = self.is_mac + return data + + runFile = ['/etc/rc.local', '/etc/profile', '/etc/inittab', '/etc/rc.sysinit'] + runList = [] + for rfile in runFile: + if not os.path.exists(rfile): continue + bodyR = self.clear_comments(mw.readFile(rfile)) + if not bodyR: continue + stat = os.stat(rfile) + accept = str(oct(stat.st_mode)[-3:]) + if accept == '644': continue + tmp = {} + tmp['name'] = rfile + tmp['srcfile'] = rfile + tmp['size'] = os.path.getsize(rfile) + tmp['access'] = accept + tmp['ps'] = self.get_run_ps(rfile) + # tmp['body'] = bodyR + runList.append(tmp) + runlevel = self.get_my_runlevel() + runPath = ['/etc/init.d', '/etc/rc' + runlevel + '.d'] + tmpAll = [] + islevel = False + for rpath in runPath: + if not os.path.exists(rpath): continue + if runPath[1] == rpath: islevel = True + for f in os.listdir(rpath): + if f[:1] != 'S': continue + filename = rpath + '/' + f + if not os.path.exists(filename): continue + if os.path.isdir(filename): continue + if os.path.islink(filename): + flink = os.readlink(filename).replace('../', '/etc/') + if not os.path.exists(flink): continue + filename = flink + tmp = {} + tmp['name'] = f + if islevel: tmp['name'] = f[3:] + if tmp['name'] in tmpAll: continue + stat = os.stat(filename) + accept = str(oct(stat.st_mode)[-3:]) + if accept == '644': continue + tmp['srcfile'] = filename + tmp['access'] = accept + tmp['size'] = os.path.getsize(filename) + tmp['ps'] = self.get_run_ps(tmp['name']) + runList.append(tmp) + tmpAll.append(tmp['name']) + data = {} + data['run_list'] = runList + data['run_level'] = runlevel + if 'search' in get: + if get['search'] != '': + data['run_list'] = self.search_run(data['run_list'], get['search']) + return data + + # 检查服务是否为系统服务get_systemctl_list——>引用cont_systemctl + def cont_systemctl(self, name): + conts = ['systemd', 'rhel', 'plymouth', 'rc-', '@', 'init', 'ipr', 'dbus', '-local'] + for c in conts: + if name.find(c) != -1: return False + return True + + # 获取系统服务运行级别 get_service_list——>引用get_systemctl_list + def get_systemctl_list(self, serviceList, runlevel): + systemctl_user_path = '/usr/lib/systemd/system/' + systemctl_run_path = '/etc/systemd/system/multi-user.target.wants/' + if not os.path.exists(systemctl_user_path) or not os.path.exists(systemctl_run_path): return serviceList + r = '.service' + for d in os.listdir(systemctl_user_path): + if d.find(r) == -1: continue; + if not self.cont_systemctl(d): continue; + isrun = '关闭' + serviceInfo = {} + serviceInfo['name'] = d.replace(r, '') + serviceInfo['runlevel_0'] = isrun + serviceInfo['runlevel_1'] = isrun + serviceInfo['runlevel_2'] = isrun + serviceInfo['runlevel_3'] = isrun + serviceInfo['runlevel_4'] = isrun + serviceInfo['runlevel_5'] = isrun + serviceInfo['runlevel_6'] = isrun + if os.path.exists(systemctl_run_path + d): + isrun = '开启' + serviceInfo['runlevel_' + runlevel] = isrun + serviceInfo['runlevel_3'] = isrun + serviceInfo['runlevel_5'] = isrun + + serviceInfo['ps'] = self.get_run_ps(serviceInfo['name']) + serviceList.append(serviceInfo) + return serviceList + + # 外部接口,删除服务。不能删除mw + def remove_service(self, get): + if not 'serviceName' in get: + return mw.returnData(False,'缺少参数'); + + serviceName = get['serviceName'] + if serviceName == 'mw': return mw.returnData(False, '不能通过面板结束面板服务!') + systemctl_user_path = '/usr/lib/systemd/system/' + if os.path.exists(systemctl_user_path + serviceName + '.service'): + return mw.returnData(False,'Systemctl托管的服务不能通过面板删除'); + + mw.execShell('service ' + serviceName + ' stop') + if os.path.exists('/usr/sbin/update-rc.d'): + mw.execShell('update-rc.d ' + serviceName + ' remove') + elif os.path.exists('/usr/sbin/chkconfig'): + mw.execShell('chkconfig --del ' + serviceName) + else: + mw.execShell("rm -f /etc/rc0.d/*" + serviceName) + mw.execShell("rm -f /etc/rc1.d/*" + serviceName) + mw.execShell("rm -f /etc/rc2.d/*" + serviceName) + mw.execShell("rm -f /etc/rc3.d/*" + serviceName) + mw.execShell("rm -f /etc/rc4.d/*" + serviceName) + mw.execShell("rm -f /etc/rc5.d/*" + serviceName) + mw.execShell("rm -f /etc/rc6.d/*" + serviceName) + filename = '/etc/init.d/' + serviceName + if os.path.exists(filename): os.remove(filename) + return mw.returnData(True, '删除成功!') + + # 外部接口,设置软件运行环境,不能设置0,6 + def set_runlevel_state(self, get): + if not 'runlevel' in get: + return mw.returnData(False,'缺少参数[runlevel]') + + if not 'serviceName' in get: + return mw.returnData(False,'缺少参数[serviceName]') + + runlevel = get['runlevel'] + serviceName = get['serviceName'] + if runlevel == '0' or runlevel == '6': + return mw.returnData(False,'为安全考虑,不能通过面板直接修改此运行级别') + + systemctl_user_path = '/usr/lib/systemd/system/' + systemctl_run_path = '/etc/systemd/system/multi-user.target.wants/' + if os.path.exists(systemctl_user_path + serviceName + '.service'): + runlevel_cmd = mw.execShell('runlevel')[0].split()[1] + if runlevel_cmd != runlevel: + return mw.returnData(False,'Systemctl托管的服务不能设置非当前运行级别的状态') + action = 'enable' + if os.path.exists(systemctl_run_path + serviceName + '.service'): + action = 'disable' + mw.execShell('systemctl ' + action + ' ' + serviceName + '.service') + return mw.returnData(True, '设置成功!') + + rc_d = '/etc/rc' + runlevel + '.d/' + import shutil + for d in os.listdir(rc_d): + if d[3:] != serviceName: continue + sfile = rc_d + d + c = 'S' + if d[:1] == 'S': c = 'K' + dfile = rc_d + c + d[1:] + shutil.move(sfile, dfile) + return mw.returnData(True, '设置成功!') + return mw.returnData(False, '设置失败!') + + + # 外部接口 查询服务启动级别 /etc/init.d/ + def get_service_list(self, get = {}): + data = {} + data['is_mac'] = False + if self.is_mac: + data['is_mac'] = self.is_mac + return data + + init_d = '/etc/init.d/' + serviceList = [] + for sname in os.listdir(init_d): + try: + if str(oct(os.stat(init_d + sname).st_mode)[-3:]) == '644': continue + serviceInfo = {} + runlevels = self.get_runlevel(sname) + serviceInfo['name'] = sname + serviceInfo['runlevel_0'] = runlevels[0] + serviceInfo['runlevel_1'] = runlevels[1] + serviceInfo['runlevel_2'] = runlevels[2] + serviceInfo['runlevel_3'] = runlevels[3] + serviceInfo['runlevel_4'] = runlevels[4] + serviceInfo['runlevel_5'] = runlevels[5] + serviceInfo['runlevel_6'] = runlevels[6] + serviceInfo['ps'] = self.get_run_ps(sname) + serviceList.append(serviceInfo) + except: + continue + + data['runlevel'] = self.get_my_runlevel() + data['serviceList'] = sorted(serviceList, key=lambda x: x['name'], reverse=False) + data['serviceList'] = self.get_systemctl_list(data['serviceList'], data['runlevel']) + if 'search' in get: + if get['search'] != '': + data['serviceList'] = self.search_service(data['serviceList'], get['search']) + return data + + def search_service(self, data, search): + try: + ldata = [] + for i in data: + if search in i['name'] or search in i['ps']: + ldata.append(i) + return ldata + except: + return data + + # 获取存放计划任务的路径 + def get_cron_file(self): + filename = '/var/spool/cron/crontabs/root' + if os.path.exists(filename): return filename + filename = '/var/spool/cron/root' + if not os.path.exists(filename): + mw.writeFile(filename, "") + return filename + + # 数转周 + def toWeek(self, num): + if num > 6: return '' + wheres = { + 0: '日', + 1: '一', + 2: '二', + 3: '三', + 4: '四', + 5: '五', + 6: '六', + } + return wheres[num] + + # 解析计划任务执行周期 + def decode_cron_cycle(self, tmp): + if not tmp[4]: tmp[4] = '*' + if tmp[4] != '*': + cycle = '每周' + self.toWeek(int(tmp[4])) + '的' + tmp[1] + '时' + tmp[0] + '分' + elif tmp[2] != '*': + if tmp[2].find('*') == -1: + cycle = '每月的' + tmp[2] + '日,' + tmp[1] + '时' + tmp[0] + '分' + else: + cycle = '每隔' + tmp[2].split('/')[1] + '天' + tmp[1] + '时' + tmp[0] + '分' + elif tmp[1] != '*': + if tmp[1].find('*') == -1: + cycle = '每天的' + tmp[1] + '时' + tmp[0] + '分' + else: + cycle = '每隔' + tmp[1].split('/')[1] + '小时' + tmp[0] + '分钟' + elif tmp[0] != '*': + if tmp[0].find('*') == -1: + cycle = '每小时的第' + tmp[0] + '分钟' + else: + cycle = '每隔' + tmp[0].split('/')[1] + '分钟' + else: + return None + return cycle + + # 添加计划任务备注 + def decode_cron_connand(self, tmp): + command = '' + for i in range(len(tmp)): + if i < 5: continue + command += tmp[i] + ' ' + ps = '未知任务' + if command.find('/www/server/cron') != -1: + ps = '通过面板添加的计划任务' + elif command.find('.acme.sh') != -1: + ps = '基于acme.sh的证书续签任务' + elif command.find('certbot-auto renew') != -1: + ps = '基于certbot的证书续签任务' + + tmpScript = command.split('>')[0].strip() + filename = tmpScript.replace('"', '').split()[0] + # if not os.path.exists(filename): filename = ''; + return command.strip(), ps, filename + + # 外部接口,获取计划任务列表 + def get_cron_list(self, get = {}): + filename = self.get_cron_file() + cronList = [] + if not os.path.exists(filename): + return cronList + tmpList = mw.readFile(filename).split("\n") + for c in tmpList: + c = c.strip() + if c.startswith('#'): continue + tmp = c.split(' ') + if len(tmp) < 6: continue + cronInfo = {} + cronInfo['cycle'] = self.decode_cron_cycle(tmp) + if not cronInfo['cycle']: continue + ctmp = self.decode_cron_connand(tmp) + cronInfo['command'] = c + cronInfo['ps'] = ctmp[1] + cronInfo['exe'] = ctmp[2] + cronInfo['test'] = ctmp[0] + cronList.append(cronInfo) + if 'search' in get: + if get['search'] != '': + cronList = self.search_cron(cronList, get['search']) + return cronList + + def search_cron(self, data, search): + try: + ldata = [] + for i in data: + if search in i['command'] or search in i['cycle'] or search in i['ps']: + ldata.append(i) + return ldata + except: + return data + + # 重启cron服务 + def crondReload(self): + if os.path.exists('/etc/init.d/crond'): + mw.execShell('/etc/init.d/crond reload') + elif os.path.exists('/etc/init.d/cron'): + mw.execShell('service cron restart') + else: + mw.execShell("systemctl reload crond") + + # 外部接口,删除计划任务 + def remove_cron(self, get): + if not 'index' in get: + return mw.returnData(False, '参数不存在[index]!') + + index = int(get['index']) + cronList = self.get_cron_list({}) + if index > len(cronList) + 1: return mw.returnData(False, '指定任务不存在!') + toCron = [] + for i in range(len(cronList)): + if i == index: continue + toCron.append(cronList[i]['command']) + cronStr = "\n".join(toCron) + "\n\n" + filename = self.get_cron_file() + mw.writeFile(filename, cronStr) + mw.execShell("chmod 600 " + filename) + self.crondReload() + return mw.returnData(True, '删除成功!') + + # 外部接口 强制结束会话 + def pkill_session(self, get= {}): + if not 'pts' in get: + return mw.returnData(False, '缺少参数!') + mw.execShell("pkill -kill -t " + get['pts']) + return mw.returnData(True, '已强行结束会话[' + get['pts'] + ']') + + # 获取当前会话 + def get_who(self, get = {}): + whoTmp = mw.execShell('who')[0] + tmpList = whoTmp.split("\n") + whoList = [] + for w in tmpList: + tmp = w.split() + if len(tmp) < 5: continue + whoInfo = {} + whoInfo['user'] = tmp[0] + whoInfo['pts'] = tmp[1] + whoInfo['date'] = tmp[2] + ' ' + tmp[3] + whoInfo['ip'] = tmp[4].replace('(', '').replace(')', '') + if len(tmp) > 5: + whoInfo['date'] = tmp[2] + ' ' + tmp[3] + ' ' + tmp[4] + whoInfo['ip'] = tmp[5].replace('(', '').replace(')', '') + whoList.append(whoInfo) + if hasattr(get, 'search'): + if get.search != '': + whoList = self.search_who(whoList, get.search) + return whoList + + def test_cpu(self): + pid = 43046 + p = psutil.Process(pid) + tmp = {} + self.new_info['cpu_time'] = self.get_cpu_time() + self.new_info['time'] = time.time() + with p.oneshot(): + p_cpus = p.cpu_times() + print(p_cpus) + + tmp['cpu_percent'] = self.get_cpu_percent(str(pid), p_cpus, self.new_info['cpu_time']) + print(tmp['cpu_percent']) + + +def get_network_list(args = {}): + return mainClass.instance().get_network_list(args) + +def get_process_list(args = {}): + try: + return mainClass.instance().get_process_list(args) + except Exception as e: + return str(e) + + +def kill_process(args = {}): + return mainClass.instance().kill_process(args) + +def kill_process_all(args = {}): + if not 'pid' in args: + return mw.returnData(False, '缺少参数!') + return mainClass.instance().kill_process_all(int(args['pid'])) + +def set_meter_head(args = {}): + return mainClass.instance().set_meter_head(args) + +def remove_service(args = {}): + return mainClass.instance().remove_service(args) + +def set_runlevel_state(args = {}): + return mainClass.instance().set_runlevel_state(args) + +def get_service_list(args = {}): + return mainClass.instance().get_service_list(args) + +def get_run_list(args = {}): + return mainClass.instance().get_run_list(args) + +def get_cron_list(args = {}): + return mainClass.instance().get_cron_list(args) + +def remove_cron(args = {}): + return mainClass.instance().remove_cron(args) + +def pkill_session(args = {}): + return mainClass.instance().pkill_session(args) + +def get_who(args = {}): + return mainClass.instance().get_who(args) + +def get_process_info(args = {}): + return mainClass.instance().get_process_info(args) + +def get_user_list(args = {}): + return mainClass.instance().get_user_list(args) + +def remove_user(args = {}): + return mainClass.instance().remove_user(args) + +# if __name__ == "__main__": + # print(mc_instance.get_process_list()) + # print(mc_instance.get_network_list()) + # print(mc_instance.get_process_info({'pid':66647})) + # for x in range(10): + # mc_instance.test_cpu() + # time.sleep(1) + + # mc_instance.test_cpu() + # time.sleep(1) + # mc_instance.test_cpu() diff --git a/plugins/tgbot/ico.png b/plugins/tgbot/ico.png new file mode 100644 index 000000000..9416145b2 Binary files /dev/null and b/plugins/tgbot/ico.png differ diff --git a/plugins/tgbot/index.html b/plugins/tgbot/index.html new file mode 100755 index 000000000..973239f62 --- /dev/null +++ b/plugins/tgbot/index.html @@ -0,0 +1,26 @@ +
                                  +
                                  +
                                  +
                                  +

                                  服务

                                  +

                                  自启动

                                  +

                                  Bot配置

                                  +

                                  扩展列表

                                  +

                                  日志

                                  +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + + \ No newline at end of file diff --git a/plugins/tgbot/index.py b/plugins/tgbot/index.py new file mode 100755 index 000000000..0be333a8e --- /dev/null +++ b/plugins/tgbot/index.py @@ -0,0 +1,377 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'tgbot' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConfigData(): + cfg_path = getServerDir() + "/data.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeConf(data): + cfg_path = getServerDir() + "/data.cfg" + mw.writeFile(cfg_path, json.dumps(data)) + return True + + +def getExtCfg(): + cfg_path = getServerDir() + "/extend.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeExtCfg(data): + cfg_path = getServerDir() + "/extend.cfg" + return mw.writeFile(cfg_path, json.dumps(data)) + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + data = mw.execShell( + "ps -ef|grep tgbot |grep -v grep | grep -v mdserver-web | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + app_path = service_path + '/' + getPluginName() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + # if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path + '/mdserver-web') + content = content.replace('{$APP_PATH}', app_path) + + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/tgbot.service' + systemServiceTpl = getPluginDir() + '/init.d/tgbot.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$APP_PATH}', app_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def tbOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell(file + ' ' + method) + # print(data) + if data[1] == '': + return 'ok' + return 'ok' + + +def start(): + return tbOp('start') + + +def stop(): + return tbOp('stop') + + +def restart(): + status = tbOp('restart') + return status + + +def reload(): + + tgbot_tpl = getPluginDir() + '/startup/tgbot.py' + tgbot_dst = getServerDir() + '/tgbot.py' + + content = mw.readFile(tgbot_tpl) + mw.writeFile(tgbot_dst, content) + + tgpush_tpl = getPluginDir() + '/startup/tgpush.py' + tgpush_dst = getServerDir() + '/tgpush.py' + + content = mw.readFile(tgpush_tpl) + mw.writeFile(tgpush_dst, content) + + ext_src = getPluginDir() + '/startup/extend' + ext_dst = getServerDir() + + mw.execShell('cp -rf ' + ext_src + ' ' + ext_dst) + + return tbOp('restart') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def getBotConf(): + data = getConfigData() + if 'bot' in data: + + return mw.returnJson(True, 'ok', data['bot']) + return mw.returnJson(False, 'ok', {}) + + +def setBotConf(): + args = getArgs() + data_args = checkArgs(args, ['app_token']) + if not data_args[0]: + return data_args[1] + + data = getConfigData() + args['app_token'] = base64.b64decode(args['app_token']).decode('ascii') + data['bot'] = args + writeConf(data) + + return mw.returnJson(True, '保存成功!', []) + + +def installPreInspection(): + i = sys.version_info + if i[0] < 3 or i[1] < 7: + return "telebot在python小于3.7无法正常使用" + return 'ok' + + +def uninstallPreInspection(): + stop() + return "请手动删除
                                  rm -rf {}".format(getServerDir()) + + +def getExtCfgByName(name): + elist = getExtCfg() + for x in elist: + if x['name'] == name: + return x + return None + + +def botExtList(): + + args = getArgs() + data_args = checkArgs(args, ['p']) + if not data_args[0]: + return data_args[1] + + ext_path = getServerDir() + '/extend' + if not os.path.exists(ext_path): + return mw.returnJson(False, 'ok', []) + elist_source = os.listdir(ext_path) + + elist = [] + for e in elist_source: + if e.endswith('py'): + elist.append(e) + + page = int(args['p']) + page_size = 5 + + make_ext_list = [] + for ex in elist: + tmp = {} + tmp['name'] = ex + edata = getExtCfgByName(ex) + if edata: + tmp['status'] = edata['status'] + else: + tmp['status'] = 'stop' + + tmp['tag'] = ex.split('_')[0] + make_ext_list.append(tmp) + + writeExtCfg(make_ext_list) + dlist_sum = len(make_ext_list) + + page_start = int((page - 1) * page_size) + page_end = page_start + page_size + + if page_end >= dlist_sum: + ret_data = make_ext_list[page_start:] + else: + ret_data = make_ext_list[page_start:page_end] + + data = {} + data['data'] = ret_data + data['args'] = args + data['list'] = mw.getPage( + {'count': dlist_sum, 'p': page, 'row': page_size, 'tojs': 'botExtListP'}) + + return mw.returnJson(True, 'ok', data) + + +def setExtStatus(): + args = getArgs() + data_args = checkArgs(args, ['name', 'status']) + if not data_args[0]: + return data_args[1] + + elist = getExtCfg() + name = args['name'] + status = args['status'] + for x in range(len(elist)): + if elist[x]['name'] == name: + elist[x]['status'] = status + break + + writeExtCfg(elist) + + action = '开启' + if status == 'stop': + action = '关闭' + + return mw.returnJson(True, action + '[' + name + ']扩展成功') + + +def runLog(): + p = getServerDir() + '/task.log' + return p + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'get_bot_conf': + print(getBotConf()) + elif func == 'set_bot_conf': + print(setBotConf()) + elif func == 'bot_ext_list': + print(botExtList()) + elif func == 'set_ext_status': + print(setExtStatus()) + elif func == 'run_log': + print(runLog()) + + else: + print('error') diff --git a/plugins/tgbot/info.json b/plugins/tgbot/info.json new file mode 100755 index 000000000..6490e66ef --- /dev/null +++ b/plugins/tgbot/info.json @@ -0,0 +1,20 @@ +{ + "sort": 7, + "ps": "简单Telegram机器人", + "name": "tgbot", + "title": "tgbot", + "shell": "install.sh", + "versions":["1.0"], + "tip": "soft", + "checks": "server/tgbot", + "path": "server/tgbot", + "install_pre_inspection":true, + "uninstall_pre_inspection":true, + "display": 1, + "author": "midoks", + "date": "2023-03-06", + "home": "https://core.telegram.org/bots/api", + "depend_doc1":"https://github.com/eternnoir/pyTelegramBotAPI", + "type": 0, + "pid": "5" +} \ No newline at end of file diff --git a/plugins/tgbot/init.d/tgbot.service.tpl b/plugins/tgbot/init.d/tgbot.service.tpl new file mode 100644 index 000000000..04ee1b952 --- /dev/null +++ b/plugins/tgbot/init.d/tgbot.service.tpl @@ -0,0 +1,14 @@ +[Unit] +Description=Tgbot Service +After=network.target + +[Service] +Type=forking +ExecStart={$APP_PATH}/init.d/tgbot start +ExecStop={$APP_PATH}/init.d/tgbot stop +ExecReload={$APP_PATH}/init.d/tgbot reload +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/tgbot/init.d/tgbot.tpl b/plugins/tgbot/init.d/tgbot.tpl new file mode 100644 index 000000000..55e6097f2 --- /dev/null +++ b/plugins/tgbot/init.d/tgbot.tpl @@ -0,0 +1,95 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: Tgbot Service + +### BEGIN INIT INFO +# Provides: Tgbot +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts Tgbot +# Description: starts the MDW-Web +### END INIT INFO + +# Simple Tgbot init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +PATH=/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export LANG=en_US.UTF-8 + + +mw_path={$SERVER_PATH} +PATH=$PATH:$mw_path/bin + +if [ -f $mw_path/bin/activate ];then + source $mw_path/bin/activate +fi + +tg_start(){ + + isStart=`ps -ef|grep 'tgbot.py' |grep -v grep | awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "starting tgbot... \c" + cd $mw_path + python3 {$APP_PATH}/tgbot.py >> {$APP_PATH}/task.log & + python3 {$APP_PATH}/tgpush.py >> {$APP_PATH}/push.log & + isStart="" + while [[ "$isStart" == "" ]]; + do + echo -e ".\c" + sleep 0.5 + isStart=`ps -ef|grep 'tgbot.py' |grep -v grep | awk '{print $2}'` + let n+=1 + if [ $n -gt 20 ];then + break; + fi + done + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo -e "\033[31mError: tgbot service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "starting tgbot...(pid $(echo $isStart)) already running" + fi +} + + +tg_stop(){ + echo -e "stopping tgbot ... \c"; + arr=`ps aux|grep 'tgbot.py'|grep -v grep|awk '{print $2}'` + for p in ${arr[@]} + do + kill -9 $p > /dev/null 2>&1 + done + echo -e "\033[32mdone\033[0m" + + + echo -e "stopping tgpush ... \c"; + arr=`ps aux|grep 'tgpush.py'|grep -v grep|awk '{print $2}'` + for p in ${arr[@]} + do + kill -9 $p > /dev/null 2>&1 + done + echo -e "\033[32mdone\033[0m" +} + +case "$1" in + start) + tg_start + ;; + stop) + tg_stop + ;; + restart|reload) + tg_stop + sleep 0.3 + tg_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/tgbot/install.sh b/plugins/tgbot/install.sh new file mode 100755 index 000000000..31d91f8e0 --- /dev/null +++ b/plugins/tgbot/install.sh @@ -0,0 +1,56 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +# pip3 install ccxt +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +pip3 install pyTelegramBotAPI +pip3 install telebot + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/tgbot + echo "${VERSION}" > $serverPath/tgbot/version.pl + + cp -rf ${rootPath}/plugins/tgbot/startup/* $serverPath/tgbot + + cd ${rootPath} && python3 ${rootPath}/plugins/tgbot/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/tgbot/index.py initd_install + echo '安装完成' +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/tgbot.service ];then + systemctl stop tgbot + systemctl disable tgbot + rm -rf /usr/lib/systemd/system/tgbot.service + systemctl daemon-reload + fi + + if [ -f $serverPath/tgbot/initd/tgbot ];then + $serverPath/tgbot/initd/tgbot stop + fi + + rm -rf $serverPath/tgbot + echo "Uninstall_redis" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/tgbot/js/tgbot.js b/plugins/tgbot/js/tgbot.js new file mode 100755 index 000000000..30e53c214 --- /dev/null +++ b/plugins/tgbot/js/tgbot.js @@ -0,0 +1,148 @@ +function appPost(method, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'tgbot'; + req_data['func'] = method; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function appPostCallbak(method, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'tgbot'; + req_data['func'] = method; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function botConf(){ + appPost('get_bot_conf','',function(data){ + var rdata = $.parseJSON(data.data); + var app_token = 'app_token'; + if(rdata['status']){ + db_data = rdata['data']; + app_token = db_data['app_token']; + + } + + var mlist = ''; + mlist += '

                                  app_token必填写

                                  ' + var option = '\ +
                                  \ + ' + mlist + '\ +
                                  \ + \ +
                                  \ +
                                  '; + $(".soft-man-con").html(option); + }); +} + +function submitBotConf(){ + var pull_data = {}; + pull_data['app_token'] = base64_encode($('input[name="app_token"]').val()); + appPost('set_bot_conf',pull_data,function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000,shade: [0.3, '#000']}); + }); +} + + +function botExtList(){ + var body = '
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                  脚本类型状态
                                  \ + \ +
                                  '; + $('.soft-man-con').html(body); + botExtListP(1); +} + +function setBotExtStatus(name,status){ + appPost('set_ext_status',{'name':name,'status':status}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata['msg'],); + showMsg(rdata['msg'], function(){ + botExtListP(1); + },{icon:rdata['status']?1:2,shade: [0.3, '#000']},2000); + }); +} + +function botExtListP(p=1){ + appPost('bot_ext_list',{'p':p}, function(rdata){ + // console.log(rdata); + var rdata = $.parseJSON(rdata.data); + // console.log(rdata); + var tBody = ''; + + if (!rdata.status && rdata.data.length == 0 ){ + var tBody = '
                                  无数据
                                  '; + } else{ + var ldata = rdata.data.data; + for (var i = 0; i < ldata.length; i++) { + tBody += '' + tBody += ''+ldata[i]['name']+''; + tBody += ''+ldata[i]['tag']+''; + + if (ldata[i]['status'] == 'start'){ + tBody += ''; + } else{ + tBody += ''; + } + tBody +=''; + } + } + + $('#ext_list').html(tBody); + $('#ext_list_page').html(rdata.data.list); + + $('#ext_list .ext_status').click(function(){ + var name = $(this).parent().parent().data('name'); + var status = 'stop'; + if ($(this).hasClass('glyphicon-pause')){ + status = 'start'; + } + setBotExtStatus(name,status); + }); + }); +} diff --git a/plugins/tgbot/startup/extend/init_cmd.py b/plugins/tgbot/startup/extend/init_cmd.py new file mode 100644 index 000000000..1efb672d5 --- /dev/null +++ b/plugins/tgbot/startup/extend/init_cmd.py @@ -0,0 +1,32 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +import telebot +from telebot import types +from telebot.util import quick_markup + + +def init(bot): + bot.delete_my_commands(scope=None, language_code=None) + bot.set_my_commands( + commands=[ + telebot.types.BotCommand("start", "查看帮助信息"), + telebot.types.BotCommand("faq", "BBS帮助"), + telebot.types.BotCommand("music", "搜索网易音乐"), + ], + ) diff --git a/plugins/tgbot/startup/extend/push_ad.py b/plugins/tgbot/startup/extend/push_ad.py new file mode 100644 index 000000000..19cdbc82c --- /dev/null +++ b/plugins/tgbot/startup/extend/push_ad.py @@ -0,0 +1,94 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +import telebot +from telebot import types +from telebot.util import quick_markup + +# 广告推送实例 + + +chat_id = -1001578009023 +# chat_id = 5568699210 + + +def send_msg(bot, tag='ad', trigger_time=300): + # 信号只在一个周期内执行一次|start + lock_file = mw.getServerDir() + '/tgbot/lock.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + lock_data = json.loads(mw.readFile(lock_file)) + if tag in lock_data: + diff_time = time.time() - lock_data[tag]['do_time'] + if diff_time >= trigger_time: + lock_data[tag]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[tag] = {'do_time': time.time()} + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + keyboard = [ + [ + types.InlineKeyboardButton( + text="高价收一切流量 @caifutong555", url='https://t.me/caifutong555') + ], + [ + types.InlineKeyboardButton( + text="18+资源采集", url='https://ckzy1.com') + ], + [ + types.InlineKeyboardButton( + text="代付-代实名-备案域名-国际云服务器", url='https://t.me/gjgzs2022') + ], + [ + types.InlineKeyboardButton( + text="实名认证/过人脸🕵️‍♀️各种账号处理✅", url='https://t.me/niuniu234') + ], + [ + types.InlineKeyboardButton( + text="官网", url='https://github.com/midoks/mdserver-web'), + types.InlineKeyboardButton( + text="💎DigitalVirt(赞助商)", url='https://digitalvirt.com/aff.php?aff=154') + ], + [ + types.InlineKeyboardButton( + text="论坛", url='https://bbs.midoks.icu'), + types.InlineKeyboardButton( + text="搜索", url='https://bbs.midoks.icu/search.php'), + types.InlineKeyboardButton( + text="@ME", url='tg://user?id=5568699210'), + types.InlineKeyboardButton( + text="300rmb/月", url='tg://user?id=5568699210') + ] + ] + markup = types.InlineKeyboardMarkup(keyboard) + image_file = mw.getPluginDir() + '/tgbot/static/image/ad.png' + + telebot_image = telebot.types.InputFile(image_file) + msg = bot.send_photo(chat_id, telebot_image, reply_markup=markup) + + # print(msg.message_id) + time.sleep(5 * 60) + del_msg = bot.delete_message(chat_id=chat_id, message_id=msg.message_id) + # print(del_msg) + + +def run(bot): + send_msg(bot, 'ad', 1 * 60 * 60) diff --git a/plugins/tgbot/startup/extend/push_bbs_ntid.py b/plugins/tgbot/startup/extend/push_bbs_ntid.py new file mode 100644 index 000000000..88411fe38 --- /dev/null +++ b/plugins/tgbot/startup/extend/push_bbs_ntid.py @@ -0,0 +1,113 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +import telebot +from telebot import types +from telebot.util import quick_markup + +# 推送最新的帖子 + +chat_id = -1001578009023 +# chat_id = 5568699210 + + +def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + now = mw.getDateFromNow() + log_file = mw.getServerDir() + '/tgbot/task.log' + mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024) + return True + + +def get_newest_tid(): + + api_new = 'https://bbs.midoks.icu/plugin.php?id=external_api&f=bbs_newest' + api_next = 'https://bbs.midoks.icu/plugin.php?id=external_api&f=bbs_next_tid&tid=' + + tid_push = mw.getServerDir() + '/tgbot/bbs_newest_push.json' + + if not os.path.exists(tid_push): + data = mw.httpGet(api_new) + data = json.loads(data) + if data['code'] == 0: + tid = data['data'][0]['tid'] + mw.writeFile(tid_push, tid) + return True, data['data'][0] + + tid = mw.readFile(tid_push) + data = mw.httpGet(api_next + tid) + data = json.loads(data) + if data['code'] == 0 and len(data['data']) > 0: + # print(data) + tid = data['data'][0]['tid'] + mw.writeFile(tid_push, tid) + return True, data['data'][0] + return False, None + + +def send_msg(bot, tag='ad', trigger_time=300): + # 信号只在一个周期内执行一次|start + lock_file = mw.getServerDir() + '/tgbot/lock.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + lock_data = json.loads(mw.readFile(lock_file)) + if tag in lock_data: + diff_time = time.time() - lock_data[tag]['do_time'] + if diff_time >= trigger_time: + lock_data[tag]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[tag] = {'do_time': time.time()} + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + + yes, info = get_newest_tid() + if yes: + url = 'https://bbs.midoks.icu/thread-' + info['tid'] + '-1-1.html' + keyboard = [ + [ + types.InlineKeyboardButton(text=info['subject'], url=url) + ], + [ + types.InlineKeyboardButton( + text="论坛", url='https://bbs.midoks.icu'), + types.InlineKeyboardButton( + text="搜索", url='https://bbs.midoks.icu/search.php') + ] + ] + markup = types.InlineKeyboardMarkup(keyboard) + bot.send_message( + chat_id, "由【" + info['author'] + "】发帖!", reply_markup=markup) + + +def run(bot): + + try: + send_msg(bot, 'push_bbs_newest_tid', 300) + except Exception as e: + writeLog('-----push_bbs_newest_tid error start -------') + print(mw.getTracebackInfo()) + writeLog('-----push_bbs_newest_tid error start -------') + + +if __name__ == "__main__": + print(get_newest_tid()) diff --git a/plugins/tgbot/startup/extend/push_notice_msg.py b/plugins/tgbot/startup/extend/push_notice_msg.py new file mode 100644 index 000000000..c109fb95c --- /dev/null +++ b/plugins/tgbot/startup/extend/push_notice_msg.py @@ -0,0 +1,116 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +import telebot +from telebot import types +from telebot.util import quick_markup + +# 轮播实例 + +chat_id = -1001578009023 +# chat_id = 5568699210 + + +def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + now = mw.getDateFromNow() + log_file = mw.getServerDir() + '/tgbot/task.log' + mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024) + return True + + +def send_msg(bot, tag='ad', trigger_time=300): + # 信号只在一个周期内执行一次|start + lock_file = mw.getServerDir() + '/tgbot/lock.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + lock_data = json.loads(mw.readFile(lock_file)) + if tag in lock_data: + diff_time = time.time() - lock_data[tag]['do_time'] + if diff_time >= trigger_time: + lock_data[tag]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[tag] = {'do_time': time.time()} + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + + # https://t.me/gjgzs2022 | 22/m | @GJ_gzs + # 实名认证/过人脸🕵️‍♀️各种账号处理✅ | 30/m| next,6/30 | @nngzs + # 18+资源采集| 4/m | next,7/14 | @liuxingyu123 + + keyboard = [ + [ + types.InlineKeyboardButton( + text="高价收一切流量 @caifutong555", url='https://t.me/caifutong555') + ], + [ + types.InlineKeyboardButton( + text="18+资源采集", url='https://ckzy1.com') + ], + [ + types.InlineKeyboardButton( + text="代付-代实名-备案域名-国际云服务器", url='https://t.me/gjgzs2022') + ], + [ + types.InlineKeyboardButton( + text="实名认证/过人脸🕵️‍♀️各种账号处理✅", url='https://t.me/niuniu234') + ], + [ + types.InlineKeyboardButton( + text="官网", url='https://github.com/midoks/mdserver-web'), + types.InlineKeyboardButton( + text="💎DigitalVirt(赞助商)", url='https://digitalvirt.com/aff.php?aff=154') + ], + [ + types.InlineKeyboardButton( + text="论坛", url='https://bbs.midoks.icu'), + types.InlineKeyboardButton( + text="搜索", url='https://bbs.midoks.icu/search.php'), + types.InlineKeyboardButton( + text="@ME", url='tg://user?id=5568699210'), + types.InlineKeyboardButton( + text="300RMB/月", url='tg://user?id=5568699210') + ] + ] + markup = types.InlineKeyboardMarkup(keyboard) + + msg_notice = "由于在解决的问题的时候,不给信息,无法了解情况。以后不再群里回答技术问题。全部去论坛提问。在解决问题的过程中,可能需要面板信息,和SSH信息,如无法提供请不要提问。为了让群里都知晓。轮播一年!\n" + msg_notice += "为了不打扰双方,私聊解决问题先转1000U,否则无视!\n" + msg = bot.send_message(chat_id, msg_notice, reply_markup=markup) + + # print(msg.message_id) + time.sleep(90) + try: + bot.delete_message( + chat_id=chat_id, message_id=msg.message_id) + except Exception as e: + pass + + +def run(bot): + try: + send_msg(bot, 'notice_msg', 90) + except Exception as e: + writeLog('-----push_notice_msg error start -------') + print(mw.getTracebackInfo()) + writeLog('-----push_notice_msg error start -------') diff --git a/plugins/tgbot/startup/extend/readme.md b/plugins/tgbot/startup/extend/readme.md new file mode 100755 index 000000000..d52224d88 --- /dev/null +++ b/plugins/tgbot/startup/extend/readme.md @@ -0,0 +1 @@ +push_*.py 识别为推送插件 diff --git a/plugins/tgbot/startup/extend/receive_faq.py b/plugins/tgbot/startup/extend/receive_faq.py new file mode 100644 index 000000000..cbb1a2760 --- /dev/null +++ b/plugins/tgbot/startup/extend/receive_faq.py @@ -0,0 +1,217 @@ +import sys +import io +import os +import time +import re +import json +import base64 +import threading + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +import telebot +from telebot import types +from telebot.util import quick_markup + + +def isThisCmd(cmd, msg): + clen = len(cmd) + msg_len = len(msg) + if msg_len < clen: + return False + + check_msg = msg[0:clen] + if cmd == check_msg: + return True + return False + + +def getReadCmd(cmd, msg): + clen = len(cmd) + msg_len = len(msg) + real_msg = msg[clen:] + return real_msg + + +def getFaqKw(cmd): + matchObj = re.match(r'寻找【(.*?)】问题如下', cmd, re.M | re.I) + data = matchObj.groups() + if len(data) > 0: + return True, data[0] + return False, '' + + +def searchHttpPage(kw='', p=1, size=1): + import urllib + kw = kw.strip() + kw = urllib.parse.quote_plus(kw) + + api = 'https://bbs.midoks.icu/plugin.php?id=external_api&f=bbs_search&q=' + kw + \ + '&size=' + str(size) + '&p=' + str(p) + + # print('url', api) + data = mw.httpGet(api) + # print(data) + data = json.loads(data) + # print(data) + if data['code'] > -1: + + alist = data['data']['list'] + r = [] + for x in alist: + tmp = {} + tmp['tid'] = x['tid'] + tmp['subject'] = x['subject'] + tmp['url'] = 'https://bbs.midoks.icu/thread-' + \ + x['tid'] + '-1-1.html' + r.append(tmp) + data['data']['list'] = r + return data + + +def searchFaq(bot, message, cmd_text): + # cmd_text = 'mw' + data = searchHttpPage(cmd_text, 1, 5) + if data['code'] == 0 and len(data['data']['list']) > 0: + keyboard = [] + + dlist = data['data']['list'] + for x in dlist: + keyboard.append([types.InlineKeyboardButton( + text=x['subject'], url=x['url'])]) + + keyboard.append([ + types.InlineKeyboardButton( + text="下一页", callback_data='bbs_next_page_2'), + types.InlineKeyboardButton( + text="第" + str(data['data']['p']) + "页,共" + str(data['data']['page_num']) + "页", callback_data='bbs_page_total') + ]) + + keyboard.append([types.InlineKeyboardButton( + text="关闭消息", callback_data='bbs_search_close')]) + + # print(keyboard) + markup = types.InlineKeyboardMarkup(keyboard) + bot.send_message(message.chat.id, "寻找【" + + cmd_text.strip() + "】问题如下:", reply_markup=markup) + else: + keyboard = [ + [ + types.InlineKeyboardButton( + text="论坛", url='https://bbs.midoks.icu'), + types.InlineKeyboardButton( + text="搜索", url='https://bbs.midoks.icu/search.php') + ], + [ + types.InlineKeyboardButton( + text="关闭消息", callback_data='bbs_search_close') + ] + + ] + markup = types.InlineKeyboardMarkup(keyboard) + bot.send_message( + message.chat.id, "未找到合适内容,请在官方论坛[bbs.midoks.icu]提问!", reply_markup=markup) + + return True + + +def searchDebug(bot, message, cmd_text): + searchFaq(bot, message, cmd_text) + return True + + +def answer_callback_query(bot, call): + + keyword = call.data + + if keyword == 'bbs_search_close': + bot.delete_message(chat_id=call.message.chat.id, + message_id=call.message.message_id) + return + + is_bbs_page = False + p = 1 + if keyword.startswith('bbs_next_page'): + is_bbs_page = True + p = keyword.replace('bbs_next_page_', '') + + if keyword.startswith('bbs_pre_page'): + is_bbs_page = True + p = keyword.replace('bbs_pre_page_', '') + + # print("p", p) + if is_bbs_page: + is_match, cmd_text = getFaqKw(call.message.text) + if not is_match: + bot.edit_message_text( + chat_id=call.message.chat.id, message_id=call.message.message_id, text="出现错误!") + return + + data = searchHttpPage(cmd_text, int(p), 5) + + dlist = data['data']['list'] + # print(data) + keyboard = [] + for x in dlist: + keyboard.append([types.InlineKeyboardButton( + text=x['subject'], url=x['url'])]) + + page_nav = [] + if int(data['data']['p']) > 1: + page_nav.append(types.InlineKeyboardButton( + text="上一页", callback_data='bbs_pre_page_' + str(int(p) - 1))) + + if data['data']['page_num'] != data['data']['p']: + page_nav.append(types.InlineKeyboardButton( + text="下一页", callback_data='bbs_next_page_' + str(int(p) + 1))) + + page_nav.append(types.InlineKeyboardButton( + text="第" + str(data['data']['p']) + "页,共" + str(data['data']['page_num']) + "页", callback_data='bbs_page_total')) + + keyboard.append(page_nav) + + keyboard.append([types.InlineKeyboardButton( + text="关闭消息", callback_data='bbs_search_close')]) + + markup = types.InlineKeyboardMarkup(keyboard) + bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, + text=call.message.text, reply_markup=markup) + + +def run(bot, message): + text_body = message.text + + # 过滤URL + is_has_url = re.search( + '(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]', text_body) + if is_has_url: + return bot + + # print(text_body) + if isThisCmd('/?:', text_body): + cmd_text = getReadCmd('/?:', text_body) + cmd_text = cmd_text.strip().strip(":") + if cmd_text == "": + return bot.send_message(message.chat.id, "搜索内容不能为空, 例如:/?: 数据库") + return searchFaq(bot, message, cmd_text) + + if isThisCmd('/faq', text_body): + cmd_text = getReadCmd('/faq', text_body) + cmd_text = cmd_text.strip().strip(":") + if cmd_text == "": + return bot.send_message(message.chat.id, "搜索内容不能为空, 例如:/faq 数据库") + return searchFaq(bot, message, cmd_text) + + return bot + + +if __name__ == "__main__": + # print(isThisCmd('/?:', '/?:如何在安装面板')) + # print(getReadCmd('/?:', '/?:如何在安装面板')) + # print(searchHttpPage('mw')) + print(getFaqKw('寻找【mw】问题如下:')) diff --git a/plugins/tgbot/startup/extend/receive_music163_search.py b/plugins/tgbot/startup/extend/receive_music163_search.py new file mode 100644 index 000000000..f3a872021 --- /dev/null +++ b/plugins/tgbot/startup/extend/receive_music163_search.py @@ -0,0 +1,347 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +import telebot +from telebot import types +from telebot.util import quick_markup + +# 网易音乐搜索 + + +def isThisCmd(cmd, msg): + clen = len(cmd) + msg_len = len(msg) + if msg_len < clen: + return False + + check_msg = msg[0:clen] + if cmd == check_msg: + return True + return False + + +def getReadCmd(cmd, msg): + clen = len(cmd) + msg_len = len(msg) + real_msg = msg[clen:] + return real_msg + + +def ip2long(ip): + import struct + import socket + return struct.unpack("!L", socket.inet_aton(ip))[0] + + +def long2ip(longip): + import struct + import socket + return socket.inet_ntoa(struct.pack('!L', longip)) + + +def mt_rand(a, b): + import random + return random.randint(a, b) + + +def httpPost(url, data, timeout=10): + """ + 发送POST请求 + @url 被请求的URL地址(必需) + @data POST参数,可以是字符串或字典(必需) + @timeout 超时时间默认60秒 + return string + """ + try: + import urllib.request + import ssl + try: + ssl._create_default_https_context = ssl._create_unverified_context + except: + pass + + headers = { + 'Referer': 'https://music.163.com/', + 'Cookie': 'appver=8.2.30; os=iPhone OS; osver=15.0; EVNSM=1.0.0; buildver=2206; channel=distribution; machineid=iPhone13.3', + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 CloudMusic/0.1.1 NeteaseMusic/8.2.30', + 'X-Real-IP': long2ip(mt_rand(1884815360, 1884890111)), + 'Accept': '*/*', + 'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4', + 'Connection': 'keep-alive', + 'Content-Type': 'application/x-www-form-urlencoded', + } + data = urllib.parse.urlencode(data).encode('utf-8') + req = urllib.request.Request(url, data, headers=headers) + response = urllib.request.urlopen(req, timeout=timeout) + result = response.read() + if type(result) == bytes: + result = result.decode('utf-8') + return result + except Exception as ex: + return str(ex) + + +def musicSearch(kw, page=1, page_size=5): + m_offset = (int(page) - 1) * int(page_size) + data = httpPost('http://music.163.com/api/cloudsearch/pc', { + 's': kw, + 'type': '1', + 'total': 'true', + 'limit': page_size, + 'offset': m_offset, + }) + # data_a = json.loads(data) + # print(data) + # exit() + return json.loads(data) + + +def musicSongD(mid): + data = httpPost('http://music.163.com/api/v3/song/detail/', { + 'c': '[{"id":' + str(mid) + ',"v":0}]', + }) + # print(data) + return json.loads(data) + + +def musicSongDataUrl(mid): + data = httpPost('http://music.163.com/api/song/enhance/player/url', { + 'br': 320 * 1000, + 'ids': [mid], + }) + return json.loads(data) + + +def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + now = mw.getDateFromNow() + log_file = mw.getServerDir() + '/tgbot/task.log' + mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024) + return True + + +def tgSearchMusic_t(cmd_text): + data = musicSearch(cmd_text, 1, 5) + + if data['code'] == 200 and len(data['result']['songs']) > 0: + slist = data['result']['songs'] + print(slist) + else: + keyboard = [ + [ + types.InlineKeyboardButton( + text="论坛", url='https://bbs.midoks.icu'), + types.InlineKeyboardButton( + text="搜索", url='https://bbs.midoks.icu/search.php') + ], + [ + types.InlineKeyboardButton( + text="关闭消息", callback_data='bbs_search_close') + ] + + ] + markup = types.InlineKeyboardMarkup(keyboard) + bot.send_message( + message.chat.id, "未找到合适内容,请在官方论坛[bbs.midoks.icu]提问!", reply_markup=markup) + return True + + +def tgSearchMusic(bot, message, cmd_text): + import math + data = musicSearch(cmd_text, 1, 5) + if data['code'] == 200 and len(data['result']['songs']) > 0: + keyboard = [] + slist = data['result']['songs'] + page_total = math.ceil(data['result']['songCount'] / 5) + + for x in slist: + author = '' + if len(x['ar']) > 0: + author = ' - ' + x['ar'][0]['name'] + keyboard.append([types.InlineKeyboardButton( + text=x['name'] + author, callback_data='m163_id:' + str(x['id']))]) + + keyboard.append([ + types.InlineKeyboardButton( + text="下一页", callback_data='m163_next_page_2'), + types.InlineKeyboardButton( + text="第1页,共" + str(page_total) + "页", callback_data='m163_page_total') + ]) + + keyboard.append([types.InlineKeyboardButton( + text="关闭消息", callback_data='m163_search_close')]) + + # print(keyboard) + markup = types.InlineKeyboardMarkup(keyboard) + bot.send_message(message.chat.id, "寻找【" + + cmd_text.strip() + "】歌曲如下:", reply_markup=markup) + else: + bot.send_message(message.chat.id, "未找到合适内容") + return True + + +def getFaqKw(cmd): + matchObj = re.match(r'寻找【(.*?)】歌曲如下', cmd, re.M | re.I) + data = matchObj.groups() + if len(data) > 0: + return True, data[0] + return False, '' + + +def cleanMusicFileExpire(dir): + pass + + +def downloadAndUpMusic(bot, chat_id, mid, title): + import requests + murl_data = musicSongDataUrl(int(mid)) + murl = murl_data['data'][0]['url'] + def_dir = '/tmp/tgbot_music' + if not os.path.exists(def_dir): + os.mkdir(def_dir) + + def_abs_path = def_dir + '/' + title + '.mp3' + # print('downloadAndUpMusic' + ":" + str(murl)) + if murl: + msg_t = bot.send_message(chat_id, "已经获取资源URL,本地下载中...") + response = requests.get(murl) + + with open(def_abs_path, "wb") as f: + f.write(response.content) + + bot.edit_message_text( + chat_id=chat_id, message_id=msg_t.message_id, text="本地下载完,正在上传中...") + + audio = open(def_abs_path, 'rb') + bot.send_audio(chat_id, audio) + + bot.edit_message_text( + chat_id=chat_id, message_id=msg_t.message_id, text="上传结束...1s自动删除") + + time.sleep(1) + bot.delete_message(chat_id=chat_id, message_id=msg_t.message_id) + else: + bot.send_message(chat_id, "无效资源") + + if os.path.exists(def_abs_path): + os.remove(def_abs_path) + + # cleanMusicFileExpire(def_dir) + return True + + +def answer_callback_query(bot, call): + import math + keyword = call.data + # print(keyword) + if keyword == 'm163_search_close': + bot.delete_message(chat_id=call.message.chat.id, + message_id=call.message.message_id) + return + + # 音乐下载 + if keyword.startswith('m163_id:'): + t = keyword.split(":") + inline_keyboard = call.json['message'][ + "reply_markup"]["inline_keyboard"] + + def_file_name = 'demo' + for x in inline_keyboard: + # print(x) + if x[0]['callback_data'] == keyword: + def_file_name = x[0]['text'] + # print(call.message) + downloadAndUpMusic(bot, call.message.chat.id, t[1], def_file_name) + bot.delete_message(chat_id=call.message.chat.id, + message_id=call.message.message_id) + return True + + is_m163_page = False + p = 1 + if keyword.startswith('m163_next_page'): + is_m163_page = True + p = keyword.replace('m163_next_page_', '') + + if keyword.startswith('m163_pre_page'): + is_m163_page = True + p = keyword.replace('m163_pre_page_', '') + + if is_m163_page: + is_match, cmd_text = getFaqKw(call.message.text) + if not is_match: + bot.edit_message_text( + chat_id=call.message.chat.id, message_id=call.message.message_id, text="出现错误!") + return + + data = musicSearch(cmd_text, p, 5) + + dlist = data['result']['songs'] + page_total = math.ceil(data['result']['songCount'] / 5) + + keyboard = [] + for x in dlist: + author = '' + if len(x['ar']) > 0: + author = ' - ' + x['ar'][0]['name'] + keyboard.append([types.InlineKeyboardButton( + text=x['name'] + author, callback_data='m163_id:' + str(x['id']))]) + + page_nav = [] + if int(p) > 1: + page_nav.append(types.InlineKeyboardButton( + text="上一页", callback_data='m163_pre_page_' + str(int(p) - 1))) + + if int(p) < page_total: + page_nav.append(types.InlineKeyboardButton( + text="下一页", callback_data='m163_next_page_' + str(int(p) + 1))) + + page_nav.append(types.InlineKeyboardButton( + text="第" + str(p) + "页,共" + str(page_total) + "页", callback_data='m163_page_total')) + + keyboard.append(page_nav) + + keyboard.append([types.InlineKeyboardButton( + text="关闭消息", callback_data='m163_search_close')]) + + markup = types.InlineKeyboardMarkup(keyboard) + bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, + text=call.message.text, reply_markup=markup) + + +def run(bot, message): + text_body = message.text + + if isThisCmd('/music', text_body): + cmd_text = getReadCmd('/music', text_body) + cmd_text = cmd_text.strip().strip(":") + if cmd_text == "": + return bot.send_message(message.chat.id, "搜索内容不能为空, 例如:/music 刀郎") + return tgSearchMusic(bot, message, cmd_text) + + return bot + + +if __name__ == '__main__': + # cleanMusicFileExpire("/tmp/tgbot_music") + # tgSearchMusic_t("一夜泥工") + # print(long2ip(mt_rand(1884815360, 1884890111))) + t = musicSongDataUrl(2063487880) + print(t['data']) + print("111") diff --git a/plugins/tgbot/startup/tgbot.py b/plugins/tgbot/startup/tgbot.py new file mode 100644 index 000000000..60b4a5546 --- /dev/null +++ b/plugins/tgbot/startup/tgbot.py @@ -0,0 +1,150 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +import telebot + + +def getPluginName(): + return 'tgbot' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +sys.path.append(getServerDir() + "/extend") + + +def getConfigData(): + cfg_path = getServerDir() + "/data.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeConf(data): + cfg_path = getServerDir() + "/data.cfg" + mw.writeFile(cfg_path, json.dumps(data)) + return True + + +def getExtCfg(): + cfg_path = getServerDir() + "/extend.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def getStartExtCfgByTag(tag='push'): + # 获取开启的扩展 + elist = getExtCfg() + rlist = [] + for x in elist: + if x['tag'] == tag and x['status'] == 'start': + rlist.append(x) + return rlist + + +def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + now = mw.getDateFromNow() + log_file = getServerDir() + '/task.log' + mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024) + return True + +# start tgbot +cfg = getConfigData() +while True: + cfg = getConfigData() + if 'bot' in cfg and 'app_token' in cfg['bot']: + if cfg['bot']['app_token'] != '' and cfg['bot']['app_token'] != 'app_token': + break + writeLog('等待输入配置,填写app_token') + time.sleep(3) + + +bot = telebot.TeleBot(cfg['bot']['app_token']) + + +init_list = getStartExtCfgByTag('init') +for p in init_list: + try: + script = p['name'].split('.')[0] + __import__(script).init(bot) + except Exception as e: + writeLog('-----init error start -------') + writeLog(mw.getTracebackInfo()) + writeLog('-----init error end -------') + + +@bot.message_handler(commands=['chat_id']) +def hanle_get_chat_id(message): + bot.reply_to(message, message.chat.id) + + +@bot.message_handler(func=lambda message: True) +def all_message(message): + rlist = getStartExtCfgByTag('receive') + for r in rlist: + try: + script = r['name'].split('.')[0] + __import__(script).run(bot, message) + except Exception as e: + writeLog('-----all_message error start -------') + writeLog(mw.getTracebackInfo()) + writeLog('-----all_message error end -------') + + +@bot.callback_query_handler(func=lambda call: True) +def callback_query_handler(call): + rlist = getStartExtCfgByTag('receive') + for r in rlist: + try: + script = r['name'].split('.')[0] + __import__(script).answer_callback_query(bot, call) + except Exception as e: + writeLog('-----callback_query_handler error start -------') + writeLog(mw.getTracebackInfo()) + writeLog('-----callback_query_handler error end -------') + + +def runBot(bot): + try: + bot.polling() + except Exception as e: + writeLog('-----runBot error start -------') + writeLog(str(e)) + writeLog('-----runBot error end -------') + time.sleep(1) + runBot(bot) + +if __name__ == "__main__": + + writeLog('启动成功') + runBot(bot) + + +# asyncio.run(bot.polling()) diff --git a/plugins/tgbot/startup/tgpush.py b/plugins/tgbot/startup/tgpush.py new file mode 100644 index 000000000..57e2cd6f4 --- /dev/null +++ b/plugins/tgbot/startup/tgpush.py @@ -0,0 +1,136 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +import telebot + + +def getPluginName(): + return 'tgbot' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +sys.path.append(getServerDir() + "/extend") + + +def getConfigData(): + cfg_path = getServerDir() + "/data.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeConf(data): + cfg_path = getServerDir() + "/data.cfg" + mw.writeFile(cfg_path, json.dumps(data)) + return True + + +def getExtCfg(): + cfg_path = getServerDir() + "/extend.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def getStartExtCfgByTag(tag='push'): + # 获取开启的扩展 + elist = getExtCfg() + rlist = [] + for x in elist: + if x['tag'] == tag and x['status'] == 'start': + rlist.append(x) + return rlist + + +def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + now = mw.getDateFromNow() + log_file = getServerDir() + '/push.log' + mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024) + return True + +# start tgbot +cfg = getConfigData() +while True: + cfg = getConfigData() + if 'bot' in cfg and 'app_token' in cfg['bot']: + if cfg['bot']['app_token'] != '' and cfg['bot']['app_token'] != 'app_token': + break + writeLog('等待输入配置,填写app_token') + time.sleep(3) + + +bot = telebot.TeleBot(cfg['bot']['app_token']) + + +def runBotPushTask(): + plist = getStartExtCfgByTag('push') + for p in plist: + try: + script = p['name'].split('.')[0] + __import__(script).run(bot) + except Exception as e: + writeLog('-----runBotPushTask error start -------') + writeLog(mw.getTracebackInfo()) + writeLog('-----runBotPushTask error end -------') + + +def botPush(): + while True: + runBotPushTask() + time.sleep(1) + + +def runBotPushOtherTask(): + plist = getStartExtCfgByTag('other') + for p in plist: + try: + script = p['name'].split('.')[0] + __import__(script).run(bot) + except Exception as e: + writeLog('-----runBotPushOtherTask error start -------') + writeLog(mw.getTracebackInfo()) + writeLog('-----runBotPushOtherTask error end -------') + + +def botPushOther(): + while True: + runBotPushOtherTask() + time.sleep(1) + + +if __name__ == "__main__": + + # 机器人推送任务 + botPushTask = threading.Thread(target=botPush) + botPushTask.start() + + # 机器人其他推送任务 + botPushOtherTask = threading.Thread(target=botPushOther) + botPushOtherTask.start() diff --git a/plugins/tgbot/static/image/ad.png b/plugins/tgbot/static/image/ad.png new file mode 100644 index 000000000..32bcd144b Binary files /dev/null and b/plugins/tgbot/static/image/ad.png differ diff --git a/plugins/tgclient/ico.png b/plugins/tgclient/ico.png new file mode 100644 index 000000000..9416145b2 Binary files /dev/null and b/plugins/tgclient/ico.png differ diff --git a/plugins/tgclient/index.html b/plugins/tgclient/index.html new file mode 100755 index 000000000..77da84f68 --- /dev/null +++ b/plugins/tgclient/index.html @@ -0,0 +1,27 @@ +
                                  +
                                  +
                                  +
                                  +

                                  服务

                                  +

                                  自启动

                                  +

                                  配置

                                  +

                                  扩展列表

                                  +

                                  日志

                                  +

                                  说明

                                  +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + + \ No newline at end of file diff --git a/plugins/tgclient/index.py b/plugins/tgclient/index.py new file mode 100755 index 000000000..ec2eb7238 --- /dev/null +++ b/plugins/tgclient/index.py @@ -0,0 +1,375 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'tgclient' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConfigData(): + cfg_path = getServerDir() + "/data.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeConf(data): + cfg_path = getServerDir() + "/data.cfg" + mw.writeFile(cfg_path, json.dumps(data)) + return True + + +def getExtCfg(): + cfg_path = getServerDir() + "/extend.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeExtCfg(data): + cfg_path = getServerDir() + "/extend.cfg" + return mw.writeFile(cfg_path, json.dumps(data)) + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + data = mw.execShell( + "ps -ef|grep tgclient |grep -v grep | grep -v mdserver-web | awk '{print $2}'") + if data[0] == '': + return 'stop' + return 'start' + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + app_path = service_path + '/' + getPluginName() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + # if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path + '/mdserver-web') + content = content.replace('{$APP_PATH}', app_path) + + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + pyMainTplContent = mw.readFile(getPluginDir() + '/startup/tgclient.py') + toPyMainPath = mw.getServerDir() + '/tgclient.py' + mw.writeFile(toPyMainPath, pyMainTplContent) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/tgclient.service' + systemServiceTpl = getPluginDir() + '/init.d/tgclient.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$APP_PATH}', app_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def tbOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell(file + ' ' + method) + # print(data) + if data[1] == '': + return 'ok' + return 'ok' + + +def start(): + return tbOp('start') + + +def stop(): + return tbOp('stop') + + +def restart(): + status = tbOp('restart') + return status + + +def reload(): + + tgbot_tpl = getPluginDir() + '/startup/tgclient.py' + tgbot_dst = getServerDir() + '/tgclient.py' + + content = mw.readFile(tgbot_tpl) + mw.writeFile(tgbot_dst, content) + + ext_src = getPluginDir() + '/startup/extend' + ext_dst = getServerDir() + + mw.execShell('cp -rf ' + ext_src + ' ' + ext_dst) + + return tbOp('restart') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def getClientConf(): + data = getConfigData() + if 'bot' in data: + return mw.returnJson(True, 'ok', data['bot']) + return mw.returnJson(False, 'ok', {}) + + +def setClientConf(): + args = getArgs() + data_args = checkArgs(args, ['api_id', 'api_hash']) + if not data_args[0]: + return data_args[1] + + data = getConfigData() + args['api_id'] = base64.b64decode(args['api_id']).decode('ascii') + args['api_hash'] = base64.b64decode(args['api_hash']).decode('ascii') + data['bot'] = args + writeConf(data) + + return mw.returnJson(True, '保存成功!', []) + + +def installPreInspection(): + i = sys.version_info + if i[0] < 3 or i[1] < 7: + return "telebot在python小于3.7无法正常使用" + return 'ok' + + +def uninstallPreInspection(): + stop() + return "请手动删除
                                  rm -rf {}".format(getServerDir()) + + +def getExtCfgByName(name): + elist = getExtCfg() + for x in elist: + if x['name'] == name: + return x + return None + + +def clientExtList(): + + args = getArgs() + data_args = checkArgs(args, ['p']) + if not data_args[0]: + return data_args[1] + + ext_path = getServerDir() + '/extend' + if not os.path.exists(ext_path): + return mw.returnJson(False, 'ok', []) + elist_source = os.listdir(ext_path) + + elist = [] + for e in elist_source: + if e.endswith('py'): + elist.append(e) + + page = int(args['p']) + page_size = 5 + + make_ext_list = [] + for ex in elist: + tmp = {} + tmp['name'] = ex + edata = getExtCfgByName(ex) + if edata: + tmp['status'] = edata['status'] + else: + tmp['status'] = 'stop' + + tmp['tag'] = ex.split('_')[0] + make_ext_list.append(tmp) + + writeExtCfg(make_ext_list) + dlist_sum = len(make_ext_list) + + page_start = int((page - 1) * page_size) + page_end = page_start + page_size + + if page_end >= dlist_sum: + ret_data = make_ext_list[page_start:] + else: + ret_data = make_ext_list[page_start:page_end] + + data = {} + data['data'] = ret_data + data['args'] = args + data['list'] = mw.getPage( + {'count': dlist_sum, 'p': page, 'row': page_size, 'tojs': 'botExtListP'}) + + return mw.returnJson(True, 'ok', data) + + +def setExtStatus(): + args = getArgs() + data_args = checkArgs(args, ['name', 'status']) + if not data_args[0]: + return data_args[1] + + elist = getExtCfg() + name = args['name'] + status = args['status'] + for x in range(len(elist)): + if elist[x]['name'] == name: + elist[x]['status'] = status + break + + writeExtCfg(elist) + + action = '开启' + if status == 'stop': + action = '关闭' + + return mw.returnJson(True, action + '[' + name + ']扩展成功') + + +def runLog(): + p = getServerDir() + '/task.log' + return p + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'get_client_conf': + print(getClientConf()) + elif func == 'set_client_conf': + print(setClientConf()) + elif func == 'client_ext_list': + print(clientExtList()) + elif func == 'set_ext_status': + print(setExtStatus()) + elif func == 'run_log': + print(runLog()) + + else: + print('error') diff --git a/plugins/tgclient/info.json b/plugins/tgclient/info.json new file mode 100755 index 000000000..76bd12c43 --- /dev/null +++ b/plugins/tgclient/info.json @@ -0,0 +1,20 @@ +{ + "sort": 7, + "ps": "简单Telegram客服端管理", + "name": "tgclient", + "title": "tgclient", + "shell": "install.sh", + "versions":["1.1"], + "tip": "soft", + "checks": "server/tgclient", + "path": "server/tgclient", + "install_pre_inspection":true, + "uninstall_pre_inspection":true, + "display": 1, + "author": "midoks", + "date": "2023-03-06", + "home": "https://my.telegram.org/apps", + "depend_doc1":"https://docs.telethon.dev/en/stable/basic/installation.html", + "type": 0, + "pid": "5" +} \ No newline at end of file diff --git a/plugins/tgclient/init.d/tgclient.service.tpl b/plugins/tgclient/init.d/tgclient.service.tpl new file mode 100644 index 000000000..4edee4e7e --- /dev/null +++ b/plugins/tgclient/init.d/tgclient.service.tpl @@ -0,0 +1,14 @@ +[Unit] +Description=Tgbot Service +After=network.target + +[Service] +Type=forking +ExecStart={$APP_PATH}/init.d/tgclient start +ExecStop={$APP_PATH}/init.d/tgclient stop +ExecReload={$APP_PATH}/init.d/tgclient reload +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/tgclient/init.d/tgclient.tpl b/plugins/tgclient/init.d/tgclient.tpl new file mode 100644 index 000000000..b44d3fa01 --- /dev/null +++ b/plugins/tgclient/init.d/tgclient.tpl @@ -0,0 +1,86 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: Tgbot Service + +### BEGIN INIT INFO +# Provides: Tgbot +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts Tgbot +# Description: starts the MDW-Web +### END INIT INFO + +# Simple Tgbot init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +PATH=/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export LANG=en_US.UTF-8 + + +mw_path={$SERVER_PATH} +PATH=$PATH:$mw_path/bin + +if [ -f $mw_path/bin/activate ];then + source $mw_path/bin/activate +fi + +tg_start(){ + + isStart=`ps -ef|grep 'tgclient.py' |grep -v grep | awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "starting tgclient... \c" + cd $mw_path + echo "python3 {$APP_PATH}/tgclient.py" + python3 {$APP_PATH}/tgclient.py >> {$APP_PATH}/task.log & + isStart="" + while [[ "$isStart" == "" ]]; + do + echo -e ".\c" + sleep 0.5 + isStart=`ps -ef|grep 'tgclient.py' |grep -v grep | awk '{print $2}'` + let n+=1 + if [ $n -gt 20 ];then + break; + fi + done + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo -e "\033[31mError: tgclient service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "starting tgclient...(pid $(echo $isStart)) already running" + fi +} + + +tg_stop(){ + echo -e "stopping tgclient ... \c"; + arr=`ps aux|grep 'tgclient.py'|grep -v grep|awk '{print $2}'` + for p in ${arr[@]} + do + kill -9 $p > /dev/null 2>&1 + done + echo -e "\033[32mdone\033[0m" +} + +case "$1" in + start) + tg_start + ;; + stop) + tg_stop + ;; + restart|reload) + tg_stop + sleep 0.3 + tg_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/tgclient/install.sh b/plugins/tgclient/install.sh new file mode 100755 index 000000000..6757ba902 --- /dev/null +++ b/plugins/tgclient/install.sh @@ -0,0 +1,55 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +# pip3 install ccxt +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +pip3 install telethon + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/tgclient + echo "${VERSION}" > $serverPath/tgclient/version.pl + + cp -rf ${rootPath}/plugins/tgclient/startup/* $serverPath/tgclient + + cd ${rootPath} && python3 ${rootPath}/plugins/tgclient/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/tgclient/index.py initd_install + echo '安装完成' +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/tgclient.service ];then + systemctl stop tgclient + systemctl disable tgclient + rm -rf /usr/lib/systemd/system/tgclient.service + systemctl daemon-reload + fi + + if [ -f $serverPath/tgclient/initd/tgclient ];then + $serverPath/tgclient/initd/tgclient stop + fi + + rm -rf $serverPath/tgclient + echo "Uninstall_redis" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/tgclient/js/tgclient.js b/plugins/tgclient/js/tgclient.js new file mode 100755 index 000000000..39b11fac1 --- /dev/null +++ b/plugins/tgclient/js/tgclient.js @@ -0,0 +1,163 @@ +function readme(){ + var readme = '
                                    '; + readme += '
                                  • 在填写好配置信息好后,还要执行下面命令。进行手机号和短信码验证。再重启,即可正常使用
                                  • '; + readme += '
                                  • cd /www/server/mdserver-web && source bin/activate && python3 /www/server/tgclient/tgclient.py
                                  • '; + readme += '
                                  • https://my.telegram.org/auth
                                  • '; + readme += '
                                  '; + $('.soft-man-con').html(readme); +} + +function appPost(method, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'tgclient'; + req_data['func'] = method; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function appPostCallbak(method, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'tgclient'; + req_data['func'] = method; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function clientConf(){ + appPost('get_client_conf','',function(data){ + var rdata = $.parseJSON(data.data); + var api_id = 'api_id'; + var api_hash = 'api_hash'; + if(rdata['status']){ + db_data = rdata['data']; + + // api_id, api_hash + api_id = db_data['api_id']; + api_hash = db_data['api_hash']; + + } + + var mlist = ''; + mlist += '

                                  api_id必填写

                                  '; + mlist += '

                                  api_hash必填写

                                  '; + var option = '\ +
                                  \ + ' + mlist + '\ +
                                  \ + \ +
                                  \ +
                                  '; + $(".soft-man-con").html(option); + }); +} + +function submitBotConf(){ + var pull_data = {}; + pull_data['api_id'] = base64_encode($('input[name="api_id"]').val()); + pull_data['api_hash'] = base64_encode($('input[name="api_hash"]').val()); + appPost('set_client_conf',pull_data,function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000,shade: [0.3, '#000']}); + }); +} + + +function botExtList(){ + var body = '
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                  脚本类型状态
                                  \ + \ +
                                  '; + $('.soft-man-con').html(body); + botExtListP(1); +} + +function setBotExtStatus(name,status){ + appPost('set_ext_status',{'name':name,'status':status}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata['msg'],); + showMsg(rdata['msg'], function(){ + botExtListP(1); + },{icon:rdata['status']?1:2,shade: [0.3, '#000']},2000); + }); +} + +function botExtListP(p=1){ + appPost('client_ext_list',{'p':p}, function(rdata){ + // console.log(rdata); + var rdata = $.parseJSON(rdata.data); + // console.log(rdata); + var tBody = ''; + + if (!rdata.status && rdata.data.length == 0 ){ + var tBody = '
                                  无数据
                                  '; + } else{ + var ldata = rdata.data.data; + for (var i = 0; i < ldata.length; i++) { + tBody += '' + tBody += ''+ldata[i]['name']+''; + tBody += ''+ldata[i]['tag']+''; + + if (ldata[i]['status'] == 'start'){ + tBody += ''; + } else{ + tBody += ''; + } + tBody +=''; + } + } + + $('#ext_list').html(tBody); + $('#ext_list_page').html(rdata.data.list); + + $('#ext_list .ext_status').click(function(){ + var name = $(this).parent().parent().data('name'); + var status = 'stop'; + if ($(this).hasClass('glyphicon-pause')){ + status = 'start'; + } + setBotExtStatus(name,status); + }); + }); +} diff --git a/plugins/tgclient/startup/extend/client_ad.py b/plugins/tgclient/startup/extend/client_ad.py new file mode 100644 index 000000000..52a18e486 --- /dev/null +++ b/plugins/tgclient/startup/extend/client_ad.py @@ -0,0 +1,97 @@ +# coding:utf-8 + +# func: 在其他发送推送AD +# url: https://docs.telethon.dev/en/stable/modules/client.html +import sys +import io +import os +import time +import re +import json +import base64 +import threading +import asyncio + +sys.path.append(os.getcwd() + "/class/core") +import mw + +from telethon import utils +from telethon import functions, types +from telethon.tl.functions.messages import AddChatUserRequest +from telethon.tl.functions.channels import InviteToChannelRequest +# 指定群ID +chat_id_list = [-1001578009023] +filter_g_id = [-1001771526434] + + +msg_ad = "本人软件推广(10s)\n\n" +msg_ad += "开源Linux面板【mdserver-web】,站长必备,无毒,源码为证。\n" +msg_ad += "不收费,全靠TG乞讨! \n" +msg_ad += "看个人简介,加入群聊,一起进步!\n" +# msg_ad += "https://github.com/midoks/mdserver-web \n" +# msg_ad += "\n" +# msg_ad += "加入群聊,一起进步! \n" +# msg_ad += "https://t.me/mdserver_web \n" +# msg_ad += "不收费,无毒。源码为证。全靠TG乞讨!😭\n\n" +# msg_ad += "捐赠地址 USDT(TRC20)\n" +# msg_ad += "TVbNgrpeGBGZVm5gTLa21ADP7RpnPFhjya\n" +# msg_ad += "日行一善,以后必定大富大贵\n" + + +async def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + now = mw.getDateFromNow() + log_file = mw.getServerDir() + '/tgclient/task.log' + mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024) + return True + +async def send_msg(client, chat_id, tag='ad', trigger_time=600): + # 信号只在一个周期内执行一次|start + lock_file = mw.getServerDir() + '/tgclient/lock.json' + if not os.path.exists(lock_file): + mw.writeFile(lock_file, '{}') + + lock_data = json.loads(mw.readFile(lock_file)) + if tag in lock_data: + diff_time = time.time() - lock_data[tag]['do_time'] + if diff_time >= trigger_time: + lock_data[tag]['do_time'] = time.time() + else: + return False, 0, 0 + else: + lock_data[tag] = {'do_time': time.time()} + mw.writeFile(lock_file, json.dumps(lock_data)) + # 信号只在一个周期内执行一次|end + + msg = await client.send_message(chat_id, msg_ad) + await asyncio.sleep(10) + await client.delete_messages(chat_id, msg) + await asyncio.sleep(3) + +async def run(client): + client.parse_mode = 'html' + # for chat_id in chat_id_list: + # await send_msg(client, chat_id) + # await asyncio.sleep(30) + + info = await client.get_dialogs() + for chat in info: + if chat.is_group and not chat.id in filter_g_id: + chat_id = str(chat.id) + if chat_id[0:4] != '-100': + continue + + # print(chat) + await writeLog('name:{0} id:{1} is_user:{2} is_channel:{3} is_group:{4}'.format( + chat.name, chat.id, chat.is_user, chat.is_channel, chat.is_group)) + try: + await send_msg(client, chat.id, 'ad_' + str(chat.id)) + except Exception as e: + await writeLog(str(chat)) + await writeLog(str(e)) + + +if __name__ == "__main__": + pass diff --git a/plugins/tgclient/startup/extend/client_check_member.py b/plugins/tgclient/startup/extend/client_check_member.py new file mode 100644 index 000000000..3593c5396 --- /dev/null +++ b/plugins/tgclient/startup/extend/client_check_member.py @@ -0,0 +1,69 @@ +# coding:utf-8 + +# func: 自动检测已经注销群成员 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading +import asyncio + +sys.path.append(os.getcwd() + "/class/core") +import mw + +import telebot +from telebot import types +from telebot.util import quick_markup + + +# 指定群ID +chat_id_list = [-1001979545570] +# 别人群ID[有API调用限制] +chat_id_list_other = [-1001578009023, -1001771526434] + +async def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + now = mw.getDateFromNow() + log_file = mw.getServerDir() + '/tgclient/task.log' + mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024) + return True + +async def run(client): + for chat_id in chat_id_list: + try: + s = await client.send_message(chat_id, '开始自动检测已经注销群成员...') + count = 0 + async for user in client.iter_participants(chat_id): + if user.deleted: + count += 1 + msg = await client.kick_participant(chat_id, user) + + await client.edit_message(chat_id, s.id, '已经检测到有(%d)个账户已失效' % (count)) + await asyncio.sleep(3) + await client.edit_message(chat_id, s.id, '自动检测已经注销群成员完毕!!!') + await asyncio.sleep(3) + await client.delete_messages(chat_id, s) + except Exception as e: + print(str(e)) + writeLog(str(e)) + + for chat_id in chat_id_list_other: + try: + async for user in client.iter_participants(chat_id): + if user.deleted: + msg = await client.kick_participant(chat_id, user) + except Exception as e: + print(str(e)) + writeLog(str(e)) + + await asyncio.sleep(300) + + +if __name__ == "__main__": + pass diff --git a/plugins/tgclient/startup/extend/client_holding.py b/plugins/tgclient/startup/extend/client_holding.py new file mode 100644 index 000000000..6950efeb9 --- /dev/null +++ b/plugins/tgclient/startup/extend/client_holding.py @@ -0,0 +1,55 @@ +# coding:utf-8 + +# func: 自动邀请群成员 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading +import asyncio + +sys.path.append(os.getcwd() + "/class/core") +import mw + +from telethon import utils +from telethon import functions, types +from telethon.tl.functions.messages import AddChatUserRequest +from telethon.tl.functions.channels import InviteToChannelRequest +# 指定群ID +chat_id = -1001979545570 +filter_user_id = 5568699210 +filter_g_id = [-1001771526434] + + +async def run(client): + info = await client.get_dialogs() + for chat in info: + is_sleep = True + print('name:{0} id:{1} is_user:{2} is_channel:{3} is_group:{4}'.format( + chat.name, chat.id, chat.is_user, chat.is_channel, chat.is_group)) + if chat.is_group and chat.id != chat_id: + list_user = [] + async for user in client.iter_participants(chat.id): + if chat.id in filter_g_id: + is_sleep = False + continue + + if filter_user_id != user.id and user.username != None and user.bot == False: + list_user.append(user.username) + print(list_user) + try: + await client(InviteToChannelRequest( + channel=chat_id, # chat_id + users=list_user, # 被邀请人id + )) + except Exception as e: + print(str(e)) + if is_sleep: + await asyncio.sleep(90000) + +if __name__ == "__main__": + pass diff --git a/plugins/tgclient/startup/extend/client_temp.py b/plugins/tgclient/startup/extend/client_temp.py new file mode 100644 index 000000000..4071e77f0 --- /dev/null +++ b/plugins/tgclient/startup/extend/client_temp.py @@ -0,0 +1,54 @@ +# coding:utf-8 + +# func: 临时测试 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading +import asyncio + +sys.path.append(os.getcwd() + "/class/core") +import mw + +from telethon import utils +from telethon import functions, types +from telethon.tl.functions.messages import AddChatUserRequest +from telethon.tl.functions.channels import InviteToChannelRequest +# 指定群ID +chat_id = -1001979545570 +filter_user_id = 5568699210 +filter_g_id = [-1001771526434] + + +async def run(client): + + my_channel = await client.get_entity(PeerChannel(-1001173826177)) + print(my_channel) + + for i in range(9999999999): + try: + v = -1001000000000 - i + my_channel = await client.get_entity(PeerChannel(v)) + print(my_channel) + except Exception as e: + pass + + # -1001809140739 + # -1001800000000 + # -1000000000001 + + info = await client.get_dialogs() + for chat in info: + if not chat.is_group and chat.is_channel: + print('name:{0} id:{1} is_user:{2} is_channel:{3} is_group:{4}'.format( + chat.name, chat.id, chat.is_user, chat.is_channel, chat.is_group)) + await asyncio.sleep(10) + + +if __name__ == "__main__": + pass diff --git a/plugins/tgclient/startup/extend/readme.md b/plugins/tgclient/startup/extend/readme.md new file mode 100755 index 000000000..d52224d88 --- /dev/null +++ b/plugins/tgclient/startup/extend/readme.md @@ -0,0 +1 @@ +push_*.py 识别为推送插件 diff --git a/plugins/tgclient/startup/tgclient.py b/plugins/tgclient/startup/tgclient.py new file mode 100644 index 000000000..933ba7170 --- /dev/null +++ b/plugins/tgclient/startup/tgclient.py @@ -0,0 +1,132 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json +import base64 +import threading +import asyncio +import logging + +# python /Users/midoks/Desktop/mwdev/server/tgclient/tgclient.py + +''' +cd /www/server/mdserver-web && source bin/activate && python3 /www/server/tgclient/tgclient.py +''' + +from telethon import TelegramClient + + +sys.path.append(os.getcwd() + "/class/core") +import mw + +logging.basicConfig(level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + + +def getPluginName(): + return 'tgclient' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +sys.path.append(getServerDir() + "/extend") + + +def getConfigData(): + cfg_path = getServerDir() + "/data.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def writeConf(data): + cfg_path = getServerDir() + "/data.cfg" + mw.writeFile(cfg_path, json.dumps(data)) + return True + + +def getExtCfg(): + cfg_path = getServerDir() + "/extend.cfg" + if not os.path.exists(cfg_path): + mw.writeFile(cfg_path, '{}') + t = mw.readFile(cfg_path) + return json.loads(t) + + +def getStartExtCfgByTag(tag='push'): + # 获取开启的扩展 + elist = getExtCfg() + rlist = [] + for x in elist: + if x['tag'] == tag and x['status'] == 'start': + rlist.append(x) + return rlist + + +def writeLog(log_str): + if __name__ == "__main__": + print(log_str) + + now = mw.getDateFromNow() + log_file = getServerDir() + '/task.log' + mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024) + return True + + +# start tgbot +cfg = getConfigData() +while True: + cfg = getConfigData() + if 'bot' in cfg and 'api_id' in cfg['bot']: + if cfg['bot']['api_id'] != '' and cfg['bot']['api_id'] != 'api_id': + break + if cfg['bot']['api_hash'] != '' and cfg['bot']['api_hash'] != 'api_hash': + break + writeLog('等待输入配置,api_id') + time.sleep(3) + +client = TelegramClient('mdioks', cfg['bot']['api_id'], cfg['bot']['api_hash']) + +async def plugins_run_task(): + plist = getStartExtCfgByTag('client') + for p in plist: + try: + script = p['name'].split('.')[0] + await __import__(script).run(client) + except Exception as e: + writeLog('----- client error start -------') + writeLog(mw.getTracebackInfo()) + writeLog('----- client error end -------') + +async def plugins_run(): + while True: + await plugins_run_task() + time.sleep(1) + +async def main(loop): + await client.start() + + # create new task + writeLog('creating plugins_run task.') + task = loop.create_task(plugins_run()) + await task + + writeLog('It works.') + await client.run_until_disconnected() + task.cancel() + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + loop.run_until_complete(main(loop)) diff --git a/plugins/valkey/config/valkey.conf b/plugins/valkey/config/valkey.conf new file mode 100644 index 000000000..318a13c0a --- /dev/null +++ b/plugins/valkey/config/valkey.conf @@ -0,0 +1,90 @@ +daemonize yes +pidfile {$SERVER_PATH}/valkey/valkey.pid + +bind 127.0.0.1 +port 6389 +requirepass {$VALKEY_PASS} + +timeout 3 +tcp-keepalive 0 + +loglevel notice + +logfile {$SERVER_PATH}/valkey/data/valkey.log +databases 16 + +################################ SNAPSHOTTING ################################# + +save 900 1000 +save 300 10000 +save 60 1000000 +stop-writes-on-bgsave-error no +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir {$SERVER_PATH}/valkey/data/ + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 218mb +#maxmemory-policy volatile-ttl +maxmemory-policy allkeys-lru + +############################## APPEND ONLY MODE ############################### + +# appendonly no + +# appendfsync always +# appendfsync everysec +# appendfsync no + +# appendfilename "appendonly.aof" + +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing yes + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/valkey/ico.png b/plugins/valkey/ico.png new file mode 100644 index 000000000..80a6300fd Binary files /dev/null and b/plugins/valkey/ico.png differ diff --git a/plugins/valkey/index.html b/plugins/valkey/index.html new file mode 100755 index 000000000..894b2ffe6 --- /dev/null +++ b/plugins/valkey/index.html @@ -0,0 +1,36 @@ + + +
                                  +
                                  +
                                  +
                                  +

                                  服务

                                  +

                                  自启动

                                  +

                                  配置修改

                                  + +

                                  性能调整

                                  +

                                  负载状态

                                  +

                                  复制状态

                                  +

                                  集群状态

                                  +

                                  集群节点

                                  +

                                  运行日志

                                  +

                                  相关说明

                                  + +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + \ No newline at end of file diff --git a/plugins/valkey/index.py b/plugins/valkey/index.py new file mode 100755 index 000000000..11d175df9 --- /dev/null +++ b/plugins/valkey/index.py @@ -0,0 +1,570 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'valkey' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/valkey.conf" + return path + + +def getConfTpl(): + path = getPluginDir() + "/config/valkey.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + pid_file = getPidFile() + if not os.path.exists(pid_file): + return 'stop' + + # data = mw.execShell( + # "ps aux|grep redis |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + + # if data[0] == '': + # return 'stop' + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/'+getPluginName()) + content = content.replace('{$VALKEY_PASS}', mw.getRandomString(10)) + return content + + + +def initDreplace(): + + file_tpl = getInitDTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/' + getPluginName() + + # initd replace + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # log + dataLog = getServerDir() + '/data' + if not os.path.exists(dataLog): + mw.execShell('mkdir -p ' + dataLog) + mw.execShell('chmod +x ' + file_bin) + + # config replace + dst_conf = getConf() + dst_conf_init = getServerDir() + '/init.pl' + if not os.path.exists(dst_conf_init): + content = mw.readFile(getConfTpl()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$VALKEY_PASS}', mw.getRandomString(10)) + + mw.writeFile(dst_conf, content) + mw.writeFile(dst_conf_init, 'ok') + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/' + getPluginName() + '.service' + if os.path.exists(systemDir) and not os.path.exists(systemService): + systemServiceTpl = getPluginDir() + '/init.d/' + getPluginName() + '.service.tpl' + content = mw.readFile(systemServiceTpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, content) + mw.execShell('systemctl daemon-reload') + + return file_bin + + +def wkOp(method): + file = initDreplace() + + current_os = mw.getOs() + if current_os == "darwin": + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + if current_os.startswith("freebsd"): + data = mw.execShell('service ' + getPluginName() + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return wkOp('start') + + +def stop(): + return wkOp('stop') + + +def restart(): + status = wkOp('restart') + + log_file = runLog() + mw.execShell("echo '' > " + log_file) + return status + + +def reload(): + return wkOp('reload') + + +def getPort(): + conf = getServerDir() + '/valkey.conf' + content = mw.readFile(conf) + + rep = r"^(port)\s*([.0-9A-Za-z_& ~]+)" + tmp = re.search(rep, content, re.M) + if tmp: + return tmp.groups()[1] + + return '6379' + + +def getRedisCmd(): + requirepass = "" + conf = getConf() + content = mw.readFile(conf) + rep = r"^(requirepass)\s*([.0-9A-Za-z_& ~]+)" + tmp = re.search(rep, content, re.M) + if tmp: + requirepass = tmp.groups()[1] + + default_ip = '127.0.0.1' + port = getPort() + # findDebian = mw.execShell('cat /etc/issue |grep Debian') + # if findDebian[0] != '': + # default_ip = mw.getLocalIp() + cmd = getServerDir() + "/bin/valkey-cli -h " + default_ip + ' -p ' + port + " " + + if requirepass != "": + cmd = getServerDir() + '/bin/valkey-cli -h ' + default_ip + ' -p ' + port + ' -a "' + requirepass + '" ' + + return cmd + +def runInfo(): + s = status() + if s == 'stop': + return mw.returnJson(False, '未启动') + + + cmd = getRedisCmd() + cmd = cmd + 'info' + data = mw.execShell(cmd)[0] + res = [ + 'tcp_port', + 'uptime_in_days', # 已运行天数 + 'connected_clients', # 连接的客户端数量 + 'used_memory', # Redis已分配的内存总量 + 'used_memory_rss', # Redis占用的系统内存总量 + 'used_memory_peak', # Redis所用内存的高峰值 + 'mem_fragmentation_ratio', # 内存碎片比率 + 'total_connections_received', # 运行以来连接过的客户端的总数量 + 'total_commands_processed', # 运行以来执行过的命令的总数量 + 'instantaneous_ops_per_sec', # 服务器每秒钟执行的命令数量 + 'keyspace_hits', # 查找数据库键成功的次数 + 'keyspace_misses', # 查找数据库键失败的次数 + 'latest_fork_usec' # 最近一次 fork() 操作耗费的毫秒数 + ] + data = data.split("\n") + result = {} + for d in data: + if len(d) < 3: + continue + t = d.strip().split(':') + if not t[0] in res: + continue + result[t[0]] = t[1] + return mw.getJson(result) + +def infoReplication(): + # 复制信息 + s = status() + if s == 'stop': + return mw.returnJson(False, '未启动') + + cmd = getRedisCmd() + cmd = cmd + 'info replication' + + # print(cmd) + data = mw.execShell(cmd)[0] + # print(data) + res = [ + #slave + 'role',#角色 + 'master_host', # 连接主库HOST + 'master_port', # 连接主库PORT + 'master_link_status', # 连接主库状态 + 'master_last_io_seconds_ago', # 上次同步时间 + 'master_sync_in_progress', # 正在同步中 + 'slave_read_repl_offset', # 从库读取复制位置 + 'slave_repl_offset', # 从库复制位置 + 'slave_priority', # 从库同步优先级 + 'slave_read_only', # 从库是否仅读 + 'replica_announced', # 已复制副本 + 'connected_slaves', # 连接从库数量 + 'master_failover_state', # 主库故障状态 + 'master_replid', # 主库复制ID + 'master_repl_offset', # 主库复制位置 + 'second_repl_offset', # 主库复制位置时间 + 'repl_backlog_active', # 复制状态 + 'repl_backlog_size', # 复制大小 + 'repl_backlog_first_byte_offset', # 第一个字节偏移量 + 'repl_backlog_histlen', # backlog中数据的长度 + ] + + data = data.split("\n") + result = {} + for d in data: + if len(d) < 3: + continue + t = d.strip().split(':') + if not t[0] in res: + continue + result[t[0]] = t[1] + + if 'role' in result and result['role'] == 'master': + connected_slaves = int(result['connected_slaves']) + slave_l = [] + for x in range(connected_slaves): + slave_l.append('slave'+str(x)) + + for d in data: + if len(d) < 3: + continue + t = d.strip().split(':') + if not t[0] in slave_l: + continue + result[t[0]] = t[1] + + return mw.getJson(result) + + +def clusterInfo(): + #集群信息 + # https://redis.io/commands/cluster-info/ + s = status() + if s == 'stop': + return mw.returnJson(False, '未启动') + + cmd = getRedisCmd() + cmd = cmd + 'cluster info' + + # print(cmd) + data = mw.execShell(cmd)[0] + # print(data) + + res = [ + 'cluster_state',#状态 + 'cluster_slots_assigned', # 被分配的槽 + 'cluster_slots_ok', # 被分配的槽状态 + 'cluster_slots_pfail', # 连接主库状态 + 'cluster_slots_fail', # 失败的槽 + 'cluster_known_nodes', # 知道的节点 + 'cluster_size', # 大小 + 'cluster_current_epoch', # + 'cluster_my_epoch', # + 'cluster_stats_messages_sent', # 发送 + 'cluster_stats_messages_received', # 接受 + 'total_cluster_links_buffer_limit_exceeded', # + ] + + data = data.split("\n") + result = {} + for d in data: + if len(d) < 3: + continue + t = d.strip().split(':') + if not t[0] in res: + continue + result[t[0]] = t[1] + + return mw.getJson(result) + +def clusterNodes(): + s = status() + if s == 'stop': + return mw.returnJson(False, '未启动') + + cmd = getRedisCmd() + cmd = cmd + 'cluster nodes' + + # print(cmd) + data = mw.execShell(cmd)[0] + # print(data) + + data = data.strip().split("\n") + return mw.getJson(data) + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + if os.path.exists(initd_bin): + return 'ok' + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + # freebsd initd install + if current_os.startswith('freebsd'): + import shutil + source_bin = initDreplace() + initd_bin = getInitDFile() + shutil.copyfile(source_bin, initd_bin) + mw.execShell('chmod +x ' + initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="YES"') + return 'ok' + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + if current_os.startswith('freebsd'): + initd_bin = getInitDFile() + os.remove(initd_bin) + mw.execShell('sysrc ' + getPluginName() + '_enable="NO"') + return 'ok' + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + return getServerDir() + '/data/valkey.log' + + +def getRedisConfInfo(): + conf = getConf() + + gets = [ + {'name': 'bind', 'type': 2, 'ps': '绑定IP(修改绑定IP可能会存在安全隐患)','must_show':1}, + {'name': 'port', 'type': 2, 'ps': '绑定端口','must_show':1}, + {'name': 'timeout', 'type': 2, 'ps': '空闲链接超时时间,0表示不断开','must_show':1}, + {'name': 'maxclients', 'type': 2, 'ps': '最大连接数','must_show':1}, + {'name': 'databases', 'type': 2, 'ps': '数据库数量','must_show':1}, + {'name': 'requirepass', 'type': 2, 'ps': 'redis密码,留空代表没有设置密码','must_show':1}, + {'name': 'maxmemory', 'type': 2, 'ps': 'MB,最大使用内存,0表示不限制','must_show':1}, + {'name': 'slaveof', 'type': 2, 'ps': '同步主库地址','must_show':0}, + {'name': 'masterauth', 'type': 2, 'ps': '同步主库密码', 'must_show':0} + ] + content = mw.readFile(conf) + + result = [] + for g in gets: + rep = r"^(" + g['name'] + r')\s*([.0-9A-Za-z_& ~]+)' + tmp = re.search(rep, content, re.M) + if not tmp: + if g['must_show'] == 0: + continue + + g['value'] = '' + result.append(g) + continue + g['value'] = tmp.groups()[1] + if g['name'] == 'maxmemory': + g['value'] = g['value'].strip("mb") + result.append(g) + + return result + + +def getRedisConf(): + data = getRedisConfInfo() + return mw.getJson(data) + + +def submitRedisConf(): + gets = ['bind', 'port', 'timeout', 'maxclients', + 'databases', 'requirepass', 'maxmemory','slaveof','masterauth'] + args = getArgs() + conf = getConf() + content = mw.readFile(conf) + for g in gets: + if g in args: + rep = g + r'\s*([.0-9A-Za-z_& ~]+)' + val = g + ' ' + args[g] + + if g == 'maxmemory': + val = g + ' ' + args[g] + "mb" + + if g == 'requirepass' and args[g] == '': + content = re.sub('requirepass', '#requirepass', content) + if g == 'requirepass' and args[g] != '': + content = re.sub('#requirepass', 'requirepass', content) + content = re.sub(rep, val, content) + + if g != 'requirepass': + content = re.sub(rep, val, content) + mw.writeFile(conf, content) + reload() + return mw.returnJson(True, '设置成功') + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'info_replication': + print(infoReplication()) + elif func == 'cluster_info': + print(clusterInfo()) + elif func == 'cluster_nodes': + print(clusterNodes()) + elif func == 'conf': + print(getConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'get_redis_conf': + print(getRedisConf()) + elif func == 'submit_redis_conf': + print(submitRedisConf()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + else: + print('error') diff --git a/plugins/valkey/info.json b/plugins/valkey/info.json new file mode 100755 index 000000000..f52232dcf --- /dev/null +++ b/plugins/valkey/info.json @@ -0,0 +1,17 @@ +{ + "sort": 8, + "ps": "一个高性能键值数据存储系统", + "name": "valkey", + "title": "valkey", + "shell": "install.sh", + "versions":["8.0.1","8.1.3"], + "tip": "soft", + "checks": "server/valkey", + "path": "server/valkey", + "display": 1, + "author": "valkey", + "date": "2024-12-08", + "home": "https://valkey.io/download/", + "type": 0, + "pid": "2" +} diff --git a/plugins/valkey/init.d/valkey.service.tpl b/plugins/valkey/init.d/valkey.service.tpl new file mode 100644 index 000000000..ac2e6201b --- /dev/null +++ b/plugins/valkey/init.d/valkey.service.tpl @@ -0,0 +1,12 @@ +[Unit] +Description=Redis In-Memory Data Store +After=network.target + +[Service] +Type=forking +ExecStart={$SERVER_PATH}/valkey/bin/valkey-server {$SERVER_PATH}/valkey/valkey.conf +ExecReload=/bin/kill -USR2 $MAINPID +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plugins/valkey/init.d/valkey.tpl b/plugins/valkey/init.d/valkey.tpl new file mode 100644 index 000000000..c2d01c3c2 --- /dev/null +++ b/plugins/valkey/init.d/valkey.tpl @@ -0,0 +1,78 @@ +#!/bin/sh +# chkconfig: 2345 55 25 +# description: Redis Service + +### BEGIN INIT INFO +# Provides: Redis +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts Redis +# Description: starts the MDW-Web +### END INIT INFO + +# Simple Redis init.d script conceived to work on Linux systems +# as it does use of the /proc filesystem. + +CONF="{$SERVER_PATH}/valkey/valkey.conf" +REDISPORT=$(cat $CONF |grep port|grep -v '#'|awk '{print $2}') +REDISPASS=$(cat $CONF |grep requirepass|grep -v '#'|awk '{print $2}') +if [ "$REDISPASS" != "" ];then + REDISPASS=" -a $REDISPASS" +fi +EXEC={$SERVER_PATH}/valkey/bin/valkey-server +CLIEXEC="{$SERVER_PATH}/valkey/bin/valkey-cli -p $REDISPORT$REDISPASS" +PIDFILE={$SERVER_PATH}/valkey/valkey.pid + +echo $REDISPASS +echo $REDISPORT +echo $CLIEXEC + +mkdir -p {$SERVER_PATH}/valkey/data + +valkey_start(){ + if [ -f $PIDFILE ];then + kill -9 `cat $PIDFILE` + fi + + echo "Starting valkey server..." + nohup $EXEC $CONF >> {$SERVER_PATH}/valkey/logs.pl 2>&1 & +} + +valkey_stop(){ + if [ ! -f $PIDFILE ] + then + echo "$PIDFILE does not exist, process is not running" + else + PID=$(cat $PIDFILE) + echo "Stopping ..." + $CLIEXEC shutdown save 2>/dev/null + while [ -x /proc/${PID} ] + do + echo "Waiting for valkey to shutdown ..." + sleep 1 + done + echo "valkey stopped" + rm -rf $PIDFILE + fi +} + + +case "$1" in + start) + valkey_start + ;; + stop) + valkey_stop + ;; + restart|reload) + valkey_stop + sleep 0.3 + valkey_start + ;; + *) + echo "Please use start or stop as first argument" + ;; +esac + diff --git a/plugins/valkey/install.sh b/plugins/valkey/install.sh new file mode 100755 index 000000000..a8343e137 --- /dev/null +++ b/plugins/valkey/install.sh @@ -0,0 +1,96 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +# https://www.cnblogs.com/zlonger/p/16177595.html +# https://www.cnblogs.com/BNTang/articles/15841688.html + +# ps -ef|grep valkey |grep -v grep | awk '{print $2}' | xargs kill + +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/valkey && bash install.sh install 7.2.2 + +# cmd查看| info replication +# /Users/midoks/Desktop/mwdev/server/valkey/bin/valkey-cli -h 127.0.0.1 -p 6399 +# /www/server/valkey/bin/valkey-cli -h 127.0.0.1 -p 6399 + +VERSION=$2 + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + mkdir -p $serverPath/source/valkey + + FILE_TGZ=${VERSION}.tar.gz + VALKEY_DIR=$serverPath/source/valkey + + if [ ! -f $VALKEY_DIR/${FILE_TGZ} ];then + wget --no-check-certificate -O $VALKEY_DIR/${FILE_TGZ} https://github.com/valkey-io/valkey/archive/refs/tags/${FILE_TGZ} + fi + + cd $VALKEY_DIR && tar -zxvf ${FILE_TGZ} + + CMD_MAKE=`which gmake` + if [ "$?" == "0" ];then + cd valkey-${VERSION} && gmake PREFIX=$serverPath/valkey install + else + cd valkey-${VERSION} && make PREFIX=$serverPath/valkey install + fi + + if [ -d $serverPath/valkey ];then + mkdir -p $serverPath/valkey/data + sed '/^ *#/d' valkey.conf > $serverPath/valkey/valkey.conf + + echo "${VERSION}" > $serverPath/valkey/version.pl + echo '安装完成' + + cd ${rootPath} && python3 plugins/valkey/index.py start + cd ${rootPath} && python3 plugins/valkey/index.py initd_install + + else + echo '安装失败!' + fi + + if [ -d ${REDIS_DIR}/valkey-${VERSION} ];then + rm -rf ${REDIS_DIR}/valkey-${VERSION} + fi +} + +Uninstall_App() +{ + if [ -f /usr/lib/systemd/system/valkey.service ];then + systemctl stop valkey + systemctl disable valkey + rm -rf /usr/lib/systemd/system/valkey.service + systemctl daemon-reload + fi + + if [ -f /lib/systemd/system/valkey.service ];then + systemctl stop valkey + systemctl disable valkey + rm -rf /lib/systemd/system/valkey.service + systemctl daemon-reload + fi + + if [ -f $serverPath/valkey/initd/valkey ];then + $serverPath/valkey/initd/valkey stop + fi + + if [ -d $serverPath/valkey ];then + rm -rf $serverPath/valkey + fi + + echo "卸载valkey成功" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/valkey/js/valkey.js b/plugins/valkey/js/valkey.js new file mode 100755 index 000000000..9540415f4 --- /dev/null +++ b/plugins/valkey/js/valkey.js @@ -0,0 +1,297 @@ +function redisPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'valkey'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function redisPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'valkey'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +//redis状态 start +function redisStatus(version) { + + redisPost('run_info',version, {},function(data){ + var rdata = $.parseJSON(data.data); + + if ('status' in rdata && !rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + hit = (parseInt(rdata.keyspace_hits) / (parseInt(rdata.keyspace_hits) + parseInt(rdata.keyspace_misses)) * 100).toFixed(2); + var con = '
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                  字段当前值说明
                                  uptime_in_days' + rdata.uptime_in_days + '已运行天数
                                  tcp_port' + rdata.tcp_port + '当前监听端口
                                  connected_clients' + rdata.connected_clients + '连接的客户端数量
                                  used_memory_rss' + toSize(rdata.used_memory_rss) + 'Redis当前占用的系统内存总量
                                  used_memory' + toSize(rdata.used_memory) + 'Redis当前已分配的内存总量
                                  used_memory_peak' + toSize(rdata.used_memory_peak) + 'Redis历史分配内存的峰值
                                  mem_fragmentation_ratio' + rdata.mem_fragmentation_ratio + '%内存碎片比率
                                  total_connections_received' + rdata.total_connections_received + '运行以来连接过的客户端的总数量
                                  total_commands_processed' + rdata.total_commands_processed + '运行以来执行过的命令的总数量
                                  instantaneous_ops_per_sec' + rdata.instantaneous_ops_per_sec + '服务器每秒钟执行的命令数量
                                  keyspace_hits' + rdata.keyspace_hits + '查找数据库键成功的次数
                                  keyspace_misses' + rdata.keyspace_misses + '查找数据库键失败的次数
                                  hit' + hit + '%查找数据库键命中率
                                  latest_fork_usec' + rdata.latest_fork_usec + '最近一次 fork() 操作耗费的微秒数
                                  '; + $(".soft-man-con").html(con); + }); +} + +function replStatus(version){ + redisPost('info_replication', version, {},function(data){ + var rdata = $.parseJSON(data.data); + + if ('status' in rdata && !rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var kv = { + 'role':'角色', + 'master_host':'连接主库HOST', + 'master_port':'连接主库PORT', + 'master_link_status':'连接主库状态', + 'master_last_io_seconds_ago':'上次同步时间', + 'master_sync_in_progress':'正在同步中', + 'slave_read_repl_offset':'从库读取复制位置', + 'slave_repl_offset':'从库复制位置', + 'slave_read_only':'从库是否仅读', + 'replica_announced':'已复制副本', + 'connected_slaves':'连接数量', + 'master_failover_state':'主库故障状态', + 'master_replid':'主库复制ID', + 'master_repl_offset':'主库复制位置', + 'repl_backlog_size':'backlog复制大小', + 'second_repl_offset':'复制位置时间', + 'repl_backlog_first_byte_offset':'第一个字节偏移量', + 'repl_backlog_histlen':'backlog中数据的长度', + 'repl_backlog_active':'开启复制缓冲区', + 'slave_priority':'同步优先级', + } + + var tbody_text = ''; + for (k in rdata){ + if (k == 'master_replid'){ + tbody_text += ''+k+'' + rdata[k] + ''+kv[k]+''; + } else{ + + if (k.substring(0,5) == 'slave' && !isNaN(k.substring(5))){ + tbody_text += ''+k+'' + rdata[k] + '从库配置信息'; + } else{ + tbody_text += ''+k+'' + rdata[k] + ''+kv[k]+''; + } + + + } + } + + var con = '
                                  \ + \ + \ + '+tbody_text+'\ +
                                  字段当前值说明
                                  '; + $(".soft-man-con").html(con); + }); +} + +function clusterStatus(version){ + redisPost('cluster_info', version, {},function(data){ + var rdata = $.parseJSON(data.data); + + if ('status' in rdata && !rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var kv = { + 'cluster_state':'集群状态', + 'cluster_slots_assigned':'被分配的槽', + 'cluster_slots_ok':'被分配的槽状态', + 'cluster_known_nodes':'知道的节点', + 'cluster_size':'大小', + 'cluster_stats_messages_sent':'发送', + 'cluster_stats_messages_received':'接收', + 'cluster_current_epoch':'集群当前epoch', + 'cluster_my_epoch':'当前我的epoch', + 'cluster_slots_pfail':'处于PFAIL状态的槽数', + 'cluster_slots_fail':'处于FAIL状态的槽数', + 'total_cluster_links_buffer_limit_exceeded':'超出缓冲区总数', + } + + var tbody_text = ''; + for (k in rdata){ + var desc = k; + if (k in kv){ + desc = kv[k]; + } + + if (k == 'master_replid'){ + tbody_text += ''+k+'' + rdata[k] + ''+desc+''; + } else{ + tbody_text += ''+k+'' + rdata[k] + ''+desc+''; + } + } + + if (tbody_text == ''){ + tbody_text += '无数据/未设置集群'; + } + + var con = '
                                  \ + \ + \ + '+tbody_text+'\ +
                                  字段当前值说明
                                  '; + $(".soft-man-con").html(con); + }); +} + +function clusterNodes(version){ + redisPost('cluster_nodes', version, {},function(data){ + var rdata = $.parseJSON(data.data); + + if ('status' in rdata && !rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + // console.log(rdata); + var tbody_text = ''; + for (k in rdata){ + tbody_text += ''+ rdata[k] +''; + } + + if (tbody_text == ''){ + tbody_text += '无数据/未设置集群'; + } + + var con = '
                                  \ + \ + \ + '+tbody_text+'\ +
                                  节点信息
                                  '; + $(".soft-man-con").html(con); + }); +} + +//redis状态 end + +//配置修改 +function getRedisConfig(version) { + redisPost('get_redis_conf', version,'',function(data){ + // console.log(data); + var rdata = $.parseJSON(data.data); + // console.log(rdata); + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '70' + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = (rdata[i].value == 1) ? 'selected' : ''; + var selected_0 = (rdata[i].value == 0) ? 'selected' : ''; + ibody = '' + break; + case 1: + var selected_1 = (rdata[i].value == 'On') ? 'selected' : ''; + var selected_0 = (rdata[i].value == 'Off') ? 'selected' : ''; + ibody = '' + break; + } + mlist += '

                                  ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

                                  ' + } + var con = '
                                  ' + mlist + '\ +
                                  \ +
                                  \ +
                                  ' + $(".soft-man-con").html(con); + }); +} + +//提交配置 +function submitConf(version) { + var data = { + version: version, + bind: $("input[name='bind']").val(), + 'port': $("input[name='port']").val(), + 'timeout': $("input[name='timeout']").val(), + maxclients: $("input[name='maxclients']").val(), + databases: $("input[name='databases']").val(), + requirepass: $("input[name='requirepass']").val(), + maxmemory: $("input[name='maxmemory']").val(), + }; + + redisPost('submit_redis_conf', version, data, function(ret_data){ + var rdata = $.parseJSON(ret_data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} + + +function valkeyReadme(){ + var cmd_01 = '/www/server/valkey/bin/valkey-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 --cluster-replicas 0'; + var cmd_02 = '/www/server/valkey/bin/valkey-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster-replicas 1'; + + + var readme = '
                                    '; + readme += '
                                  • 集群创建1
                                  • '; + readme += '
                                  • '+cmd_01+'
                                  • '; + readme += '
                                  • 集群创建2
                                  • '; + readme += '
                                  • '+cmd_02+'
                                  • '; + readme += '
                                  '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/valkey/tpl/valkey_cluster.conf b/plugins/valkey/tpl/valkey_cluster.conf new file mode 100644 index 000000000..100d86cef --- /dev/null +++ b/plugins/valkey/tpl/valkey_cluster.conf @@ -0,0 +1,94 @@ +daemonize yes +pidfile {$SERVER_PATH}/valkey/valkey.pid + +loglevel notice +logfile {$SERVER_PATH}/valkey/data/valkey.log +databases 16 + +timeout 0 +tcp-keepalive 0 + +bind 127.0.0.1 +port 6379 + +################################ SNAPSHOTTING ################################# + +save 900 100 +save 300 1000 +save 60 1000000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir {$SERVER_PATH}/valkey/data/ + +################################# CLUSTER ################################# + + +protected-mode no +cluster-enabled yes +cluster-config-file nodes_{$REDIS_PASS}.conf +cluster-require-full-coverage no +cluster-node-timeout 15000 + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 218mb +maxmemory-policy volatile-ttl + +############################## APPEND ONLY MODE ############################### + + +#appendonly no +# appendfsync always +#appendfsync everysec +# appendfsync no +#no-appendfsync-on-rewrite no + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing yes + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/valkey/tpl/valkey_simple.conf b/plugins/valkey/tpl/valkey_simple.conf new file mode 100644 index 000000000..ba0e9ae21 --- /dev/null +++ b/plugins/valkey/tpl/valkey_simple.conf @@ -0,0 +1,87 @@ +daemonize yes +pidfile {$SERVER_PATH}/valkey/valkey.pid + +bind 127.0.0.1 +port 6379 +requirepass {$VALKEY_PASS} + +timeout 0 +tcp-keepalive 0 + +loglevel notice + +logfile {$SERVER_PATH}/valkey/data/valkey.log +databases 16 + +################################ SNAPSHOTTING ################################# + +save 900 100 +save 300 1000 +save 60 1000000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir {$SERVER_PATH}/valkey/data/ + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 218mb +maxmemory-policy volatile-ttl + +############################## APPEND ONLY MODE ############################### + + +#appendonly no +# appendfsync always +#appendfsync everysec +# appendfsync no +#no-appendfsync-on-rewrite no + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing yes + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/valkey/tpl/valkey_slave.conf b/plugins/valkey/tpl/valkey_slave.conf new file mode 100644 index 000000000..de9184030 --- /dev/null +++ b/plugins/valkey/tpl/valkey_slave.conf @@ -0,0 +1,91 @@ +daemonize yes +pidfile {$SERVER_PATH}/valkey/valkey.pid + +bind 127.0.0.1 +port 6379 +requirepass {$VALKEY_PASS} + +timeout 0 +tcp-keepalive 0 + +loglevel notice + +logfile {$SERVER_PATH}/valkey/data/valkey.log +databases 16 + +################################ SNAPSHOTTING ################################# + +save 900 100 +save 300 1000 +save 60 1000000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir {$SERVER_PATH}/valkey/data/ + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +# 填写主库信息 +#slaveof 127.0.0.1 6379 +#masterauth 123123 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 0mb +maxmemory-policy volatile-ttl + +############################## APPEND ONLY MODE ############################### + + +#appendonly no +# appendfsync always +#appendfsync everysec +# appendfsync no +#no-appendfsync-on-rewrite no + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing yes + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/valkey/tpl/valkey_slave_mem.conf b/plugins/valkey/tpl/valkey_slave_mem.conf new file mode 100644 index 000000000..baf66f54f --- /dev/null +++ b/plugins/valkey/tpl/valkey_slave_mem.conf @@ -0,0 +1,75 @@ +daemonize yes +pidfile {$SERVER_PATH}/valkey/valkey.pid + +loglevel notice +logfile {$SERVER_PATH}/valkey/data/valkey.log +databases 16 + +timeout 0 +tcp-keepalive 0 + +bind 127.0.0.1 +port 6379 +requirepass {$VALKEY_PASS} + +################################ SNAPSHOTTING ################################# + +save "" +stop-writes-on-bgsave-error no + +################################# REPLICATION ################################# + +slave-serve-stale-data yes +slave-read-only yes + +repl-disable-tcp-nodelay no +slave-priority 100 + +# 填写主库信息 +#slaveof 127.0.0.1 6379 +#masterauth 123123 + +################################## SECURITY ################################### + + +################################### LIMITS #################################### +maxclients 10000 +#maxmemory-samples 3 +maxmemory 218mb +maxmemory-policy allkeys-lru + +############################## APPEND ONLY MODE ############################### + + +################################ LUA SCRIPTING ############################### + +lua-time-limit 5000 + +################################## SLOW LOG ################################### + + +slowlog-log-slower-than 10000 +slowlog-max-len 128 + +############################### ADVANCED CONFIG ############################### + +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +set-max-intset-entries 512 + +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +activerehashing no + +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +hz 10 + +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/plugins/varnish/ico.png b/plugins/varnish/ico.png new file mode 100644 index 000000000..3d8c4eb1b Binary files /dev/null and b/plugins/varnish/ico.png differ diff --git a/plugins/varnish/index.html b/plugins/varnish/index.html new file mode 100755 index 000000000..d250b0686 --- /dev/null +++ b/plugins/varnish/index.html @@ -0,0 +1,21 @@ +
                                  +
                                  +
                                  +

                                  服务

                                  +

                                  自启动

                                  +

                                  VCL

                                  +

                                  服务配置

                                  +

                                  日志

                                  +

                                  状态

                                  +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + \ No newline at end of file diff --git a/plugins/varnish/index.py b/plugins/varnish/index.py new file mode 100755 index 000000000..5336dc01a --- /dev/null +++ b/plugins/varnish/index.py @@ -0,0 +1,212 @@ +# coding:utf-8 + +import sys +import io +import os +import time + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'varnish' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = '/etc/varnish/vcl.conf' + if os.path.exists(path): + return path + path = "/etc/varnish/default.vcl" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$SERVER_APP}', service_path + '/varnish') + return content + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + data = mw.execShell( + "ps -ef|grep varnish |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'") + + if data[0] == '': + return 'stop' + return 'start' + + +def vaOp(method): + mw.execShell("systemctl daemon-reload") + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return 'fail' + + +def start(): + return vaOp('start') + + +def stop(): + return vaOp('stop') + + +def restart(): + return vaOp('restart') + + +def reload(): + return vaOp('reload') + + +def runInfo(): + cmd = "varnishstat -j" + data = mw.execShell(cmd)[0].strip() + return data + + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + + +def runLog(): + + if os.path.exists("/var/log/varnish/varnish.log"): + return "/var/log/varnish/varnish.log" + + return "/var/log/varnish/varnishncsa.log" + + +def confService(): + return mw.systemdCfgDir() + '/varnish.service' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'conf': + print(getConf()) + elif func == 'conf_service': + print(confService()) + elif func == 'run_log': + print(runLog()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + else: + print('error') diff --git a/plugins/varnish/info.json b/plugins/varnish/info.json new file mode 100755 index 000000000..a550cd2a7 --- /dev/null +++ b/plugins/varnish/info.json @@ -0,0 +1,18 @@ +{ + "sort": 7, + "ps": "一款高性能的开源HTTP加速器", + "name": "varnish", + "title": "varnish", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "checks": "server/varnish", + "path": "server/varnish", + "display": 1, + "author": "midoks", + "date": "2022-06-11", + "home": "https://varnish-cache.org", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/varnish/install.sh b/plugins/varnish/install.sh new file mode 100755 index 000000000..2ce3ad69f --- /dev/null +++ b/plugins/varnish/install.sh @@ -0,0 +1,73 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +OSNAME_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + + +Install_varnish() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source + + if [ "${OSNAME}" == "macos" ]; then + brew install varnish + elif [ "${OSNAME}" == "centos" ] || [ "${OSNAME}" == "fedora" ] || [ "${OSNAME}" == "alma" ] || [ "${OSNAME}" == "rocky" ]; then + yum install varnish -y + elif [ "${OSNAME}" == "debian" ] || [ "${OSNAME}" == "ubuntu" ]; then + apt install varnish -y + elif [[ "$OSNAME" == "arch" ]]; then + echo y | pacman -Sy varnish + elif [ "${OSNAME}" == "opensuse" ];then + zypper install -y varnish + else + echo "I won't support it" + exit 1 + fi + + mkdir -p $serverPath/varnish + echo "1.0" > $serverPath/varnish/version.pl + echo '安装完成' + + cd ${rootPath} && python3 ${rootPath}/plugins/varnish/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/varnish/index.py initd_install +} + +Uninstall_varnish() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/varnish/index.py stop + cd ${rootPath} && python3 ${rootPath}/plugins/varnish/index.py initd_uninstall + + if [ "${OSNAME}" == "macos" ]; then + brew uninstall varnish + elif [ "${OSNAME}" == "centos" ] || [ "${OSNAME}" == "fedora" ] || [ "${OSNAME}" == "alma" ] || [ "${OSNAME}" == "rocky" ]; then + yum remove varnish -y + elif [ "${OSNAME}" == "debian" ] || [ "${OSNAME}" == "ubuntu" ]; then + apt remove varnish -y + elif [[ "$OSNAME" == "arch" ]]; then + echo y | pacman -Rv varnish + elif [ "${OSNAME}" == "opensuse" ];then + zypper remove -y varnish + else + echo "I won't support it" + fi + rm -rf $serverPath/varnish + echo "uninstall varnish" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_varnish +else + Uninstall_varnish +fi diff --git a/plugins/varnish/js/varnish.js b/plugins/varnish/js/varnish.js new file mode 100644 index 000000000..16adeba78 --- /dev/null +++ b/plugins/varnish/js/varnish.js @@ -0,0 +1,101 @@ + + +function pRead(){ + var readme = '
                                    '; + readme += '
                                  • 修改后,点击重启按钮
                                  • '; + readme += '
                                  '; + + $('.soft-man-con').html(readme); +} + + +//varnish负载状态 start +function varnishStatus() { + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'varnish', func:'run_info'}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + var rdata = $.parseJSON(data.data); + console.log(rdata); + + var tmp = ""; + for (let i in rdata) { + // console.log(i,rdata[i]); + if (i == 'timestamp'){ + tmp += ""+i+""+rdata[i]+"" + } else{ + tmp += ""+i+""+rdata[i]['value']+""+rdata[i]['description']+"" + } + } + + hit = (parseInt(rdata.keyspace_hits) / (parseInt(rdata.keyspace_hits) + parseInt(rdata.keyspace_misses)) * 100).toFixed(2); + var Con = '
                                  \ + \ + \ + '+tmp+'\ +
                                  字段当前值说明
                                  ' + $(".soft-man-con").html(Con); + },'json'); +} +//varnish负载状态 end + + +//varnish service --- +function varnishPluginConfig(_name, version, func){ + if ( typeof(version) == 'undefined' ){ + version = ''; + } + + var func_name = 'conf'; + if ( typeof(func) != 'undefined' ){ + func_name = func; + } + + var con = '

                                  提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!

                                  \ + \ + \ +
                                    \ +
                                  • 此处为'+ _name + version +'主配置文件,若您不了解配置规则,请勿随意修改。
                                  • \ +
                                  '; + $(".soft-man-con").html(con); + + var loadT = layer.msg('配置文件路径获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/plugins/run', {name:_name, func:func_name,version:version},function (data) { + layer.close(loadT); + + var loadT2 = layer.msg('文件内容获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + var fileName = data.data; + $.post('/files/get_body', 'path=' + fileName, function(rdata) { + layer.close(loadT2); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + $("#textBody").empty().text(rdata.data.data); + $(".CodeMirror").remove(); + var editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName); + } + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").click(function(){ + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName); + }); + },'json'); + },'json'); +} diff --git a/plugins/varnish/tpl/default.vcl b/plugins/varnish/tpl/default.vcl new file mode 100755 index 000000000..b86560303 --- /dev/null +++ b/plugins/varnish/tpl/default.vcl @@ -0,0 +1,17 @@ +# https://www.varnish-cache.org/docs/ +vcl 4.0; + +# Default backend definition. Set this to point to your content server. +backend default { + .host = "127.0.0.1"; + .port = "8080"; +} + +sub vcl_recv { +} + +sub vcl_backend_response { +} + +sub vcl_deliver { +} diff --git a/plugins/varnish/tpl/default_note.vcl b/plugins/varnish/tpl/default_note.vcl new file mode 100755 index 000000000..b86560303 --- /dev/null +++ b/plugins/varnish/tpl/default_note.vcl @@ -0,0 +1,17 @@ +# https://www.varnish-cache.org/docs/ +vcl 4.0; + +# Default backend definition. Set this to point to your content server. +backend default { + .host = "127.0.0.1"; + .port = "8080"; +} + +sub vcl_recv { +} + +sub vcl_backend_response { +} + +sub vcl_deliver { +} diff --git a/plugins/webhook/ico.png b/plugins/webhook/ico.png new file mode 100644 index 000000000..633088a7e Binary files /dev/null and b/plugins/webhook/ico.png differ diff --git a/plugins/webhook/index.html b/plugins/webhook/index.html new file mode 100755 index 000000000..0de594878 --- /dev/null +++ b/plugins/webhook/index.html @@ -0,0 +1,33 @@ + +
                                  + + + + + + + + + + + + + +
                                  名称添加时间近期调用调用次数密钥操作
                                  +
                                  + + \ No newline at end of file diff --git a/plugins/webhook/index.py b/plugins/webhook/index.py new file mode 100755 index 000000000..473f3bbfe --- /dev/null +++ b/plugins/webhook/index.py @@ -0,0 +1,296 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'webhook' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def getCfgFilePath(): + return getServerDir() + "/cfg.json" + + +def initCfg(): + cfg = getCfgFilePath() + data = [] + mw.writeFile(cfg, json.dumps(data)) + + +def getCfg(): + cfg = getCfgFilePath() + if not os.path.exists(cfg): + initCfg() + + data = mw.readFile(cfg) + data = json.loads(data) + return data + + +def addCfg(val): + cfg = getCfgFilePath() + data = getCfg() + data.append(val) + mw.writeFile(cfg, json.dumps(data)) + + +def status(): + return 'start' + + +def addHook(): + args = getArgs() + data = checkArgs(args, ['title', "shell"]) + if not data[0]: + return data[1] + + hook = {} + hook['title'] = args['title'] + if hook['title'] == '': + return mw.returnJson(False, '名称不能为空!') + + hook['access_key'] = mw.md5(hook['title']) + hook['count'] = 0 + hook['addtime'] = int(time.time()) + hook['uptime'] = 0 + + script_dir = getServerDir() + "/scripts" + if not os.path.exists(script_dir): + os.mkdir(script_dir) + + addCfg(hook) + shellFile = script_dir + '/' + hook['access_key'] + mw.writeFile(shellFile, args['shell']) + return mw.returnJson(True, '添加成功!') + +def addHookShell(args): + if args['title'] == '': + return mw.returnJson(False, '名称不能为空!') + + hook = {} + hook['title'] = args['title'] + hook['access_key'] = mw.md5(hook['title']) + hook['count'] = 0 + hook['addtime'] = int(time.time()) + hook['uptime'] = 0 + + script_dir = getServerDir() + "/scripts" + if not os.path.exists(script_dir): + os.mkdir(script_dir) + + addCfg(hook) + shellFile = script_dir + '/' + hook['access_key'] + mw.writeFile(shellFile, args['shell']) + return mw.returnJson(True, '添加成功!') + + +def getList(): + data = getCfg() + + rdata = {} + rdata['list'] = data + rdata['script_dir'] = getServerDir() + "/scripts" + return mw.returnJson(True, 'ok', rdata) + + +def getLog(): + args = getArgs() + check_arg = checkArgs(args, ['path']) + if not check_arg[0]: + return check_arg[1] + + logPath = args['path'] + + content = mw.getLastLine(logPath, 500) + return mw.returnJson(True, 'ok', content) + +def getLogCb(args): + # print(args) + logPath = args['path'] + content = mw.getLastLine(logPath, 3000) + return mw.returnData(True, 'ok', content) + + +def runShellArgs(args): + data = getCfg() + for i in range(len(data)): + if data[i]['access_key'] == args['access_key']: + script_dir = getServerDir() + "/scripts" + shellFile = script_dir + '/' + args['access_key'] + param = args['params'] + if param == '': + param = 'no-parameters' + + param = re.sub("\"", '', param) + + cmd = "bash {} {} >> {}.log 2>&1 &".format( + shellFile, param, shellFile) + # print(cmd) + os.system(cmd) + data[i]['count'] += 1 + data[i]['uptime'] = int(time.time()) + mw.writeFile(getCfgFilePath(), json.dumps(data)) + return mw.returnJson(True, '运行成功!') + return mw.returnJson(False, '指定Hook不存在!') + +def getRunShellCmd(): + args = getArgs() + check_arg = checkArgs(args, ['access_key']) + if not check_arg[0]: + return check_arg[1] + + script_dir = getServerDir() + "/scripts" + shellFile = script_dir + '/' + args['access_key'] + param = '' + if 'params' in args: + param = args['params'] + param = re.sub("\"", '', param) + + cmd = "" + cmd += "git config --global credential.helper store\n" + cmd += "git config --global pull.rebase false\n" + cmd += "bash {} {}".format(shellFile, param) + return mw.returnJson(True, 'ok', cmd) + + +def runShell(): + args = getArgs() + check_arg = checkArgs(args, ['access_key']) + if not check_arg[0]: + return check_arg[1] + + args['params'] = 'panel-test' + return runShellArgs(args) + + +def delHook(): + args = getArgs() + check_arg = checkArgs(args, ['access_key']) + if not check_arg[0]: + return check_arg[1] + + data = getCfg() + newdata = [] + for hook in data: + if hook['access_key'] == args['access_key']: + continue + newdata.append(hook) + + jsonFile = getCfgFilePath() + shellFile = getServerDir() + "/scripts/" + args['access_key'] + if not os.path.exists(shellFile): + return mw.returnJson(False, '删除失败!') + os.remove(shellFile) + log_file = "{}.log".format(shellFile) + if os.path.exists(log_file): + os.remove(log_file) + + mw.writeFile(jsonFile, json.dumps(newdata)) + return mw.returnJson(True, '删除成功!') + + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + return content + + +def configTpl(): + path = getPluginDir() + '/tpl' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + +def readConfigTpl(): + args = getArgs() + data = checkArgs(args, ['file', 'title']) + if not data[0]: + return data[1] + + if args['title'] == '': + return mw.returnJson(False, '名称不能为空!') + + content = mw.readFile(args['file']) + content = contentReplace(content) + + content = content.replace('{$REPO}', args['title']) + content = content.replace('{$REPO_NAME}', mw.md5(args['title'])) + + return mw.returnJson(True, 'ok', content) + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == "add_hook": + print(addHook()) + elif func == "get_list": + print(getList()) + elif func == "run_shell": + print(runShell()) + elif func == 'run_shell_cmd': + print(getRunShellCmd()) + elif func == 'del_hook': + print(delHook()) + elif func == 'get_log': + print(getLog()) + elif func == 'config_tpl': + print(configTpl()) + elif func == 'read_config_tpl': + print(readConfigTpl()) + else: + print('error') diff --git a/plugins/webhook/info.json b/plugins/webhook/info.json new file mode 100755 index 000000000..251feb70e --- /dev/null +++ b/plugins/webhook/info.json @@ -0,0 +1,18 @@ +{ + "sort": 4, + "ps": "WebHook,可设置回调脚本,通常用于第三方回调通知!", + "name": "webhook", + "title": "WebHook", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "checks": "server/webhook", + "path": "server/webhook", + "display": 1, + "author": "", + "date": "2022-11-02", + "home": "", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/webhook/install.sh b/plugins/webhook/install.sh new file mode 100755 index 000000000..7386b71d4 --- /dev/null +++ b/plugins/webhook/install.sh @@ -0,0 +1,33 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=1.0 + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/webhook + echo "${VERSION}" > $serverPath/webhook/version.pl + echo '安装完成' + + which mw && mw restart +} + +Uninstall_App() +{ + rm -rf $serverPath/webhook + echo "Uninstall_App" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/webhook/js/webhook.js b/plugins/webhook/js/webhook.js new file mode 100755 index 000000000..dc3a30192 --- /dev/null +++ b/plugins/webhook/js/webhook.js @@ -0,0 +1,408 @@ + +function whPost(method, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'webhook'; + req_data['func'] = method; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function whPostNoMessage(method, args,callback){ + + var req_data = {}; + req_data['name'] = 'webhook'; + req_data['func'] = method; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) {; + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function whPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'webhook'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function whPostCallbakN(method, version, args,callback){ + var req_data = {}; + req_data['name'] = 'webhook'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function getFileName(file){ + var list = file.split('/'); + var f = list[list.length-1]; + return f; +} + +var whEditor = null; + +//添加 +function addHook(){ + layer.open({ + type: 1, + area: '650px', + title: '添加Hook', + closeBtn: 2, + shift: 5, + shadeClose: false, + btn:['提交','关闭'], + content: "
                                  \ +
                                  \ + 名称\ +
                                  \ +
                                  \ +
                                  \ + 执行脚本\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + 模板选择\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  ", + success:function(){ + + $("#hook_shell").empty().text(''); + $(".CodeMirror").remove(); + whEditor = CodeMirror.fromTextArea(document.getElementById("hook_shell"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() {} + }, + lineNumbers: true, + matchBrackets:true, + mode:"sh", + }); + + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + whEditor.focus(); + + + whPostNoMessage('config_tpl','', function(data){ + var rdata = $.parseJSON(data.data); + for (var i = 0; i < rdata.length; i++) { + $('#hook_tpl').append(''); + } + + $('#hook_tpl').change(function(){ + var selected = $(this).val(); + if (selected == '0') { + return; + } + + var title = $('#hook_title').val(); + var loadT = layer.msg('配置模版获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + whPostNoMessage('read_config_tpl', {file:selected,title:title}, function(data){ + layer.close(loadT); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:16,time:2000,shade: [5, '#000']}); + return; + } + + $("#hook_shell").empty().text(rdata.data); + $(".CodeMirror").remove(); + whEditor = CodeMirror.fromTextArea(document.getElementById("hook_shell"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() {} + }, + lineNumbers: true, + matchBrackets:true, + mode:"sh", + }); + + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + whEditor.focus(); + + }); + }); + }); + + }, + yes:function(indexs){ + var loadT = layer.msg("提交中...",{icon:16,time:0}); + var data = { + title: $("#hook_title").val(), + shell: whEditor.getValue(), + } + // whPost('add_hook', data, function(rdata){ + // var rdata = $.parseJSON(rdata.data); + // if (!rdata.status){ + // layer.msg(rdata.msg,{icon:2}); + // return; + // } + // layer.close(indexs); + // showMsg(rdata.msg, function(){ + // getHookList(); + // }, {icon:1}, 2000); + // }); + + whPostCallbak('addHookShell', '', data, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2}); + return; + } + layer.close(indexs); + showMsg(rdata.msg, function(){ + getHookList(); + }, {icon:1}, 2000); + }); + } + }); +} +//获取列表 +function getHookList(){ + whPost('get_list', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var zbody = ''; + var mlist = rdata.data.list; + var script_dir = rdata.data.script_dir; + for(var i=0;i'+mlist[i].title+'' + +''+getLocalTime(mlist[i].addtime)+'' + +''+getLocalTime(mlist[i].uptime)+'' + +''+mlist[i].count+'' + +'查看密钥' + +'测试 | ' + +'命令 | ' + +'编辑 | ' + +'日志 | ' + +'删除' + +'' + } + $("#zipBody").html(zbody); + }); +} + +//查看密钥 +function showWebHookCode(url,code){ + layer.open({ + type:1, + title:'查看密钥', + area: '600px', + shadeClose:false, + closeBtn:2, + content:'
                                  \ +
                                  密钥
                                  \ +
                                  \ + WebHook使用方法:
                                  \ + GET/POST:
                                  \ + '+window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '')+'/hook?access_key='+code+'&params=aaa
                                  \ + @param access_key string HOOK密钥
                                  \ + @param params string 自定义参数(在hook脚本中使用$1接收)| 多个参数 "1 2" -> $1为1, $2为2
                                  \ +
                                  \ +
                                  ' + }) +} + +function getLogsTimer(path,success,error){ + whPostNoMessage('get_log', {"path":path}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (!rdata.status){ + if (typeof(error) == 'function'){ + error(); + } + return; + } + + $('[name="site_logs"]').text(rdata.data); + if (typeof(success) == 'function'){ + success(); + } + }); +} + +function getLogsTimerCb(path,success,error){ + whPostCallbakN('getLogCb', "", {"path":path}, function(rdata){ + var rdata = rdata.data; + if (!rdata.status){ + if (typeof(error) == 'function'){ + error(); + } + return; + } + + $('[name="site_logs"]').text(rdata.data); + if (typeof(success) == 'function'){ + success(); + } + }); +} + +//查看日志 +function getLogs(path){ + logs_web = layer.open({ + type:1, + title:'任务执行日志', + area: ['600px','400px'], + shadeClose:false, + closeBtn:2, + content:'
                                  \ + \ +
                                  ', + success:function(){ + $('[name="site_logs"]').scrollTop($('[name="site_logs"]')[0].scrollHeight); + + logs_timer = setInterval(function(){ + getLogsTimerCb(path,function(){ + + },function(){ + layer.msg(rdata.msg,{icon:2}); + layer.close(logs_web); + }); + },1000); + }, + cancel:function(){ + clearInterval(logs_timer); + } + }); +} + +//运行 +function runHook(key){ + whPost('run_shell', {"access_key":key}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2}); + } + + showMsg(rdata.msg,function(){ + getHookList(); + },{icon:1},2000); + }); +} + + +function getRunHookCmd(key) { + whPost('run_shell_cmd', {"access_key":key}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2}); + } + layer.open({ + title: "手动执行命令CMD", + area: ['600px', '180px'], + type:1, + closeBtn: 1, + shadeClose: false, + btn:["复制","取消"], + content: '
                                  \ +
                                  \ +
                                  '+rdata.data+'
                                  \ +
                                  \ +
                                  ', + success:function(){ + copyText(rdata.data); + }, + yes:function(){ + copyText(rdata.data); + } + }); + }); +} + +//删除 +function deleteHook(key, title){ + layer.confirm('删除Hook-['+ title +']',{ + title:'是否删除Hook-['+ title +']任务,是否继续' + },function(){ + whPost('del_hook', {"access_key":key}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2}); + } + + showMsg(rdata.msg,function(){ + getHookList(); + },{icon:1},2000); + }); + }); +} + diff --git a/plugins/webhook/tpl/git.tpl b/plugins/webhook/tpl/git.tpl new file mode 100644 index 000000000..26004bb91 --- /dev/null +++ b/plugins/webhook/tpl/git.tpl @@ -0,0 +1,34 @@ +echo "" > {$ROOT_PATH}/server/webhook/scripts/{$REPO_NAME}.log + +export HOME=/tmp +date "+%Y-%m-%d %H:%M:%S" + +# 和手动命令一起执行一次 +git config --global credential.helper store +git config --global pull.rebase false + + +if [ ! -d {$ROOT_PATH}/gitcode ];then + mkdir -p {$ROOT_PATH}/gitcode +fi + +if [ -d {$ROOT_PATH}/gitcode/{$REPO} ];then + which sudo + if [ "$?" == "0" ];then + cd {$ROOT_PATH}/gitcode/{$REPO} && sudo git pull + else + cd {$ROOT_PATH}/gitcode/{$REPO} && git pull + fi +else + cd {$ROOT_PATH}/gitcode && git clone http://0.0.0.0:6660/xx/{$REPO} +fi + +if [ ! -d {$ROOT_PATH}/wwwroot/{$REPO} ];then + mkdir -p {$ROOT_PATH}/wwwroot/{$REPO} +fi + +rsync -vauP '--exclude=.*' {$ROOT_PATH}/gitcode/{$REPO}/ {$ROOT_PATH}/wwwroot/{$REPO} +chown -R www:www {$ROOT_PATH}/wwwroot/{$REPO} + + + diff --git a/plugins/webhook/webhook_index.py b/plugins/webhook/webhook_index.py new file mode 100755 index 000000000..dc60912d3 --- /dev/null +++ b/plugins/webhook/webhook_index.py @@ -0,0 +1,67 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'webhook' + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getCfgFilePath(): + return getServerDir() + "/cfg.json" + + +def getCfg(): + cfg = getCfgFilePath() + if not os.path.exists(cfg): + initCfg() + + data = mw.readFile(cfg) + data = json.loads(data) + return data + + +def runShellArgs(args): + data = getCfg() + for i in range(len(data)): + if data[i]['access_key'] == args['access_key']: + script_dir = getServerDir() + "/scripts" + shellFile = script_dir + '/' + args['access_key'] + param = args['params'] + if param == '': + param = 'no-parameters' + + param = re.sub("\"", '', param) + + cmd = "bash {} {} >> {}.log 2>&1 &".format( + shellFile, param, shellFile) + # print(cmd) + os.system(cmd) + data[i]['count'] += 1 + data[i]['uptime'] = int(time.time()) + mw.writeFile(getCfgFilePath(), json.dumps(data)) + return mw.returnJson(True, '运行成功!') + return mw.returnJson(False, '指定Hook不存在!') diff --git a/plugins/webssh/ico.png b/plugins/webssh/ico.png new file mode 100755 index 000000000..5a6983475 Binary files /dev/null and b/plugins/webssh/ico.png differ diff --git a/plugins/webssh/img/ico-cmd.png b/plugins/webssh/img/ico-cmd.png new file mode 100644 index 000000000..c88796d6b Binary files /dev/null and b/plugins/webssh/img/ico-cmd.png differ diff --git a/plugins/webssh/index.html b/plugins/webssh/index.html new file mode 100755 index 000000000..59bacbcf1 --- /dev/null +++ b/plugins/webssh/index.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/plugins/webssh/index.py b/plugins/webssh/index.py new file mode 100755 index 000000000..3793914bb --- /dev/null +++ b/plugins/webssh/index.py @@ -0,0 +1,232 @@ +# coding: utf-8 + +import time +import random +import os +import json +import re +import sys + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +class App(): + + __cmd_file = 'cmd.json' + __cmd_path = '' + __host_dir = '' + + def __init__(self): + self.__cmd_path = self.getServerDir() + '/' + self.__cmd_file + + if not os.path.exists(self.__cmd_path): + mw.writeFile(self.__cmd_path, '[]') + + self.__host_dir = self.getServerDir() + '/host' + if not os.path.exists(self.__host_dir): + mw.execShell('mkdir -p ' + self.__host_dir) + + def getPluginName(self): + return 'webssh' + + def getPluginDir(self): + return mw.getPluginDir() + '/' + self.getPluginName() + + def getServerDir(self): + return mw.getServerDir() + '/' + self.getPluginName() + + def getArgs(self): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + + def checkArgs(self, data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + def status(self): + return 'start' + + def saveCmd(self, t): + data_tmp = json.loads(mw.readFile(self.__cmd_path)) + is_has = False + for x in range(0, len(data_tmp) - 1): + if data_tmp[x]['title'] == t['title']: + is_has = True + data_tmp[x]['cmd'] = t['cmd'] + if not is_has: + data_tmp.append(t) + mw.writeFile(self.__cmd_path, json.dumps(data_tmp)) + + def add_cmd(self): + args = self.getArgs() + check = self.checkArgs(args, ['title', 'cmd']) + if not check[0]: + return check[1] + + title = args['title'].strip() + cmd = args['cmd'] + + t = { + 'title': title, + 'cmd': cmd + } + self.saveCmd(t) + + return mw.returnJson(True, '添加成功!') + + def del_cmd(self): + args = self.getArgs() + check = self.checkArgs(args, ['title']) + if not check[0]: + return check[1] + + title = args['title'].strip() + data_tmp = json.loads(mw.readFile(self.__cmd_path)) + for x in range(0, len(data_tmp)): + if data_tmp[x]['title'] == title: + del(data_tmp[x]) + mw.writeFile(self.__cmd_path, json.dumps(data_tmp)) + return mw.returnJson(True, '删除成功') + return mw.returnJson(False, '删除无效') + + def get_cmd_list(self): + alist = json.loads(mw.readFile(self.__cmd_path)) + return mw.returnJson(True, 'ok', alist) + + def getSshInfo(self, file): + rdata = mw.readFile(file) + destr = mw.deDoubleCrypt('mdserver-web', rdata) + return json.loads(destr) + + def get_server_by_host_data(self, host): + info_file = self.__host_dir + '/' + host + '/info.json' + info_data = self.getSshInfo(info_file) + return info_data + + def get_server_by_host(self): + args = self.getArgs() + check = self.checkArgs(args, ['host']) + if not check[0]: + return check[1] + + info_file = self.__host_dir + '/' + args['host'] + '/info.json' + if os.path.exists(info_file): + try: + info_tmp = self.getSshInfo(info_file) + host_info = {} + host_info['host'] = args['host'] + host_info['port'] = info_tmp['port'] + host_info['ps'] = info_tmp['ps'] + host_info['type'] = info_tmp['type'] + if 'password' in info_tmp: + host_info['password'] = info_tmp['password'] + if 'pkey' in info_tmp: + host_info['pkey'] = info_tmp['pkey'] + if 'pkey_passwd' in info_tmp: + host_info['pkey_passwd'] = info_tmp['pkey_passwd'] + except Exception as e: + return mw.returnJson(False, '错误:' + str(e)) + + return mw.returnJson(True, 'ok!', host_info) + return mw.returnJson(False, '不存在此配置') + + def get_server_list(self): + host_list = [] + if os.path.exists(self.__host_dir): + for name in os.listdir(self.__host_dir): + info_file = self.__host_dir + '/' + name + '/info.json' + # print(info_file) + if not os.path.exists(info_file): + continue + + + host_info = {} + try: + info_tmp = self.getSshInfo(info_file) + + host_info['host'] = name + host_info['port'] = info_tmp['port'] + host_info['ps'] = info_tmp['ps'] + # host_info['sort'] = int(info_tmp['sort']) + except Exception as e: + # print(e) + return mw.returnJson(False, str(e)) + + # if os.path.exists(info_file): + # os.remove(info_file) + # continue + + host_list.append(host_info) + + host_list = sorted(host_list, key=lambda x: x['host'], reverse=False) + return mw.returnJson(True, 'ok!', host_list) + + def del_server(self): + args = self.getArgs() + check = self.checkArgs(args, ['host']) + if not check[0]: + return check[1] + host = args['host'] + info_file = self.__host_dir + '/' + host + mw.execShell('rm -rf {}'.format(info_file)) + return mw.returnJson(True, '删除成功!') + + def add_server(self): + args = self.getArgs() + check = self.checkArgs( + args, ['host', 'port', 'type', 'username', 'ps']) + if not check[0]: + return check[1] + + host = args['host'] + info = { + 'port': args['port'], + 'username': args['username'], + 'ps': args['ps'], + 'type': args['type'], + } + + if args['type'] == '0': + info['password'] = args['password'] + else: + info['pkey'] = args['pkey'] + info['pkey_passwd'] = args['pkey_passwd'] + + dst_host_dir = self.__host_dir + '/' + host + if not os.path.exists(dst_host_dir): + os.makedirs(dst_host_dir) + + enstr = mw.enDoubleCrypt('mdserver-web', json.dumps(info)) + mw.writeFile(dst_host_dir + '/info.json', enstr) + return mw.returnJson(True, '添加成功!') + +if __name__ == "__main__": + func = sys.argv[1] + classApp = App() + try: + data = eval("classApp." + func + "()") + print(data) + except Exception as e: + print(mw.getTracebackInfo()) diff --git a/plugins/webssh/info.json b/plugins/webssh/info.json new file mode 100755 index 000000000..dea473672 --- /dev/null +++ b/plugins/webssh/info.json @@ -0,0 +1,36 @@ +{ + "hook":[ + { + "tag":"menu", + "menu": { + "title":"终端管理", + "name":"webssh", + "path":"menu/index.html", + "css_path":"menu/index.css", + "js_path":"js/webssh.js" + } + }, + { + "tag":"global_static", + "global_static": { + "title":"终端管理", + "name":"webssh", + "css_path":"menu/ico.css" + } + } + ], + "sort":3, + "title":"SSH终端", + "tip":"lib", + "name":"webssh", + "type":"扩展", + "ps":"完整功能的SSH客户端,仅用于连接本服务器", + "versions":"2.0", + "shell":"install.sh", + "checks":"server/webssh", + "path": "server/webssh", + "author":"ssh", + "home":"", + "date":"2018-12-20", + "pid":"4" +} \ No newline at end of file diff --git a/plugins/webssh/install.sh b/plugins/webssh/install.sh new file mode 100755 index 000000000..31782e34c --- /dev/null +++ b/plugins/webssh/install.sh @@ -0,0 +1,33 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +Install_webssh() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/webssh + echo "${VERSION}" > $serverPath/webssh/version.pl + echo '安装完成' + +} + +Uninstall_webssh() +{ + rm -rf $serverPath/webssh + echo "卸载完成" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_webssh +else + Uninstall_webssh +fi diff --git a/plugins/webssh/js/webssh.js b/plugins/webssh/js/webssh.js new file mode 100755 index 000000000..0079d8f11 --- /dev/null +++ b/plugins/webssh/js/webssh.js @@ -0,0 +1,601 @@ + +//全局 +var host_ssh_list = []; + +//刷新页面 +$(window).unload(function(){ + for (i in host_ssh_list) { + host_ssh_list[i].close(); + } + return false; +}); + +function appPost(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'webssh', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function appAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'webssh', func:method, args:_args}); +} + +function appPostCallbak(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'webssh'; + req_data['func'] = method; + args['version'] = '1.0'; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +$(document).ready(function(){ + var tag = $.getUrlParam('tag'); + if(tag == 'webssh'){ + webShell_Load(); + } +}); + +function webShell_Resize(){ + var cur_ssh = $('.term_item_tab .list .active'); + if (cur_ssh.length > 0){ + var data = $(cur_ssh).data(); + var item = host_ssh_list[data.id]; + item.term.fit(); + item.resize({ cols: item.term.cols, rows: item.term.rows}); + item.term.focus(); + } +} + +function webShell_Load(){ + changeDivH(); + $(window).resize(function(){ + changeDivH(); + webShell_Resize(); + }); + + $('.term_content_tab .term-tool-button').click(function(){ + if ($(this).hasClass('tool-show')){ + $('#term_box_view').css('margin-right',300); + $('.term_tootls').css('display',"block"); + $(this).removeClass('tool-show').addClass('tool-hide'); + $(this).find('span').removeClass('glyphicon-menu-left').addClass('glyphicon-menu-right'); + } else { + $('#term_box_view').css('margin-right',0); + $('.term_tootls').css('display',"none"); + $(this).removeClass('tool-hide').addClass('tool-show'); + $(this).find('span').removeClass('glyphicon-menu-right').addClass('glyphicon-menu-left'); + } + webShell_Resize(); + }); + + + + $('.full_exit_screen').click(function(ele){ + if($(this).hasClass('glyphicon-resize-full')){ + requestFullScreen($('#term_box_view')[0]); + $(this).removeClass('glyphicon-resize-full').addClass('glyphicon-resize-small'); + } else{ + exitFull(); + $(this).removeClass('glyphicon-resize-small').addClass('glyphicon-resize-full'); + } + }); + + $('.addServer').click(function(){ + webShell_addServer(); + }); + + $('.tootls_host_btn').click(function(){ + webShell_addServer(); + }); + + $('.tootls_commonly_btn').click(function(){ + webShell_cmd(); + }); + + // 切换服务器终端视图 + $('.term_item_tab .list').on('click', 'span.item', function (ev) { + var index = $(this).index(); + var data = $(this).data(); + if ($(this).hasClass('addServer')) { + } else if ($(this).hasClass('tab_tootls')) { + } else { + $(this).addClass('active').siblings().removeClass('active'); + $('.term_content_tab .term_item:eq(' + index + ')').addClass('active').siblings().removeClass('active'); + webShell_Resize(); + } + }); + + $('.term_item_tab').on('click', '.icon-trem-close', function () { + var id = $(this).parent().data('id'); + webShell_removeTermView(id); + }) + + //服务器列表和常用命令 + webShell_getHostList(); + + //服务器列表和命令切换 + $('.term_tootls .tab-nav span').click(function(){ + var list_type = $(this).attr('data-type'); + if (!$(this).hasClass('on')){ + + $('.term_tootls .tab-nav span').removeClass('on'); + $(this).addClass('on'); + + $('.term_tootls .tab-con .tab-block').removeClass('on') + if (list_type == 'host'){ + $('.term_tootls .tab-con .tab-block').eq(0).addClass('on'); + webShell_getHostList(); + } + + if (list_type == 'shell'){ + $('.term_tootls .tab-con .tab-block').eq(1).addClass('on'); + webShell_getCmdList(); + } + } + }); + + webShell_Menu(); +} + + +function changeDivH(){ + var l = $(window).height(); + $('#term_box_view').parent().css('height',l-80); + $('#xterm-viewport').css('height',l-80); + + $('.tootls_host_list').css('display','block').css('height',l-192); + $('.tootls_commonly_list').css('display','block').css('height',l-192); +} + + +// 窗口状态改变 +function fullScreenChange(el, callback) { + el.addEventListener("fullscreenchange", function () { + callback && callback(document.fullscreen); + }); + + el.addEventListener("webkitfullscreenchange", function () { + callback && callback(document.webkitIsFullScreen); + }); + + el.addEventListener("mozfullscreenchange", function () { + callback && callback(document.mozFullScreen); + }); + + el.addEventListener("msfullscreenchange", function () { + callback && callback(document.msFullscreenElement); + }); +} + +// 全屏 +function requestFullScreen(element) { + var requestMethod = element.requestFullScreen || //W3C + element.webkitRequestFullScreen || //Chrome等 + element.mozRequestFullScreen || //FireFox + element.msRequestFullScreen; //IE11 + if (requestMethod) { + requestMethod.call(element); + }else if (typeof window.ActiveXObject !== "undefined") {//for Internet Explorer + var wscript = new ActiveXObject("WScript.Shell"); + if (wscript !== null) { + wscript.SendKeys("{F11}"); + } + } +} + +//退出全屏 +function exitFull() { + var exitMethod = document.exitFullscreen || //W3C + document.mozCancelFullScreen || //Chrome等 + document.webkitExitFullscreen || //FireFox + document.webkitExitFullscreen; //IE11 + if (exitMethod) { + exitMethod.call(document); + }else if (typeof window.ActiveXObject !== "undefined") {//for Internet Explorer + var wscript = new ActiveXObject("WScript.Shell"); + if (wscript !== null) { + wscript.SendKeys("{F11}"); + } + } +} + + +function webShell_getCmdList(){ + appPost('get_cmd_list', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var alist = rdata.data; + + var tli = ''; + for (var i = 0; i < alist.length; i++) { + tli+='
                                • \ + \ + '+alist[i]['title']+'\ + \ + \ + \ + \ +
                                • '; + } + + $('.tootls_commonly_list').html(tli); + + $('.data-cmd-list .glyphicon-edit').click(function(){ + var index = $(this).parent().parent().attr('data-index'); + var t = alist[index]; + webShell_cmd(t['title'],t['cmd']); + }); + + $('.data-cmd-list .glyphicon-trash').click(function(){ + var index = $(this).parent().parent().attr('data-index'); + var t = alist[index]; + appPost('del_cmd', {title:t['title']}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + webShell_getCmdList(); + },{ icon: rdata.status ? 1 : 2 }); + }); + }); + + $('.data-cmd-list .span_title').click(function(e){ + copyText($(this).parent().attr('data-clipboard-text')); + e.preventDefault(); + }); + }); +} + +var n; +function Terms_WebSocketIO_Create(ip, random){ + n = new Terms_WebSocketIO('#'+random, { ssh_info: { host: ip, ps: "22", id: random } }); + n.registerCloseCallBack(function(){ + webShell_removeTermView(random); + layer.msg('已经关闭【'+ip+'】', { icon: 1, time: 3000 }); + }); + + n.registerConnectedCallBack(function(){ + var that = this; + $('.term_item_tab .item').each(function(){ + var id = $(this).data('id'); + if (id == that.id){ + $(this).find('.icon').removeClass('icon-warning').addClass('icon-sucess'); + } + }); + webShell_Resize(); + }); + + n.registerExitCallBack(function(){ + var that = this; + $('.term_item_tab .item').each(function(){ + var id = $(this).data('id'); + if (id == that.id){ + $(this).find('.icon').removeClass('icon-sucess').addClass('icon-warning'); + } + }); + }); + return n; +} + + +function webShell_Menu(){ + var random = 'localhost'; + // host_ssh_list[random] = new Terms_WebSocketIO('#'+random, { ssh_info: { host: "38.6.224.67", ps: "22", id: random } }); + host_ssh_list[random] = Terms_WebSocketIO_Create('127.0.0.1',random); +} + +function webShell_openTermView(info) { + if (typeof info === "undefined") { + info = { host: '127.0.0.1', ps: '本地服务器' } + } + var random = getRandomString(9); + var tab_content = $('.term_content_tab'); + var item_list = $('.term_item_tab .list'); + tab_content.find('.term_item').removeClass('active').siblings().removeClass('active'); + tab_content.append('
                                  '); + item_list.find('.item').removeClass('active'); + if (info.ps == ''){ + info.ps = info.host; + } + item_list.append('
                                  ' + info.ps + '
                                  '); + host_ssh_list[random] = Terms_WebSocketIO_Create(info.host, random); +} + + +function webShell_removeTermView(id){ + var item = $('[data-id="' + id + '"]'); + var next = item.next(); + var prev = item.prev(); + $('#' + id).remove(); + item.remove(); + try { + host_ssh_list[id].close(); + } catch (error) { + console.log(error); + } + + delete host_ssh_list[id]; + if (item.hasClass('active')) { + if (next.length > 0) { + next.click(); + } else { + prev.click(); + } + } +} + +function webShell_getHostList(info){ + appPost('get_server_list', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var alist = rdata.data; + + var tli = ''; + for (var i = 0; i < alist.length; i++) { + tli+='
                                • \ + \ + '+alist[i]['host']+'\ + \ + \ + \ + \ +
                                • '; + } + + $('.tootls_host_list').html(tli); + + $('.data-host-list .glyphicon-edit').click(function(e){ + var index = $(this).parent().parent().attr('data-index'); + var info = alist[index]; + webShell_addServer(info); + }); + + $('.data-host-list .glyphicon-trash').click(function(e){ + var index = $(this).parent().parent().attr('data-index'); + var t = alist[index]; + appPost('del_server', {host:t['host']}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + webShell_getHostList(); + },{ icon: rdata.status ? 1 : 2 }); + }); + }); + + + $('.tootls_host_list .host').on('click', function (e) { + var _this = $(this).parent(); + var index = $(_this).index(); + var host = $(_this).data('host'); + $(_this).find('i').addClass('active'); + if ($('.item[data-host="' + host + '"]').length > 0) { + layer.msg('已经打开!', { icon: 0, time: 3000 }); + } else { + webShell_openTermView(alist[index]); + } + e.preventDefault(); + }); + }); +} + +function webShell_addServer(info=[]){ + layer.open({ + type: 1, + title: '添加主机信息', + area: '500px', + btn:["确定","取消"], + content:'
                                  \ +
                                  \ + 服务器IP\ +
                                  \ + \ + \ +
                                  \ +
                                  \ +
                                  \ + SSH账号\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + 验证方式\ +
                                  \ +
                                  \ + \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + 密码\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + 私钥\ +
                                  \ + \ +
                                  \ +
                                  \ + \ +
                                  \ + 备注\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  ', + success:function(){ + if (typeof(info['host'])!='undefined'){ + $('input[name="host"]').val(info['host']); + appPost('get_server_by_host',{host:info['host']},function(rdata){ + var rdata = $.parseJSON(rdata.data); + var jdata = rdata.data; + if (jdata['type'] == 0){ + $('input[name="password"]').val(jdata['password']); + } else{ + $('textarea[name="pkey"]').val(jdata['pkey']); + // $('input[name="pkey_passwd"]').val(jdata['pkey_passwd']); + } + $('input[name="ps"]').val(jdata['ps']); + $('input[name="port"]').val(jdata['port']); + $('.auth_type_checkbox').each(function(){ + if ($(this).data('ctype') == jdata['type']){ + $(this).addClass('btn-success'); + } else { + $(this).removeClass('btn-success'); + } + }); + if (jdata['type'] == 0){ + $('.c_password_view').removeClass('show').addClass('show'); + $('.c_pkey_view').removeClass('show').addClass('hide'); + // $('.key_pwd_line').removeClass('show').addClass('hide'); + + }else{ + $('.c_password_view').removeClass('show').addClass('hide'); + $('.c_pkey_view').removeClass('show').addClass('show'); + // $('.key_pwd_line').removeClass('show').addClass('show'); + } + }); + } + + $('.auth_type_checkbox').click(function(){ + var ctype = $(this).attr('data-ctype'); + $('.auth_type_checkbox').removeClass('btn-success'); + $(this).addClass('btn-success'); + + if (ctype == 0){ + $('.c_password_view').removeClass('show').addClass('show'); + $('.c_pkey_view').removeClass('show').addClass('hide'); + // $('.key_pwd_line').removeClass('show').addClass('hide'); + + }else{ + $('.c_password_view').removeClass('show').addClass('hide'); + $('.c_pkey_view').removeClass('show').addClass('show'); + // $('.key_pwd_line').removeClass('show').addClass('show'); + } + }); + }, + yes:function(l,layeror){ + var host = $('input[name="host"]').val(); + var port = $('input[name="port"]').val(); + var username = $('input[name="username"]').val(); + var password = ''; + var pkey = ''; + var pkey_passwd = ''; + var type = $('.btn-group .btn-success').attr('data-ctype'); + if (type == "0"){ + password = $('input[name="password"]').val(); + } else{ + pkey = $('textarea[name="pkey"]').val(); + pkey_passwd = $('input[name="pkey_passwd"]').val(); + } + + var ps = $('input[name="ps"]').val(); + + var req_data = { + type:type, + host:host, + port:port, + username:username, + password:password, + pkey:pkey, + pkey_passwd:pkey_passwd, + ps:ps, + }; + + console.log(req_data); + + appPost('add_server',req_data,function(rdata){ + layer.close(l); + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + webShell_getHostList(); + },{ icon: rdata.status ? 1 : 2 }); + }); + }, + }); +} + +function webShell_cmd(title='', cmd=''){ + layer.open({ + type: 1, + title: '添加常用命令信息', + area: '500px', + btn:["确定","取消"], + content:'
                                  \ +
                                  \ + 命令名称\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + 命令内容\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  ', + success:function(){ + }, + yes:function(l,layer_id){ + var title = $('input[name="title"]').val(); + var cmd = $('textarea[name="cmd"]').val(); + + appPost('add_cmd', {title:title,cmd:cmd}, function(rdata){ + layer.close(l); + var rdata = $.parseJSON(rdata.data); + showMsg(rdata.msg, function(){ + webShell_getCmdList(); + },{ icon: rdata.status ? 1 : 2 }); + }); + return false; + } + }); +} + diff --git a/plugins/webssh/menu/ico.css b/plugins/webssh/menu/ico.css new file mode 100644 index 000000000..287bb6d2b --- /dev/null +++ b/plugins/webssh/menu/ico.css @@ -0,0 +1,10 @@ + +/* menu start */ +.menu .menu_plugin_webssh { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAMBJREFUeNpiYKAQMM6cOVMASBuQozk9Pf0AC5AuAOJ6cgwAWt7IhMQvRKIfAPEEYgxhQWL3o9EFpBgwAeifQhKdD7YI5oWPUMEEmAQR4COyATBwAIgdgIacB2IFYkxhQouWB0BsCA3E86QGIsxvDSBXgGIDSxr5ALTgAk4XADWAQt4fiB2BChcAaQVoGoHheLwuAGqagBz/UNsciQ4DEgE/zAUfQM4DOt+eRANAYTMRZADIrxfIdMUFSnMzA0CAAQD0hjqnYxWD2gAAAABJRU5ErkJggg=="); +} + +.menu .current .menu_plugin_webssh:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAMQXAJ6ipUFITySRPIiMkDFRQuXm5jJKQy1kQCGeOzBXQSl3PiWLPVhfZSxrQCpxP9na2yeEPTREQ8LExmRqb5OXm/Hx8TU9RAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjMyQjYwMTU3OTA2QTExRUJCN0NFQUUwQjRDMzM4RDJDIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjMyQjYwMTU4OTA2QTExRUJCN0NFQUUwQjRDMzM4RDJDIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MzJCNjAxNTU5MDZBMTFFQkI3Q0VBRTBCNEMzMzhEMkMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MzJCNjAxNTY5MDZBMTFFQkI3Q0VBRTBCNEMzMzhEMkMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAAAh+QQFMgAXACwAAAAAEAAQAAAFU6AljmRpBVOqrqtFVXAsy9Q7VMMDzHHN/5UawGQC1CiWg4IoOiINAoGB6BxBENSjyIE4oFKMprYhIFgYkvTQpWWOqm6RcVBI2+/2AuXL6k8CcSYhACH5BAUyABcALAcACQAFAAIAAAUIYJSMBLGcSggAIfkEBTIAFwAsBwAJAAUAAgAABQhgMI0MI51ACAAh+QQFMgAXACwHAAkABQACAAAFCGCUjASxnEoIADs="); +} +/* menu end */ \ No newline at end of file diff --git a/plugins/webssh/menu/index.css b/plugins/webssh/menu/index.css new file mode 100644 index 000000000..68b348085 --- /dev/null +++ b/plugins/webssh/menu/index.css @@ -0,0 +1,582 @@ + +.xterm{ + position: inherit; +} + +.tab-nav { + border-bottom: #cacad9 1px solid; +} + +.tab-nav span { + background-color: #f0f0f1; + height: 32px; + line-height: 32px; + padding: 0 15px; + border: #cacad9 1px solid; + color: #444; + display: inline-block; + margin: 0 -1px -1px 0; + cursor: pointer; + position: relative; +} + +.tab-nav .on { + background: #fff; + border-bottom: #fff 1px solid; + color: #444 +} + +.tab-con { + padding: 10px; + position: relative; +} + +.tab-con .tab-block { + display: none; + height: 100%; + width: 100%; +} + +.tab-con .tab-block.on { + display: inline-block; +} + +.tab-con ul.cmdlist { + list-style-type: decimal +} +ul.cmdlist { + list-style-type: decimal; + height: 505px; + overflow-y: auto; +} + +.tab-con ul.cmdlist li { + position: relative; + list-style-type: decimal; + list-style-position: inside; + line-height: 40px; + border-bottom: #dbdbea 1px solid; + margin-top: 6px +} + +.tab-con ul.cmdlist li .com-progress, +.tab-con ul.cmdlist li .state, +.opencmd { + float: right; + margin-left: 20px; + color: #535362 +} + +.tab-con ul.cmdlist li .line-progress { + position: absolute; + bottom: -1px; + left: 0; + height: 1px; + background-color: #20a53a +} + +.tab-con ul.cmdlist li .cmd { + border: 0 none; + border-radius: 0; + display: block; + width: 570px; + height: 200px; + line-height: 22px; + padding: 0 10px; + background-color: #333; + color: #eee; + overflow: auto +} + +.term_item_tab .list { + display: inline-block; + webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + /* display: flex; */ + height: 38px; + overflow: hidden; + overflow-x: auto; +} + +.term_item_tab .list>span { + height: 38px; + line-height: 38px; + text-align: left; + padding: 0 25px 0 22px; + display: inline-block; + vertical-align: middle; + border-right: 1px solid #e2e2e2; + cursor: pointer; + width: 150px; + position: relative; + flex-shrink: 0; +} + +.icon-trem-close { + height: 18px; + width: 18px; + position: absolute; + top: 50%; + margin-top: -8.5px; + right: 8px; + display: none !important; +} + +.icon-trem-close::after, +.icon-trem-close::before { + content: ''; + height: 14px; + width: 2px; + display: inline-block; + background: #fb0000; + z-index: 999; + position: absolute; + position: absolute; + top: 1.5px; + left: 8px; + transform: rotate(-45deg); +} + +.icon-trem-close::after { + transform: rotate(45deg); +} + +.icon-trem-close::before { + transform: rotate(-45deg); +} + +/*term_item_tab*/ +.term_item_tab .list>span.active .icon-trem-close, +.term_item_tab .list>span:hover .icon-trem-close { + display: inline-block !important; +} + +.term_item_tab { + background: #f1f1f1; + font-size: 0; +} + +.term_item_tab .list>span i { + transition: all 50ms; +} + +.term_item_tab .list>span .icon { + height: 9px; + width: 9px; + border-radius: 5px; + display: inline-block; + background: #fff; + position: absolute; + left: 8px; + top: 50%; + margin-top: -4.5px; +} + +.term_item_tab .list>span .icon.icon-sucess { + background-color: #10952a; +} + +.term_item_tab .list>span .icon.icon-info { + background-color: #fc6d26; +} + +.term_item_tab .list>span .icon.icon-warning { + background-color: #ff5d2c; +} + +.term_item_tab .list>span:hover { + background-color: #dadada; +} + +.term_item_tab .list>span.active { + background-color: #424242; +} + +.term_item_tab .list span.active span { + color: #fff; +} + +.term_item_tab .list .content { + position: relative; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.term_item_tab .item .glyphicon { + vertical-align: top; + position: absolute; + right: 12px; + font-size: 15px; + height: 38px; + line-height: 38px; + color: #ff7070 !important; + display: none; + transition: all 500ms; +} + +.term_item_tab .item .glyphicon:hover { + color: red; +} + +.term_item_tab .list span span { + vertical-align: middle; + font-size: 13px; + font-weight: 500; + color: #666; + display: inline-block; + line-height: 37px; +} + +.term_item_tab .glyphicon { + font-size: 15px; + margin-left: 8px; + vertical-align: middle; + transition: all 500ms; + cursor: pointer; + display: inline-block; + color: #ea7575; +} + +.term_item_tab span.glyphicon { + color: #888; +} + +.term_item_tab .glyphicon:hover { + color: red; +} + +.term_item_tab .tab_tootls { + padding: 0; + float: right; + /* padding-right: 15px; */ + /* display: inline-block; */ + /* display: none; */ +} + + +/* .term_item_tab .tab_tootls span{ +margin-left: 0; +font-size: 12px; +display: block; +height: 19px; +color: #bbb; +/* vertical-align: bottom; +} */ + +.term_item_tab .tab_tootls .glyphicon-resize-full, +.term_item_tab .tab_tootls .glyphicon-resize-small { + height: 38px; + line-height: 38px; + width: 40px; + text-align: center; + margin: 0 +} + +.term_item_tab .addServer { + display: inline-block; + width: 40px; + text-align: center; + padding: 0; + height: 38px; + line-height: 38px; + vertical-align: top; +} + +.term_item_tab .addServer span { + margin: 0; + color: #20a53a; +} + +.term_item_tab>span:hover { + background-color: #ececec; + cursor: pointer; +} + +.term_item_tab .addServer span:hover { + color: #4c4c4c; +} + + + +.term_content_tab { + background-color: rgb(51, 51, 51); + position: absolute; + top: 38px; + left: 0; + right: 0; + bottom: 0; + padding: 10px 10px 10px 10px; + overflow: hidden; +} + +.term_content_tab .term_item { + height: 100%; + width: 100%; + display: none; +} + +.term_content_tab .term_item.active { + display: inline-block; +} + +.term-tool-button { + position: absolute; + right: -7px; + top: 50%; + width: 28px; + height: 60px; + background-color: #565656; + border-top-left-radius: 30px; + border-bottom-left-radius: 30px; + cursor: pointer; + z-index: 999; + margin-top: -30px; +} + +.term-tool-button span { + font-size: 12px; + position: relative; + left: 15%; + top: 35%; + font-size: 18px; + color: #FFFFFF; + cursor: pointer; +} + +.term-tool-button:hover { + background-color: #D8D8D8; +} + +.term_box { + height: 100%; + border-radius: 3px; + margin-right: 300px; + position: relative; +} + +.term_tootls { + width: 290px; + position: absolute; + right: 15px; + top: 15px; + /* border: 1px solid #ececec; */ +} + +.term_tootls .tootls_tab { + display: inline-block; + /* border: 1px solid #20a53a; */ + width: 100%; + position: relative; +} + +.term_tootls .tootls_tab a { + display: inline-block; + width: 38px; + text-align: center; + height: 38px; + line-height: 38px; + position: absolute; + right: 0; + color: #ececec; + background-color: #c7c7c71a; +} + +.term_tootls .tab-con { +/* position: absolute;*/ + top: 10px; + left: 0; + right: 0; + bottom: 0; + padding: 0; + overflow: hidden; +} + +.term_tootls .tootls_tab a:hover { + background-color: #8282824d; +} + +.term_tootls .tootls_tab i { + font-style: normal; + font-size: 12px; + color: #ececec; + margin-left: 5px; +} + +.main-content .safe { + position: relative; +} + +.term_content_tab .xterm .xterm-viewport{ + /* On OS X this is required in order for the scroll bar to appear fully opaque */ + background-color: #000; + overflow-y: auto; + cursor: default; + position: absolute; + right: 0; + left: 0; + top: 0; + bottom: 0; +} + +.term_content_tab .xterm .composition-view.active { + display: block; +} + +.term_content_tab .xterm .xterm-viewport::-webkit-scrollbar { + width: 8px; + height: 5px; + border-radius: 4px; +} + +.term_content_tab .xterm .xterm-viewport::-webkit-scrollbar-thumb { + border-radius: 0; + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); + background: #666; + border-radius: 4px; + transition: all 1s; +} + +.term_content_tab .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb { + background: #aaa; +} + +.term_content_tab .xterm .xterm-viewport::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); + border-radius: 0; + background: #222; + border-radius: 4px; + transition: all 1s; +} + +.term_content_tab .xterm .xterm-viewport:hover::-webkit-scrollbar-track { + background-color: #444; +} + +.tootls_host_list, +.tootls_commonly_list { + list-style: none; + border-top: none; + border: 1px solid #ececec; + overflow-y: auto; +} + +.tootls_host_list, +.tootls_commonly_list { + overflow: auto; + overflow-x: hidden; + font-size: 12px; + bottom: 0; + right: 0; + left: 0; + margin-top: 10px; +} + +.tootls_commonly_list li, +.tootls_host_list li { + display: inline-block; + height: 38px; + line-height: 38px; + color: #444; + border-bottom: 1px solid #ececec; + font-size: 13px; + cursor: pointer; + position: relative; + width: 100%; +} + +.tootls_commonly_list li { + padding-left: 15px; +} + +.tootls_commonly_list li:hover, +.tootls_host_list li:hover { + background-color: #eee; +} + +.tootls_commonly_list li:hover .tootls, +.tootls_host_list li:hover .tootls { + display: inline-block; +} + +.tootls_host_list li i { + display: inline-block; + height: 100%; + width: 38px; + background-size: 16px; + background: url('/plugins/file?name=webssh&f=img/ico-cmd.png') no-repeat center center; +} + +.tootls_commonly_list li>span, +.tootls_host_list li>span { + vertical-align: top; + display: inline-block; +} + +.tootls_commonly_list li:hover>span:nth-child(2), +.tootls_host_list li:hover>span:nth-child(2) { + width: 120px; +} + +.tootls_commonly_list li>span:nth-child(2), +.tootls_host_list li>span:nth-child(2) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 12px; + max-width: 190px; +} + + +/* .tootls_host_list li>span:nth-child(2){ +margin-left: 35px; +} */ + +.tootls_commonly_list li>span:nth-child(2) { + max-width: 190px; +} + +.tootls_commonly_list li span:nth-child(3), +.tootls_host_list li span:nth-child(3) { + padding-left: 10px; +} + +.tootls_commonly_list li .tootls, +.tootls_host_list li .tootls { + width: 70px; + text-align: right; + padding-right: 10px; + display: none; + position: absolute; + right: 5px; +} + +.tootls_commonly_list li .tootls { + width: 90px; +} + +.tootls_commonly_list li .tootls span, +.tootls_host_list li .tootls span { + width: 22px; + height: 22px; + line-height: 22px; + text-align: center; +} + +.tootls_commonly_list li .tootls span:nth-child(1), +.tootls_host_list li .tootls span:nth-child(1) { + color: #888; + font-size: 14px; +} + +.tootls_commonly_list li .tootls span:nth-child(2), +.tootls_host_list li .tootls span:nth-child(2) { + color: #ea7575; +} + +.tootls_commonly_list li .tootls span:nth-child(2):hover, +.tootls_host_list li .tootls span:nth-child(2):hover { + color: red; +} diff --git a/plugins/webssh/menu/index.html b/plugins/webssh/menu/index.html new file mode 100755 index 000000000..2ef622f5e --- /dev/null +++ b/plugins/webssh/menu/index.html @@ -0,0 +1,56 @@ + + +
                                  +
                                  +
                                  + +
                                  +
                                  +
                                  + + +
                                  + 本地服务器 +
                                  + +
                                  + + + + + + + +
                                  + + +
                                  +
                                  +
                                  +
                                  +
                                  +
                                  + +
                                  + 服务器列表 + 常用命令 +
                                  +
                                  +
                                  +
                                  + +
                                  +
                                    +
                                    +
                                    +
                                    + +
                                    +
                                      +
                                      +
                                      + +
                                      +
                                      +
                                      +
                                      \ No newline at end of file diff --git a/plugins/webstats/class/LuaMaker.py b/plugins/webstats/class/LuaMaker.py new file mode 100644 index 000000000..f9b0a8ade --- /dev/null +++ b/plugins/webstats/class/LuaMaker.py @@ -0,0 +1,56 @@ +import sys +import os + + +class LuaMaker: + """ + lua 处理器 + """ + @staticmethod + def makeLuaTable(table): + """ + table 转换为 lua table 字符串 + """ + _tableMask = {} + _keyMask = {} + + def analysisTable(_table, _indent, _parent): + if isinstance(_table, tuple): + _table = list(_table) + if isinstance(_table, list): + _table = dict(zip(range(1, len(_table) + 1), _table)) + if isinstance(_table, dict): + _tableMask[id(_table)] = _parent + cell = [] + thisIndent = _indent + " " + for k in _table: + if sys.version_info[0] == 2: + if type(k) not in [int, float, bool, list, dict, tuple]: + k = k.encode() + + if not (isinstance(k, str) or isinstance(k, int) or isinstance(k, float)): + return + key = isinstance( + k, int) and "[" + str(k) + "]" or "[\"" + str(k) + "\"]" + if _parent + key in _keyMask.keys(): + return + _keyMask[_parent + key] = True + var = None + v = _table[k] + if sys.version_info[0] == 2: + if type(v) not in [int, float, bool, list, dict, tuple]: + v = v.encode() + if isinstance(v, str): + var = "\"" + v + "\"" + elif isinstance(v, bool): + var = v and "true" or "false" + elif isinstance(v, int) or isinstance(v, float): + var = str(v) + else: + var = analysisTable(v, thisIndent, _parent + key) + cell.append(thisIndent + key + " = " + str(var)) + lineJoin = ",\n" + return "{\n" + lineJoin.join(cell) + "\n" + _indent + "}" + else: + pass + return analysisTable(table, "", "root") diff --git a/plugins/webstats/conf/config.json b/plugins/webstats/conf/config.json new file mode 100644 index 000000000..080561990 --- /dev/null +++ b/plugins/webstats/conf/config.json @@ -0,0 +1,54 @@ +{ + "global": { + "monitor": true, + "save_day": 1, + "autorefresh": false, + "refresh_interval": 3, + "cdn": true, + "cdn_headers": [ + "x-forwarded-for", + "x-real-ip", + "x-forwarded", + "forwarded-for", + "forwarded", + "true-client-ip", + "client-ip", + "ali-cdn-real-ip", + "cdn-src-ip", + "cdn-real-ip", + "cf-connecting-ip", + "x-cluster-client-ip", + "wl-proxy-client-ip", + "proxy-client-ip", + "true-client-ip" + ], + "exclude_extension": [ + "png", + "gif", + "jpg", + "css", + "svg", + "js", + "ico", + "woff2" + ], + "exclude_status": [ + 301, + 302, + 303 + ], + "exclude_ip":[ + "192.168.1.1" + ], + "exclude_url":[], + "ip_top_num": 10, + "uri_top_num": 10, + "statistics_machine_access": true, + "statistics_ip": true, + "statistics_uri": true, + "record_post_args": false, + "record_get_403_args": false, + "push_report": false, + "data_dir": "/www/server/webstats/logs" + } +} \ No newline at end of file diff --git a/plugins/webstats/conf/init.sql b/plugins/webstats/conf/init.sql new file mode 100644 index 000000000..86051d8f3 --- /dev/null +++ b/plugins/webstats/conf/init.sql @@ -0,0 +1,271 @@ +PRAGMA synchronous = 0; +PRAGMA page_size = 4096; +PRAGMA journal_mode = wal; +PRAGMA journal_size_limit = 1073741824; + + +CREATE TABLE IF NOT EXISTS `web_logs` ( + `time` INTEGER, + `ip` TEXT, + `domain` TEXT, + `server_name` TEXT, + `method` TEXT, + `status_code` INTEGER, + `uri` TEXT, + `body_length` INTEGER, + `referer` TEXT DEFAULT "", + `user_agent` TEXT, + `is_spider` INTEGER DEFAULT 0, + `protocol` TEXT, + `request_time` INTEGER, + `request_headers` TEXT DEFAULT "", + `ip_list` TEXT DEFAULT "", + `client_port` INTEGER DEFAULT -1 +); + +CREATE INDEX time_idx ON web_logs(`time`); +CREATE INDEX uri_idx ON web_logs (`uri`); +CREATE INDEX ip_idx ON web_logs (`ip`); +CREATE INDEX referer_idx ON web_logs (`referer`); +CREATE INDEX method_idx ON web_logs (`method`); +CREATE INDEX status_code_idx ON web_logs (`status_code`); +CREATE INDEX request_time_idx ON web_logs (`request_time`); +CREATE INDEX is_spider_idx ON web_logs (`is_spider`); +CREATE INDEX body_length_idx ON web_logs (`body_length`); +CREATE INDEX all_union_idx ON web_logs(`status_code`, `time`, `ip`, `domain`, `server_name`, `method`, `is_spider`, `protocol`, `request_headers`, `ip_list`, `client_port`, `body_length`, `user_agent`, `referer`, `request_time`, `uri`); + + +CREATE TABLE IF NOT EXISTS `client_stat`( + `time` INTEGER PRIMARY KEY, + `weixin` INTEGER DEFAULT 0, + `android` INTEGER DEFAULT 0, + `iphone` INTEGER DEFAULT 0, + `mac` INTEGER DEFAULT 0, + `windows` INTEGER DEFAULT 0, + `linux` INTEGER DEFAULT 0, + `edeg` INTEGER DEFAULT 0, + `firefox` INTEGER DEFAULT 0, + `msie` INTEGER DEFAULT 0, + `metasr` INTEGER DEFAULT 0, + `qh360` INTEGER DEFAULT 0, + `theworld` INTEGER DEFAULT 0, + `tt` INTEGER DEFAULT 0, + `maxthon` INTEGER DEFAULT 0, + `opera` INTEGER DEFAULT 0, + `qq` INTEGER DEFAULT 0, + `uc` INTEGER DEFAULT 0, + `pc2345` INTEGER DEFAULT 0, + `safari` INTEGER DEFAULT 0, + `chrome` INTEGER DEFAULT 0, + `machine` INTEGER DEFAULT 0, + `mobile` INTEGER DEFAULT 0, + `other` INTEGER DEFAULT 0 +); + +CREATE TABLE `request_stat`( + `time` INTEGER PRIMARY KEY, + `req` INTEGER DEFAULT 0, + `pv` INTEGER DEFAULT 0, + `uv` INTEGER DEFAULT 0, + `ip` INTEGER DEFAULT 0, + `length` INTEGER DEFAULT 0, + `spider` INTEGER DEFAULT 0, + `status_500` INTEGER DEFAULT 0, + `status_501` INTEGER DEFAULT 0, + `status_502` INTEGER DEFAULT 0, + `status_503` INTEGER DEFAULT 0, + `status_504` INTEGER DEFAULT 0, + `status_505` INTEGER DEFAULT 0, + `status_506` INTEGER DEFAULT 0, + `status_507` INTEGER DEFAULT 0, + `status_509` INTEGER DEFAULT 0, + `status_510` INTEGER DEFAULT 0, + `status_400` INTEGER DEFAULT 0, + `status_401` INTEGER DEFAULT 0, + `status_402` INTEGER DEFAULT 0, + `status_403` INTEGER DEFAULT 0, + `status_404` INTEGER DEFAULT 0, + `status_499` INTEGER DEFAULT 0, + `http_get` INTEGER DEFAULT 0, + `http_post` INTEGER DEFAULT 0, + `http_put` INTEGER DEFAULT 0, + `http_patch` INTEGER DEFAULT 0, + `http_delete` INTEGER DEFAULT 0 +); + +CREATE TABLE `spider_stat`( + `time` INTEGER PRIMARY KEY, + `bytes` INTEGER DEFAULT 0, + `bing` INTEGER DEFAULT 0, + `soso` INTEGER DEFAULT 0, + `yahoo` INTEGER DEFAULT 0, + `sogou` INTEGER DEFAULT 0, + `google` INTEGER DEFAULT 0, + `baidu` INTEGER DEFAULT 0, + `qh360` INTEGER DEFAULT 0, + `youdao` INTEGER DEFAULT 0, + `yandex` INTEGER DEFAULT 0, + `dnspod` INTEGER DEFAULT 0, + `yisou` INTEGER DEFAULT 0, + `mpcrawler` INTEGER DEFAULT 0, + `duckduckgo` INTEGER DEFAULT 0, + `bytes_flow` INTEGER DEFAULT 0, + `bing_flow` INTEGER DEFAULT 0, + `soso_flow` INTEGER DEFAULT 0, + `yahoo_flow` INTEGER DEFAULT 0, + `sogou_flow` INTEGER DEFAULT 0, + `google_flow` INTEGER DEFAULT 0, + `baidu_flow` INTEGER DEFAULT 0, + `qh360_flow` INTEGER DEFAULT 0, + `youdao_flow` INTEGER DEFAULT 0, + `yandex_flow` INTEGER DEFAULT 0, + `dnspod_flow` INTEGER DEFAULT 0, + `yisou_flow` INTEGER DEFAULT 0, + `other_flow` INTEGER DEFAULT 0, + `mpcrawler_flow` INTEGER DEFAULT 0, + `duckduckgo_flow` INTEGER DEFAULT 0, + `other` INTEGER DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS `uri_stat` ( + `uri_md5` CHAR(32) PRIMARY KEY, + `uri` TEXT, + `day1` INTEGER DEFAULT 0, + `day2` INTEGER DEFAULT 0, + `day3` INTEGER DEFAULT 0, + `day4` INTEGER DEFAULT 0, + `day5` INTEGER DEFAULT 0, + `day6` INTEGER DEFAULT 0, + `day7` INTEGER DEFAULT 0, + `day8` INTEGER DEFAULT 0, + `day9` INTEGER DEFAULT 0, + `day10` INTEGER DEFAULT 0, + `day11` INTEGER DEFAULT 0, + `day12` INTEGER DEFAULT 0, + `day13` INTEGER DEFAULT 0, + `day14` INTEGER DEFAULT 0, + `day15` INTEGER DEFAULT 0, + `day16` INTEGER DEFAULT 0, + `day17` INTEGER DEFAULT 0, + `day18` INTEGER DEFAULT 0, + `day19` INTEGER DEFAULT 0, + `day20` INTEGER DEFAULT 0, + `day21` INTEGER DEFAULT 0, + `day22` INTEGER DEFAULT 0, + `day23` INTEGER DEFAULT 0, + `day24` INTEGER DEFAULT 0, + `day25` INTEGER DEFAULT 0, + `day26` INTEGER DEFAULT 0, + `day27` INTEGER DEFAULT 0, + `day28` INTEGER DEFAULT 0, + `day29` INTEGER DEFAULT 0, + `day30` INTEGER DEFAULT 0, + `day31` INTEGER DEFAULT 0 +); + +ALTER TABLE `uri_stat` ADD COLUMN `flow1` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow2` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow3` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow4` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow5` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow6` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow7` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow8` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow9` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow10` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow11` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow12` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow13` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow14` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow15` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow16` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow17` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow18` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow19` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow20` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow21` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow22` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow23` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow24` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow25` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow26` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow27` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow28` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow29` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow30` INTEGER DEFAULT 0; +ALTER TABLE `uri_stat` ADD COLUMN `flow31` INTEGER DEFAULT 0; + +CREATE TABLE IF NOT EXISTS `ip_stat` ( + `ip` CHAR(15) PRIMARY KEY, + `area` CHAR(8) DEFAULT "", + `day1` INTEGER DEFAULT 0, + `day2` INTEGER DEFAULT 0, + `day3` INTEGER DEFAULT 0, + `day4` INTEGER DEFAULT 0, + `day5` INTEGER DEFAULT 0, + `day6` INTEGER DEFAULT 0, + `day7` INTEGER DEFAULT 0, + `day8` INTEGER DEFAULT 0, + `day9` INTEGER DEFAULT 0, + `day10` INTEGER DEFAULT 0, + `day11` INTEGER DEFAULT 0, + `day12` INTEGER DEFAULT 0, + `day13` INTEGER DEFAULT 0, + `day14` INTEGER DEFAULT 0, + `day15` INTEGER DEFAULT 0, + `day16` INTEGER DEFAULT 0, + `day17` INTEGER DEFAULT 0, + `day18` INTEGER DEFAULT 0, + `day19` INTEGER DEFAULT 0, + `day20` INTEGER DEFAULT 0, + `day21` INTEGER DEFAULT 0, + `day22` INTEGER DEFAULT 0, + `day23` INTEGER DEFAULT 0, + `day24` INTEGER DEFAULT 0, + `day25` INTEGER DEFAULT 0, + `day26` INTEGER DEFAULT 0, + `day27` INTEGER DEFAULT 0, + `day28` INTEGER DEFAULT 0, + `day29` INTEGER DEFAULT 0, + `day30` INTEGER DEFAULT 0, + `day31` INTEGER DEFAULT 0 +); + +ALTER TABLE `ip_stat` ADD COLUMN `flow1` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow2` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow3` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow4` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow5` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow6` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow7` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow8` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow9` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow10` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow11` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow12` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow13` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow14` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow15` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow16` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow17` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow18` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow19` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow20` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow21` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow22` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow23` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow24` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow25` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow26` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow27` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow28` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow29` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow30` INTEGER DEFAULT 0; +ALTER TABLE `ip_stat` ADD COLUMN `flow31` INTEGER DEFAULT 0; + + +CREATE TABLE `referer_stat`( + `time` INTEGER, + `domain` TEXT, + `count` INTEGER DEFAULT 0 +); diff --git a/plugins/webstats/conf/webstats.conf b/plugins/webstats/conf/webstats.conf new file mode 100644 index 000000000..5b3277ea2 --- /dev/null +++ b/plugins/webstats/conf/webstats.conf @@ -0,0 +1,3 @@ +lua_shared_dict mw_total 100m; +lua_need_request_body off; +include {$SERVER_APP}/lua/webstats_log.lua; \ No newline at end of file diff --git a/plugins/webstats/ico.png b/plugins/webstats/ico.png new file mode 100644 index 000000000..33f6c09f0 Binary files /dev/null and b/plugins/webstats/ico.png differ diff --git a/plugins/webstats/index.html b/plugins/webstats/index.html new file mode 100755 index 000000000..028f37282 --- /dev/null +++ b/plugins/webstats/index.html @@ -0,0 +1,221 @@ + + + +
                                      +
                                      +
                                      +

                                      服务

                                      +

                                      概览

                                      +

                                      网站列表

                                      +

                                      蜘蛛统计

                                      +

                                      客户端统计

                                      +

                                      IP统计

                                      +

                                      URI统计

                                      +

                                      错误日志

                                      +

                                      网站日志

                                      +

                                      全局设置

                                      +
                                      +
                                      +
                                      +
                                      +
                                      +
                                      + \ No newline at end of file diff --git a/plugins/webstats/index.py b/plugins/webstats/index.py new file mode 100755 index 000000000..4b56d657d --- /dev/null +++ b/plugins/webstats/index.py @@ -0,0 +1,1452 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'webstats' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +sys.path.append(getPluginDir() + "/class") +from LuaMaker import LuaMaker + + +def listToLuaFile(path, lists): + content = LuaMaker.makeLuaTable(lists) + content = "return " + content + mw.writeFile(path, content) + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConf(): + conf = getServerDir() + "/lua/config.json" + return conf + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def luaConf(): + return mw.getServerDir() + '/web_conf/nginx/vhost/webstats.conf' + + +def status(): + path = luaConf() + if not os.path.exists(path): + return 'stop' + return 'start' + + +def loadLuaFile(name): + lua_dir = getServerDir() + "/lua" + lua_dst = lua_dir + "/" + name + + if not os.path.exists(lua_dst): + lua_tpl = getPluginDir() + '/lua/' + name + content = mw.readFile(lua_tpl) + content = content.replace('{$SERVER_APP}', getServerDir()) + content = content.replace('{$ROOT_PATH}', mw.getServerDir()) + mw.writeFile(lua_dst, content) + + +def loadLuaFileReload(name): + lua_dir = getServerDir() + "/lua" + lua_dst = lua_dir + "/" + name + + lua_tpl = getPluginDir() + '/lua/' + name + content = mw.readFile(lua_tpl) + content = content.replace('{$SERVER_APP}', getServerDir()) + content = content.replace('{$ROOT_PATH}', mw.getServerDir()) + mw.writeFile(lua_dst, content) + + +def loadConfigFile(): + lua_dir = getServerDir() + "/lua" + conf_tpl = getPluginDir() + "/conf/config.json" + + content = mw.readFile(conf_tpl) + content = json.loads(content) + + dst_conf_json = getServerDir() + "/lua/config.json" + if not os.path.exists(dst_conf_json): + mw.writeFile(dst_conf_json, json.dumps(content)) + + dst_conf_lua = getServerDir() + "/lua/webstats_config.lua" + if not os.path.exists(dst_conf_lua): + listToLuaFile(dst_conf_lua, content) + + +# def loadConfigFileReload(): +# -- 配置生活或可使用 +# lua_dir = getServerDir() + "/lua" +# conf_tpl = getPluginDir() + "/conf/config.json" + +# content = mw.readFile(conf_tpl) +# content = json.loads(content) + +# dst_conf_json = getServerDir() + "/lua/config.json" +# mw.writeFile(dst_conf_json, json.dumps(content)) + +# dst_conf_lua = getServerDir() + "/lua/webstats_config.lua" +# listToLuaFile(dst_conf_lua, content) + + +def loadLuaSiteFile(): + lua_dir = getServerDir() + "/lua" + + content = makeSiteConfig() + for index in range(len(content)): + pSqliteDb('web_log', content[index]['name']) + + lua_site_json = lua_dir + "/sites.json" + mw.writeFile(lua_site_json, json.dumps(content)) + + # 设置默认列表 + default_json = lua_dir + "/default.json" + ddata = {} + dlist = [] + for i in content: + dlist.append(i["name"]) + + dlist.append('unset') + ddata["list"] = dlist + if len(ddata["list"]) < 1: + ddata["default"] = "unset" + else: + ddata["default"] = dlist[0] + + mw.writeFile(default_json, json.dumps(ddata)) + + lua_site = lua_dir + "/webstats_sites.lua" + + tmp = { + "name": "unset", + "domains": [], + } + content.append(tmp) + listToLuaFile(lua_site, content) + + +def loadDebugLogFile(): + debug_log = getServerDir() + "/debug.log" + lua_dir = getServerDir() + "/lua" + mw.writeFile(debug_log, '') + + +def pSqliteDb(dbname='web_logs', site_name='unset', name="logs"): + + db_dir = getServerDir() + '/logs/' + site_name + if not os.path.exists(db_dir): + mw.execShell('mkdir -p ' + db_dir) + + file = db_dir + '/' + name + '.db' + if not os.path.exists(file): + conn = mw.M(dbname).dbPos(db_dir, name) + sql = mw.readFile(getPluginDir() + '/conf/init.sql') + sql_list = sql.split(';') + for index in range(len(sql_list)): + conn.execute(sql_list[index]) + else: + conn = mw.M(dbname).dbPos(db_dir, name) + + conn.execute("PRAGMA synchronous = 0") + conn.execute("PRAGMA cache_size = 8000") + conn.execute("PRAGMA page_size = 32768") + conn.execute("PRAGMA journal_mode = wal") + conn.execute("PRAGMA journal_size_limit = 1073741824") + return conn + + +def makeSiteConfig(): + siteM = mw.M('sites') + domainM = mw.M('domain') + slist = siteM.field('id,name').where( + 'status=?', (1,)).order('id desc').select() + + data = [] + for s in slist: + tmp = {} + tmp['name'] = s['name'] + + dlist = domainM.field('id,name').where( + 'pid=?', (s['id'],)).order('id desc').select() + + _t = [] + for d in dlist: + _t.append(d['name']) + + tmp['domains'] = _t + data.append(tmp) + + return data + + +def initDreplace(): + + service_path = getServerDir() + + pSqliteDb() + + path = luaConf() + path_tpl = getPluginDir() + '/conf/webstats.conf' + if not os.path.exists(path): + content = mw.readFile(path_tpl) + content = content.replace('{$SERVER_APP}', service_path) + content = content.replace('{$ROOT_PATH}', mw.getServerDir()) + mw.writeFile(path, content) + + # 已经安装的 + al_config = getServerDir() + "/lua/config.json" + if os.path.exists(al_config): + tmp = json.loads(mw.readFile(al_config)) + if tmp['global']['record_post_args'] or tmp['global']['record_get_403_args']: + openLuaNeedRequestBody() + else: + closeLuaNeedRequestBody() + + lua_dir = getServerDir() + "/lua" + if not os.path.exists(lua_dir): + mw.execShell('mkdir -p ' + lua_dir) + + log_path = getServerDir() + "/logs" + if not os.path.exists(log_path): + mw.execShell('mkdir -p ' + log_path) + + file_list = [ + 'webstats_common.lua', + 'webstats_log.lua', + ] + + for fl in file_list: + loadLuaFile(fl) + + loadConfigFile() + loadLuaSiteFile() + loadDebugLogFile() + + if not mw.isAppleSystem(): + mw.execShell("chown -R www:www " + getServerDir()) + return 'ok' + + +def luaRestart(): + mw.opWeb("stop") + mw.opWeb("start") + + +def start(): + initDreplace() + + import tool_task + tool_task.createBgTask() + + # issues:326 + luaRestart() + return 'ok' + + +def stop(): + path = luaConf() + if os.path.exists(path): + os.remove(path) + + import tool_task + tool_task.removeBgTask() + + luaRestart() + return 'ok' + + +def restart(): + initDreplace() + loadDebugLogFile() + luaRestart() + return 'ok' + + +def reload(): + initDreplace() + + file_list = [ + 'webstats_common.lua', + 'webstats_log.lua', + ] + for fl in file_list: + loadLuaFileReload(fl) + + loadDebugLogFile() + + luaRestart() + return 'ok' + + +def getGlobalConf(): + conf = getConf() + content = mw.readFile(conf) + content = json.loads(content) + return mw.returnJson(True, 'ok', content) + + +def openLuaNeedRequestBody(): + conf = luaConf() + content = mw.readFile(conf) + content = re.sub(r"lua_need_request_body (.*);", + 'lua_need_request_body on;', content) + mw.writeFile(conf, content) + + +def closeLuaNeedRequestBody(): + conf = luaConf() + content = mw.readFile(conf) + content = re.sub(r"lua_need_request_body (.*);", + 'lua_need_request_body off;', content) + mw.writeFile(conf, content) + + +def setGlobalConf(): + args = getArgs() + + conf = getConf() + content = mw.readFile(conf) + content = json.loads(content) + + open_force_get_request_body = False + for v in ['record_post_args', 'record_get_403_args']: + data = checkArgs(args, [v]) + if data[0]: + rval = False + if args[v] == "true": + rval = True + open_force_get_request_body = True + + content['global'][v] = rval + + # 开启强制获取日志配置 + if open_force_get_request_body: + openLuaNeedRequestBody() + else: + closeLuaNeedRequestBody() + + for v in ['ip_top_num', 'uri_top_num', 'save_day']: + data = checkArgs(args, [v]) + if data[0]: + content['global'][v] = int(args[v]) + + for v in ['cdn_headers', 'exclude_extension', 'exclude_status', 'exclude_ip']: + data = checkArgs(args, [v]) + if data[0]: + content['global'][v] = args[v].split("\\n") + + data = checkArgs(args, ['exclude_url']) + if data[0]: + exclude_url = args['exclude_url'].strip(";") + exclude_url_val = [] + if exclude_url != "": + exclude_url_list = exclude_url.split(";") + for i in exclude_url_list: + t = i.split("|") + val = {} + val['mode'] = t[0] + val['url'] = t[1] + exclude_url_val.append(val) + content['global']['exclude_url'] = exclude_url_val + + mw.writeFile(conf, json.dumps(content)) + conf_lua = getServerDir() + "/lua/webstats_config.lua" + listToLuaFile(conf_lua, content) + luaRestart() + return mw.returnJson(True, '设置成功') + + +def getSiteConf(): + args = getArgs() + + check = checkArgs(args, ['site']) + if not check[0]: + return check[1] + + domain = args['site'] + conf = getConf() + content = mw.readFile(conf) + content = json.loads(content) + + site_conf = {} + if domain in content: + site_conf = content[domain] + else: + site_conf["cdn_headers"] = content['global']['cdn_headers'] + site_conf["exclude_extension"] = content['global']['exclude_extension'] + site_conf["exclude_status"] = content['global']['exclude_status'] + site_conf["exclude_ip"] = content['global']['exclude_ip'] + site_conf["exclude_url"] = content['global']['exclude_url'] + site_conf["record_post_args"] = content['global']['record_post_args'] + site_conf["record_get_403_args"] = content[ + 'global']['record_get_403_args'] + + return mw.returnJson(True, 'ok', site_conf) + + +def setSiteConf(): + args = getArgs() + check = checkArgs(args, ['site']) + if not check[0]: + return check[1] + + domain = args['site'] + conf = getConf() + content = mw.readFile(conf) + content = json.loads(content) + + site_conf = {} + if domain in content: + site_conf = content[domain] + else: + site_conf["cdn_headers"] = content['global']['cdn_headers'] + site_conf["exclude_extension"] = content['global']['exclude_extension'] + site_conf["exclude_status"] = content['global']['exclude_status'] + site_conf["exclude_ip"] = content['global']['exclude_ip'] + site_conf["exclude_url"] = content['global']['exclude_url'] + site_conf["record_post_args"] = content['global']['record_post_args'] + site_conf["record_get_403_args"] = content[ + 'global']['record_get_403_args'] + + for v in ['record_post_args', 'record_get_403_args']: + data = checkArgs(args, [v]) + if data[0]: + rval = False + if args[v] == "true": + rval = True + site_conf[v] = rval + + for v in ['ip_top_num', 'uri_top_num', 'save_day']: + data = checkArgs(args, [v]) + if data[0]: + site_conf[v] = int(args[v]) + + for v in ['cdn_headers', 'exclude_extension', 'exclude_status', 'exclude_ip']: + data = checkArgs(args, [v]) + if data[0]: + site_conf[v] = args[v].strip().split("\\n") + + data = checkArgs(args, ['exclude_url']) + if data[0]: + exclude_url = args['exclude_url'].strip(";") + exclude_url_val = [] + if exclude_url != "": + exclude_url_list = exclude_url.split(";") + for i in exclude_url_list: + t = i.split("|") + val = {} + val['mode'] = t[0] + val['url'] = t[1] + exclude_url_val.append(val) + site_conf['exclude_url'] = exclude_url_val + + content[domain] = site_conf + + mw.writeFile(conf, json.dumps(content)) + conf_lua = getServerDir() + "/lua/webstats_config.lua" + listToLuaFile(conf_lua, content) + luaRestart() + return mw.returnJson(True, '设置成功') + + +def getSiteListData(): + lua_dir = getServerDir() + "/lua" + path = lua_dir + "/default.json" + data = mw.readFile(path) + return json.loads(data) + + +def getDefaultSite(): + data = getSiteListData() + return mw.returnJson(True, 'OK', data) + + +def setDefaultSite(name): + lua_dir = getServerDir() + "/lua" + path = lua_dir + "/default.json" + data = mw.readFile(path) + data = json.loads(data) + data['default'] = name + mw.writeFile(path, json.dumps(data)) + return mw.returnJson(True, 'OK') + + +def toSumField(sql): + l = sql.split(",") + field = "" + for x in l: + field += "sum(" + x + ") as " + x + "," + field = field.strip(',') + return field + + +def getSiteStatInfo(domain, query_date): + conn = pSqliteDb('request_stat', domain) + conn = conn.where("1=1", ()) + + field = 'time,req,pv,uv,ip,length' + field_sum = toSumField(field.replace("time,", "")) + + time_field = "substr(time,1,6)," + + field_sum = time_field + field_sum + conn = conn.field(field_sum) + if query_date == "today": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 0 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + elif query_date == "yesterday": + startTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 1 * 86400)) + endTime = time.strftime( + '%Y%m%d00', time.localtime(time.time())) + conn.andWhere("time>=? and time<=?", (startTime, endTime)) + elif query_date == "l7": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 7 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + elif query_date == "l30": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 30 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + else: + exlist = query_date.split("-") + start = time.strftime( + '%Y%m%d00', time.localtime(int(exlist[0]))) + end = time.strftime( + '%Y%m%d23', time.localtime(int(exlist[1]))) + conn.andWhere("time >= ? and time <= ? ", (start, end,)) + + # 统计总数 + stat_list = conn.inquiry(field) + del(stat_list[0]['time']) + return stat_list[0] + + +def getOverviewList(): + args = getArgs() + check = checkArgs(args, ['site', 'query_date', 'order']) + if not check[0]: + return check[1] + + domain = args['site'] + query_date = args['query_date'] + order = args['order'] + + setDefaultSite(domain) + conn = pSqliteDb('request_stat', domain) + conn = conn.where("1=1", ()) + + field = 'time,req,pv,uv,ip,length' + field_sum = toSumField(field.replace("time,", "")) + + time_field = "substr(time,1,8)," + if order == "hour": + time_field = "substr(time,9,10)," + + field_sum = time_field + field_sum + conn = conn.field(field_sum) + if query_date == "today": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 0 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + elif query_date == "yesterday": + startTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 1 * 86400)) + endTime = time.strftime( + '%Y%m%d00', time.localtime(time.time())) + conn.andWhere("time>=? and time<=?", (startTime, endTime)) + elif query_date == "l7": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 7 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + elif query_date == "l30": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 30 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + else: + exlist = query_date.split("-") + start = time.strftime( + '%Y%m%d00', time.localtime(int(exlist[0]))) + end = time.strftime( + '%Y%m%d23', time.localtime(int(exlist[1]))) + conn.andWhere("time >= ? and time <= ? ", (start, end,)) + + # 统计总数 + stat_list = conn.inquiry(field) + del(stat_list[0]['time']) + + # 分组统计 + dlist = conn.group(time_field.strip(",")).inquiry(field) + + data = {} + data['data'] = dlist + data['stat_list'] = stat_list[0] + + return mw.returnJson(True, 'ok', data) + + +def getSiteList(): + args = getArgs() + check = checkArgs(args, ['query_date']) + if not check[0]: + return check[1] + + query_date = args['query_date'] + + data = getSiteListData() + data_list = data["list"] + + rdata = [] + for x in data_list: + tmp = getSiteStatInfo(x, query_date) + tmp["site"] = x + rdata.append(tmp) + return mw.returnJson(True, 'ok', rdata) + + +def getLogsRealtimeInfo(): + ''' + 实时信息 + ''' + import datetime + args = getArgs() + check = checkArgs(args, ['site', 'type','second']) + if not check[0]: + return check[1] + + domain = args['site'] + dtype = args['type'] + second = int(args['second']) + + + conn = pSqliteDb('web_logs', domain) + timeInt = time.mktime(datetime.datetime.now().timetuple()) + + conn = conn.where("time>=?", (int(timeInt) - second,)) + + field = 'time,body_length' + field_sum = toSumField(field.replace("time,", "")) + time_field = "substr(time,1,2) as time," + time_field = time_field + field_sum + clist = conn.field(time_field.strip(",")).group( + 'substr(time,1,2)').inquiry(field) + + body_count = 0 + if len(clist) > 0: + body_count = clist[0]['body_length'] + + req_count = conn.count() + + data = {} + data['realtime_traffic'] = body_count + data['realtime_request'] = req_count + + return mw.returnJson(True, 'ok', data) + + +def attacHistoryLogHack(conn, site_name, query_date='today'): + if query_date == "today": + return + db_dir = getServerDir() + '/logs/' + site_name + file = db_dir + '/history_logs.db' + if os.path.exists(file): + attach = "ATTACH DATABASE '" + file + "' as 'history_logs'" + # print(attach) + r = conn.originExecute(attach) + sql_table = "(select * from web_logs union all select * from history_logs.web_logs)" + # print(sql_table) + conn.table(sql_table) + + +def getLogsList(): + args = getArgs() + check = checkArgs(args, ['page', 'page_size','site', 'method', + 'status_code', 'spider_type', 'request_time', 'query_date', 'search_uri']) + if not check[0]: + return check[1] + + page = int(args['page']) + page_size = int(args['page_size']) + domain = args['site'] + tojs = args['tojs'] + method = args['method'] + status_code = args['status_code'] + request_time = args['request_time'] + request_size = args['request_size'] + spider_type = args['spider_type'] + query_date = args['query_date'] + search_uri = args['search_uri'] + referer = args['referer'] + ip = args['ip'] + setDefaultSite(domain) + + limit = str(page_size) + ' offset ' + str(page_size * (page - 1)) + conn = pSqliteDb('web_logs', domain) + + field = 'time,ip,domain,server_name,method,is_spider,protocol,status_code,request_headers,ip_list,client_port,body_length,user_agent,referer,request_time,uri,body_length' + condition = '' + conn = conn.field(field) + conn = conn.where("1=1", ()) + + if referer != 'all': + if referer == '1': + conn = conn.andWhere("referer <> ? ", ('',)) + elif referer == '-1': + conn = conn.andWhere("referer is null ", ()) + + if ip != '': + conn = conn.andWhere("ip=?", (ip,)) + + if method != "all": + conn = conn.andWhere("method=?", (method,)) + + if request_time != "all": + request_time_s = request_time.strip().split('-') + # print(request_time_s) + if len(request_time_s) == 2: + conn = conn.andWhere("request_time>=? and request_time=?", (request_time,)) + + if request_size != "all": + request_size_s = request_size.strip().split('-') + # print(int(request_size_s[0])*1024) + if len(request_size_s) == 2: + conn = conn.andWhere("body_length>=? and body_length=?", (int(request_size_s[0])*1024,)) + + if spider_type == "normal": + pass + elif spider_type == "only_spider": + conn = conn.andWhere("is_spider>?", (0,)) + elif spider_type == "no_spider": + conn = conn.andWhere("is_spider=?", (0,)) + elif int(spider_type) > 0: + conn = conn.andWhere("is_spider=?", (spider_type,)) + + todayTime = time.strftime('%Y-%m-%d 00:00:00', time.localtime()) + todayUt = int(time.mktime(time.strptime(todayTime, "%Y-%m-%d %H:%M:%S"))) + if query_date == 'today': + conn = conn.andWhere("time>=?", (todayUt,)) + elif query_date == "yesterday": + conn = conn.andWhere("time>=? and time<=?", (todayUt - 86400, todayUt)) + elif query_date == "l7": + conn = conn.andWhere("time>=?", (todayUt - 7 * 86400,)) + elif query_date == "l30": + conn = conn.andWhere("time>=?", (todayUt - 30 * 86400,)) + else: + exlist = query_date.split("-") + conn = conn.andWhere("time>=? and time<=?", (exlist[0], exlist[1])) + + if search_uri != "": + conn = conn.andWhere("uri like '%" + search_uri + "%'", ()) + + if status_code != "all": + conn = conn.andWhere("status_code=?", (status_code,)) + + attacHistoryLogHack(conn, domain, query_date) + + conn.changeTextFactoryToBytes() + clist = conn.limit(limit).order('time desc').inquiry() + + for x in range(len(clist)): + req_line = clist[x] + for cx in req_line: + v = req_line[cx] + if type(v) == bytes: + try: + clist[x][cx] = v.decode('utf-8') + except Exception as e: + v = str(v) + v = v.replace("b'",'').strip("'") + clist[x][cx] = v + else: + clist[x][cx] = v + + # print(clist) + count_key = "count(*) as num" + count = conn.field(count_key).limit('').order('').inquiry() + # print(count) + count = count[0][count_key] + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = tojs + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.returnJson(True, 'ok', data) + + +def getLogsErrorList(): + args = getArgs() + check = checkArgs(args, ['page', 'page_size', + 'site', 'status_code', 'query_date']) + if not check[0]: + return check[1] + + page = int(args['page']) + page_size = int(args['page_size']) + domain = args['site'] + tojs = args['tojs'] + status_code = args['status_code'] + query_date = args['query_date'] + setDefaultSite(domain) + + limit = str(page_size) + ' offset ' + str(page_size * (page - 1)) + conn = pSqliteDb('web_logs', domain) + + field = 'time,ip,domain,server_name,method,protocol,status_code,ip_list,client_port,body_length,user_agent,referer,request_time,uri,body_length' + conn = conn.field(field) + conn = conn.where("1=1", ()) + + if status_code != "all": + if status_code.find("x") > -1: + status_code = status_code.replace("x", "%") + conn = conn.andWhere("status_code like ?", (status_code,)) + else: + conn = conn.andWhere("status_code=?", (status_code,)) + else: + conn = conn.andWhere( + "(status_code like '50%' or status_code like '40%')", ()) + + todayTime = time.strftime('%Y-%m-%d 00:00:00', time.localtime()) + todayUt = int(time.mktime(time.strptime(todayTime, "%Y-%m-%d %H:%M:%S"))) + if query_date == 'today': + conn = conn.andWhere("time>=?", (todayUt,)) + elif query_date == "yesterday": + conn = conn.andWhere("time>=? and time<=?", (todayUt - 86400, todayUt)) + elif query_date == "l7": + conn = conn.andWhere("time>=?", (todayUt - 7 * 86400,)) + elif query_date == "l30": + conn = conn.andWhere("time>=?", (todayUt - 30 * 86400,)) + else: + exlist = query_date.split("-") + conn = conn.andWhere("time>=? and time<=?", (exlist[0], exlist[1])) + + attacHistoryLogHack(conn, domain, query_date) + + clist = conn.limit(limit).order('time desc').inquiry() + count_key = "count(*) as num" + count = conn.field(count_key).limit('').order('').inquiry() + count = count[0][count_key] + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = tojs + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.returnJson(True, 'ok', data) + + +def getClientStatList(): + args = getArgs() + check = checkArgs(args, ['page', 'page_size', + 'site', 'query_date']) + if not check[0]: + return check[1] + + page = int(args['page']) + page_size = int(args['page_size']) + domain = args['site'] + tojs = args['tojs'] + query_date = args['query_date'] + setDefaultSite(domain) + + conn = pSqliteDb('client_stat', domain) + stat = pSqliteDb('client_stat', domain) + + # 列表 + limit = str(page_size) + ' offset ' + str(page_size * (page - 1)) + field = 'time,weixin,android,iphone,mac,windows,linux,edeg,firefox,msie,metasr,qh360,theworld,tt,maxthon,opera,qq,uc,pc2345,safari,chrome,machine,mobile,other' + field_sum = toSumField(field.replace("time,", "")) + time_field = "substr(time,1,8)," + field_sum = time_field + field_sum + + stat = stat.field(field_sum) + if query_date == "today": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 0 * 86400)) + stat.where("time >= ?", (todayTime,)) + elif query_date == "yesterday": + startTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 1 * 86400)) + endTime = time.strftime( + '%Y%m%d00', time.localtime(time.time())) + stat.where("time>=? and time<=?", (startTime, endTime)) + elif query_date == "l7": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 7 * 86400)) + stat.where("time >= ?", (todayTime,)) + elif query_date == "l30": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 30 * 86400)) + stat.where("time >= ?", (todayTime,)) + else: + exlist = query_date.split("-") + start = time.strftime( + '%Y%m%d00', time.localtime(int(exlist[0]))) + end = time.strftime( + '%Y%m%d23', time.localtime(int(exlist[1]))) + stat.where("time >= ? and time <= ? ", (start, end,)) + + # 图表数据 + statlist = stat.group('substr(time,1,4)').inquiry(field) + + if len(statlist) > 0: + del(statlist[0]['time']) + + pc = 0 + pc_key_list = ['chrome', 'qh360', 'edeg', 'firefox', 'safari', 'msie', + 'metasr', 'theworld', 'tt', 'maxthon', 'opera', 'qq', 'pc2345'] + + for x in pc_key_list: + pc += statlist[0][x] + + mobile = 0 + mobile_key_list = ['mobile', 'android', 'iphone', 'weixin'] + for x in mobile_key_list: + mobile += statlist[0][x] + reqest_total = pc + mobile + + sum_data = { + "pc": pc, + "mobile": mobile, + "reqest_total": reqest_total, + } + + statlist = sorted(statlist[0].items(), + key=lambda x: x[1], reverse=True) + _statlist = statlist[0:10] + __statlist = {} + statlist = [] + for x in _statlist: + __statlist[x[0]] = x[1] + statlist.append(__statlist) + else: + sum_data = { + "pc": 0, + "mobile": 0, + "reqest_total": 0, + } + statlist = [] + + # 列表数据 + conn = conn.field(field_sum) + clist = conn.group('substr(time,1,8)').limit( + limit).order('time desc').inquiry(field) + + sql = "SELECT count(*) num from (\ + SELECT count(*) as num FROM client_stat GROUP BY substr(time,1,8)\ + )" + result = conn.query(sql, ()) + result = list(result) + count = result[0][0] + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = tojs + data['page'] = mw.getPage(_page) + data['data'] = clist + data['stat_list'] = statlist + data['sum_data'] = sum_data + + return mw.returnJson(True, 'ok', data) + + +def getDateRangeList(start, end): + dlist = [] + if start > end: + for x in list(range(start, 32, 1)): + dlist.append(x) + + for x in list(range(1, end, 1)): + dlist.append(x) + else: + for x in list(range(start, end, 1)): + dlist.append(x) + + return dlist + + +def getIpStatList(): + args = getArgs() + check = checkArgs(args, ['site', 'query_date']) + if not check[0]: + return check[1] + + domain = args['site'] + tojs = args['tojs'] + query_date = args['query_date'] + setDefaultSite(domain) + + conn = pSqliteDb('ip_stat', domain) + + origin_field = "ip,day,flow" + + if query_date == "today": + ftime = time.localtime(time.time()) + day = ftime.tm_mday + + field_day = "day" + str(day) + field_flow = "flow" + str(day) + # print(field_day, field_flow) + + field = "ip," + field_day + ' as day,' + field_flow + " as flow" + + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + elif query_date == "yesterday": + + ftime = time.localtime(time.time() - 86400) + day = ftime.tm_mday + + field_day = "day" + str(day) + field_flow = "flow" + str(day) + + field = "ip," + field_day + ' as day,' + field_flow + " as flow" + + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + elif query_date == "l7": + + field_day = "" + field_flow = "" + + now_time = time.localtime(time.time()) + end_day = now_time.tm_mday + + start_time = time.localtime(time.time() - 7 * 86400) + start_day = start_time.tm_mday + + rlist = getDateRangeList(start_day, end_day) + + for x in rlist: + field_day += "+cast(day" + str(x) + " as TEXT)" + field_flow += "+cast(flow" + str(x) + " as TEXT)" + + field_day = field_day.strip("+") + field_flow = field_flow.strip("+") + + field = "ip,(" + field_day + ') as day,(' + field_flow + ") as flow" + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + elif query_date == "l30": + + field_day = "" + field_flow = "" + + for x in list(range(1, 32, 1)): + field_day += "+cast(day" + str(x) + " as TEXT)" + field_flow += "+cast(flow" + str(x) + " as TEXT)" + + field_day = field_day.strip("+") + field_flow = field_flow.strip("+") + + # print(field_day) + # print(field_flow) + field = "ip,(" + field_day + ') as day,(' + field_flow + ") as flow" + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + clist = conn.order("flow desc").limit("50").inquiry(origin_field) + # print(clist) + + total_req = 0 + total_flow = 0 + + gepip_mmdb = getServerDir() + '/GeoLite2-City.mmdb' + geoip_exists = False + if os.path.exists(gepip_mmdb): + import geoip2.database + reader = geoip2.database.Reader(gepip_mmdb) + geoip_exists = True + # response = reader.city("172.70.206.144") + # print(response.country.names["zh-CN"]) + # print(response.subdivisions.most_specific.names["zh-CN"]) + # print(response.city.names["zh-CN"]) + + for x in clist: + total_req += x['day'] + total_flow += x['flow'] + + for i in range(len(clist)): + clist[i]['day_rate'] = round((clist[i]['day'] / total_req) * 100, 2) + clist[i]['flow_rate'] = round((clist[i]['flow'] / total_flow) * 100, 2) + ip = clist[i]['ip'] + + if ip == "127.0.0.1": + clist[i]['area'] = "本地" + elif geoip_exists: + try: + response = reader.city(ip) + country = response.country.names["zh-CN"] + + # print(ip, response.subdivisions) + _subdivisions = response.subdivisions + try: + if len(_subdivisions) < 1: + subdivisions = "" + else: + subdivisions = "," + response.subdivisions.most_specific.names[ + "zh-CN"] + except Exception as e: + subdivisions = "" + + try: + if 'zh-CN' in response.city.names: + city = "," + response.city.names["zh-CN"] + else: + city = "," + response.city.names["en"] + except Exception as e: + city = "" + + clist[i]['area'] = country + subdivisions + city + except Exception as e: + clist[i]['area'] = "内网?" + + return mw.returnJson(True, 'ok', clist) + + +def getUriStatList(): + args = getArgs() + check = checkArgs(args, ['site', 'query_date']) + if not check[0]: + return check[1] + + domain = args['site'] + tojs = args['tojs'] + query_date = args['query_date'] + setDefaultSite(domain) + + conn = pSqliteDb('uri_stat', domain) + + origin_field = "uri,day,flow" + + if query_date == "today": + ftime = time.localtime(time.time()) + day = ftime.tm_mday + + field_day = "day" + str(day) + field_flow = "flow" + str(day) + # print(field_day, field_flow) + + field = "uri," + field_day + ' as day,' + field_flow + " as flow" + + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + elif query_date == "yesterday": + + ftime = time.localtime(time.time() - 86400) + day = ftime.tm_mday + + field_day = "day" + str(day) + field_flow = "flow" + str(day) + + field = "uri," + field_day + ' as day,' + field_flow + " as flow" + + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + elif query_date == "l7": + + field_day = "" + field_flow = "" + + now_time = time.localtime(time.time()) + end_day = now_time.tm_mday + + start_time = time.localtime(time.time() - 7 * 86400) + start_day = start_time.tm_mday + + rlist = getDateRangeList(start_day, end_day) + + for x in rlist: + field_day += "+cast(day" + str(x) + " as TEXT)" + field_flow += "+cast(flow" + str(x) + " as TEXT)" + + field_day = field_day.strip("+") + field_flow = field_flow.strip("+") + + field = "uri,(" + field_day + ') as day,(' + field_flow + ") as flow" + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + elif query_date == "l30": + + field_day = "" + field_flow = "" + + for x in list(range(1, 32, 1)): + field_day += "+cast(day" + str(x) + " as TEXT)" + field_flow += "+cast(flow" + str(x) + " as TEXT)" + + field_day = field_day.strip("+") + field_flow = field_flow.strip("+") + + # print(field_day) + # print(field_flow) + field = "uri,(" + field_day + ') as day,(' + field_flow + ") as flow" + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + clist = conn.order("flow desc").limit("50").inquiry(origin_field) + + total_req = 0 + total_flow = 0 + + for x in clist: + total_req += x['day'] + total_flow += x['flow'] + + for i in range(len(clist)): + clist[i]['day_rate'] = round((clist[i]['day'] / total_req) * 100, 2) + clist[i]['flow_rate'] = round((clist[i]['flow'] / total_flow) * 100, 2) + + return mw.returnJson(True, 'ok', clist) + + +def getWebLogCount(domain, query_date): + conn = pSqliteDb('web_logs', domain) + + todayTime = time.strftime('%Y-%m-%d 00:00:00', time.localtime()) + todayUt = int(time.mktime(time.strptime(todayTime, "%Y-%m-%d %H:%M:%S"))) + if query_date == 'today': + conn = conn.where("time>=?", (todayUt,)) + elif query_date == "yesterday": + conn = conn.where("time>=? and time<=?", (todayUt - 86400, todayUt)) + elif query_date == "l7": + conn = conn.where("time>=?", (todayUt - 7 * 86400,)) + elif query_date == "l30": + conn = conn.where("time>=?", (todayUt - 30 * 86400,)) + else: + exlist = query_date.split("-") + conn = conn.where("time>=? and time<=?", (exlist[0], exlist[1])) + + count_key = "count(*) as num" + count = conn.field(count_key).limit('').order('').inquiry() + count = count[0][count_key] + return count + + +def getSpiderStatList(): + args = getArgs() + check = checkArgs(args, ['page', 'page_size', + 'site', 'query_date']) + if not check[0]: + return check[1] + + page = int(args['page']) + page_size = int(args['page_size']) + domain = args['site'] + tojs = args['tojs'] + query_date = args['query_date'] + setDefaultSite(domain) + + conn = pSqliteDb('spider_stat', domain) + stat = pSqliteDb('spider_stat', domain) + + total_req = getWebLogCount(domain, query_date) + + # 列表 + limit = str(page_size) + ' offset ' + str(page_size * (page - 1)) + field = 'time,bytes,bing,soso,yahoo,sogou,google,baidu,qh360,youdao,yandex,dnspod,other' + field_sum = toSumField(field.replace("time,", "")) + time_field = "substr(time,1,8)," + field_sum = time_field + field_sum + + stat = stat.field(field_sum) + if query_date == "today": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 0 * 86400)) + stat.where("time >= ?", (todayTime,)) + elif query_date == "yesterday": + startTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 1 * 86400)) + endTime = time.strftime( + '%Y%m%d00', time.localtime(time.time())) + stat.where("time>=? and time<=?", (startTime, endTime)) + elif query_date == "l7": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 7 * 86400)) + stat.where("time >= ?", (todayTime,)) + elif query_date == "l30": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 30 * 86400)) + stat.where("time >= ?", (todayTime,)) + else: + exlist = query_date.split("-") + start = time.strftime( + '%Y%m%d00', time.localtime(int(exlist[0]))) + end = time.strftime( + '%Y%m%d23', time.localtime(int(exlist[1]))) + stat.where("time >= ? and time <= ? ", (start, end,)) + + # 图表数据 + statlist = stat.group('substr(time,1,4)').inquiry(field) + + if len(statlist) > 0: + del(statlist[0]['time']) + + spider_total = 0 + for x in statlist[0]: + spider_total += statlist[0][x] + + sum_data = {"spider": spider_total, "reqest_total": total_req} + statlist = sorted(statlist[0].items(), + key=lambda x: x[1], reverse=True) + _statlist = statlist[0:9] + __statlist = {} + statlist = [] + for x in _statlist: + __statlist[x[0]] = x[1] + statlist.append(__statlist) + else: + sum_data = {"spider": 0, "reqest_total": total_req} + statlist = [] + + # 列表数据 + conn = conn.field(field_sum) + clist = conn.group('substr(time,1,8)').limit( + limit).order('time desc').inquiry(field) + + sql = "SELECT count(*) num from (\ + SELECT count(*) as num FROM spider_stat GROUP BY substr(time,1,8)\ + )" + result = conn.query(sql, ()) + result = list(result) + count = result[0][0] + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = tojs + data['page'] = mw.getPage(_page) + data['data'] = clist + data['stat_list'] = statlist + data['sum_data'] = sum_data + + return mw.returnJson(True, 'ok', data) + + +def installPreInspection(): + check_op = mw.getServerDir() + "/openresty" + if not os.path.exists(check_op): + return "请先安装OpenResty" + return 'ok' + +def uninstallPreInspection(): + stop() + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'run_info': + print(runInfo()) + elif func == 'get_global_conf': + print(getGlobalConf()) + elif func == 'set_global_conf': + print(setGlobalConf()) + elif func == 'get_site_conf': + print(getSiteConf()) + elif func == 'set_site_conf': + print(setSiteConf()) + elif func == 'get_default_site': + print(getDefaultSite()) + elif func == 'get_overview_list': + print(getOverviewList()) + elif func == 'get_site_list': + print(getSiteList()) + elif func == 'get_logs_list': + print(getLogsList()) + elif func == 'get_logs_error_list': + print(getLogsErrorList()) + elif func == 'get_logs_realtime_info': + print(getLogsRealtimeInfo()) + elif func == 'get_client_stat_list': + print(getClientStatList()) + elif func == 'get_ip_stat_list': + print(getIpStatList()) + elif func == 'get_uri_stat_list': + print(getUriStatList()) + elif func == 'get_spider_stat_list': + print(getSpiderStatList()) + else: + print('error') diff --git a/plugins/webstats/info.json b/plugins/webstats/info.json new file mode 100755 index 000000000..0eadd5e46 --- /dev/null +++ b/plugins/webstats/info.json @@ -0,0 +1,31 @@ +{ + "hook":[ + { + "tag":"site_cb", + "site_cb": { + "title":"网站统计", + "name":"webstats", + "add":{"func":"reload"}, + "update":{"func":"reload"}, + "delete":{"func":"reload"} + } + } + ], + "sort": "2", + "ps": "网站统计报表", + "name": "webstats", + "title": "网站统计", + "shell": "install.sh", + "versions":"0.2.5", + "tip": "soft", + "install_pre_inspection":true, + "uninstall_pre_inspection":true, + "checks": "server/webstats/version.pl", + "path": "server/webstats", + "display": 1, + "author": "midoks", + "date": "2022-07-18", + "home": "https://github.com/midoks/mdserver-web", + "type": 0, + "pid": "1" +} \ No newline at end of file diff --git a/plugins/webstats/install.old.sh b/plugins/webstats/install.old.sh new file mode 100755 index 000000000..bbf82e3fd --- /dev/null +++ b/plugins/webstats/install.old.sh @@ -0,0 +1,160 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH=$PATH:/opt/homebrew/bin + + +## https://www.yangshuaibin.com/detail/392251 +# cd /www/server/mdserver-web/plugins/webstats && bash install.sh install 0.2.5 +# /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/webstats && bash install.sh install 0.2.5 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 +sys_os=`uname` + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + HTTP_PREFIX="https://" +fi + +PIPSRC="https://pypi.python.org/simple" +if [ "$LOCAL_ADDR" != "common" ];then + PIPSRC="https://pypi.tuna.tsinghua.edu.cn/simple" +fi + + +if [ "$sys_os" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +get_latest_release() { + curl -sL "https://api.github.com/repos/$1/releases/latest" | grep '"tag_name":' | cut -d'"' -f4 +} + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/webstats + mkdir -p $serverPath/webstats + + # 下载源码安装包 + # curl -O $serverPath/source/webstats/lua-5.1.5.tar.gz https://www.lua.org/ftp/lua-5.1.5.tar.gz + # cd $serverPath/source/webstats && tar xvf lua-5.1.5.tar.gz + # cd lua-5.1.5 + # make linux test && make install + + + # luarocks + if [ ! -f $serverPath/source/webstats/luarocks-3.5.0.tar.gz ];then + wget --no-check-certificate -O $serverPath/source/webstats/luarocks-3.5.0.tar.gz http://luarocks.org/releases/luarocks-3.5.0.tar.gz + fi + + if [ ! -d $serverPath/source/webstats/luarocks-3.5.0 ];then + cd $serverPath/source/webstats && tar xvf luarocks-3.5.0.tar.gz + fi + + cd $serverPath/source/webstats/luarocks-3.5.0 && ./configure --prefix=$serverPath/webstats/luarocks \ + --with-lua-include=$serverPath/openresty/luajit/include/luajit-2.1 \ + --with-lua-bin=$serverPath/openresty/luajit/bin + make -I${serverPath}/openresty/luajit/bin + make install + + + # lsqlite3_fsl09y + if [ ! -f $serverPath/source/webstats/lsqlite3_fsl09y.zip ];then + wget --no-check-certificate -O $serverPath/source/webstats/lsqlite3_fsl09y.zip http://lua.sqlite.org/index.cgi/zip/lsqlite3_fsl09y.zip?uuid=fsl_9y + + fi + + if [ ! -d $serverPath/source/webstats/lsqlite3_fsl09y ];then + cd $serverPath/source/webstats && unzip lsqlite3_fsl09y.zip + fi + + PATH=${serverPath}/openresty/luajit:${serverPath}/openresty/luajit/include/luajit-2.1:$PATH + export PATH=$PATH:$serverPath/webstats/luarocks/bin + + if [ ! -f $serverPath/webstats/lua/lsqlite3.so ];then + if [ "${sys_os}" == "Darwin" ];then + cd $serverPath/source/webstats/lsqlite3_fsl09y + # SQLITE_DIR=/usr/local/Cellar/sqlite/3.36.0 + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + echo "BREW_DIR:"${BREW_DIR} + find_cfg=`cat Makefile | grep 'SQLITE_DIR'` + if [ "$find_cfg" == "" ];then + LIB_SQLITE_DIR=`brew info sqlite | grep ${BREW_DIR}/Cellar/sqlite | cut -d \ -f 1 | awk 'END {print}'` + echo "LIB_SQLITE_DIR:"${LIB_SQLITE_DIR} + sed -i $BAK "s#\$(ROCKSPEC)#\$(ROCKSPEC) SQLITE_DIR=${LIB_SQLITE_DIR}#g" Makefile + fi + make + else + cd $serverPath/source/webstats/lsqlite3_fsl09y && make + fi + fi + + # copy to code path + DEFAULT_DIR=$serverPath/webstats/luarocks/lib/lua/5.1 + if [ -f ${DEFAULT_DIR}/lsqlite3.so ];then + mkdir -p $serverPath/webstats/lua + cp -rf ${DEFAULT_DIR}/lsqlite3.so $serverPath/webstats/lua/lsqlite3.so + fi + + # https://github.com/P3TERX/GeoLite.mmdb + pip install geoip2 -i $PIPSRC + # if [ ! -f $serverPath/webstats/GeoLite2-City.mmdb ];then + # wget --no-check-certificate -O $serverPath/webstats/GeoLite2-City.mmdb https://github.com/P3TERX/GeoLite.mmdb/releases/download/2022.10.16/GeoLite2-City.mmdb + # fi + + # 缓存数据 + GEO_VERSION=$(get_latest_release "P3TERX/GeoLite.mmdb") + if [ ! -f $serverPath/source/webstats/GeoLite2-City.mmdb ];then + if [ "$LOCAL_ADDR" == "cn" ];then + wget --no-check-certificate -O $serverPath/source/webstats/GeoLite2-City.mmdb https://dl.midoks.icu/soft/webstats/GeoLite2-City.mmdb + else + wget --no-check-certificate -O $serverPath/source/webstats/GeoLite2-City.mmdb https://github.com/P3TERX/GeoLite.mmdb/releases/download/${GEO_VERSION}/GeoLite2-City.mmdb + fi + fi + + if [ -f $serverPath/source/webstats/GeoLite2-City.mmdb ];then + cp -rf $serverPath/source/webstats/GeoLite2-City.mmdb $serverPath/webstats/GeoLite2-City.mmdb + fi + + cd $rootPath && python3 plugins/webstats/index.py start + echo "${VERSION}" > $serverPath/webstats/version.pl + + echo '网站统计安装完成' + + # delete install data + if [ -d $serverPath/source/webstats/lsqlite3_fsl09y ];then + rm -rf $serverPath/source/webstats/lsqlite3_fsl09y + fi + if [ -d $serverPath/source/webstats/luarocks-3.5.0 ];then + rm -rf $serverPath/source/webstats/luarocks-3.5.0 + fi +} + +Uninstall_App() +{ + cd $rootPath && python3 plugins/webstats/index.py stop + rm -rf $serverPath/webstats + echo "网站统计卸载完成" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/webstats/install.sh b/plugins/webstats/install.sh new file mode 100755 index 000000000..426ca07ec --- /dev/null +++ b/plugins/webstats/install.sh @@ -0,0 +1,158 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH=$PATH:/opt/homebrew/bin + + +## https://www.yangshuaibin.com/detail/392251 +# cd /www/server/mdserver-web/plugins/webstats && bash install.sh install 0.2.5 +# cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/webstats && bash install.sh install 0.2.5 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 +sys_os=`uname` + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + HTTP_PREFIX="https://" +fi + +PIPSRC="https://pypi.python.org/simple" +if [ "$LOCAL_ADDR" != "common" ];then + PIPSRC="https://pypi.tuna.tsinghua.edu.cn/simple" +fi + + +if [ "$sys_os" == "Darwin" ];then + BAK='_bak' +else + BAK='' +fi + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +get_latest_release() { + curl -sL "https://api.github.com/repos/$1/releases/latest" | grep '"tag_name":' | cut -d'"' -f4 +} + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/webstats + mkdir -p $serverPath/webstats + + # 下载源码安装包 + # curl -O $serverPath/source/webstats/lua-5.1.5.tar.gz https://www.lua.org/ftp/lua-5.1.5.tar.gz + # cd $serverPath/source/webstats && tar xvf lua-5.1.5.tar.gz + # cd lua-5.1.5 + # make linux test && make install + + + # luarocks + if [ ! -f $serverPath/source/webstats/luarocks-3.5.0.tar.gz ];then + wget --no-check-certificate -O $serverPath/source/webstats/luarocks-3.5.0.tar.gz http://luarocks.org/releases/luarocks-3.5.0.tar.gz + fi + + if [ ! -d $serverPath/source/webstats/luarocks-3.5.0 ];then + cd $serverPath/source/webstats && tar xvf luarocks-3.5.0.tar.gz + fi + + cd $serverPath/source/webstats/luarocks-3.5.0 && ./configure --prefix=$serverPath/webstats/luarocks \ + --with-lua-include=$serverPath/openresty/luajit/include/luajit-2.1 \ + --with-lua-bin=$serverPath/openresty/luajit/bin + make -I${serverPath}/openresty/luajit/bin + make install + + if [ ! -f $serverPath/source/webstats/lsqlite3_v096.zip ];then + wget --no-check-certificate -O $serverPath/source/webstats/lsqlite3_v096.zip https://github.com/midoks/mdserver-web/releases/download/0.18.4/lsqlite3_v096.zip + fi + + if [ ! -d $serverPath/source/webstats/lsqlite3_v096 ];then + cd $serverPath/source/webstats && unzip lsqlite3_v096.zip + fi + + + PATH=${serverPath}/openresty/luajit:${serverPath}/openresty/luajit/include/luajit-2.1:$PATH + export PATH=$PATH:$serverPath/webstats/luarocks/bin + + if [ ! -f $serverPath/webstats/lua/lsqlite3.so ];then + if [ "${sys_os}" == "Darwin" ];then + cd $serverPath/source/webstats/lsqlite3_v096 + # SQLITE_DIR=/usr/local/Cellar/sqlite/3.36.0 + BREW_DIR=`which brew` + BREW_DIR=${BREW_DIR/\/bin\/brew/} + echo "BREW_DIR:"${BREW_DIR} + find_cfg=`cat Makefile | grep 'SQLITE_DIR'` + if [ "$find_cfg" == "" ];then + LIB_SQLITE_DIR=`brew info sqlite | grep ${BREW_DIR}/Cellar/sqlite | cut -d \ -f 1 | awk 'END {print}'` + echo "LIB_SQLITE_DIR:"${LIB_SQLITE_DIR} + sed -i $BAK "s#\$(ROCKSPEC)#\$(ROCKSPEC) SQLITE_DIR=${LIB_SQLITE_DIR}#g" Makefile + fi + make + else + cd $serverPath/source/webstats/lsqlite3_v096 && make + fi + fi + + # copy to code path + DEFAULT_DIR=$serverPath/webstats/luarocks/lib/lua/5.1 + if [ -f ${DEFAULT_DIR}/lsqlite3.so ];then + mkdir -p $serverPath/webstats/lua + cp -rf ${DEFAULT_DIR}/lsqlite3.so $serverPath/webstats/lua/lsqlite3.so + fi + + # https://github.com/P3TERX/GeoLite.mmdb + pip install geoip2 -i $PIPSRC + # if [ ! -f $serverPath/webstats/GeoLite2-City.mmdb ];then + # wget --no-check-certificate -O $serverPath/webstats/GeoLite2-City.mmdb https://github.com/P3TERX/GeoLite.mmdb/releases/download/2022.10.16/GeoLite2-City.mmdb + # fi + + # 缓存数据 + GEO_VERSION=$(get_latest_release "P3TERX/GeoLite.mmdb") + if [ ! -s $serverPath/source/webstats/GeoLite2-City.mmdb ];then + if [ "$LOCAL_ADDR" == "cn" ];then + wget --no-check-certificate -O $serverPath/source/webstats/GeoLite2-City.mmdb https://dl.midoks.icu/soft/webstats/GeoLite2-City.mmdb + else + wget --no-check-certificate -O $serverPath/source/webstats/GeoLite2-City.mmdb https://github.com/P3TERX/GeoLite.mmdb/releases/download/${GEO_VERSION}/GeoLite2-City.mmdb + fi + fi + + if [ -s $serverPath/source/webstats/GeoLite2-City.mmdb ];then + cp -rf $serverPath/source/webstats/GeoLite2-City.mmdb $serverPath/webstats/GeoLite2-City.mmdb + fi + + cd $rootPath && python3 plugins/webstats/index.py start + echo "${VERSION}" > $serverPath/webstats/version.pl + + echo '网站统计安装完成' + + # delete install data + if [ -d $serverPath/source/webstats/lsqlite3_v096 ];then + rm -rf $serverPath/source/webstats/lsqlite3_v096 + fi + if [ -d $serverPath/source/webstats/luarocks-3.5.0 ];then + rm -rf $serverPath/source/webstats/luarocks-3.5.0 + fi +} + +Uninstall_App() +{ + cd $rootPath && python3 plugins/webstats/index.py stop + rm -rf $serverPath/webstats + echo "网站统计卸载完成" +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/webstats/js/setting.js b/plugins/webstats/js/setting.js new file mode 100644 index 000000000..53350a607 --- /dev/null +++ b/plugins/webstats/js/setting.js @@ -0,0 +1,269 @@ + +function wsGlobalSetting(){ +//////////////////////////////////////////////// +wsPost('get_global_conf', '' ,{}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + var html = '
                                      \ +
                                      \ +
                                      统计:
                                      \ +
                                      \ +
                                      \ +
                                      IP统计
                                      \ + \ + \ + ?\ +
                                      \ +
                                      \ +
                                      URI统计
                                      \ + \ + \ + ?\ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      日志:
                                      \ +
                                      \ +
                                      \ +
                                      日志保存天数
                                      \ + /天 \ + \ + ?\ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      监控配置:
                                      \ +
                                      \ +
                                      \ + CDN headers\ + 排除扩展\ + 排除响应状态\ + 排除路径\ + 排除IP\ + 记录请求原文\ +
                                      \ +
                                      \ + * 准确识别CDN网络IP地址,请注意大小写,如需多个请换行填写\ + \ +
                                      \ +

                                      \ + \ + \ + ?\ +

                                      \ +
                                      \ +
                                      \ +
                                      '; + $(".soft-man-con").html(html); + $('[data-toggle="tooltip"]').tooltip(); + + var common_tpl_tips = '* 准确识别CDN网络IP地址,请注意大小写,如需多个请换行填写'; + var common_tpl_area = ''; + + $('#webstats .tab-con textarea').text(rdata['global']['cdn_headers'].join('\n')); + $('#webstats .tab-nav span').click(function(e){ + $('#webstats .tab-nav span').removeClass('on'); + $(this).addClass('on'); + $('#webstats .tab-con').html(''); + + var typename = $(this).attr('data-type'); + if (typename == 'cdn_headers'){ + var content = $(common_tpl_tips).html('* 准确识别CDN网络IP地址,请注意大小写,如需多个请换行填写').prop('outerHTML'); + var area = $(common_tpl_area).html(rdata['global']['cdn_headers'].join('\n')).prop('outerHTML'); + + content += area; + $('#webstats .tab-con').html(content); + } else if (typename == 'exclude_extension'){ + + var content = $(common_tpl_tips).html('* 排除的请求不写入网站日志,不统计PV、UV、IP,只累计总请求、总流量数,如需多个请换行填写').prop('outerHTML'); + var area = $(common_tpl_area).html(rdata['global']['exclude_extension'].join('\n')).prop('outerHTML'); + content += area; + $('#webstats .tab-con').html(content); + } else if (typename == 'exclude_status'){ + var content = $(common_tpl_tips).html('* 排除的请求不写入网站日志,不统计PV、UV、IP,只累计总请求、总流量数,如需多个请换行填写').prop('outerHTML'); + var area = $(common_tpl_area).html(rdata['global']['exclude_status'].join('\n')).prop('outerHTML'); + content += area; + $('#webstats .tab-con').html(content); + } else if (typename == 'exclude_ip'){ + var txt = '
                                      * 排除的IP不写入网站日志,不统计PV、UV、IP,只累计总请求、总流量数,如需多个请换行填写
                                      \ +
                                      * 支持 192.168.1.1-192.168.1.10格式排除区间IP
                                      ' + var content = $(common_tpl_tips).html(txt).prop('outerHTML'); + var area = $(common_tpl_area).html(rdata['global']['exclude_ip'].join('\n')).prop('outerHTML'); + content += area; + $('#webstats .tab-con').html(content); + } else if (typename == 'record_post_args'){ + var txt = '
                                      记录请求原文说明:HTTP请求原文包括客户端请求详细参数,有助于分析或排查异常请求;
                                      \ +
                                      考虑到HTTP请求原文会占用额外存储空间,默认仅记录500错误请求原文。
                                      ' + var content = $(common_tpl_tips).html(txt).prop('outerHTML'); + + var record_post_args = ''; + if (rdata['global']['record_post_args']){ + record_post_args = 'checked'; + } + var record_get_403_args = ''; + if (rdata['global']['record_get_403_args']){ + record_get_403_args = 'checked'; + } + + + var check = '
                                      \ + \ + \ +
                                      '; + content+=check; + + $('#webstats .tab-con').html(content); + } else if ( typename == 'exclude_url'){ + var txt = '* 排除的请求不写入网站日志,不统计PV、UV、IP,只累计总请求、总流量数' + var content = $(common_tpl_tips).html(txt).prop('outerHTML'); + + var _text = ''; + var _tmp = rdata['global']['exclude_url']; + for(var i = 0; i<10; i++){ + if(typeof _tmp[i] == 'undefined'){ + _tmp[i] = {mode:'regular',url:''} + } + + _text += '\ + \ + \ + \ + \ + '; + } + + var list = '
                                      \ + \ + \ + \ + \ + '+_text+'\ +
                                      排除方式排除路径
                                      \ +
                                      '; + + content += list; + $('#webstats .tab-con').html(content); + } + + }); + + $('#ip_top_num').click(function(){ + var num = $('input[name="ip_top_num"]').val(); + if(num == '' || num <= 0 || num > 2000) return layer.msg('请设置1-2000范围的统计数量',{icon:2}); + wsPost('set_global_conf','',{ip_top_num:num}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + }); + }); + + $('#uri_top_num').click(function(){ + var num = $('input[name="uri_top_num"]').val(); + if(num == '' || num <= 0 || num > 2000) return layer.msg('请设置1-2000范围的统计数量',{icon:2}) + wsPost('set_global_conf','',{uri_top_num:num}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + }); + }); + + $('#save_day').click(function(){ + var num = $('input[name="save_day"]').val(); + wsPost('set_global_conf','',{save_day:num}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + }); + }); + + $('#submitSetting').click(function(){ + var select = $('#webstats .tab-nav span'); + var select_pos = 0; + $('#webstats .tab-nav span').each(function(i){ + if ($(this).hasClass('on')){select_pos = i;} + }); + + if ( [0,1,2,4].indexOf(select_pos)>-1 ){ + var setting_cdn = $('textarea[name="setting-cdn"]').val(); + + // var list = setting_cdn.split('\n') + var args = {} + + if ( select_pos == 0 ){ + args['cdn_headers'] = setting_cdn; + } else if ( select_pos == 1 ){ + args['exclude_extension'] = setting_cdn; + } else if ( select_pos == 2 ){ + args['exclude_status'] = setting_cdn; + } else if ( select_pos == 4 ){ + args['exclude_ip'] = setting_cdn; + } + + wsPost('set_global_conf','', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + }); + } + + if (select_pos == 3 ){ + + var list = ""; + for (var i = 0; i<10; i++) { + var tmp = ""; + var url_type = $('select[name="url_type_'+i+'"]').val(); + var url_val = $('input[name="url_val_'+i+'"]').val(); + + if (url_val != ""){ + list += url_type +'|' + url_val +';'; + } + } + wsPost('set_global_conf','', {"exclude_url":list}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + }); + } + + if (select_pos == 5){ + + var record_post_args = $('input[name="record_post_args"]').prop('checked'); + var record_get_403_args = $('input[name="record_get_403_args"]').prop('checked'); + wsPost('set_global_conf','', {"record_post_args":record_post_args,'record_get_403_args':record_get_403_args}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + }); + } + + wsGlobalSetting(); + }); + + + $('#setAll').click(function(){ + var args = "name=webstats&func=reload"; + layer.confirm('您真的要同步所有站点吗?', {icon:3,closeBtn: 1}, function() { + var e = layer.msg('正在同步,请稍候...', {icon: 16,time: 0}); + $.post("/plugins/run", args, function(g) { + layer.close(e); + if( g.status && g.data != 'ok' ) { + layer.msg(g.data, {icon: 2,time: 3000,shade: 0.3,shadeClose: true}); + } else { + layer.msg('同步成功!', {icon: 1,time: 0}); + } + },'json').error(function() { + layer.close(e); + layer.msg('操作异常!', {icon: 1}); + }); + }) + }); + + +}); +/////////////////////////////////////////////// +} \ No newline at end of file diff --git a/plugins/webstats/js/stats.js b/plugins/webstats/js/stats.js new file mode 100644 index 000000000..ab5130ca1 --- /dev/null +++ b/plugins/webstats/js/stats.js @@ -0,0 +1,2604 @@ +function str2Obj(str){ + var data = {}; + kv = str.split('&'); + for(i in kv){ + v = kv[i].split('='); + data[v[0]] = v[1]; + } + return data; +} + +function wsOriginPost(method, version, args, callback){ + + var req_data = {}; + req_data['name'] = 'webstats'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(str2Obj(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + + +function wsPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + wsOriginPost(method, version, args,function(data){ + layer.close(loadT); + callback(data); + }); +} + + + +function wsPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'webstats'; + req_data['script']='webstats_index'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(str2Obj(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function toSecond(val){ + if (val>=1000){ + val = (val / 1000).toFixed()+"s"; + return val; + } + return val + "ms"; +} + + +function makeHoursData(data, type="ip"){ + var list = []; + var rlist = []; + for (var i = 0; i < 24; i++) { + if (i<10){ + list.push("0"+i) + } else { + list.push(i+"") + } + rlist.push(i+"") + } + + var rdata = {}; + rdata['key'] = rlist; + + var tmpdata = {}; + for (var i = 0; i < data.length; i++) { + var tk = data[i]['time']; + + var v = data[i]; + if (type=='length'){ + v['length'] = (v['length']/1024).toFixed(); + } + tmpdata[tk] = v; + } + + var val = []; + for (var i = 0; i < list.length; i++) { + var tk = list[i]; + + if (tmpdata[tk]){ + val.push(tmpdata[tk][type]); + }else{ + val.push(0); + } + } + + rdata['value'] = val; + // console.log(rdata); + return rdata +} + +function makeDayData(data, type="ip") { + var rdata = {}; + + var rdata_key = []; + var rdata_val = []; + + for (var i = 0; i < data.length; i++) { + var tk = data[i]['time']; + + rdata_key.push(tk); + + var v = data[i][type]; + if (type=='length'){ + v = (v/1024).toFixed(); + } + rdata_val.push(v) + } + + rdata['key'] = rdata_key; + rdata['value'] = rdata_val; + + return rdata +} + +function getTime() { + var now = new Date(); + var hour = now.getHours(); + var minute = now.getMinutes(); + var second = now.getSeconds(); + if (minute < 10) { + minute = "0" + minute; + } + if (second < 10) { + second = "0" + second; + } + var nowdate = hour + ":" + minute + ":" + second; + return nowdate; +} + +var ovTimer = null; +function wsOverviewRequest(page){ + clearInterval(ovTimer); + + var args = {}; + + args['site'] = $('select[name="site"]').val(); + + var query_date = 'today'; + if ($('#time_choose').attr("data-name") != ''){ + query_date = $('#time_choose').attr("data-name"); + } else { + query_date = $('#search_time button.cur').attr("data-name"); + } + args['query_date'] = query_date; + args['order'] = $('#time_order button.cur').attr('data-name'); + + var select_option = $('.indicators-container input:checked').parent().attr('data-name'); + + // console.log($('.indicators-container input:checked').parent().find('span').text()); + // console.log(select_option); + + wsPost('get_overview_list', '' ,args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var list = ''; + var data = rdata.data.data; + var statData = rdata.data.stat_list; + + // console.log(statData, data); + + var stat_pv = statData['pv'] == null?0:statData['pv']; + var stat_uv = statData['uv'] == null?0:statData['uv']; + var stat_ip = statData['ip'] == null?0:statData['ip']; + var stat_length = statData['length'] == null?0:statData['length']; + var stat_req = statData['req'] == null?0:statData['req']; + + $('.overview_list .overview_box:eq(0) .ov_num').text(stat_pv); + $('.overview_list .overview_box:eq(1) .ov_num').text(stat_uv); + $('.overview_list .overview_box:eq(2) .ov_num').text(stat_ip); + $('.overview_list .overview_box:eq(3) .ov_num').text(toSize(stat_length)); + $('.overview_list .overview_box:eq(4) .ov_num').text(stat_req); + + var list = []; + for (var i = 0; i < data.length; i++) { + list.push(data[i][select_option]); + } + + // console.log("list",list); + var chat = {}; + var is_compare = false; + + var tmpChatData = { + "key":[], + "value":[] + } + + if (select_option == 'realtime_traffic' || select_option == 'realtime_request'){ + } else { + if (args['order'] == 'hour'){ + tmpChatData = makeHoursData(data, select_option); + } else { + tmpChatData = makeDayData(data, select_option); + } + } + + + chat['yAxis'] = [{ + type: 'value', + splitNumber: 5, + axisLabel: { + textStyle: { + color: '#a8aab0', + fontStyle: 'normal', + fontFamily: '微软雅黑', + fontSize: 12, + }, + }, + axisLine:{ + show: false + }, + axisTick:{ + show: false + }, + splitLine: { + show: true, + lineStyle: { + color: '#E5E9ED' + } + } + }]; + + chat['tooltip'] = { + show:true, + trigger: 'axis', + backgroundColor: 'rgba(255,255,255,0.8)', + axisPointer: { // 坐标轴指示器,坐标轴触发有效 + type: 'line', // 默认为直线,可选为:'line' | 'shadow' + lineStyle: { + color: 'rgba(150,150,150,0.2)' + } + }, + textStyle: { + color: '#666', + fontSize: '14px', + }, + extraCssText: 'width:220px;height:'+(is_compare?'30%':'22%')+';padding:0;box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);"', + formatter: function (params) { + var htmlStr = ""; + for (var i = 0; i < params.length; i++) { + var tem = params[i].name; + var val = params[i].value; + if(args['order'] == 'hour'){ + if (tem.indexOf('/') < 0) { + tem > 9 ? tem = tem + ":00 - " + tem + ":59" : tem = "0" + tem + ":00 - " + + "0" + tem + ":59"; + } + val > 0 ? val = val : val = '--' + } + + htmlStr += + '
                                      ' + + '' + + '' + params[i].seriesName + '' + + '' + val + '' + '
                                      ' + } + var res ='
                                      ' + + tem + '' + (is_compare?trend_name:'') + + '' + htmlStr + '
                                      ' + return res; + } + } + + var legendName = $('.indicators-container input:checked').parent().find('span').text() + chat['legendData'] = [legendName]; + + var statEc = echarts.init(document.getElementById('total_num_echart')); + var option = { + tooltip:chat['tooltip'], + backgroundColor:'#fff', + legend:{ + data:chat['legendData'], + left:'center', + top:'94%', + }, + grid: { + bottom: '9%', + containLabel: true, + x: 20, + y: 20, + x2: 20, + y2: 20 + }, + xAxis: { + type: 'category', + boundaryGap: false, + boundaryGap: false, + axisTick: { + alignWithLabel: true + }, + data: tmpChatData["key"], + }, + yAxis: chat['yAxis'], + graphic:[{ + type: 'group', + right: 330, + top: 0, + z: 100, + children: [{ + type: 'text', + left: 'center', + top: 'center', + z: 100, + style: { + fill: '#ccc', + text: args['site'], + font: '16px Arial' + } + }] + }], + series: [ + { + name:legendName, + data: tmpChatData["value"], + type: 'line', + smooth: true, + itemStyle: { + normal: { + color:'#3A84FF', + lineStyle: { + color: "#3A84FF", + width:1, + }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ + offset: 0, + color: 'rgba(58,132,255,0)' + }, { + offset: 1, + color: 'rgba(58,132,255,0.35)' + }]), + } + } + }, + areaStyle: {} + } + ] + }; + + statEc.setOption(option); + + if (select_option == 'realtime_traffic' || select_option == 'realtime_request'){ + + var xData = []; + var yData = []; + ovTimer = setInterval(function(){ + var select_option = $('.indicators-container input:checked').parent().attr('data-name'); + if (select_option != "realtime_traffic" && select_option != 'realtime_request' ){ + clearInterval(ovTimer); + // console.log("get_logs_realtime_info over:"+select_option); + return; + } + + var second = $('#check_realtime_second').val(); + + wsOriginPost("get_logs_realtime_info",'',{"site":args["site"], "type":select_option,'second':second} , function(rdata){ + + var rdata = $.parseJSON(rdata.data); + + var realtime_traffic = rdata.data['realtime_traffic']; + var realtime_request = rdata.data['realtime_request']; + + realtime_traffic_calc = toSize(realtime_traffic); + + + $('.overview_list .overview_box:eq(5) .ov_num').text(realtime_traffic_calc); + $('.overview_list .overview_box:eq(6) .ov_num').text(realtime_request); + + + var realtime_name = select_option == 'realtime_traffic' ? '实时流量':'每秒请求'; + var val = realtime_request; + if (select_option == 'realtime_traffic'){ + val = realtime_traffic_calc.split(' ')[0]; + realtime_name = realtime_traffic_calc; + } + + xData.push(getTime()); + yData.push(val); + + if (xData.length > 20){ + xData.shift(); + yData.shift(); + } + + statEc.setOption({ + xAxis: { + data: xData + }, + series: [{ + name: realtime_name, + data: yData + }] + }); + }); + },2000); + } + }); +} + + +function wsOverview(){ +//////////////////////////////////////////////////////////////////////////////////////////////////////// +var randstr = getRandomString(10); + +var html = '
                                      \ +
                                      \ + 网站: \ + \ + 时间: \ +
                                      \ +
                                      \ + \ + \ + \ + \ +
                                      \ + \ +
                                      \ + 时段: \ +
                                      \ +
                                      \ + \ + \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ + \ +
                                      \ +
                                      \ +
                                      \ + \ +
                                      \ +
                                      \ +

                                      浏览量(PV)?

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      访客量(UV)?

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      IP数?

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      流量?

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      请求?

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      实时流量?

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      每秒请求?

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ + 趋势指标: \ +
                                      \ + \ + 浏览量(PV)\ +
                                      \ +
                                      \ + \ + 访客量(UV)\ +
                                      \ +
                                      \ + \ + IP数\ +
                                      \ +
                                      \ + \ + 流量(KB)\ +
                                      \ +
                                      \ + \ + 请求\ +
                                      \ +
                                      \ + \ + 实时流量\ +
                                      \ +
                                      \ + \ + 每X秒请求\ +
                                      \ +
                                      \ + \ + \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      '; +$(".soft-man-con").html(html); +$('[data-toggle="tooltip"]').tooltip(); +//日期范围 +laydate.render({ + elem: '#time_choose', + value:'', + range:true, + done:function(value, startDate, endDate){ + if(!value){ + return false; + } + + $('#search_time button').each(function(){ + $(this).removeClass('cur'); + }); + + var timeA = value.split('-') + var start = $.trim(timeA[0]+'-'+timeA[1]+'-'+timeA[2]) + var end = $.trim(timeA[3]+'-'+timeA[4]+'-'+timeA[5]) + query_txt = toUnixTime(start + " 00:00:00") + "-"+ toUnixTime(end + " 00:00:00") + + $('#time_choose').attr("data-name",query_txt); + $('#time_choose').addClass("cur"); + + wsOverviewRequest(1); + }, +}); + +$('#ov_refresh').click(function(){ + wsOverviewRequest(1); +}); + +$('#time_order button:eq(0)').addClass('cur'); +$('#time_order button').click(function(){ + $('#time_order button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $(this).addClass('cur'); + wsOverviewRequest(1); +}); + + + +$('#search_time button:eq(0)').addClass('cur'); +$('#search_time button').click(function(){ + $('#search_time button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $('#time_choose').attr("data-name",''); + $('#time_choose').removeClass("cur"); + + $(this).addClass('cur'); + + wsOverviewRequest(1); +}); + + +function initRealtimeTraffic(){ + var check_realtime_second = $('#check_realtime_second').val(); + if (check_realtime_second<1){ + check_realtime_second = 1; + $('#check_realtime_second').val(check_realtime_second); + } + + if (check_realtime_second>10){ + check_realtime_second = 10; + $('#check_realtime_second').val(check_realtime_second); + } + var title = "每秒请求"; + if (check_realtime_second > 1){ + title = '每'+check_realtime_second+'秒请求' + } + + $('#ov_title_req_second').text(title) + $('.check_realtime_request').text(title); +} + + +initRealtimeTraffic(); +$('#check_realtime_second').change(function(){ + initRealtimeTraffic(); +}); + +$('.indicators-container input[type=radio]').click(function(){ + $('.indicators-container input[type=radio]').each(function(){ + $(this).removeAttr('checked'); + }); + $(this).prop({'checked':true}); + + wsOverviewRequest(1); +}); + +wsPost('get_default_site','',{},function(rdata){ + $('select[name="site"]').html(''); + + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + var default_site = rdata["default"]; + var select = ''; + for (var i = 0; i < rdata["list"].length; i++) { + if (default_site == rdata["list"][i]){ + select += ''; + } else{ + select += ''; + } + } + $('select[name="site"]').html(select); + wsOverviewRequest(1); + + $('select[name="site"]').change(function(){ + wsOverviewRequest(1); + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +} + + +function wsSitesListRequest(page){ + + var args = {}; + var query_date = 'today'; + if ($('#time_choose').attr("data-name") != ''){ + query_date = $('#time_choose').attr("data-name"); + } else { + query_date = $('#search_time button.cur').attr("data-name"); + } + args['query_date'] = query_date; + + + wsPost('get_site_list', '' ,args, function(rdata){ + + var rdata = $.parseJSON(rdata.data); + var data = rdata.data; + + + var stat_pv = 0; + var stat_uv = 0; + var stat_ip = 0; + var stat_length = 0; + var stat_req = 0; + + // console.log(rdata, data); + var list = ''; + if (data.length > 0){ + for(i in data){ + + var tmp_pv = 0; + var tmp_uv = 0; + var tmp_ip = 0; + var tmp_length = 0; + var tmp_req = 0; + + if (data[i]['pv'] != null){ + tmp_pv = data[i]['pv']; + stat_pv += data[i]['pv']; + } + + if (data[i]['uv'] != null){ + tmp_uv = data[i]['uv']; + stat_uv += data[i]['uv']; + } + + if (data[i]['ip'] != null){ + tmp_ip = data[i]['ip']; + stat_ip += data[i]['ip']; + } + + if (data[i]['length'] != null){ + tmp_length = data[i]['length']; + stat_length += data[i]['length']; + } + + if (data[i]['req'] != null){ + tmp_req = data[i]['req']; + stat_req += data[i]['req']; + } + + list += ''; + list += '' + data[i]['site']+''; + list += '' + tmp_pv +''; + list += '' + tmp_uv +''; + list += '' + tmp_ip +''; + list += '' + tmp_req +''; + list += '' + toSize(tmp_length) +''; + list += '设置'; + list += ''; + } + } else{ + list += '网站列表为空'; + } + + $('.overview_list .overview_box:eq(0) .ov_num').text(stat_pv); + $('.overview_list .overview_box:eq(1) .ov_num').text(stat_uv); + $('.overview_list .overview_box:eq(2) .ov_num').text(stat_ip); + $('.overview_list .overview_box:eq(3) .ov_num').text(toSize(stat_length)); + $('.overview_list .overview_box:eq(4) .ov_num').text(stat_req); + + var table = '
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ + \\ + \ + '+ list +'\ +
                                      网站流览量访客数IP数请求数总流量操作
                                      \ +
                                      \ +
                                      '; + $('#ws_table').html(table); + + + $(".tablescroll .web_set").click(function(){ + var index = $(this).attr('data-id'); + + var domain = data[index]["site"]; + wsPost('get_site_conf', '' ,{"site":domain}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + console.log(rdata); + layer.open({ + type: 1, + title: "【"+domain + "】监控配置", + btn: ['保存','取消'], + area: ['600px',"380px"], + closeBtn: 1, + shadeClose: false, + content: '
                                      \ +
                                      \ +
                                      \ + CDN headers\ + 排除扩展\ + 排除响应状态\ + 排除路径\ + 排除IP\ + 记录请求原文\ +
                                      \ +
                                      \ + * 准确识别CDN网络IP地址,请注意大小写,如需多个请换行填写\ + \ +
                                      \ +
                                      \ +
                                      ', + success:function(){ + var common_tpl_tips = '* 准确识别CDN网络IP地址,请注意大小写,如需多个请换行填写'; + var common_tpl_area = ''; + + + $('#site_conf .tab-con textarea').text(rdata['cdn_headers'].join('\n')); + $('#site_conf .tab-nav span').click(function(e){ + $('#site_conf .tab-nav span').removeClass('on'); + $(this).addClass('on'); + $('#site_conf .tab-con').html(''); + + var typename = $(this).attr('data-type'); + if (typename == 'cdn_headers'){ + var content = $(common_tpl_tips).html('* 准确识别CDN网络IP地址,请注意大小写,如需多个请换行填写').prop('outerHTML'); + var area = $(common_tpl_area).html(rdata['cdn_headers'].join('\n')).prop('outerHTML'); + + content += area; + $('#site_conf .tab-con').html(content); + } else if (typename == 'exclude_extension'){ + + var content = $(common_tpl_tips).html('* 排除的请求不写入网站日志,不统计PV、UV、IP,只累计总请求、总流量数,如需多个请换行填写').prop('outerHTML'); + var area = $(common_tpl_area).html(rdata['exclude_extension'].join('\n')).prop('outerHTML'); + content += area; + $('#site_conf .tab-con').html(content); + } else if (typename == 'exclude_status'){ + var content = $(common_tpl_tips).html('* 排除的请求不写入网站日志,不统计PV、UV、IP,只累计总请求、总流量数,如需多个请换行填写').prop('outerHTML'); + var area = $(common_tpl_area).html(rdata['exclude_status'].join('\n')).prop('outerHTML'); + content += area; + $('#site_conf .tab-con').html(content); + } else if (typename == 'exclude_ip'){ + var txt = '
                                      * 排除的IP不写入网站日志,不统计PV、UV、IP,只累计总请求、总流量数,如需多个请换行填写
                                      \ +
                                      * 支持 192.168.1.1-192.168.1.10格式排除区间IP
                                      ' + var content = $(common_tpl_tips).html(txt).prop('outerHTML'); + var area = $(common_tpl_area).html(rdata['exclude_ip'].join('\n')).prop('outerHTML'); + content += area; + $('#site_conf .tab-con').html(content); + } else if (typename == 'record_post_args'){ + var txt = '
                                      记录请求原文说明:HTTP请求原文包括客户端请求详细参数,有助于分析或排查异常请求;
                                      \ +
                                      考虑到HTTP请求原文会占用额外存储空间,默认仅记录500错误请求原文。
                                      ' + var content = $(common_tpl_tips).html(txt).prop('outerHTML'); + + var record_post_args = ''; + if (rdata['record_post_args']){ + record_post_args = 'checked'; + } + var record_get_403_args = ''; + if (rdata['record_get_403_args']){ + record_get_403_args = 'checked'; + } + + + var check = '
                                      \ + \ + \ +
                                      '; + content+=check; + + $('#site_conf .tab-con').html(content); + } else if ( typename == 'exclude_url'){ + var txt = '* 排除的请求不写入网站日志,不统计PV、UV、IP,只累计总请求、总流量数' + var content = $(common_tpl_tips).html(txt).prop('outerHTML'); + + var _text = ''; + var _tmp = rdata['exclude_url']; + for(var i = 0; i<10; i++){ + if(typeof _tmp[i] == 'undefined'){ + _tmp[i] = {mode:'regular',url:''} + } + + _text += '\ + \ + \ + \ + \ + '; + } + + var list = '
                                      \ + \ + \ + \ + \ + '+_text+'\ +
                                      排除方式排除路径
                                      \ +
                                      '; + + content += list; + $('#site_conf .tab-con').html(content); + } + }); + }, + yes:function(){ + var select_pos = 0; + $('#site_conf .tab-nav span').each(function(i){ + if ($(this).hasClass('on')){select_pos = i;} + }); + var args = {"site":domain}; + if ( [0,1,2,4].indexOf(select_pos)>-1 ){ + var setting_cdn = $('textarea[name="setting-cdn"]').val(); + // var list = setting_cdn.split('\n') + if ( select_pos == 0 ){ + args['cdn_headers'] = setting_cdn; + } else if ( select_pos == 1 ){ + args['exclude_extension'] = setting_cdn; + } else if ( select_pos == 2 ){ + args['exclude_status'] = setting_cdn; + } else if ( select_pos == 4 ){ + args['exclude_ip'] = setting_cdn; + } + + wsPost('set_site_conf','', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + }); + } + + if (select_pos == 3 ){ + + var list = ""; + for (var i = 0; i<10; i++) { + var tmp = ""; + var url_type = $('select[name="url_type_'+i+'"]').val(); + var url_val = $('input[name="url_val_'+i+'"]').val(); + + if (url_val != ""){ + list += url_type +'|' + url_val +';'; + } + } + args['exclude_url'] = list; + wsPost('set_site_conf','', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + }); + } + + if (select_pos == 5){ + var record_post_args = $('input[name="record_post_args"]').prop('checked'); + var record_get_403_args = $('input[name="record_get_403_args"]').prop('checked'); + args["record_post_args"] = record_post_args; + args['record_get_403_args'] = record_get_403_args; + wsPost('set_site_conf','', args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + }); + } + }, + }); + }); + }); + }); +} + + +function wsSitesList(){ +//////////////////////////////////////////////////////////////////////////////////////////////////////// +var randstr = getRandomString(10); + +var html = '
                                      \ +
                                      \ +
                                      \ +
                                      \ + \ + \ + \ + \ +
                                      \ + \ +
                                      \ +
                                      \ + \ +
                                      \ +
                                      \ +

                                      总浏览量(PV)

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      总访客量(UV)

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      总IP数

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      总流量

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +

                                      总请求

                                      \ +

                                      0

                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      '; +$(".soft-man-con").html(html); +$('[data-toggle="tooltip"]').tooltip(); +//日期范围 +laydate.render({ + elem: '#time_choose', + value:'', + range:true, + done:function(value, startDate, endDate){ + if(!value){ + return false; + } + + $('#search_time button').each(function(){ + $(this).removeClass('cur'); + }); + + var timeA = value.split('-') + var start = $.trim(timeA[0]+'-'+timeA[1]+'-'+timeA[2]) + var end = $.trim(timeA[3]+'-'+timeA[4]+'-'+timeA[5]) + query_txt = toUnixTime(start + " 00:00:00") + "-"+ toUnixTime(end + " 00:00:00") + + $('#time_choose').attr("data-name",query_txt); + $('#time_choose').addClass("cur"); + + wsSitesListRequest(1); + }, +}); + + +$('#time_order button:eq(0)').addClass('cur'); +$('#time_order button').click(function(){ + $('#time_order button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $(this).addClass('cur'); + wsSitesListRequest(1); +}); + + + +$('#search_time button:eq(0)').addClass('cur'); +$('#search_time button').click(function(){ + $('#search_time button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $('#time_choose').attr("data-name",''); + $('#time_choose').removeClass("cur"); + + $(this).addClass('cur'); + + wsSitesListRequest(1); +}); + +wsSitesListRequest(1); +//////////////////////////////////////////////////////////////////////////////////////////////////////// +} + + +function wsSpiderStatLogRequest(page){ + + var args = {}; + args['page'] = page; + args['page_size'] = 10; + + args['site'] = $('select[name="site"]').val(); + args['status_code'] = $('select[name="status_code"]').val(); + + var query_date = 'today'; + if ($('#time_choose').attr("data-name") != ''){ + query_date = $('#time_choose').attr("data-name"); + } else { + query_date = $('#search_time button.cur').attr("data-name"); + } + args['query_date'] = query_date; + + args['tojs'] = 'wsSpiderStatLogRequest'; + wsPost('get_spider_stat_list', '' ,args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var list = ''; + var data = rdata.data.data; + if (data.length > 0){ + for(i in data){ + list += ''; + list += '' + data[i]['time']+''; + list += '' + data[i]['baidu'] +''; + list += '' + data[i]['bing'] +''; + list += '' + data[i]['qh360'] +''; + list += '' + data[i]['google'] +''; + list += '' + data[i]['bytes'] +''; + list += '' + data[i]['sogou'] +''; + list += '' + data[i]['soso'] +''; + list += '' + data[i]['youdao'] +''; + list += '' + data[i]['youdao'] +''; + list += '' + data[i]['dnspod'] +''; + list += '' + data[i]['yandex'] +''; + list += '' + data[i]['other'] +''; + list += '' + data[i]['other'] +''; + list += ''; + } + } else{ + list += '蜘蛛列表为空'; + } + + var table = '
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \\ + \ + '+ list +'\ +
                                      日期百度必应奇虎360Google头条搜狗搜搜神马有道DNSPODYandex其他 ?操作
                                      \ +
                                      \ +
                                      '; + $('#ws_table').html(table); + $('#wsPage').html(rdata.data.page); + $('[data-toggle="tooltip"]').tooltip(); + + var sumData = rdata.data.sum_data; + + var percent = ((sumData.spider/sumData.reqest_total)*100).toFixed(); + + $('#spider_left_total .request_spider').text(sumData.spider+"("+percent+"%)"); + $('#spider_left_total .request_total').text(sumData.reqest_total); + + // 图形化 + var initData = rdata.data.stat_list; + + var colorList = ['#6ec71e','#4885FF']; + var source_name = {baidu:'百度',google:'Google',bytes:'头条',soso:'搜搜',bing:'必应',qh360:'奇虎360',youdao:'有道',yandex:'Yandex',dnspod:'DNSPOD',mpcrawler:'mpcrawler',other:'其他',}; + var lenend2_obj = {}; + + var rightEc = echarts.init(document.getElementById('echart_right_total')); + + var xAxixName = $('#search_time button.cur').text(); + var is_compare = false; + + var lenend = []; + var serData = []; + for(var i = 0;i (is_compare?2:4)) { + lenend2_obj[lenend[i]] = false; + } else { + lenend2_obj[lenend[i]] = true; + } + } + + var rightOption = { + backgroundColor:'#fff', + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' , + textStyle: { + color: '#fff', + fontSize: '26' + }, + } + }, + legend: { + top:'0%', + data: lenend, + selected:lenend2_obj, + textStyle:{ + fontSize:12, + color:'#808080' + }, + icon:'rect' + }, + grid: { + top:60, + left:60, + right:0, + bottom:50 + }, + xAxis: [{ + type: 'category', + axisLabel:{ + color:'#4D4D4D', + fontSize:14, + fontWeight:'bold' + }, + data: [xAxixName], + }], + color:['#4fa8f9', '#6ec71e', '#f56e6a', '#fc8b40', '#818af8', '#31c9d7', '#f35e7a', '#ab7aee', + '#14d68b', '#cde5ff'], + yAxis: [{ + type: 'value', + axisLine: { + show: false, + }, + axisTick: { + show: false + }, + splitNumber:4, //y轴分割线数量 + axisLabel:{ + color:'#8C8C8C' + }, + splitLine:{ + lineStyle:{ + type:'dashed' + } + } + }], + series: serData + } + + + rightEc.setOption(rightOption); + + var oop = lenend.slice(0, (is_compare?3:5)); + rightEc.on('legendselectchanged', function (params) { + var legend_option = this.getOption(),newAxisName = []; + $.each(legend_option['xAxis'][0]['data'],function(index,item){ + newAxisName.push(item.replace(/\([^\)]*\)/g,"")) + }) + legend_option['xAxis'][0]['data'] = newAxisName; + + var num = 0; + for(var e in params.selected){ + if(params.selected.hasOwnProperty(e)){ + params.selected[e]? num++ : ''; + } + } + if(num > (is_compare?3:5)){ + oop.push(params.name) + } + if (num > (is_compare?3:5)) { + var hah = oop.slice(oop.length - (is_compare?4:6), oop.length - (is_compare?3:4))[0] + ''; + legend_option.legend[0].selected[hah] = false; + } + if (num < 1){ + legend_option.legend[0].selected[params.name] = true; + } + this.setOption(legend_option); + }); + }); +} + + +function wsSpiderStat(){ +//////////////////////////////////////////////////////////////////////////////////////////////////////// +var randstr = getRandomString(10); + +var html = '
                                      \ +
                                      \ + 网站: \ + \ + 时间: \ +
                                      \ +
                                      \ + \ + \ + \ + \ +
                                      \ + \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +

                                      总蜘蛛

                                      0

                                      \ +

                                      总请求

                                      0

                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      '; +$(".soft-man-con").html(html); + +//日期范围 +laydate.render({ + elem: '#time_choose', + value:'', + range:true, + done:function(value, startDate, endDate){ + if(!value){ + return false; + } + + $('#search_time button').each(function(){ + $(this).removeClass('cur'); + }); + + var timeA = value.split('-') + var start = $.trim(timeA[0]+'-'+timeA[1]+'-'+timeA[2]) + var end = $.trim(timeA[3]+'-'+timeA[4]+'-'+timeA[5]) + query_txt = toUnixTime(start + " 00:00:00") + "-"+ toUnixTime(end + " 00:00:00") + + $('#time_choose').attr("data-name",query_txt); + $('#time_choose').addClass("cur"); + + wsSpiderStatLogRequest(1); + }, +}); + +$('#search_time button:eq(0)').addClass('cur'); +$('#search_time button').click(function(){ + $('#search_time button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $('#time_choose').attr("data-name",''); + $('#time_choose').removeClass("cur"); + + $(this).addClass('cur'); + + wsSpiderStatLogRequest(1); +}); + + +$('select[name="status_code"]').change(function(){ + wsSpiderStatLogRequest(1); +}); + +wsPost('get_default_site','',{},function(rdata){ + $('select[name="site"]').html(''); + + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + var default_site = rdata["default"]; + var select = ''; + for (var i = 0; i < rdata["list"].length; i++) { + if (default_site == rdata["list"][i]){ + select += ''; + } else{ + select += ''; + } + } + $('select[name="site"]').html(select); + wsSpiderStatLogRequest(1); + + $('select[name="site"]').change(function(){ + wsSpiderStatLogRequest(1); + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +} + + +function wsClientStatLogRequest(page){ + + var args = {}; + args['page'] = page; + args['page_size'] = 10; + + args['site'] = $('select[name="site"]').val(); + args['status_code'] = $('select[name="status_code"]').val(); + + var query_date = 'today'; + if ($('#time_choose').attr("data-name") != ''){ + query_date = $('#time_choose').attr("data-name"); + } else { + query_date = $('#search_time button.cur').attr("data-name"); + } + args['query_date'] = query_date; + + args['tojs'] = 'wsClientStatLogRequest'; + wsPost('get_client_stat_list', '' ,args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var list = ''; + var data = rdata.data.data; + if (data.length > 0){ + for(i in data){ + list += ''; + list += '' + data[i]['time']+''; + list += '' + data[i]['android'] +''; + list += '' + data[i]['iphone'] +''; + list += '' + data[i]['windows'] +''; + list += '' + data[i]['chrome'] +''; + list += '' + data[i]['weixin'] +''; + list += '' + data[i]['qh360'] +''; + list += '' + data[i]['edeg'] +''; + list += '' + data[i]['firefox'] +''; + list += '' + data[i]['safari'] +''; + list += '' + data[i]['mac'] +''; + list += '' + data[i]['msie'] +''; + list += '' + data[i]['machine'] +''; + list += '' + data[i]['other'] +''; + list += ''; + } + } else{ + list += '客服端列表为空'; + } + + var table = '
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \\ + \ + '+ list +'\ +
                                      日期安桌iOSWindowsChrome微信360Edge火狐SafariMacIE机器 ?其他
                                      \ +
                                      \ +
                                      '; + $('#ws_table').html(table); + $('#wsPage').html(rdata.data.page); + $('[data-toggle="tooltip"]').tooltip(); + + + // 图形化 + var initData = rdata.data.stat_list; + var sumData = rdata.data.sum_data; + var colorList = ['#6ec71e','#4885FF']; + var source_name = {android:'安卓',iphone:'iOS',windows:'Windows',chrome:'Chrome',weixin:'微信',qh360:'360',edeg:'Edge',firefox:'火狐',safari:'Safari',mac:'Mac',linux:'Linux',msie:'IE',metasr:'搜狗',theworld:'世界之窗',tt:'腾讯TT',maxthon:'遨游',opera:'Opera',qq:'QQ浏览器',uc:'UC',pc2345:'2345',other:'其他',machine:'Machine'}; + var lenend2_obj = {}; + + var leftEc = echarts.init(document.getElementById('echart_left_total')); + var rightEc = echarts.init(document.getElementById('echart_right_total')); + + + var datas = [ + { value: sumData.pc, name: 'PC客服端' }, + { value: sumData.mobile, name: '移动客服端' }, + ]; + + var leftOption = { + backgroundColor:'#fff', + title: { + text: sumData.reqest_total, + textStyle: { + color: '#484848', + fontSize: 17 + }, + subtext: '总请求数', + subtextStyle: { + color: '#717171', + fontSize: 15 + }, + itemGap: 20, + left: 'center', + top: '42%' + }, + tooltip: { + trigger: 'item' + }, + series: [{ + type: 'pie', + radius: ['45%', '55%'], + center: ["50%", "50%"], + clockwise: true, + avoidLabelOverlap: false, + hoverOffset: 15, + itemStyle: { + normal: { + label: { + show: true, + position: 'outside', + color: '#666', + formatter: function(params) { + var percent = 0; + var total = 0; + for (var i = 0; i < datas.length; i++) { + total += datas[i].value; + } + if(params.name !== '') { + return params.name + '\n' + '\n' + params.value + '/次'; + }else { + return ''; + } + }, + }, + labelLine: { + length: 20, + length2: 10 + }, + color: function(params) { + return colorList[params.dataIndex] + } + } + }, + data: datas + },{ + itemStyle: { + normal: { + color: '#F5F6FA', + } + }, + type: 'pie', + hoverAnimation: false, + radius: ['42%', '58%'], + center: ["50%", "50%"], + label: { + normal: { + show:false, + } + }, + data: [], + z:-1 + }] + } + leftEc.setOption(leftOption); + + var xAxixName = $('#search_time button.cur').text(); + var is_compare = false; + + var lenend = []; + var serData = []; + for(var i = 0;i (is_compare?2:4)) { + lenend2_obj[lenend[i]] = false; + } else { + lenend2_obj[lenend[i]] = true; + } + } + + var rightOption = { + backgroundColor:'#fff', + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' , + textStyle: { + color: '#fff', + fontSize: '26' + }, + } + }, + legend: { + top:'0%', + data: lenend, + selected:lenend2_obj, + textStyle:{ + fontSize:12, + color:'#808080' + }, + icon:'rect' + }, + grid: { + top:60, + left:60, + right:0, + bottom:50 + }, + xAxis: [{ + type: 'category', + axisLabel:{ + color:'#4D4D4D', + fontSize:14, + fontWeight:'bold' + }, + data: [xAxixName], + }], + color:['#4fa8f9', '#6ec71e', '#f56e6a', '#fc8b40', '#818af8', '#31c9d7', '#f35e7a', '#ab7aee', + '#14d68b', '#cde5ff'], + yAxis: [{ + type: 'value', + axisLine: { + show: false, + }, + axisTick: { + show: false + }, + splitNumber:4, //y轴分割线数量 + axisLabel:{ + color:'#8C8C8C' + }, + splitLine:{ + lineStyle:{ + type:'dashed' + } + } + }], + series: serData + } + + + rightEc.setOption(rightOption); + + var oop = lenend.slice(0, (is_compare?3:5)); + rightEc.on('legendselectchanged', function (params) { + var legend_option = this.getOption(),newAxisName = []; + $.each(legend_option['xAxis'][0]['data'],function(index,item){ + newAxisName.push(item.replace(/\([^\)]*\)/g,"")) + }) + legend_option['xAxis'][0]['data'] = newAxisName; + + var num = 0; + for(var e in params.selected){ + if(params.selected.hasOwnProperty(e)){ + params.selected[e]? num++ : ''; + } + } + if(num > (is_compare?3:5)){ + oop.push(params.name) + } + if (num > (is_compare?3:5)) { + var hah = oop.slice(oop.length - (is_compare?4:6), oop.length - (is_compare?3:4))[0] + ''; + legend_option.legend[0].selected[hah] = false; + } + if (num < 1){ + legend_option.legend[0].selected[params.name] = true; + } + this.setOption(legend_option) + }); + }); +} + + +function wsClientStat(){ +//////////////////////////////////////////////////////////////////////////////////////////////////////// +var randstr = getRandomString(10); + +var html = '
                                      \ +
                                      \ + 网站: \ + \ + 时间: \ +
                                      \ +
                                      \ + \ + \ + \ + \ +
                                      \ + \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      '; +$(".soft-man-con").html(html); + +//日期范围 +laydate.render({ + elem: '#time_choose', + value:'', + range:true, + done:function(value, startDate, endDate){ + if(!value){ + return false; + } + + $('#search_time button').each(function(){ + $(this).removeClass('cur'); + }); + + var timeA = value.split('-') + var start = $.trim(timeA[0]+'-'+timeA[1]+'-'+timeA[2]) + var end = $.trim(timeA[3]+'-'+timeA[4]+'-'+timeA[5]) + query_txt = toUnixTime(start + " 00:00:00") + "-"+ toUnixTime(end + " 00:00:00") + + $('#time_choose').attr("data-name",query_txt); + $('#time_choose').addClass("cur"); + + wsClientStatLogRequest(1); + }, +}); + +$('#search_time button:eq(0)').addClass('cur'); +$('#search_time button').click(function(){ + $('#search_time button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $('#time_choose').attr("data-name",''); + $('#time_choose').removeClass("cur"); + + $(this).addClass('cur'); + + wsClientStatLogRequest(1); +}); + + +$('select[name="status_code"]').change(function(){ + wsClientStatLogRequest(1); +}); + +wsPost('get_default_site','',{},function(rdata){ + $('select[name="site"]').html(''); + + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + var default_site = rdata["default"]; + var select = ''; + for (var i = 0; i < rdata["list"].length; i++) { + if (default_site == rdata["list"][i]){ + select += ''; + } else{ + select += ''; + } + } + $('select[name="site"]').html(select); + wsClientStatLogRequest(1); + + $('select[name="site"]').change(function(){ + wsClientStatLogRequest(1); + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +} + + + +function wsIpStatLogRequest(page){ + + var args = {} + args['site'] = $('select[name="site"]').val(); + var query_date = 'today'; + query_date = $('#search_time button.cur').attr("data-name"); + args['query_date'] = query_date; + + args['tojs'] = 'wsIpStatLogRequest'; + wsPost('get_ip_stat_list', '' ,args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var list = ''; + var data = rdata.data; + // console.log(rdata,data); + if (data.length > 0){ + for(i in data){ + list += ''; + list += '' + (parseInt(i)+1)+''; + list += '' + data[i]['ip']+''; + list += '' + data[i]['area'] +''; + list += '' + data[i]['day'] +'('+data[i]['day_rate']+'%)'; + list += '' + toSize(data[i]['flow']) +'('+data[i]['flow_rate']+'%)'; + list += '' +''; + list += ''; + } + + + } else{ + list += 'IP列表为空'; + } + + var table = '
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                      序号IP归属地(仅供参考)请求数流量流量占比图
                                      \ +
                                      \ +
                                      '; + $('#ws_table').html(table); + }); +} + + +function wsIpStat(){ +//////////////////////////////////////////////////////////////////////////////////////////////////////// +var randstr = getRandomString(10); + +var html = '
                                      \ +
                                      \ + 网站: \ + \ + 时间: \ +
                                      \ +
                                      \ + \ + \ + \ + \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      '; +$(".soft-man-con").html(html); + + +$('#search_time button:eq(0)').addClass('cur'); +$('#search_time button').click(function(){ + $('#search_time button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $('#time_choose').attr("data-name",''); + $('#time_choose').removeClass("cur"); + + $(this).addClass('cur'); + + wsIpStatLogRequest(1); +}); + +wsPost('get_default_site','',{},function(rdata){ + $('select[name="site"]').html(''); + + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + var default_site = rdata["default"]; + var select = ''; + for (var i = 0; i < rdata["list"].length; i++) { + if (default_site == rdata["list"][i]){ + select += ''; + } else{ + select += ''; + } + } + $('select[name="site"]').html(select); + wsIpStatLogRequest(1); + + $('select[name="site"]').change(function(){ + wsIpStatLogRequest(1); + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +} + + +function wsUriStatLogRequest(page){ + + var args = {} + args['site'] = $('select[name="site"]').val(); + var query_date = 'today'; + query_date = $('#search_time button.cur').attr("data-name"); + args['query_date'] = query_date; + + args['tojs'] = 'wsUriStatLogRequest'; + wsPost('get_uri_stat_list', '' ,args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var list = ''; + var data = rdata.data; + // console.log(rdata,data); + if (data.length > 0){ + for(i in data){ + list += ''; + list += '' + (parseInt(i)+1)+''; + list += '' + data[i]['uri']+''; + list += '' + data[i]['day'] +'('+data[i]['day_rate']+'%)'; + list += '' + toSize(data[i]['flow']) +'('+data[i]['flow_rate']+'%)'; + list += '' +''; + list += ''; + } + + + } else{ + list += 'URI列表为空'; + } + + var table = '
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                      序号URI请求数流量流量占比图
                                      \ +
                                      \ +
                                      '; + $('#ws_table').html(table); + }); +} + + +function wsUriStat(){ +//////////////////////////////////////////////////////////////////////////////////////////////////////// +var randstr = getRandomString(10); + +var html = '
                                      \ +
                                      \ + 网站: \ + \ + 时间: \ +
                                      \ +
                                      \ + \ + \ + \ + \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      '; +$(".soft-man-con").html(html); + + +$('#search_time button:eq(0)').addClass('cur'); +$('#search_time button').click(function(){ + $('#search_time button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $('#time_choose').attr("data-name",''); + $('#time_choose').removeClass("cur"); + + $(this).addClass('cur'); + + wsUriStatLogRequest(1); +}); + +wsPost('get_default_site','',{},function(rdata){ + $('select[name="site"]').html(''); + + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + var default_site = rdata["default"]; + var select = ''; + for (var i = 0; i < rdata["list"].length; i++) { + if (default_site == rdata["list"][i]){ + select += ''; + } else{ + select += ''; + } + } + $('select[name="site"]').html(select); + wsUriStatLogRequest(1); + + $('select[name="site"]').change(function(){ + wsUriStatLogRequest(1); + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +} + + + + + +function wsTableErrorLogRequest(page){ + + var args = {}; + args['page'] = page; + args['page_size'] = 10; + + args['site'] = $('select[name="site"]').val(); + args['status_code'] = $('select[name="status_code"]').val(); + + var query_date = 'today'; + if ($('#time_choose').attr("data-name") != ''){ + query_date = $('#time_choose').attr("data-name"); + } else { + query_date = $('#search_time button.cur').attr("data-name"); + } + args['query_date'] = query_date; + + args['tojs'] = 'wsTableErrorLogRequest'; + wsPost('get_logs_error_list', '' ,args, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var list = ''; + var data = rdata.data.data; + if (data.length > 0){ + for(i in data){ + list += ''; + list += '' + getLocalTime(data[i]['time'])+''; + list += '' + data[i]['domain'] +''; + list += '' + data[i]['ip'] +''; + list += '' + toSize(data[i]['body_length']) +''; + list += '' + toSecond(data[i]['request_time']) +''; + list += '' + data[i]['uri'] +''; + list += '' + data[i]['status_code']+'/' + data[i]['method'] +''; + list += '详情'; + list += ''; + } + } else{ + list += '错误日志为空'; + } + + var table = '
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                      时间域名IP响应耗时URL状态/类型操作
                                      \ +
                                      \ +
                                      '; + $('#ws_table').html(table); + $('#wsPage').html(rdata.data.page); + + + $(".tablescroll .details").click(function(){ + var index = $(this).attr('data-id'); + var res = data[index]; + layer.open({ + type: 1, + title: "【"+res.domain + "】详情信息", + area: '600px', + closeBtn: 1, + shadeClose: false, + content: '
                                      \ +
                                      \ + \ + \ + \\ +
                                      时间' + getLocalTime(res.time) + '真实IP' + res.ip + '客户端端口'+(res.client_port>0 && res.client_port != ''?res.client_port:'')+'
                                      类型' + res.method + '状态' + res.status_code + '响应大小' + toSize(res.body_length) + '
                                      \ +
                                      协议
                                      \ +
                                      ' + res.protocol + '
                                      \ +
                                      URL
                                      \ +
                                      ' + $('
                                      ').text(res.uri).html() + '
                                      \ +
                                      完整IP列表
                                      \ +
                                      ' + $('
                                      ').text(res.ip_list).html() + '
                                      \ +
                                      来路
                                      \ +
                                      ' + $('
                                      ').text(res.referer == null ?'None':res.referer).html() + '
                                      \ +
                                      User-Agent
                                      \ +
                                      ' + $('
                                      ').text(res.user_agent).html() + '
                                      \ +
                                      处理耗时
                                      \ +
                                      ' +res.request_time + ' ms
                                      \ +
                                      ', + }); + }); + }); +} + + +function wsSitesErrorLog(){ +//////////////////////////////////////////////////////////////////////////////////////////////////////// +var randstr = getRandomString(10); + +var html = '
                                      \ +
                                      \ + 网站: \ + \ + 状态码: \ + \ + 时间: \ +
                                      \ +
                                      \ + \ + \ + \ + \ +
                                      \ + \ +
                                      \ +
                                      \ +
                                      \ +
                                      '; +$(".soft-man-con").html(html); + +//日期范围 +laydate.render({ + elem: '#time_choose', + value:'', + range:true, + done:function(value, startDate, endDate){ + if(!value){ + return false; + } + + $('#search_time button').each(function(){ + $(this).removeClass('cur'); + }); + + var timeA = value.split('-'); + var start = $.trim(timeA[0]+'-'+timeA[1]+'-'+timeA[2]) + var end = $.trim(timeA[3]+'-'+timeA[4]+'-'+timeA[5]) + query_txt = toUnixTime(start + " 00:00:00") + "-"+ toUnixTime(end + " 00:00:00") + + $('#time_choose').attr("data-name",query_txt); + $('#time_choose').addClass("cur"); + + wsTableErrorLogRequest(1); + }, +}); + +$('#search_time button:eq(0)').addClass('cur'); +$('#search_time button').click(function(){ + $('#search_time button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $('#time_choose').attr("data-name",''); + $('#time_choose').removeClass("cur"); + + $(this).addClass('cur'); + + wsTableErrorLogRequest(1); +}); + + +$('select[name="status_code"]').change(function(){ + wsTableErrorLogRequest(1); +}); + +wsPost('get_default_site','',{},function(rdata){ + $('select[name="site"]').html(''); + + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + var default_site = rdata["default"]; + var select = ''; + for (var i = 0; i < rdata["list"].length; i++) { + if (default_site == rdata["list"][i]){ + select += ''; + } else{ + select += ''; + } + } + $('select[name="site"]').html(select); + wsTableErrorLogRequest(1); + + $('select[name="site"]').change(function(){ + wsTableErrorLogRequest(1); + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +} + + +function wsTableLogRequest(page){ + + var args = {}; + args['page'] = page; + args['page_size'] = 9; + + args['site'] = $('select[name="site"]').val(); + args['method'] = $('select[name="method"]').val(); + args['status_code'] = $('select[name="status_code"]').val(); + args['request_time'] = $('select[name="request_time"]').val(); + args['request_size'] = $('select[name="request_size"]').val(); + args['spider_type'] = $('select[name="spider_type"]').val(); + args['referer'] = $('select[name="referer"]').val(); + args['ip'] = $('input[name="ip"]').val(); + + var query_date = 'today'; + if ($('#time_choose').attr("data-name") != ''){ + query_date = $('#time_choose').attr("data-name"); + } else { + query_date = $('#search_time button.cur').attr("data-name"); + } + args['query_date'] = query_date; + // console.log("query_date:",query_date); + + + var search_uri = $('input[name="search_uri"]').val(); + args['search_uri'] = search_uri; + + args['tojs'] = 'wsTableLogRequest'; + + var spider_table = { + "1":"百度", + "2":"必应", + "3":"奇虎360", + "4":"Google", + "5":"头条", + "6":"搜狗", + "7":"有道", + "8":"搜搜", + "9":"Dnspod", + "10":"Yandex", + "11":"一搜", + "12":"其他", + } + + var req_status = $('#logs_search').attr('req'); + // console.log(req_status); + if (typeof(req_status) != 'undefined'){ + if (req_status == 'start'){ + layer.msg("正在请求中,请稍候!"); + return; + } + } + + $('#logs_search').attr('req','start'); + // wsPost('get_logs_list', '' ,args, function(rdata){ + wsPostCallbak('get_logs_list', '' ,args, function(rdata){ + $('#logs_search').attr('req','end'); + var rdata = $.parseJSON(rdata.data); + var list = ''; + var data = rdata.data.data; + // console.log(data); + + if (data.length > 0){ + for(i in data){ + + var spider_tip = ''; + if (data[i]['is_spider']>0){ + spider_tip_name = spider_table[data[i]['is_spider']] + spider_tip = '
                                      '; + } + + list += ''; + list += '' + getLocalTime(data[i]['time'])+''; + list += '' + data[i]['domain'] +''; + list += '' + data[i]['ip'] +''; + list += '' + toSize(data[i]['body_length']) +''; + list += '' + toSecond(data[i]['request_time']) +''; + list += '' + data[i]['uri'] +''; + list += ''+spider_tip+'' + data[i]['status_code']+'/' + data[i]['method'] +''; + + var http_data = '            '; + if (data[i]['request_headers']!=''){ + http_data = 'HTTP | '; + } + list += ''+http_data+'详情'; + list += ''; + } + } else{ + list += '网站日志为空'; + } + + var table = '
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+ list +'\ +
                                      时间域名IP响应耗时URL状态/类型操作
                                      \ +
                                      \ +
                                      '; + $('#ws_table').html(table); + $('#wsPage').html(rdata.data.page); + + $(".tablescroll .details").click(function(){ + var index = $(this).attr('data-id'); + var res = data[index]; + layer.open({ + type: 1, + title: "【"+res.domain + "】详情信息", + area: '600px', + closeBtn: 1, + shadeClose: false, + content: '
                                      \ +
                                      \ + \ + \ + \\ +
                                      时间' + getLocalTime(res.time) + '真实IP' + res.ip + '客户端端口'+(res.client_port>0 && res.client_port != ''?res.client_port:'')+'
                                      类型' + res.method + '状态' + res.status_code + '响应大小' + toSize(res.body_length) + '
                                      \ +
                                      协议
                                      \ +
                                      ' + res.protocol + '
                                      \ +
                                      URL
                                      \ +
                                      ' + $('
                                      ').text(res.uri).html() + '
                                      \ +
                                      完整IP列表
                                      \ +
                                      ' + $('
                                      ').text(res.ip_list).html() + '
                                      \ +
                                      来路
                                      \ +
                                      ' + $('
                                      ').text(res.referer == null ?'None':res.referer).html() + '
                                      \ +
                                      User-Agent
                                      \ +
                                      ' + $('
                                      ').text(res.user_agent).html() + '
                                      \ +
                                      处理耗时
                                      \ +
                                      ' +res.request_time + ' ms
                                      \ +
                                      ', + }); + }); + + $(".tablescroll .http_data").click(function(){ + var index = $(this).attr('data-id'); + var res = data[index]; + var request_headers = res.request_headers; + + var req_data_html = res.method +' ' + res.uri + '
                                      '; + + try { + var req_data = $.parseJSON(request_headers); + for (var d in req_data) { + if (d == 'payload'){ + req_data_html += ''+d +":"+req_data[d]+"
                                      "; + } else{ + req_data_html += d+":"+req_data[d]+"
                                      "; + } + } + } catch (error) { + req_data_html += request_headers; + } + + + layer.open({ + type: 1, + title: "【"+res.domain + "】HTTP详情", + area: ['600px','375px'], + closeBtn: 1, + shadeClose: false, + content: '
                                      \ +
                                      ' + req_data_html + '
                                      \ +
                                        \ +
                                      • payload: POST请求中客户端提交的参数。
                                      • \ +
                                      \ +
                                      ', + }); + }); + + $('[data-toggle="tooltip"]').tooltip(); + }); +} + +function wsSitesLog(){ +//////////////////////////////////////////////////////////////////////////////////////////////////////// +var randstr = getRandomString(10); + +var html = '
                                      \ +
                                      \ + 网站: \ + \ + 时间: \ +
                                      \ +
                                      \ + \ + \ + \ + \ +
                                      \ + \ +
                                      \ +
                                      \ +
                                      \ + 请求类型: \ + \ + 状态码: \ + \ + 来源: \ + \ + 蜘蛛过滤: \ + \ + IP: \ +
                                      \ + \ +
                                      \ +
                                      \ +
                                      \ + 耗时: \ + \ + 大小: \ + \ + URL过滤: \ +
                                      \ + \ +
                                      \ + \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      '; +$(".soft-man-con").html(html); + +$('input[name="ip"]').bind('focus', function(e){ + $(this).keyup(function(e){ + if(e.keyCode == 13) { + wsTableLogRequest(1); + } + }); +}); + +$('input[name="search_uri"]').bind('focus', function(e){ + $(this).keyup(function(e){ + if(e.keyCode == 13) { + wsTableLogRequest(1); + } + }); +}); + +//日期范围 +laydate.render({ + elem: '#time_choose', + value:'', + range:'~', + type:'datetime', + done:function(value, startDate, endDate){ + console.log(value, startDate, endDate); + if(!value){ + return false; + } + + $('#search_time button').each(function(){ + $(this).removeClass('cur'); + }); + + var timeArr = value.split('~'); + var start = $.trim(timeArr[0]); + var end = $.trim(timeArr[1]); + query_txt = toUnixTime(start) + "-"+ toUnixTime(end); + + $('#time_choose').attr("data-name",query_txt); + $('#time_choose').addClass("cur"); + + wsTableLogRequest(1); + }, +}); + +$('#search_time button:eq(0)').addClass('cur'); +$('#search_time button').click(function(){ + $('#search_time button').each(function(){ + if ($(this).hasClass('cur')){ + $(this).removeClass('cur'); + } + }); + $('#time_choose').attr("data-name",''); + $('#time_choose').removeClass("cur"); + + $(this).addClass('cur'); + + wsTableLogRequest(1); +}); + +$('select[name="method"]').change(function(){ + wsTableLogRequest(1); +}); + +$('select[name="status_code"]').change(function(){ + wsTableLogRequest(1); +}); + +$('select[name="spider_type"]').change(function(){ + wsTableLogRequest(1); +}); + +$('select[name="referer"]').change(function(){ + wsTableLogRequest(1); +}); + +$('select[name="request_time"]').change(function(){ + wsTableLogRequest(1); +}); + +$('select[name="request_size"]').change(function(){ + wsTableLogRequest(1); +}); + +$('#logs_search').click(function(){ + wsTableLogRequest(1); +}); + +wsPost('get_default_site','',{},function(rdata){ + $('select[name="site"]').html(''); + + var rdata = $.parseJSON(rdata.data); + var rdata = rdata.data; + var default_site = rdata["default"]; + var select = ''; + for (var i = 0; i < rdata["list"].length; i++) { + if (default_site == rdata["list"][i]){ + select += ''; + } else{ + select += ''; + } + } + $('select[name="site"]').html(select); + wsTableLogRequest(1); + + $('select[name="site"]').change(function(){ + wsTableLogRequest(1); + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +} + + diff --git a/plugins/webstats/lua/webstats_common.lua b/plugins/webstats/lua/webstats_common.lua new file mode 100644 index 000000000..d73c0142c --- /dev/null +++ b/plugins/webstats/lua/webstats_common.lua @@ -0,0 +1,1220 @@ + +local setmetatable = setmetatable +local _M = { _VERSION = '1.0' } +local mt = { __index = _M } + +local json = require "cjson" +local sqlite3 = require "lsqlite3" +local config = require "webstats_config" +local sites = require "webstats_sites" + +local debug_mode = true +local total_key = "log_kv_total" + +local unset_server_name = "unset" +local max_log_id = 99999999999999 +local cache = ngx.shared.mw_total + +local today = ngx.re.gsub(ngx.today(),'-','') +local request_header = ngx.req.get_headers() +local method = ngx.req.get_method() + +local day = os.date("%d") +local number_day = tonumber(day) +local day_column = "day"..number_day +local flow_column = "flow"..number_day +local spider_column = "spider_flow"..number_day + +-- _M.setInputSn | need +local auto_config = nil + +local log_dir = "{$SERVER_APP}/logs" + +function _M.new(self) + local self = { + total_key = total_key, + params = nil, + site_config = nil, + config = nil, + } + -- self.dbs = {} + return setmetatable(self, mt) +end + + +-- function _M.getInstance(self) +-- if rawget(self, "instance") == nil then +-- rawset(self, "instance", self.new()) +-- self.cron() +-- end +-- assert(self.instance ~= nil) +-- return self.instance +-- end + + +function _M.getInstance(self) + if self.instance == nil then + self.instance = self:new() + self:cron() + end + assert(self.instance ~= nil) + return self.instance +end + + +function _M.initDB(self, input_sn) + local path = log_dir .. '/' .. input_sn .. "/logs.db" + db, err = sqlite3.open(path) + + if err then + return nil + end + + db:exec([[PRAGMA synchronous = 0]]) + db:exec([[PRAGMA cache_size = 8000]]) + db:exec([[PRAGMA page_size = 32768]]) + db:exec([[PRAGMA journal_mode = wal]]) + db:exec([[PRAGMA journal_size_limit = 21474836480]]) + return db +end + +function _M.getTotalKey(self) + return self.total_key +end + +function _M.to_json(self, msg) + return json.encode(msg) +end + +function _M.setConfData( self, config, site_config ) + self.config = config + self.site_config = site_config +end + +function _M.setParams( self, params ) + self.params = params +end + +function _M.setInputSn(self, input_sn) + local global_config = config["global"] + if config[input_sn] == nil then + auto_config = global_config + else + auto_config = config[input_sn] + for k, v in pairs(global_config) do + if auto_config[k] == nil then + auto_config[k] = v + end + end + end + return auto_config +end + +function _M.get_domain(self) + local domain = ngx.req.get_headers()['host'] + -- domain = ngx.re.gsub(domain, "_", ".") + if domain == nil then + domain = "unknown" + end + return domain +end + +function _M.split(self, str, reps) + local arr = {} + -- 修复反向代理代过来的数据 + if "table" == type(str) then + return str + end + string.gsub(str,'[^'..reps..']+',function(w) table.insert(arr,w) end) + return arr +end + +function _M.arrlen(self, arr) + if not arr then return 0 end + local count = 0 + for _,v in ipairs(arr) do + count = count + 1 + end + return count +end + +function _M.is_ipaddr(self, client_ip) + local cipn = self:split(client_ip,'.') + if self:arrlen(cipn) < 4 then return false end + for _,v in ipairs({1,2,3,4}) + do + local ipv = tonumber(cipn[v]) + if ipv == nil then return false end + if ipv > 255 or ipv < 0 then return false end + end + return true +end + + +function _M.get_sn(self, input_sn) + local dst_name = cache:get(input_sn) + if dst_name then return dst_name end + + -- self:D(json.encode(sites)) + for _,v in ipairs(sites) + do + if input_sn == v["name"] then + cache:set(input_sn, v['name'], 86400) + return v["name"] + end + -- self:D("get_sn:"..json.encode(v)) + for _,dst_domain in ipairs(v['domains']) + do + if input_sn == dst_domain then + cache:set(input_sn, v['name'], 86400) + return v['name'] + elseif string.find(dst_domain, "*") then + local new_domain = string.gsub(dst_domain, '*', '.*') + if string.find(input_sn, new_domain) then + dst_domain = v['name'] + cache:set(input_sn, dst_domain, 86400) + end + end + end + end + + cache:set(input_sn, unset_server_name, 86400) + return unset_server_name +end + + +function _M.get_store_key(self) + return os.date("%Y%m%d%H", ngx.time()) +end + +function _M.get_store_key_with_time(self, htime) + return os.date("%Y%m%d%H", htime) +end + +function _M.get_length(self) + local clen = ngx.var.body_bytes_sent + if clen == nil then clen = 0 end + return tonumber(clen) +end + +function _M.get_last_id(self, input_sn) + local last_insert_id_key = input_sn .. "_last_id" + local new_id, err = cache:incr(last_insert_id_key, 1, 0) + cache:incr(cache_count_id_key, 1, 0) + if new_id >= max_log_id then + cache:set(last_insert_id_key, 1) + new_id = cache:get(last_insert_id_key) + end + return new_id +end + +function _M.get_http_origin(self) + local data = "" + local headers = ngx.req.get_headers() + if not headers then return data end + local req_method = ngx.req.get_method() + if req_method ~='GET' then + + -- proxy_pass, fastcgi_pass, uwsgi_pass, and scgi_pass + data = ngx.var.request_body + if not data then + data = ngx.req.get_body_data() + end + + -- API disabled in the context of log_by_lua + -- if not data then + -- ngx.req.read_body() + -- data = ngx.req.get_post_args(1000000) + -- end + + if "string" == type(data) then + headers["payload"] = data + end + + if "table" == type(data) then + -- headers = table.concat(headers, data) + headers["payload"] = table.concat(data, "&") + end + end + return json.encode(headers) +end + +function _M.cronPre(self) + self:lock_working('cron_init_stat') + + local time_key = self:get_store_key() + local time_key_next = self:get_store_key_with_time(ngx.time()+3600) + + + for site_k, site_v in ipairs(sites) do + local input_sn = site_v["name"] + + local db = self:initDB(input_sn) + + local wc_stat = { + 'request_stat', + 'client_stat', + 'spider_stat' + } + + local v1 = true + local v2 = true + for _,ws_v in pairs(wc_stat) do + v1 = self:_update_stat_pre(db, ws_v, time_key) + v2 = self:_update_stat_pre(db, ws_v, time_key_next) + end + + if db and db:isopen() then + db:execute([[COMMIT]]) + db:close() + end + + if not v1 or not v2 then + return false + end + end + + self:unlock_working('cron_init_stat') + + return true +end + +-- 后台任务 +function _M.cron(self) + + local timer_every_get_data = function (premature) + + local llen, _ = ngx.shared.mw_total:llen(total_key) + -- self:D("PID:"..tostring(ngx.worker.id())..",llen:"..tostring(llen)) + if llen == 0 then + return true + end + + -- self:D("dedebide:cron task is busy!") + local ready_ok = self:cronPre() + if not ready_ok then + -- self:D("cron task is busy!") + return true + end + + local cron_key = 'cron_every_1s' + if self:is_working(cron_key) then + return true + end + + ngx.update_time() + local begin = ngx.now() + + local dbs = {} + local stmts = {} + local stat_fields = {} + local ip_stats = {} + local url_stats = {} + + local time_key = self:get_store_key() + + for site_k, site_v in ipairs(sites) do + local input_sn = site_v["name"] + -- self:D("input_sn:"..input_sn) + -- 迁移合并时不执行 + if self:is_migrating(input_sn) then + return true + end + + -- 初始化统计表时不执行 + if self:is_working('cron_init_stat') then + return true + end + + local db = self:initDB(input_sn) + + stat_fields[input_sn] = {} + + if db then + dbs[input_sn] = db + self:clean_stats(db,input_sn) + + local tmp_stmt = {} + local stmt = db:prepare[[INSERT INTO web_logs( + time, ip, domain, server_name, method, status_code, uri, body_length, + referer, user_agent, protocol, request_time, is_spider, request_headers, ip_list, client_port) + VALUES(:time, :ip, :domain, :server_name, :method, :status_code, :uri, + :body_length, :referer, :user_agent, :protocol, :request_time, :is_spider, + :request_headers, :ip_list, :client_port)]] + tmp_stmt["web_logs"] = stmt + stmts[input_sn] = tmp_stmt + + db:exec([[BEGIN TRANSACTION]]) + end + end + + self:lock_working(cron_key) + + -- 每秒100条 + for i=1,llen do + local data, _ = ngx.shared.mw_total:lpop(total_key) + if not data then + self:unlock_working(cron_key) + break + end + + local info = json.decode(data) + + -- self:D("info:"..json.encode(info)) + local input_sn = info['server_name'] + -- self:D("insert data input_sn:"..input_sn) + local db = dbs[input_sn] + local stat_fields_is = stat_fields[input_sn] + if not db then + ngx.shared.mw_total:rpush(total_key, data) + self:unlock_working(cron_key) + break + end + + local input_stmts = stmts[input_sn]["web_logs"] + if not input_stmts then + ngx.shared.mw_total:rpush(total_key, data) + self:unlock_working(cron_key) + break + end + + local insert_ok = self:store_logs_line(db, input_stmts, input_sn, info) + if not insert_ok then + ngx.shared.mw_total:rpush(total_key, data) + self:unlock_working(cron_key) + break + end + + local excluded = info["log_kv"]['excluded'] + local stat_tmp_fields = info['stat_fields'] + + -- 合并统计数据 + for stf_k,stf_v in pairs(stat_tmp_fields) do + -- 排除请求过滤 + if excluded then + if stf_k == "spider_stat_fields" or stf_k == "client_stat_fields" then + break + end + end + + if not stat_fields_is[stf_k] then + stat_fields_is[stf_k] = {} + end + + for sv_k,sv_v in pairs(stf_v) do + if not stat_fields_is[stf_k][sv_k] then + stat_fields_is[stf_k][sv_k] = sv_v + else + stat_fields_is[stf_k][sv_k] = stat_fields_is[stf_k][sv_k] + 1 + end + end + end + + stat_fields[input_sn] = stat_fields_is + -- ip 统计合并 + -- url 统计 + if not excluded then + local ip = info["log_kv"]['ip'] + local body_length = info["log_kv"]["body_length"] + + if not ip_stats[input_sn] then + ip_stats[input_sn] = {} + end + + if not ip_stats[input_sn][ip] then + local tmp = { + ip_num=1, + body_length=body_length + } + ip_stats[input_sn][ip] = tmp + else + ip_stats[input_sn][ip]["ip_num"] = ip_stats[input_sn][ip]["ip_num"]+1 + ip_stats[input_sn][ip]["body_length"] = ip_stats[input_sn][ip]["body_length"]+body_length + end + + + -- uri统计 + if not url_stats[input_sn] then + url_stats[input_sn] = {} + end + local request_uri = info["log_kv"]["request_uri"] + local request_uri_md5 = ngx.md5(request_uri) + if not url_stats[input_sn][request_uri_md5] then + local tmp = { + url_num=1, + uri=request_uri, + body_length=body_length + } + url_stats[input_sn][request_uri_md5] = tmp + else + url_stats[input_sn][request_uri_md5]["url_num"] = url_stats[input_sn][request_uri_md5]["url_num"]+1 + url_stats[input_sn][request_uri_md5]["body_length"] = url_stats[input_sn][request_uri_md5]["body_length"]+body_length + end + end + -- self:D("url_stats:.."..json.encode(url_stats)) + end + + for site_k, site_v in ipairs(sites) do + local input_sn = site_v["name"] + + if stmts[input_sn] then + for stmts_k,stmts_v in pairs(stmts[input_sn]) do + -- self:D("stmts_k:"..tostring(stmts_k)) + local res, err = stmts_v:finalize() + if tostring(res) == "5" then + self:D(stmts_k..":Finalize res:"..tostring(res)..",Finalize err:"..tostring(err)) + end + end + end + + + local stat_fields_is = stat_fields[input_sn] + local db = dbs[input_sn] + local local_ip_stats = ip_stats[input_sn] + local local_url_stats = url_stats[input_sn] + + if db then + -- 统计【spider_stat,client_stat,request_stat】 + for sti_k,sti_v in pairs(stat_fields_is) do + local vkk = "" + for sv_k,sv_v in pairs(sti_v) do + vkk = vkk..sv_k.."="..sv_k.."+"..sv_v.."," + end + if vkk ~= "" then + vkk = string.sub(vkk,1,string.len(vkk)-1) + end + if sti_k == 'request_stat_fields' and vkk ~= '' then + self:update_stat( db, "request_stat", time_key, vkk) + end + + if sti_k == 'client_stat_fields' and vkk ~= '' then + self:update_stat( db, "client_stat", time_key, vkk) + end + + if sti_k == 'spider_stat_fields' and vkk ~= '' then + self:update_stat( db, "spider_stat", time_key, vkk) + end + end + + -- ip统计 + if local_ip_stats then + for ip_addr,ip_val in pairs(local_ip_stats) do + self:update_statistics_ip( db, ip_addr,ip_val["ip_num"], ip_val["body_length"]) + end + end + + -- url统计 + if local_url_stats then + for url_md5,url_val in pairs(local_url_stats) do + self:update_statistics_uri( db, tostring(url_val["uri"]),url_md5, url_val["url_num"], url_val["body_length"]) + end + end + + -- delete expire data + local now_date = os.date("*t") + local save_day = config['global']["save_day"] + local save_date_timestamp = os.time{year=now_date.year, + month=now_date.month, day=now_date.day-save_day, hour=0} + + db:exec("DELETE FROM web_logs WHERE time<"..tostring(save_date_timestamp)) + end + + if db and db:isopen() then + db:execute([[COMMIT]]) + db:close() + end + end + + self:unlock_working(cron_key) + ngx.update_time() + -- self:D("PID:"..tostring(ngx.worker.id()).."--【"..tostring(llen).."】, elapsed: " .. tostring(ngx.now() - begin)) + end + + + function timer_every_get_data_try() + local presult, err = pcall( function() timer_every_get_data() end) + if not presult then + self:D("debug cron error on :"..tostring(err)) + return true + end + end + + ngx.timer.every(0.5, timer_every_get_data_try) +end + + +function _M.store_logs_line(self, db, stmt, input_sn, info) + local logline = info['log_kv'] + + local time = logline["time"] + local id = logline["id"] + local protocol = logline["protocol"] + local client_port = logline["client_port"] + local status_code = logline["status_code"] + local uri = logline["uri"] + local request_uri = logline["request_uri"] + local method = logline["method"] + local body_length = logline["body_length"] + local referer = logline["referer"] + local ip = logline["ip"] + local ip_list = logline["ip_list"] + local request_time = logline["request_time"] + local is_spider = logline["is_spider"] + local domain = logline["domain"] + local server_name = logline["server_name"] + local user_agent = logline["user_agent"] + local request_headers = logline["request_headers"] + local excluded = logline["excluded"] + local time_key = logline["time_key"] + + if "table" == type(user_agent) then + user_agent = self:to_json(user_agent) + end + + if not excluded then + stmt:bind_names { + time=time, + ip=ip, + domain=domain, + server_name=server_name, + method=method, + status_code=status_code, + uri=request_uri, + body_length=body_length, + referer=referer, + user_agent=user_agent, + protocol=protocol, + request_time=request_time, + is_spider=is_spider, + request_headers=request_headers, + ip_list=ip_list, + client_port=client_port, + } + + local res, err = stmt:step() + if tostring(res) == "5" then + -- self:D("json:"..json.encode(logline)) + -- self:D("the step database connection is busy, so it will be stored later | step res:"..tostring(res) ..",step err:"..tostring(err)) + if stmt then + stmt:reset() + end + return false + end + stmt:reset() + end + return true +end + +function _M.statistics_ipc(self, input_sn, ip) + -- 判断IP是否重复的时间限定范围是请求的当前时间+24小时 + local ipc = 0 + local ip_token = input_sn..'_'..ip + if not cache:get(ip_token) then + ipc = 1 + cache:set(ip_token,1, self:get_end_time()) + end + return ipc +end + +function _M.statistics_request(self, ip, is_spider, body_length) + -- 计算pv uv + local pvc = 0 + local uvc = 0 + local request_header = ngx.req.get_headers() + if not is_spider and ngx.status == 200 and body_length > 0 then + local ua = '' + if request_header['user-agent'] then + if "table" == type(request_header['user-agent']) then + ua = self:to_json(request_header['user-agent']) + else + ua = request_header['user-agent'] + end + ua = string.lower(ua) + end + + pvc = 1 + if ua then + local today = ngx.today() + local uv_token = ngx.md5(ip .. ua .. today) + if not cache:get(uv_token) then + uvc = 1 + cache:set(uv_token,1, self:get_end_time()) + end + end + end + return pvc, uvc +end + +-- 仅计算GET/HTML +function _M.statistics_request_old(self, ip, is_spider, body_length) + -- 计算pv uv + local pvc = 0 + local uvc = 0 + local method = ngx.req.get_method() + if not is_spider and method == 'GET' and ngx.status == 200 and body_length > 512 then + local ua = '' + if request_header['user-agent'] then + ua = string.lower(request_header['user-agent']) + end + + out_header = ngx.resp.get_headers() + if out_header['content-type'] then + if string.find(out_header['content-type'], 'text/html', 1, true) then + pvc = 1 + if request_header['user-agent'] then + if string.find(ua,'mozilla') then + local today = ngx.today() + local uv_token = ngx.md5(ip .. request_header['user-agent'] .. today) + if not cache:get(uv_token) then + uvc = 1 + cache:set(uv_token,1, self:get_end_time()) + end + end + end + end + end + end + return pvc, uvc +end + +--------------------- db start --------------------------- + + +function _M.update_statistics_uri(self, db, uri, uri_md5, day_num, body_length) + -- count the number of URI requests and traffic + local open_statistics_uri = config['global']["statistics_uri"] + if not open_statistics_uri then return true end + + local stat_sql = nil + stat_sql = "INSERT INTO uri_stat(uri_md5,uri) SELECT \""..uri_md5.."\",\""..uri.."\" WHERE NOT EXISTS (SELECT uri_md5 FROM uri_stat WHERE uri_md5=\""..uri_md5.."\");" + local res, err = db:exec(stat_sql) + + stat_sql = "UPDATE uri_stat SET "..day_column.."="..day_column.."+"..day_num..","..flow_column.."="..flow_column.."+"..body_length.." WHERE uri_md5=\""..uri_md5.."\";" + local res, err = db:exec(stat_sql) + return true +end + +function _M.statistics_uri(self, db, uri, uri_md5, body_length) + -- count the number of URI requests and traffic + local open_statistics_uri = config['global']["statistics_uri"] + if not open_statistics_uri then return true end + + local stat_sql = nil + stat_sql = "INSERT INTO uri_stat(uri_md5,uri) SELECT \""..uri_md5.."\",\""..uri.."\" WHERE NOT EXISTS (SELECT uri_md5 FROM uri_stat WHERE uri_md5=\""..uri_md5.."\");" + local res, err = db:exec(stat_sql) + + stat_sql = "UPDATE uri_stat SET "..day_column.."="..day_column.."+1,"..flow_column.."="..flow_column.."+"..body_length.." WHERE uri_md5=\""..uri_md5.."\";" + local res, err = db:exec(stat_sql) + return true +end + + +function _M.update_statistics_ip(self, db, ip, day_num ,body_length) + local open_statistics_ip = config['global']["statistics_ip"] + if not open_statistics_ip then return true end + + local stat_sql = nil + stat_sql = "INSERT INTO ip_stat(ip) SELECT \""..ip.."\" WHERE NOT EXISTS (SELECT ip FROM ip_stat WHERE ip=\""..ip.."\");" + local res, err = db:exec(stat_sql) + + stat_sql = "UPDATE ip_stat SET "..day_column.."="..day_column.."+"..day_num..","..flow_column.."="..flow_column.."+"..body_length.." WHERE ip=\""..ip.."\"" + local res, err = db:exec(stat_sql) + return true +end + +function _M.statistics_ip(self, db, ip, body_length) + local open_statistics_ip = config['global']["statistics_ip"] + if not open_statistics_ip then return true end + + local stat_sql = nil + stat_sql = "INSERT INTO ip_stat(ip) SELECT \""..ip.."\" WHERE NOT EXISTS (SELECT ip FROM ip_stat WHERE ip=\""..ip.."\");" + local res, err = db:exec(stat_sql) + + stat_sql = "UPDATE ip_stat SET "..day_column.."="..day_column.."+1,"..flow_column.."="..flow_column.."+"..body_length.." WHERE ip=\""..ip.."\"" + local res, err = db:exec(stat_sql) + return true +end + + +function _M._update_stat_pre(self, db, stat_table, key) + local local_sql = string.format("INSERT INTO %s(time) SELECT :time WHERE NOT EXISTS(SELECT time FROM %s WHERE time=:time);", stat_table, stat_table) + local update_stat_stmt = db:prepare(local_sql) + + if update_stat_stmt then + update_stat_stmt:bind_names{time=key} + update_stat_stmt:step() + update_stat_stmt:finalize() + return true + end + return false +end + +function _M.update_stat_quick(self, db, stat_table, key,columns) + if not columns then return end + local update_sql = "UPDATE ".. stat_table .. " SET " .. columns .. " WHERE time=" .. key + return db:exec(update_sql) +end + + +function _M.update_stat(self, db, stat_table, key, columns) + -- 根据指定表名,更新统计数据 + if not columns then return end + local local_sql = string.format("INSERT INTO %s(time) SELECT :time WHERE NOT EXISTS(SELECT time FROM %s WHERE time=:time);", stat_table, stat_table) + local stmt = db:prepare(local_sql) + stmt:bind_names{time=key} + local res, err = stmt:step() + stmt:finalize() + local update_sql = "UPDATE ".. stat_table .. " SET " .. columns .. " WHERE time=" .. key + return db:exec(update_sql) +end +--------------------- db end --------------------------- + +-- debug func +function _M.D(self,msg) + if not debug_mode then return true end + local fp = io.open('{$SERVER_APP}/debug.log', 'ab') + if fp == nil then + return nil + end + local localtime = os.date("%Y-%m-%d %H:%M:%S") + if server_name then + fp:write(tostring(msg) .. "\n") + else + fp:write(localtime..":"..tostring(msg) .. "\n") + end + fp:flush() + fp:close() + return true +end + +function _M.is_migrating(self, input_sn) + local file = io.open("{$SERVER_APP}/migrating", "rb") + if file then return true end + local file = io.open("{$SERVER_APP}/logs/"..input_sn.."/migrating", "rb") + if file then return true end + return false +end + +function _M.is_working(self,sign) + local work_status = cache:get(sign.."_working") + if work_status ~= nil and work_status == true then + return true + end + return false +end + +function _M.lock_working(self, sign) + local working_key = sign.."_working" + cache:set(working_key, true, 60) +end + +function _M.unlock_working(self, sign) + local working_key = sign.."_working" + cache:set(working_key, false) +end + +function _M.write_file(self, filename, body, mode) + local fp = io.open(filename, mode) + if fp == nil then + return nil + end + fp:write(body) + fp:flush() + fp:close() + return true + end + +function _M.read_file_body(self, filename) + local fp = io.open(filename,'rb') + if not fp then + return nil + end + fbody = fp:read("*a") + fp:close() + if fbody == '' then + return nil + end + return fbody +end + +function _M.load_update_day(self, input_sn) + local _file = "{$SERVER_APP}/logs/"..input_sn.."/update_day.log" + return self:read_file_body(_file) +end + +function _M.write_update_day(self, input_sn) + local _file = "{$SERVER_APP}/logs/"..input_sn.."/update_day.log" + return self:write_file(_file, today, "w") +end + +function _M.clean_stats(self, db, input_sn) + -- 清空 uri,ip 汇总的信息[昨日] + local update_day = self:load_update_day(input_sn) + if not update_day or update_day ~= today then + + local update_sql = "UPDATE uri_stat SET "..day_column.."=0,"..flow_column.."=0" + db:exec(update_sql) + + update_sql = "UPDATE ip_stat SET "..day_column.."=0,"..flow_column.."=0" + db:exec(update_sql) + self:write_update_day(input_sn) + end +end + +function _M.lpop(self) + local cache = ngx.shared.mw_total + return cache:lpop(total_key) +end + +function _M.rpop(self) + local cache = ngx.shared.mw_total + return cache:rpop(total_key) +end + + +function _M.get_update_field(self, field, value) + return field.."="..field.."+"..tostring(value) +end + +function _M.get_request_time(self) + local request_time = math.floor((ngx.now() - ngx.req.start_time()) * 1000) + if request_time == 0 then request_time = 1 end + return request_time +end + + +function _M.get_end_time(self) + local s_time = ngx.time() + local n_date = os.date("*t",s_time + 86400) + n_date.hour = 0 + n_date.min = 0 + n_date.sec = 0 + local d_time = ngx.time(n_date) + return d_time - s_time +end + + +function _M.match_spider(self, ua) + -- 匹配蜘蛛请求 + local is_spider = false + local spider_name = "" + local spider_match = "" + + local spider_table = { + ["baidu"] = 1, -- check + ["bing"] = 2, -- check + ["qh360"] = 3, -- check + ["google"] = 4, + ["bytes"] = 5, -- check + ["sogou"] = 6, -- check + ["youdao"] = 7, + ["soso"] = 8, + ["dnspod"] = 9, + ["yandex"] = 10, + ["yisou"] = 11, + ["other"] = 12, + ["mpcrawler"] = 13, + ["yahoo"] = 14, -- check + ["duckduckgo"] = 15 + } + + local find_spider, _ = ngx.re.match(ua, "(Baiduspider|Bytespider|360Spider|Sogou web spider|Sosospider|Googlebot|bingbot|AdsBot-Google|Google-Adwords|YoudaoBot|Yandex|DNSPod-Monitor|YisouSpider|mpcrawler)", "ijo") + if find_spider then + is_spider = true + spider_match = string.lower(find_spider[0]) + if string.find(spider_match, "baidu", 1, true) then + spider_name = "baidu" + elseif string.find(spider_match, "bytes", 1, true) then + spider_name = "bytes" + elseif string.find(spider_match, "360", 1, true) then + spider_name = "qh360" + elseif string.find(spider_match, "sogou", 1, true) then + spider_name = "sogou" + elseif string.find(spider_match, "soso", 1, true) then + spider_name = "soso" + elseif string.find(spider_match, "google", 1, true) then + spider_name = "google" + elseif string.find(spider_match, "bingbot", 1, true) then + spider_name = "bing" + elseif string.find(spider_match, "youdao", 1, true) then + spider_name = "youdao" + elseif string.find(spider_match, "dnspod", 1, true) then + spider_name = "dnspod" + elseif string.find(spider_match, "yandex", 1, true) then + spider_name = "yandex" + elseif string.find(spider_match, "yisou", 1, true) then + spider_name = "yisou" + elseif string.find(spider_match, "mpcrawler", 1, true) then + spider_name = "mpcrawler" + end + end + + if is_spider then + return is_spider, spider_name, spider_table[spider_name] + end + + -- Curl|Yahoo|HeadlessChrome|包含bot|Wget|Spider|Crawler|Scrapy|zgrab|python|java|Adsbot|DuckDuckGo + find_spider, _ = ngx.re.match(ua, "(Yahoo|Slurp|DuckDuckGo|Semrush|Spider|Bot|crawler)", "ijo") + if find_spider then + spider_match = string.lower(find_spider[0]) + if string.find(spider_match, "yahoo", 1, true) then + spider_name = "other" + elseif string.find(spider_match, "slurp", 1, true) then + spider_name = "other" + elseif string.find(spider_match, "duckduckgo", 1, true) then + spider_name = "other" + elseif string.find(spider_match, "semrush", 1, true) then + spider_name = "other" + elseif string.find(spider_match, "spider", 1, true) then + spider_name = "other" + elseif string.find(spider_match, "bot", 1, true) then + spider_name = "other" + elseif string.find(spider_match, "crawler", 1, true) then + spider_name = "other" + end + return true, spider_name, spider_table[spider_name] + end + return false, "", 0 +end + + +function _M.match_client(self, ua) + local client_stat_fields = "" + + if not ua then + return client_stat_fields + end + + local clients_map = { + ["android"] = "android", + ["iphone"] = "iphone", + ["ipod"] = "iphone", + ["ipad"] = "iphone", + ["firefox"] = "firefox", + ["msie"] = "msie", + ["trident"] = "msie", + ["360se"] = "qh360", + ["360ee"] = "qh360", + ["360browser"] = "qh360", + ["qihoo"] = "qh360", + ["the world"] = "theworld", + ["theworld"] = "theworld", + ["tencenttraveler"] = "tt", + ["maxthon"] = "maxthon", + ["opera"] = "opera", + ["qqbrowser"] = "qq", + ["ucweb"] = "uc", + ["ubrowser"] = "uc", + ["safari"] = "safari", + ["chrome"] = "chrome", + ["metasr"] = "metasr", + ["2345explorer"] = "pc2345", + ["edge"] = "edeg", + ["edg"] = "edeg", + ["windows"] = "windows", + ["linux"] = "linux", + ["macintosh"] = "mac", + ["mobile"] = "mobile" + } + local mobile_regx = "(Mobile|Android|iPhone|iPod|iPad)" + local mobile_res = ngx.re.match(ua, mobile_regx, "ijo") + --mobile + if mobile_res then + client_stat_fields = client_stat_fields..","..self:get_update_field("mobile", 1) + mobile_res = string.lower(mobile_res[0]) + if mobile_res ~= "mobile" then + client_stat_fields = client_stat_fields..","..self:get_update_field(clients_map[mobile_res], 1) + end + else + --pc + -- 匹配结果的顺序,与ua中关键词的顺序有关 + -- lua的正则不支持|语法 + -- 短字符串string.find效率要比ngx正则高 + local pc_regx1 = "(360SE|360EE|360browser|Qihoo|TheWorld|TencentTraveler|Maxthon|Opera|QQBrowser|UCWEB|UBrowser|MetaSr|2345Explorer|Edg[e]*)" + local pc_res = ngx.re.match(ua, pc_regx1, "ijo") + local cls_pc = nil + if not pc_res then + if ngx.re.find(ua, "[Ff]irefox") then + cls_pc = "firefox" + elseif string.find(ua, "MSIE") or string.find(ua, "Trident") then + cls_pc = "msie" + elseif string.find(ua, "[Cc]hrome") then + cls_pc = "chrome" + elseif string.find(ua, "[Ss]afari") then + cls_pc = "safari" + end + else + cls_pc = string.lower(pc_res[0]) + end + -- self::D("UA:"..ua) + -- D("PC cls:"..tostring(cls_pc)) + if cls_pc then + client_stat_fields = client_stat_fields..","..self:get_update_field(clients_map[cls_pc], 1) + else + -- machine and other + local machine_res, err = ngx.re.match(ua, "(ApacheBench|[Cc]url|HeadlessChrome|[a-zA-Z]+[Bb]ot|[Ww]get|[Ss]pider|[Cc]rawler|[Ss]crapy|zgrab|[Pp]ython|java)", "ijo") + if machine_res then + client_stat_fields = client_stat_fields..","..self:get_update_field("machine", 1) + else + -- 移动端+PC端+机器以外 归类到 其他 + client_stat_fields = client_stat_fields..","..self:get_update_field("other", 1) + end + end + + local os_regx = "(Windows|Linux|Macintosh)" + local os_res = ngx.re.match(ua, os_regx, "ijo") + if os_res then + os_res = string.lower(os_res[0]) + client_stat_fields = client_stat_fields..","..self:get_update_field(clients_map[os_res], 1) + end + end + + local other_regx = "MicroMessenger" + local other_res = ngx.re.find(ua, other_regx) + if other_res then + client_stat_fields = client_stat_fields..","..self:get_update_field("weixin", 1) + end + if client_stat_fields then + client_stat_fields = string.sub(client_stat_fields, 2) + end + return client_stat_fields +end + + +function _M.match_client_arr(self, ua) + local client_stat_fields = {} + + if not ua then + return client_stat_fields + end + + local clients_map = { + ["android"] = "android", + ["iphone"] = "iphone", + ["ipod"] = "iphone", + ["ipad"] = "iphone", + ["firefox"] = "firefox", + ["msie"] = "msie", + ["trident"] = "msie", + ["360se"] = "qh360", + ["360ee"] = "qh360", + ["360browser"] = "qh360", + ["qihoo"] = "qh360", + ["the world"] = "theworld", + ["theworld"] = "theworld", + ["tencenttraveler"] = "tt", + ["maxthon"] = "maxthon", + ["opera"] = "opera", + ["qqbrowser"] = "qq", + ["ucweb"] = "uc", + ["ubrowser"] = "uc", + ["safari"] = "safari", + ["chrome"] = "chrome", + ["metasr"] = "metasr", + ["2345explorer"] = "pc2345", + ["edge"] = "edeg", + ["edg"] = "edeg", + ["windows"] = "windows", + ["linux"] = "linux", + ["macintosh"] = "mac", + ["mobile"] = "mobile" + } + local mobile_regx = "(Mobile|Android|iPhone|iPod|iPad)" + local mobile_res = ngx.re.match(ua, mobile_regx, "ijo") + --mobile + if mobile_res then + client_stat_fields['mobile'] = 1 + mobile_res = string.lower(mobile_res[0]) + if mobile_res ~= "mobile" then + client_stat_fields[clients_map[mobile_res]] = 1 + end + else + --pc + -- 匹配结果的顺序,与ua中关键词的顺序有关 + -- lua的正则不支持|语法 + -- 短字符串string.find效率要比ngx正则高 + local pc_regx1 = "(360SE|360EE|360browser|Qihoo|TheWorld|TencentTraveler|Maxthon|Opera|QQBrowser|UCWEB|UBrowser|MetaSr|2345Explorer|Edg[e]*)" + local pc_res = ngx.re.match(ua, pc_regx1, "ijo") + local cls_pc = nil + + -- self:D("UA-JSON:"..self:to_json(ua)) + if "table" == type(ua) then + ua = self:to_json(ua) + end + + if not pc_res then + if ngx.re.find(ua, "[Ff]irefox") then + cls_pc = "firefox" + elseif string.find(ua, "MSIE") or string.find(ua, "Trident") then + cls_pc = "msie" + elseif string.find(ua, "[Cc]hrome") then + cls_pc = "chrome" + elseif string.find(ua, "[Ss]afari") then + cls_pc = "safari" + end + else + cls_pc = string.lower(pc_res[0]) + end + -- self:D("UA:"..ua) + -- self:D("PC cls:"..tostring(cls_pc)) + if cls_pc then + client_stat_fields[clients_map[cls_pc]] = 1 + else + -- machine and other + local machine_res, err = ngx.re.match(ua, "(ApacheBench|[Cc]url|HeadlessChrome|[a-zA-Z]+[Bb]ot|[Ww]get|[Ss]pider|[Cc]rawler|[Ss]crapy|zgrab|[Pp]ython|java)", "ijo") + if machine_res then + client_stat_fields["machine"] = 1 + else + -- 移动端+PC端+机器以外 归类到 其他 + client_stat_fields["other"] = 1 + end + end + + local os_regx = "(Windows|Linux|Macintosh)" + local os_res = ngx.re.match(ua, os_regx, "ijo") + if os_res then + os_res = string.lower(os_res[0]) + client_stat_fields[clients_map[os_res]] = 1 + end + end + + local other_regx = "MicroMessenger" + local other_res = ngx.re.find(ua, other_regx) + if other_res then + client_stat_fields["weixin"] = 1 + end + return client_stat_fields +end + +function _M.get_client_ip(self) + local client_ip = "unknown" + local cdn = auto_config['cdn'] + local request_header = ngx.req.get_headers() + if cdn == true then + for _,v in ipairs(auto_config['cdn_headers']) do + if request_header[v] ~= nil and request_header[v] ~= "" then + local ip_list = request_header[v] + client_ip = self:split(ip_list,',')[1] + break; + end + end + end + + -- ipv6 + if type(client_ip) == 'table' then client_ip = "" end + if client_ip ~= "unknown" and ngx.re.match(client_ip,"^([a-fA-F0-9]*):") then + return client_ip + end + + -- ipv4 + if not ngx.re.match(client_ip,"\\d+\\.\\d+\\.\\d+\\.\\d+") == nil or not self:is_ipaddr(client_ip) then + client_ip = ngx.var.remote_addr + if client_ip == nil then + client_ip = "unknown" + end + end + + return client_ip +end + +return _M \ No newline at end of file diff --git a/plugins/webstats/lua/webstats_log.lua b/plugins/webstats/lua/webstats_log.lua new file mode 100644 index 000000000..693424770 --- /dev/null +++ b/plugins/webstats/lua/webstats_log.lua @@ -0,0 +1,667 @@ +log_by_lua_block { + + local cpath = "{$SERVER_APP}/lua/" + if not package.cpath:find(cpath) then + package.cpath = cpath .. "?.so;" .. package.cpath + end + if not package.path:find(cpath) then + package.path = cpath .. "?.lua;" .. package.path + end + + local ver = '0.2.4' + local debug_mode = true + + local __C = require "webstats_common" + local C = __C:getInstance() + + -- cache start --- + local cache = ngx.shared.mw_total + local function cache_set(server_name, id ,key, val) + local line_kv = "log_kv_"..server_name..'_'..id.."_"..key + -- cache:set(line_kv, val) + cache:set(line_kv, val) + end + + local function cache_clear(server_name, id, key) + local line_kv = "log_kv_"..server_name..'_'..id.."_"..key + cache:delete(line_kv) + end + + local function cache_get(server_name, id, key) + local line_kv = "log_kv_"..server_name..'_'..id.."_"..key + local value = cache:get(line_kv) + return value + end + + + -- cache end --- + + -- domain config is import + local db = nil + local json = require "cjson" + local sqlite3 = require "lsqlite3" + local config = require "webstats_config" + local sites = require "webstats_sites" + + -- string.gsub(C:get_sn(ngx.var.server_name),'_','.') + local server_name = C:get_sn(ngx.var.server_name) + + + C:setConfData(config, sites) + + local auto_config = C:setInputSn(server_name) + + local request_header = ngx.req.get_headers() + local method = ngx.req.get_method() + local excluded = false + + local day = os.date("%d") + local number_day = tonumber(day) + local day_column = "day"..number_day + local flow_column = "flow"..number_day + local spider_column = "spider_flow"..number_day + --- default common var end --- + + local function init_var() + return true + end + + --------------------- exclude_func start -------------------------- + local function load_global_exclude_ip() + local load_key = "global_exclude_ip_load" + -- update global exclude ip + local global_exclude_ip = auto_config["exclude_ip"] + if global_exclude_ip then + for i, _ip in pairs(global_exclude_ip) + do + -- global + -- D("set global exclude ip: ".._ip) + if not cache:get("global_exclude_ip_".._ip) then + cache:set("global_exclude_ip_".._ip, true) + end + end + end + -- set tag + cache:set(load_key, true) + end + + local function load_exclude_ip(input_server_name) + + local load_key = input_server_name .. "_exclude_ip_load" + local site_config = config[input_server_name] + + local site_exclude_ip = nil + if site_config then + site_exclude_ip = site_config["exclude_ip"] + end + + -- update server_name exclude ip + if site_exclude_ip then + for i, _ip in pairs(site_exclude_ip) + do + cache:set(input_server_name .. "_exclude_ip_".._ip, true) + end + end + + -- set tag + cache:set(load_key, true) + return true + end + + local function filter_status() + if not auto_config['exclude_status'] then return false end + local the_status = tostring(ngx.status) + for _,v in ipairs(auto_config['exclude_status']) + do + if the_status == v then + return true + end + end + return false + end + + local function exclude_extension() + if not ngx.var.uri then return false end + if not auto_config['exclude_extension'] then return false end + for _,v in ipairs(auto_config['exclude_extension']) + do + if ngx.re.find(ngx.var.uri,"[.]"..v.."$",'ijo') then + return true + end + end + return false + end + + local function exclude_url() + if not ngx.var.uri then return false end + if not ngx.var.request_uri then return false end + if not auto_config['exclude_url'] then return false end + local the_uri = string.sub(ngx.var.request_uri, 2) + local url_conf = auto_config["exclude_url"] + for i,conf in ipairs(url_conf) + do + local mode = conf["mode"] + local url = conf["url"] + if mode == "regular" then + if ngx.re.find(the_uri, url, "ijo") then + return true + end + else + if the_uri == url then + return true + end + end + end + return false + end + + local function exclude_ip(input_server_name, ip) + -- 排除IP匹配,分网站单独的配置和全局配置两种方式 + local site_config = config[input_server_name] + local server_exclude_ips = nil + local check_server_exclude_ip = false + if site_config then + server_exclude_ips = site_config["exclude_ip"] + if not server_exclude_ips then + return false + end + for k, _ip in pairs(server_exclude_ips) + do + check_server_exclude_ip = true + break + end + end + -- D("server[" ..input_server_name.."]check exclude ip : "..tostring(check_server_exclude_ip)) + if check_server_exclude_ip then + if cache:get(input_server_name .. "_exclude_ip_"..ip) then + -- D("-Exclude server ip:"..ip) + return true + end + else + if cache:get("global_exclude_ip_"..ip) then + -- D("*Excluded global ip:"..ip) + return true + end + end + return false + end + --------------------- exclude_func end --------------------------- + + local function cache_logs_old(server_name) + + -- make new id + local new_id = C:get_last_id(server_name) + + local excluded = false + local ip = C:get_client_ip() + excluded = filter_status() or exclude_extension() or exclude_url() or exclude_ip(server_name, ip) + + local ip_list = request_header["x-forwarded-for"] + if ip and not ip_list then + ip_list = ip + end + + local remote_addr = ngx.var.remote_addr + if not string.find(ip_list, remote_addr) then + if remote_addr then + ip_list = ip_list .. "," .. remote_addr + end + end + + -- local request_time = ngx.var.request_time + local request_time = C:get_request_time() + local client_port = ngx.var.remote_port + local real_server_name = ngx.var.server_name + local uri = ngx.var.uri + local status_code = ngx.status + local protocol = ngx.var.server_protocol + local request_uri = ngx.var.request_uri + local time_key = C:get_store_key() + local method = method + local body_length = C:get_length() + local domain = C:get_domain() + local referer = ngx.var.http_referer + + local kv = { + id=new_id, + time_key=time_key, + time=ngx.time(), + ip=ip, + domain=domain, + server_name=server_name, + real_server_name=real_server_name, + method=method, + status_code=status_code, + uri=uri, + request_uri=request_uri, + body_length=body_length, + referer=referer, + user_agent=request_header['user-agent'], + protocol=protocol, + is_spider=0, + request_time=request_time, + excluded=excluded, + request_headers='', + ip_list=ip_list, + client_port=client_port + } + + local request_stat_fields = "req=req+1,length=length+"..body_length + local spider_stat_fields = "x" + local client_stat_fields = "x" + + if not excluded then + + if status_code == 500 or (method=="POST" and config["record_post_args"] == true) or (status_code==403 and config["record_get_403_args"] == true) then + local data = "" + local ok, err = pcall(function() data = C:get_http_origin() end) + if ok and not err then + kv["request_headers"] = data + end + end + + if ngx.re.find("500,501,502,503,504,505,506,507,509,510,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,421,422,423,424,425,426,449,451,499", tostring(status_code), "jo") then + local field = "status_"..status_code + request_stat_fields = request_stat_fields .. ","..field.."="..field.."+1" + end + + local lower_method = string.lower(method) + if ngx.re.find("get,post,put,patch,delete", lower_method, "ijo") then + local field = "http_"..lower_method + request_stat_fields = request_stat_fields .. ","..field.."="..field.."+1" + end + + + local ipc = 0 + local pvc = 0 + local uvc = 0 + + local is_spider, request_spider, spider_index = C:match_spider(kv['user_agent']) + if not is_spider then + + client_stat_fields = C:match_client(kv['user_agent']) + if not client_stat_fields or #client_stat_fields == 0 then + client_stat_fields = request_stat_fields..",other=other+1" + end + + pvc, uvc = C:statistics_request(ip, is_spider,body_length) + ipc = C:statistics_ipc(server_name,ip) + else + kv["is_spider"] = spider_index + local field = "spider" + spider_stat_fields = request_spider.."="..request_spider.."+"..1 + request_stat_fields = request_stat_fields .. ","..field.."="..field.."+"..1 + end + + if ipc > 0 then + request_stat_fields = request_stat_fields..",ip=ip+1" + end + if uvc > 0 then + request_stat_fields = request_stat_fields..",uv=uv+1" + end + if pvc > 0 then + request_stat_fields = request_stat_fields..",pv=pv+1" + end + end + + local stat_fields = request_stat_fields..";"..client_stat_fields..";"..spider_stat_fields + + cache_set(server_name, new_id, "stat_fields", stat_fields) + cache_set(server_name, new_id, "log_kv", json.encode(kv)) + end + + local function cache_logs(input_sn) + + -- make new id + local new_id = C:get_last_id(input_sn) + + local excluded = false + local ip = C:get_client_ip() + excluded = filter_status() or exclude_extension() or exclude_url() or exclude_ip(input_sn, ip) + + local ip_list = request_header["x-forwarded-for"] + if ip and not ip_list then + ip_list = ip + end + + local remote_addr = ngx.var.remote_addr + + -- 修复反向代理代过来的数据 + if "table" == type(ip_list) then + ip_list = json.encode(ip_list) + end + + if not string.find(ip_list, remote_addr) then + if remote_addr then + ip_list = ip_list .. "," .. remote_addr + end + end + + -- local request_time = ngx.var.request_time + local request_time = C:get_request_time() + local client_port = ngx.var.remote_port + local real_server_name = input_sn + local uri = tostring(ngx.var.uri) + local status_code = ngx.status + local protocol = ngx.var.server_protocol + local request_uri = ngx.var.request_uri + local time_key = C:get_store_key() + local method = method + local body_length = C:get_length() + local domain = C:get_domain() + local referer = ngx.var.http_referer + + local kv = { + id=new_id, + time_key=time_key, + time=ngx.time(), + ip=ip, + domain=domain, + server_name=input_sn, + real_server_name=real_server_name, + method=method, + status_code=status_code, + uri=uri, + request_uri=request_uri, + body_length=body_length, + referer=referer, + user_agent=request_header['user-agent'], + protocol=protocol, + is_spider=0, + request_time=request_time, + excluded=excluded, + request_headers='', + ip_list=ip_list, + client_port=client_port + } + + -- C:D(json.encode(kv)) + local request_stat_fields = {req=1,length=body_length} + + local spider_stat_fields = {} + local client_stat_fields = {} + + if not excluded then + if status_code == 500 or (method=="POST" and config['global']["record_post_args"] == true) or (status_code==403 and config['global']["record_get_403_args"] == true) then + local data = "" + local ok, err = pcall(function() data = C:get_http_origin() end) + if ok and not err then + kv["request_headers"] = data + else + C:D("debug request_headers error:"..tostring(err)) + end + end + + if ngx.re.find("500,501,502,503,504,505,506,507,509,510,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,421,422,423,424,425,426,449,451,499", tostring(status_code), "jo") then + local field = "status_"..status_code + request_stat_fields[field] = 1 + end + + -- D("method:"..method) + local lower_method = string.lower(method) + if ngx.re.find("get,post,put,patch,delete", lower_method, "ijo") then + local field = "http_"..lower_method + request_stat_fields[field] = 1 + end + + + local ipc = 0 + local pvc = 0 + local uvc = 0 + + local is_spider, request_spider, spider_index = C:match_spider(kv['user_agent']) + if not is_spider then + + client_stat_fields = C:match_client_arr(kv['user_agent']) + pvc, uvc = C:statistics_request(ip, is_spider,body_length) + ipc = C:statistics_ipc(input_sn,ip) + else + kv["is_spider"] = spider_index + local field = "spider" + spider_stat_fields[request_spider] = 1 + request_stat_fields[field] = 1 + end + + if ipc > 0 then + request_stat_fields["ip"] = 1 + end + if uvc > 0 then + request_stat_fields["uv"] = 1 + end + if pvc > 0 then + request_stat_fields["pv"] = 1 + end + end + + local stat_fields = { + request_stat_fields=request_stat_fields, + client_stat_fields=client_stat_fields, + spider_stat_fields=spider_stat_fields, + } + + local data = { + server_name=input_sn, + stat_fields=stat_fields, + log_kv=kv, + } + + local push_data = json.encode(data) + -- C:D(json.encode(push_data)) + local key = C:getTotalKey() + ngx.shared.mw_total:rpush(key, push_data) + end + + local function store_logs_line(db, stmt, input_server_name, lineno) + local logvalue = cache_get(input_server_name, lineno, "log_kv") + if not logvalue then return false end + local logline = json.decode(logvalue) + + local time = logline["time"] + local id = logline["id"] + local protocol = logline["protocol"] + local client_port = logline["client_port"] + local status_code = logline["status_code"] + local uri = logline["uri"] + local request_uri = logline["request_uri"] + local method = logline["method"] + local body_length = logline["body_length"] + local referer = logline["referer"] + local ip = logline["ip"] + local ip_list = logline["ip_list"] + local request_time = logline["request_time"] + local is_spider = logline["is_spider"] + local domain = logline["domain"] + local server_name = logline["server_name"] + local user_agent = logline["user_agent"] + local request_headers = logline["request_headers"] + local excluded = logline["excluded"] + + local request_stat_fields = nil + local client_stat_fields = nil + local spider_stat_fields = nil + local stat_fields = cache_get(input_server_name, id, "stat_fields") + if stat_fields == nil then + -- D("Log stat fields is nil.") + -- D("Logdata:"..logvalue) + else + stat_fields = C:split(stat_fields, ";") + request_stat_fields = stat_fields[1] + client_stat_fields = stat_fields[2] + spider_stat_fields = stat_fields[3] + + if "x" == client_stat_fields then + client_stat_fields = nil + end + + if "x" == spider_stat_fields then + spider_stat_fields = nil + end + end + + local time_key = logline["time_key"] + if not excluded then + stmt:bind_names{ + time=time, + ip=ip, + domain=domain, + server_name=server_name, + method=method, + status_code=status_code, + uri=request_uri, + body_length=body_length, + referer=referer, + user_agent=user_agent, + protocol=protocol, + request_time=request_time, + is_spider=is_spider, + request_headers=request_headers, + ip_list=ip_list, + client_port=client_port, + } + + local res, err = stmt:step() + if tostring(res) == "5" then + -- D("step res:"..tostring(res)) + -- D("step err:"..tostring(err)) + -- D("the step database connection is busy, so it will be stored later.") + return false + end + stmt:reset() + -- D("store_logs_line ok") + + C:update_stat( db, "client_stat", time_key, client_stat_fields) + C:update_stat( db, "spider_stat", time_key, spider_stat_fields) + -- C:D("stat ok") + + -- only count non spider requests + local ok, err = C:statistics_uri(db, request_uri, ngx.md5(request_uri), body_length) + local ok, err = C:statistics_ip(db, ip, body_length) + end + + C:update_stat( db, "request_stat", time_key, request_stat_fields) + return true + end + + local function store_logs(input_sn) + if C:is_migrating(input_sn) == true then + -- D("migrating...") + return + end + + local last_insert_id_key = input_sn.."_last_id" + local store_start_id_key = input_sn.."_store_start" + local last_id = cache:get(last_insert_id_key) + local store_start = cache:get(store_start_id_key) + if store_start == nil then + store_start = 1 + end + local store_end = last_id + if store_end == nil then + store_end = 1 + end + + local worker_id = ngx.worker.id() + if C:is_working(input_sn) then + -- D("other workers are being stored, please store later.") + -- cache:delete(flush_data_key) + return true + end + C:lock_working(input_sn) + + local log_dir = "{$SERVER_APP}/logs" + local db_path = log_dir .. '/' .. input_sn .. "/logs.db" + local db, err = sqlite3.open(db_path) + + if tostring(err) ~= 'nil' then + C:D("sqlite3 open error:"..tostring(err)) + return true + end + + db:exec([[PRAGMA synchronous = 0]]) + db:exec([[PRAGMA page_size = 4096]]) + db:exec([[PRAGMA journal_mode = wal]]) + db:exec([[PRAGMA journal_size_limit = 1073741824]]) + + local stmt2 = nil + if db ~= nil then + stmt2 = db:prepare[[INSERT INTO web_logs( + time, ip, domain, server_name, method, status_code, uri, body_length, + referer, user_agent, protocol, request_time, is_spider, request_headers, ip_list, client_port) + VALUES(:time, :ip, :domain, :server_name, :method, :status_code, :uri, + :body_length, :referer, :user_agent, :protocol, :request_time, :is_spider, + :request_headers, :ip_list, :client_port)]] + end + + if db == nil or stmt2 == nil then + -- D("web data db error") + -- cache:set(storing_key, false) + if db and db:isopen() then + db:close() + end + return true + end + + status, errorString = db:exec([[BEGIN TRANSACTION]]) + + C:clean_stats(db, input_sn) + + if store_end >= store_start then + for i=store_start, store_end, 1 do + -- D("store_start:"..store_start..":store_end:".. store_end) + if store_logs_line(db, stmt2, input_sn, i) then + cache_clear(input_sn, i, "log_kv") + cache_clear(input_sn, i, "stat_fields") + end + end + end + + local res, err = stmt2:finalize() + if tostring(res) == "5" then + C:D("Finalize res:"..tostring(res)..",Finalize err:"..tostring(err)) + end + + local now_date = os.date("*t") + local save_day = config['global']["save_day"] + local save_date_timestamp = os.time{year=now_date.year, + month=now_date.month, day=now_date.day-save_day, hour=0} + -- delete expire data + db:exec("DELETE FROM web_logs WHERE time<"..tostring(save_date_timestamp)) + + local res, err = db:execute([[COMMIT]]) + if db and db:isopen() then + db:close() + end + + cache:set(store_start_id_key, store_end+1) + C:unlock_working(input_sn) + end + + local function run_app() + -- C:D("------------ debug start ------------") + init_var() + + load_global_exclude_ip() + load_exclude_ip(server_name) + + cache_logs(server_name) + -- C:cron() + + -- cache_logs_old(server_name) + -- store_logs(server_name) + -- C:D("------------ debug end -------------") + end + + + local function run_app_ok() + if not debug_mode then return run_app() end + + local presult, err = pcall( function() run_app() end) + if not presult then + C:D("debug error on :"..tostring(err)) + return true + end + end + + return run_app_ok() +} + diff --git a/plugins/webstats/t/bench/bench.sh b/plugins/webstats/t/bench/bench.sh new file mode 100755 index 000000000..1882c65ac --- /dev/null +++ b/plugins/webstats/t/bench/bench.sh @@ -0,0 +1,29 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") +rootPath=$(dirname "$rootPath") + +# echo $rootPath + +resty=$rootPath/openresty/bin/resty + +RUN_CMD=$resty +if [ ! -f $resty ];then + RUN_CMD=/www/server/openresty/bin/resty +fi + + +# test +# $RUN_CMD simple.lua + +# $RUN_CMD test_today.lua +# $RUN_CMD test_time.lua + +# $RUN_CMD test_ngx_find.lua + +$RUN_CMD test_match_spider.lua \ No newline at end of file diff --git a/plugins/webstats/t/bench/simple.lua b/plugins/webstats/t/bench/simple.lua new file mode 100755 index 000000000..7f52523a0 --- /dev/null +++ b/plugins/webstats/t/bench/simple.lua @@ -0,0 +1,18 @@ +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end + +collectgarbage() + +ngx.update_time() +local begin = ngx.now() +local N = 1e7 +for i = 1, N do + target() +end +ngx.update_time() + +ngx.say("elapsed: ", (ngx.now() - begin) / N) \ No newline at end of file diff --git a/plugins/webstats/t/bench/test_match_spider.lua b/plugins/webstats/t/bench/test_match_spider.lua new file mode 100644 index 000000000..833b75f56 --- /dev/null +++ b/plugins/webstats/t/bench/test_match_spider.lua @@ -0,0 +1,106 @@ + +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end +-- 以上为预热操作 +collectgarbage() + +local function match_spider(ua) + -- 匹配蜘蛛请求 + local is_spider = false + local spider_name = "" + local spider_match = "" + + local spider_table = { + ["baidu"] = 1, -- check + ["bing"] = 2, -- check + ["qh360"] = 3, -- check + ["google"] = 4, + ["bytes"] = 5, -- check + ["sogou"] = 6, -- check + ["youdao"] = 7, + ["soso"] = 8, + ["dnspod"] = 9, + ["yandex"] = 10, + ["yisou"] = 11, + ["other"] = 12, + ["mpcrawler"] = 13, + ["yahoo"] = 14, -- check + ["duckduckgo"] = 15 + } + + local find_spider, _ = ngx.re.match(ua, "(Baiduspider|Bytespider|360Spider|Sogou web spider|Sosospider|Googlebot|bingbot|AdsBot-Google|Google-Adwords|YoudaoBot|Yandex|DNSPod-Monitor|YisouSpider|mpcrawler)", "ijo") + if find_spider then + is_spider = true + spider_match = string.lower(find_spider[0]) + if string.find(spider_match, "baidu", 1, true) then + spider_name = "baidu" + elseif string.find(spider_match, "bytes", 1, true) then + spider_name = "bytes" + elseif string.find(spider_match, "360", 1, true) then + spider_name = "qh360" + elseif string.find(spider_match, "sogou", 1, true) then + spider_name = "sogou" + elseif string.find(spider_match, "soso", 1, true) then + spider_name = "soso" + elseif string.find(spider_match, "google", 1, true) then + spider_name = "google" + elseif string.find(spider_match, "bingbot", 1, true) then + spider_name = "bing" + elseif string.find(spider_match, "youdao", 1, true) then + spider_name = "youdao" + elseif string.find(spider_match, "dnspod", 1, true) then + spider_name = "dnspod" + elseif string.find(spider_match, "yandex", 1, true) then + spider_name = "yandex" + elseif string.find(spider_match, "yisou", 1, true) then + spider_name = "yisou" + elseif string.find(spider_match, "mpcrawler", 1, true) then + spider_name = "mpcrawler" + end + end + + if is_spider then + return is_spider, spider_name, spider_table[spider_name] + end + + -- Curl|Yahoo|HeadlessChrome|包含bot|Wget|Spider|Crawler|Scrapy|zgrab|python|java|Adsbot|DuckDuckGo + find_spider, _ = ngx.re.match(ua, "(Yahoo|Slurp|DuckDuckGo)", "ijo") + if res then + spider_match = string.lower(find_spider[0]) + if string.find(spider_match, "yahoo", 1, true) then + spider_name = "yahoo" + elseif string.find(spider_match, "slurp", 1, true) then + spider_name = "yahoo" + elseif string.find(spider_match, "duckduckgo", 1, true) then + spider_name = "duckduckgo" + end + return true, spider_name, spider_table[spider_name] + end + return false, "", 0 +end + + + +-- local is_spider, request_spider, spider_index = match_spider("Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)") + +-- ngx.say(is_spider,request_spider, spider_index) + +ngx.update_time() +local begin = ngx.now() +local N = 1e6 +for i = 1, N do + match_spider("Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)") +end +ngx.update_time() + +ngx.say("match_spider elapsed: ", (ngx.now() - begin) / N) + + + + + + diff --git a/plugins/webstats/t/bench/test_ngx_find.lua b/plugins/webstats/t/bench/test_ngx_find.lua new file mode 100644 index 000000000..67a045d6e --- /dev/null +++ b/plugins/webstats/t/bench/test_ngx_find.lua @@ -0,0 +1,35 @@ + +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end +-- 以上为预热操作 +collectgarbage() + +local spider_match = "aa 220" + + +ngx.update_time() +local begin = ngx.now() +local N = 1e7 +for i = 1, N do + ngx.re.find(spider_match, "360", "ijo") +end +ngx.update_time() + +ngx.say("ngx.re.find elapsed: ", (ngx.now() - begin) / N) + + + +ngx.update_time() +local begin = ngx.now() +local N = 1e7 +for i = 1, N do + string.find(spider_match, "360", 1, true) +end +ngx.update_time() + +ngx.say("string.find elapsed: ", (ngx.now() - begin) / N) + diff --git a/plugins/webstats/t/bench/test_time.lua b/plugins/webstats/t/bench/test_time.lua new file mode 100644 index 000000000..492b79bed --- /dev/null +++ b/plugins/webstats/t/bench/test_time.lua @@ -0,0 +1,118 @@ + +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end +-- 以上为预热操作 +collectgarbage() + + +local function get_store_key() + return os.date("%Y%m%d%H", os.time()) +end + +local function get_store_key2() + return os.date("%Y%m%d%H", ngx.time()) +end + + +local function get_end_time() + local s_time = os.time() + local n_date = os.date("*t",s_time + 86400) + n_date.hour = 0 + n_date.min = 0 + n_date.sec = 0 + local d_time = os.time(n_date) + return d_time - s_time +end + + + + +local function get_end_time2() + local s_time = ngx.time() + local n_date = os.date("*t",s_time + 86400) + n_date.hour = 0 + n_date.min = 0 + n_date.sec = 0 + local d_time = ngx.time(n_date) + return d_time - s_time +end + +local function get_update_field(field, value) + return field.."="..field.."+"..value +end + +local function get_update_field2(field, value) + return field.."="..field.."+"..tostring(value) +end + + + +ngx.update_time() +local begin = ngx.now() +local N = 1e3 +for i = 1, N do + get_store_key() +end +ngx.update_time() + +ngx.say("get_store_key elapsed: ", (ngx.now() - begin) / N) + + +ngx.update_time() +local begin = ngx.now() +local N = 1e3 +for i = 1, N do + get_store_key2() +end +ngx.update_time() + +ngx.say("get_store_key2 elapsed: ", (ngx.now() - begin) / N) + + +ngx.update_time() +local begin = ngx.now() +local N = 1e5 +for i = 1, N do + get_end_time() +end +ngx.update_time() + +ngx.say("get_end_time elapsed: ", (ngx.now() - begin) / N) + + +ngx.update_time() +local begin = ngx.now() +local N = 1e5 +for i = 1, N do + get_end_time2() +end +ngx.update_time() + +ngx.say("get_end_time2 elapsed: ", (ngx.now() - begin) / N) + + +ngx.update_time() +local begin = ngx.now() +local N = 1e9 +for i = 1, N do + get_update_field("ss","1") +end +ngx.update_time() + +ngx.say("get_update_field elapsed: ", (ngx.now() - begin) / N) + + +ngx.update_time() +local begin = ngx.now() +local N = 1e9 +for i = 1, N do + get_update_field2("ss",1) +end +ngx.update_time() + +ngx.say("get_update_field2 elapsed: ", (ngx.now() - begin) / N) + diff --git a/plugins/webstats/t/bench/test_today.lua b/plugins/webstats/t/bench/test_today.lua new file mode 100644 index 000000000..433710420 --- /dev/null +++ b/plugins/webstats/t/bench/test_today.lua @@ -0,0 +1,33 @@ + +local function target() + ngx.re.find("hello, world.", [[\w+\.]], "jo") +end +for i = 1, 100 do + target() +end +-- 以上为预热操作 +collectgarbage() + + +ngx.update_time() +local begin = ngx.now() +local N = 1e6 +for i = 1, N do + os.date("%Y%m%d") + -- ngx.say(t) +end +ngx.update_time() + +ngx.say("os.date elapsed: ", (ngx.now() - begin) / N) + + +ngx.update_time() +local begin = ngx.now() +local N = 1e6 +for i = 1, N do + ngx.re.gsub(ngx.today(),'-','') + -- ngx.say(t) +end +ngx.update_time() + +ngx.say("ngx.today() elapsed: ", (ngx.now() - begin) / N) diff --git a/plugins/webstats/t/index.py b/plugins/webstats/t/index.py new file mode 100644 index 000000000..3b89c9149 --- /dev/null +++ b/plugins/webstats/t/index.py @@ -0,0 +1,122 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +import os +import sys +import time +import string +import json +import hashlib +import shlex +import datetime +import subprocess +import re +from random import Random + + +TEST_URL = "http://t1.cn/" +# TEST_URL = "https://www.zzzvps.com/" + + +def httpGet(url, timeout=10): + import urllib.request + + try: + req = urllib.request.urlopen(url, timeout=timeout) + result = req.read().decode('utf-8') + return result + + except Exception as e: + return str(e) + + +def httpPost(url, data, timeout=10): + """ + 发送POST请求 + @url 被请求的URL地址(必需) + @data POST参数,可以是字符串或字典(必需) + @timeout 超时时间默认60秒 + return string + """ + if sys.version_info[0] == 2: + try: + import urllib + import urllib2 + import ssl + ssl._create_default_https_context = ssl._create_unverified_context + data = urllib.urlencode(data) + req = urllib2.Request(url, data) + response = urllib2.urlopen(req, timeout=timeout) + return response.read() + except Exception as ex: + return str(ex) + else: + try: + import urllib.request + import ssl + try: + ssl._create_default_https_context = ssl._create_unverified_context + except: + pass + data = urllib.parse.urlencode(data).encode('utf-8') + req = urllib.request.Request(url, data) + response = urllib.request.urlopen(req, timeout=timeout) + result = response.read() + if type(result) == bytes: + result = result.decode('utf-8') + return result + except Exception as ex: + return str(ex) + + +def httpGet__UA(url, ua, timeout=10): + import urllib.request + headers = {'user-agent': ua} + try: + req = urllib.request.Request(url, headers=headers) + response = urllib.request.urlopen(req) + result = response.read().decode('utf-8') + return result + + except Exception as e: + return str(e) + + +def test_OK(): + ''' + 目录保存 + ''' + url = TEST_URL + "ok.txt" + print("ok test start") + url_val = httpGet__UA( + url, "Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit / 537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36") + print(url_val) + print("ok test end") + + +def test_Spider(): + ''' + 目录保存 + ''' + url = TEST_URL + "ok.txt" + print("spider test start") + url_val = httpGet__UA( + url, "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.103 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)") + print(url_val) + print("spider test end") + + +def test_start(): + test_OK() + test_Spider() + + +if __name__ == "__main__": + os.system('cd /Users/midoks/Desktop/mwdev/server/mdserver-web/plugins/webstats && sh install.sh uninstall 0.2.2 && sh install.sh install 0.2.2') + os.system('cd /Users/midoks/Desktop/mwdev/server/mdserver-web/ && python3 plugins/openresty/index.py stop && python3 plugins/openresty/index.py start') + test_start() diff --git a/plugins/webstats/t/test.sh b/plugins/webstats/t/test.sh new file mode 100755 index 000000000..bf823a160 --- /dev/null +++ b/plugins/webstats/t/test.sh @@ -0,0 +1,6 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +python3 index.py + diff --git a/plugins/webstats/tool_migrate.py b/plugins/webstats/tool_migrate.py new file mode 100644 index 000000000..c0febe2e7 --- /dev/null +++ b/plugins/webstats/tool_migrate.py @@ -0,0 +1,209 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'webstats' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConf(): + conf = getServerDir() + "/lua/config.json" + return conf + + +def getGlobalConf(): + conf = getConf() + content = mw.readFile(conf) + result = json.loads(content) + return result + + +def pSqliteDb(dbname='web_logs', site_name='unset', fn="logs"): + + db_dir = getServerDir() + '/logs/' + site_name + if not os.path.exists(db_dir): + mw.execShell('mkdir -p ' + db_dir) + + name = fn + file = db_dir + '/' + name + '.db' + + if not os.path.exists(file): + conn = mw.M(dbname).dbPos(db_dir, name) + sql = mw.readFile(getPluginDir() + '/conf/init.sql') + sql_list = sql.split(';') + for index in range(len(sql_list)): + conn.execute(sql_list[index], ()) + else: + conn = mw.M(dbname).dbPos(db_dir, name) + + conn.execute("PRAGMA synchronous = 0", ()) + conn.execute("PRAGMA page_size = 4096", ()) + conn.execute("PRAGMA journal_mode = wal", ()) + + conn.autoTextFactory() + + # conn.text_factory = lambda x: str(x, encoding="utf-8", errors='ignore') + # conn.text_factory = lambda x: unicode(x, "utf-8", "ignore") + return conn + + +def migrateSiteHotLogs(site_name, query_date): + print(site_name, query_date) + + migrating_flag = getServerDir() + "/logs/%s/migrating" % site_name + hot_db = getServerDir() + "/logs/%s/logs.db" % site_name + hot_db_tmp = getServerDir() + "/logs/%s/logs_tmp.db" % site_name + history_logs_db = getServerDir() + "/logs/%s/history_logs.db" % site_name + # 1. copy to tmp file + try: + import shutil + print("coping {} to {} ...".format(hot_db, hot_db_tmp)) + mw.writeFile(migrating_flag, "yes") + time.sleep(3) + shutil.copy(hot_db, hot_db_tmp) + if not os.path.exists(hot_db_tmp): + return mw.returnMsg(False, "migrating fail, copy tmp file!") + except: + return mw.returnMsg(False, "{} migrating fail.".format(site_name)) + finally: + if os.path.exists(migrating_flag): + os.remove(migrating_flag) + + # 2. 从临时备份中迁移热日志数据到历史日志 + print("begin tmp to hot log data ...") + try: + print("history file: {}".format(history_logs_db)) + logs_conn = pSqliteDb('web_log', site_name, 'logs_tmp') + history_logs_conn = pSqliteDb('web_log', site_name, 'history_logs') + + hot_db_columns = logs_conn.originExecute( + "PRAGMA table_info([web_logs])") + _columns = ",".join([c[1] for c in hot_db_columns if c[1] != "id"]) + query_start = 0 + todayTime = time.strftime('%Y-%m-%d 00:00:00', time.localtime()) + todayUt = int(time.mktime(time.strptime( + todayTime, "%Y-%m-%d %H:%M:%S"))) + + logs_sql = "select {} from web_logs where time<{}".format( + _columns, todayUt) + selector = logs_conn.originExecute(logs_sql) + log = selector.fetchone() + while log: + params = "" + for field in log: + if params: + params += "," + if field is None: + field = "\'\'" + elif type(field) == str: + field = "\'" + field.replace("\'", "\"") + "\'" + params += str(field) + insert_sql = "insert into web_logs(" + \ + _columns + ") values(" + params + ")" + history_logs_conn.execute(insert_sql) + log = selector.fetchone() + + print("sorting historical data, this action takes a long time...") + history_logs_conn.execute("VACUUM;") + + gcfg = getGlobalConf() + save_day = gcfg['global']["save_day"] + print("delete historical data {} days ago...".format(save_day)) + time_now = time.localtime() + save_timestamp = time.mktime((time_now.tm_year, time_now.tm_mon, time_now.tm_mday - save_day, 0, 0, 0, 0, 0, 0)) + delete_sql = "delete from web_logs where time <= {}".format( + save_timestamp) + print('delete history_logs') + print(delete_sql) + history_logs_conn.execute(delete_sql) + history_logs_conn.commit() + + # 3. delete merged data and clean up statistics + print("delete merged thermal data...") + mw.writeFile(migrating_flag, "yes") + + hot_db_conn = pSqliteDb('web_logs', site_name) + del_hot_log = "delete from web_logs where time<{}".format(todayUt) + print(del_hot_log) + r = hot_db_conn.execute(del_hot_log) + print("delete:", r) + print("deleting statistics over 180 days...") + save_time_key = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 180 * 86400)) + + del_request_stat_sql = "delete from request_stat where time<={}".format( + save_time_key) + hot_db_conn.execute(del_request_stat_sql) + + hot_db_conn.execute( + "delete from spider_stat where time<={}".format(save_time_key)) + hot_db_conn.execute( + "delete from client_stat where time<={}".format(save_time_key)) + hot_db_conn.execute( + "delete from referer_stat where time<={}".format(save_time_key)) + hot_db_conn.commit() + print("clean up the hot database...") + hot_db_conn.execute("VACUUM;") + hot_db_conn.commit() + + if os.path.exists(migrating_flag): + os.remove(migrating_flag) + except Exception as e: + if site_name: + print("{} logs to history error:{}".format(site_name, e)) + else: + print("logs to history error:{}".format(e)) + finally: + if os.path.exists(hot_db_tmp): + os.remove(hot_db_tmp) + + print("{} logs migrate ok.".format(site_name)) + + if not mw.isAppleSystem(): + mw.execShell("chown -R www:www " + getServerDir()) + + mw.opWeb('restart') + return mw.returnMsg(True, "{} logs migrate ok".format(site_name)) + + +def migrateHotLogs(query_date="today"): + print("begin migrate hot logs") + sites = mw.M('sites').field('name').order("add_time").select() + + unset_site = {"name": "unset"} + sites.append(unset_site) + + # migrateSiteHotLogs('t1.cn', query_date) + + for site_info in sites: + # print(site_info['name']) + site_name = site_info["name"] + migrate_res = migrateSiteHotLogs(site_name, query_date) + if not migrate_res["status"]: + print(migrate_res["msg"]) + print("end migrate hot logs") diff --git a/plugins/webstats/tool_task.py b/plugins/webstats/tool_task.py new file mode 100644 index 000000000..95f923e28 --- /dev/null +++ b/plugins/webstats/tool_task.py @@ -0,0 +1,132 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'webstats' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getTaskConf(): + conf = getServerDir() + "/task_config.json" + return conf + + +def getConfigData(): + conf = getTaskConf() + if os.path.exists(conf): + return json.loads(mw.readFile(getTaskConf())) + return { + "task_id": -1, + "task_list": ["migrate_hot_logs"], + "default_execute_hour": 3, + "default_execute_minute": 15, + } + + +def createBgTask(): + cfg = getConfigData() + name = "[勿删]网站统计插件定时任务" + res = mw.M("crontab").field("id, name").where("name=?", (name,)).find() + if res: + return True + + if "task_id" in cfg.keys() and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where("id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + print("计划任务已经存在!") + return True + + + cmd = "cd " + mw.getPanelDir() + " && nice -n 10 python3 " + getPluginDir() + "/tool_task.py execute" + params = { + 'name': name, + 'type': 'day', + 'week': "", + 'where1': "", + 'hour': cfg['default_execute_hour'], + 'minute': cfg['default_execute_minute'], + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + } + + task_id = MwCrontab.instance().add(params) + if task_id > 0: + cfg["task_id"] = task_id + mw.writeFile(getTaskConf(), json.dumps(cfg)) + + +def removeBgTask(): + cfg = getConfigData() + if "task_id" in cfg.keys() and cfg["task_id"] > 0: + res = mw.M("crontab").field("id, name").where( + "id=?", (cfg["task_id"],)).find() + if res and res["id"] == cfg["task_id"]: + data = MwCrontab.instance().delete(cfg["task_id"]) + if data['status']: + # print(data[1]) + cfg["task_id"] = -1 + mw.writeFile(getTaskConf(), json.dumps(cfg)) + return True + return False + + +def execute(): + try: + import time + now = time.strftime("%Y-%m-%d", time.localtime()) + print("-" * 30) + cfg = getConfigData() + task_list = cfg["task_list"] + for task in task_list: + # print(task) + if task == "migrate_hot_logs": + try: + import tool_migrate + tool_migrate.migrateHotLogs("yesterday") + except Exception as e: + print(e) + print(now) + print("-" * 30) + except Exception as e: + print(e) + +if __name__ == "__main__": + if len(sys.argv) > 1: + action = sys.argv[1] + if action == "execute": + execute() + elif action == "remove": + removeBgTask() + elif action == "add": + createBgTask() diff --git a/plugins/webstats/webstats_index.py b/plugins/webstats/webstats_index.py new file mode 100644 index 000000000..0a8ab8d92 --- /dev/null +++ b/plugins/webstats/webstats_index.py @@ -0,0 +1,1397 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import json +import re + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.crontab import crontab as MwCrontab + + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'webstats' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +sys.path.append(getPluginDir() + "/class") +from LuaMaker import LuaMaker + + +def listToLuaFile(path, lists): + content = LuaMaker.makeLuaTable(lists) + content = "return " + content + mw.writeFile(path, content) + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getConf(): + conf = getServerDir() + "/lua/config.json" + return conf + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def luaConf(): + return mw.getServerDir() + '/web_conf/nginx/vhost/webstats.conf' + + +def status(): + path = luaConf() + if not os.path.exists(path): + return 'stop' + return 'start' + + +def loadLuaFile(name): + lua_dir = getServerDir() + "/lua" + lua_dst = lua_dir + "/" + name + + if not os.path.exists(lua_dst): + lua_tpl = getPluginDir() + '/lua/' + name + content = mw.readFile(lua_tpl) + content = content.replace('{$SERVER_APP}', getServerDir()) + content = content.replace('{$ROOT_PATH}', mw.getServerDir()) + mw.writeFile(lua_dst, content) + + +def loadLuaFileReload(name): + lua_dir = getServerDir() + "/lua" + lua_dst = lua_dir + "/" + name + + lua_tpl = getPluginDir() + '/lua/' + name + content = mw.readFile(lua_tpl) + content = content.replace('{$SERVER_APP}', getServerDir()) + content = content.replace('{$ROOT_PATH}', mw.getServerDir()) + mw.writeFile(lua_dst, content) + + +def loadConfigFile(): + lua_dir = getServerDir() + "/lua" + conf_tpl = getPluginDir() + "/conf/config.json" + + content = mw.readFile(conf_tpl) + content = json.loads(content) + + dst_conf_json = getServerDir() + "/lua/config.json" + if not os.path.exists(dst_conf_json): + mw.writeFile(dst_conf_json, json.dumps(content)) + + dst_conf_lua = getServerDir() + "/lua/webstats_config.lua" + if not os.path.exists(dst_conf_lua): + listToLuaFile(dst_conf_lua, content) + + +# def loadConfigFileReload(): +# -- 配置生活或可使用 +# lua_dir = getServerDir() + "/lua" +# conf_tpl = getPluginDir() + "/conf/config.json" + +# content = mw.readFile(conf_tpl) +# content = json.loads(content) + +# dst_conf_json = getServerDir() + "/lua/config.json" +# mw.writeFile(dst_conf_json, json.dumps(content)) + +# dst_conf_lua = getServerDir() + "/lua/webstats_config.lua" +# listToLuaFile(dst_conf_lua, content) + + +def loadLuaSiteFile(): + lua_dir = getServerDir() + "/lua" + + content = makeSiteConfig() + for index in range(len(content)): + pSqliteDb('web_log', content[index]['name']) + + lua_site_json = lua_dir + "/sites.json" + mw.writeFile(lua_site_json, json.dumps(content)) + + # 设置默认列表 + default_json = lua_dir + "/default.json" + ddata = {} + dlist = [] + for i in content: + dlist.append(i["name"]) + + dlist.append('unset') + ddata["list"] = dlist + if len(ddata["list"]) < 1: + ddata["default"] = "unset" + else: + ddata["default"] = dlist[0] + + mw.writeFile(default_json, json.dumps(ddata)) + + lua_site = lua_dir + "/webstats_sites.lua" + + tmp = { + "name": "unset", + "domains": [], + } + content.append(tmp) + listToLuaFile(lua_site, content) + + +def loadDebugLogFile(): + debug_log = getServerDir() + "/debug.log" + lua_dir = getServerDir() + "/lua" + mw.writeFile(debug_log, '') + + +def pSqliteDb(dbname='web_logs', site_name='unset', name="logs"): + + db_dir = getServerDir() + '/logs/' + site_name + if not os.path.exists(db_dir): + mw.execShell('mkdir -p ' + db_dir) + + file = db_dir + '/' + name + '.db' + if not os.path.exists(file): + conn = mw.M(dbname).dbPos(db_dir, name) + sql = mw.readFile(getPluginDir() + '/conf/init.sql') + sql_list = sql.split(';') + for index in range(len(sql_list)): + conn.execute(sql_list[index]) + else: + conn = mw.M(dbname).dbPos(db_dir, name) + + conn.execute("PRAGMA synchronous = 0") + conn.execute("PRAGMA cache_size = 8000") + conn.execute("PRAGMA page_size = 32768") + conn.execute("PRAGMA journal_mode = wal") + conn.execute("PRAGMA journal_size_limit = 1073741824") + return conn + + +def makeSiteConfig(): + siteM = mw.M('sites') + domainM = mw.M('domain') + slist = siteM.field('id,name').where( + 'status=?', (1,)).order('id desc').select() + + data = [] + for s in slist: + tmp = {} + tmp['name'] = s['name'] + + dlist = domainM.field('id,name').where( + 'pid=?', (s['id'],)).order('id desc').select() + + _t = [] + for d in dlist: + _t.append(d['name']) + + tmp['domains'] = _t + data.append(tmp) + + return data + + +def initDreplace(): + + service_path = getServerDir() + + pSqliteDb() + + path = luaConf() + path_tpl = getPluginDir() + '/conf/webstats.conf' + if not os.path.exists(path): + content = mw.readFile(path_tpl) + content = content.replace('{$SERVER_APP}', service_path) + content = content.replace('{$ROOT_PATH}', mw.getServerDir()) + mw.writeFile(path, content) + + # 已经安装的 + al_config = getServerDir() + "/lua/config.json" + if os.path.exists(al_config): + tmp = json.loads(mw.readFile(al_config)) + if tmp['global']['record_post_args'] or tmp['global']['record_get_403_args']: + openLuaNeedRequestBody() + else: + closeLuaNeedRequestBody() + + lua_dir = getServerDir() + "/lua" + if not os.path.exists(lua_dir): + mw.execShell('mkdir -p ' + lua_dir) + + log_path = getServerDir() + "/logs" + if not os.path.exists(log_path): + mw.execShell('mkdir -p ' + log_path) + + file_list = [ + 'webstats_common.lua', + 'webstats_log.lua', + ] + + for fl in file_list: + loadLuaFile(fl) + + loadConfigFile() + loadLuaSiteFile() + loadDebugLogFile() + + if not mw.isAppleSystem(): + mw.execShell("chown -R www:www " + getServerDir()) + return 'ok' + + +def luaRestart(): + mw.opWeb("stop") + mw.opWeb("start") + + +def start(): + initDreplace() + + import tool_task + tool_task.createBgTask() + + # issues:326 + luaRestart() + return 'ok' + + +def stop(): + path = luaConf() + if os.path.exists(path): + os.remove(path) + + import tool_task + tool_task.removeBgTask() + + luaRestart() + return 'ok' + + +def restart(): + initDreplace() + + luaRestart() + return 'ok' + + +def reload(): + initDreplace() + + file_list = [ + 'webstats_common.lua', + 'webstats_log.lua', + ] + for fl in file_list: + loadLuaFileReload(fl) + + loadDebugLogFile() + + luaRestart() + return 'ok' + + +def getGlobalConf(): + conf = getConf() + content = mw.readFile(conf) + content = json.loads(content) + return mw.returnJson(True, 'ok', content) + + +def openLuaNeedRequestBody(): + conf = luaConf() + content = mw.readFile(conf) + content = re.sub("lua_need_request_body (.*);", + 'lua_need_request_body on;', content) + mw.writeFile(conf, content) + + +def closeLuaNeedRequestBody(): + conf = luaConf() + content = mw.readFile(conf) + content = re.sub("lua_need_request_body (.*);", + 'lua_need_request_body off;', content) + mw.writeFile(conf, content) + + +def setGlobalConf(): + args = getArgs() + + conf = getConf() + content = mw.readFile(conf) + content = json.loads(content) + + open_force_get_request_body = False + for v in ['record_post_args', 'record_get_403_args']: + data = checkArgs(args, [v]) + if data[0]: + rval = False + if args[v] == "true": + rval = True + open_force_get_request_body = True + + content['global'][v] = rval + + # 开启强制获取日志配置 + if open_force_get_request_body: + openLuaNeedRequestBody() + else: + closeLuaNeedRequestBody() + + for v in ['ip_top_num', 'uri_top_num', 'save_day']: + data = checkArgs(args, [v]) + if data[0]: + content['global'][v] = int(args[v]) + + for v in ['cdn_headers', 'exclude_extension', 'exclude_status', 'exclude_ip']: + data = checkArgs(args, [v]) + if data[0]: + content['global'][v] = args[v].split("\\n") + + data = checkArgs(args, ['exclude_url']) + if data[0]: + exclude_url = args['exclude_url'].strip(";") + exclude_url_val = [] + if exclude_url != "": + exclude_url_list = exclude_url.split(";") + for i in exclude_url_list: + t = i.split("|") + val = {} + val['mode'] = t[0] + val['url'] = t[1] + exclude_url_val.append(val) + content['global']['exclude_url'] = exclude_url_val + + mw.writeFile(conf, json.dumps(content)) + conf_lua = getServerDir() + "/lua/webstats_config.lua" + listToLuaFile(conf_lua, content) + luaRestart() + return mw.returnJson(True, '设置成功') + + +def getSiteConf(): + args = getArgs() + + check = checkArgs(args, ['site']) + if not check[0]: + return check[1] + + domain = args['site'] + conf = getConf() + content = mw.readFile(conf) + content = json.loads(content) + + site_conf = {} + if domain in content: + site_conf = content[domain] + else: + site_conf["cdn_headers"] = content['global']['cdn_headers'] + site_conf["exclude_extension"] = content['global']['exclude_extension'] + site_conf["exclude_status"] = content['global']['exclude_status'] + site_conf["exclude_ip"] = content['global']['exclude_ip'] + site_conf["exclude_url"] = content['global']['exclude_url'] + site_conf["record_post_args"] = content['global']['record_post_args'] + site_conf["record_get_403_args"] = content[ + 'global']['record_get_403_args'] + + return mw.returnJson(True, 'ok', site_conf) + + +def setSiteConf(): + args = getArgs() + check = checkArgs(args, ['site']) + if not check[0]: + return check[1] + + domain = args['site'] + conf = getConf() + content = mw.readFile(conf) + content = json.loads(content) + + site_conf = {} + if domain in content: + site_conf = content[domain] + else: + site_conf["cdn_headers"] = content['global']['cdn_headers'] + site_conf["exclude_extension"] = content['global']['exclude_extension'] + site_conf["exclude_status"] = content['global']['exclude_status'] + site_conf["exclude_ip"] = content['global']['exclude_ip'] + site_conf["exclude_url"] = content['global']['exclude_url'] + site_conf["record_post_args"] = content['global']['record_post_args'] + site_conf["record_get_403_args"] = content[ + 'global']['record_get_403_args'] + + for v in ['record_post_args', 'record_get_403_args']: + data = checkArgs(args, [v]) + if data[0]: + rval = False + if args[v] == "true": + rval = True + site_conf[v] = rval + + for v in ['ip_top_num', 'uri_top_num', 'save_day']: + data = checkArgs(args, [v]) + if data[0]: + site_conf[v] = int(args[v]) + + for v in ['cdn_headers', 'exclude_extension', 'exclude_status', 'exclude_ip']: + data = checkArgs(args, [v]) + if data[0]: + site_conf[v] = args[v].strip().split("\\n") + + data = checkArgs(args, ['exclude_url']) + if data[0]: + exclude_url = args['exclude_url'].strip(";") + exclude_url_val = [] + if exclude_url != "": + exclude_url_list = exclude_url.split(";") + for i in exclude_url_list: + t = i.split("|") + val = {} + val['mode'] = t[0] + val['url'] = t[1] + exclude_url_val.append(val) + site_conf['exclude_url'] = exclude_url_val + + content[domain] = site_conf + + mw.writeFile(conf, json.dumps(content)) + conf_lua = getServerDir() + "/lua/webstats_config.lua" + listToLuaFile(conf_lua, content) + luaRestart() + return mw.returnJson(True, '设置成功') + + +def getSiteListData(): + lua_dir = getServerDir() + "/lua" + path = lua_dir + "/default.json" + data = mw.readFile(path) + return json.loads(data) + + +def getDefaultSite(): + data = getSiteListData() + return mw.returnJson(True, 'OK', data) + + +def setDefaultSite(name): + lua_dir = getServerDir() + "/lua" + path = lua_dir + "/default.json" + data = mw.readFile(path) + data = json.loads(data) + data['default'] = name + mw.writeFile(path, json.dumps(data)) + return mw.returnJson(True, 'OK') + + +def toSumField(sql): + l = sql.split(",") + field = "" + for x in l: + field += "sum(" + x + ") as " + x + "," + field = field.strip(',') + return field + + +def getSiteStatInfo(domain, query_date): + conn = pSqliteDb('request_stat', domain) + conn = conn.where("1=1", ()) + + field = 'time,req,pv,uv,ip,length' + field_sum = toSumField(field.replace("time,", "")) + + time_field = "substr(time,1,6)," + + field_sum = time_field + field_sum + conn = conn.field(field_sum) + if query_date == "today": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 0 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + elif query_date == "yesterday": + startTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 1 * 86400)) + endTime = time.strftime( + '%Y%m%d00', time.localtime(time.time())) + conn.andWhere("time>=? and time<=?", (startTime, endTime)) + elif query_date == "l7": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 7 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + elif query_date == "l30": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 30 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + else: + exlist = query_date.split("-") + start = time.strftime( + '%Y%m%d00', time.localtime(int(exlist[0]))) + end = time.strftime( + '%Y%m%d23', time.localtime(int(exlist[1]))) + conn.andWhere("time >= ? and time <= ? ", (start, end,)) + + # 统计总数 + stat_list = conn.inquiry(field) + del(stat_list[0]['time']) + return stat_list[0] + + +def getOverviewList(): + args = getArgs() + check = checkArgs(args, ['site', 'query_date', 'order']) + if not check[0]: + return check[1] + + domain = args['site'] + query_date = args['query_date'] + order = args['order'] + + setDefaultSite(domain) + conn = pSqliteDb('request_stat', domain) + conn = conn.where("1=1", ()) + + field = 'time,req,pv,uv,ip,length' + field_sum = toSumField(field.replace("time,", "")) + + time_field = "substr(time,1,8)," + if order == "hour": + time_field = "substr(time,9,10)," + + field_sum = time_field + field_sum + conn = conn.field(field_sum) + if query_date == "today": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 0 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + elif query_date == "yesterday": + startTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 1 * 86400)) + endTime = time.strftime( + '%Y%m%d00', time.localtime(time.time())) + conn.andWhere("time>=? and time<=?", (startTime, endTime)) + elif query_date == "l7": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 7 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + elif query_date == "l30": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 30 * 86400)) + conn.andWhere("time >= ?", (todayTime,)) + else: + exlist = query_date.split("-") + start = time.strftime( + '%Y%m%d00', time.localtime(int(exlist[0]))) + end = time.strftime( + '%Y%m%d23', time.localtime(int(exlist[1]))) + conn.andWhere("time >= ? and time <= ? ", (start, end,)) + + # 统计总数 + stat_list = conn.inquiry(field) + del(stat_list[0]['time']) + + # 分组统计 + dlist = conn.group(time_field.strip(",")).inquiry(field) + + data = {} + data['data'] = dlist + data['stat_list'] = stat_list[0] + + return mw.returnJson(True, 'ok', data) + + +def getSiteList(): + args = getArgs() + check = checkArgs(args, ['query_date']) + if not check[0]: + return check[1] + + query_date = args['query_date'] + + data = getSiteListData() + data_list = data["list"] + + rdata = [] + for x in data_list: + tmp = getSiteStatInfo(x, query_date) + tmp["site"] = x + rdata.append(tmp) + return mw.returnJson(True, 'ok', rdata) + + +def getLogsRealtimeInfo(): + ''' + 实时信息 + ''' + import datetime + args = getArgs() + check = checkArgs(args, ['site', 'type']) + if not check[0]: + return check[1] + + domain = args['site'] + dtype = args['type'] + + conn = pSqliteDb('web_logs', domain) + timeInt = time.mktime(datetime.datetime.now().timetuple()) + + conn = conn.where("time>=?", (int(timeInt) - 10,)) + + field = 'time,body_length' + field_sum = toSumField(field.replace("time,", "")) + time_field = "substr(time,1,2) as time," + time_field = time_field + field_sum + clist = conn.field(time_field.strip(",")).group( + 'substr(time,1,2)').inquiry(field) + + body_count = 0 + if len(clist) > 0: + body_count = clist[0]['body_length'] + + req_count = conn.count() + + data = {} + data['realtime_traffic'] = body_count + data['realtime_request'] = req_count + + return mw.returnJson(True, 'ok', data) + + +def attacHistoryLogHack(conn, site_name, query_date='today'): + if query_date == "today": + return + db_dir = getServerDir() + '/logs/' + site_name + file = db_dir + '/history_logs.db' + if os.path.exists(file): + attach = "ATTACH DATABASE '" + file + "' as 'history_logs'" + # print(attach) + r = conn.originExecute(attach) + sql_table = "(select * from web_logs union all select * from history_logs.web_logs)" + # print(sql_table) + conn.table(sql_table) + + +def get_logs_list(args): + + start_time = time.time() + + check = checkArgs(args, ['page', 'page_size','site', 'method', + 'status_code', 'spider_type', 'request_time', 'request_size', 'query_date', 'search_uri']) + if not check[0]: + return check[1] + + page = int(args['page']) + page_size = int(args['page_size']) + domain = args['site'] + tojs = args['tojs'] + method = args['method'] + status_code = args['status_code'] + request_time = args['request_time'] + request_size = args['request_size'] + spider_type = args['spider_type'] + query_date = args['query_date'] + search_uri = args['search_uri'] + referer = args['referer'] + ip = args['ip'] + setDefaultSite(domain) + + limit = str(page_size) + ' offset ' + str(page_size * (page - 1)) + conn = pSqliteDb('web_logs', domain) + + field = 'time,ip,domain,server_name,method,is_spider,protocol,status_code,request_headers,ip_list,client_port,body_length,user_agent,referer,request_time,uri' + condition = '' + conn = conn.field(field) + conn = conn.where("1=1", ()) + + todayTime = time.strftime('%Y-%m-%d 00:00:00', time.localtime()) + todayUt = int(time.mktime(time.strptime(todayTime, "%Y-%m-%d %H:%M:%S"))) + if query_date == 'today': + conn = conn.andWhere("time>=?", (todayUt,)) + elif query_date == "yesterday": + conn = conn.andWhere("time>=? and time<=?", (todayUt - 86400, todayUt)) + elif query_date == "l7": + conn = conn.andWhere("time>=?", (todayUt - 7 * 86400,)) + elif query_date == "l30": + conn = conn.andWhere("time>=?", (todayUt - 30 * 86400,)) + else: + exlist = query_date.split("-") + conn = conn.andWhere("time>=? and time<=?", (exlist[0], exlist[1])) + + if ip != '': + conn = conn.andWhere("ip=?", (ip,)) + + if method != "all": + conn = conn.andWhere("method=?", (method,)) + + if request_time != "all": + request_time_s = request_time.strip().split('-') + # print(request_time_s) + if len(request_time_s) == 2: + conn = conn.andWhere("request_time>=? and request_time=?", (request_time,)) + + if request_size != "all": + request_size_s = request_size.strip().split('-') + # print(int(request_size_s[0])*1024) + if len(request_size_s) == 2: + conn = conn.andWhere("body_length>=? and body_length=?", (int(request_size_s[0])*1024,)) + + if spider_type == "normal": + pass + elif spider_type == "only_spider": + conn = conn.andWhere("is_spider>?", (0,)) + elif spider_type == "no_spider": + conn = conn.andWhere("is_spider=?", (0,)) + elif int(spider_type) > 0: + conn = conn.andWhere("is_spider=?", (spider_type,)) + + if referer != 'all': + if referer == '1': + conn = conn.andWhere("referer <> ? ", ('',)) + elif referer == '-1': + conn = conn.andWhere("referer is null ", ()) + + if search_uri != "": + conn = conn.andWhere("uri like '%" + search_uri + "%'", ()) + + if status_code != "all": + conn = conn.andWhere("status_code=?", (status_code,)) + + attacHistoryLogHack(conn, domain, query_date) + + conn.changeTextFactoryToBytes() + clist = conn.limit(limit).order('time desc').inquiry() + for x in range(len(clist)): + req_line = clist[x] + for cx in req_line: + v = req_line[cx] + if type(v) == bytes: + try: + clist[x][cx] = v.decode('utf-8') + except Exception as e: + v = str(v) + v = v.replace("b'",'').strip("'") + clist[x][cx] = v + else: + clist[x][cx] = v + + count_key = "count(*) as num" + count = conn.field(count_key).limit('').order('').inquiry() + # print(count) + count = count[0][count_key] + + end_time = time.time() + cos_time = end_time-start_time + + data = {} + data['cos_time'] = cos_time + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = tojs + data['page'] = mw.getPage(_page) + data['data'] = clist + + + return mw.returnJson(True, 'ok', data) + + +def getLogsErrorList(): + args = getArgs() + check = checkArgs(args, ['page', 'page_size', + 'site', 'status_code', 'query_date']) + if not check[0]: + return check[1] + + page = int(args['page']) + page_size = int(args['page_size']) + domain = args['site'] + tojs = args['tojs'] + status_code = args['status_code'] + query_date = args['query_date'] + setDefaultSite(domain) + + limit = str(page_size) + ' offset ' + str(page_size * (page - 1)) + conn = pSqliteDb('web_logs', domain) + + field = 'time,ip,domain,server_name,method,protocol,status_code,ip_list,client_port,body_length,user_agent,referer,request_time,uri' + conn = conn.field(field) + conn = conn.where("1=1", ()) + + if status_code != "all": + if status_code.find("x") > -1: + status_code = status_code.replace("x", "%") + conn = conn.andWhere("status_code like ?", (status_code,)) + else: + conn = conn.andWhere("status_code=?", (status_code,)) + else: + conn = conn.andWhere( + "(status_code like '50%' or status_code like '40%')", ()) + + todayTime = time.strftime('%Y-%m-%d 00:00:00', time.localtime()) + todayUt = int(time.mktime(time.strptime(todayTime, "%Y-%m-%d %H:%M:%S"))) + if query_date == 'today': + conn = conn.andWhere("time>=?", (todayUt,)) + elif query_date == "yesterday": + conn = conn.andWhere("time>=? and time<=?", (todayUt - 86400, todayUt)) + elif query_date == "l7": + conn = conn.andWhere("time>=?", (todayUt - 7 * 86400,)) + elif query_date == "l30": + conn = conn.andWhere("time>=?", (todayUt - 30 * 86400,)) + else: + exlist = query_date.split("-") + conn = conn.andWhere("time>=? and time<=?", (exlist[0], exlist[1])) + + attacHistoryLogHack(conn, domain, query_date) + + clist = conn.limit(limit).order('time desc').inquiry() + count_key = "count(*) as num" + count = conn.field(count_key).limit('').order('').inquiry() + count = count[0][count_key] + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = tojs + data['page'] = mw.getPage(_page) + data['data'] = clist + + return mw.returnJson(True, 'ok', data) + + +def getClientStatList(): + args = getArgs() + check = checkArgs(args, ['page', 'page_size', + 'site', 'query_date']) + if not check[0]: + return check[1] + + page = int(args['page']) + page_size = int(args['page_size']) + domain = args['site'] + tojs = args['tojs'] + query_date = args['query_date'] + setDefaultSite(domain) + + conn = pSqliteDb('client_stat', domain) + stat = pSqliteDb('client_stat', domain) + + # 列表 + limit = str(page_size) + ' offset ' + str(page_size * (page - 1)) + field = 'time,weixin,android,iphone,mac,windows,linux,edeg,firefox,msie,metasr,qh360,theworld,tt,maxthon,opera,qq,uc,pc2345,safari,chrome,machine,mobile,other' + field_sum = toSumField(field.replace("time,", "")) + time_field = "substr(time,1,8)," + field_sum = time_field + field_sum + + stat = stat.field(field_sum) + if query_date == "today": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 0 * 86400)) + stat.where("time >= ?", (todayTime,)) + elif query_date == "yesterday": + startTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 1 * 86400)) + endTime = time.strftime( + '%Y%m%d00', time.localtime(time.time())) + stat.where("time>=? and time<=?", (startTime, endTime)) + elif query_date == "l7": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 7 * 86400)) + stat.where("time >= ?", (todayTime,)) + elif query_date == "l30": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 30 * 86400)) + stat.where("time >= ?", (todayTime,)) + else: + exlist = query_date.split("-") + start = time.strftime( + '%Y%m%d00', time.localtime(int(exlist[0]))) + end = time.strftime( + '%Y%m%d23', time.localtime(int(exlist[1]))) + stat.where("time >= ? and time <= ? ", (start, end,)) + + # 图表数据 + statlist = stat.group('substr(time,1,4)').inquiry(field) + + if len(statlist) > 0: + del(statlist[0]['time']) + + pc = 0 + pc_key_list = ['chrome', 'qh360', 'edeg', 'firefox', 'safari', 'msie', + 'metasr', 'theworld', 'tt', 'maxthon', 'opera', 'qq', 'pc2345'] + + for x in pc_key_list: + pc += statlist[0][x] + + mobile = 0 + mobile_key_list = ['mobile', 'android', 'iphone', 'weixin'] + for x in mobile_key_list: + mobile += statlist[0][x] + reqest_total = pc + mobile + + sum_data = { + "pc": pc, + "mobile": mobile, + "reqest_total": reqest_total, + } + + statlist = sorted(statlist[0].items(), + key=lambda x: x[1], reverse=True) + _statlist = statlist[0:10] + __statlist = {} + statlist = [] + for x in _statlist: + __statlist[x[0]] = x[1] + statlist.append(__statlist) + else: + sum_data = { + "pc": 0, + "mobile": 0, + "reqest_total": 0, + } + statlist = [] + + # 列表数据 + conn = conn.field(field_sum) + clist = conn.group('substr(time,1,8)').limit( + limit).order('time desc').inquiry(field) + + sql = "SELECT count(*) num from (\ + SELECT count(*) as num FROM client_stat GROUP BY substr(time,1,8)\ + )" + result = conn.query(sql, ()) + result = list(result) + count = result[0][0] + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = tojs + data['page'] = mw.getPage(_page) + data['data'] = clist + data['stat_list'] = statlist + data['sum_data'] = sum_data + + return mw.returnJson(True, 'ok', data) + + +def getDateRangeList(start, end): + dlist = [] + if start > end: + for x in list(range(start, 32, 1)): + dlist.append(x) + + for x in list(range(1, end, 1)): + dlist.append(x) + else: + for x in list(range(start, end, 1)): + dlist.append(x) + + return dlist + + +def getIpStatList(): + args = getArgs() + check = checkArgs(args, ['site', 'query_date']) + if not check[0]: + return check[1] + + domain = args['site'] + tojs = args['tojs'] + query_date = args['query_date'] + setDefaultSite(domain) + + conn = pSqliteDb('ip_stat', domain) + + origin_field = "ip,day,flow" + + if query_date == "today": + ftime = time.localtime(time.time()) + day = ftime.tm_mday + + field_day = "day" + str(day) + field_flow = "flow" + str(day) + # print(field_day, field_flow) + + field = "ip," + field_day + ' as day,' + field_flow + " as flow" + + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + elif query_date == "yesterday": + + ftime = time.localtime(time.time() - 86400) + day = ftime.tm_mday + + field_day = "day" + str(day) + field_flow = "flow" + str(day) + + field = "ip," + field_day + ' as day,' + field_flow + " as flow" + + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + elif query_date == "l7": + + field_day = "" + field_flow = "" + + now_time = time.localtime(time.time()) + end_day = now_time.tm_mday + + start_time = time.localtime(time.time() - 7 * 86400) + start_day = start_time.tm_mday + + rlist = getDateRangeList(start_day, end_day) + + for x in rlist: + field_day += "+cast(day" + str(x) + " as TEXT)" + field_flow += "+cast(flow" + str(x) + " as TEXT)" + + field_day = field_day.strip("+") + field_flow = field_flow.strip("+") + + field = "ip,(" + field_day + ') as day,(' + field_flow + ") as flow" + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + elif query_date == "l30": + + field_day = "" + field_flow = "" + + for x in list(range(1, 32, 1)): + field_day += "+cast(day" + str(x) + " as TEXT)" + field_flow += "+cast(flow" + str(x) + " as TEXT)" + + field_day = field_day.strip("+") + field_flow = field_flow.strip("+") + + # print(field_day) + # print(field_flow) + field = "ip,(" + field_day + ') as day,(' + field_flow + ") as flow" + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + clist = conn.order("flow desc").limit("50").inquiry(origin_field) + # print(clist) + + total_req = 0 + total_flow = 0 + + gepip_mmdb = getServerDir() + '/GeoLite2-City.mmdb' + geoip_exists = False + if os.path.exists(gepip_mmdb): + import geoip2.database + reader = geoip2.database.Reader(gepip_mmdb) + geoip_exists = True + # response = reader.city("172.70.206.144") + # print(response.country.names["zh-CN"]) + # print(response.subdivisions.most_specific.names["zh-CN"]) + # print(response.city.names["zh-CN"]) + + for x in clist: + total_req += x['day'] + total_flow += x['flow'] + + for i in range(len(clist)): + clist[i]['day_rate'] = round((clist[i]['day'] / total_req) * 100, 2) + clist[i]['flow_rate'] = round((clist[i]['flow'] / total_flow) * 100, 2) + ip = clist[i]['ip'] + + if ip == "127.0.0.1": + clist[i]['area'] = "本地" + elif geoip_exists: + try: + response = reader.city(ip) + country = response.country.names["zh-CN"] + + # print(ip, response.subdivisions) + _subdivisions = response.subdivisions + try: + if len(_subdivisions) < 1: + subdivisions = "" + else: + subdivisions = "," + response.subdivisions.most_specific.names[ + "zh-CN"] + except Exception as e: + subdivisions = "" + + try: + if 'zh-CN' in response.city.names: + city = "," + response.city.names["zh-CN"] + else: + city = "," + response.city.names["en"] + except Exception as e: + city = "" + + clist[i]['area'] = country + subdivisions + city + except Exception as e: + clist[i]['area'] = "内网?" + + return mw.returnJson(True, 'ok', clist) + + +def getUriStatList(): + args = getArgs() + check = checkArgs(args, ['site', 'query_date']) + if not check[0]: + return check[1] + + domain = args['site'] + tojs = args['tojs'] + query_date = args['query_date'] + setDefaultSite(domain) + + conn = pSqliteDb('uri_stat', domain) + + origin_field = "uri,day,flow" + + if query_date == "today": + ftime = time.localtime(time.time()) + day = ftime.tm_mday + + field_day = "day" + str(day) + field_flow = "flow" + str(day) + # print(field_day, field_flow) + + field = "uri," + field_day + ' as day,' + field_flow + " as flow" + + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + elif query_date == "yesterday": + + ftime = time.localtime(time.time() - 86400) + day = ftime.tm_mday + + field_day = "day" + str(day) + field_flow = "flow" + str(day) + + field = "uri," + field_day + ' as day,' + field_flow + " as flow" + + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + elif query_date == "l7": + + field_day = "" + field_flow = "" + + now_time = time.localtime(time.time()) + end_day = now_time.tm_mday + + start_time = time.localtime(time.time() - 7 * 86400) + start_day = start_time.tm_mday + + rlist = getDateRangeList(start_day, end_day) + + for x in rlist: + field_day += "+cast(day" + str(x) + " as TEXT)" + field_flow += "+cast(flow" + str(x) + " as TEXT)" + + field_day = field_day.strip("+") + field_flow = field_flow.strip("+") + + field = "uri,(" + field_day + ') as day,(' + field_flow + ") as flow" + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + elif query_date == "l30": + + field_day = "" + field_flow = "" + + for x in list(range(1, 32, 1)): + field_day += "+cast(day" + str(x) + " as TEXT)" + field_flow += "+cast(flow" + str(x) + " as TEXT)" + + field_day = field_day.strip("+") + field_flow = field_flow.strip("+") + + # print(field_day) + # print(field_flow) + field = "uri,(" + field_day + ') as day,(' + field_flow + ") as flow" + conn = conn.field(field) + conn = conn.where("day>? and flow>?", (0, 0,)) + + clist = conn.order("flow desc").limit("50").inquiry(origin_field) + + total_req = 0 + total_flow = 0 + + for x in clist: + total_req += x['day'] + total_flow += x['flow'] + + for i in range(len(clist)): + clist[i]['day_rate'] = round((clist[i]['day'] / total_req) * 100, 2) + clist[i]['flow_rate'] = round((clist[i]['flow'] / total_flow) * 100, 2) + + return mw.returnJson(True, 'ok', clist) + + +def getWebLogCount(domain, query_date): + conn = pSqliteDb('web_logs', domain) + + todayTime = time.strftime('%Y-%m-%d 00:00:00', time.localtime()) + todayUt = int(time.mktime(time.strptime(todayTime, "%Y-%m-%d %H:%M:%S"))) + if query_date == 'today': + conn = conn.where("time>=?", (todayUt,)) + elif query_date == "yesterday": + conn = conn.where("time>=? and time<=?", (todayUt - 86400, todayUt)) + elif query_date == "l7": + conn = conn.where("time>=?", (todayUt - 7 * 86400,)) + elif query_date == "l30": + conn = conn.where("time>=?", (todayUt - 30 * 86400,)) + else: + exlist = query_date.split("-") + conn = conn.where("time>=? and time<=?", (exlist[0], exlist[1])) + + count_key = "count(*) as num" + count = conn.field(count_key).limit('').order('').inquiry() + count = count[0][count_key] + return count + + +def getSpiderStatList(): + args = getArgs() + check = checkArgs(args, ['page', 'page_size', + 'site', 'query_date']) + if not check[0]: + return check[1] + + page = int(args['page']) + page_size = int(args['page_size']) + domain = args['site'] + tojs = args['tojs'] + query_date = args['query_date'] + setDefaultSite(domain) + + conn = pSqliteDb('spider_stat', domain) + stat = pSqliteDb('spider_stat', domain) + + total_req = getWebLogCount(domain, query_date) + + # 列表 + limit = str(page_size) + ' offset ' + str(page_size * (page - 1)) + field = 'time,bytes,bing,soso,yahoo,sogou,google,baidu,qh360,youdao,yandex,dnspod,other' + field_sum = toSumField(field.replace("time,", "")) + time_field = "substr(time,1,8)," + field_sum = time_field + field_sum + + stat = stat.field(field_sum) + if query_date == "today": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 0 * 86400)) + stat.where("time >= ?", (todayTime,)) + elif query_date == "yesterday": + startTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 1 * 86400)) + endTime = time.strftime( + '%Y%m%d00', time.localtime(time.time())) + stat.where("time>=? and time<=?", (startTime, endTime)) + elif query_date == "l7": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 7 * 86400)) + stat.where("time >= ?", (todayTime,)) + elif query_date == "l30": + todayTime = time.strftime( + '%Y%m%d00', time.localtime(time.time() - 30 * 86400)) + stat.where("time >= ?", (todayTime,)) + else: + exlist = query_date.split("-") + start = time.strftime( + '%Y%m%d00', time.localtime(int(exlist[0]))) + end = time.strftime( + '%Y%m%d23', time.localtime(int(exlist[1]))) + stat.where("time >= ? and time <= ? ", (start, end,)) + + # 图表数据 + statlist = stat.group('substr(time,1,4)').inquiry(field) + + if len(statlist) > 0: + del(statlist[0]['time']) + + spider_total = 0 + for x in statlist[0]: + spider_total += statlist[0][x] + + sum_data = {"spider": spider_total, "reqest_total": total_req} + statlist = sorted(statlist[0].items(), + key=lambda x: x[1], reverse=True) + _statlist = statlist[0:9] + __statlist = {} + statlist = [] + for x in _statlist: + __statlist[x[0]] = x[1] + statlist.append(__statlist) + else: + sum_data = {"spider": 0, "reqest_total": total_req} + statlist = [] + + # 列表数据 + conn = conn.field(field_sum) + clist = conn.group('substr(time,1,8)').limit( + limit).order('time desc').inquiry(field) + + sql = "SELECT count(*) num from (\ + SELECT count(*) as num FROM spider_stat GROUP BY substr(time,1,8)\ + )" + result = conn.query(sql, ()) + result = list(result) + count = result[0][0] + + data = {} + _page = {} + _page['count'] = count + _page['p'] = page + _page['row'] = page_size + _page['tojs'] = tojs + data['page'] = mw.getPage(_page) + data['data'] = clist + data['stat_list'] = statlist + data['sum_data'] = sum_data + + return mw.returnJson(True, 'ok', data) diff --git a/plugins/xhprof/conf/xhprof.conf b/plugins/xhprof/conf/xhprof.conf new file mode 100755 index 000000000..92df8fb24 --- /dev/null +++ b/plugins/xhprof/conf/xhprof.conf @@ -0,0 +1,27 @@ +server +{ + listen 5858; + server_name 127.0.0.1; + index index.html index.htm index.php; + root {$SERVER_PATH}/xhprof/xhprof_html; + + #error_page 404 /404.html; + include {$SERVER_PATH}/web_conf/php/conf/enable-php-{$PHP_VER}.conf; + + location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ + { + expires 30d; + } + + location ~ .*\.(js|css)?$ + { + expires 12h; + } + + location ~ /\. + { + deny all; + } + + access_log {$SERVER_PATH}/xhprof/access.log main; +} \ No newline at end of file diff --git a/plugins/xhprof/ico.png b/plugins/xhprof/ico.png new file mode 100644 index 000000000..c710c5861 Binary files /dev/null and b/plugins/xhprof/ico.png differ diff --git a/plugins/xhprof/index.html b/plugins/xhprof/index.html new file mode 100755 index 000000000..754cf36ef --- /dev/null +++ b/plugins/xhprof/index.html @@ -0,0 +1,20 @@ +
                                      +
                                      +
                                      +

                                      服务

                                      +

                                      重写模版

                                      +

                                      主页

                                      +

                                      预加载脚本

                                      +

                                      PHP版本

                                      +

                                      安全设置

                                      +
                                      +
                                      +
                                      +
                                      +
                                      + +
                                      + \ No newline at end of file diff --git a/plugins/xhprof/index.py b/plugins/xhprof/index.py new file mode 100755 index 000000000..5e4b3619c --- /dev/null +++ b/plugins/xhprof/index.py @@ -0,0 +1,257 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re +import json + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +from utils.site import sites as MwSites + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'xhprof' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def getConf(): + return mw.getServerDir() + '/web_conf/nginx/vhost/xhprof.conf' + + +def getPort(): + file = getConf() + content = mw.readFile(file) + rep = r'listen\s*(.*);' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + + +def getHomePage(): + try: + port = getPort() + ip = '127.0.0.1' + if not mw.isAppleSystem(): + ip = mw.getLocalIp() + url = 'http://' + ip + ':' + port + '/index.php' + return mw.returnJson(True, 'OK', url) + except Exception as e: + return mw.returnJson(False, '插件未启动!') + + +def getPhpVer(expect=74): + php_vers = MwSites.instance().getPhpVersion() + v = php_vers['data'] + for i in range(len(v)): + t = int(v[i]['version']) + if (t >= expect): + return str(t) + return str(expect) + + +def getCachePhpVer(): + cacheFile = getServerDir() + '/php.pl' + v = '' + if os.path.exists(cacheFile): + v = mw.readFile(cacheFile) + else: + v = getPhpVer() + mw.writeFile(cacheFile, v) + return v + + +def contentReplace(content): + service_path = mw.getServerDir() + php_ver = getCachePhpVer() + # print php_ver + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PHP_VER}', php_ver) + content = content.replace('{$LOCAL_IP}', mw.getLocalIp()) + return content + + +def contentReplacePHP(content, version): + service_path = mw.getServerDir() + # print php_ver + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$PHP_VER}', version) + return content + + +def status(): + conf = getConf() + if os.path.exists(conf): + return 'start' + return 'stop' + +def getConfAppStart(): + pstart = mw.getServerDir() + '/php/app_start.php' + return pstart + + +def phpPrependFile(): + app_start = getConfAppStart() + tpl = mw.getPluginDir() + '/php/conf/app_start.php' + content = mw.readFile(tpl) + content = contentReplace(content) + mw.writeFile(app_start, content) + return True + +def start(): + phpPrependFile() + + file_tpl = getPluginDir() + '/conf/xhprof.conf' + file_run = getConf() + + if not os.path.exists(file_run): + centent = mw.readFile(file_tpl) + centent = contentReplace(centent) + mw.writeFile(file_run, centent) + + mw.restartWeb() + return 'ok' + + +def stop(): + conf = getConf() + if os.path.exists(conf): + os.remove(conf) + mw.restartWeb() + return 'ok' + + +def restart(): + return start() + + +def reload(): + return start() + + +def setPhpVer(): + args = getArgs() + + if not 'phpver' in args: + return 'phpver missing' + + cacheFile = getServerDir() + '/php.pl' + mw.writeFile(cacheFile, args['phpver']) + + file_tpl = getPluginDir() + '/conf/xhprof.conf' + file_run = getConf() + + content = mw.readFile(file_tpl) + content = contentReplacePHP(content, args['phpver']) + mw.writeFile(file_run, content) + + mw.restartWeb() + return 'ok' + + +def getSetPhpVer(): + cacheFile = getServerDir() + '/php.pl' + if os.path.exists(cacheFile): + return mw.readFile(cacheFile).strip() + return '' + + +def getXhPort(): + try: + port = getPort() + return mw.returnJson(True, 'OK', port) + except Exception as e: + return mw.returnJson(False, '插件未启动!') + + +def setXhPort(): + args = getArgs() + if not 'port' in args: + return mw.returnJson(False, 'port missing!') + + port = args['port'] + if port == '80': + return mw.returnJson(False, '80端不能使用!') + + file = getConf() + if not os.path.exists(file): + return mw.returnJson(False, '插件未启动!') + content = mw.readFile(file) + rep = r'listen\s*(.*);' + content = re.sub(rep, "listen " + port + ';', content) + mw.writeFile(file, content) + mw.restartWeb() + return mw.returnJson(True, '修改成功!') + + +def installPreInspection(): + path = mw.getServerDir() + '/php' + if not os.path.exists(path): + return "先安装一个可用的PHP版本!" + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getConf()) + elif func == 'get_home_page': + print(getHomePage()) + elif func == 'set_php_ver': + print(setPhpVer()) + elif func == 'get_set_php_ver': + print(getSetPhpVer()) + elif func == 'get_xhprof_port': + print(getXhPort()) + elif func == 'set_xhprof_port': + print(setXhPort()) + elif func == 'app_start': + print(getConfAppStart()) + else: + print('error') diff --git a/plugins/xhprof/info.json b/plugins/xhprof/info.json new file mode 100755 index 000000000..f93d85531 --- /dev/null +++ b/plugins/xhprof/info.json @@ -0,0 +1,19 @@ +{ + "sort": 4, + "title":"xhprof", + "tip":"soft", + "name":"xhprof", + "type":"运行环境", + "ps":"PHP性能瓶颈查找工具", + "to_ver":["1.0"], + "versions":["1.0"], + "updates":["1.0"], + "install_pre_inspection":true, + "shell":"install.sh", + "checks":"server/xhprof", + "path": "server/xhprof", + "author":"midoks", + "home":"http://pecl.php.net/package/xhprof", + "date":"2020-07-12", + "pid": "1" +} \ No newline at end of file diff --git a/plugins/xhprof/install.sh b/plugins/xhprof/install.sh new file mode 100755 index 000000000..45eabd0e1 --- /dev/null +++ b/plugins/xhprof/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sys_os=`uname` +Install_xh() +{ + mkdir -p ${serverPath}/xhprof + + if [ ! -d ${serverPath}/xhprof/xhprof_lib ];then + cp -rf $curPath/lib/* ${serverPath}/xhprof + fi + + echo "${1}" > ${serverPath}/xhprof/version.pl + echo '安装完成' + + if [ "$sys_os" != "Darwin" ];then + cd $rootPath && python3 ${rootPath}/plugins/xhprof/index.py start + fi +} + +Uninstall_xh() +{ + if [ "$sys_os" != "Darwin" ];then + cd $rootPath && python3 ${rootPath}/plugins/xhprof/index.py stop + fi + + rm -rf ${serverPath}/xhprof + cd /tmp/xhprof && rm -rf *.xhprof + + if [ -f ${serverPath}/web_conf/nginx/vhost/xhprof.conf ];then + rm -f ${serverPath}/web_conf/nginx/vhost/xhprof.conf + fi + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_xh $2 +else + Uninstall_xh $2 +fi diff --git a/plugins/xhprof/js/xhprof.js b/plugins/xhprof/js/xhprof.js new file mode 100755 index 000000000..af6939fbf --- /dev/null +++ b/plugins/xhprof/js/xhprof.js @@ -0,0 +1,108 @@ +function xhPost(method,args,callback){ + + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'xhprof', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function xhAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'xhprof', func:method, args:_args}); +} + +function homePage(){ + xhPost('get_home_page', '', function(data){ + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + var con = ''; + $(".soft-man-con").html(con); + }); +} + +//phpmyadmin切换php版本 +function phpVer(version) { + + var _version = xhAsyncPost('get_set_php_ver','') + if (_version['data'] != ''){ + version = _version['data']; + } + + $.post('/site/get_php_version', function(rdata) { + // console.log(rdata); + var body = "
                                      PHP版本
                                      '; + $(".soft-man-con").html(body); + },'json'); +} + +function phpVerChange(type, msg) { + var phpver = $("#phpver").val(); + xhPost('set_php_ver', 'phpver='+phpver, function(data){ + if ( data.data == 'ok' ){ + layer.msg('设置成功!',{icon:1,time:2000,shade: [0.3, '#000']}); + } else { + layer.msg('设置失败!',{icon:2,time:2000,shade: [0.3, '#000']}); + } + }); +} + + +//xhprf 安全设置 +function safeConf() { + var data = xhAsyncPost('get_xhprof_port'); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:2,time:2000,shade: [0.3, '#000']}); + return; + } + var con = '
                                      \ + 访问端口\ + \ + \ +
                                      '; + $(".soft-man-con").html(con); +} + +//修改 xhprf 端口 +function xhprofPort() { + var pmport = $("#pmport").val(); + if (pmport < 80 || pmport > 65535) { + layer.msg('端口范围不合法!', { icon: 2 }); + return; + } + var data = 'port=' + pmport; + + xhPost('set_xhprof_port',data, function(data){ + var rdata = $.parseJSON(data.data); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); +} \ No newline at end of file diff --git a/plugins/xhprof/lib/xhprof_html/callgraph.php b/plugins/xhprof/lib/xhprof_html/callgraph.php new file mode 100755 index 000000000..46fc6a524 --- /dev/null +++ b/plugins/xhprof/lib/xhprof_html/callgraph.php @@ -0,0 +1,91 @@ + array(XHPROF_STRING_PARAM, ''), + + // source/namespace/type of run + 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), + + // the focus function, if it is set, only directly + // parents/children functions of it will be shown. + 'func' => array(XHPROF_STRING_PARAM, ''), + + // image type, can be 'jpg', 'gif', 'ps', 'png' + 'type' => array(XHPROF_STRING_PARAM, 'png'), + + // only functions whose exclusive time over the total time + // is larger than this threshold will be shown. + // default is 0.01. + 'threshold' => array(XHPROF_FLOAT_PARAM, 0.01), + + // whether to show critical_path + 'critical' => array(XHPROF_BOOL_PARAM, true), + + // first run in diff mode. + 'run1' => array(XHPROF_STRING_PARAM, ''), + + // second run in diff mode. + 'run2' => array(XHPROF_STRING_PARAM, '') + ); + +// pull values of these params, and create named globals for each param +xhprof_param_init($params); + +// if invalid value specified for threshold, then use the default +if ($threshold < 0 || $threshold > 1) { + $threshold = $params['threshold'][1]; +} + +// if invalid value specified for type, use the default +if (!array_key_exists($type, $xhprof_legal_image_types)) { + $type = $params['type'][1]; // default image type. +} + +$xhprof_runs_impl = new XHProfRuns_Default(); + +if (!empty($run)) { + // single run call graph image generation + xhprof_render_image($xhprof_runs_impl, $run, $type, + $threshold, $func, $source, $critical); +} else { + // diff report call graph image generation + xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, + $type, $threshold, $source); +} diff --git a/plugins/xhprof/lib/xhprof_html/css/xhprof.css b/plugins/xhprof/lib/xhprof_html/css/xhprof.css new file mode 100755 index 000000000..c3abf8622 --- /dev/null +++ b/plugins/xhprof/lib/xhprof_html/css/xhprof.css @@ -0,0 +1,81 @@ +/* Copyright (c) 2009 Facebook + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +td.sorted { + color:#0000FF; +} + +td.vbar, th.vbar { + text-align: right; + border-left: + solid 1px #bdc7d8; +} + +td.vbbar, th.vbar { + text-align: right; + border-left: + solid 1px #bdc7d8; + color:blue; +} + +/* diff reports: display regressions in red */ +td.vrbar { + text-align: right; + border-left:solid 1px #bdc7d8; + color:red; +} + +/* diff reports: display improvements in green */ +td.vgbar { + text-align: right; + border-left: solid 1px #bdc7d8; + color:green; +} + +td.vwbar, th.vwbar { + text-align: right; + border-left: solid 1px white; +} + +td.vwlbar, th.vwlbar { + text-align: left; + border-left: solid 1px white; +} + +p.blue { + color:blue +} + +.bubble { + background-color:#C3D9FF +} + +ul.xhprof_actions { + float: right; + padding-left: 16px; + list-style-image: none; + list-style-type: none; + margin:10px 10px 10px 3em; + position:relative; +} + +ul.xhprof_actions li { + border-bottom:1px solid #D8DFEA; +} + +ul.xhprof_actions li a:hover { + background:#3B5998 none repeat scroll 0 0; + color:#FFFFFF; +} diff --git a/plugins/xhprof/lib/xhprof_html/index.php b/plugins/xhprof/lib/xhprof_html/index.php new file mode 100755 index 000000000..f21d32fd5 --- /dev/null +++ b/plugins/xhprof/lib/xhprof_html/index.php @@ -0,0 +1,90 @@ + array(XHPROF_STRING_PARAM, ''), + 'wts' => array(XHPROF_STRING_PARAM, ''), + 'symbol' => array(XHPROF_STRING_PARAM, ''), + 'sort' => array(XHPROF_STRING_PARAM, 'wt'), // wall time + 'run1' => array(XHPROF_STRING_PARAM, ''), + 'run2' => array(XHPROF_STRING_PARAM, ''), + 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), + 'all' => array(XHPROF_UINT_PARAM, 0), + ); + +// pull values of these params, and create named globals for each param +xhprof_param_init($params); + +/* reset params to be a array of variable names to values + by the end of this page, param should only contain values that need + to be preserved for the next page. unset all unwanted keys in $params. + */ +foreach ($params as $k => $v) { + $params[$k] = $$k; + + // unset key from params that are using default values. So URLs aren't + // ridiculously long. + if ($params[$k] == $v[1]) { + unset($params[$k]); + } +} + +echo ""; + +echo "XHProf: Hierarchical Profiler Report"; +xhprof_include_js_css(); +echo ""; + +echo ""; + +$vbar = ' class="vbar"'; +$vwbar = ' class="vwbar"'; +$vwlbar = ' class="vwlbar"'; +$vbbar = ' class="vbbar"'; +$vrbar = ' class="vrbar"'; +$vgbar = ' class="vgbar"'; + +$xhprof_runs_impl = new XHProfRuns_Default(); + +displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts, + $symbol, $sort, $run1, $run2); + + +echo ""; +echo ""; diff --git a/plugins/xhprof/lib/xhprof_html/jquery/indicator.gif b/plugins/xhprof/lib/xhprof_html/jquery/indicator.gif new file mode 100755 index 000000000..9c26a717c Binary files /dev/null and b/plugins/xhprof/lib/xhprof_html/jquery/indicator.gif differ diff --git a/plugins/xhprof/lib/xhprof_html/jquery/jquery-1.2.6.js b/plugins/xhprof/lib/xhprof_html/jquery/jquery-1.2.6.js new file mode 100755 index 000000000..5b8bfc2dd --- /dev/null +++ b/plugins/xhprof/lib/xhprof_html/jquery/jquery-1.2.6.js @@ -0,0 +1,3549 @@ +(function(){ +/* + * jQuery 1.2.6 - New Wave Javascript + * + * Copyright (c) 2008 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2009-03-17 18:35:18 $ + * $Rev: 5685 $ + */ + +// Map over jQuery in case of overwrite +var _jQuery = window.jQuery, +// Map over the $ in case of overwrite + _$ = window.$; + +var jQuery = window.jQuery = window.$ = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); +}; + +// A simple way to check for HTML strings or ID strings +// (both of which we optimize for) +var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/, + +// Is it a simple selector + isSimple = /^.[^:#\[\.]*$/, + +// Will speed up references to undefined, and allows munging its name. + undefined; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + this.length = 1; + return this; + } + // Handle HTML strings + if ( typeof selector == "string" ) { + // Are we dealing with HTML string or an ID? + var match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) + selector = jQuery.clean( [ match[1] ], context ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // Make sure an element was located + if ( elem ){ + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id != match[3] ) + return jQuery().find( selector ); + + // Otherwise, we inject the element directly into the jQuery object + return jQuery( elem ); + } + selector = []; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); + + return this.setArray(jQuery.makeArray(selector)); + }, + + // The current version of jQuery being used + jquery: "1.2.6", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // The number of elements contained in the matched element set + length: 0, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == undefined ? + + // Return a 'clean' array + jQuery.makeArray( this ) : + + // Return just the object + this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + // Build a new jQuery matched element set + var ret = jQuery( elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + Array.prototype.push.apply( this, elems ); + + return this; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + var ret = -1; + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem && elem.jquery ? elem[0] : elem + , this ); + }, + + attr: function( name, value, type ) { + var options = name; + + // Look for the case where we're accessing a style value + if ( name.constructor == String ) + if ( value === undefined ) + return this[0] && jQuery[ type || "attr" ]( this[0], name ); + + else { + options = {}; + options[ name ] = value; + } + + // Check to see if we're setting style values + return this.each(function(i){ + // Set all the styles + for ( name in options ) + jQuery.attr( + type ? + this.style : + this, + name, jQuery.prop( this, options[ name ], type, i, name ) + ); + }); + }, + + css: function( key, value ) { + // ignore negative width and height values + if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) + value = undefined; + return this.attr( key, value, "curCSS" ); + }, + + text: function( text ) { + if ( typeof text != "object" && text != null ) + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + + var ret = ""; + + jQuery.each( text || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + ret += this.nodeType != 1 ? + this.nodeValue : + jQuery.fn.text( [ this ] ); + }); + }); + + return ret; + }, + + wrapAll: function( html ) { + if ( this[0] ) + // The elements to wrap the target around + jQuery( html, this[0].ownerDocument ) + .clone() + .insertBefore( this[0] ) + .map(function(){ + var elem = this; + + while ( elem.firstChild ) + elem = elem.firstChild; + + return elem; + }) + .append(this); + + return this; + }, + + wrapInner: function( html ) { + return this.each(function(){ + jQuery( this ).contents().wrapAll( html ); + }); + }, + + wrap: function( html ) { + return this.each(function(){ + jQuery( this ).wrapAll( html ); + }); + }, + + append: function() { + return this.domManip(arguments, true, false, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, true, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + find: function( selector ) { + var elems = jQuery.map(this, function(elem){ + return jQuery.find( selector, elem ); + }); + + return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? + jQuery.unique( elems ) : + elems ); + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function(){ + if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var clone = this.cloneNode(true), + container = document.createElement("div"); + container.appendChild(clone); + return jQuery.clean([container.innerHTML])[0]; + } else + return this.cloneNode(true); + }); + + // Need to set the expando to null on the cloned set if it exists + // removeData doesn't work here, IE removes it from the original as well + // this is primarily for IE but the data expando shouldn't be copied over in any browser + var clone = ret.find("*").andSelf().each(function(){ + if ( this[ expando ] != undefined ) + this[ expando ] = null; + }); + + // Copy the events from the original to the clone + if ( events === true ) + this.find("*").andSelf().each(function(i){ + if (this.nodeType == 3) + return; + var events = jQuery.data( this, "events" ); + + for ( var type in events ) + for ( var handler in events[ type ] ) + jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); + }); + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, this ) ); + }, + + not: function( selector ) { + if ( selector.constructor == String ) + // test special case where just one selector is passed in + if ( isSimple.test( selector ) ) + return this.pushStack( jQuery.multiFilter( selector, this, true ) ); + else + selector = jQuery.multiFilter( selector, this ); + + var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; + return this.filter(function() { + return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; + }); + }, + + add: function( selector ) { + return this.pushStack( jQuery.unique( jQuery.merge( + this.get(), + typeof selector == 'string' ? + jQuery( selector ) : + jQuery.makeArray( selector ) + ))); + }, + + is: function( selector ) { + return !!selector && jQuery.multiFilter( selector, this ).length > 0; + }, + + hasClass: function( selector ) { + return this.is( "." + selector ); + }, + + val: function( value ) { + if ( value == undefined ) { + + if ( this.length ) { + var elem = this[0]; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + + // Everything else, we just grab the value + } else + return (this[0].value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + if( value.constructor == Number ) + value += ''; + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(value); + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value == undefined ? + (this[0] ? + this[0].innerHTML : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + data: function( key, value ){ + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data === undefined && this.length ) + data = jQuery.data( this[0], key ); + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } else + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ + jQuery.data( this, key, value ); + }); + }, + + removeData: function( key ){ + return this.each(function(){ + jQuery.removeData( this, key ); + }); + }, + + domManip: function( args, table, reverse, callback ) { + var clone = this.length > 1, elems; + + return this.each(function(){ + if ( !elems ) { + elems = jQuery.clean( args, this.ownerDocument ); + + if ( reverse ) + elems.reverse(); + } + + var obj = this; + + if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) + obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); + + var scripts = jQuery( [] ); + + jQuery.each(elems, function(){ + var elem = clone ? + jQuery( this ).clone( true )[0] : + this; + + // execute all scripts after the elements have been injected + if ( jQuery.nodeName( elem, "script" ) ) + scripts = scripts.add( elem ); + else { + // Remove any inner scripts for later evaluation + if ( elem.nodeType == 1 ) + scripts = scripts.add( jQuery( "script", elem ).remove() ); + + // Inject the elements into the document + callback.call( obj, elem ); + } + }); + + scripts.each( evalScript ); + }); + } +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +function evalScript( i, elem ) { + if ( elem.src ) + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild( elem ); +} + +function now(){ + return +new Date; +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; + + // Handle a deep copy situation + if ( target.constructor == Boolean ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target != "object" && typeof target != "function" ) + target = {}; + + // extend jQuery itself if only one argument is passed + if ( length == i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) + // Extend the base object + for ( var name in options ) { + var src = target[ name ], copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) + continue; + + // Recurse if we're merging object values + if ( deep && copy && typeof copy == "object" && !copy.nodeType ) + target[ name ] = jQuery.extend( deep, + // Never move original objects, clone them + src || ( copy.length != null ? [ ] : { } ) + , copy ); + + // Don't bring in undefined values + else if ( copy !== undefined ) + target[ name ] = copy; + + } + + // Return the modified object + return target; +}; + +var expando = "jQuery" + now(), uuid = 0, windowData = {}, + // exclude the following css properties to add px + exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, + // cache defaultView + defaultView = document.defaultView || {}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // See test/unit/core.js for details concerning this function. + isFunction: function( fn ) { + return !!fn && typeof fn != "string" && !fn.nodeName && + fn.constructor != Array && /^[\s[]?function/.test( fn + "" ); + }, + + // check if an element is in a (or is an) XML document + isXMLDoc: function( elem ) { + return elem.documentElement && !elem.body || + elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + data = jQuery.trim( data ); + + if ( data ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.browser.msie ) + script.text = data; + else + script.appendChild( document.createTextNode( data ) ); + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + cache: {}, + + data: function( elem, name, data ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // Compute a unique ID for the element + if ( !id ) + id = elem[ expando ] = ++uuid; + + // Only generate the data cache if we're + // trying to access or manipulate it + if ( name && !jQuery.cache[ id ] ) + jQuery.cache[ id ] = {}; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) + jQuery.cache[ id ][ name ] = data; + + // Return the named cache data, or the ID for the element + return name ? + jQuery.cache[ id ][ name ] : + id; + }, + + removeData: function( elem, name ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( jQuery.cache[ id ] ) { + // Remove the section of cache data + delete jQuery.cache[ id ][ name ]; + + // If we've removed all the data, remove the element's cache + name = ""; + + for ( name in jQuery.cache[ id ] ) + break; + + if ( !name ) + jQuery.removeData( elem ); + } + + // Otherwise, we want to remove all of the element's data + } else { + // Clean up the element expando + try { + delete elem[ expando ]; + } catch(e){ + // IE has trouble directly removing the expando + // but it's ok with using removeAttribute + if ( elem.removeAttribute ) + elem.removeAttribute( expando ); + } + + // Completely remove the data cache + delete jQuery.cache[ id ]; + } + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, length = object.length; + + if ( args ) { + if ( length == undefined ) { + for ( name in object ) + if ( callback.apply( object[ name ], args ) === false ) + break; + } else + for ( ; i < length; ) + if ( callback.apply( object[ i++ ], args ) === false ) + break; + + // A special, fast, case for the most common use of each + } else { + if ( length == undefined ) { + for ( name in object ) + if ( callback.call( object[ name ], name, object[ name ] ) === false ) + break; + } else + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + prop: function( elem, value, type, i, name ) { + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, i ); + + // Handle passing in a number to a CSS property + return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, classNames ) { + jQuery.each((classNames || "").split(/\s+/), function(i, className){ + if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) + elem.className += (elem.className ? " " : "") + className; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, classNames ) { + if (elem.nodeType == 1) + elem.className = classNames != undefined ? + jQuery.grep(elem.className.split(/\s+/), function(className){ + return !jQuery.className.has( classNames, className ); + }).join(" ") : + ""; + }, + + // internal only, use hasClass("class") + has: function( elem, className ) { + return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + css: function( elem, name, force ) { + if ( name == "width" || name == "height" ) { + var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; + + function getWH() { + val = name == "width" ? elem.offsetWidth : elem.offsetHeight; + var padding = 0, border = 0; + jQuery.each( which, function() { + padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + }); + val -= Math.round(padding + border); + } + + if ( jQuery(elem).is(":visible") ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, val); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret, style = elem.style; + + // A helper method for determining if an element's values are broken + function color( elem ) { + if ( !jQuery.browser.safari ) + return false; + + // defaultView is cached + var ret = defaultView.getComputedStyle( elem, null ); + return !ret || ret.getPropertyValue("color") == ""; + } + + // We need to handle opacity special in IE + if ( name == "opacity" && jQuery.browser.msie ) { + ret = jQuery.attr( style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + // Opera sometimes will give the wrong display answer, this fixes it, see #2037 + if ( jQuery.browser.opera && name == "display" ) { + var save = style.outline; + style.outline = "0 solid black"; + style.outline = save; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && style && style[ name ] ) + ret = style[ name ]; + + else if ( defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var computedStyle = defaultView.getComputedStyle( elem, null ); + + if ( computedStyle && !color( elem ) ) + ret = computedStyle.getPropertyValue( name ); + + // If the element isn't reporting its values properly in Safari + // then some display: none elements are involved + else { + var swap = [], stack = [], a = elem, i = 0; + + // Locate all of the parent display: none elements + for ( ; a && color(a); a = a.parentNode ) + stack.unshift(a); + + // Go through and make them visible, but in reverse + // (It would be better if we knew the exact display type that they had) + for ( ; i < stack.length; i++ ) + if ( color( stack[ i ] ) ) { + swap[ i ] = stack[ i ].style.display; + stack[ i ].style.display = "block"; + } + + // Since we flip the display style, we have to handle that + // one special, otherwise get the value + ret = name == "display" && swap[ stack.length - 1 ] != null ? + "none" : + ( computedStyle && computedStyle.getPropertyValue( name ) ) || ""; + + // Finally, revert the display styles back + for ( i = 0; i < swap.length; i++ ) + if ( swap[ i ] != null ) + stack[ i ].style.display = swap[ i ]; + } + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var left = style.left, rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = ret || 0; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + } + + return ret; + }, + + clean: function( elems, context ) { + var ret = []; + context = context || document; + // !context.createElement fails in IE with an error but returns typeof 'object' + if (typeof context.createElement == 'undefined') + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + jQuery.each(elems, function(i, elem){ + if ( !elem ) + return; + + if ( elem.constructor == Number ) + elem += ''; + + // Convert html string into DOM nodes + if ( typeof elem == "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? + all : + front + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); + + var wrap = + // option or optgroup + !tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "", "
                                      " ] || + + !tags.indexOf("", "" ] || + + // matched above + (!tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + // IE can't serialize and "; + echo ""; + echo ""; + echo ""; +} + + +/* + * Formats call counts for XHProf reports. + * + * Description: + * Call counts in single-run reports are integer values. + * However, call counts for aggregated reports can be + * fractional. This function will print integer values + * without decimal point, but with commas etc. + * + * 4000 ==> 4,000 + * + * It'll round fractional values to decimal precision of 3 + * 4000.1212 ==> 4,000.121 + * 4000.0001 ==> 4,000 + * + */ +function xhprof_count_format($num) { + $num = round($num, 3); + if (round($num) == $num) { + return number_format($num); + } else { + return number_format($num, 3); + } +} + +function xhprof_percent_format($s, $precision = 1) { + return sprintf('%.'.$precision.'f%%', 100 * $s); +} + +/** + * Implodes the text for a bunch of actions (such as links, forms, + * into a HTML list and returns the text. + */ +function xhprof_render_actions($actions) { + $out = array(); + + if (count($actions)) { + $out[] = '
                                        '; + foreach ($actions as $action) { + $out[] = '
                                      • '.$action.'
                                      • '; + } + $out[] = '
                                      '; + } + + return implode('', $out); +} + + +/** + * @param html-str $content the text/image/innerhtml/whatever for the link + * @param raw-str $href + * @param raw-str $class + * @param raw-str $id + * @param raw-str $title + * @param raw-str $target + * @param raw-str $onclick + * @param raw-str $style + * @param raw-str $access + * @param raw-str $onmouseover + * @param raw-str $onmouseout + * @param raw-str $onmousedown + * @param raw-str $dir + * @param raw-str $rel + */ +function xhprof_render_link($content, $href, $class='', $id='', $title='', + $target='', + $onclick='', $style='', $access='', $onmouseover='', + $onmouseout='', $onmousedown='') { + + if (!$content) { + return ''; + } + + if ($href) { + $link = ' 1, + "ct" => 1, + "wt" => 1, + "excl_wt" => 1, + "ut" => 1, + "excl_ut" => 1, + "st" => 1, + "excl_st" => 1, + "mu" => 1, + "excl_mu" => 1, + "pmu" => 1, + "excl_pmu" => 1, + "cpu" => 1, + "excl_cpu" => 1, + "samples" => 1, + "excl_samples" => 1 + ); + +// Textual descriptions for column headers in "single run" mode +$descriptions = array( + "fn" => "Function Name", + "ct" => "Calls", + "Calls%" => "Calls%", + + "wt" => "Incl. Wall Time
                                      (microsec)", + "IWall%" => "IWall%", + "excl_wt" => "Excl. Wall Time
                                      (microsec)", + "EWall%" => "EWall%", + + "ut" => "Incl. User
                                      (microsecs)", + "IUser%" => "IUser%", + "excl_ut" => "Excl. User
                                      (microsec)", + "EUser%" => "EUser%", + + "st" => "Incl. Sys
                                      (microsec)", + "ISys%" => "ISys%", + "excl_st" => "Excl. Sys
                                      (microsec)", + "ESys%" => "ESys%", + + "cpu" => "Incl. CPU
                                      (microsecs)", + "ICpu%" => "ICpu%", + "excl_cpu" => "Excl. CPU
                                      (microsec)", + "ECpu%" => "ECPU%", + + "mu" => "Incl.
                                      MemUse
                                      (bytes)", + "IMUse%" => "IMemUse%", + "excl_mu" => "Excl.
                                      MemUse
                                      (bytes)", + "EMUse%" => "EMemUse%", + + "pmu" => "Incl.
                                      PeakMemUse
                                      (bytes)", + "IPMUse%" => "IPeakMemUse%", + "excl_pmu" => "Excl.
                                      PeakMemUse
                                      (bytes)", + "EPMUse%" => "EPeakMemUse%", + + "samples" => "Incl. Samples", + "ISamples%" => "ISamples%", + "excl_samples" => "Excl. Samples", + "ESamples%" => "ESamples%", + ); + +// Formatting Callback Functions... +$format_cbk = array( + "fn" => "", + "ct" => "xhprof_count_format", + "Calls%" => "xhprof_percent_format", + + "wt" => "number_format", + "IWall%" => "xhprof_percent_format", + "excl_wt" => "number_format", + "EWall%" => "xhprof_percent_format", + + "ut" => "number_format", + "IUser%" => "xhprof_percent_format", + "excl_ut" => "number_format", + "EUser%" => "xhprof_percent_format", + + "st" => "number_format", + "ISys%" => "xhprof_percent_format", + "excl_st" => "number_format", + "ESys%" => "xhprof_percent_format", + + "cpu" => "number_format", + "ICpu%" => "xhprof_percent_format", + "excl_cpu" => "number_format", + "ECpu%" => "xhprof_percent_format", + + "mu" => "number_format", + "IMUse%" => "xhprof_percent_format", + "excl_mu" => "number_format", + "EMUse%" => "xhprof_percent_format", + + "pmu" => "number_format", + "IPMUse%" => "xhprof_percent_format", + "excl_pmu" => "number_format", + "EPMUse%" => "xhprof_percent_format", + + "samples" => "number_format", + "ISamples%" => "xhprof_percent_format", + "excl_samples" => "number_format", + "ESamples%" => "xhprof_percent_format", + ); + + +// Textual descriptions for column headers in "diff" mode +$diff_descriptions = array( + "fn" => "Function Name", + "ct" => "Calls Diff", + "Calls%" => "Calls
                                      Diff%", + + "wt" => "Incl. Wall
                                      Diff
                                      (microsec)", + "IWall%" => "IWall
                                      Diff%", + "excl_wt" => "Excl. Wall
                                      Diff
                                      (microsec)", + "EWall%" => "EWall
                                      Diff%", + + "ut" => "Incl. User Diff
                                      (microsec)", + "IUser%" => "IUser
                                      Diff%", + "excl_ut" => "Excl. User
                                      Diff
                                      (microsec)", + "EUser%" => "EUser
                                      Diff%", + + "cpu" => "Incl. CPU Diff
                                      (microsec)", + "ICpu%" => "ICpu
                                      Diff%", + "excl_cpu" => "Excl. CPU
                                      Diff
                                      (microsec)", + "ECpu%" => "ECpu
                                      Diff%", + + "st" => "Incl. Sys Diff
                                      (microsec)", + "ISys%" => "ISys
                                      Diff%", + "excl_st" => "Excl. Sys Diff
                                      (microsec)", + "ESys%" => "ESys
                                      Diff%", + + "mu" => "Incl.
                                      MemUse
                                      Diff
                                      (bytes)", + "IMUse%" => "IMemUse
                                      Diff%", + "excl_mu" => "Excl.
                                      MemUse
                                      Diff
                                      (bytes)", + "EMUse%" => "EMemUse
                                      Diff%", + + "pmu" => "Incl.
                                      PeakMemUse
                                      Diff
                                      (bytes)", + "IPMUse%" => "IPeakMemUse
                                      Diff%", + "excl_pmu" => "Excl.
                                      PeakMemUse
                                      Diff
                                      (bytes)", + "EPMUse%" => "EPeakMemUse
                                      Diff%", + + "samples" => "Incl. Samples Diff", + "ISamples%" => "ISamples Diff%", + "excl_samples" => "Excl. Samples Diff", + "ESamples%" => "ESamples Diff%", + ); + +// columns that'll be displayed in a top-level report +$stats = array(); + +// columns that'll be displayed in a function's parent/child report +$pc_stats = array(); + +// Various total counts +$totals = 0; +$totals_1 = 0; +$totals_2 = 0; + +/* + * The subset of $possible_metrics that is present in the raw profile data. + */ +$metrics = null; + +/** + * Callback comparison operator (passed to usort() for sorting array of + * tuples) that compares array elements based on the sort column + * specified in $sort_col (global parameter). + * + * @author Kannan + */ +function sort_cbk($a, $b) { + global $sort_col; + global $diff_mode; + + if ($sort_col == "fn") { + + // case insensitive ascending sort for function names + $left = strtoupper($a["fn"]); + $right = strtoupper($b["fn"]); + + if ($left == $right) + return 0; + return ($left < $right) ? -1 : 1; + + } else { + + // descending sort for all others + $left = $a[$sort_col]; + $right = $b[$sort_col]; + + // if diff mode, sort by absolute value of regression/improvement + if ($diff_mode) { + $left = abs($left); + $right = abs($right); + } + + if ($left == $right) + return 0; + return ($left > $right) ? -1 : 1; + } +} + +/** + * Get the appropriate description for a statistic + * (depending upon whether we are in diff report mode + * or single run report mode). + * + * @author Kannan + */ +function stat_description($stat) { + global $descriptions; + global $diff_descriptions; + global $diff_mode; + + if ($diff_mode) { + return $diff_descriptions[$stat]; + } else { + return $descriptions[$stat]; + } +} + + +/** + * Analyze raw data & generate the profiler report + * (common for both single run mode and diff mode). + * + * @author: Kannan + */ +function profiler_report ($url_params, + $rep_symbol, + $sort, + $run1, + $run1_desc, + $run1_data, + $run2 = 0, + $run2_desc = "", + $run2_data = array()) { + global $totals; + global $totals_1; + global $totals_2; + global $stats; + global $pc_stats; + global $diff_mode; + global $base_path; + + // if we are reporting on a specific function, we can trim down + // the report(s) to just stuff that is relevant to this function. + // That way compute_flat_info()/compute_diff() etc. do not have + // to needlessly work hard on churning irrelevant data. + if (!empty($rep_symbol)) { + $run1_data = xhprof_trim_run($run1_data, array($rep_symbol)); + if ($diff_mode) { + $run2_data = xhprof_trim_run($run2_data, array($rep_symbol)); + } + } + + if ($diff_mode) { + $run_delta = xhprof_compute_diff($run1_data, $run2_data); + $symbol_tab = xhprof_compute_flat_info($run_delta, $totals); + $symbol_tab1 = xhprof_compute_flat_info($run1_data, $totals_1); + $symbol_tab2 = xhprof_compute_flat_info($run2_data, $totals_2); + } else { + $symbol_tab = xhprof_compute_flat_info($run1_data, $totals); + } + + $run1_txt = sprintf("Run #%s: %s", + $run1, $run1_desc); + + $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, + 'symbol'), + 'all'); + + $top_link_query_string = "$base_path/?" . http_build_query($base_url_params); + + if ($diff_mode) { + $diff_text = "Diff"; + $base_url_params = xhprof_array_unset($base_url_params, 'run1'); + $base_url_params = xhprof_array_unset($base_url_params, 'run2'); + $run1_link = xhprof_render_link('View Run #' . $run1, + "$base_path/?" . + http_build_query(xhprof_array_set($base_url_params, + 'run', + $run1))); + $run2_txt = sprintf("Run #%s: %s", + $run2, $run2_desc); + + $run2_link = xhprof_render_link('View Run #' . $run2, + "$base_path/?" . + http_build_query(xhprof_array_set($base_url_params, + 'run', + $run2))); + } else { + $diff_text = "Run"; + } + + // set up the action links for operations that can be done on this report + $links = array(); + $links [] = xhprof_render_link("View Top Level $diff_text Report", + $top_link_query_string); + + if ($diff_mode) { + $inverted_params = $url_params; + $inverted_params['run1'] = $url_params['run2']; + $inverted_params['run2'] = $url_params['run1']; + + // view the different runs or invert the current diff + $links [] = $run1_link; + $links [] = $run2_link; + $links [] = xhprof_render_link('Invert ' . $diff_text . ' Report', + "$base_path/?". + http_build_query($inverted_params)); + } + + // lookup function typeahead form + $links [] = ''; + + echo xhprof_render_actions($links); + + + echo + '
                                      ' . + '
                                      ' . $diff_text . ' Report
                                      ' . + '
                                      ' . ($diff_mode ? + $run1_txt . '
                                      vs.
                                      ' . $run2_txt : + $run1_txt) . + '
                                      ' . + '
                                      Tip
                                      ' . + '
                                      Click a function name below to drill down.
                                      ' . + '
                                      ' . + '
                                      '; + + // data tables + if (!empty($rep_symbol)) { + if (!isset($symbol_tab[$rep_symbol])) { + echo "
                                      Symbol $rep_symbol not found in XHProf run
                                      "; + return; + } + + /* single function report with parent/child information */ + if ($diff_mode) { + $info1 = isset($symbol_tab1[$rep_symbol]) ? + $symbol_tab1[$rep_symbol] : null; + $info2 = isset($symbol_tab2[$rep_symbol]) ? + $symbol_tab2[$rep_symbol] : null; + symbol_report($url_params, $run_delta, $symbol_tab[$rep_symbol], + $sort, $rep_symbol, + $run1, $info1, + $run2, $info2); + } else { + symbol_report($url_params, $run1_data, $symbol_tab[$rep_symbol], + $sort, $rep_symbol, $run1); + } + } else { + /* flat top-level report of all functions */ + full_report($url_params, $symbol_tab, $sort, $run1, $run2); + } +} + +/** + * Computes percentage for a pair of values, and returns it + * in string format. + */ +function pct($a, $b) { + if ($b == 0) { + return "N/A"; + } else { + $res = (round(($a * 1000 / $b)) / 10); + return $res; + } +} + +/** + * Given a number, returns the td class to use for display. + * + * For instance, negative numbers in diff reports comparing two runs (run1 & run2) + * represent improvement from run1 to run2. We use green to display those deltas, + * and red for regression deltas. + */ +function get_print_class($num, $bold) { + global $vbar; + global $vbbar; + global $vrbar; + global $vgbar; + global $diff_mode; + + if ($bold) { + if ($diff_mode) { + if ($num <= 0) { + $class = $vgbar; // green (improvement) + } else { + $class = $vrbar; // red (regression) + } + } else { + $class = $vbbar; // blue + } + } + else { + $class = $vbar; // default (black) + } + + return $class; +} + +/** + * Prints a element with a numeric value. + */ +function print_td_num($num, $fmt_func, $bold=false, $attributes=null) { + + $class = get_print_class($num, $bold); + + if (!empty($fmt_func) && is_numeric($num) ) { + $num = call_user_func($fmt_func, $num); + } + + print("$num\n"); +} + +/** + * Prints a element with a pecentage. + */ +function print_td_pct($numer, $denom, $bold=false, $attributes=null) { + global $vbar; + global $vbbar; + global $diff_mode; + + $class = get_print_class($numer, $bold); + + if ($denom == 0) { + $pct = "N/A%"; + } else { + $pct = xhprof_percent_format($numer / abs($denom)); + } + + print("$pct\n"); +} + +/** + * Print "flat" data corresponding to one function. + * + * @author Kannan + */ +function print_function_info($url_params, $info, $sort, $run1, $run2) { + static $odd_even = 0; + + global $totals; + global $sort_col; + global $metrics; + global $format_cbk; + global $display_calls; + global $base_path; + + // Toggle $odd_or_even + $odd_even = 1 - $odd_even; + + if ($odd_even) { + print(""); + } + else { + print(''); + } + + $href = "$base_path/?" . + http_build_query(xhprof_array_set($url_params, + 'symbol', $info["fn"])); + + print(''); + print(xhprof_render_link($info["fn"], $href)); + print_source_link($info); + print("\n"); + + if ($display_calls) { + // Call Count.. + print_td_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct")); + print_td_pct($info["ct"], $totals["ct"], ($sort_col == "ct")); + } + + // Other metrics.. + foreach ($metrics as $metric) { + // Inclusive metric + print_td_num($info[$metric], $format_cbk[$metric], + ($sort_col == $metric)); + print_td_pct($info[$metric], $totals[$metric], + ($sort_col == $metric)); + + // Exclusive Metric + print_td_num($info["excl_" . $metric], + $format_cbk["excl_" . $metric], + ($sort_col == "excl_" . $metric)); + print_td_pct($info["excl_" . $metric], + $totals[$metric], + ($sort_col == "excl_" . $metric)); + } + + print("\n"); +} + +/** + * Print non-hierarchical (flat-view) of profiler data. + * + * @author Kannan + */ +function print_flat_data($url_params, $title, $flat_data, $sort, $run1, $run2, $limit) { + + global $stats; + global $sortable_columns; + global $vwbar; + global $base_path; + + $size = count($flat_data); + if (!$limit) { // no limit + $limit = $size; + $display_link = ""; + } else { + $display_link = xhprof_render_link(" [ display all ]", + "$base_path/?" . + http_build_query(xhprof_array_set($url_params, + 'all', 1))); + } + + print("

                                      $title $display_link


                                      "); + + print(''); + print(''); + + foreach ($stats as $stat) { + $desc = stat_description($stat); + if (array_key_exists($stat, $sortable_columns)) { + $href = "$base_path/?" + . http_build_query(xhprof_array_set($url_params, 'sort', $stat)); + $header = xhprof_render_link($desc, $href); + } else { + $header = $desc; + } + + if ($stat == "fn") + print(""); + else print(""); + } + print("\n"); + + if ($limit >= 0) { + $limit = min($size, $limit); + for ($i = 0; $i < $limit; $i++) { + print_function_info($url_params, $flat_data[$i], $sort, $run1, $run2); + } + } else { + // if $limit is negative, print abs($limit) items starting from the end + $limit = min($size, abs($limit)); + for ($i = 0; $i < $limit; $i++) { + print_function_info($url_params, $flat_data[$size - $i - 1], $sort, $run1, $run2); + } + } + print("
                                      $header$header
                                      "); + + // let's print the display all link at the bottom as well... + if ($display_link) { + echo '
                                      ' . $display_link . '
                                      '; + } + +} + +/** + * Generates a tabular report for all functions. This is the top-level report. + * + * @author Kannan + */ +function full_report($url_params, $symbol_tab, $sort, $run1, $run2) { + global $vwbar; + global $vbar; + global $totals; + global $totals_1; + global $totals_2; + global $metrics; + global $diff_mode; + global $descriptions; + global $sort_col; + global $format_cbk; + global $display_calls; + global $base_path; + + $possible_metrics = xhprof_get_possible_metrics(); + + if ($diff_mode) { + + $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, + 'run1'), + 'run2'); + $href1 = "$base_path/?" . + http_build_query(xhprof_array_set($base_url_params, + 'run', $run1)); + $href2 = "$base_path/?" . + http_build_query(xhprof_array_set($base_url_params, + 'run', $run2)); + + print("

                                      Overall Diff Summary

                                      "); + print('' . "\n"); + print(''); + print(""); + print(""); + print(""); + print(""); + print(""); + print(''); + + if ($display_calls) { + print(''); + print(""); + print_td_num($totals_1["ct"], $format_cbk["ct"]); + print_td_num($totals_2["ct"], $format_cbk["ct"]); + print_td_num($totals_2["ct"] - $totals_1["ct"], $format_cbk["ct"], true); + print_td_pct($totals_2["ct"] - $totals_1["ct"], $totals_1["ct"], true); + print(''); + } + + foreach ($metrics as $metric) { + $m = $metric; + print(''); + print(""); + print_td_num($totals_1[$m], $format_cbk[$m]); + print_td_num($totals_2[$m], $format_cbk[$m]); + print_td_num($totals_2[$m] - $totals_1[$m], $format_cbk[$m], true); + print_td_pct($totals_2[$m] - $totals_1[$m], $totals_1[$m], true); + print(''); + } + print('
                                      " . xhprof_render_link("Run #$run1", $href1) . "" . xhprof_render_link("Run #$run2", $href2) . "DiffDiff%
                                      Number of Function Calls
                                      " . str_replace("
                                      ", " ", $descriptions[$m]) . "
                                      '); + + $callgraph_report_title = '[View Regressions/Improvements using Callgraph Diff]'; + + } else { + print("

                                      \n"); + + print('' . "\n"); + echo ""; + echo ""; + echo ""; + echo ""; + + foreach ($metrics as $metric) { + echo ""; + echo ""; + echo ""; + echo ""; + } + + if ($display_calls) { + echo ""; + echo ""; + echo ""; + echo ""; + } + + echo "
                                      Overall Summary
                                      Total " + . str_replace("
                                      ", " ", stat_description($metric)) . ":
                                      " . number_format($totals[$metric]) . " " + . $possible_metrics[$metric][1] . "
                                      Number of Function Calls:" . number_format($totals['ct']) . "
                                      "; + print("

                                      \n"); + + $callgraph_report_title = '[View Full Callgraph]'; + } + + print("

                                      " . + xhprof_render_link($callgraph_report_title, + "$base_path/callgraph.php" . "?" . http_build_query($url_params)) + . "

                                      "); + + + $flat_data = array(); + foreach ($symbol_tab as $symbol => $info) { + $tmp = $info; + $tmp["fn"] = $symbol; + $flat_data[] = $tmp; + } + usort($flat_data, 'sort_cbk'); + + print("
                                      "); + + if (!empty($url_params['all'])) { + $all = true; + $limit = 0; // display all rows + } else { + $all = false; + $limit = 100; // display only limited number of rows + } + + $desc = str_replace("
                                      ", " ", $descriptions[$sort_col]); + + if ($diff_mode) { + if ($all) { + $title = "Total Diff Report: ' + .'Sorted by absolute value of regression/improvement in $desc"; + } else { + $title = "Top 100 Regressions/" + . "Improvements: " + . "Sorted by $desc Diff"; + } + } else { + if ($all) { + $title = "Sorted by $desc"; + } else { + $title = "Displaying top $limit functions: Sorted by $desc"; + } + } + print_flat_data($url_params, $title, $flat_data, $sort, $run1, $run2, $limit); +} + + +/** + * Return attribute names and values to be used by javascript tooltip. + */ +function get_tooltip_attributes($type, $metric) { + return "type='$type' metric='$metric'"; +} + +/** + * Print info for a parent or child function in the + * parent & children report. + * + * @author Kannan + */ +function pc_info($info, $base_ct, $base_info, $parent) { + global $sort_col; + global $metrics; + global $format_cbk; + global $display_calls; + + if ($parent) + $type = "Parent"; + else $type = "Child"; + + if ($display_calls) { + $mouseoverct = get_tooltip_attributes($type, "ct"); + /* call count */ + print_td_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct"), $mouseoverct); + print_td_pct($info["ct"], $base_ct, ($sort_col == "ct"), $mouseoverct); + } + + /* Inclusive metric values */ + foreach ($metrics as $metric) { + print_td_num($info[$metric], $format_cbk[$metric], + ($sort_col == $metric), + get_tooltip_attributes($type, $metric)); + print_td_pct($info[$metric], $base_info[$metric], ($sort_col == $metric), + get_tooltip_attributes($type, $metric)); + } +} + +function print_pc_array($url_params, $results, $base_ct, $base_info, $parent, + $run1, $run2) { + global $base_path; + + // Construct section title + if ($parent) { + $title = 'Parent function'; + } + else { + $title = 'Child function'; + } + if (count($results) > 1) { + $title .= 's'; + } + + print(""); + print("
                                      " . $title . "
                                      "); + print(""); + + $odd_even = 0; + foreach ($results as $info) { + $href = "$base_path/?" . + http_build_query(xhprof_array_set($url_params, + 'symbol', $info["fn"])); + + $odd_even = 1 - $odd_even; + + if ($odd_even) { + print(''); + } + else { + print(''); + } + + print("" . xhprof_render_link($info["fn"], $href)); + print_source_link($info); + print(""); + pc_info($info, $base_ct, $base_info, $parent); + print(""); + } +} + +function print_source_link($info) { + if (strncmp($info['fn'], 'run_init', 8) && $info['fn'] !== 'main()') { + if (defined('XHPROF_SYMBOL_LOOKUP_URL')) { + $link = xhprof_render_link( + 'source', + XHPROF_SYMBOL_LOOKUP_URL . '?symbol='.rawurlencode($info["fn"])); + print(' ('.$link.')'); + } + } +} + + +function print_symbol_summary($symbol_info, $stat, $base) { + + $val = $symbol_info[$stat]; + $desc = str_replace("
                                      ", " ", stat_description($stat)); + + print("$desc: "); + print(number_format($val)); + print(" (" . pct($val, $base) . "% of overall)"); + if (substr($stat, 0, 4) == "excl") { + $func_base = $symbol_info[str_replace("excl_", "", $stat)]; + print(" (" . pct($val, $func_base) . "% of this function)"); + } + print("
                                      "); +} + +/** + * Generates a report for a single function/symbol. + * + * @author Kannan + */ +function symbol_report($url_params, + $run_data, $symbol_info, $sort, $rep_symbol, + $run1, + $symbol_info1 = null, + $run2 = 0, + $symbol_info2 = null) { + global $vwbar; + global $vbar; + global $totals; + global $pc_stats; + global $sortable_columns; + global $metrics; + global $diff_mode; + global $descriptions; + global $format_cbk; + global $sort_col; + global $display_calls; + global $base_path; + + $possible_metrics = xhprof_get_possible_metrics(); + + if ($diff_mode) { + $diff_text = "Diff"; + $regr_impr = "Regression/Improvement"; + } else { + $diff_text = ""; + $regr_impr = ""; + } + + if ($diff_mode) { + + $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, + 'run1'), + 'run2'); + $href1 = "$base_path?" + . http_build_query(xhprof_array_set($base_url_params, 'run', $run1)); + $href2 = "$base_path?" + . http_build_query(xhprof_array_set($base_url_params, 'run', $run2)); + + print("

                                      $regr_impr summary for $rep_symbol

                                      "); + print('' . "\n"); + print(''); + print(""); + print(""); + print(""); + print(""); + print(""); + print(''); + print(''); + + if ($display_calls) { + print(""); + print_td_num($symbol_info1["ct"], $format_cbk["ct"]); + print_td_num($symbol_info2["ct"], $format_cbk["ct"]); + print_td_num($symbol_info2["ct"] - $symbol_info1["ct"], + $format_cbk["ct"], true); + print_td_pct($symbol_info2["ct"] - $symbol_info1["ct"], + $symbol_info1["ct"], true); + print(''); + } + + + foreach ($metrics as $metric) { + $m = $metric; + + // Inclusive stat for metric + print(''); + print(""); + print_td_num($symbol_info1[$m], $format_cbk[$m]); + print_td_num($symbol_info2[$m], $format_cbk[$m]); + print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true); + print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true); + print(''); + + // AVG (per call) Inclusive stat for metric + print(''); + print(""); + $avg_info1 = 'N/A'; + $avg_info2 = 'N/A'; + if ($symbol_info1['ct'] > 0) { + $avg_info1 = ($symbol_info1[$m] / $symbol_info1['ct']); + } + if ($symbol_info2['ct'] > 0) { + $avg_info2 = ($symbol_info2[$m] / $symbol_info2['ct']); + } + print_td_num($avg_info1, $format_cbk[$m]); + print_td_num($avg_info2, $format_cbk[$m]); + print_td_num($avg_info2 - $avg_info1, $format_cbk[$m], true); + print_td_pct($avg_info2 - $avg_info1, $avg_info1, true); + print(''); + + // Exclusive stat for metric + $m = "excl_" . $metric; + print(''); + print(""); + print_td_num($symbol_info1[$m], $format_cbk[$m]); + print_td_num($symbol_info2[$m], $format_cbk[$m]); + print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true); + print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true); + print(''); + } + + print('
                                      $rep_symbolRun #$run1Run #$run2DiffDiff%
                                      Number of Function Calls
                                      " . str_replace("
                                      ", " ", $descriptions[$m]) . "
                                      " . str_replace("
                                      ", " ", $descriptions[$m]) . " per call
                                      " . str_replace("
                                      ", " ", $descriptions[$m]) . "
                                      '); + } + + print("

                                      "); + print("Parent/Child $regr_impr report for $rep_symbol"); + + $callgraph_href = "$base_path/callgraph.php?" + . http_build_query(xhprof_array_set($url_params, 'func', $rep_symbol)); + + print(" [View Callgraph $diff_text]
                                      "); + + print("


                                      "); + + print('' . "\n"); + print(''); + + foreach ($pc_stats as $stat) { + $desc = stat_description($stat); + if (array_key_exists($stat, $sortable_columns)) { + + $href = "$base_path/?" . + http_build_query(xhprof_array_set($url_params, + 'sort', $stat)); + $header = xhprof_render_link($desc, $href); + } else { + $header = $desc; + } + + if ($stat == "fn") + print(""); + else print(""); + } + print(""); + + print(""); + + print(""); + // make this a self-reference to facilitate copy-pasting snippets to e-mails + print(""); + + if ($display_calls) { + // Call Count + print_td_num($symbol_info["ct"], $format_cbk["ct"]); + print_td_pct($symbol_info["ct"], $totals["ct"]); + } + + // Inclusive Metrics for current function + foreach ($metrics as $metric) { + print_td_num($symbol_info[$metric], $format_cbk[$metric], ($sort_col == $metric)); + print_td_pct($symbol_info[$metric], $totals[$metric], ($sort_col == $metric)); + } + print(""); + + print(""); + print(""); + + if ($display_calls) { + // Call Count + print(""); + print(""); + } + + // Exclusive Metrics for current function + foreach ($metrics as $metric) { + print_td_num($symbol_info["excl_" . $metric], $format_cbk["excl_" . $metric], + ($sort_col == $metric), + get_tooltip_attributes("Child", $metric)); + print_td_pct($symbol_info["excl_" . $metric], $symbol_info[$metric], + ($sort_col == $metric), + get_tooltip_attributes("Child", $metric)); + } + print(""); + + // list of callers/parent functions + $results = array(); + if ($display_calls) { + $base_ct = $symbol_info["ct"]; + } else { + $base_ct = 0; + } + foreach ($metrics as $metric) { + $base_info[$metric] = $symbol_info[$metric]; + } + foreach ($run_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if (($child == $rep_symbol) && ($parent)) { + $info_tmp = $info; + $info_tmp["fn"] = $parent; + $results[] = $info_tmp; + } + } + usort($results, 'sort_cbk'); + + if (count($results) > 0) { + print_pc_array($url_params, $results, $base_ct, $base_info, true, + $run1, $run2); + } + + // list of callees/child functions + $results = array(); + $base_ct = 0; + foreach ($run_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if ($parent == $rep_symbol) { + $info_tmp = $info; + $info_tmp["fn"] = $child; + $results[] = $info_tmp; + if ($display_calls) { + $base_ct += $info["ct"]; + } + } + } + usort($results, 'sort_cbk'); + + if (count($results)) { + print_pc_array($url_params, $results, $base_ct, $base_info, false, + $run1, $run2); + } + + print("
                                      $header$header
                                      "); + print("
                                      Current Function
                                      "); + print("
                                      $rep_symbol"); + print_source_link(array('fn' => $rep_symbol)); + print("
                                      " + ."Exclusive Metrics $diff_text for Current Function
                                      "); + + // These will be used for pop-up tips/help. + // Related javascript code is in: xhprof_report.js + print("\n"); + print(''); + print("\n"); + +} + +/** + * Generate the profiler report for a single run. + * + * @author Kannan + */ +function profiler_single_run_report ($url_params, + $xhprof_data, + $run_desc, + $rep_symbol, + $sort, + $run) { + + init_metrics($xhprof_data, $rep_symbol, $sort, false); + + profiler_report($url_params, $rep_symbol, $sort, $run, $run_desc, + $xhprof_data); +} + + + +/** + * Generate the profiler report for diff mode (delta between two runs). + * + * @author Kannan + */ +function profiler_diff_report($url_params, + $xhprof_data1, + $run1_desc, + $xhprof_data2, + $run2_desc, + $rep_symbol, + $sort, + $run1, + $run2) { + + + // Initialize what metrics we'll display based on data in Run2 + init_metrics($xhprof_data2, $rep_symbol, $sort, true); + + profiler_report($url_params, + $rep_symbol, + $sort, + $run1, + $run1_desc, + $xhprof_data1, + $run2, + $run2_desc, + $xhprof_data2); +} + + +/** + * Generate a XHProf Display View given the various URL parameters + * as arguments. The first argument is an object that implements + * the iXHProfRuns interface. + * + * @param object $xhprof_runs_impl An object that implements + * the iXHProfRuns interface + *. + * @param array $url_params Array of non-default URL params. + * + * @param string $source Category/type of the run. The source in + * combination with the run id uniquely + * determines a profiler run. + * + * @param string $run run id, or comma separated sequence of + * run ids. The latter is used if an aggregate + * report of the runs is desired. + * + * @param string $wts Comma separate list of integers. + * Represents the weighted ratio in + * which which a set of runs will be + * aggregated. [Used only for aggregate + * reports.] + * + * @param string $symbol Function symbol. If non-empty then the + * parent/child view of this function is + * displayed. If empty, a flat-profile view + * of the functions is displayed. + * + * @param string $run1 Base run id (for diff reports) + * + * @param string $run2 New run id (for diff reports) + * + */ +function displayXHProfReport($xhprof_runs_impl, $url_params, $source, + $run, $wts, $symbol, $sort, $run1, $run2) { + + if ($run) { // specific run to display? + + // run may be a single run or a comma separate list of runs + // that'll be aggregated. If "wts" (a comma separated list + // of integral weights is specified), the runs will be + // aggregated in that ratio. + // + $runs_array = explode(",", $run); + + if (count($runs_array) == 1) { + $xhprof_data = $xhprof_runs_impl->get_run($runs_array[0], + $source, + $description); + } else { + if (!empty($wts)) { + $wts_array = explode(",", $wts); + } else { + $wts_array = null; + } + $data = xhprof_aggregate_runs($xhprof_runs_impl, + $runs_array, $wts_array, $source, false); + $xhprof_data = $data['raw']; + $description = $data['description']; + } + + + profiler_single_run_report($url_params, + $xhprof_data, + $description, + $symbol, + $sort, + $run); + + } else if ($run1 && $run2) { // diff report for two runs + + $xhprof_data1 = $xhprof_runs_impl->get_run($run1, $source, $description1); + $xhprof_data2 = $xhprof_runs_impl->get_run($run2, $source, $description2); + + profiler_diff_report($url_params, + $xhprof_data1, + $description1, + $xhprof_data2, + $description2, + $symbol, + $sort, + $run1, + $run2); + + } else { + echo "No XHProf runs specified in the URL."; + if (method_exists($xhprof_runs_impl, 'list_runs')) { + $xhprof_runs_impl->list_runs(); + } + } +} diff --git a/plugins/xhprof/lib/xhprof_lib/utils/callgraph_utils.php b/plugins/xhprof/lib/xhprof_lib/utils/callgraph_utils.php new file mode 100755 index 000000000..90f4c31a2 --- /dev/null +++ b/plugins/xhprof/lib/xhprof_lib/utils/callgraph_utils.php @@ -0,0 +1,486 @@ + 1, + "gif" => 1, + "png" => 1, + "svg" => 1, // support scalable vector graphic + "ps" => 1, + ); + +/** + * Send an HTTP header with the response. You MUST use this function instead + * of header() so that we can debug header issues because they're virtually + * impossible to debug otherwise. If you try to commit header(), SVN will + * reject your commit. + * + * @param string HTTP header name, like 'Location' + * @param string HTTP header value, like 'http://www.example.com/' + * + */ +function xhprof_http_header($name, $value) { + + if (!$name) { + xhprof_error('http_header usage'); + return null; + } + + if (!is_string($value)) { + xhprof_error('http_header value not a string'); + } + + header($name.': '.$value, true); +} + +/** + * Genearte and send MIME header for the output image to client browser. + * + * @author cjiang + */ +function xhprof_generate_mime_header($type, $length) { + switch ($type) { + case 'jpg': + $mime = 'image/jpeg'; + break; + case 'gif': + $mime = 'image/gif'; + break; + case 'png': + $mime = 'image/png'; + break; + case 'svg': + $mime = 'image/svg+xml'; // content type for scalable vector graphic + break; + case 'ps': + $mime = 'application/postscript'; + default: + $mime = false; + } + + if ($mime) { + xhprof_http_header('Content-type', $mime); + xhprof_http_header('Content-length', (string)$length); + } +} + +/** + * Generate image according to DOT script. This function will spawn a process + * with "dot" command and pipe the "dot_script" to it and pipe out the + * generated image content. + * + * @param dot_script, string, the script for DOT to generate the image. + * @param type, one of the supported image types, see + * $xhprof_legal_image_types. + * @returns, binary content of the generated image on success. empty string on + * failure. + * + * @author cjiang + */ +function xhprof_generate_image_by_dot($dot_script, $type) { + $descriptorspec = array( + // stdin is a pipe that the child will read from + 0 => array("pipe", "r"), + // stdout is a pipe that the child will write to + 1 => array("pipe", "w"), + // stderr is a pipe that the child will write to + 2 => array("pipe", "w") + ); + + $cmd = " dot -T".$type; + + $process = proc_open( $cmd, $descriptorspec, $pipes, sys_get_temp_dir(), array( 'PATH' => getenv( 'PATH' ) ) ); + if (is_resource($process)) { + fwrite($pipes[0], $dot_script); + fclose($pipes[0]); + + $output = stream_get_contents($pipes[1]); + + $err = stream_get_contents($pipes[2]); + if (!empty($err)) { + print "failed to execute cmd: \"$cmd\". stderr: `$err'\n"; + exit; + } + + fclose($pipes[2]); + fclose($pipes[1]); + proc_close($process); + return $output; + } + print "failed to execute cmd \"$cmd\""; + exit(); +} + +/* + * Get the children list of all nodes. + */ +function xhprof_get_children_table($raw_data) { + $children_table = array(); + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if (!isset($children_table[$parent])) { + $children_table[$parent] = array($child); + } else { + $children_table[$parent][] = $child; + } + } + return $children_table; +} + +/** + * Generate DOT script from the given raw phprof data. + * + * @param raw_data, phprof profile data. + * @param threshold, float, the threshold value [0,1). The functions in the + * raw_data whose exclusive wall times ratio are below the + * threshold will be filtered out and won't apprear in the + * generated image. + * @param page, string(optional), the root node name. This can be used to + * replace the 'main()' as the root node. + * @param func, string, the focus function. + * @param critical_path, bool, whether or not to display critical path with + * bold lines. + * @returns, string, the DOT script to generate image. + * + * @author cjiang + */ +function xhprof_generate_dot_script($raw_data, $threshold, $source, $page, + $func, $critical_path, $right=null, + $left=null) { + + $max_width = 5; + $max_height = 3.5; + $max_fontsize = 35; + $max_sizing_ratio = 20; + + $totals; + + if ($left === null) { + // init_metrics($raw_data, null, null); + } + $sym_table = xhprof_compute_flat_info($raw_data, $totals); + + if ($critical_path) { + $children_table = xhprof_get_children_table($raw_data); + $node = "main()"; + $path = array(); + $path_edges = array(); + $visited = array(); + while ($node) { + $visited[$node] = true; + if (isset($children_table[$node])) { + $max_child = null; + foreach ($children_table[$node] as $child) { + + if (isset($visited[$child])) { + continue; + } + if ($max_child === null || + abs($raw_data[xhprof_build_parent_child_key($node, + $child)]["wt"]) > + abs($raw_data[xhprof_build_parent_child_key($node, + $max_child)]["wt"])) { + $max_child = $child; + } + } + if ($max_child !== null) { + $path[$max_child] = true; + $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true; + } + $node = $max_child; + } else { + $node = null; + } + } + } + + // if it is a benchmark callgraph, we make the benchmarked function the root. + if ($source == "bm" && array_key_exists("main()", $sym_table)) { + $total_times = $sym_table["main()"]["ct"]; + $remove_funcs = array("main()", + "hotprofiler_disable", + "call_user_func_array", + "xhprof_disable"); + + foreach ($remove_funcs as $cur_del_func) { + if (array_key_exists($cur_del_func, $sym_table) && + $sym_table[$cur_del_func]["ct"] == $total_times) { + unset($sym_table[$cur_del_func]); + } + } + } + + // use the function to filter out irrelevant functions. + if (!empty($func)) { + $interested_funcs = array(); + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if ($parent == $func || $child == $func) { + $interested_funcs[$parent] = 1; + $interested_funcs[$child] = 1; + } + } + foreach ($sym_table as $symbol => $info) { + if (!array_key_exists($symbol, $interested_funcs)) { + unset($sym_table[$symbol]); + } + } + } + + $result = "digraph call_graph {\n"; + + // Filter out functions whose exclusive time ratio is below threshold, and + // also assign a unique integer id for each function to be generated. In the + // meantime, find the function with the most exclusive time (potentially the + // performance bottleneck). + $cur_id = 0; $max_wt = 0; + foreach ($sym_table as $symbol => $info) { + if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) { + unset($sym_table[$symbol]); + continue; + } + if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) { + $max_wt = abs($info["excl_wt"]); + } + $sym_table[$symbol]["id"] = $cur_id; + $cur_id ++; + } + + // Generate all nodes' information. + foreach ($sym_table as $symbol => $info) { + if ($info["excl_wt"] == 0) { + $sizing_factor = $max_sizing_ratio; + } else { + $sizing_factor = $max_wt / abs($info["excl_wt"]) ; + if ($sizing_factor > $max_sizing_ratio) { + $sizing_factor = $max_sizing_ratio; + } + } + $fillcolor = (($sizing_factor < 1.5) ? + ", style=filled, fillcolor=red" : ""); + + if ($critical_path) { + // highlight nodes along critical path. + if (!$fillcolor && array_key_exists($symbol, $path)) { + $fillcolor = ", style=filled, fillcolor=yellow"; + } + } + + $fontsize = ", fontsize=" + .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1)); + + $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor); + $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor); + + if ($symbol == "main()") { + $shape = "octagon"; + $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n"; + $name .= addslashes(isset($page) ? $page : $symbol); + } else { + $shape = "box"; + $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) . + " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")"; + } + if ($left === null) { + $label = ", label=\"".$name."\\nExcl: " + .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms (" + .sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"]) + . ")\\n".$info["ct"]." total calls\""; + } else { + if (isset($left[$symbol]) && isset($right[$symbol])) { + $label = ", label=\"".addslashes($symbol). + "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) + ." ms - " + .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = " + .(sprintf("%.3f",$info["wt"] / 1000.0))." ms". + "\\nExcl: " + .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) + ." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) + ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". + "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - " + .(sprintf("%.3f",$right[$symbol]["ct"]))." = " + .(sprintf("%.3f",$info["ct"]))."\""; + } else if (isset($left[$symbol])) { + $label = ", label=\"".addslashes($symbol). + "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) + ." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0)) + ." ms"."\\nExcl: " + .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) + ." ms - 0 ms = " + .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". + "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = " + .(sprintf("%.3f",$info["ct"]))."\""; + } else { + $label = ", label=\"".addslashes($symbol). + "\\nInc: 0 ms - " + .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0)) + ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms". + "\\nExcl: 0 ms - " + .(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) + ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". + "\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"])) + ." = ".(sprintf("%.3f",$info["ct"]))."\""; + } + } + $result .= "N" . $sym_table[$symbol]["id"]; + $result .= "[shape=$shape ".$label.$width + .$height.$fontsize.$fillcolor."];\n"; + } + + // Generate all the edges' information. + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if (isset($sym_table[$parent]) && isset($sym_table[$child]) && + (empty($func) || + (!empty($func) && ($parent == $func || $child == $func)))) { + + $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls"; + + $headlabel = $sym_table[$child]["wt"] > 0 ? + sprintf("%.1f%%", 100 * $info["wt"] + / $sym_table[$child]["wt"]) + : "0.0%"; + + $taillabel = ($sym_table[$parent]["wt"] > 0) ? + sprintf("%.1f%%", + 100 * $info["wt"] / + ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"])) + : "0.0%"; + + $linewidth = 1; + $arrow_size = 1; + + if ($critical_path && + isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) { + $linewidth = 10; $arrow_size = 2; + } + + $result .= "N" . $sym_table[$parent]["id"] . " -> N" + . $sym_table[$child]["id"]; + $result .= "[arrowsize=$arrow_size, color=grey, style=\"setlinewidth($linewidth)\"," + ." label=\"" + .$label."\", headlabel=\"".$headlabel + ."\", taillabel=\"".$taillabel."\" ]"; + $result .= ";\n"; + + } + } + $result = $result . "\n}"; + + return $result; +} + +function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, + $type, $threshold, $source) { + $total1; + $total2; + + $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); + $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); + + // init_metrics($raw_data1, null, null); + $children_table1 = xhprof_get_children_table($raw_data1); + $children_table2 = xhprof_get_children_table($raw_data2); + $symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1); + $symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2); + $run_delta = xhprof_compute_diff($raw_data1, $raw_data2); + $script = xhprof_generate_dot_script($run_delta, $threshold, $source, + null, null, true, + $symbol_tab1, $symbol_tab2); + $content = xhprof_generate_image_by_dot($script, $type); + + xhprof_generate_mime_header($type, strlen($content)); + echo $content; +} + +/** + * Generate image content from phprof run id. + * + * @param object $xhprof_runs_impl An object that implements + * the iXHProfRuns interface + * @param run_id, integer, the unique id for the phprof run, this is the + * primary key for phprof database table. + * @param type, string, one of the supported image types. See also + * $xhprof_legal_image_types. + * @param threshold, float, the threshold value [0,1). The functions in the + * raw_data whose exclusive wall times ratio are below the + * threshold will be filtered out and won't apprear in the + * generated image. + * @param func, string, the focus function. + * @returns, string, the DOT script to generate image. + * + * @author cjiang + */ +function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, + $threshold, $func, $source, + $critical_path) { + if (!$run_id) + return ""; + + $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); + if (!$raw_data) { + xhprof_error("Raw data is empty"); + return ""; + } + + $script = xhprof_generate_dot_script($raw_data, $threshold, $source, + $description, $func, $critical_path); + + $content = xhprof_generate_image_by_dot($script, $type); + return $content; +} + +/** + * Generate image from phprof run id and send it to client. + * + * @param object $xhprof_runs_impl An object that implements + * the iXHProfRuns interface + * @param run_id, integer, the unique id for the phprof run, this is the + * primary key for phprof database table. + * @param type, string, one of the supported image types. See also + * $xhprof_legal_image_types. + * @param threshold, float, the threshold value [0,1). The functions in the + * raw_data whose exclusive wall times ratio are below the + * threshold will be filtered out and won't apprear in the + * generated image. + * @param func, string, the focus function. + * @param bool, does this run correspond to a PHProfLive run or a dev run? + * @author cjiang + */ +function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold, + $func, $source, $critical_path) { + + $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, + $threshold, + $func, $source, $critical_path); + if (!$content) { + print "Error: either we can not find profile data for run_id ".$run_id + ." or the threshold ".$threshold." is too small or you do not" + ." have 'dot' image generation utility installed."; + exit(); + } + + xhprof_generate_mime_header($type, strlen($content)); + echo $content; +} diff --git a/plugins/xhprof/lib/xhprof_lib/utils/xhprof_lib.php b/plugins/xhprof/lib/xhprof_lib/utils/xhprof_lib.php new file mode 100755 index 000000000..4a07e900f --- /dev/null +++ b/plugins/xhprof/lib/xhprof_lib/utils/xhprof_lib.php @@ -0,0 +1,945 @@ + array("Wall", "microsecs", "walltime"), + "ut" => array("User", "microsecs", "user cpu time"), + "st" => array("Sys", "microsecs", "system cpu time"), + "cpu" => array("Cpu", "microsecs", "cpu time"), + "mu" => array("MUse", "bytes", "memory usage"), + "pmu" => array("PMUse", "bytes", "peak memory usage"), + "samples" => array("Samples", "samples", "cpu time")); + return $possible_metrics; +} + +/** + * Initialize the metrics we'll display based on the information + * in the raw data. + * + * @author Kannan + */ +function init_metrics($xhprof_data, $rep_symbol, $sort, $diff_report = false) { + global $stats; + global $pc_stats; + global $metrics; + global $diff_mode; + global $sortable_columns; + global $sort_col; + global $display_calls; + + $diff_mode = $diff_report; + + if (!empty($sort)) { + if (array_key_exists($sort, $sortable_columns)) { + $sort_col = $sort; + } else { + print("Invalid Sort Key $sort specified in URL"); + } + } + + // For C++ profiler runs, walltime attribute isn't present. + // In that case, use "samples" as the default sort column. + if (!isset($xhprof_data["main()"]["wt"])) { + + if ($sort_col == "wt") { + $sort_col = "samples"; + } + + // C++ profiler data doesn't have call counts. + // ideally we should check to see if "ct" metric + // is present for "main()". But currently "ct" + // metric is artificially set to 1. So, relying + // on absence of "wt" metric instead. + $display_calls = false; + } else { + $display_calls = true; + } + + // parent/child report doesn't support exclusive times yet. + // So, change sort hyperlinks to closest fit. + if (!empty($rep_symbol)) { + $sort_col = str_replace("excl_", "", $sort_col); + } + + if ($display_calls) { + $stats = array("fn", "ct", "Calls%"); + } else { + $stats = array("fn"); + } + + $pc_stats = $stats; + + $possible_metrics = xhprof_get_possible_metrics(); + foreach ($possible_metrics as $metric => $desc) { + if (isset($xhprof_data["main()"][$metric])) { + $metrics[] = $metric; + // flat (top-level reports): we can compute + // exclusive metrics reports as well. + $stats[] = $metric; + $stats[] = "I" . $desc[0] . "%"; + $stats[] = "excl_" . $metric; + $stats[] = "E" . $desc[0] . "%"; + + // parent/child report for a function: we can + // only breakdown inclusive times correctly. + $pc_stats[] = $metric; + $pc_stats[] = "I" . $desc[0] . "%"; + } + } +} + +/* + * Get the list of metrics present in $xhprof_data as an array. + * + * @author Kannan + */ +function xhprof_get_metrics($xhprof_data) { + + // get list of valid metrics + $possible_metrics = xhprof_get_possible_metrics(); + + // return those that are present in the raw data. + // We'll just look at the root of the subtree for this. + $metrics = array(); + foreach ($possible_metrics as $metric => $desc) { + if (isset($xhprof_data["main()"][$metric])) { + $metrics[] = $metric; + } + } + + return $metrics; +} + +/** + * Takes a parent/child function name encoded as + * "a==>b" and returns array("a", "b"). + * + * @author Kannan + */ +function xhprof_parse_parent_child($parent_child) { + $ret = explode("==>", $parent_child); + + // Return if both parent and child are set + if (isset($ret[1])) { + return $ret; + } + + return array(null, $ret[0]); +} + +/** + * Given parent & child function name, composes the key + * in the format present in the raw data. + * + * @author Kannan + */ +function xhprof_build_parent_child_key($parent, $child) { + if ($parent) { + return $parent . "==>" . $child; + } else { + return $child; + } +} + + +/** + * Checks if XHProf raw data appears to be valid and not corrupted. + * + * @param int $run_id Run id of run to be pruned. + * [Used only for reporting errors.] + * @param array $raw_data XHProf raw data to be pruned + * & validated. + * + * @return bool true on success, false on failure + * + * @author Kannan + */ +function xhprof_valid_run($run_id, $raw_data) { + + $main_info = $raw_data["main()"]; + if (empty($main_info)) { + xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id"); + return false; + } + + // raw data should contain either wall time or samples information... + if (isset($main_info["wt"])) { + $metric = "wt"; + } else if (isset($main_info["samples"])) { + $metric = "samples"; + } else { + xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id"); + return false; + } + + foreach ($raw_data as $info) { + $val = $info[$metric]; + + // basic sanity checks... + if ($val < 0) { + xhprof_error("XHProf: $metric should not be negative: Run ID $run_id" + . serialize($info)); + return false; + } + if ($val > (86400000000)) { + xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id " + . serialize($info)); + return false; + } + } + return true; +} + + +/** + * Return a trimmed version of the XHProf raw data. Note that the raw + * data contains one entry for each unique parent/child function + * combination.The trimmed version of raw data will only contain + * entries where either the parent or child function is in the list + * of $functions_to_keep. + * + * Note: Function main() is also always kept so that overall totals + * can still be obtained from the trimmed version. + * + * @param array XHProf raw data + * @param array array of function names + * + * @return array Trimmed XHProf Report + * + * @author Kannan + */ +function xhprof_trim_run($raw_data, $functions_to_keep) { + + // convert list of functions to a hash with function as the key + $function_map = array_fill_keys($functions_to_keep, 1); + + // always keep main() as well so that overall totals can still + // be computed if need be. + $function_map['main()'] = 1; + + $new_raw_data = array(); + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if (isset($function_map[$parent]) || isset($function_map[$child])) { + $new_raw_data[$parent_child] = $info; + } + } + + return $new_raw_data; +} + +/** + * Takes raw XHProf data that was aggregated over "$num_runs" number + * of runs averages/nomalizes the data. Essentially the various metrics + * collected are divided by $num_runs. + * + * @author Kannan + */ +function xhprof_normalize_metrics($raw_data, $num_runs) { + + if (empty($raw_data) || ($num_runs == 0)) { + return $raw_data; + } + + $raw_data_total = array(); + + if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) { + xhprof_error("XHProf Error: both ==>main() and main() set in raw data..."); + } + + foreach ($raw_data as $parent_child => $info) { + foreach ($info as $metric => $value) { + $raw_data_total[$parent_child][$metric] = ($value / $num_runs); + } + } + + return $raw_data_total; +} + + +/** + * Get raw data corresponding to specified array of runs + * aggregated by certain weightage. + * + * Suppose you have run:5 corresponding to page1.php, + * run:6 corresponding to page2.php, + * and run:7 corresponding to page3.php + * + * and you want to accumulate these runs in a 2:4:1 ratio. You + * can do so by calling: + * + * xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1)); + * + * The above will return raw data for the runs aggregated + * in 2:4:1 ratio. + * + * @param object $xhprof_runs_impl An object that implements + * the iXHProfRuns interface + * @param array $runs run ids of the XHProf runs.. + * @param array $wts integral (ideally) weights for $runs + * @param string $source source to fetch raw data for run from + * @param bool $use_script_name If true, a fake edge from main() to + * to __script:: is introduced + * in the raw data so that after aggregations + * the script name is still preserved. + * + * @return array Return aggregated raw data + * + * @author Kannan + */ +function xhprof_aggregate_runs($xhprof_runs_impl, $runs, + $wts, $source="phprof", + $use_script_name=false) { + + $raw_data_total = null; + $raw_data = null; + $metrics = array(); + + $run_count = count($runs); + $wts_count = count($wts); + + if (($run_count == 0) || + (($wts_count > 0) && ($run_count != $wts_count))) { + return array('description' => 'Invalid input..', + 'raw' => null); + } + + $bad_runs = array(); + foreach ($runs as $idx => $run_id) { + + $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); + + // use the first run to derive what metrics to aggregate on. + if ($idx == 0) { + foreach ($raw_data["main()"] as $metric => $val) { + if ($metric != "pmu") { + // for now, just to keep data size small, skip "peak" memory usage + // data while aggregating. + // The "regular" memory usage data will still be tracked. + if (isset($val)) { + $metrics[] = $metric; + } + } + } + } + + if (!xhprof_valid_run($run_id, $raw_data)) { + $bad_runs[] = $run_id; + continue; + } + + if ($use_script_name) { + $page = $description; + + // create a fake function '__script::$page', and have and edge from + // main() to '__script::$page'. We will also need edges to transfer + // all edges originating from main() to now originate from + // '__script::$page' to all function called from main(). + // + // We also weight main() ever so slightly higher so that + // it shows up above the new entry in reports sorted by + // inclusive metrics or call counts. + if ($page) { + foreach ($raw_data["main()"] as $metric => $val) { + $fake_edge[$metric] = $val; + $new_main[$metric] = $val + 0.00001; + } + $raw_data["main()"] = $new_main; + $raw_data[xhprof_build_parent_child_key("main()", + "__script::$page")] + = $fake_edge; + } else { + $use_script_name = false; + } + } + + // if no weights specified, use 1 as the default weightage.. + $wt = ($wts_count == 0) ? 1 : $wts[$idx]; + + // aggregate $raw_data into $raw_data_total with appropriate weight ($wt) + foreach ($raw_data as $parent_child => $info) { + if ($use_script_name) { + // if this is an old edge originating from main(), it now + // needs to be from '__script::$page' + if (substr($parent_child, 0, 9) == "main()==>") { + $child = substr($parent_child, 9); + // ignore the newly added edge from main() + if (substr($child, 0, 10) != "__script::") { + $parent_child = xhprof_build_parent_child_key("__script::$page", + $child); + } + } + } + + if (!isset($raw_data_total[$parent_child])) { + foreach ($metrics as $metric) { + $raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]); + } + } else { + foreach ($metrics as $metric) { + $raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]); + } + } + } + } + + $runs_string = implode(",", $runs); + + if (isset($wts)) { + $wts_string = "in the ratio (" . implode(":", $wts) . ")"; + $normalization_count = array_sum($wts); + } else { + $wts_string = ""; + $normalization_count = $run_count; + } + + $run_count = $run_count - count($bad_runs); + + $data['description'] = "Aggregated Report for $run_count runs: ". + "$runs_string $wts_string\n"; + $data['raw'] = xhprof_normalize_metrics($raw_data_total, + $normalization_count); + $data['bad_runs'] = $bad_runs; + + return $data; +} + + +/** + * Analyze hierarchical raw data, and compute per-function (flat) + * inclusive and exclusive metrics. + * + * Also, store overall totals in the 2nd argument. + * + * @param array $raw_data XHProf format raw profiler data. + * @param array &$overall_totals OUT argument for returning + * overall totals for various + * metrics. + * @return array Returns a map from function name to its + * call count and inclusive & exclusive metrics + * (such as wall time, etc.). + * + * @author Kannan Muthukkaruppan + */ +function xhprof_compute_flat_info($raw_data, &$overall_totals) { + + global $display_calls; + + $metrics = xhprof_get_metrics($raw_data); + + $overall_totals = array("ct" => 0, + "wt" => 0, + "ut" => 0, + "st" => 0, + "cpu" => 0, + "mu" => 0, + "pmu" => 0, + "samples" => 0 + ); + + // compute inclusive times for each function + $symbol_tab = xhprof_compute_inclusive_times($raw_data); + + /* total metric value is the metric value for "main()" */ + foreach ($metrics as $metric) { + $overall_totals[$metric] = $symbol_tab["main()"][$metric]; + } + + /* + * initialize exclusive (self) metric value to inclusive metric value + * to start with. + * In the same pass, also add up the total number of function calls. + */ + foreach ($symbol_tab as $symbol => $info) { + foreach ($metrics as $metric) { + $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric]; + } + if ($display_calls) { + /* keep track of total number of calls */ + $overall_totals["ct"] += $info["ct"]; + } + } + + /* adjust exclusive times by deducting inclusive time of children */ + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if ($parent) { + foreach ($metrics as $metric) { + // make sure the parent exists hasn't been pruned. + if (isset($symbol_tab[$parent])) { + $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric]; + } + } + } + } + + return $symbol_tab; +} + +/** + * Hierarchical diff: + * Compute and return difference of two call graphs: Run2 - Run1. + * + * @author Kannan + */ +function xhprof_compute_diff($xhprof_data1, $xhprof_data2) { + global $display_calls; + + // use the second run to decide what metrics we will do the diff on + $metrics = xhprof_get_metrics($xhprof_data2); + + $xhprof_delta = $xhprof_data2; + + foreach ($xhprof_data1 as $parent_child => $info) { + + if (!isset($xhprof_delta[$parent_child])) { + + // this pc combination was not present in run1; + // initialize all values to zero. + if ($display_calls) { + $xhprof_delta[$parent_child] = array("ct" => 0); + } else { + $xhprof_delta[$parent_child] = array(); + } + foreach ($metrics as $metric) { + $xhprof_delta[$parent_child][$metric] = 0; + } + } + + if ($display_calls) { + $xhprof_delta[$parent_child]["ct"] -= $info["ct"]; + } + + foreach ($metrics as $metric) { + $xhprof_delta[$parent_child][$metric] -= $info[$metric]; + } + } + + return $xhprof_delta; +} + + +/** + * Compute inclusive metrics for function. This code was factored out + * of xhprof_compute_flat_info(). + * + * The raw data contains inclusive metrics of a function for each + * unique parent function it is called from. The total inclusive metrics + * for a function is therefore the sum of inclusive metrics for the + * function across all parents. + * + * @return array Returns a map of function name to total (across all parents) + * inclusive metrics for the function. + * + * @author Kannan + */ +function xhprof_compute_inclusive_times($raw_data) { + global $display_calls; + + $metrics = xhprof_get_metrics($raw_data); + + $symbol_tab = array(); + + /* + * First compute inclusive time for each function and total + * call count for each function across all parents the + * function is called from. + */ + foreach ($raw_data as $parent_child => $info) { + + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if ($parent == $child) { + /* + * XHProf PHP extension should never trigger this situation any more. + * Recursion is handled in the XHProf PHP extension by giving nested + * calls a unique recursion-depth appended name (for example, foo@1). + */ + xhprof_error("Error in Raw Data: parent & child are both: $parent"); + return; + } + + if (!isset($symbol_tab[$child])) { + + if ($display_calls) { + $symbol_tab[$child] = array("ct" => $info["ct"]); + } else { + $symbol_tab[$child] = array(); + } + foreach ($metrics as $metric) { + $symbol_tab[$child][$metric] = $info[$metric]; + } + } else { + if ($display_calls) { + /* increment call count for this child */ + $symbol_tab[$child]["ct"] += $info["ct"]; + } + + /* update inclusive times/metric for this child */ + foreach ($metrics as $metric) { + $symbol_tab[$child][$metric] += $info[$metric]; + } + } + } + + return $symbol_tab; +} + + +/* + * Prunes XHProf raw data: + * + * Any node whose inclusive walltime accounts for less than $prune_percent + * of total walltime is pruned. [It is possible that a child function isn't + * pruned, but one or more of its parents get pruned. In such cases, when + * viewing the child function's hierarchical information, the cost due to + * the pruned parent(s) will be attributed to a special function/symbol + * "__pruned__()".] + * + * @param array $raw_data XHProf raw data to be pruned & validated. + * @param double $prune_percent Any edges that account for less than + * $prune_percent of time will be pruned + * from the raw data. + * + * @return array Returns the pruned raw data. + * + * @author Kannan + */ +function xhprof_prune_run($raw_data, $prune_percent) { + + $main_info = $raw_data["main()"]; + if (empty($main_info)) { + xhprof_error("XHProf: main() missing in raw data"); + return false; + } + + // raw data should contain either wall time or samples information... + if (isset($main_info["wt"])) { + $prune_metric = "wt"; + } else if (isset($main_info["samples"])) { + $prune_metric = "samples"; + } else { + xhprof_error("XHProf: for main() we must have either wt " + ."or samples attribute set"); + return false; + } + + // determine the metrics present in the raw data.. + $metrics = array(); + foreach ($main_info as $metric => $val) { + if (isset($val)) { + $metrics[] = $metric; + } + } + + $prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0); + + init_metrics($raw_data, null, null, false); + $flat_info = xhprof_compute_inclusive_times($raw_data); + + foreach ($raw_data as $parent_child => $info) { + + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + // is this child's overall total from all parents less than threshold? + if ($flat_info[$child][$prune_metric] < $prune_threshold) { + unset($raw_data[$parent_child]); // prune the edge + } else if ($parent && + ($parent != "__pruned__()") && + ($flat_info[$parent][$prune_metric] < $prune_threshold)) { + + // Parent's overall inclusive metric is less than a threshold. + // All edges to the parent node will get nuked, and this child will + // be a dangling child. + // So instead change its parent to be a special function __pruned__(). + $pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child); + + if (isset($raw_data[$pruned_edge])) { + foreach ($metrics as $metric) { + $raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric]; + } + } else { + $raw_data[$pruned_edge] = $raw_data[$parent_child]; + } + + unset($raw_data[$parent_child]); // prune the edge + } + } + + return $raw_data; +} + + +/** + * Set one key in an array and return the array + * + * @author Kannan + */ +function xhprof_array_set($arr, $k, $v) { + $arr[$k] = $v; + return $arr; +} + +/** + * Removes/unsets one key in an array and return the array + * + * @author Kannan + */ +function xhprof_array_unset($arr, $k) { + unset($arr[$k]); + return $arr; +} + +/** + * Type definitions for URL params + */ +define('XHPROF_STRING_PARAM', 1); +define('XHPROF_UINT_PARAM', 2); +define('XHPROF_FLOAT_PARAM', 3); +define('XHPROF_BOOL_PARAM', 4); + + +/** + * Internal helper function used by various + * xhprof_get_param* flavors for various + * types of parameters. + * + * @param string name of the URL query string param + * + * @author Kannan + */ +function xhprof_get_param_helper($param) { + $val = null; + if (isset($_GET[$param])) + $val = $_GET[$param]; + else if (isset($_POST[$param])) { + $val = $_POST[$param]; + } + return $val; +} + +/** + * Extracts value for string param $param from query + * string. If param is not specified, return the + * $default value. + * + * @author Kannan + */ +function xhprof_get_string_param($param, $default = '') { + $val = xhprof_get_param_helper($param); + + if ($val === null) + return $default; + + return $val; +} + +/** + * Extracts value for unsigned integer param $param from + * query string. If param is not specified, return the + * $default value. + * + * If value is not a valid unsigned integer, logs error + * and returns null. + * + * @author Kannan + */ +function xhprof_get_uint_param($param, $default = 0) { + $val = xhprof_get_param_helper($param); + + if ($val === null) + $val = $default; + + // trim leading/trailing whitespace + $val = trim($val); + + // if it only contains digits, then ok.. + if (ctype_digit($val)) { + return $val; + } + + xhprof_error("$param is $val. It must be an unsigned integer."); + return null; +} + + +/** + * Extracts value for a float param $param from + * query string. If param is not specified, return + * the $default value. + * + * If value is not a valid unsigned integer, logs error + * and returns null. + * + * @author Kannan + */ +function xhprof_get_float_param($param, $default = 0) { + $val = xhprof_get_param_helper($param); + + if ($val === null) + $val = $default; + + // trim leading/trailing whitespace + $val = trim($val); + + // TBD: confirm the value is indeed a float. + if (true) // for now.. + return (float)$val; + + xhprof_error("$param is $val. It must be a float."); + return null; +} + +/** + * Extracts value for a boolean param $param from + * query string. If param is not specified, return + * the $default value. + * + * If value is not a valid unsigned integer, logs error + * and returns null. + * + * @author Kannan + */ +function xhprof_get_bool_param($param, $default = false) { + $val = xhprof_get_param_helper($param); + + if ($val === null) + $val = $default; + + // trim leading/trailing whitespace + $val = trim($val); + + switch (strtolower($val)) { + case '0': + case '1': + $val = (bool)$val; + break; + case 'true': + case 'on': + case 'yes': + $val = true; + break; + case 'false': + case 'off': + case 'no': + $val = false; + break; + default: + xhprof_error("$param is $val. It must be a valid boolean string."); + return null; + } + + return $val; + +} + +/** + * Initialize params from URL query string. The function + * creates globals variables for each of the params + * and if the URL query string doesn't specify a particular + * param initializes them with the corresponding default + * value specified in the input. + * + * @params array $params An array whose keys are the names + * of URL params who value needs to + * be retrieved from the URL query + * string. PHP globals are created + * with these names. The value is + * itself an array with 2-elems (the + * param type, and its default value). + * If a param is not specified in the + * query string the default value is + * used. + * @author Kannan + */ +function xhprof_param_init($params) { + /* Create variables specified in $params keys, init defaults */ + foreach ($params as $k => $v) { + switch ($v[0]) { + case XHPROF_STRING_PARAM: + $p = xhprof_get_string_param($k, $v[1]); + break; + case XHPROF_UINT_PARAM: + $p = xhprof_get_uint_param($k, $v[1]); + break; + case XHPROF_FLOAT_PARAM: + $p = xhprof_get_float_param($k, $v[1]); + break; + case XHPROF_BOOL_PARAM: + $p = xhprof_get_bool_param($k, $v[1]); + break; + default: + xhprof_error("Invalid param type passed to xhprof_param_init: " + . $v[0]); + exit(); + } + + if ($k === 'run') { + $p = implode(',', array_filter(explode(',', $p), 'ctype_xdigit')); + } + + // create a global variable using the parameter name. + $GLOBALS[$k] = $p; + } +} + + +/** + * Given a partial query string $q return matching function names in + * specified XHProf run. This is used for the type ahead function + * selector. + * + * @author Kannan + */ +function xhprof_get_matching_functions($q, $xhprof_data) { + + $matches = array(); + + foreach ($xhprof_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if (stripos($parent, $q) !== false) { + $matches[$parent] = 1; + } + if (stripos($child, $q) !== false) { + $matches[$child] = 1; + } + } + + $res = array_keys($matches); + + // sort it so the answers are in some reliable order... + asort($res); + + return ($res); +} diff --git a/plugins/xhprof/lib/xhprof_lib/utils/xhprof_runs.php b/plugins/xhprof/lib/xhprof_lib/utils/xhprof_runs.php new file mode 100755 index 000000000..7af62a966 --- /dev/null +++ b/plugins/xhprof/lib/xhprof_lib/utils/xhprof_runs.php @@ -0,0 +1,167 @@ +suffix; + + if (!empty($this->dir)) { + $file = $this->dir . "/" . $file; + } + return $file; + } + + public function __construct($dir = null) { + + // if user hasn't passed a directory location, + // we use the xhprof.output_dir ini setting + // if specified, else we default to the directory + // in which the error_log file resides. + + if (empty($dir)) { + $dir = ini_get("xhprof.output_dir"); + if (empty($dir)) { + + $dir = sys_get_temp_dir(); + + xhprof_error("Warning: Must specify directory location for XHProf runs. " . + "Trying {$dir} as default. You can either pass the " . + "directory location as an argument to the constructor " . + "for XHProfRuns_Default() or set xhprof.output_dir " . + "ini param."); + } + } + $this->dir = $dir; + } + + public function get_run($run_id, $type, &$run_desc) { + $file_name = $this->file_name($run_id, $type); + + if (!file_exists($file_name)) { + xhprof_error("Could not find file $file_name"); + $run_desc = "Invalid Run Id = $run_id"; + return null; + } + + $contents = file_get_contents($file_name); + $run_desc = "XHProf Run (Namespace=$type)"; + return unserialize($contents); + } + + public function save_run($xhprof_data, $type, $run_id = null) { + + // Use PHP serialize function to store the XHProf's + // raw profiler data. + $xhprof_data = serialize($xhprof_data); + + if ($run_id === null) { + $run_id = $this->gen_run_id($type); + } + + $file_name = $this->file_name($run_id, $type); + $file = fopen($file_name, 'w'); + + if ($file) { + fwrite($file, $xhprof_data); + fclose($file); + } else { + xhprof_error("Could not open $file_name\n"); + } + + // echo "Saved run in {$file_name}.\nRun id = {$run_id}.\n"; + return $run_id; + } + + function list_runs_callback($a, $b) { + return filemtime($b) - filemtime($a); + } + + function list_runs() { + if (is_dir($this->dir)) { + echo "
                                      Existing runs:\n
                                        \n"; + $files = glob("{$this->dir}/*.{$this->suffix}"); + usort($files, 'list_runs_callback'); + foreach ($files as $file) { + list($run, $source) = explode('.', basename($file)); + echo '
                                      • ' + . htmlentities(basename($file)) . " " + . date("Y-m-d H:i:s", filemtime($file)) . "
                                      • \n"; + } + echo "
                                      \n"; + } + } +} diff --git a/plugins/xui/ico.png b/plugins/xui/ico.png new file mode 100644 index 000000000..b7f2362c5 Binary files /dev/null and b/plugins/xui/ico.png differ diff --git a/plugins/xui/index.html b/plugins/xui/index.html new file mode 100755 index 000000000..81e32de12 --- /dev/null +++ b/plugins/xui/index.html @@ -0,0 +1,25 @@ +
                                      +
                                      +
                                      +

                                      服务

                                      +

                                      自启动

                                      +

                                      常用功能

                                      +
                                      +
                                      +
                                      +
                                      +
                                      +
                                      + + + \ No newline at end of file diff --git a/plugins/xui/index.py b/plugins/xui/index.py new file mode 100755 index 000000000..0fc9d7764 --- /dev/null +++ b/plugins/xui/index.py @@ -0,0 +1,223 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'xui' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + +def status(): + cmd = "ps -ef|grep x-ui |grep -v grep | grep -v python | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + + +def getServiceFile(): + systemDir = mw.systemdCfgDir() + return systemDir + '/xui.service' + + +def getXuiPort(): + return '8349' + + +def __release_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort(port, 'xui', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + +def __delete_port(port): + from collections import namedtuple + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().delAcceptPortCmd(port, 'tcp') + return port + except Exception as e: + return "Delete failed {}".format(e) + + +def pSqliteDb(dbname='databases'): + conn = mw.M(dbname).dbPos('/etc/x-ui', 'x-ui') + return conn + +def initDreplace(): + return 'ok' + + +def xuiOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + mw.execShell('systemctl daemon-reload') + data = mw.execShell('systemctl ' + method + ' x-ui') + if data[1] == '': + return 'ok' + return data[1] + + return 'fail' + + +def start(): + openPort() + return xuiOp('start') + + +def stop(): + closePort() + return xuiOp('stop') + + +def restart(): + return xuiOp('restart') + + +def reload(): + return redisOp('reload') + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status x-ui | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + mw.execShell('systemctl enable x-ui') + return 'ok' + + +def initdUinstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + mw.execShell('systemctl disable x-ui') + return 'ok' + +def openPort(): + setting = pSqliteDb('settings') + port_data = setting.field('id,key,value').where("key=?", ('webPort',)).find() + port = port_data['value'] + __release_port(port) + +def closePort(): + setting = pSqliteDb('settings') + port_data = setting.field('id,key,value').where("key=?", ('webPort',)).find() + port = port_data['value'] + __delete_port(port) + +def getXuiInfo(): + + data = {} + user = pSqliteDb('users') + info = user.field('username,password').where("id=?", (1,)).find() + + setting = pSqliteDb('settings') + port_data = setting.field('id,key,value').where("key=?", ('webPort',)).find() + path_data = setting.field('id,key,value').where("key=?", ('webBasePath',)).find() + + if path_data is not None: + data['path'] = path_data['value'] + else: + data['path'] = '/' + + data['username'] = info['username'] + data['password'] = info['password'] + data['port'] = port_data['value'] + + data['ip'] = mw.getHostAddr() + return mw.returnJson(True, 'ok', data) + +def installPreInspection(): + sys = mw.execShell("cat /etc/*-release | grep PRETTY_NAME |awk -F = '{print $2}' | awk -F '\"' '{print $2}'| awk '{print $1}'") + + if sys[1] != '': + return '不支持该系统' + + sys_id = mw.execShell("cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F '\"' '{print $2}'") + + sysName = sys[0].strip().lower() + sysId = sys_id[0].strip() + + if sysName in ('opensuse'): + return '不支持该系统' + + return 'ok' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'conf': + print(getServiceFile()) + elif func == 'info': + print(getXuiInfo()) + else: + print('error') diff --git a/plugins/xui/info.json b/plugins/xui/info.json new file mode 100755 index 000000000..4959bfc54 --- /dev/null +++ b/plugins/xui/info.json @@ -0,0 +1,19 @@ +{ + "sort": 7, + "ps": "支持多协议多用户的xray面板", + "install_pre_inspection":true, + "name": "xui", + "title": "xui", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "checks": "server/xui", + "path": "server/xui", + "display": 1, + "author": "FranzKafkaYu", + "date": "2024-12-03", + "home": "https://github.com/FranzKafkaYu/x-ui", + "type": 0, + "pid": "5" +} \ No newline at end of file diff --git a/plugins/xui/install.sh b/plugins/xui/install.sh new file mode 100755 index 000000000..4b90aeac1 --- /dev/null +++ b/plugins/xui/install.sh @@ -0,0 +1,51 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +sysArch=`arch` +sysName=`uname` + + +# cd /www/server/mdserver-web && python3 plugins/xui/index.py info +# cd /www/server/mdserver-web/plugins/xui && /bin/bash install.sh install 1.0 + +VERSION=$2 +Install_app() +{ + mkdir -p $serverPath/source + mkdir -p $serverPath/source/xui + mkdir -p $serverPath/xui + + echo "$VERSION" > $serverPath/xui/version.pl + + bash ${curPath}/install_xui.sh + + + # start xray + cd /usr/local/x-ui && bin/xray-linux-amd64 -c bin/config.json + + cd ${rootPath} && python3 plugins/xui/index.py start + echo '安装完成' +} + +Uninstall_app() +{ + cd ${rootPath} && python3 plugins/xui/index.py stop + + rm -rf $serverPath/xui + echo 'y' | x-ui uninstall + rm -rf /usr/bin/x-ui + echo '卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_app $2 +else + Uninstall_app $2 +fi diff --git a/plugins/xui/install_xui.sh b/plugins/xui/install_xui.sh new file mode 100644 index 000000000..0309c9460 --- /dev/null +++ b/plugins/xui/install_xui.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash + +red='\033[0;31m' +green='\033[0;32m' +yellow='\033[0;33m' +plain='\033[0m' + +cur_dir=$(pwd) + +# check root +[[ $EUID -ne 0 ]] && echo -e "${red}错误:${plain} 必须使用root用户运行此脚本!\n" && exit 1 + +# check os +if [[ -f /etc/redhat-release ]]; then + release="centos" +elif cat /etc/issue | grep -Eqi "debian"; then + release="debian" +elif cat /etc/issue | grep -Eqi "ubuntu"; then + release="ubuntu" +elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then + release="centos" +elif cat /proc/version | grep -Eqi "debian"; then + release="debian" +elif cat /proc/version | grep -Eqi "ubuntu"; then + release="ubuntu" +elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then + release="centos" +else + echo -e "${red}未检测到系统版本,请联系脚本作者!${plain}\n" && exit 1 +fi + +arch=$(arch) + +if [[ $arch == "x86_64" || $arch == "x64" || $arch == "s390x" || $arch == "amd64" ]]; then + arch="amd64" +elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then + arch="arm64" +else + arch="amd64" + echo -e "${red}检测架构失败,使用默认架构: ${arch}${plain}" +fi + +echo "架构: ${arch}" + +if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then + echo "本软件不支持 32 位系统(x86),请使用 64 位系统(x86_64),如果检测有误,请联系作者" + exit -1 +fi + +os_version="" + +# os version +if [[ -f /etc/os-release ]]; then + os_version=$(awk -F'[= ."]' '/VERSION_ID/{print $3}' /etc/os-release) +fi +if [[ -z "$os_version" && -f /etc/lsb-release ]]; then + os_version=$(awk -F'[= ."]+' '/DISTRIB_RELEASE/{print $2}' /etc/lsb-release) +fi + +if [[ x"${release}" == x"centos" ]]; then + if [[ ${os_version} -le 6 ]]; then + echo -e "${red}请使用 CentOS 7 或更高版本的系统!${plain}\n" && exit 1 + fi +elif [[ x"${release}" == x"ubuntu" ]]; then + if [[ ${os_version} -lt 16 ]]; then + echo -e "${red}请使用 Ubuntu 16 或更高版本的系统!${plain}\n" && exit 1 + fi +elif [[ x"${release}" == x"debian" ]]; then + if [[ ${os_version} -lt 8 ]]; then + echo -e "${red}请使用 Debian 8 或更高版本的系统!${plain}\n" && exit 1 + fi +fi + +install_base() { + if [[ x"${release}" == x"centos" ]]; then + yum install wget curl tar jq -y + else + apt install wget curl tar jq -y + fi +} + +#This function will be called when user installed x-ui out of sercurity +config_after_install() { + echo -e "${yellow}出于安全考虑,安装/更新完成后需要强制修改端口与账户密码${plain}" + if [[ ! -f "/etc/x-ui/x-ui.db" ]]; then + local usernameTemp=$(head -c 6 /dev/urandom | base64) + local passwordTemp=$(head -c 6 /dev/urandom | base64) + local portTemp=$(echo $RANDOM) + /usr/local/x-ui/x-ui setting -username ${usernameTemp} -password ${passwordTemp} + /usr/local/x-ui/x-ui setting -port ${portTemp} + echo -e "检测到您属于全新安装,出于安全考虑已自动为您生成随机用户与端口:" + echo -e "###############################################" + echo -e "${green}面板登录用户名:${usernameTemp}${plain}" + echo -e "${green}面板登录用户密码:${passwordTemp}${plain}" + echo -e "${red}面板登录端口:${portTemp}${plain}" + echo -e "###############################################" + echo -e "${red}如您遗忘了面板登录相关信息,可在安装完成后输入x-ui,输入选项7查看面板登录信息${plain}" + else + echo -e "${red}当前属于版本升级,保留之前设置项,登录方式保持不变,可输入x-ui后键入数字7查看面板登录信息${plain}" + fi +} + +install_x-ui() { + systemctl stop x-ui + cd /usr/local/ + + if [ $# == 0 ]; then + last_version=$(curl -Lsk "https://api.github.com/repos/FranzKafkaYu/x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + if [[ ! -n "$last_version" ]]; then + echo -e "${red}检测 x-ui 版本失败,可能是超出 Github API 限制,请稍后再试,或手动指定 x-ui 版本安装${plain}" + exit 1 + fi + echo -e "检测到 x-ui 最新版本:${last_version},开始安装" + wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/FranzKafkaYu/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz + if [[ $? -ne 0 ]]; then + echo -e "${red}下载 x-ui 失败,请确保你的服务器能够下载 Github 的文件${plain}" + exit 1 + fi + else + last_version=$1 + url="https://github.com/FranzKafkaYu/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz" + echo -e "开始安装 x-ui v$1" + wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url} + if [[ $? -ne 0 ]]; then + echo -e "${red}下载 x-ui v$1 失败,请确保此版本存在${plain}" + exit 1 + fi + fi + + if [[ -e /usr/local/x-ui/ ]]; then + rm /usr/local/x-ui/ -rf + fi + + tar zxvf x-ui-linux-${arch}.tar.gz + rm x-ui-linux-${arch}.tar.gz -f + cd x-ui + chmod +x x-ui bin/xray-linux-${arch} + cp -f x-ui.service /etc/systemd/system/ + wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/FranzKafkaYu/x-ui/main/x-ui.sh + chmod +x /usr/local/x-ui/x-ui.sh + chmod +x /usr/bin/x-ui + config_after_install + #echo -e "如果是全新安装,默认网页端口为 ${green}54321${plain},用户名和密码默认都是 ${green}admin${plain}" + #echo -e "请自行确保此端口没有被其他程序占用,${yellow}并且确保 54321 端口已放行${plain}" + # echo -e "若想将 54321 修改为其它端口,输入 x-ui 命令进行修改,同样也要确保你修改的端口也是放行的" + #echo -e "" + #echo -e "如果是更新面板,则按你之前的方式访问面板" + #echo -e "" + systemctl daemon-reload + systemctl enable x-ui + systemctl start x-ui +} + +echo -e "${green}开始安装${plain}" +install_base +install_x-ui $1 \ No newline at end of file diff --git a/plugins/xui/js/xui.js b/plugins/xui/js/xui.js new file mode 100644 index 000000000..e9bb21e61 --- /dev/null +++ b/plugins/xui/js/xui.js @@ -0,0 +1,64 @@ +function xuiPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'xui'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function xuiCommonFunc(){ + + xuiPost('info', '', {}, function(rdata){ + var rdata = $.parseJSON(rdata.data); + var info = rdata['data']; + var con = '

                                      \ + 用户名\ + \ +

                                      '; + + con += '

                                      \ + 密码\ + \ +

                                      '; + con += '

                                      \ + 端口\ + \ +

                                      '; + + con += '

                                      \ + 路径\ + \ +

                                      '; + + con += '

                                      \ + \ +

                                      '; + + $(".soft-man-con").html(con); + $('#open_url').click(function(){ + var url = 'http://' + info.ip + ':' + info.port + info.path; + window.open(url); + copyText(url); + }); + }); +} \ No newline at end of file diff --git a/plugins/zabbix/conf/zabbix.conf.php b/plugins/zabbix/conf/zabbix.conf.php new file mode 100644 index 000000000..882a34034 --- /dev/null +++ b/plugins/zabbix/conf/zabbix.conf.php @@ -0,0 +1,58 @@ + 'http://localhost:9200', +// 'text' => 'http://localhost:9200' +//]; +// Value types stored in Elasticsearch. +//$HISTORY['types'] = ['uint', 'text']; + +// Used for SAML authentication. +// Uncomment to override the default paths to SP private key, SP and IdP X.509 certificates, and to set extra settings. +//$SSO['SP_KEY'] = 'conf/certs/sp.key'; +//$SSO['SP_CERT'] = 'conf/certs/sp.crt'; +//$SSO['IDP_CERT'] = 'conf/certs/idp.crt'; +//$SSO['SETTINGS'] = []; + +// If set to false, support for HTTP authentication will be disabled. +// $ALLOW_HTTP_AUTH = true; diff --git a/plugins/zabbix/conf/zabbix.nginx.conf b/plugins/zabbix/conf/zabbix.nginx.conf new file mode 100644 index 000000000..5a1e6eb45 --- /dev/null +++ b/plugins/zabbix/conf/zabbix.nginx.conf @@ -0,0 +1,45 @@ +server +{ + listen {$ZABBIX_PORT}; + server_name 127.0.0.1; + index index.php index.html index.htm default.php default.htm default.html; + root {$ZABBIX_ROOT}; + + #SSL-START + #error_page 404/404.html; + #SSL-END + + #301-START + + #PROXY-START + + #ERROR-PAGE-START + #error_page 404 /404.html; + #error_page 502 /502.html; + #ERROR-PAGE-END + + #PHP-INFO-START + include {$SERVER_PATH}/web_conf/php/conf/enable-{$PHP_VER}.conf; + #PHP-INFO-END + + #禁止访问的文件或目录 + location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md) + { + return 404; + } + + #一键申请SSL证书验证目录相关设置 + location ~ \.well-known{ + allow all; + } + + location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf|js|css)$ + { + expires 30d; + error_log /dev/null; + access_log /dev/null; + } + + access_log /www/wwwlogs/zabbix.log main; + error_log /www/wwwlogs/zabbix.error.log; +} \ No newline at end of file diff --git a/plugins/zabbix/conf/zabbix_agentd.conf b/plugins/zabbix/conf/zabbix_agentd.conf new file mode 100644 index 000000000..9ffe32e8b --- /dev/null +++ b/plugins/zabbix/conf/zabbix_agentd.conf @@ -0,0 +1,18 @@ +PidFile=/run/zabbix/zabbix_agentd.pid +LogFile=/var/log/zabbix/zabbix_agentd.log +LogFileSize=1 + +ListenIP=0.0.0.0 +ListenPort=10050 +EnableRemoteCommands=1 +Timeout=3 + +Server=127.0.0.1 +ServerActive=127.0.0.1 + +Hostname=Zabbix server +Include=/etc/zabbix/zabbix_agentd.d/*.conf + +# Include=/usr/local/etc/zabbix_agentd.userparams.conf +# Include=/usr/local/etc/zabbix_agentd.conf.d/ +# Include=/usr/local/etc/zabbix_agentd.conf.d/*.conf diff --git a/plugins/zabbix/conf/zabbix_server.conf b/plugins/zabbix/conf/zabbix_server.conf new file mode 100644 index 000000000..b0241a422 --- /dev/null +++ b/plugins/zabbix/conf/zabbix_server.conf @@ -0,0 +1,13 @@ +LogFile=/var/log/zabbix/zabbix_server.log +LogFileSize=1 +PidFile=/run/zabbix/zabbix_server.pid +SocketDir=/run/zabbix +DBHost=127.0.0.1 +DBPort={$ZABBIX_DB_PORT} +DBName=zabbix +DBUser=zabbix +DBPassword={$ZABBIX_DB_PASS} +ListenPort=10051 +#EnableGlobalScripts=1 +SNMPTrapperFile=/var/log/snmptrap/snmptrap.log + diff --git a/plugins/zabbix/conf/zabbix_server.conf.6.bak b/plugins/zabbix/conf/zabbix_server.conf.6.bak new file mode 100644 index 000000000..02d1eaa2e --- /dev/null +++ b/plugins/zabbix/conf/zabbix_server.conf.6.bak @@ -0,0 +1,991 @@ +# This is a configuration file for Zabbix server daemon +# To get more information about Zabbix, visit http://www.zabbix.com + +############ GENERAL PARAMETERS ################# + +### Option: ListenPort +# Listen port for trapper. +# +# Mandatory: no +# Range: 1024-32767 +# Default: +# ListenPort=10051 + +### Option: SourceIP +# Source IP address for outgoing connections. +# +# Mandatory: no +# Default: +# SourceIP= + +### Option: LogType +# Specifies where log messages are written to: +# system - syslog +# file - file specified with LogFile parameter +# console - standard output +# +# Mandatory: no +# Default: +# LogType=file + +### Option: LogFile +# Log file name for LogType 'file' parameter. +# +# Mandatory: yes, if LogType is set to file, otherwise no +# Default: +# LogFile= + +LogFile=/var/log/zabbixsrv/zabbix_server.log + +### Option: LogFileSize +# Maximum size of log file in MB. +# 0 - disable automatic log rotation. +# +# Mandatory: no +# Range: 0-1024 +# Default: +# LogFileSize=1 +LogFileSize=0 + +### Option: DebugLevel +# Specifies debug level: +# 0 - basic information about starting and stopping of Zabbix processes +# 1 - critical information +# 2 - error information +# 3 - warnings +# 4 - for debugging (produces lots of information) +# 5 - extended debugging (produces even more information) +# +# Mandatory: no +# Range: 0-5 +# Default: +# DebugLevel=3 + +### Option: PidFile +# Name of PID file. +# +# Mandatory: no +# Default: +# PidFile=/tmp/zabbix_server.pid +PidFile=/run/zabbixsrv/zabbix_server.pid + +### Option: SocketDir +# IPC socket directory. +# Directory to store IPC sockets used by internal Zabbix services. +# +# Mandatory: no +# Default: +# SocketDir=/tmp + +### Option: DBHost +# Database host name. +# If set to localhost, socket is used for MySQL. +# If set to empty string, socket is used for PostgreSQL. +# If set to empty string, the Net Service Name connection method is used to connect to Oracle database; also see +# the TNS_ADMIN environment variable to specify the directory where the tnsnames.ora file is located. +# +# Mandatory: no +# Default: +# DBHost=localhost + +### Option: DBName +# Database name. +# If the Net Service Name connection method is used to connect to Oracle database, specify the service name from +# the tnsnames.ora file or set to empty string; also see the TWO_TASK environment variable if DBName is set to +# empty string. +# +# Mandatory: yes +# Default: +# DBName= + +DBName=zabbix + +### Option: DBSchema +# Schema name. Used for PostgreSQL. +# +# Mandatory: no +# Default: +# DBSchema= + +### Option: DBUser +# Database user. +# +# Mandatory: no +# Default: +# DBUser= + +DBUser=zabbix + +### Option: DBPassword +# Database password. +# Comment this line if no password is used. +# +# Mandatory: no +# Default: +# DBPassword= + +### Option: DBSocket +# Path to MySQL socket. +# +# Mandatory: no +# Default: +DBSocket=/var/lib/mysql/mysql.sock + +### Option: DBPort +# Database port when not using local socket. +# If the Net Service Name connection method is used to connect to Oracle database, the port number from the +# tnsnames.ora file will be used. The port number set here will be ignored. +# +# Mandatory: no +# Range: 1024-65535 +# Default: +# DBPort= + +### Option: AllowUnsupportedDBVersions +# Allow server to work with unsupported database versions. +# 0 - do not allow +# 1 - allow +# +# Mandatory: no +# Default: +# AllowUnsupportedDBVersions=0 + +### Option: HistoryStorageURL +# History storage HTTP[S] URL. +# +# Mandatory: no +# Default: +# HistoryStorageURL= + +### Option: HistoryStorageTypes +# Comma separated list of value types to be sent to the history storage. +# +# Mandatory: no +# Default: +# HistoryStorageTypes=uint,dbl,str,log,text + +### Option: HistoryStorageDateIndex +# Enable preprocessing of history values in history storage to store values in different indices based on date. +# 0 - disable +# 1 - enable +# +# Mandatory: no +# Default: +# HistoryStorageDateIndex=0 + +### Option: ExportDir +# Directory for real time export of events, history and trends in newline delimited JSON format. +# If set, enables real time export. +# +# Mandatory: no +# Default: +# ExportDir= + +### Option: ExportFileSize +# Maximum size per export file in bytes. +# Only used for rotation if ExportDir is set. +# +# Mandatory: no +# Range: 1M-1G +# Default: +# ExportFileSize=1G + +### Option: ExportType +# List of comma delimited types of real time export - allows to control export entities by their +# type (events, history, trends) individually. +# Valid only if ExportDir is set. +# +# Mandatory: no +# Default: +# ExportType=events,history,trends + +############ ADVANCED PARAMETERS ################ + +### Option: StartPollers +# Number of pre-forked instances of pollers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartPollers=5 + +### Option: StartIPMIPollers +# Number of pre-forked instances of IPMI pollers. +# The IPMI manager process is automatically started when at least one IPMI poller is started. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartIPMIPollers=0 + +### Option: StartPreprocessors +# Number of pre-forked instances of preprocessing workers. +# The preprocessing manager process is automatically started when preprocessor worker is started. +# +# Mandatory: no +# Range: 1-1000 +# Default: +# StartPreprocessors=3 + +### Option: StartPollersUnreachable +# Number of pre-forked instances of pollers for unreachable hosts (including IPMI and Java). +# At least one poller for unreachable hosts must be running if regular, IPMI or Java pollers +# are started. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartPollersUnreachable=1 + +### Option: StartHistoryPollers +# Number of pre-forked instances of history pollers. +# Only required for calculated and internal checks. +# A database connection is required for each history poller instance. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartHistoryPollers=5 + +### Option: StartTrappers +# Number of pre-forked instances of trappers. +# Trappers accept incoming connections from Zabbix sender, active agents and active proxies. +# At least one trapper process must be running to display server availability and view queue +# in the frontend. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartTrappers=5 + +### Option: StartPingers +# Number of pre-forked instances of ICMP pingers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartPingers=1 + +### Option: StartDiscoverers +# Number of pre-forked instances of discoverers. +# +# Mandatory: no +# Range: 0-250 +# Default: +# StartDiscoverers=1 + +### Option: StartHTTPPollers +# Number of pre-forked instances of HTTP pollers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartHTTPPollers=1 + +### Option: StartTimers +# Number of pre-forked instances of timers. +# Timers process maintenance periods. +# Only the first timer process handles host maintenance updates. Problem suppression updates are shared +# between all timers. +# +# Mandatory: no +# Range: 1-1000 +# Default: +# StartTimers=1 + +### Option: StartEscalators +# Number of pre-forked instances of escalators. +# +# Mandatory: no +# Range: 1-100 +# Default: +# StartEscalators=1 + +### Option: StartAlerters +# Number of pre-forked instances of alerters. +# Alerters send the notifications created by action operations. +# +# Mandatory: no +# Range: 1-100 +# Default: +# StartAlerters=3 + +### Option: JavaGateway +# IP address (or hostname) of Zabbix Java gateway. +# Only required if Java pollers are started. +# +# Mandatory: no +# Default: +# JavaGateway= + +### Option: JavaGatewayPort +# Port that Zabbix Java gateway listens on. +# +# Mandatory: no +# Range: 1024-32767 +# Default: +# JavaGatewayPort=10052 + +### Option: StartJavaPollers +# Number of pre-forked instances of Java pollers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartJavaPollers=0 + +### Option: StartVMwareCollectors +# Number of pre-forked vmware collector instances. +# +# Mandatory: no +# Range: 0-250 +# Default: +# StartVMwareCollectors=0 + +### Option: VMwareFrequency +# How often Zabbix will connect to VMware service to obtain a new data. +# +# Mandatory: no +# Range: 10-86400 +# Default: +# VMwareFrequency=60 + +### Option: VMwarePerfFrequency +# How often Zabbix will connect to VMware service to obtain performance data. +# +# Mandatory: no +# Range: 10-86400 +# Default: +# VMwarePerfFrequency=60 + +### Option: VMwareCacheSize +# Size of VMware cache, in bytes. +# Shared memory size for storing VMware data. +# Only used if VMware collectors are started. +# +# Mandatory: no +# Range: 256K-2G +# Default: +# VMwareCacheSize=8M + +### Option: VMwareTimeout +# Specifies how many seconds vmware collector waits for response from VMware service. +# +# Mandatory: no +# Range: 1-300 +# Default: +# VMwareTimeout=10 + +### Option: SNMPTrapperFile +# Temporary file used for passing data from SNMP trap daemon to the server. +# Must be the same as in zabbix_trap_receiver.pl or SNMPTT configuration file. +# +# Mandatory: no +# Default: +# SNMPTrapperFile=/tmp/zabbix_traps.tmp + +### Option: StartSNMPTrapper +# If 1, SNMP trapper process is started. +# +# Mandatory: no +# Range: 0-1 +# Default: +# StartSNMPTrapper=0 + +### Option: ListenIP +# List of comma delimited IP addresses that the trapper should listen on. +# Trapper will listen on all network interfaces if this parameter is missing. +# +# Mandatory: no +# Default: +# ListenIP=0.0.0.0 + +### Option: HousekeepingFrequency +# How often Zabbix will perform housekeeping procedure (in hours). +# Housekeeping is removing outdated information from the database. +# To prevent Housekeeper from being overloaded, no more than 4 times HousekeepingFrequency +# hours of outdated information are deleted in one housekeeping cycle, for each item. +# To lower load on server startup housekeeping is postponed for 30 minutes after server start. +# With HousekeepingFrequency=0 the housekeeper can be only executed using the runtime control option. +# In this case the period of outdated information deleted in one housekeeping cycle is 4 times the +# period since the last housekeeping cycle, but not less than 4 hours and not greater than 4 days. +# +# Mandatory: no +# Range: 0-24 +# Default: +# HousekeepingFrequency=1 + +### Option: MaxHousekeeperDelete +# The table "housekeeper" contains "tasks" for housekeeping procedure in the format: +# [housekeeperid], [tablename], [field], [value]. +# No more than 'MaxHousekeeperDelete' rows (corresponding to [tablename], [field], [value]) +# will be deleted per one task in one housekeeping cycle. +# If set to 0 then no limit is used at all. In this case you must know what you are doing! +# +# Mandatory: no +# Range: 0-1000000 +# Default: +# MaxHousekeeperDelete=5000 + +### Option: CacheSize +# Size of configuration cache, in bytes. +# Shared memory size for storing host, item and trigger data. +# +# Mandatory: no +# Range: 128K-64G +# Default: +# CacheSize=32M + +### Option: CacheUpdateFrequency +# How often Zabbix will perform update of configuration cache, in seconds. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# CacheUpdateFrequency=60 + +### Option: StartDBSyncers +# Number of pre-forked instances of DB Syncers. +# +# Mandatory: no +# Range: 1-100 +# Default: +# StartDBSyncers=4 + +### Option: HistoryCacheSize +# Size of history cache, in bytes. +# Shared memory size for storing history data. +# +# Mandatory: no +# Range: 128K-2G +# Default: +# HistoryCacheSize=16M + +### Option: HistoryIndexCacheSize +# Size of history index cache, in bytes. +# Shared memory size for indexing history cache. +# +# Mandatory: no +# Range: 128K-2G +# Default: +# HistoryIndexCacheSize=4M + +### Option: TrendCacheSize +# Size of trend write cache, in bytes. +# Shared memory size for storing trends data. +# +# Mandatory: no +# Range: 128K-2G +# Default: +# TrendCacheSize=4M + +### Option: TrendFunctionCacheSize +# Size of trend function cache, in bytes. +# Shared memory size for caching calculated trend function data. +# +# Mandatory: no +# Range: 128K-2G +# Default: +# TrendFunctionCacheSize=4M + +### Option: ValueCacheSize +# Size of history value cache, in bytes. +# Shared memory size for caching item history data requests. +# Setting to 0 disables value cache. +# +# Mandatory: no +# Range: 0,128K-64G +# Default: +# ValueCacheSize=8M + +### Option: Timeout +# Specifies how long we wait for agent, SNMP device or external check (in seconds). +# +# Mandatory: no +# Range: 1-30 +# Default: +# Timeout=3 + +Timeout=4 + +### Option: TrapperTimeout +# Specifies how many seconds trapper may spend processing new data. +# +# Mandatory: no +# Range: 1-300 +# Default: +# TrapperTimeout=300 + +### Option: UnreachablePeriod +# After how many seconds of unreachability treat a host as unavailable. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# UnreachablePeriod=45 + +### Option: UnavailableDelay +# How often host is checked for availability during the unavailability period, in seconds. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# UnavailableDelay=60 + +### Option: UnreachableDelay +# How often host is checked for availability during the unreachability period, in seconds. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# UnreachableDelay=15 + +### Option: AlertScriptsPath +# Full path to location of custom alert scripts. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# AlertScriptsPath=/usr/share/zabbix/alertscripts +AlertScriptsPath=/var/lib/zabbixsrv/alertscripts + +### Option: ExternalScripts +# Full path to location of external scripts. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# ExternalScripts=/usr/share/zabbix/externalscripts +ExternalScripts=/var/lib/zabbixsrv/externalscripts + +### Option: FpingLocation +# Location of fping. +# Make sure that fping binary has root ownership and SUID flag set. +# +# Mandatory: no +# Default: +# FpingLocation=/usr/sbin/fping + +### Option: Fping6Location +# Location of fping6. +# Make sure that fping6 binary has root ownership and SUID flag set. +# Make empty if your fping utility is capable to process IPv6 addresses. +# +# Mandatory: no +# Default: +# Fping6Location=/usr/sbin/fping6 + +### Option: SSHKeyLocation +# Location of public and private keys for SSH checks and actions. +# +# Mandatory: no +# Default: +# SSHKeyLocation= + +### Option: LogSlowQueries +# How long a database query may take before being logged (in milliseconds). +# Only works if DebugLevel set to 3, 4 or 5. +# 0 - don't log slow queries. +# +# Mandatory: no +# Range: 1-3600000 +# Default: +# LogSlowQueries=0 + +LogSlowQueries=3000 + +### Option: TmpDir +# Temporary directory. +# +# Mandatory: no +# Default: +# TmpDir=/tmp +TmpDir=/var/lib/zabbixsrv/tmp + +### Option: StartProxyPollers +# Number of pre-forked instances of pollers for passive proxies. +# +# Mandatory: no +# Range: 0-250 +# Default: +# StartProxyPollers=1 + +### Option: ProxyConfigFrequency +# How often Zabbix Server sends configuration data to a Zabbix Proxy in seconds. +# This parameter is used only for proxies in the passive mode. +# +# Mandatory: no +# Range: 1-3600*24*7 +# Default: +# ProxyConfigFrequency=3600 + +### Option: ProxyDataFrequency +# How often Zabbix Server requests history data from a Zabbix Proxy in seconds. +# This parameter is used only for proxies in the passive mode. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# ProxyDataFrequency=1 + +### Option: StartLLDProcessors +# Number of pre-forked instances of low level discovery processors. +# +# Mandatory: no +# Range: 1-100 +# Default: +# StartLLDProcessors=2 + +### Option: AllowRoot +# Allow the server to run as 'root'. If disabled and the server is started by 'root', the server +# will try to switch to the user specified by the User configuration option instead. +# Has no effect if started under a regular user. +# 0 - do not allow +# 1 - allow +# +# Mandatory: no +# Default: +# AllowRoot=0 + +### Option: User +# Drop privileges to a specific, existing user on the system. +# Only has effect if run as 'root' and AllowRoot is disabled. +# +# Mandatory: no +# Default: +# User=zabbix + +### Option: Include +# You may include individual files or all files in a directory in the configuration file. +# Installing Zabbix will create include directory in /etc, unless modified during the compile time. +# +# Mandatory: no +# Default: +# Include= + +# Include=/etc/zabbix_server.general.conf +# Include=/etc/zabbix_server.conf.d/ +# Include=/etc/zabbix_server.conf.d/*.conf + +### Option: SSLCertLocation +# Location of SSL client certificates. +# This parameter is used only in web monitoring. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# SSLCertLocation=/usr/share/zabbix/ssl/certs + +### Option: SSLKeyLocation +# Location of private keys for SSL client certificates. +# This parameter is used only in web monitoring. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# SSLKeyLocation=/usr/share/zabbix/ssl/keys + +### Option: SSLCALocation +# Override the location of certificate authority (CA) files for SSL server certificate verification. +# If not set, system-wide directory will be used. +# This parameter is used in web monitoring, SMTP authentication, HTTP agent items and for communication with Vault. +# +# Mandatory: no +# Default: +# SSLCALocation= + +### Option: StatsAllowedIP +# List of comma delimited IP addresses, optionally in CIDR notation, or DNS names of external Zabbix instances. +# Stats request will be accepted only from the addresses listed here. If this parameter is not set no stats requests +# will be accepted. +# If IPv6 support is enabled then '127.0.0.1', '::127.0.0.1', '::ffff:127.0.0.1' are treated equally +# and '::/0' will allow any IPv4 or IPv6 address. +# '0.0.0.0/0' can be used to allow any IPv4 address. +# Example: StatsAllowedIP=127.0.0.1,192.168.1.0/24,::1,2001:db8::/32,zabbix.example.com +# +# Mandatory: no +# Default: +# StatsAllowedIP= +StatsAllowedIP=127.0.0.1 + +####### LOADABLE MODULES ####### + +### Option: LoadModulePath +# Full path to location of server modules. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# LoadModulePath=${libdir}/modules + +### Option: LoadModule +# Module to load at server startup. Modules are used to extend functionality of the server. +# Formats: +# LoadModule= +# LoadModule= +# LoadModule= +# Either the module must be located in directory specified by LoadModulePath or the path must precede the module name. +# If the preceding path is absolute (starts with '/') then LoadModulePath is ignored. +# It is allowed to include multiple LoadModule parameters. +# +# Mandatory: no +# Default: +# LoadModule= + +####### TLS-RELATED PARAMETERS ####### + +### Option: TLSCAFile +# Full pathname of a file containing the top-level CA(s) certificates for +# peer certificate verification. +# +# Mandatory: no +# Default: +# TLSCAFile= + +### Option: TLSCRLFile +# Full pathname of a file containing revoked certificates. +# +# Mandatory: no +# Default: +# TLSCRLFile= + +### Option: TLSCertFile +# Full pathname of a file containing the server certificate or certificate chain. +# +# Mandatory: no +# Default: +# TLSCertFile= + +### Option: TLSKeyFile +# Full pathname of a file containing the server private key. +# +# Mandatory: no +# Default: +# TLSKeyFile= + +####### For advanced users - TLS ciphersuite selection criteria ####### + +### Option: TLSCipherCert13 +# Cipher string for OpenSSL 1.1.1 or newer in TLS 1.3. +# Override the default ciphersuite selection criteria for certificate-based encryption. +# +# Mandatory: no +# Default: +# TLSCipherCert13= + +### Option: TLSCipherCert +# GnuTLS priority string or OpenSSL (TLS 1.2) cipher string. +# Override the default ciphersuite selection criteria for certificate-based encryption. +# Example for GnuTLS: +# NONE:+VERS-TLS1.2:+ECDHE-RSA:+RSA:+AES-128-GCM:+AES-128-CBC:+AEAD:+SHA256:+SHA1:+CURVE-ALL:+COMP-NULL:+SIGN-ALL:+CTYPE-X.509 +# Example for OpenSSL: +# EECDH+aRSA+AES128:RSA+aRSA+AES128 +# +# Mandatory: no +# Default: +# TLSCipherCert= + +### Option: TLSCipherPSK13 +# Cipher string for OpenSSL 1.1.1 or newer in TLS 1.3. +# Override the default ciphersuite selection criteria for PSK-based encryption. +# Example: +# TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 +# +# Mandatory: no +# Default: +# TLSCipherPSK13= + +### Option: TLSCipherPSK +# GnuTLS priority string or OpenSSL (TLS 1.2) cipher string. +# Override the default ciphersuite selection criteria for PSK-based encryption. +# Example for GnuTLS: +# NONE:+VERS-TLS1.2:+ECDHE-PSK:+PSK:+AES-128-GCM:+AES-128-CBC:+AEAD:+SHA256:+SHA1:+CURVE-ALL:+COMP-NULL:+SIGN-ALL +# Example for OpenSSL: +# kECDHEPSK+AES128:kPSK+AES128 +# +# Mandatory: no +# Default: +# TLSCipherPSK= + +### Option: TLSCipherAll13 +# Cipher string for OpenSSL 1.1.1 or newer in TLS 1.3. +# Override the default ciphersuite selection criteria for certificate- and PSK-based encryption. +# Example: +# TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 +# +# Mandatory: no +# Default: +# TLSCipherAll13= + +### Option: TLSCipherAll +# GnuTLS priority string or OpenSSL (TLS 1.2) cipher string. +# Override the default ciphersuite selection criteria for certificate- and PSK-based encryption. +# Example for GnuTLS: +# NONE:+VERS-TLS1.2:+ECDHE-RSA:+RSA:+ECDHE-PSK:+PSK:+AES-128-GCM:+AES-128-CBC:+AEAD:+SHA256:+SHA1:+CURVE-ALL:+COMP-NULL:+SIGN-ALL:+CTYPE-X.509 +# Example for OpenSSL: +# EECDH+aRSA+AES128:RSA+aRSA+AES128:kECDHEPSK+AES128:kPSK+AES128 +# +# Mandatory: no +# Default: +# TLSCipherAll= + +### Option: DBTLSConnect +# Setting this option enforces to use TLS connection to database. +# required - connect using TLS +# verify_ca - connect using TLS and verify certificate +# verify_full - connect using TLS, verify certificate and verify that database identity specified by DBHost +# matches its certificate +# On MySQL starting from 5.7.11 and PostgreSQL following values are supported: "required", "verify_ca" and +# "verify_full". +# On MariaDB starting from version 10.2.6 "required" and "verify_full" values are supported. +# Default is not to set any option and behavior depends on database configuration +# +# Mandatory: no +# Default: +# DBTLSConnect= + +### Option: DBTLSCAFile +# Full pathname of a file containing the top-level CA(s) certificates for database certificate verification. +# Supported only for MySQL and PostgreSQL +# +# Mandatory: no +# (yes, if DBTLSConnect set to one of: verify_ca, verify_full) +# Default: +# DBTLSCAFile= + +### Option: DBTLSCertFile +# Full pathname of file containing Zabbix server certificate for authenticating to database. +# Supported only for MySQL and PostgreSQL +# +# Mandatory: no +# Default: +# DBTLSCertFile= + +### Option: DBTLSKeyFile +# Full pathname of file containing the private key for authenticating to database. +# Supported only for MySQL and PostgreSQL +# +# Mandatory: no +# Default: +# DBTLSKeyFile= + +### Option: DBTLSCipher +# The list of encryption ciphers that Zabbix server permits for TLS protocols up through TLSv1.2 +# Supported only for MySQL +# +# Mandatory no +# Default: +# DBTLSCipher= + +### Option: DBTLSCipher13 +# The list of encryption ciphersuites that Zabbix server permits for TLSv1.3 protocol +# Supported only for MySQL, starting from version 8.0.16 +# +# Mandatory no +# Default: +# DBTLSCipher13= + +### Option: VaultToken +# Vault authentication token that should have been generated exclusively for Zabbix server with read only permission +# to paths specified in Vault macros and read only permission to path specified in optional VaultDBPath +# configuration parameter. +# It is an error if VaultToken and VAULT_TOKEN environment variable are defined at the same time. +# +# Mandatory: no +# Default: +# VaultToken= + +### Option: VaultURL +# Vault server HTTP[S] URL. System-wide CA certificates directory will be used if SSLCALocation is not specified. +# +# Mandatory: no +# Default: +# VaultURL=https://127.0.0.1:8200 + +### Option: VaultDBPath +# Vault path from where credentials for database will be retrieved by keys 'password' and 'username'. +# Example: secret/zabbix/database +# This option can only be used if DBUser and DBPassword are not specified. +# +# Mandatory: no +# Default: +# VaultDBPath= + +### Option: StartReportWriters +# Number of pre-forked report writer instances. +# +# Mandatory: no +# Range: 0-100 +# Default: +# StartReportWriters=0 + +### Option: WebServiceURL +# URL to Zabbix web service, used to perform web related tasks. +# Example: http://localhost:10053/report +# +# Mandatory: no +# Default: +# WebServiceURL= + +### Option: ServiceManagerSyncFrequency +# How often Zabbix will synchronize configuration of a service manager (in seconds). +# +# Mandatory: no +# Range: 1-3600 +# Default: +# ServiceManagerSyncFrequency=60 + +### Option: ProblemHousekeepingFrequency +# How often Zabbix will delete problems for deleted triggers (in seconds). +# +# Mandatory: no +# Range: 1-3600 +# Default: +# ProblemHousekeepingFrequency=60 + +## Option: StartODBCPollers +# Number of pre-forked ODBC poller instances. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartODBCPollers=1 + +####### For advanced users - TCP-related fine-tuning parameters ####### + +## Option: ListenBacklog +# The maximum number of pending connections in the queue. This parameter is passed to +# listen() function as argument 'backlog' (see "man listen"). +# +# Mandatory: no +# Range: 0 - INT_MAX (depends on system, too large values may be silently truncated to implementation-specified maximum) +# Default: SOMAXCONN (hard-coded constant, depends on system) +# ListenBacklog= + + +####### High availability cluster parameters ####### + +## Option: HANodeName +# The high availability cluster node name. +# When empty, server is working in standalone mode; a node with empty name is registered with address for the frontend to connect to. +# +# Mandatory: no +# Default: +# HANodeName= + +## Option: NodeAddress +# IP or hostname with optional port to specify how frontend should connect to the server. +# Format:
                                      [:] +# +# If IP or hostname is not set, then ListenIP value will be used. In case ListenIP is not set, localhost will be used. +# If port is not set, then ListenPort value will be used. In case ListenPort is not set, 10051 will be used. +# This option can be overridden by address specified in frontend configuration. +# +# Mandatory: no +# Default: +# NodeAddress=localhost:10051 diff --git a/plugins/zabbix/conf/zabbix_server.conf.bak b/plugins/zabbix/conf/zabbix_server.conf.bak new file mode 100644 index 000000000..b33d1926c --- /dev/null +++ b/plugins/zabbix/conf/zabbix_server.conf.bak @@ -0,0 +1,1130 @@ +# This is a configuration file for Zabbix server daemon +# To get more information about Zabbix, visit https://www.zabbix.com + +############ GENERAL PARAMETERS ################# + +### Option: ListenPort +# Listen port for trapper. +# +# Mandatory: no +# Range: 1024-32767 +# Default: +# ListenPort=10051 + +### Option: SourceIP +# Source IP address for outgoing connections. +# +# Mandatory: no +# Default: +# SourceIP= + +### Option: LogType +# Specifies where log messages are written to: +# system - syslog +# file - file specified with LogFile parameter +# console - standard output +# +# Mandatory: no +# Default: +# LogType=file + +### Option: LogFile +# Log file name for LogType 'file' parameter. +# +# Mandatory: yes, if LogType is set to file, otherwise no +# Default: +# LogFile= + +LogFile=/var/log/zabbix/zabbix_server.log + +### Option: LogFileSize +# Maximum size of log file in MB. +# 0 - disable automatic log rotation. +# +# Mandatory: no +# Range: 0-1024 +# Default: +# LogFileSize=1 + +LogFileSize=0 + +### Option: DebugLevel +# Specifies debug level: +# 0 - basic information about starting and stopping of Zabbix processes +# 1 - critical information +# 2 - error information +# 3 - warnings +# 4 - for debugging (produces lots of information) +# 5 - extended debugging (produces even more information) +# +# Mandatory: no +# Range: 0-5 +# Default: +# DebugLevel=3 + +### Option: PidFile +# Name of PID file. +# +# Mandatory: no +# Default: +# PidFile=/tmp/zabbix_server.pid + +PidFile=/run/zabbix/zabbix_server.pid + +### Option: SocketDir +# IPC socket directory. +# Directory to store IPC sockets used by internal Zabbix services. +# +# Mandatory: no +# Default: +# SocketDir=/tmp + +SocketDir=/run/zabbix + +### NOTE: Support for Oracle DB is deprecated since Zabbix 7.0 and will be removed in future versions. + +### Option: DBHost +# Database host name. +# If set to localhost, socket is used for MySQL. +# If set to empty string, socket is used for PostgreSQL. +# If set to empty string, the Net Service Name connection method is used to connect to Oracle database; also see +# the TNS_ADMIN environment variable to specify the directory where the tnsnames.ora file is located. +# +# Mandatory: no +# Default: +DBHost=127.0.0.1 + +### Option: DBName +# Database name. +# If the Net Service Name connection method is used to connect to Oracle database, specify the service name from +# the tnsnames.ora file or set to empty string; also see the TWO_TASK environment variable if DBName is set to +# empty string. +# +# Mandatory: yes +# Default: +# DBName= + +DBName=zabbix + +### Option: DBSchema +# Schema name. Used for PostgreSQL. +# +# Mandatory: no +# Default: +# DBSchema= + +### Option: DBUser +# Database user. +# +# Mandatory: no +# Default: +# DBUser= + +DBUser=zabbix + +### Option: DBPassword +# Database password. +# Comment this line if no password is used. +# +# Mandatory: no +# Default: +DBPassword=XXX + +### Option: DBSocket +# Path to MySQL socket. +# +# Mandatory: no +# Default: +# DBSocket= + +### Option: DBPort +# Database port when not using local socket. +# If the Net Service Name connection method is used to connect to Oracle database, the port number from the +# tnsnames.ora file will be used. The port number set here will be ignored. +# +# Mandatory: no +# Range: 1024-65535 +# Default: +# DBPort= + +### Option: AllowUnsupportedDBVersions +# Allow server to work with unsupported database versions. +# 0 - do not allow +# 1 - allow +# +# Mandatory: no +# Default: +# AllowUnsupportedDBVersions=0 + +### Option: HistoryStorageURL +# History storage HTTP[S] URL. +# +# Mandatory: no +# Default: +# HistoryStorageURL= + +### Option: HistoryStorageTypes +# Comma separated list of value types to be sent to the history storage. +# +# Mandatory: no +# Default: +# HistoryStorageTypes=uint,dbl,str,log,text + +### Option: HistoryStorageDateIndex +# Enable preprocessing of history values in history storage to store values in different indices based on date. +# 0 - disable +# 1 - enable +# +# Mandatory: no +# Default: +# HistoryStorageDateIndex=0 + +### Option: ExportDir +# Directory for real time export of events, history and trends in newline delimited JSON format. +# If set, enables real time export. +# +# Mandatory: no +# Default: +# ExportDir= + +### Option: ExportFileSize +# Maximum size per export file in bytes. +# Only used for rotation if ExportDir is set. +# +# Mandatory: no +# Range: 1M-1G +# Default: +# ExportFileSize=1G + +### Option: ExportType +# List of comma delimited types of real time export - allows to control export entities by their +# type (events, history, trends) individually. +# Valid only if ExportDir is set. +# +# Mandatory: no +# Default: +# ExportType=events,history,trends + +############ ADVANCED PARAMETERS ################ + +### Option: StartPollers +# Number of pre-forked instances of pollers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartPollers=5 + +### Option: StartAgentPollers +# Number of pre-forked instances of asynchronous Zabbix agent pollers. Also see MaxConcurrentChecksPerPoller. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartAgentPollers=1 + +### Option: StartHTTPAgentPollers +# Number of pre-forked instances of asynchronous HTTP agent pollers. Also see MaxConcurrentChecksPerPoller. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartHTTPAgentPollers=1 + +### Option: StartSNMPPollers +# Number of pre-forked instances of asynchronous SNMP pollers. Also see MaxConcurrentChecksPerPoller. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartSNMPPollers=1 + +### Option: MaxConcurrentChecksPerPoller +# Maximum number of asynchronous checks that can be executed at once by each HTTP agent poller or agent poller. +# +# Mandatory: no +# Range: 1-1000 +# Default: +# MaxConcurrentChecksPerPoller=1000 + +### Option: StartIPMIPollers +# Number of pre-forked instances of IPMI pollers. +# The IPMI manager process is automatically started when at least one IPMI poller is started. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartIPMIPollers=0 + + +### Option: StartPreprocessors +# Number of pre-started instances of preprocessing workers. +# +# Mandatory: no +# Range: 1-1000 +# Default: +# StartPreprocessors=3 + +### Option: StartConnectors +# Number of pre-forked instances of connector workers. +# The connector manager process is automatically started when connector worker is started. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartConnectors=0 + +### Option: StartPollersUnreachable +# Number of pre-forked instances of pollers for unreachable hosts (including IPMI and Java). +# At least one poller for unreachable hosts must be running if regular, IPMI or Java pollers +# are started. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartPollersUnreachable=1 + +### Option: StartHistoryPollers +# Number of pre-forked instances of history pollers. +# Only required for calculated checks. +# A database connection is required for each history poller instance. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartHistoryPollers=5 + +### Option: StartTrappers +# Number of pre-forked instances of trappers. +# Trappers accept incoming connections from Zabbix sender, active agents and active proxies. +# At least one trapper process must be running to display server availability and view queue +# in the frontend. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartTrappers=5 + +### Option: StartPingers +# Number of pre-forked instances of ICMP pingers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartPingers=1 + +### Option: StartDiscoverers +# Number of pre-started instances of discovery workers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartDiscoverers=5 + +### Option: StartHTTPPollers +# Number of pre-forked instances of HTTP pollers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartHTTPPollers=1 + +### Option: StartTimers +# Number of pre-forked instances of timers. +# Timers process maintenance periods. +# Only the first timer process handles host maintenance updates. Problem suppression updates are shared +# between all timers. +# +# Mandatory: no +# Range: 1-1000 +# Default: +# StartTimers=1 + +### Option: StartEscalators +# Number of pre-forked instances of escalators. +# +# Mandatory: no +# Range: 1-100 +# Default: +# StartEscalators=1 + +### Option: StartAlerters +# Number of pre-forked instances of alerters. +# Alerters send the notifications created by action operations. +# +# Mandatory: no +# Range: 1-100 +# Default: +# StartAlerters=3 + +### Option: JavaGateway +# IP address (or hostname) of Zabbix Java gateway. +# Only required if Java pollers are started. +# +# Mandatory: no +# Default: +# JavaGateway= + +### Option: JavaGatewayPort +# Port that Zabbix Java gateway listens on. +# +# Mandatory: no +# Range: 1024-32767 +# Default: +# JavaGatewayPort=10052 + +### Option: StartJavaPollers +# Number of pre-forked instances of Java pollers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartJavaPollers=0 + +### Option: StartVMwareCollectors +# Number of pre-forked vmware collector instances. +# +# Mandatory: no +# Range: 0-250 +# Default: +# StartVMwareCollectors=0 + +### Option: VMwareFrequency +# How often Zabbix will connect to VMware service to obtain a new data. +# +# Mandatory: no +# Range: 10-86400 +# Default: +# VMwareFrequency=60 + +### Option: VMwarePerfFrequency +# How often Zabbix will connect to VMware service to obtain performance data. +# +# Mandatory: no +# Range: 10-86400 +# Default: +# VMwarePerfFrequency=60 + +### Option: VMwareCacheSize +# Size of VMware cache, in bytes. +# Shared memory size for storing VMware data. +# Only used if VMware collectors are started. +# +# Mandatory: no +# Range: 256K-2G +# Default: +# VMwareCacheSize=8M + +### Option: VMwareTimeout +# Specifies how many seconds vmware collector waits for response from VMware service. +# +# Mandatory: no +# Range: 1-300 +# Default: +# VMwareTimeout=10 + +### Option: SNMPTrapperFile +# Temporary file used for passing data from SNMP trap daemon to the server. +# Must be the same as in zabbix_trap_receiver.pl or SNMPTT configuration file. +# +# Mandatory: no +# Default: +# SNMPTrapperFile=/tmp/zabbix_traps.tmp + +SNMPTrapperFile=/var/log/snmptrap/snmptrap.log + +### Option: StartSNMPTrapper +# If 1, SNMP trapper process is started. +# +# Mandatory: no +# Range: 0-1 +# Default: +# StartSNMPTrapper=0 + +### Option: ListenIP +# List of comma delimited IP addresses that the trapper should listen on. +# Trapper will listen on all network interfaces if this parameter is missing. +# +# Mandatory: no +# Default: +# ListenIP=0.0.0.0 + +### Option: HousekeepingFrequency +# How often Zabbix will perform housekeeping procedure (in hours). +# Housekeeping is removing outdated information from the database. +# To prevent Housekeeper from being overloaded, no more than 4 times HousekeepingFrequency +# hours of outdated information are deleted in one housekeeping cycle, for each item. +# To lower load on server startup housekeeping is postponed for 30 minutes after server start. +# With HousekeepingFrequency=0 the housekeeper can be only executed using the runtime control option. +# In this case the period of outdated information deleted in one housekeeping cycle is 4 times the +# period since the last housekeeping cycle, but not less than 4 hours and not greater than 4 days. +# +# Mandatory: no +# Range: 0-24 +# Default: +# HousekeepingFrequency=1 + +### Option: MaxHousekeeperDelete +# The table "housekeeper" contains "tasks" for housekeeping procedure in the format: +# [housekeeperid], [tablename], [field], [value]. +# No more than 'MaxHousekeeperDelete' rows (corresponding to [tablename], [field], [value]) +# will be deleted per one task in one housekeeping cycle. +# If set to 0 then no limit is used at all. In this case you must know what you are doing! +# +# Mandatory: no +# Range: 0-1000000 +# Default: +# MaxHousekeeperDelete=5000 + +### Option: CacheSize +# Size of configuration cache, in bytes. +# Shared memory size for storing host, item and trigger data. +# +# Mandatory: no +# Range: 128K-64G +# Default: +# CacheSize=32M + +### Option: CacheUpdateFrequency +# How often Zabbix will perform update of configuration cache, in seconds. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# CacheUpdateFrequency=10 + +### Option: StartDBSyncers +# Number of pre-forked instances of DB Syncers. +# +# Mandatory: no +# Range: 1-100 +# Default: +# StartDBSyncers=4 + +### Option: HistoryCacheSize +# Size of history cache, in bytes. +# Shared memory size for storing history data. +# +# Mandatory: no +# Range: 128K-2G +# Default: +# HistoryCacheSize=16M + +### Option: HistoryIndexCacheSize +# Size of history index cache, in bytes. +# Shared memory size for indexing history cache. +# +# Mandatory: no +# Range: 128K-2G +# Default: +# HistoryIndexCacheSize=4M + +### Option: TrendCacheSize +# Size of trend write cache, in bytes. +# Shared memory size for storing trends data. +# +# Mandatory: no +# Range: 128K-2G +# Default: +# TrendCacheSize=4M + +### Option: TrendFunctionCacheSize +# Size of trend function cache, in bytes. +# Shared memory size for caching calculated trend function data. +# +# Mandatory: no +# Range: 128K-2G +# Default: +# TrendFunctionCacheSize=4M + +### Option: ValueCacheSize +# Size of history value cache, in bytes. +# Shared memory size for caching item history data requests. +# Setting to 0 disables value cache. +# +# Mandatory: no +# Range: 0,128K-64G +# Default: +# ValueCacheSize=8M + +### Option: Timeout +# Specifies timeout for communications (in seconds). +# +# Mandatory: no +# Range: 1-30 +# Default: +# Timeout=3 + +Timeout=4 + +### Option: TrapperTimeout +# Specifies how many seconds trapper may spend processing new data. +# +# Mandatory: no +# Range: 1-300 +# Default: +# TrapperTimeout=300 + +### Option: UnreachablePeriod +# After how many seconds of unreachability treat a host as unavailable. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# UnreachablePeriod=45 + +### Option: UnavailableDelay +# How often host is checked for availability during the unavailability period, in seconds. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# UnavailableDelay=60 + +### Option: UnreachableDelay +# How often host is checked for availability during the unreachability period, in seconds. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# UnreachableDelay=15 + +### Option: AlertScriptsPath +# Full path to location of custom alert scripts. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# AlertScriptsPath=/usr/lib/zabbix/alertscripts + +### Option: ExternalScripts +# Full path to location of external scripts. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# ExternalScripts=/usr/lib/zabbix/externalscripts + +### Option: FpingLocation +# Location of fping. +# Make sure that fping binary has root ownership and SUID flag set. +# +# Mandatory: no +# Default: +# FpingLocation=/usr/sbin/fping + +FpingLocation=/usr/bin/fping + +### Option: Fping6Location +# Location of fping6. +# Make sure that fping6 binary has root ownership and SUID flag set. +# Make empty if your fping utility is capable to process IPv6 addresses. +# +# Mandatory: no +# Default: +# Fping6Location=/usr/sbin/fping6 + +Fping6Location=/usr/bin/fping6 + +### Option: SSHKeyLocation +# Location of public and private keys for SSH checks and actions. +# +# Mandatory: no +# Default: +# SSHKeyLocation= + +### Option: LogSlowQueries +# How long a database query may take before being logged (in milliseconds). +# Only works if DebugLevel set to 3, 4 or 5. +# 0 - don't log slow queries. +# +# Mandatory: no +# Range: 1-3600000 +# Default: +# LogSlowQueries=0 + +LogSlowQueries=3000 + +### Option: TmpDir +# Temporary directory. +# +# Mandatory: no +# Default: +# TmpDir=/tmp + +### Option: StartProxyPollers +# Number of pre-forked instances of pollers for passive proxies. +# +# Mandatory: no +# Range: 0-250 +# Default: +# StartProxyPollers=1 + +### Option: ProxyConfigFrequency +# How often Zabbix Server sends configuration data to a Zabbix Proxy in seconds. +# This parameter is used only for proxies in the passive mode. +# +# Mandatory: no +# Range: 1-3600*24*7 +# Default: +# ProxyConfigFrequency=10 + +### Option: ProxyDataFrequency +# How often Zabbix Server requests history data from a Zabbix Proxy in seconds. +# This parameter is used only for proxies in the passive mode. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# ProxyDataFrequency=1 + +### Option: StartLLDProcessors +# Number of pre-forked instances of low level discovery processors. +# +# Mandatory: no +# Range: 1-100 +# Default: +# StartLLDProcessors=2 + +### Option: AllowRoot +# Allow the server to run as 'root'. If disabled and the server is started by 'root', the server +# will try to switch to the user specified by the User configuration option instead. +# Has no effect if started under a regular user. +# 0 - do not allow +# 1 - allow +# +# Mandatory: no +# Default: +# AllowRoot=0 + +### Option: User +# Drop privileges to a specific, existing user on the system. +# Only has effect if run as 'root' and AllowRoot is disabled. +# +# Mandatory: no +# Default: +# User=zabbix + +### Option: Include +# You may include individual files or all files in a directory in the configuration file. +# Installing Zabbix will create include directory in /usr/local/etc, unless modified during the compile time. +# +# Mandatory: no +# Default: +# Include= + +# Include=/usr/local/etc/zabbix_server.general.conf +# Include=/usr/local/etc/zabbix_server.conf.d/ +# Include=/usr/local/etc/zabbix_server.conf.d/*.conf + +### Option: SSLCertLocation +# Location of SSL client certificates. +# This parameter is used in web monitoring and for communication with Vault. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# SSLCertLocation=${datadir}/zabbix/ssl/certs + +### Option: SSLKeyLocation +# Location of private keys for SSL client certificates. +# This parameter is used in web monitoring and for communication with Vault. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# SSLKeyLocation=${datadir}/zabbix/ssl/keys + +### Option: SSLCALocation +# Override the location of certificate authority (CA) files for SSL server certificate verification. +# If not set, system-wide directory will be used. +# This parameter is used in web monitoring, SMTP authentication, HTTP agent items and for communication with Vault. +# +# Mandatory: no +# Default: +# SSLCALocation= + +### Option: StatsAllowedIP +# List of comma delimited IP addresses, optionally in CIDR notation, or DNS names of external Zabbix instances. +# Stats request will be accepted only from the addresses listed here. If this parameter is not set no stats requests +# will be accepted. +# If IPv6 support is enabled then '127.0.0.1', '::127.0.0.1', '::ffff:127.0.0.1' are treated equally +# and '::/0' will allow any IPv4 or IPv6 address. +# '0.0.0.0/0' can be used to allow any IPv4 address. +# Example: StatsAllowedIP=127.0.0.1,192.168.1.0/24,::1,2001:db8::/32,zabbix.example.com +# +# Mandatory: no +# Default: +# StatsAllowedIP= +StatsAllowedIP=127.0.0.1 + +####### LOADABLE MODULES ####### + +### Option: LoadModulePath +# Full path to location of server modules. +# Default depends on compilation options. +# To see the default path run command "zabbix_server --help". +# +# Mandatory: no +# Default: +# LoadModulePath=${libdir}/modules + +### Option: LoadModule +# Module to load at server startup. Modules are used to extend functionality of the server. +# Formats: +# LoadModule= +# LoadModule= +# LoadModule= +# Either the module must be located in directory specified by LoadModulePath or the path must precede the module name. +# If the preceding path is absolute (starts with '/') then LoadModulePath is ignored. +# It is allowed to include multiple LoadModule parameters. +# +# Mandatory: no +# Default: +# LoadModule= + +####### TLS-RELATED PARAMETERS ####### + +### Option: TLSCAFile +# Full pathname of a file containing the top-level CA(s) certificates for +# peer certificate verification. +# +# Mandatory: no +# Default: +# TLSCAFile= + +### Option: TLSCRLFile +# Full pathname of a file containing revoked certificates. +# +# Mandatory: no +# Default: +# TLSCRLFile= + +### Option: TLSCertFile +# Full pathname of a file containing the server certificate or certificate chain. +# +# Mandatory: no +# Default: +# TLSCertFile= + +### Option: TLSKeyFile +# Full pathname of a file containing the server private key. +# +# Mandatory: no +# Default: +# TLSKeyFile= + +####### For advanced users - TLS ciphersuite selection criteria ####### + +### Option: TLSCipherCert13 +# Cipher string for OpenSSL 1.1.1 or newer in TLS 1.3. +# Override the default ciphersuite selection criteria for certificate-based encryption. +# +# Mandatory: no +# Default: +# TLSCipherCert13= + +### Option: TLSCipherCert +# GnuTLS priority string or OpenSSL (TLS 1.2) cipher string. +# Override the default ciphersuite selection criteria for certificate-based encryption. +# Example for GnuTLS: +# NONE:+VERS-TLS1.2:+ECDHE-RSA:+RSA:+AES-128-GCM:+AES-128-CBC:+AEAD:+SHA256:+SHA1:+CURVE-ALL:+COMP-NULL:+SIGN-ALL:+CTYPE-X.509 +# Example for OpenSSL: +# EECDH+aRSA+AES128:RSA+aRSA+AES128 +# +# Mandatory: no +# Default: +# TLSCipherCert= + +### Option: TLSCipherPSK13 +# Cipher string for OpenSSL 1.1.1 or newer in TLS 1.3. +# Override the default ciphersuite selection criteria for PSK-based encryption. +# Example: +# TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 +# +# Mandatory: no +# Default: +# TLSCipherPSK13= + +### Option: TLSCipherPSK +# GnuTLS priority string or OpenSSL (TLS 1.2) cipher string. +# Override the default ciphersuite selection criteria for PSK-based encryption. +# Example for GnuTLS: +# NONE:+VERS-TLS1.2:+ECDHE-PSK:+PSK:+AES-128-GCM:+AES-128-CBC:+AEAD:+SHA256:+SHA1:+CURVE-ALL:+COMP-NULL:+SIGN-ALL +# Example for OpenSSL: +# kECDHEPSK+AES128:kPSK+AES128 +# +# Mandatory: no +# Default: +# TLSCipherPSK= + +### Option: TLSCipherAll13 +# Cipher string for OpenSSL 1.1.1 or newer in TLS 1.3. +# Override the default ciphersuite selection criteria for certificate- and PSK-based encryption. +# Example: +# TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 +# +# Mandatory: no +# Default: +# TLSCipherAll13= + +### Option: TLSCipherAll +# GnuTLS priority string or OpenSSL (TLS 1.2) cipher string. +# Override the default ciphersuite selection criteria for certificate- and PSK-based encryption. +# Example for GnuTLS: +# NONE:+VERS-TLS1.2:+ECDHE-RSA:+RSA:+ECDHE-PSK:+PSK:+AES-128-GCM:+AES-128-CBC:+AEAD:+SHA256:+SHA1:+CURVE-ALL:+COMP-NULL:+SIGN-ALL:+CTYPE-X.509 +# Example for OpenSSL: +# EECDH+aRSA+AES128:RSA+aRSA+AES128:kECDHEPSK+AES128:kPSK+AES128 +# +# Mandatory: no +# Default: +# TLSCipherAll= + +### Option: DBTLSConnect +# Setting this option enforces to use TLS connection to database. +# required - connect using TLS +# verify_ca - connect using TLS and verify certificate +# verify_full - connect using TLS, verify certificate and verify that database identity specified by DBHost +# matches its certificate +# On MySQL starting from 5.7.11 and PostgreSQL following values are supported: "required", "verify_ca" and +# "verify_full". +# On MariaDB starting from version 10.2.6 "required" and "verify_full" values are supported. +# Default is not to set any option and behavior depends on database configuration +# +# Mandatory: no +# Default: +# DBTLSConnect= + +### Option: DBTLSCAFile +# Full pathname of a file containing the top-level CA(s) certificates for database certificate verification. +# Supported only for MySQL and PostgreSQL +# +# Mandatory: no +# (yes, if DBTLSConnect set to one of: verify_ca, verify_full) +# Default: +# DBTLSCAFile= + +### Option: DBTLSCertFile +# Full pathname of file containing Zabbix server certificate for authenticating to database. +# Supported only for MySQL and PostgreSQL +# +# Mandatory: no +# Default: +# DBTLSCertFile= + +### Option: DBTLSKeyFile +# Full pathname of file containing the private key for authenticating to database. +# Supported only for MySQL and PostgreSQL +# +# Mandatory: no +# Default: +# DBTLSKeyFile= + +### Option: DBTLSCipher +# The list of encryption ciphers that Zabbix server permits for TLS protocols up through TLSv1.2 +# Supported only for MySQL +# +# Mandatory no +# Default: +# DBTLSCipher= + +### Option: DBTLSCipher13 +# The list of encryption ciphersuites that Zabbix server permits for TLSv1.3 protocol +# Supported only for MySQL, starting from version 8.0.16 +# +# Mandatory no +# Default: +# DBTLSCipher13= + +### Option: Vault +# Specifies vault: +# HashiCorp - HashiCorp KV Secrets Engine - Version 2 +# CyberArk - CyberArk Central Credential Provider +# +# Mandatory: no +# Default: +# Vault=HashiCorp + +### Option: VaultToken +# Vault authentication token that should have been generated exclusively for Zabbix server with read only permission +# to paths specified in Vault macros and read only permission to path specified in optional VaultDBPath +# configuration parameter. +# It is an error if VaultToken and VAULT_TOKEN environment variable are defined at the same time. +# +# Mandatory: no +# (yes, if Vault is explicitly set to HashiCorp) +# Default: +# VaultToken= + +### Option: VaultURL +# Vault server HTTP[S] URL. System-wide CA certificates directory will be used if SSLCALocation is not specified. +# +# Mandatory: no +# Default: +# VaultURL=https://127.0.0.1:8200 + +### Option: VaultPrefix +# Custom prefix for Vault path or query depending on the Vault. +# Most suitable defaults will be used if not specified. +# Note that 'data' is automatically appended after mountpoint for HashiCorp if VaultPrefix is not specified. +# Example prefix for HashiCorp: +# /v1/secret/data/ +# Example prefix for CyberArk: +# /AIMWebService/api/Accounts? +# Mandatory: no +# Default: +# VaultPrefix= + +### Option: VaultDBPath +# Vault path or query depending on the Vault from where credentials for database will be retrieved by keys. +# Keys used for HashiCorp are 'password' and 'username'. +# Example path: +# secret/zabbix/database +# Keys used for CyberArk are 'Content' and 'UserName'. +# Example query: +# AppID=zabbix_server&Query=Safe=passwordSafe;Object=zabbix_server_database +# This option can only be used if DBUser and DBPassword are not specified. +# +# Mandatory: no +# Default: +# VaultDBPath= + +### Option: VaultTLSCertFile +# Name of the SSL certificate file used for client authentication. The certificate file must be in PEM1 format. +# If the certificate file contains also the private key, leave the SSL key file field empty. The directory +# containing this file is specified by configuration parameter SSLCertLocation. +# +# Mandatory: no +# Default: +# VaultTLSCertFile= + +### Option: VaultTLSKeyFile +# Name of the SSL private key file used for client authentication. The private key file must be in PEM1 format. +# The directory containing this file is specified by configuration parameter SSLKeyLocation. +# +# Mandatory: no +# Default: +# VaultTLSKeyFile= + +### Option: StartReportWriters +# Number of pre-forked report writer instances. +# +# Mandatory: no +# Range: 0-100 +# Default: +# StartReportWriters=0 + +### Option: WebServiceURL +# URL to Zabbix web service, used to perform web related tasks. +# Example: http://localhost:10053/report +# +# Mandatory: no +# Default: +# WebServiceURL= + +### Option: ServiceManagerSyncFrequency +# How often Zabbix will synchronize configuration of a service manager (in seconds). +# +# Mandatory: no +# Range: 1-3600 +# Default: +# ServiceManagerSyncFrequency=60 + +### Option: ProblemHousekeepingFrequency +# How often Zabbix will delete problems for deleted triggers (in seconds). +# +# Mandatory: no +# Range: 1-3600 +# Default: +# ProblemHousekeepingFrequency=60 + +## Option: StartODBCPollers +# Number of pre-forked ODBC poller instances. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartODBCPollers=1 + +### Option: EnableGlobalScripts +# Enable global scripts on Zabbix server. +# 0 - disable +# 1 - enable +# +# Mandatory: no +# Default: +# EnableGlobalScripts=1 +EnableGlobalScripts=0 + +# Option: AllowSoftwareUpdateCheck +# Allow Zabbix UI to receive information about software updates from zabbix.com +# 0 - disable software update checks +# 1 - enable software update checks +# +# Mandatory: no +# Default: +# AllowSoftwareUpdateCheck=1 + +### Option: SMSDevices +# List of comma delimited modem files allowed to use Zabbix server +# SMS sending not possible if this parameter is not set +# Example: SMSDevices=/dev/ttyUSB0,/dev/ttyUSB1 +# +# Mandatory: no +# Default: +# SMSDevices= + +####### For advanced users - TCP-related fine-tuning parameters ####### + +## Option: ListenBacklog +# The maximum number of pending connections in the queue. This parameter is passed to +# listen() function as argument 'backlog' (see "man listen"). +# +# Mandatory: no +# Range: 0 - INT_MAX (depends on system, too large values may be silently truncated to implementation-specified maximum) +# Default: SOMAXCONN (hard-coded constant, depends on system) +# ListenBacklog= + + +####### High availability cluster parameters ####### + +## Option: HANodeName +# The high availability cluster node name. +# When empty, server is working in standalone mode; a node with empty name is registered with address for the frontend to connect to. +# +# Mandatory: no +# Default: +# HANodeName= + +## Option: NodeAddress +# IP or hostname with optional port to specify how frontend should connect to the server. +# Format:
                                      [:] +# +# If IP or hostname is not set, then ListenIP value will be used. In case ListenIP is not set, localhost will be used. +# If port is not set, then ListenPort value will be used. In case ListenPort is not set, 10051 will be used. +# This option can be overridden by address specified in frontend configuration. +# +# Mandatory: no +# Default: +# NodeAddress=localhost:10051 + +####### Browser monitoring ####### + +### Option: WebDriverURL +# WebDriver interface HTTP[S] URL. For example http://localhost:4444 used with Selenium WebDriver standalone server. +# +# Mandatory: no +# Default: +# WebDriverURL= + +### Option: StartBrowserPollers +# Number of pre-forked instances of browser item pollers. +# +# Mandatory: no +# Range: 0-1000 +# Default: +# StartBrowserPollers=1 diff --git a/plugins/zabbix/conf/zabbix_server6.conf b/plugins/zabbix/conf/zabbix_server6.conf new file mode 100644 index 000000000..3c20f6938 --- /dev/null +++ b/plugins/zabbix/conf/zabbix_server6.conf @@ -0,0 +1,13 @@ +LogFile=/var/log/zabbixsrv/zabbix_server.log +LogFileSize=1 +ListenPort=10051 +PidFile=/run/zabbixsrv/zabbix_server.pid + +DBHost=127.0.0.1 +DBPort={$ZABBIX_DB_PORT} +DBName=zabbix +DBUser=zabbix +DBPassword={$ZABBIX_DB_PASS} +AlertScriptsPath=/var/lib/zabbixsrv/alertscripts +ExternalScripts=/var/lib/zabbixsrv/externalscripts +TmpDir=/var/lib/zabbixsrv/tmp diff --git a/plugins/zabbix/data/server6.0.sql.gz b/plugins/zabbix/data/server6.0.sql.gz new file mode 100644 index 000000000..32513529d Binary files /dev/null and b/plugins/zabbix/data/server6.0.sql.gz differ diff --git a/plugins/zabbix/data/server7.0.sql.gz b/plugins/zabbix/data/server7.0.sql.gz new file mode 100644 index 000000000..d0118365d Binary files /dev/null and b/plugins/zabbix/data/server7.0.sql.gz differ diff --git a/plugins/zabbix/ico.png b/plugins/zabbix/ico.png new file mode 100644 index 000000000..01024eb07 Binary files /dev/null and b/plugins/zabbix/ico.png differ diff --git a/plugins/zabbix/ico.svg b/plugins/zabbix/ico.svg new file mode 100644 index 000000000..9425964a3 --- /dev/null +++ b/plugins/zabbix/ico.svg @@ -0,0 +1 @@ + diff --git a/plugins/zabbix/index.html b/plugins/zabbix/index.html new file mode 100755 index 000000000..3fa1f338c --- /dev/null +++ b/plugins/zabbix/index.html @@ -0,0 +1,34 @@ + + +
                                      +
                                      +
                                      +
                                      +

                                      服务

                                      +

                                      自启动

                                      +

                                      OP配置

                                      +

                                      PHP配置

                                      +

                                      ZS配置

                                      +

                                      ZS运行日志

                                      +

                                      ZA配置

                                      +

                                      ZA运行日志

                                      +

                                      相关说明

                                      + +
                                      +
                                      +
                                      +
                                      +
                                      +
                                      + \ No newline at end of file diff --git a/plugins/zabbix/index.py b/plugins/zabbix/index.py new file mode 100755 index 000000000..7be515276 --- /dev/null +++ b/plugins/zabbix/index.py @@ -0,0 +1,537 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'zabbix' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + + if current_os.startswith('freebsd'): + return '/etc/rc.d/' + getPluginName() + + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/web_conf/nginx/vhost/zabbix.conf" + return path + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + cmd = "ps aux|grep zabbix_server |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + +def getInstallVerion(): + version_pl = getServerDir() + "/version.pl" + version = mw.readFile(version_pl).strip() + return version + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + content = content.replace('{$ZABBIX_ROOT}', '/usr/share/zabbix') + content = content.replace('{$ZABBIX_PORT}', '18888') + + psdb = pSqliteDb('databases') + db_pass = psdb.where('name = ?', ('zabbix',)).getField('password') + content = content.replace('{$ZABBIX_DB_PORT}', getMySQLPort()) + content = content.replace('{$ZABBIX_DB_PASS}', db_pass) + return content + + +def getMySQLConf(): + choose_mysql = getServerDir()+'/mysql.pl' + if os.path.exists(choose_mysql): + ver = mw.readFile(choose_mysql) + return mw.getServerDir() + '/'+ver+'/etc/my.cnf' + + apt_path = mw.getServerDir() + '/mysql-apt/etc/my.cnf' + if os.path.exists(apt_path): + mw.writeFile(choose_mysql, 'mysql-apt') + return apt_path + + yum_path = mw.getServerDir() + '/mysql-yum/etc/my.cnf' + if os.path.exists(yum_path): + mw.writeFile(choose_mysql, 'mysql-yum') + return yum_path + + path = mw.getServerDir() + '/mysql/etc/my.cnf' + if os.path.exists(path): + mw.writeFile(choose_mysql, 'mysql') + return path + return path + + +def getMySQLPort(): + file = getMySQLConf() + content = mw.readFile(file) + rep = r'port\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getMySQLSocketFile(): + file = getMySQLConf() + content = mw.readFile(file) + rep = r'socket\s*=\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def getMySQLBin(): + choose_mysql = getServerDir()+'/mysql.pl' + ver = mw.readFile(choose_mysql) + mysql_dir = mw.getServerDir() + '/'+ver + + if ver == 'mysql-apt': + return '/www/server/mysql-apt/bin/usr/bin/mysql' + if ver == 'mysql-yum': + return '/www/server/mysql-yum/bin/usr/bin/mysql' + return '/www/server/mysql/bin/mysql' + +def getMySQLBinLink(): + choose_mysql = getServerDir()+'/mysql.pl' + ver = mw.readFile(choose_mysql) + mysql_dir = mw.getServerDir() + '/'+ver + + if ver == 'mysql-apt': + return '/www/server/mysql-apt/bin/usr/bin/mysql -S /www/server/mysql-apt/mysql.sock' + if ver == 'mysql-yum': + return '/www/server/mysql-yum/bin/usr/bin/mysql -S /www/server/mysql-yum/mysql.sock' + return '/www/server/mysql/bin/mysql -S /www/server/mysql/mysql.sock' + +def pSqliteDb(dbname='databases'): + choose_mysql = getServerDir()+'/mysql.pl' + ver = mw.readFile(choose_mysql) + + mysql_dir = mw.getServerDir() + '/'+ver + conn = mw.M(dbname).dbPos(mysql_dir, 'mysql') + return conn + + +def pMysqlDb(): + # pymysql + db = mw.getMyORM() + db.setDbName('zabbix') + db.setPort(getMySQLPort()) + db.setSocket(getMySQLSocketFile()) + db.setPwd(pSqliteDb('config').where('id=?', (1,)).getField('mysql_root')) + return db + + +def getInstalledPhpConfDir(): + phpver = ["80","81","82","83","84"] + php_type = ['php-apt','php-yum', 'php']; + + for pt in php_type: + for ver in phpver: + php_install_dir = mw.getServerDir() + '/'+ pt+'/'+ver + if os.path.exists(php_install_dir): + if pt == 'php-apt': + return pt + ver[0:1]+'.'+ver[1:2] + if pt == 'php': + return pt + '-' + ver + if pt == 'php-yum': + return pt + '-' + ver + return pt + ver + return 'php-80' + +def isInstalledPhp(): + phpver = ["80","81","82","83","84","85"] + php_type = ['php-apt','php-yum', 'php']; + + for pt in php_type: + for ver in phpver: + php_install_dir = mw.getServerDir() + '/'+ pt+'/'+ver + if os.path.exists(php_install_dir): + return True + return False + +def isInstalledMySQL(): + mysql_type = ['mysql-apt','mysql-yum', 'mysql']; + for mt in mysql_type: + mysql_install_dir = mw.getServerDir() + '/'+ mt + if os.path.exists(mysql_install_dir): + return True + return False + + +def zabbixNginxConf(): + return mw.getServerDir()+'/web_conf/nginx/vhost/zabbix.conf' + +def zabbixPhpConf(): + # ver = getInstallVerion() + # if ver == '6.0': + # return '/usr/share/zabbix/conf/zabbix.conf.php' + return '/etc/zabbix/web/zabbix.conf.php' + +def zabbixServerConf(): + ver = getInstallVerion() + if ver == '6.0': + return '/etc/zabbix_server.conf' + return '/etc/zabbix/zabbix_server.conf' + +def zabbixAgentConf(): + return '/etc/zabbix/zabbix_agentd.conf' + +def zabbixImportMySQLDataFile(): + tgz_file = getPluginDir()+"/data/server6.0.sql.gz" + ver = getInstallVerion() + if ver == '6.0': + return tgz_file + return '/usr/share/zabbix-sql-scripts/mysql/server.sql.gz' + +def zabbixImportMySQLData(): + mysql_conf = getMySQLConf() + if not os.path.exists(mysql_conf): + exit("需要安装MySQL") + + choose_mysql = getServerDir()+'/mysql.pl' + ver = mw.readFile(choose_mysql) + + pmdb = pMysqlDb() + psdb = pSqliteDb('databases') + find_ps_zabbix = psdb.field('id').where('name = ?', ('zabbix',)).select() + if len(find_ps_zabbix) < 1: + db_pass = mw.getRandomString(16) + # 创建数据 + cmd = 'python3 plugins/'+ver+'/index.py add_db {"name":"zabbix","codeing":"utf8mb4","db_user":"zabbix","password":"'+db_pass+'","dataAccess":"127.0.0.1","ps":"zabbix","address":"127.0.0.1"}' + # print(cmd) + mw.execShell(cmd) + pmdb.query("ALTER DATABASE `zabbix` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin") + pmdb.query("grant all privileges on zabbix.* to zabbix@127.0.0.1") + + + db_pass = psdb.where('name = ?', ('zabbix',)).getField('password') + find_zabbix_version = pmdb.query("show tables like 'dbversion'") + if len(find_zabbix_version) == 0: + # 初始化导入数据 + pmdb.query("set global log_bin_trust_function_creators=1") + + mysql_bin = getMySQLBinLink() + + tgz_file = zabbixImportMySQLDataFile() + # zcat /usr/share/zabbix-sql-scripts/mysql/server.sql.gz | /www/server/mysql/bin/mysql --default-character-set=utf8mb4 -uzabbix -p"LGhb1f7QG6SDL5CX" zabbix + import_data_cmd = 'zcat '+tgz_file+' | '+mysql_bin+' --default-character-set=utf8mb4 -uzabbix -p"'+db_pass+'" zabbix' + # print(import_data_cmd) + mw.execShell(import_data_cmd) + # pmdb.query("set global log_bin_trust_function_creators=0") + + + ver = getInstallVerion() + if ver == '6.0': + pmdb.query("update dbversion set mandatory=6000000") + + return True + +def initOpConf(): + nginx_src_tpl = getPluginDir()+'/conf/zabbix.nginx.conf' + nginx_dst_vhost = zabbixNginxConf() + + phpver = getInstalledPhpConfDir() + + # nginx配置 + if not os.path.exists(nginx_dst_vhost): + content = mw.readFile(nginx_src_tpl) + content = contentReplace(content) + content = content.replace('{$PHP_VER}',phpver) + mw.writeFile(nginx_dst_vhost, content) + +def initZsConf(): + ver = getInstallVerion() + zs_src_tpl = getPluginDir()+'/conf/zabbix_server.conf' + if ver == '6.0': + zs_src_tpl = getPluginDir()+'/conf/zabbix_server6.conf' + + zs_dst_path = zabbixServerConf() + + # zabbix_server配置 + content = mw.readFile(zs_src_tpl) + content = contentReplace(content) + mw.writeFile(zs_dst_path, content) + +def initPhpConf(): + php_src_tpl = getPluginDir()+'/conf/zabbix.conf.php' + php_dst_path = zabbixPhpConf() + # php配置 + # if not os.path.exists(php_dst_path): + content = mw.readFile(php_src_tpl) + content = contentReplace(content) + mw.writeFile(php_dst_path, content) + +def initAgentConf(): + za_src_tpl = getPluginDir()+'/conf/zabbix_agentd.conf' + za_dst_path = zabbixAgentConf() + + # zabbix_agent配置 + content = mw.readFile(za_src_tpl) + content = contentReplace(content) + mw.writeFile(za_dst_path, content) + +def openPort(): + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort('18888', 'zabbix-web', 'port') + MwFirewall.instance().addAcceptPort('10051', 'zabbix-server', 'port') + MwFirewall.instance().addAcceptPort('10050', 'zabbix-agent', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + return True + + +def initDreplace(): + # 导入MySQL配置 + zabbixImportMySQLData() + + # 初始化OP配置 + initOpConf() + + + init_file = getServerDir() + '/init.pl' + if not os.path.exists(init_file): + initZsConf() + initAgentConf() + initPhpConf() + openPort() + mw.writeFile(init_file, 'ok') + return True + + +def zOp(method): + + initDreplace() + + data = mw.execShell('systemctl ' + method + ' zabbix-server') + mw.execShell('systemctl ' + method + ' zabbix-agent') + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + val = zOp('start') + mw.restartWeb() + return val + + +def stop(): + val = zOp('stop') + + # 删除nginx配置 + nginx_dst_vhost = zabbixNginxConf() + if os.path.exists(nginx_dst_vhost): + os.remove(nginx_dst_vhost) + + mw.restartWeb() + + return val + +def restart(): + status = zOp('restart') + return status + +def reload(): + initZsConf() + initAgentConf() + initPhpConf() + return zOp('reload') + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + shell_cmd = 'systemctl status zabbix-server | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + data = mw.execShell('systemctl enable zabbix-server') + if data[1] != '': + return data[1] + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + data = mw.execShell('systemctl disable zabbix-server') + if data[1] != '': + return data[1] + return 'ok' + +def runLog(): + zs_conf = zabbixServerConf() + content = mw.readFile(zs_conf) + + rep = r'LogFile=\s*(.*)' + tmp = re.search(rep, content) + + if tmp.groups(): + return tmp.groups()[0].strip() + return '/var/log/zabbix/zabbix_server.log' + +def zabbixAgentLog(): + za_conf = zabbixAgentConf() + content = mw.readFile(za_conf) + + rep = r'LogFile=\s*(.*)' + tmp = re.search(rep, content) + + if tmp.groups(): + return tmp.groups()[0].strip() + return '/var/log/zabbix/zabbix_agentd.log' + + +def installPreInspection(): + cmd = "cat /etc/*-release | grep PRETTY_NAME |awk -F = '{print $2}' | awk -F '\"' '{print $2}'| awk '{print $1}'" + sys = mw.execShell(cmd) + + + cmd = "cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F '\"' '{print $2}'" + sys_id = mw.execShell(cmd) + + sysName = sys[0].strip().lower() + sysId = sys_id[0].strip().lower() + + # opensuse + if not sysName in ['debian','centos','ubuntu','almalinux','rocky']: + return '不支持该系统' + + if sysName == 'debian' and not sysId in ['12']: + return '不支持,'+sysName+'['+sysId+'],仅支持debian12!' + + openresty_dir = mw.getServerDir() + "/openresty" + if not os.path.exists(openresty_dir): + return '需要安装Openresty插件' + + is_installed_php = isInstalledPhp() + if not is_installed_php: + return '需要安装PHP/PHP-APT/PHP-YUM插件,至少8.0!' + + + is_installed_mysql = isInstalledMySQL() + if not is_installed_mysql: + return '需要安装MySQL/MySQL-APT/MySQL-YUM插件,至少8.0!' + + return 'ok' + + +def uninstallPreInspection(): + return 'ok' + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'conf': + print(zabbixNginxConf()) + elif func == 'php_conf': + print(zabbixPhpConf()) + elif func == 'zabbix_server_conf': + print(zabbixServerConf()) + elif func == 'zabbix_agent_conf': + print(zabbixAgentConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'zabbix_agent_log': + print(zabbixAgentLog()) + else: + print('error') diff --git a/plugins/zabbix/info.json b/plugins/zabbix/info.json new file mode 100755 index 000000000..c77a1334b --- /dev/null +++ b/plugins/zabbix/info.json @@ -0,0 +1,19 @@ +{ + "sort": 7, + "ps": "Zabbix是一个成熟、易用的企业级开源监控解决方案,适用于百万级指标的网络监控和应用监控[开发中]", + "name": "zabbix", + "title": "Zabbix", + "shell": "install.sh", + "versions":["6.0","7.0"], + "tip": "soft", + "install_pre_inspection":true, + "uninstall_pre_inspection":true, + "checks": "server/zabbix", + "path": "server/zabbix", + "display": 1, + "author": "midoks", + "date": "2022-07-14", + "home": "https://www.zabbix.com/", + "type": 0, + "pid": "5" +} diff --git a/plugins/zabbix/install.sh b/plugins/zabbix/install.sh new file mode 100755 index 000000000..349265683 --- /dev/null +++ b/plugins/zabbix/install.sh @@ -0,0 +1,85 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://www.zabbix.com + +# cd /www/server/mdserver-web/plugins/zabbix && /bin/bash install.sh install 7.0 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/zabbix/index.py start + + + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +sysName=`uname` +echo "use system: ${sysName}" + +OSNAME=`bash ${rootPath}/scripts/getos.sh` + +if [ "" == "$OSNAME" ];then + OSNAME=`cat ${rootPath}/data/osname.pl` +fi + +if [ "macos" == "$OSNAME" ];then + echo "不支持Macox" + exit +fi + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/zabbix + + mkdir -p $serverPath/zabbix + echo "${VERSION}" > $serverPath/zabbix/version.pl + + shell_file=${curPath}/versions/${VERSION}/${OSNAME}.sh + + if [ -f $shell_file ];then + bash -x $shell_file install + else + echo '不支持...' + exit 1 + fi + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/zabbix/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/zabbix/index.py initd_install + + + if [ -d /etc/zabbix/web ];then + chown -R www:www /etc/zabbix/web + fi + echo 'Zabbix安装完成' +} + +Uninstall_App() +{ + shell_file=${curPath}/versions/${VERSION}/${OSNAME}.sh + if [ -f $shell_file ];then + bash -x $shell_file uninstall + fi + + cd ${rootPath} && python3 ${rootPath}/plugins/zabbix/index.py stop + cd ${rootPath} && python3 ${rootPath}/plugins/zabbix/index.py initd_uninstall + + rm -rf $serverPath/zabbix + rm -rf $serverPath/source/zabbix + echo 'Zabbix卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/js/zabbix.js b/plugins/zabbix/js/zabbix.js new file mode 100755 index 000000000..da1d95bc2 --- /dev/null +++ b/plugins/zabbix/js/zabbix.js @@ -0,0 +1,66 @@ +function zabbixPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'zabbix'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function zabbixPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'zabbix'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function zabbixReadme(){ + var readme = '
                                        '; + readme += '
                                      • 默认配置OpenResty端口:18888
                                      • '; + readme += '
                                      • 初始化账户:Admin/zabbix
                                      • '; + readme += '
                                      • https://www.zabbix.com/download
                                      • '; + readme += '
                                      '; + + $('.soft-man-con').html(readme); +} + diff --git a/plugins/zabbix/versions/6.0/alma.sh b/plugins/zabbix/versions/6.0/alma.sh new file mode 100644 index 000000000..d432a18e4 --- /dev/null +++ b/plugins/zabbix/versions/6.0/alma.sh @@ -0,0 +1,44 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-6.0-5.el${SYS_VERSION_ID:0:1}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/6.0/alma/${SYS_VERSION_ID:0:1}/x86_64/${ZABBIX_NAME} + + cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + dnf install -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + dnf install -y zabbix-agent +} + +Uninstall_App() +{ + dnf remove -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + dnf install -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/6.0/centos.sh b/plugins/zabbix/versions/6.0/centos.sh new file mode 100644 index 000000000..5ddb82e7a --- /dev/null +++ b/plugins/zabbix/versions/6.0/centos.sh @@ -0,0 +1,44 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-6.0-5.el${SYS_VERSION_ID}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/6.0/rhel/${SYS_VERSION_ID}/x86_64/${ZABBIX_NAME} + + cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + dnf install -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy zabbix-agent + + # dnf module switch-to zabbix-web-1:7.0.4 +} + +Uninstall_App() +{ + dnf remove -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/6.0/debian.sh b/plugins/zabbix/versions/6.0/debian.sh new file mode 100644 index 000000000..3df4767ee --- /dev/null +++ b/plugins/zabbix/versions/6.0/debian.sh @@ -0,0 +1,52 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +debian_suffix= +if [ "$SYS_ARCH" == "aarch64" ];then + debian_suffix="-arm64" +fi + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release_6.0-5+debian${SYS_VERSION_ID}_all.deb + echo "wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/6.0/debian${debian_suffix}/pool/main/z/zabbix-release/${ZABBIX_NAME}" + if [ ! -f $serverPath/source/zabbix/${ZABBIX_NAME} ];then + wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/6.0/debian${debian_suffix}/pool/main/z/zabbix-release/${ZABBIX_NAME} + fi + + dpkg --configure -a + cd $serverPath/source/zabbix && dpkg -i ${ZABBIX_NAME} + apt update -y + rm -rf /etc/zabbix/zabbix_server.conf.dpkg-new + rm -rf /etc/zabbix/zabbix_server.conf + apt install -y zabbix-server-mysql zabbix-frontend-php zabbix-sql-scripts zabbix-agent zabbix-get +} + +Uninstall_App() +{ + apt remove -y zabbix-server-mysql zabbix-frontend-php zabbix-sql-scripts zabbix-agent zabbix-get + rm -rf /etc/zabbix + dpkg --configure -a + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/6.0/opensuse.sh b/plugins/zabbix/versions/6.0/opensuse.sh new file mode 100644 index 000000000..3b316159b --- /dev/null +++ b/plugins/zabbix/versions/6.0/opensuse.sh @@ -0,0 +1,45 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-6.0-4.sles${SYS_VERSION_ID:0:2}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/6.0/sles/${SYS_VERSION_ID:0:2}/x86_64/${ZABBIX_NAME} + echo "rpm -Uvh https://repo.zabbix.com/zabbix/6.0/sles/${SYS_VERSION_ID:0:2}/x86_64/${ZABBIX_NAME}" + + # cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + zypper install -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + zypper install -y zabbix-agent +} + +Uninstall_App() +{ + zypper remove -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + zypper remove -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/6.0/rocky.sh b/plugins/zabbix/versions/6.0/rocky.sh new file mode 100644 index 000000000..b9e8cfbec --- /dev/null +++ b/plugins/zabbix/versions/6.0/rocky.sh @@ -0,0 +1,44 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-6.0-5.el${SYS_VERSION_ID:0:1}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/6.0/rocky/${SYS_VERSION_ID:0:1}/x86_64/${ZABBIX_NAME} + + cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + dnf install -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + dnf install -y zabbix-agent +} + +Uninstall_App() +{ + dnf remove -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + dnf install -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/6.0/ubuntu.sh b/plugins/zabbix/versions/6.0/ubuntu.sh new file mode 100644 index 000000000..b564164a0 --- /dev/null +++ b/plugins/zabbix/versions/6.0/ubuntu.sh @@ -0,0 +1,58 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +SYS_ARCH=`arch` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +ubuntu_suffix= +if [ "$SYS_ARCH" == "aarch64" ];then + ubuntu_suffix="-arm64" +fi + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release_6.0-5+ubuntu${SYS_VERSION_ID}_all.deb + echo "wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/6.0/ubuntu${ubuntu_suffix}/pool/main/z/zabbix-release/${ZABBIX_NAME}" + if [ ! -f $serverPath/source/zabbix/${ZABBIX_NAME} ];then + wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/6.0/ubuntu${ubuntu_suffix}/pool/main/z/zabbix-release/${ZABBIX_NAME} + fi + + # apt-get -f install + # dpkg --configure -a + + cd $serverPath/source/zabbix && dpkg -i ${ZABBIX_NAME} + apt update -y + + apt install -y zabbix-server-mysql zabbix-frontend-php zabbix-sql-scripts zabbix-get + apt install -y zabbix-agent +} + +Uninstall_App() +{ + apt remove -y zabbix-server-mysql zabbix-frontend-php zabbix-sql-scripts zabbix-get + apt remove -y zabbix-agent + rm -rf /etc/zabbix + + # dpkg --configure -a + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/7.0/alma.sh b/plugins/zabbix/versions/7.0/alma.sh new file mode 100644 index 000000000..d53b56da3 --- /dev/null +++ b/plugins/zabbix/versions/7.0/alma.sh @@ -0,0 +1,44 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-7.0-4.el${SYS_VERSION_ID:0:1}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/7.0/alma/${SYS_VERSION_ID:0:1}/x86_64/${ZABBIX_NAME} + + # cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + dnf install -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + dnf install -y zabbix-agent +} + +Uninstall_App() +{ + dnf remove -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + dnf install -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/7.0/centos.sh b/plugins/zabbix/versions/7.0/centos.sh new file mode 100644 index 000000000..8bfddec8c --- /dev/null +++ b/plugins/zabbix/versions/7.0/centos.sh @@ -0,0 +1,44 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-7.0-4.el${SYS_VERSION_ID}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/7.0/centos/${SYS_VERSION_ID}/x86_64/${ZABBIX_NAME} + + # cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + dnf install -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy zabbix-agent + + # dnf module switch-to zabbix-web-1:7.0.4 +} + +Uninstall_App() +{ + dnf remove -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/7.0/debian.sh b/plugins/zabbix/versions/7.0/debian.sh new file mode 100644 index 000000000..3492f1786 --- /dev/null +++ b/plugins/zabbix/versions/7.0/debian.sh @@ -0,0 +1,57 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +sysArch=`arch` + +debian_suffix= +if [ "$sysArch" == "aarch64" ];then + debian_suffix="-arm64" +fi + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release_7.0-2+debian${SYS_VERSION_ID}_all.deb + echo "wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/7.0/debian${debian_suffix}/pool/main/z/zabbix-release/${ZABBIX_NAME}" + if [ ! -f $serverPath/source/zabbix/${ZABBIX_NAME} ];then + wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/7.0/debian${debian_suffix}/pool/main/z/zabbix-release/${ZABBIX_NAME} + fi + + # apt-get -f install + # dpkg --configure -a + + cd $serverPath/source/zabbix && dpkg -i ${ZABBIX_NAME} + apt update -y + + apt install -y zabbix-server-mysql zabbix-frontend-php zabbix-sql-scripts zabbix-get + apt install -y zabbix-agent +} + +Uninstall_App() +{ + apt remove -y zabbix-server-mysql zabbix-frontend-php zabbix-sql-scripts zabbix-get + apt remove -y zabbix-agent + rm -rf /etc/zabbix + + # dpkg --configure -a + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/7.0/opensuse.sh b/plugins/zabbix/versions/7.0/opensuse.sh new file mode 100644 index 000000000..6b093949f --- /dev/null +++ b/plugins/zabbix/versions/7.0/opensuse.sh @@ -0,0 +1,59 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + + + ZABBIX_NAME=zabbix-release-7.0-2.sles${SYS_VERSION_ID:0:2}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/7.0/sles/${SYS_VERSION_ID:0:2}/x86_64/${ZABBIX_NAME} + echo "rpm -Uvh https://repo.zabbix.com/zabbix/7.0/sles/${SYS_VERSION_ID:0:2}/x86_64/${ZABBIX_NAME}" + + + # debug + # symbol lookup error: /usr/sbin/zabbix_server: undefined symbol: usmAES256CiscoPrivProtocol + # /usr/sbin/zabbix_server -c /etc/zabbix/zabbix_server.conf + + # zypper update -y net-snmp + # zypper update -y net-snmp-utils + # zypper install -y net-snmp-devel + + zypper install -y zabbix-server-mysql + zypper install -y zabbix-web-mysql + zypper install -y zabbix-sql-scripts + zypper install -y zabbix-agent +} + +Uninstall_App() +{ + zypper remove -y zabbix-server-mysql + zypper remove -y zabbix-web-mysql + zypper remove -y zabbix-sql-scripts + zypper remove -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/7.0/rocky.sh b/plugins/zabbix/versions/7.0/rocky.sh new file mode 100644 index 000000000..7d19a292f --- /dev/null +++ b/plugins/zabbix/versions/7.0/rocky.sh @@ -0,0 +1,45 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-7.0-4.el${SYS_VERSION_ID:0:1}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/7.0/rocky/${SYS_VERSION_ID:0:1}/x86_64/${ZABBIX_NAME} + + # cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + dnf install -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + dnf install -y zabbix-agent +} + +Uninstall_App() +{ + dnf remove -y zabbix-server-mysql zabbix-web-mysql zabbix-sql-scripts zabbix-selinux-policy + dnf install -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix/versions/7.0/ubuntu.sh b/plugins/zabbix/versions/7.0/ubuntu.sh new file mode 100644 index 000000000..1e34aa830 --- /dev/null +++ b/plugins/zabbix/versions/7.0/ubuntu.sh @@ -0,0 +1,59 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` +sysArch=`arch` + +ubuntu_suffix= +if [ "$sysArch" == "aarch64" ];then + ubuntu_suffix="-arm64" +fi +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release_7.0-2+ubuntu${SYS_VERSION_ID}_all.deb + echo "wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/7.0/ubuntu${ubuntu_suffix}/pool/main/z/zabbix-release/${ZABBIX_NAME}" + if [ ! -f $serverPath/source/zabbix/${ZABBIX_NAME} ];then + wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/7.0/ubuntu${ubuntu_suffix}/pool/main/z/zabbix-release/${ZABBIX_NAME} + fi + + # apt-get -f install + # dpkg --configure -a + + cd $serverPath/source/zabbix && dpkg -i ${ZABBIX_NAME} + apt update -y + + apt install -y zabbix-server-mysql + apt install -y zabbix-frontend-php zabbix-sql-scripts + apt install -y zabbix-agent +} + +Uninstall_App() +{ + apt remove -y zabbix-server-mysql + apt remove -y zabbix-frontend-php zabbix-sql-scripts + apt remove -y zabbix-agent + rm -rf /etc/zabbix + + # dpkg --configure -a + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix_agent/conf/zabbix_agentd.conf b/plugins/zabbix_agent/conf/zabbix_agentd.conf new file mode 100644 index 000000000..2dba7375f --- /dev/null +++ b/plugins/zabbix_agent/conf/zabbix_agentd.conf @@ -0,0 +1,18 @@ +PidFile=/run/zabbix/zabbix_agentd.pid +LogFile=/var/log/zabbix/zabbix_agentd.log +LogFileSize=1 + +ListenIP=0.0.0.0 +ListenPort=10050 +#EnableRemoteCommands=1 +Timeout=3 + +Server=127.0.0.1 +ServerActive=127.0.0.1 + +Hostname=Zabbix server +Include=/etc/zabbix/zabbix_agentd.d/*.conf + +# Include=/usr/local/etc/zabbix_agentd.userparams.conf +# Include=/usr/local/etc/zabbix_agentd.conf.d/ +# Include=/usr/local/etc/zabbix_agentd.conf.d/*.conf diff --git a/plugins/zabbix_agent/conf/zabbix_agentd.conf.bak b/plugins/zabbix_agent/conf/zabbix_agentd.conf.bak new file mode 100644 index 000000000..b81c79ba6 --- /dev/null +++ b/plugins/zabbix_agent/conf/zabbix_agentd.conf.bak @@ -0,0 +1,554 @@ +# This is a configuration file for Zabbix agent daemon (Unix) +# To get more information about Zabbix, visit https://www.zabbix.com + +############ GENERAL PARAMETERS ################# + +### Option: PidFile +# Name of PID file. +# +# Mandatory: no +# Default: +# PidFile=/tmp/zabbix_agentd.pid + +PidFile=/run/zabbix/zabbix_agentd.pid + +### Option: LogType +# Specifies where log messages are written to: +# system - syslog +# file - file specified with LogFile parameter +# console - standard output +# +# Mandatory: no +# Default: +# LogType=file + +### Option: LogFile +# Log file name for LogType 'file' parameter. +# +# Mandatory: yes, if LogType is set to file, otherwise no +# Default: +# LogFile= + +LogFile=/var/log/zabbix/zabbix_agentd.log + +### Option: LogFileSize +# Maximum size of log file in MB. +# 0 - disable automatic log rotation. +# +# Mandatory: no +# Range: 0-1024 +# Default: +# LogFileSize=1 + +LogFileSize=0 + +### Option: DebugLevel +# Specifies debug level: +# 0 - basic information about starting and stopping of Zabbix processes +# 1 - critical information +# 2 - error information +# 3 - warnings +# 4 - for debugging (produces lots of information) +# 5 - extended debugging (produces even more information) +# +# Mandatory: no +# Range: 0-5 +# Default: +# DebugLevel=3 + +### Option: SourceIP +# Source IP address for outgoing connections. +# +# Mandatory: no +# Default: +# SourceIP= + +### Option: AllowKey +# Allow execution of item keys matching pattern. +# Multiple keys matching rules may be defined in combination with DenyKey. +# Key pattern is wildcard expression, which support "*" character to match any number of any characters in certain position. It might be used in both key name and key arguments. +# Parameters are processed one by one according their appearance order. +# If no AllowKey or DenyKey rules defined, all keys are allowed. +# +# Mandatory: no + +### Option: DenyKey +# Deny execution of items keys matching pattern. +# Multiple keys matching rules may be defined in combination with AllowKey. +# Key pattern is wildcard expression, which support "*" character to match any number of any characters in certain position. It might be used in both key name and key arguments. +# Parameters are processed one by one according their appearance order. +# If no AllowKey or DenyKey rules defined, all keys are allowed. +# Unless another system.run[*] rule is specified DenyKey=system.run[*] is added by default. +# +# Mandatory: no +# Default: +# DenyKey=system.run[*] + +### Option: EnableRemoteCommands - Deprecated, use AllowKey=system.run[*] or DenyKey=system.run[*] instead +# Internal alias for AllowKey/DenyKey parameters depending on value: +# 0 - DenyKey=system.run[*] +# 1 - AllowKey=system.run[*] +# +# Mandatory: no + +### Option: LogRemoteCommands +# Enable logging of executed shell commands as warnings. +# 0 - disabled +# 1 - enabled +# +# Mandatory: no +# Default: +# LogRemoteCommands=0 + +##### Passive checks related + +### Option: Server +# List of comma delimited IP addresses, optionally in CIDR notation, or DNS names of Zabbix servers and Zabbix proxies. +# Incoming connections will be accepted only from the hosts listed here. +# If IPv6 support is enabled then '127.0.0.1', '::127.0.0.1', '::ffff:127.0.0.1' are treated equally +# and '::/0' will allow any IPv4 or IPv6 address. +# '0.0.0.0/0' can be used to allow any IPv4 address. +# Example: Server=127.0.0.1,192.168.1.0/24,::1,2001:db8::/32,zabbix.example.com +# +# Mandatory: yes, if StartAgents is not explicitly set to 0 +# Default: +# Server= + +Server=127.0.0.1 + +### Option: ListenPort +# Agent will listen on this port for connections from the server. +# +# Mandatory: no +# Range: 1024-32767 +# Default: +# ListenPort=10050 + +### Option: ListenIP +# List of comma delimited IP addresses that the agent should listen on. +# First IP address is sent to Zabbix server if connecting to it to retrieve list of active checks. +# +# Mandatory: no +# Default: +# ListenIP=0.0.0.0 + +### Option: StartAgents +# Number of pre-forked instances of zabbix_agentd that process passive checks. +# If set to 0, disables passive checks and the agent will not listen on any TCP port. +# +# Mandatory: no +# Range: 0-100 +# Default: +# StartAgents=10 + +##### Active checks related + +### Option: ServerActive +# Zabbix server/proxy address or cluster configuration to get active checks from. +# Server/proxy address is IP address or DNS name and optional port separated by colon. +# Cluster configuration is one or more server addresses separated by semicolon. +# Multiple Zabbix servers/clusters and Zabbix proxies can be specified, separated by comma. +# More than one Zabbix proxy should not be specified from each Zabbix server/cluster. +# If Zabbix proxy is specified then Zabbix server/cluster for that proxy should not be specified. +# Multiple comma-delimited addresses can be provided to use several independent Zabbix servers in parallel. Spaces are allowed. +# If port is not specified, default port is used. +# IPv6 addresses must be enclosed in square brackets if port for that host is specified. +# If port is not specified, square brackets for IPv6 addresses are optional. +# If this parameter is not specified, active checks are disabled. +# Example for Zabbix proxy: +# ServerActive=127.0.0.1:10051 +# Example for multiple servers: +# ServerActive=127.0.0.1:20051,zabbix.domain,[::1]:30051,::1,[12fc::1] +# Example for high availability: +# ServerActive=zabbix.cluster.node1;zabbix.cluster.node2:20051;zabbix.cluster.node3 +# Example for high availability with two clusters and one server: +# ServerActive=zabbix.cluster.node1;zabbix.cluster.node2:20051,zabbix.cluster2.node1;zabbix.cluster2.node2,zabbix.domain +# +# Mandatory: no +# Default: +# ServerActive= + +ServerActive=127.0.0.1 + +### Option: Hostname +# List of comma delimited unique, case sensitive hostnames. +# Required for active checks and must match hostnames as configured on the server. +# Value is acquired from HostnameItem if undefined. +# +# Mandatory: no +# Default: +# Hostname= + +Hostname=Zabbix server + +### Option: HostnameItem +# Item used for generating Hostname if it is undefined. Ignored if Hostname is defined. +# Does not support UserParameters or aliases. +# +# Mandatory: no +# Default: +# HostnameItem=system.hostname + +### Option: HostMetadata +# Optional parameter that defines host metadata. +# Host metadata is used at host auto-registration process. +# An agent will issue an error and not start if the value is over limit of 2034 bytes. +# If not defined, value will be acquired from HostMetadataItem. +# +# Mandatory: no +# Range: 0-2034 bytes +# Default: +# HostMetadata= + +### Option: HostMetadataItem +# Optional parameter that defines an item used for getting host metadata. +# Host metadata is used at host auto-registration process. +# During an auto-registration request an agent will log a warning message if +# the value returned by specified item is over limit of 65535 characters. +# This option is only used when HostMetadata is not defined. +# +# Mandatory: no +# Default: +# HostMetadataItem= + +### Option: HostInterface +# Optional parameter that defines host interface. +# Host interface is used at host auto-registration process. +# An agent will issue an error and not start if the value is over limit of 255 characters. +# If not defined, value will be acquired from HostInterfaceItem. +# +# Mandatory: no +# Range: 0-255 characters +# Default: +# HostInterface= + +### Option: HostInterfaceItem +# Optional parameter that defines an item used for getting host interface. +# Host interface is used at host auto-registration process. +# During an auto-registration request an agent will log a warning message if +# the value returned by specified item is over limit of 255 characters. +# This option is only used when HostInterface is not defined. +# +# Mandatory: no +# Default: +# HostInterfaceItem= + +### Option: RefreshActiveChecks +# How often list of active checks is refreshed, in seconds. +# +# Mandatory: no +# Range: 1-86400 +# Default: +# RefreshActiveChecks=5 + +### Option: BufferSend +# Do not keep data longer than N seconds in buffer. +# +# Mandatory: no +# Range: 1-3600 +# Default: +# BufferSend=5 + +### Option: BufferSize +# Maximum number of values in a memory buffer. The agent will send +# all collected data to Zabbix Server or Proxy if the buffer is full. +# +# Mandatory: no +# Range: 2-65535 +# Default: +# BufferSize=100 + +### Option: MaxLinesPerSecond +# Maximum number of new lines the agent will send per second to Zabbix Server +# or Proxy processing 'log' and 'logrt' active checks. +# The provided value will be overridden by the parameter 'maxlines', +# provided in 'log' or 'logrt' item keys. +# +# Mandatory: no +# Range: 1-1000 +# Default: +# MaxLinesPerSecond=20 + +### Option: HeartbeatFrequency +# Frequency of heartbeat messages in seconds. +# Used for monitoring availability of active checks. +# 0 - heartbeat messages disabled. +# +# Mandatory: no +# Range: 0-3600 +# Default: 60 +# HeartbeatFrequency= + +############ ADVANCED PARAMETERS ################# + +### Option: Alias +# Sets an alias for an item key. It can be used to substitute long and complex item key with a smaller and simpler one. +# Multiple Alias parameters may be present. Multiple parameters with the same Alias key are not allowed. +# Different Alias keys may reference the same item key. +# For example, to retrieve the ID of user 'zabbix': +# Alias=zabbix.userid:vfs.file.regexp[/etc/passwd,^zabbix:.:([0-9]+),,,,\1] +# Now shorthand key zabbix.userid may be used to retrieve data. +# Aliases can be used in HostMetadataItem but not in HostnameItem parameters. +# +# Mandatory: no +# Range: +# Default: + +### Option: Timeout +# Specifies timeout for communications (in seconds). +# +# Mandatory: no +# Range: 1-30 +# Default: +# Timeout=3 + +### Option: AllowRoot +# Allow the agent to run as 'root'. If disabled and the agent is started by 'root', the agent +# will try to switch to the user specified by the User configuration option instead. +# Has no effect if started under a regular user. +# 0 - do not allow +# 1 - allow +# +# Mandatory: no +# Default: +# AllowRoot=0 + +### Option: User +# Drop privileges to a specific, existing user on the system. +# Only has effect if run as 'root' and AllowRoot is disabled. +# +# Mandatory: no +# Default: +# User=zabbix +# NOTE: This option is overriden by settings in systemd service file! + +### Option: Include +# You may include individual files or all files in a directory in the configuration file. +# Installing Zabbix will create include directory in /usr/local/etc, unless modified during the compile time. +# +# Mandatory: no +# Default: +# Include= + +Include=/etc/zabbix/zabbix_agentd.d/*.conf + +# Include=/usr/local/etc/zabbix_agentd.userparams.conf +# Include=/usr/local/etc/zabbix_agentd.conf.d/ +# Include=/usr/local/etc/zabbix_agentd.conf.d/*.conf + +####### USER-DEFINED MONITORED PARAMETERS ####### + +### Option: UnsafeUserParameters +# Allow all characters to be passed in arguments to user-defined parameters. +# The following characters are not allowed: +# \ ' " ` * ? [ ] { } ~ $ ! & ; ( ) < > | # @ +# Additionally, newline characters are not allowed. +# 0 - do not allow +# 1 - allow +# +# Mandatory: no +# Range: 0-1 +# Default: +# UnsafeUserParameters=0 + +### Option: UserParameter +# User-defined parameter to monitor. There can be several user-defined parameters. +# Format: UserParameter=, +# See 'zabbix_agentd' directory for examples. +# +# Mandatory: no +# Default: +# UserParameter= + +### Option: UserParameterDir +# Directory to execute UserParameter commands from. Only one entry is allowed. +# When executing UserParameter commands the agent will change the working directory to the one +# specified in the UserParameterDir option. +# This way UserParameter commands can be specified using the relative ./ prefix. +# +# Mandatory: no +# Default: +# UserParameterDir= + +####### LOADABLE MODULES ####### + +### Option: LoadModulePath +# Full path to location of agent modules. +# Default depends on compilation options. +# To see the default path run command "zabbix_agentd --help". +# +# Mandatory: no +# Default: +# LoadModulePath=${libdir}/modules + +### Option: LoadModule +# Module to load at agent startup. Modules are used to extend functionality of the agent. +# Formats: +# LoadModule= +# LoadModule= +# LoadModule= +# Either the module must be located in directory specified by LoadModulePath or the path must precede the module name. +# If the preceding path is absolute (starts with '/') then LoadModulePath is ignored. +# It is allowed to include multiple LoadModule parameters. +# +# Mandatory: no +# Default: +# LoadModule= + +####### TLS-RELATED PARAMETERS ####### + +### Option: TLSConnect +# How the agent should connect to server or proxy. Used for active checks. +# Only one value can be specified: +# unencrypted - connect without encryption +# psk - connect using TLS and a pre-shared key +# cert - connect using TLS and a certificate +# +# Mandatory: yes, if TLS certificate or PSK parameters are defined (even for 'unencrypted' connection) +# Default: +# TLSConnect=unencrypted + +### Option: TLSAccept +# What incoming connections to accept. +# Multiple values can be specified, separated by comma: +# unencrypted - accept connections without encryption +# psk - accept connections secured with TLS and a pre-shared key +# cert - accept connections secured with TLS and a certificate +# +# Mandatory: yes, if TLS certificate or PSK parameters are defined (even for 'unencrypted' connection) +# Default: +# TLSAccept=unencrypted + +### Option: TLSCAFile +# Full pathname of a file containing the top-level CA(s) certificates for +# peer certificate verification. +# +# Mandatory: no +# Default: +# TLSCAFile= + +### Option: TLSCRLFile +# Full pathname of a file containing revoked certificates. +# +# Mandatory: no +# Default: +# TLSCRLFile= + +### Option: TLSServerCertIssuer +# Allowed server certificate issuer. +# +# Mandatory: no +# Default: +# TLSServerCertIssuer= + +### Option: TLSServerCertSubject +# Allowed server certificate subject. +# +# Mandatory: no +# Default: +# TLSServerCertSubject= + +### Option: TLSCertFile +# Full pathname of a file containing the agent certificate or certificate chain. +# +# Mandatory: no +# Default: +# TLSCertFile= + +### Option: TLSKeyFile +# Full pathname of a file containing the agent private key. +# +# Mandatory: no +# Default: +# TLSKeyFile= + +### Option: TLSPSKIdentity +# Unique, case sensitive string used to identify the pre-shared key. +# +# Mandatory: no +# Default: +# TLSPSKIdentity= + +### Option: TLSPSKFile +# Full pathname of a file containing the pre-shared key. +# +# Mandatory: no +# Default: +# TLSPSKFile= + +####### For advanced users - TLS ciphersuite selection criteria ####### + +### Option: TLSCipherCert13 +# Cipher string for OpenSSL 1.1.1 or newer in TLS 1.3. +# Override the default ciphersuite selection criteria for certificate-based encryption. +# +# Mandatory: no +# Default: +# TLSCipherCert13= + +### Option: TLSCipherCert +# GnuTLS priority string or OpenSSL (TLS 1.2) cipher string. +# Override the default ciphersuite selection criteria for certificate-based encryption. +# Example for GnuTLS: +# NONE:+VERS-TLS1.2:+ECDHE-RSA:+RSA:+AES-128-GCM:+AES-128-CBC:+AEAD:+SHA256:+SHA1:+CURVE-ALL:+COMP-NULL:+SIGN-ALL:+CTYPE-X.509 +# Example for OpenSSL: +# EECDH+aRSA+AES128:RSA+aRSA+AES128 +# +# Mandatory: no +# Default: +# TLSCipherCert= + +### Option: TLSCipherPSK13 +# Cipher string for OpenSSL 1.1.1 or newer in TLS 1.3. +# Override the default ciphersuite selection criteria for PSK-based encryption. +# Example: +# TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 +# +# Mandatory: no +# Default: +# TLSCipherPSK13= + +### Option: TLSCipherPSK +# GnuTLS priority string or OpenSSL (TLS 1.2) cipher string. +# Override the default ciphersuite selection criteria for PSK-based encryption. +# Example for GnuTLS: +# NONE:+VERS-TLS1.2:+ECDHE-PSK:+PSK:+AES-128-GCM:+AES-128-CBC:+AEAD:+SHA256:+SHA1:+CURVE-ALL:+COMP-NULL:+SIGN-ALL +# Example for OpenSSL: +# kECDHEPSK+AES128:kPSK+AES128 +# +# Mandatory: no +# Default: +# TLSCipherPSK= + +### Option: TLSCipherAll13 +# Cipher string for OpenSSL 1.1.1 or newer in TLS 1.3. +# Override the default ciphersuite selection criteria for certificate- and PSK-based encryption. +# Example: +# TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 +# +# Mandatory: no +# Default: +# TLSCipherAll13= + +### Option: TLSCipherAll +# GnuTLS priority string or OpenSSL (TLS 1.2) cipher string. +# Override the default ciphersuite selection criteria for certificate- and PSK-based encryption. +# Example for GnuTLS: +# NONE:+VERS-TLS1.2:+ECDHE-RSA:+RSA:+ECDHE-PSK:+PSK:+AES-128-GCM:+AES-128-CBC:+AEAD:+SHA256:+SHA1:+CURVE-ALL:+COMP-NULL:+SIGN-ALL:+CTYPE-X.509 +# Example for OpenSSL: +# EECDH+aRSA+AES128:RSA+aRSA+AES128:kECDHEPSK+AES128:kPSK+AES128 +# +# Mandatory: no +# Default: +# TLSCipherAll= + +####### For advanced users - TCP-related fine-tuning parameters ####### + +## Option: ListenBacklog +# The maximum number of pending connections in the queue. This parameter is passed to +# listen() function as argument 'backlog' (see "man listen"). +# +# Mandatory: no +# Range: 0 - INT_MAX (depends on system, too large values may be silently truncated to implementation-specified maximum) +# Default: SOMAXCONN (hard-coded constant, depends on system) +# ListenBacklog= diff --git a/plugins/zabbix_agent/conf/zabbix_agentd/userparameter_examples.conf b/plugins/zabbix_agent/conf/zabbix_agentd/userparameter_examples.conf new file mode 100644 index 000000000..d8a09ee64 --- /dev/null +++ b/plugins/zabbix_agent/conf/zabbix_agentd/userparameter_examples.conf @@ -0,0 +1,28 @@ +# Emulating built-in agent parameter 'system.users.num' +#UserParameter=system.test,who | wc -l + +# Get size of a directory +# Defaults to /tmp +#UserParameter=vfs.dir.size[*],dir="$1"; du -s -B 1 "${dir:-/tmp}" | cut -f1 + +# Total CPU utilisation by all processes with a given name. +# Returns empty value if no such processes are present, numeric items will turn unsupported +# Defaults to zabbix_agentd +#UserParameter=proc.cpu[*],proc="$1"; ps -o pcpu= -C "${proc:-zabbix_agentd}" | awk '{sum += $$1} END {print sum}' + +# Mail queue length from mailq +#UserParameter=unix_mail.queue,mailq | grep -v "Mail queue is empty" | grep -c '^[0-9A-Z]' + +# Partition discovery on Linux +#UserParameter=vfs.partitions.discovery.linux,for partition in $(awk 'NR > 2 {print $4}' /proc/partitions); do #partitionlist="$partitionlist,"'{"{#PARTITION}":"'$partition'"}'; done; echo '{"data":['${partitionlist#,}']}' + +# Partition discovery on Solaris (using iostat output) +# On Solaris bash usually is not the one linked from /bin/sh, so a wrapper script is suggested +#UserParameter=vfs.partitions.discovery.solaris,/somewhere/solaris_partitions.sh + +# Wrapper script (solaris_partitions.sh) contents: +##!/bin/bash +#for partition in $(iostat -x | tail +3 | awk '{print $1}'); do +# partitionlist="$partitionlist,"'{"{#PARTITION}":"'$partition'"}' +#done +#echo '{"data":['${partitionlist#,}']}' diff --git a/plugins/zabbix_agent/conf/zabbix_agentd/userparameter_mysql.conf b/plugins/zabbix_agent/conf/zabbix_agentd/userparameter_mysql.conf new file mode 100644 index 000000000..97a4fb1f1 --- /dev/null +++ b/plugins/zabbix_agent/conf/zabbix_agentd/userparameter_mysql.conf @@ -0,0 +1,16 @@ +#template_db_mysql.conf created by Zabbix for "Template DB MySQL" and Zabbix 4.2 +#For OS Linux: You need create .my.cnf in zabbix-agent home directory (/var/lib/zabbix by default) +#For OS Windows: You need add PATH to mysql and mysqladmin and create my.cnf in %WINDIR%\my.cnf,C:\my.cnf,BASEDIR\my.cnf https://dev.mysql.com/doc/refman/5.7/en/option-files.html +#The file must have three strings: +#[client] +#user=zbx_monitor +#password= +# + +#UserParameter=mysql.ping[*], mysqladmin -h"$1" -P"$2" ping +#UserParameter=mysql.get_status_variables[*], mysql -h"$1" -P"$2" -sNX -e "show global status" +#UserParameter=mysql.version[*], mysqladmin -s -h"$1" -P"$2" version +#UserParameter=mysql.db.discovery[*], mysql -h"$1" -P"$2" -sN -e "show databases" +#UserParameter=mysql.dbsize[*], mysql -h"$1" -P"$2" -sN -e "SELECT SUM(DATA_LENGTH + INDEX_LENGTH) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='$3'" +#UserParameter=mysql.replication.discovery[*], mysql -h"$1" -P"$2" -sNX -e "show slave status" +#UserParameter=mysql.slave_status[*], mysql -h"$1" -P"$2" -sNX -e "show slave status" diff --git a/plugins/zabbix_agent/ico.png b/plugins/zabbix_agent/ico.png new file mode 100644 index 000000000..01024eb07 Binary files /dev/null and b/plugins/zabbix_agent/ico.png differ diff --git a/plugins/zabbix_agent/index.html b/plugins/zabbix_agent/index.html new file mode 100755 index 000000000..de22c4356 --- /dev/null +++ b/plugins/zabbix_agent/index.html @@ -0,0 +1,31 @@ + + +
                                      +
                                      +
                                      +
                                      +

                                      服务

                                      +

                                      自启动

                                      +

                                      默认配置

                                      +

                                      子配置

                                      +

                                      运行日志

                                      +

                                      相关说明

                                      + +
                                      +
                                      +
                                      +
                                      +
                                      +
                                      + \ No newline at end of file diff --git a/plugins/zabbix_agent/index.py b/plugins/zabbix_agent/index.py new file mode 100755 index 000000000..d87b1b689 --- /dev/null +++ b/plugins/zabbix_agent/index.py @@ -0,0 +1,269 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'zabbix_agent' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + current_os = mw.getOs() + if current_os == 'darwin': + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getInitDTpl(): + path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl" + return path + + +def getArgs(): + args = sys.argv[3:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + if t.strip() == '': + tmp = [] + else: + t = t.split(':') + tmp[t[0]] = t[1] + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + return tmp + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + +def getPidFile(): + file = getConf() + content = mw.readFile(file) + rep = r'pidfile\s*(.*)' + tmp = re.search(rep, content) + return tmp.groups()[0].strip() + +def status(): + cmd = "ps aux|grep zabbix_agentd |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return 'stop' + return 'start' + +def contentReplace(content): + service_path = mw.getServerDir() + content = content.replace('{$ROOT_PATH}', mw.getFatherDir()) + content = content.replace('{$SERVER_PATH}', service_path) + return content + +def zabbixAgentConf(): + return '/etc/zabbix/zabbix_agentd.conf' + +def runLog(): + za_conf = zabbixAgentConf() + content = mw.readFile(za_conf) + + rep = r'LogFile=\s*(.*)' + tmp = re.search(rep, content) + + if tmp.groups() == 0: + return tmp.groups()[0].strip() + return '/var/log/zabbix/zabbix_agentd.log' + +def initAgentConf(): + za_src_tpl = getPluginDir()+'/conf/zabbix_agentd.conf' + za_dst_path = zabbixAgentConf() + + # zabbix_agent配置 + content = mw.readFile(za_src_tpl) + content = contentReplace(content) + mw.writeFile(za_dst_path, content) + +def initAgentDConf(): + clist = ['userparameter_mysql.conf', 'userparameter_examples.conf'] + dst_dir = '/etc/zabbix/zabbix_agentd.d' + for c in clist: + za_src_tpl = getPluginDir()+'/conf/zabbix_agentd/'+c + dst_path = dst_dir+'/'+c + if not os.path.exists(dst_path): + content = mw.readFile(za_src_tpl) + mw.writeFile(dst_path,content) + +def initDreplace(): + + init_file = getServerDir() + '/init.pl' + if not os.path.exists(init_file): + initAgentDConf() + initAgentConf() + openPort() + mw.writeFile(init_file, 'ok') + return True + +def openPort(): + try: + from utils.firewall import Firewall as MwFirewall + MwFirewall.instance().addAcceptPort('10050', 'zabbix-agent', 'port') + return port + except Exception as e: + return "Release failed {}".format(e) + return True + +def zOp(method): + + initDreplace() + + data = mw.execShell('systemctl ' + method + ' zabbix-agent') + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + + return zOp('start') + + +def stop(): + val = zOp('stop') + return val + +def restart(): + status = zOp('restart') + return status + +def reload(): + return zOp('reload') + +def initdStatus(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + shell_cmd = 'systemctl status zabbix-agent | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + mw.execShell('systemctl enable zabbix-agent') + return 'ok' + + +def initdUinstall(): + current_os = mw.getOs() + if current_os == 'darwin': + return "Apple Computer does not support" + + mw.execShell('systemctl disable zabbix-agent') + return 'ok' + + +def installPreInspection(): + zabbix_dir = mw.getServerDir()+'/zabbix' + if os.path.exists(zabbix_dir): + return '已经安装zabbix插件' + return 'ok' + + +def uninstallPreInspection(): + return 'ok' + + +def agentdDefaultConf(): + return '/etc/zabbix/zabbix_agentd.d/userparameter_mysql.conf' + + +def agentdConf(): + path = '/etc/zabbix/zabbix_agentd.d' + pathFile = os.listdir(path) + tmp = [] + for one in pathFile: + file = path + '/' + one + tmp.append(file) + return mw.getJson(tmp) + +def agentdReadConf(): + args = getArgs() + data = checkArgs(args, ['file']) + if not data[0]: + return data[1] + + content = mw.readFile(args['file']) + content = contentReplace(content) + return mw.returnJson(True, 'ok', content) + + + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'install_pre_inspection': + print(installPreInspection()) + elif func == 'uninstall_pre_inspection': + print(uninstallPreInspection()) + elif func == 'conf': + print(zabbixAgentConf()) + elif func == 'zabbix_agent_conf': + print(zabbixAgentConf()) + elif func == 'run_log': + print(runLog()) + elif func == 'agentd_default_conf': + print(agentdDefaultConf()) + elif func == 'agentd_conf': + print(agentdConf()) + elif func == 'agentd_read_conf': + print(agentdReadConf()) + else: + print('error') diff --git a/plugins/zabbix_agent/info.json b/plugins/zabbix_agent/info.json new file mode 100755 index 000000000..23bc1d7e7 --- /dev/null +++ b/plugins/zabbix_agent/info.json @@ -0,0 +1,19 @@ +{ + "sort": 7, + "ps": "Zabbix被控服务器[开发中]", + "name": "zabbix_agent", + "title": "Zabbix Agent", + "shell": "install.sh", + "versions":["7.0"], + "tip": "soft", + "install_pre_inspection":true, + "uninstall_pre_inspection":true, + "checks": "server/zabbix_agent", + "path": "server/zabbix_agent", + "display": 1, + "author": "midoks", + "date": "2022-07-14", + "home": "https://www.zabbix.com", + "type": 0, + "pid": "5" +} diff --git a/plugins/zabbix_agent/install.sh b/plugins/zabbix_agent/install.sh new file mode 100755 index 000000000..955c8c072 --- /dev/null +++ b/plugins/zabbix_agent/install.sh @@ -0,0 +1,78 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +# https://www.zabbix.com + +# cd /www/server/mdserver-web/plugins/zabbix_agent && /bin/bash install.sh install 7.0 +# cd /www/server/mdserver-web && python3 /www/server/mdserver-web/plugins/zabbix_agent/index.py start + + +# /usr/sbin/zabbix_agentd -c /etc/zabbix/zabbix_agentd.conf + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +VERSION=$2 + +sysName=`uname` +echo "use system: ${sysName}" + +OSNAME=`bash ${rootPath}/scripts/getos.sh` + +if [ "" == "$OSNAME" ];then + OSNAME=`cat ${rootPath}/data/osname.pl` +fi + +if [ "macos" == "$OSNAME" ];then + echo "不支持Macox" + exit +fi + +if [ -f ${rootPath}/bin/activate ];then + source ${rootPath}/bin/activate +fi + +Install_App() +{ + echo '正在安装脚本文件...' + mkdir -p $serverPath/source/zabbix_agent + shell_file=${curPath}/versions/${VERSION}/${OSNAME}.sh + + if [ -f $shell_file ];then + bash -x $shell_file install + else + echo '不支持...' + exit 1 + fi + + + mkdir -p $serverPath/zabbix_agent + echo "${VERSION}" > $serverPath/zabbix_agent/version.pl + + #初始化 + cd ${rootPath} && python3 ${rootPath}/plugins/zabbix_agent/index.py start + cd ${rootPath} && python3 ${rootPath}/plugins/zabbix_agent/index.py initd_install + + + echo 'Zabbix安装完成' +} + +Uninstall_App() +{ + cd ${rootPath} && python3 ${rootPath}/plugins/zabbix_agent/index.py stop + cd ${rootPath} && python3 ${rootPath}/plugins/zabbix_agent/index.py initd_uninstall + + rm -rf $serverPath/zabbix_agent + rm -rf $serverPath/source/zabbix_agent + echo 'Zabbix卸载完成' +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix_agent/js/zabbix.js b/plugins/zabbix_agent/js/zabbix.js new file mode 100755 index 000000000..00221700f --- /dev/null +++ b/plugins/zabbix_agent/js/zabbix.js @@ -0,0 +1,187 @@ +function zabbixPost(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'zabbix'; + req_data['func'] = method; + req_data['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/run', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + //错误展示10S + layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function zabbixPostCallbak(method, version, args,callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'zabbix'; + req_data['func'] = method; + args['version'] = version; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + + +function zabbixReadme(){ + var readme = '
                                        '; + readme += '
                                      • 需要手动配置【默认配置】
                                      • '; + readme += '
                                      '; + + $('.soft-man-con').html(readme); +} + +//配置修改模版 --- start +function zagentDConfigTpl(_name, version, func, config_tpl_func, read_config_tpl_func, save_callback_func){ + if ( typeof(version) == 'undefined' ){ + version = ''; + } + + var func_name = 'conf'; + if ( typeof(func) != 'undefined' ){ + func_name = func; + } + + var _config_tpl_func = 'config_tpl'; + if ( typeof(config_tpl_func) != 'undefined' ){ + _config_tpl_func = config_tpl_func; + } + + var _read_config_tpl_func = 'read_config_tpl'; + if ( typeof(read_config_tpl_func) != 'undefined' ){ + _read_config_tpl_func = read_config_tpl_func; + } + + + var con = '

                                      提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!

                                      \ + \ + \ + \ +
                                        \ +
                                      • 此处为【'+ _name + version +'】主配置文件,若您不了解配置规则,请勿随意修改。
                                      • \ +
                                      '; + $(".soft-man-con").html(con); + + function getFileName(file){ + var list = file.split('/'); + var f = list[list.length-1]; + return f + } + + var fileName = ''; + $.post('/plugins/run',{name:_name, func:_config_tpl_func,version:version}, function(data){ + var rdata = $.parseJSON(data.data); + for (var i = 0; i < rdata.length; i++) { + $('#config_tpl').append(''); + } + + $('#config_tpl').change(function(){ + var selected = $(this).val(); + if (selected != '0'){ + var loadT = layer.msg('配置模版获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + fileName = selected; + var _args = JSON.stringify({file:selected}); + $.post('/plugins/run', {name:_name, func:_read_config_tpl_func,version:version,args:_args}, function(data){ + layer.close(loadT); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + $("#textBody").empty().text(rdata.data); + $(".CodeMirror").remove(); + var editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName,save_callback_func); + } + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").unbind('click'); + $("#onlineEditFileBtn").click(function(){ + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName, save_callback_func); + }); + },'json'); + } + }); + + },'json'); + + var loadT = layer.msg('配置文件路径获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/plugins/run', {name:_name, func:func_name,version:version}, function (data) { + layer.close(loadT); + + var loadT2 = layer.msg('文件内容获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + fileName = data.data; + $.post('/files/get_body', 'path=' + fileName, function(rdata) { + layer.close(loadT2); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + $("#textBody").empty().text(rdata.data.data); + $(".CodeMirror").remove(); + var editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName,save_callback_func); + } + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").click(function(){ + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName,save_callback_func); + }); + },'json'); + },'json'); +} + diff --git a/plugins/zabbix_agent/versions/6.0/debian.sh b/plugins/zabbix_agent/versions/6.0/debian.sh new file mode 100644 index 000000000..dd284d4e2 --- /dev/null +++ b/plugins/zabbix_agent/versions/6.0/debian.sh @@ -0,0 +1,40 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +Install_App() +{ + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release_6.0-5+debian${SYS_VERSION_ID}_all.deb + echo "wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/6.0/debian/pool/main/z/zabbix-release/${ZABBIX_NAME}" + if [ ! -f $serverPath/source/zabbix/${ZABBIX_NAME} ];then + wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/6.0/debian/pool/main/z/zabbix-release/${ZABBIX_NAME} + fi + + cd $serverPath/source/zabbix && dpkg -i ${ZABBIX_NAME} + apt update -y + + apt install -y zabbix-agent +} + +Uninstall_App() +{ + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix_agent/versions/7.0/alma.sh b/plugins/zabbix_agent/versions/7.0/alma.sh new file mode 100644 index 000000000..0c72c43b7 --- /dev/null +++ b/plugins/zabbix_agent/versions/7.0/alma.sh @@ -0,0 +1,43 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-7.0-4.el${SYS_VERSION_ID:0:1}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/7.0/alma/${SYS_VERSION_ID:0:1}/x86_64/${ZABBIX_NAME} + + cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + dnf install -y zabbix-agent +} + +Uninstall_App() +{ + dnf remove -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix_agent/versions/7.0/centos.sh b/plugins/zabbix_agent/versions/7.0/centos.sh new file mode 100644 index 000000000..8c4e391d5 --- /dev/null +++ b/plugins/zabbix_agent/versions/7.0/centos.sh @@ -0,0 +1,44 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-7.0-4.el${SYS_VERSION_ID}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/7.0/centos/${SYS_VERSION_ID}/x86_64/${ZABBIX_NAME} + + cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + dnf install -y zabbix-agent + + # dnf module switch-to zabbix-web-1:7.0.4 +} + +Uninstall_App() +{ + dnf remove -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix_agent/versions/7.0/debian.sh b/plugins/zabbix_agent/versions/7.0/debian.sh new file mode 100644 index 000000000..f29fb7992 --- /dev/null +++ b/plugins/zabbix_agent/versions/7.0/debian.sh @@ -0,0 +1,41 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +Install_App() +{ + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release_7.0-2+debian${SYS_VERSION_ID}_all.deb + echo "wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/7.0/debian/pool/main/z/zabbix-release/${ZABBIX_NAME}" + if [ ! -f $serverPath/source/zabbix/${ZABBIX_NAME} ];then + wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/7.0/debian/pool/main/z/zabbix-release/${ZABBIX_NAME} + fi + + cd $serverPath/source/zabbix && dpkg -i ${ZABBIX_NAME} + apt update -y + + apt install -y zabbix-agent +} + +Uninstall_App() +{ + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix_agent/versions/7.0/opensuse.sh b/plugins/zabbix_agent/versions/7.0/opensuse.sh new file mode 100644 index 000000000..2b08e20a5 --- /dev/null +++ b/plugins/zabbix_agent/versions/7.0/opensuse.sh @@ -0,0 +1,44 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-7.0-2.sles${SYS_VERSION_ID:0:2}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/7.0/sles/${SYS_VERSION_ID:0:2}/x86_64/${ZABBIX_NAME} + echo "rpm -Uvh https://repo.zabbix.com/zabbix/7.0/sles/${SYS_VERSION_ID:0:2}/x86_64/${ZABBIX_NAME}" + + # cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + zypper install -y zabbix-agent +} + +Uninstall_App() +{ + zypper remove -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix_agent/versions/7.0/rocky.sh b/plugins/zabbix_agent/versions/7.0/rocky.sh new file mode 100644 index 000000000..9d1f8e95c --- /dev/null +++ b/plugins/zabbix_agent/versions/7.0/rocky.sh @@ -0,0 +1,42 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + yum install -y glibc-langpack-zh + + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release-7.0-4.el${SYS_VERSION_ID:0:1}.noarch.rpm + + rpm -Uvh https://repo.zabbix.com/zabbix/7.0/rocky/${SYS_VERSION_ID:0:1}/x86_64/${ZABBIX_NAME} + + cd $serverPath/source/zabbix && rpm -Uvh ${ZABBIX_NAME} + dnf install -y zabbix-agent +} + +Uninstall_App() +{ + dnf install -y zabbix-agent + rm -rf /etc/zabbix + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/zabbix_agent/versions/7.0/ubuntu.sh b/plugins/zabbix_agent/versions/7.0/ubuntu.sh new file mode 100644 index 000000000..98d7c1cf6 --- /dev/null +++ b/plugins/zabbix_agent/versions/7.0/ubuntu.sh @@ -0,0 +1,51 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") +sourcePath=${serverPath}/source +sysName=`uname` + + +SYS_VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# 检查是否通 +# zabbix_get -s 127.0.0.1 -k agent.ping +Install_App() +{ + mkdir -p $serverPath/source/zabbix + + ZABBIX_NAME=zabbix-release_7.0-2+ubuntu${SYS_VERSION_ID}_all.deb + echo "wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/7.0/ubuntu/pool/main/z/zabbix-release/${ZABBIX_NAME}" + if [ ! -f $serverPath/source/zabbix/${ZABBIX_NAME} ];then + wget -O $serverPath/source/zabbix/${ZABBIX_NAME} https://repo.zabbix.com/zabbix/7.0/ubuntu/pool/main/z/zabbix-release/${ZABBIX_NAME} + fi + + # apt-get -f install + # dpkg --configure -a + + cd $serverPath/source/zabbix && dpkg -i ${ZABBIX_NAME} + apt update -y + + apt install -y zabbix-agent +} + +Uninstall_App() +{ + apt remove -y zabbix-agent + rm -rf /etc/zabbix + + # dpkg --configure -a + echo "卸载成功" +} + +action=${1} +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..c7f897d97 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,48 @@ +setuptools>=33.1.1 +Werkzeug>=1.0.1 +wheel>=0.37.1 +gunicorn>=21.2.0 +requests>=2.27.1 +urllib3>=1.21.1 +flask>=2.0.3 +flask-session==0.3.2 +flask-helper==0.19 +flask-bcrypt==1.0.1 +flask-caching>=1.10.1 +cache==1.0.3 +gevent>=22.10.2 +gevent-websocket==0.10.1 +eventlet>=0.24.1 +psutil==5.9.1 +chardet==3.0.4 +SQLAlchemy>=1.4.54 +Flask-SQLAlchemy>=2.5.1 +#Flask-Migrate==4.* +#Flask-Security-Too==5.5.*; python_version >= '3.10' +#Flask-Security-Too==5.4.*; python_version <= '3.9' +pyOpenSSL==23.2.0 +cryptography>=40.0.2 +configparser==5.2.0 +python-engineio>=4.3.2 +python-socketio>=4.2.0 +flask-socketio>=5.2.0 +flask-sockets>=0.2.1 +zmq==0.0.0 +paramiko>=2.8.0 +pymongo +pymemcache +redis +pillow +Jinja2>=2.11.2 +PyMySQL>=1.0.2 +whitenoise==5.3.0 +tldextract +pyotp +pytz +supervisor +pyTelegramBotAPI +telebot +pyyaml +croniter +bcrypt +packaging diff --git a/scripts/backup.py b/scripts/backup.py new file mode 100755 index 000000000..14673b286 --- /dev/null +++ b/scripts/backup.py @@ -0,0 +1,300 @@ +# coding: utf-8 +#----------------------------- +# 网站备份工具 +#----------------------------- + +import sys +import os +import re +import time + +if sys.platform != 'darwin': + os.chdir('/www/server/mdserver-web') + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw +import core.db as db + + +class backupTools: + + def backupSite(self, name, count, echo=None): + exclude_dir_cmd = self.makeExcludeDirCmd(echo) + + sql = db.Sql() + path = sql.table('sites').where('name=?', (name,)).getField('path') + startTime = time.time() + if not path: + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + log = "网站[" + name + "]不存在!" + print("★[" + endDate + "] " + log) + print( + "----------------------------------------------------------------------------") + return + + backup_path = mw.getBackupDir() + '/site' + if not os.path.exists(backup_path): + mw.execShell("mkdir -p " + backup_path) + + filename = backup_path + "/web_" + name + "_" + \ + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.tar.gz' + + cmd = "cd " + os.path.dirname(path) + " && tar zcvf '" + \ + filename + "' " + exclude_dir_cmd + " '" + os.path.basename(path) + "' > /dev/null" + + # print(cmd) + mw.execShell(cmd) + + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + # print(filename) + if not os.path.exists(filename): + log = "网站[" + name + "]备份失败!" + print("★[" + endDate + "] " + log) + print("----------------------------------------------------------------------------") + return + + outTime = time.time() - startTime + pid = sql.table('sites').where('name=?', (name,)).getField('id') + sql.table('backup').add('type,name,pid,filename,add_time,size', ('0', os.path.basename( + filename), pid, filename, endDate, os.path.getsize(filename))) + log = "网站[" + name + "]备份成功,用时[" + str(round(outTime, 2)) + "]秒" + mw.writeLog('计划任务', log) + print("★[" + endDate + "] " + log) + print("|---保留最新的[" + count + "]份备份") + print("|---文件名:" + filename) + + # 清理多余备份 + backups = sql.table('backup').where( + 'type=? and pid=?', ('0', pid)).field('id,filename').select() + + num = len(backups) - int(count) + if num > 0: + for backup in backups: + mw.execShell("rm -f " + backup['filename']) + sql.table('backup').where('id=?', (backup['id'],)).delete() + num -= 1 + print("|---已清理过期备份文件:" + backup['filename']) + if num < 1: + break + + def getConf(self, mtype='mysql'): + path = mw.getServerDir() + '/' + mtype + '/etc/my.cnf' + return path + + def recognizeDbMode(self, mtype='mysql'): + conf = self.getConf(mtype) + con = mw.readFile(conf) + rep = r"!include %s/(.*)?\.cnf" % (mw.getServerDir() +'/'+ mtype +"/etc/mode",) + mode = 'none' + try: + data = re.findall(rep, con, re.M) + mode = data[0] + except Exception as e: + pass + return mode + + # 数据库密码处理 + def mypass(self, act, root): + conf_file = self.getConf('mysql') + mw.execShell("sed -i '/user=root/d' {}".format(conf_file)) + mw.execShell("sed -i '/password=/d' {}".format(conf_file)) + if act: + mycnf = mw.readFile(conf_file) + src_dump = "[mysqldump]\n" + sub_dump = src_dump + "user=root\npassword=\"{}\"\n".format(root) + if not mycnf: + return False + mycnf = mycnf.replace(src_dump, sub_dump) + if len(mycnf) > 100: + mw.writeFile(conf_file, mycnf) + return True + return True + + def backupDatabase(self, name, count): + db_path = mw.getServerDir() + '/mysql' + db_name = 'mysql' + name = mw.M('databases').dbPos(db_path, 'mysql').where( + 'name=?', (name,)).getField('name') + startTime = time.time() + if not name: + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + log = "数据库[" + name + "]不存在!" + print("★[" + endDate + "] " + log) + print( + "----------------------------------------------------------------------------") + return + + backup_path = mw.getBackupDir() + '/database' + if not os.path.exists(backup_path): + mw.execShell("mkdir -p " + backup_path) + + filename = backup_path + "/db_" + name + "_" + \ + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + ".sql.gz" + + mysql_root = mw.M('config').dbPos(db_path, db_name).where( + "id=?", (1,)).getField('mysql_root') + + my_cnf = self.getConf('mysql') + self.mypass(True, mysql_root) + + # mw.execShell(db_path + "/bin/mysqldump --opt --default-character-set=utf8 " + + # name + " | gzip > " + filename) + + # mw.execShell(db_path + "/bin/mysqldump --single-transaction --quick --default-character-set=utf8 " + + # name + " | gzip > " + filename) + + # 开启一致性事务 会lock表 + # cmd = db_path + "/bin/mysqldump --defaults-file=" + my_cnf + " --force --opt --default-character-set=utf8 " + \ + # name + " | gzip > " + filename + option = '' + mode = self.recognizeDbMode('mysql') + if mode == 'gtid': + option = ' --set-gtid-purged=off ' + + # skip-opt 不会lock表 + # --skip-opt --create-options + cmd = db_path + "/bin/mysqldump --defaults-file=" + my_cnf +" " + option +" --single-transaction -q --default-character-set=utf8mb4 " + \ + name + " | gzip > " + filename + # print(cmd) + mw.execShell(cmd) + + if not os.path.exists(filename): + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + log = "数据库[" + name + "]备份失败!" + print("★[" + endDate + "] " + log) + print( + "----------------------------------------------------------------------------") + return + + self.mypass(False, mysql_root) + + endDate = time.strftime('%Y/%m/%d %X', time.localtime()) + outTime = time.time() - startTime + pid = mw.M('databases').dbPos(db_path, db_name).where( + 'name=?', (name,)).getField('id') + + mw.M('backup').add('type,name,pid,filename,add_time,size', (1, os.path.basename( + filename), pid, filename, endDate, os.path.getsize(filename))) + log = "数据库[" + name + "]备份成功,用时[" + str(round(outTime, 2)) + "]秒" + mw.writeLog('计划任务', log) + print("★[" + endDate + "] " + log) + print("|---保留最新的[" + count + "]份备份") + print("|---文件名:" + filename) + + # 清理多余备份 + backups = mw.M('backup').where( + 'type=? and pid=?', ('1', pid)).field('id,filename').select() + + num = len(backups) - int(count) + if num > 0: + for backup in backups: + mw.execShell("rm -f " + backup['filename']) + mw.M('backup').where('id=?', (backup['id'],)).delete() + num -= 1 + print("|---已清理过期备份文件:" + backup['filename']) + if num < 1: + break + + def backupSiteAll(self, save): + sites = mw.M('sites').field('name').select() + for site in sites: + self.backupSite(site['name'], save) + + def backupDatabaseAll(self, save): + db_path = mw.getServerDir() + '/mysql' + db_name = 'mysql' + databases = mw.M('databases').dbPos( + db_path, db_name).field('name').select() + for database in databases: + self.backupDatabase(database['name'], save) + + def findPathName(self, path, filename): + f = os.scandir(path) + l = [] + for ff in f: + if ff.name.find(filename) > -1: + l.append(ff.name) + return l + + def makeExcludeDirCmd(self,echo): + exclude_dirs = [] + crontab_list = mw.M('crontab').where('echo=?', (echo,)).field('attr').find() + if crontab_list: + attr = crontab_list['attr'] + if attr != "": + ed_arrs = attr.split("\n") + for ed in ed_arrs: + exclude_dirs.append(ed.strip()) + + + cmd = "" + for v in exclude_dirs: + cmd += " --exclude='"+v+"'" + return cmd + + def backupPath(self, path, count, echo=None): + + exclude_dir_cmd = self.makeExcludeDirCmd(echo) + # print(exclude_dir_cmd) + mw.echoStart('备份') + + backup_path = mw.getBackupDir() + '/path' + if not os.path.exists(backup_path): + mw.execShell("mkdir -p " + backup_path) + + dirname = os.path.basename(path) + fname = 'path_{}_{}.tar.gz'.format( + dirname, mw.formatDate("%Y%m%d_%H%M%S")) + dfile = os.path.join(backup_path, fname) + + p_size = mw.getPathSize(path) + stime = time.time() + + cmd = "cd " + os.path.dirname(path) + " && tar zcvf '" + dfile + "' " + exclude_dir_cmd + " '" + dirname + "' 2>{err_log} 1> /dev/null".format( + err_log='/tmp/backup_err.log') + # print(cmd) + mw.execShell(cmd) + + tar_size = os.path.getsize(dfile) + + mw.echoInfo('备份目录:' + path) + mw.echoInfo('目录已备份到:' + dfile) + mw.echoInfo("目录大小:{}".format(mw.toSize(p_size))) + mw.echoInfo("开始压缩文件:{}".format(mw.formatDate(times=stime))) + mw.echoInfo("文件压缩完成,耗时{:.2f}秒,压缩包大小:{}".format( + time.time() - stime, mw.toSize(tar_size))) + mw.echoInfo('保留最新的备份数:' + count + '份') + + backups = self.findPathName(backup_path, 'path_{}'.format(dirname)) + num = len(backups) - int(count) + backups.sort() + if num > 0: + for backup in backups: + abspath_bk = backup_path + "/" + backup + mw.execShell("rm -f " + abspath_bk) + mw.echoInfo("已清理过期备份文件:" + abspath_bk) + num -= 1 + if num < 1: + break + + mw.echoEnd('备份') + +if __name__ == "__main__": + backup = backupTools() + stype = sys.argv[1] + if stype == 'site': + if sys.argv[2] == 'ALL': + backup.backupSiteAll(sys.argv[3]) + else: + backup.backupSite(sys.argv[2], sys.argv[3], sys.argv[4]) + elif stype == 'database': + if sys.argv[2] == 'ALL': + backup.backupDatabaseAll(sys.argv[3]) + else: + backup.backupDatabase(sys.argv[2], sys.argv[3]) + elif stype == 'path': + backup.backupPath(sys.argv[2], sys.argv[3], sys.argv[4]) diff --git a/scripts/getos.sh b/scripts/getos.sh new file mode 100755 index 000000000..2e15da244 --- /dev/null +++ b/scripts/getos.sh @@ -0,0 +1,49 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +#获取信息和版本 +_os=`uname` +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' +elif grep -Eqi "EulerOS" /etc/*-release || grep -Eqi "openEuler" /etc/*-release; then + OSNAME='euler' +elif grep -Eqi "Arch" /etc/issue || grep -Eq "Arch" /etc/*-release; then + OSNAME='arch' +elif grep -Eqi "CentOS" /etc/issue || grep -Eq "CentOS" /etc/*-release; then + OSNAME='centos' +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eq "AlmaLinux" /etc/*-release; then + OSNAME='alma' +elif grep -Eqi "Rocky" /etc/issue || grep -Eq "Rocky" /etc/*-release; then + OSNAME='rocky' +elif grep -Eqi "Red Hat Enterprise Linux Server" /etc/issue || grep -Eq "Red Hat Enterprise Linux Server" /etc/*-release; then + OSNAME='rhel' +elif grep -Eqi "Aliyun" /etc/issue || grep -Eq "Aliyun" /etc/*-release; then + OSNAME='aliyun' +elif grep -Eqi "Fedora" /etc/issue || grep -Eq "Fedora" /etc/*-release; then + OSNAME='fedora' +elif grep -Eqi "Amazon Linux AMI" /etc/issue || grep -Eq "Amazon Linux AMI" /etc/*-release; then + OSNAME='amazon' +elif grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/*-release; then + OSNAME='debian' +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eq "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' +elif grep -Eqi "Raspbian" /etc/issue || grep -Eq "Raspbian" /etc/*-release; then + OSNAME='raspbian' +elif grep -Eqi "Deepin" /etc/issue || grep -Eq "Deepin" /etc/*-release; then + OSNAME='deepin' +else + OSNAME='unknow' +fi + +if [ -d /www/server/mdserver-web ];then + echo "$OSNAME" > /www/server/mdserver-web/data/osname.pl +fi + +if [ "$OSNAME" == "macos" ];then + echo "$OSNAME" +fi \ No newline at end of file diff --git a/scripts/init.d/mw-task.service.tpl b/scripts/init.d/mw-task.service.tpl new file mode 100755 index 000000000..405d9138d --- /dev/null +++ b/scripts/init.d/mw-task.service.tpl @@ -0,0 +1,16 @@ +[Unit] +Description=mw-task daemon +After=network.target + +[Service] +Type=simple +WorkingDirectory={$SERVER_PATH} +EnvironmentFile={$SERVER_PATH}/scripts/init.d/service.sh +ExecStart=python3 panel_task.py +ExecStop=kill -HUP $MAINID +ExecReload=kill -HUP $MAINID +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/scripts/init.d/mw.service.tpl b/scripts/init.d/mw.service.tpl new file mode 100755 index 000000000..8c92d1214 --- /dev/null +++ b/scripts/init.d/mw.service.tpl @@ -0,0 +1,21 @@ +[Unit] +Description=mw-panel daemon +After=network.target + +[Service] +Type=simple +WorkingDirectory={$SERVER_PATH} +EnvironmentFile={$SERVER_PATH}/scripts/init.d/service.sh +ExecStart=cd web && gunicorn -c setting.py app:app +ExecStop=kill -HUP $MAINID +ExecReload=kill -HUP $MAINID +KillMode=process +Restart=on-failure + +[Timer] +# 每日凌晨点重启 +OnCalendar=*-*-* 03:30:00 +Unit=mw.service + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/scripts/init.d/mw.tpl b/scripts/init.d/mw.tpl new file mode 100755 index 000000000..aa1307f5c --- /dev/null +++ b/scripts/init.d/mw.tpl @@ -0,0 +1,781 @@ +#!/bin/bash +# chkconfig: 2345 55 25 +# description: MW Cloud Service + +### BEGIN INIT INFO +# Provides: Midoks +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts mw +# Description: starts the mw +### END INIT INFO + +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +PLAIN='\033[0m' +BOLD='\033[1m' +SUCCESS='[\033[32mOK\033[0m]' +COMPLETE='[\033[32mDONE\033[0m]' +WARN='[\033[33mWARN\033[0m]' +ERROR='[\033[31mERROR\033[0m]' +WORKING='[\033[34m*\033[0m]' + + +PATH=/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export LANG=en_US.UTF-8 + +PANEL_DIR={$SERVER_PATH} +ROOT_PATH=$(dirname "$PANEL_DIR") +PATH=$PATH:${PANEL_DIR}/bin + + +if [ -f ${PANEL_DIR}/bin/activate ];then + source ${PANEL_DIR}/bin/activate + if [ "$?" != "0" ];then + echo "load local python env fail!" + fi +fi + +mw_start_panel() +{ + isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "starting mw-panel... \c" + cd ${PANEL_DIR}/web && gunicorn -c setting.py app:app + port=$(cat ${PANEL_DIR}/data/port.pl) + isStart="" + while [[ "$isStart" == "" ]]; + do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + if [[ "$isStart" == "" ]];then + isStart=$(ps -ef|grep python3|grep mdserver-web|grep app:app|awk '{print $2}'|xargs) + fi + let n+=1 + if [ $n -gt 60 ];then + break; + fi + done + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo '------------------------------------------------------' + tail -n 20 ${PANEL_DIR}/logs/panel_error.log + echo '------------------------------------------------------' + echo -e "\033[31mError: mw-panel service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "starting mw-panel... mw(pid $(echo $isStart)) already running" + fi +} + + +mw_start_task() +{ + isStart=$(ps aux |grep 'panel_task.py'|grep -v grep|awk '{print $2}') + if [ "$isStart" == '' ];then + echo -e "starting mw-tasks... \c" + cd ${PANEL_DIR} && python3 panel_task.py >> ${PANEL_DIR}/logs/panel_task.log 2>&1 & + sleep 0.3 + isStart=$(ps aux |grep 'panel_task.py'|grep -v grep|awk '{print $2}') + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo '------------------------------------------------------' + tail -n 20 ${PANEL_DIR}/logs/panel_task.log + echo '------------------------------------------------------' + echo -e "\033[31mError: mw-tasks service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo "starting mw-tasks... mw-tasks (pid $(echo $isStart)) already running" + fi +} + +mw_start() +{ + mw_start_task + mw_start_panel +} + +# /www/server/mdserver-web/logs/panel_task.lock && service mw restart_task +mw_stop_task() +{ + if [ -f ${PANEL_DIR}/logs/panel_task.lock ];then + echo -e "\033[32mthe task is running and cannot be stopped\033[0m" + return 0 + fi + + echo -e "stopping mw-tasks... \c"; + panel_task=$(ps aux | grep 'panel_task.py'|grep -v grep|awk '{print $2}') + panel_task=($panel_task) + for p in ${panel_task[@]} + do + kill -9 $p > /dev/null 2>&1 + done + + zzpids=$(ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]' | awk '{print $2}') + zzpids=($zzpids) + for p in ${zzpids[@]} + do + kill -9 $p > /dev/null 2>&1 + done + echo -e "\033[32mdone\033[0m" +} + +mw_stop_panel() +{ + echo -e "stopping mw-panel... \c"; + pidfile=${PANEL_DIR}/logs/mw.pid + if [ -f $pidfile ];then + pid=`cat $pidfile` + kill -9 $pid > /dev/null 2>&1 + rm -f $pidfile + fi + + APP_LIST=`ps aux|grep 'gunicorn -c setting.py app:app'|grep -v grep|awk '{print $2}'` + APP_LIST=($APP_LIST) + for p in ${APP_LIST[@]} + do + kill -9 $p > /dev/null 2>&1 + done + + APP_LIST=`ps -ef|grep app:app |grep -v grep|awk '{print $2}'` + APP_LIST=($APP_LIST) + for i in ${APP_LIST[@]} + do + kill -9 $i > /dev/null 2>&1 + done + echo -e "\033[32mdone\033[0m" +} + +mw_stop() +{ + mw_stop_task + mw_stop_panel +} + +mw_status() +{ + isStart=$(ps aux|grep 'gunicorn -c setting.py app:app'|grep -v grep|awk '{print $2}') + if [ "$isStart" != '' ];then + echo -e "\033[32mmw (pid $(echo $isStart)) already running\033[0m" + else + echo -e "\033[31mmw not running\033[0m" + fi + + isStart=$(ps aux |grep 'panel_task.py'|grep -v grep|awk '{print $2}') + if [ "$isStart" != '' ];then + echo -e "\033[32mmw-task (pid $isStart) already running\033[0m" + else + echo -e "\033[31mmw-task not running\033[0m" + fi +} + + +mw_reload() +{ + isStart=$(ps aux|grep 'gunicorn -c setting.py app:app'|grep -v grep|awk '{print $2}') + + if [ "$isStart" != '' ];then + echo -e "reload mw... \c"; + arr=`ps aux|grep 'gunicorn -c setting.py app:app'|grep -v grep|awk '{print $2}'` + for p in ${arr[@]} + do + kill -9 $p + done + cd ${PANEL_DIR}/web && gunicorn -c setting.py app:app + isStart=`ps aux|grep 'gunicorn -c setting.py app:app'|grep -v grep|awk '{print $2}'` + if [ "$isStart" == '' ];then + echo -e "\033[31mfailed\033[0m" + echo '------------------------------------------------------' + tail -n 20 $mw_path/logs/error.log + echo '------------------------------------------------------' + echo -e "\033[31mError: mw service startup failed.\033[0m" + return; + fi + echo -e "\033[32mdone\033[0m" + else + echo -e "\033[31mmw not running\033[0m" + mw_start + fi +} + +mw_close(){ + cd ${PANEL_DIR} && python3 panel_tools.py cli 14 +} + +mw_open() +{ + cd ${PANEL_DIR} && python3 panel_tools.py cli 15 +} + +mw_unbind_domain() +{ + if [ -f ${PANEL_DIR}/data/bind_domain.pl ];then + rm -rf ${PANEL_DIR}/data/bind_domain.pl + fi +} + +mw_unbind_ssl() +{ + if [ -f ${PANEL_DIR}/local ];then + rm -rf ${PANEL_DIR}/local + fi + + if [ -f $mw_path/nginx ];then + rm -rf $mw_path/nginx + fi + + if [ -f $mw_path/ssl/choose.pl ];then + rm -rf $mw_path/ssl/choose.pl + fi +} + +error_logs() +{ + tail -n 100 ${PANEL_DIR}/logs/panel_error.log +} + +# 00----00----00----00----00----00----00----00----00----00----00----00----00----00 + +function AutoSizeStr(){ + NAME_STR=$1 + NAME_NUM=$2 + + NAME_STR_LEN=`echo "$NAME_STR" | wc -L` + NAME_NUM_LEN=`echo "$NAME_NUM" | wc -L` + + fix_len=35 + remaining_len=`expr $fix_len - $NAME_STR_LEN - $NAME_NUM_LEN` + FIX_SPACE=' ' + for ((ass_i=1;ass_i<=$remaining_len;ass_i++)) + do + FIX_SPACE="$FIX_SPACE " + done + echo -e " ❖ ${1}${FIX_SPACE}${2})" +} + +function ChooseProxyURL(){ + clear + echo -e '+---------------------------------------------------+' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '| 欢迎使用 Linux 一键安装mdserver-web面板源码 |' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '+---------------------------------------------------+' + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e ' 提供以下国内代理地址可供选择: ' + echo -e '' + echo -e '#####################################################' + echo -e '' + cm_i=0 + for V in ${SOURCE_LIST_KEY[@]}; do + num=`expr $cm_i + 1` + AutoSizeStr "${V}" "$num" + cm_i=`expr $cm_i + 1` + done + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e " 系统时间 ${BLUE}$(date "+%Y-%m-%d %H:%M:%S")${PLAIN}" + echo -e '' + echo -e '#####################################################' + CHOICE_A=$(echo -e "\n${BOLD}└─ 请选择并输入你想使用的代理地址 [ 1-${SOURCE_LIST_LEN} ]:${PLAIN}") + + read -p "${CHOICE_A}" INPUT + # echo $INPUT + if [ "$INPUT" == "" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n默认选择[${BLUE}${INPUT_KEY}${PLAIN}]安装!" + fi + + if [ "$INPUT" -lt "0" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n低于边界错误!选择[${BLUE}${INPUT_KEY}${PLAIN}]安装!" + sleep 2s + fi + + if [ "$INPUT" -gt "${SOURCE_LIST_LEN}" ];then + INPUT=${SOURCE_LIST_LEN} + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n超出边界错误!选择[${BLUE}${INPUT_KEY}${PLAIN}]安装!" + sleep 2s + fi + + INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$INPUT]} + HTTP_PREFIX=${PROXY_URL[$INPUT_KEY]} +} + + +mw_common_proxy(){ + HTTP_PREFIX="https://" + LOCAL_ADDR=common + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" != "common" ];then + # https://github.akams.cn + declare -A PROXY_URL + PROXY_URL["gh_proxy_com"]="https://gh-proxy.com/" + PROXY_URL["github_do"]="https://github.do/" + PROXY_URL["gh_llkk_cc"]="https://gh.llkk.cc/https://" + PROXY_URL["gh_felicity_ac_cn"]="https://gh.felicity.ac.cn/https://" + PROXY_URL["ghfast_top"]="https://ghfast.top/" + PROXY_URL["ghproxy_net"]="https://ghproxy.net/" + PROXY_URL["gh_927223_xyz"]="https://gh.927223.xyz/https://" + PROXY_URL["gh_proxy_net"]="https://gh-proxy.net/" + + PROXY_URL["source"]="https://" + + + SOURCE_LIST_KEY_SORT_TMP=$(echo ${!PROXY_URL[@]} | tr ' ' '\n' | sort -n) + SOURCE_LIST_KEY=(${SOURCE_LIST_KEY_SORT_TMP//'\n'/}) + SOURCE_LIST_LEN=${#PROXY_URL[*]} + fi + + if [ "$LOCAL_ADDR" != "common" ];then + ChooseProxyURL + + if [ "$HTTP_PREFIX" != "https://" ];then + DOMAIN=`echo $HTTP_PREFIX | sed 's|https://||g'` + DOMAIN=`echo $DOMAIN | sed 's|/||g'` + ping -c 3 $DOMAIN > /dev/null 2>&1 + if [ "$?" != "0" ];then + echo "无效代理地址:${HTTP_PREFIX}" + exit + fi + fi + fi +} + +mw_install(){ + if [ -f ${PANEL_DIR}/task.py ];then + echo "与后续版本差异太大,不再提供更新" + exit 0 + fi + + mw_common_proxy + echo "bash <(curl -fsSL "${HTTP_PREFIX}raw.githubusercontent.com/midoks/mdserver-web/master/scripts/install.sh")" + bash <(curl -fsSL "${HTTP_PREFIX}raw.githubusercontent.com/midoks/mdserver-web/master/scripts/install.sh") +} + +mw_update() +{ + if [ -f ${PANEL_DIR}/task.py ];then + echo "与后续版本差异太大,不再提供更新" + exit 0 + fi + + mw_common_proxy + echo "bash <(curl -fsSL "${HTTP_PREFIX}raw.githubusercontent.com/midoks/mdserver-web/master/scripts/update.sh")" + bash <(curl -fsSL "${HTTP_PREFIX}raw.githubusercontent.com/midoks/mdserver-web/master/scripts/update.sh") +} + +mw_update_dev() +{ + if [ -f ${PANEL_DIR}/task.py ];then + echo "与后续版本差异太大,不再提供更新" + exit 0 + fi + + mw_common_proxy + echo "bash <(curl -fsSL "${HTTP_PREFIX}raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/update_dev.sh")" + bash <(curl -fsSL "${HTTP_PREFIX}raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/update_dev.sh") + + cd ${PANEL_DIR} +} + +mw_update_venv() +{ + rm -rf ${PANEL_DIR}/bin + rm -rf ${PANEL_DIR}/lib64 + rm -rf ${PANEL_DIR}/lib + + mw_common_proxy + echo "bash <(curl -fsSL "${HTTP_PREFIX}raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/update_dev.sh")" + bash <(curl -fsSL "${HTTP_PREFIX}raw.githubusercontent.com/midoks/mdserver-web/dev/scripts/update_dev.sh") + + cd ${PANEL_DIR} +} + +mw_mirror() +{ + LOCAL_ADDR=common + cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") + if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + fi + + if [ "$LOCAL_ADDR" == "common" ];then + bash <(curl --insecure -sSL https://raw.githubusercontent.com/midoks/change-linux-mirrors/main/change-mirrors.sh) + else + bash <(curl -sSL https://linuxmirrors.cn/main.sh) + fi + cd ${ROOT_PATH} +} + +mw_install_app() +{ + bash $mw_path/scripts/quick/app.sh +} + +mw_close_admin_path(){ + cd ${PANEL_DIR} && python3 panel_tools.py cli 6 +} + +mw_force_kill() +{ + PLIST=`ps -ef|grep app:app |grep -v grep|awk '{print $2}'` + for i in $PLIST + do + kill -9 $i + done + + pids=`ps -ef|grep task.py | grep -v grep |awk '{print $2}'` + arr=($pids) + for p in ${arr[@]} + do + kill -9 $p + done +} + +mw_debug(){ + mw_stop + mw_force_kill + + port=7200 + if [ -f ${PANEL_DIR}/data/port.pl ];then + port=$(cat ${PANEL_DIR}/data/port.pl) + fi + + if [ -d ${PANEL_DIR}/web ];then + cd ${PANEL_DIR}/web + fi + # gunicorn -b :$port -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 app:app + gunicorn -b :$port -k eventlet -w 1 app:app +} + +mw_connect_mysql(){ + # choose mysql login + + declare -A DB_TYPE + + if [ -d "${ROOT_PATH}/mysql" ];then + DB_TYPE["mysql"]="mysql" + fi + + if [ -d "${ROOT_PATH}/mariadb" ];then + DB_TYPE["mariadb"]="mariadb" + fi + + if [ -d "${ROOT_PATH}/mysql-apt" ];then + DB_TYPE["mysql-apt"]="mysql-apt" + fi + + if [ -d "${ROOT_PATH}/mysql-yum" ];then + DB_TYPE["mysql-yum"]="mysql-yum" + fi + + if [ -d "${ROOT_PATH}/mysql-community" ];then + DB_TYPE["mysql-community"]="mysql-community" + fi + + SOURCE_LIST_KEY_SORT_TMP=$(echo ${!DB_TYPE[@]} | tr ' ' '\n' | sort -n) + SOURCE_LIST_KEY=(${SOURCE_LIST_KEY_SORT_TMP//'\n'/}) + SOURCE_LIST_LEN=${#DB_TYPE[*]} + + if [ "$SOURCE_LIST_LEN" == "0" ]; then + echo -e "no data!" + exit 1 + fi + + cm_i=0 + for M in ${SOURCE_LIST_KEY[@]}; do + num=`expr $cm_i + 1` + AutoSizeStr "${M}" "$num" + cm_i=`expr $cm_i + 1` + done + CHOICE_A=$(echo -e "\n${BOLD}└─ Please select and enter the database you want to log in to [ 1-${SOURCE_LIST_LEN} ]:${PLAIN}") + read -p "${CHOICE_A}" INPUT + + if [ "$INPUT" == "" ]; then + INPUT=1 + fi + + if [ "$INPUT" -lt "0" ] || [ "$INPUT" -gt "${SOURCE_LIST_LEN}" ]; then + echo -e "\nBoundary error not selected!" + exit 1 + fi + + INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$INPUT]} + CHOICE_DB=${DB_TYPE[$INPUT_KEY]} + echo "login to ${CHOICE_DB}:" + + pwd=$(cd ${ROOT_PATH}/mdserver-web && python3 ${ROOT_PATH}/mdserver-web/plugins/${CHOICE_DB}/index.py root_pwd) + if [ "$pwd" == "admin" ];then + pwd="" + fi + + if [ "$CHOICE_DB" == "mysql" ];then + ${ROOT_PATH}/mysql/bin/mysql -uroot -p"${pwd}" + fi + + if [ "$CHOICE_DB" == "mariadb" ];then + ${ROOT_PATH}/mariadb/bin/mariadb -S ${ROOT_PATH}/mariadb/mysql.sock -uroot -p"${pwd}" + fi + + if [ "$CHOICE_DB" == "mysql-community" ];then + ${ROOT_PATH}/mysql-community/bin/mysql -S ${ROOT_PATH}/mysql-community/mysql.sock -uroot -p"${pwd}" + fi + + if [ "$CHOICE_DB" == "mysql-apt" ];then + ${ROOT_PATH}/mysql-apt/bin/usr/bin/mysql -S ${ROOT_PATH}/mysql-apt/mysql.sock -uroot -p"${pwd}" + fi + + if [ "$CHOICE_DB" == "mysql-yum" ];then + ${ROOT_PATH}/mysql-yum/bin/usr/bin/mysql -S ${ROOT_PATH}/mysql-yum/mysql.sock -uroot -p"${pwd}" + fi +} + + +mw_connect_pgdb(){ + if [ ! -d "${ROOT_PATH}/postgresql" ];then + echo -e "postgresql not install!" + exit 1 + fi + + + pwd=$(cd ${PANEL_DIR} && python3 ${PANEL_DIR}/plugins/postgresql/index.py root_pwd) + export PGPASSWORD=${pwd} + echo "${ROOT_PATH}/postgresql/bin/psql -U postgres -W" + ${ROOT_PATH}/postgresql/bin/psql -U postgres -W +} + + +mw_mongodb(){ + CONF="${ROOT_PATH}/mongodb/mongodb.conf" + if [ ! -f "$CONF" ]; then + echo -e "not install mongodb!" + exit 1 + fi + + MGDB_PORT=$(cat $CONF |grep port|grep -v '#'|awk '{print $2}') + MGDB_AUTH=$(cat $CONF |grep authorization | grep -v '#'|awk '{print $2}') + + AUTH_STR="" + if [[ "$MGDB_AUTH" == "enabled" ]];then + pwd=$(cd ${PANEL_DIR} && python3 ${PANEL_DIR}/plugins/mongodb/index.py root_pwd) + AUTH_STR="-u root -p ${pwd}" + fi + + CLIEXEC="${ROOT_PATH}/mongodb/bin/mongosh --port ${MGDB_PORT} ${AUTH_STR}" + echo $CLIEXEC + ${CLIEXEC} +} + + +mw_redis(){ + CONF="${ROOT_PATH}/redis/redis.conf" + + if [ ! -f "$CONF" ]; then + echo -e "not install redis!" + exit 1 + fi + + REDISPORT=$(cat $CONF |grep port|grep -v '#'|awk '{print $2}') + REDISPASS=$(cat $CONF |grep requirepass|grep -v '#'|awk '{print $2}') + if [ "$REDISPASS" != "" ];then + REDISPASS=" -a $REDISPASS" + fi + CLIEXEC="${ROOT_PATH}/redis/bin/redis-cli -p $REDISPORT$REDISPASS" + echo $CLIEXEC + ${CLIEXEC} +} + +mw_valkey(){ + CONF="${ROOT_PATH}/valkey/valkey.conf" + + if [ ! -f "$CONF" ]; then + echo -e "not install valkey!" + exit 1 + fi + + REDISPORT=$(cat $CONF |grep port|grep -v '#'|awk '{print $2}') + REDISPASS=$(cat $CONF |grep requirepass|grep -v '#'|awk '{print $2}') + if [ "$REDISPASS" != "" ];then + REDISPASS=" -a $REDISPASS" + fi + CLIEXEC="${ROOT_PATH}/valkey/bin/valkey-cli -p $REDISPORT$REDISPASS" + echo $CLIEXEC + ${CLIEXEC} +} + +mw_ssh(){ + cd ${PANEL_DIR} && python3 panel_tools.py cli 202 + + if [[ "$?" == "0" ]]; then + POS=$(echo -e "\n${BLUE}└─ 选择登陆终端:${PLAIN}") + read -p "${POS}" INPUT + SSS=`cd ${PANEL_DIR} && python3 panel_tools.py cli 202 $INPUT` + # echo "info:$SSS" + # SSS="127.0.0.1|22|root|xx" + IFS='|' read -r SERVER_IP SERVER_PORT SERVER_USER SERVER_PASS <<< "$SSS" + echo "Attempting SSH connection to $SERVER_USER@$SERVER_IP:$SERVER_PORT" + sshpass -p "$SERVER_PASS" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -p "$SERVER_PORT" "$SERVER_USER@$SERVER_IP" + fi +} + +mw_venv(){ + cd ${PANEL_DIR} && source bin/activate +} + +mw_clean_lib(){ + cd ${PANEL_DIR} && rm -rf lib + cd ${PANEL_DIR} && rm -rf lib64 + cd ${PANEL_DIR} && rm -rf bin + cd ${PANEL_DIR} && rm -rf include +} + +mw_list(){ + echo -e "mw default - 显示面板默认信息" + echo -e "mw db - 连接MySQL" + echo -e "mw pgdb - 连接PostgreSQL" + echo -e "mw mongdb - 连接MongoDB" + echo -e "mw redis - 连接Redis" + echo -e "mw valkey - 连接WalKey" + echo -e "mw install - 执行安装脚本" + echo -e "mw update - 更新到正式环境最新代码" + echo -e "mw update_dev - 更新到测试环境最新代码" + echo -e "mw debug - 调式开发面板" + echo -e "mw list - 显示命令列表" +} + +mw_default(){ + cd ${PANEL_DIR} + port=7200 + scheme=$(cd ${PANEL_DIR} && python3 ${PANEL_DIR}/panel_tools.py panel_ssl_type) + + if [ -f ${PANEL_DIR}/data/port.pl ];then + port=$(cat ${PANEL_DIR}/data/port.pl) + fi + + if [ ! -f ${PANEL_DIR}/data/default.pl ];then + echo -e "\033[33mInstall Failed\033[0m" + exit 1 + fi + + password=$(cat ${PANEL_DIR}/data/default.pl) + + admin_path=$(cd ${PANEL_DIR} && python3 ${PANEL_DIR}/panel_tools.py admin_path) + if [ "$address" == "" ];then + v4=$(cd ${PANEL_DIR} && python3 ${PANEL_DIR}/panel_tools.py getServerIp 4) + v6=$(cd ${PANEL_DIR} && python3 ${PANEL_DIR}/panel_tools.py getServerIp 6) + + if [ "$v4" != "" ] && [ "$v6" != "" ]; then + + if [ ! -f ${PANEL_DIR}/data/ipv6.pl ];then + echo 'True' > ${PANEL_DIR}/data/ipv6.pl + mw_stop + mw_start + fi + + address="MW-PANEL-URL-IPV4: ${scheme}://$v4:$port$admin_path \nMW-PANEL-URL-IPV6: ${scheme}://[$v6]:$port$admin_path" + elif [ "$v4" != "" ]; then + address="MW-PANEL-URL: ${scheme}://$v4:$port$admin_path" + elif [ "$v6" != "" ]; then + + if [ ! -f ${PANEL_DIR}/data/ipv6.pl ];then + # Need to restart ipv6 to take effect + echo 'True' > ${PANEL_DIR}/data/ipv6.pl + mw_stop + mw_start + fi + address="MW-PANEL-URL: ${scheme}://[$v6]:$port$admin_path" + else + address="MW-PANEL-URL: ${scheme}://you-network-ip:$port$admin_path" + fi + else + address="MW-PANEL-URL: ${scheme}://$address:$port$admin_path" + fi + + # bind domain check + panel_bind_domain=$(cd ${PANEL_DIR} && python3 ${PANEL_DIR}/panel_tools.py panel_bind_domain) + if [ "$panel_bind_domain" != "" ];then + address="MW-PANEL-URL: ${scheme}://$panel_bind_domain:$port$admin_path\n${address}" + fi + + show_panel_ip="$port|" + echo -e "==================================================================" + echo -e "\033[32mMW-PANEL DEFAULT INFO!\033[0m" + echo -e "==================================================================" + echo -e "$address" + echo -e `cd ${PANEL_DIR} && python3 ${PANEL_DIR}/panel_tools.py username` + echo -e `cd ${PANEL_DIR} && python3 ${PANEL_DIR}/panel_tools.py password` + echo -e "\033[33mWarning:\033[0m" + echo -e "\033[33mIf you cannot access the panel. \033[0m" + echo -e "\033[33mrelease the following port (${show_panel_ip}80|443|22) in the security group.\033[0m" + echo -e "==================================================================" +} + +case "$1" in + 'start') mw_start;; + 'stop') mw_stop;; + 'reload') mw_reload;; + 'restart') + mw_stop + mw_start;; + 'restart_panel') + mw_stop_panel + mw_start_panel;; + 'restart_task') + mw_stop_task + mw_start_task;; + 'status') mw_status;; + 'logs') error_logs;; + 'close') mw_close;; + 'open') mw_open;; + 'install') mw_install;; + 'update') mw_update;; + 'dev') mw_update_dev;; + 'update_dev') mw_update_dev;; + 'install_app') mw_install_app;; + 'close_admin_path') mw_close_admin_path;; + 'unbind_domain') mw_unbind_domain;; + 'unbind_ssl') mw_unbind_domain;; + 'debug') mw_debug;; + 'mirror') mw_mirror;; + 'db') mw_connect_mysql;; + 'pgdb') mw_connect_pgdb;; + 'redis') mw_redis;; + 'valkey')mw_valkey;; + 'mongodb') mw_mongodb;; + 'ssh') mw_ssh;; + 'venv') mw_update_venv;; + 'clean_lib') mw_clean_lib;; + 'list') mw_list;; + 'default') mw_default;; + *) + cd ${PANEL_DIR} && python3 ${PANEL_DIR}/panel_tools.py cli $1 + ;; +esac diff --git a/scripts/init.d/service.sh b/scripts/init.d/service.sh new file mode 100644 index 000000000..fea074dbe --- /dev/null +++ b/scripts/init.d/service.sh @@ -0,0 +1,8 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +cd /www/server/mdserver-web +if [ -f bin/activate ];then + source bin/activate +fi \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 000000000..0561aff95 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,299 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +PLAIN='\033[0m' +BOLD='\033[1m' +SUCCESS='[\033[32mOK\033[0m]' +COMPLETE='[\033[32mDONE\033[0m]' +WARN='[\033[33mWARN\033[0m]' +ERROR='[\033[31mERROR\033[0m]' +WORKING='[\033[34m*\033[0m]' +REPO_OWNER="AndyXeCM" +REPO_NAME="PowerLinux" +REPO_BRANCH="master" + + +# LANG=en_US.UTF-8 +is64bit=`getconf LONG_BIT` + +if [ -f /www/server/mdserver-web/tools.py ];then + echo -e "检测到旧版代码,为避免翻车先停一下~" + echo -e "如确认继续,请先执行: rm -rf /www/server/mdserver-web" + echo -e "处理完再回来开装吧!需要帮忙也可以随时再问我。" + exit 0 +fi + +LOG_FILE=/var/log/mw-install.log +{ + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn +fi + +if [ "$LOCAL_ADDR" != "common" ];then + declare -A PROXY_URL + PROXY_URL["gh_proxy_com"]="https://gh-proxy.com/" + PROXY_URL["github_do"]="https://github.do/" + PROXY_URL["gh_llkk_cc"]="https://gh.llkk.cc/https://" + PROXY_URL["gh_felicity_ac_cn"]="https://gh.felicity.ac.cn/https://" + PROXY_URL["ghfast_top"]="https://ghfast.top/" + PROXY_URL["ghproxy_net"]="https://ghproxy.net/" + PROXY_URL["gh_927223_xyz"]="https://gh.927223.xyz/https://" + PROXY_URL["gh_proxy_net"]="https://gh-proxy.net/" + + PROXY_URL["source"]="https://" + + + SOURCE_LIST_KEY_SORT_TMP=$(echo ${!PROXY_URL[@]} | tr ' ' '\n' | sort -n) + SOURCE_LIST_KEY=(${SOURCE_LIST_KEY_SORT_TMP//'\n'/}) + SOURCE_LIST_LEN=${#PROXY_URL[*]} +fi + + +function AutoSizeStr(){ + NAME_STR=$1 + NAME_NUM=$2 + + NAME_STR_LEN=`echo "$NAME_STR" | wc -L` + NAME_NUM_LEN=`echo "$NAME_NUM" | wc -L` + + fix_len=35 + remaining_len=`expr $fix_len - $NAME_STR_LEN - $NAME_NUM_LEN` + FIX_SPACE=' ' + for ((ass_i=1;ass_i<=$remaining_len;ass_i++)) + do + FIX_SPACE="$FIX_SPACE " + done + echo -e " ❖ ${1}${FIX_SPACE}${2})" +} + +function ChooseProxyURL(){ + clear + echo -e '+---------------------------------------------------+' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '| 欢迎使用 Linux 一键安装面板源码,马上开始吧! |' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '+---------------------------------------------------+' + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e ' 提供以下国内代理地址可供选择: ' + echo -e '' + echo -e '#####################################################' + echo -e '' + cm_i=0 + for V in ${SOURCE_LIST_KEY[@]}; do + num=`expr $cm_i + 1` + AutoSizeStr "${V}" "$num" + cm_i=`expr $cm_i + 1` + done + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e " 系统时间 ${BLUE}$(date "+%Y-%m-%d %H:%M:%S")${PLAIN}" + echo -e '' + echo -e '#####################################################' + CHOICE_A=$(echo -e "\n${BOLD}└─ 请选择并输入你想使用的代理地址 [ 1-${SOURCE_LIST_LEN} ](回车默认 1):${PLAIN}") + + read -p "${CHOICE_A}" INPUT + # echo $INPUT + if [ "$INPUT" == "" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n默认选择[${BLUE}${INPUT_KEY}${PLAIN}],准备开始安装~" + fi + + if [ "$INPUT" -lt "0" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n低于边界啦!已切回[${BLUE}${INPUT_KEY}${PLAIN}]继续安装~" + sleep 2s + fi + + if [ "$INPUT" -gt "${SOURCE_LIST_LEN}" ];then + INPUT=${SOURCE_LIST_LEN} + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n超出边界啦!已切回[${BLUE}${INPUT_KEY}${PLAIN}]继续安装~" + sleep 2s + fi + + INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$INPUT]} + HTTP_PREFIX=${PROXY_URL[$INPUT_KEY]} +} + +if [ "$LOCAL_ADDR" != "common" ];then + ChooseProxyURL + + if [ "$HTTP_PREFIX" != "https://" ];then + DOMAIN=`echo $HTTP_PREFIX | sed 's|https://||g'` + DOMAIN=`echo $DOMAIN | sed 's|/||g'` + ping -c 3 $DOMAIN > /dev/null 2>&1 + if [ "$?" != "0" ];then + echo "无效代理地址:${DOMAIN}" + exit + fi + fi +fi + +if [ -f /etc/motd ];then + echo "welcome to mdserver-web panel" > /etc/motd +fi + +startTime=`date +%s` + +_os=`uname` +echo "检测到系统: ${_os}" + +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh + zypper install cron wget curl zip unzip +elif grep -Eqi "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget curl zip unzip unrar rar +elif grep -Eqi "EulerOS" /etc/*-release || grep -Eqi "openEuler" /etc/*-release; then + OSNAME='euler' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "CentOS" /etc/issue || grep -Eqi "CentOS" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Fedora" /etc/issue || grep -Eqi "Fedora" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Rocky" /etc/issue || grep -Eqi "Rocky" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Anolis" /etc/issue || grep -Eqi "Anolis" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eqi "AlmaLinux" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Amazon Linux" /etc/issue || grep -Eqi "Amazon Linux" /etc/*-release; then + OSNAME='amazon' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Debian" /etc/issue || grep -Eqi "Debian" /etc/os-release; then + OSNAME='debian' + # apt update -y + apt install -y wget curl zip unzip tar cron +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eqi "Ubuntu" /etc/os-release; then + OSNAME='ubuntu' + # apt update -y + apt install -y wget curl zip unzip tar cron +elif grep -Eqi "Alpine" /etc/issue || grep -Eqi "Alpine" /etc/*-release; then + OSNAME='alpine' + apk update + apk add devscripts -force-broken-world + apk add wget zip unzip tar -force-broken-world +else + OSNAME='unknow' +fi + +if [ "$EUID" -ne 0 ] && [ "$OSNAME" != "macos" ];then + echo "需要使用 root 权限运行安装脚本,请用 sudo 或切换到 root 后重试~" + exit +fi + +echo "LOCAL:${LOCAL_ADDR}" +echo "OSNAME:${OSNAME}" + +if [ $OSNAME != "macos" ];then + if id www &> /dev/null ;then + echo "" + else + groupadd www + useradd -g www -s /usr/sbin/nologin www + fi + + mkdir -p /www/server + mkdir -p /www/wwwroot + mkdir -p /www/wwwlogs + mkdir -p /www/backup/database + mkdir -p /www/backup/site + + if [ ! -d /www/server/mdserver-web ];then + echo "开始下载面板源码(${REPO_OWNER}/${REPO_NAME},分支 ${REPO_BRANCH})..." + curl --insecure -sSLo /tmp/master.tar.gz ${HTTP_PREFIX}github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/heads/${REPO_BRANCH}.tar.gz + TARBALL_DIR=$(tar -tf /tmp/master.tar.gz | head -1 | cut -d/ -f1) + cd /tmp && tar -zxvf /tmp/master.tar.gz + mv -f /tmp/${TARBALL_DIR} /www/server/mdserver-web + rm -rf /tmp/master.tar.gz + rm -rf /tmp/${TARBALL_DIR} + fi + + # install acme.sh + if [ ! -d /root/.acme.sh ];then + if [ "$LOCAL_ADDR" != "common" ];then + curl --insecure -sSLo /tmp/acme.sh-master.tar.gz ${HTTP_PREFIX}github.com/acmesh-official/acme.sh/archive/refs/heads/master.tar.gz + tar xvzf /tmp/acme.sh-master.tar.gz -C /tmp + cd /tmp/acme.sh-master + bash acme.sh install + else + curl -fsSL https://get.acme.sh | bash + fi + fi +fi + +echo "use system version: ${OSNAME}" +if [ "${OSNAME}" == "macos" ];then + curl --insecure -fsSL ${HTTP_PREFIX}raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/refs/heads/${REPO_BRANCH}/scripts/install/macos.sh | bash +else + cd /www/server/mdserver-web && bash scripts/install/${OSNAME}.sh +fi + +if [ "${OSNAME}" == "macos" ];then + echo "macos end" + exit 0 +fi + +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [ ! -f /etc/rc.d/init.d/mw ]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done + +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw stop +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw start +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw default + +sleep 2 +if [ ! -e /usr/bin/mw ]; then + if [ -f /etc/rc.d/init.d/mw ];then + ln -s /etc/rc.d/init.d/mw /usr/bin/mw + fi +fi + +endTime=`date +%s` +((outTime=(${endTime}-${startTime})/60)) +echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" + +} 1> >(tee $LOG_FILE) 2>&1 + +echo -e "\n安装完成啦!如有报错,请带上日志 mw-install.log 来找我们~" +echo "搞定!如果出现问题,请携带安装日志 mw-install.log 反馈哦。" diff --git a/scripts/install/alma.sh b/scripts/install/alma.sh new file mode 100755 index 000000000..016409759 --- /dev/null +++ b/scripts/install/alma.sh @@ -0,0 +1,129 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=C.UTF-8 + + + +setenforce 0 +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +dnf install -y wget lsof +dnf install -y rar unrar +dnf install -y python3-devel +dnf install -y python-devel +dnf install -y crontabs +dnf install -y mysql-devel +dnf install -y pv +yum install -y bzip2 +yum install -y bzip2-devel +yum install -y ncurses-compat-libs +yum install -y numactl +yum install -y sshpass + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +# if [ -f /usr/sbin/iptables ];then + +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 888 -j ACCEPT +# service iptables save + +# iptables_status=`service iptables status | grep 'not running'` +# if [ "${iptables_status}" == '' ];then +# service iptables restart +# fi + +# #安装时不开启 +# service iptables stop +# fi + + +if [ ! -f /usr/sbin/iptables ];then + yum install firewalld -y + systemctl enable firewalld + systemctl start firewalld + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + + sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf + firewall-cmd --reload +fi + + +#安装时不开启 +systemctl stop firewalld + +dnf upgrade -y +dnf autoremove -y + +dnf groupinstall -y "Development Tools" +dnf install -y epel-release +dnf install -y zip unzip + +dnf install -y libevent libevent-devel libmcrypt libmcrypt-devel +dnf install -y wget libicu-devel bzip2-devel gcc libxml2 libxml2-devel libjpeg-devel libpng-devel libwebp libwebp-devel pcre pcre-devel +dnf install -y lsof net-tools +dnf install -y ncurses-devel cmake + +dnf --enablerepo=crb install -y mysql-devel +dnf --enablerepo=crb install -y oniguruma oniguruma-devel +dnf --enablerepo=crb install -y rpcgen +dnf --enablerepo=crb install -y libzip-devel +dnf --enablerepo=crb install -y libmemcached-devel +dnf --enablerepo=crb install -y libtirpc libtirpc-devel +dnf --enablerepo=crb install -y patchelf + +dnf install -y langpacks-zh_CN langpacks-en langpacks-en_GB + + +yum install -y libtirpc libtirpc-devel +yum install -y rpcgen +yum install -y openldap openldap-devel +yum install -y bison re2c cmake +yum install -y cmake3 +yum install -y autoconf +yum install -y expect +yum install -y bc + +yum install -y curl curl-devel +yum install -y zlib zlib-devel +yum install -y libzip libzip-devel +yum install -y pcre pcre-devel +yum install -y icu libicu-devel +yum install -y freetype freetype-devel +yum install -y openssl openssl-devel +yum install -y graphviz libxml2 libxml2-devel +yum install -y sqlite-devel +yum install -y oniguruma oniguruma-devel +yum install -y ImageMagick ImageMagick-devel +yum install -y libargon2-devel + + +for yumPack in make cmake gcc gcc-c++ flex bison file libtool libtool-libs autoconf kernel-devel patch wget gd gd-devel libxml2 libxml2-devel zlib zlib-devel glib2 glib2-devel tar bzip2 bzip2-devel libevent libevent-devel ncurses ncurses-devel curl curl-devel libcurl libcurl-devel e2fsprogs e2fsprogs-devel libidn libidn-devel vim-minimal gettext gettext-devel ncurses-devel gmp-devel libcap diffutils ca-certificates net-tools psmisc libXpm-devel git-core c-ares-devel libicu-devel libxslt libxslt-devel zip unzip glibc.i686 libstdc++.so.6 cairo-devel bison-devel ncurses-devel libaio-devel perl perl-devel perl-Data-Dumper lsof crontabs expat-devel readline-devel; +do dnf --enablerepo=crb install -y $yumPack;done + + +# findLD=`cat /etc/ld.so.conf | grep '/usr/local/lib64'` +# echo "/usr/local/lib64" >> /etc/ld.so.conf + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + diff --git a/scripts/install/alpine.sh b/scripts/install/alpine.sh new file mode 100644 index 000000000..cd1afdd2c --- /dev/null +++ b/scripts/install/alpine.sh @@ -0,0 +1,93 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + +# for debug +apk add htop --force-broken-world +apk add linux-headers --force-broken-world +# for debug end +apk add shadow --force-broken-world +apk add build-base --force-broken-world +apk add openssl openssl-devel --force-broken-world +apk add bison re2c make cmake gcc --force-broken-world +apk add gcc-c++ --force-broken-world +apk add autoconf --force-broken-world +apk add python3-pip --force-broken-world +apk add pcre pcre-devel --force-broken-world +apk add graphviz libxml2 libxml2-devel --force-broken-world +apk add curl curl-devel --force-broken-world +apk add freetype freetype-devel --force-broken-world +apk add mysql-devel --force-broken-world +apk add ImageMagick ImageMagick-devel --force-broken-world +apk add libjpeg-devel libpng-devel --force-broken-world +apk add libevent-devel --force-broken-world +apk add libtirpc-devel --force-broken-world +apk add rpcgen --force-broken-world +apk add libstdc++6 --force-broken-world +apk add expect --force-broken-world +apk add pv --force-broken-world +apk add bc --force-broken-world +apk add bzip2 --force-broken-world + +apk add libzip libzip-devel --force-broken-world +apk add unrar rar --force-broken-world +apk add libmemcached libmemcached-devel --force-broken-world + +apk add icu libicu-devel --force-broken-world +apk add sqlite3 sqlite3-devel --force-broken-world +apk add oniguruma-devel --force-broken-world + +# apk add libmcrypt libmcrypt-devel +# apk add protobuf +# apk add zlib-devel + +apk add python3-devel --force-broken-world +apk add python-devel --force-broken-world + +apk add libwebp-devel --force-broken-world +apk add libtomcrypt --force-broken-world +apk add libtomcrypt-devel --force-broken-world + +apk add libXpm-devel --force-broken-world +apk add freetype2-devel --force-broken-world +apk add libargon2-devel --force-broken-world + +apk add net-tools-deprecated --force-broken-world +apk add numactl --force-broken-world + +# apk add php-config + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +if [ ! -f /usr/sbin/firewalld ];then + apk add firewalld --force-broken-world + which systemctl && systemctl enable firewalld + which systemctl && systemctl start firewalld + + if [ "$SSH_PORT" != "" ];then + which firewall-cmd && firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + which firewall-cmd && firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + + which firewall-cmd && firewall-cmd --permanent --zone=public --add-port=80/tcp + which firewall-cmd && firewall-cmd --permanent --zone=public --add-port=443/tcp + which firewall-cmd && firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + + sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf + which systemctl && firewall-cmd --reload + #安装时不开启 + which systemctl && systemctl stop firewalld +fi + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + diff --git a/scripts/install/amazon.sh b/scripts/install/amazon.sh new file mode 100755 index 000000000..9f3a9b5da --- /dev/null +++ b/scripts/install/amazon.sh @@ -0,0 +1,157 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +if [ ! -f /usr/bin/applydeltarpm ];then + yum -y provides '*/applydeltarpm' + yum -y install deltarpm +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +setenforce 0 +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +yum install -y wget lsof crontabs +yum install -y rar unrar +yum install -y python3-devel +yum install -y python3-pip +yum install -y python-devel +yum install -y curl-devel libmcrypt libmcrypt-devel +yum install -y mysql-devel +yum install -y expect +yum install -y pv +yum install -y bc +yum install -y bzip2 +yum install -y bzip2-devel +yum install -y ncurses-compat-libs +yum install -y numactl +yum install -y sshpass + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +# if [ -f /usr/sbin/iptables ];then + +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 888 -j ACCEPT +# service iptables save + +# iptables_status=`service iptables status | grep 'not running'` +# if [ "${iptables_status}" == '' ];then +# service iptables restart +# fi + +# #安装时不开启 +# service iptables stop +# fi + + +if [ ! -f /usr/sbin/firewalld ];then + yum install firewalld -y + systemctl enable firewalld + #取消服务锁定 + systemctl unmask firewalld + systemctl start firewalld + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + + sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf + firewall-cmd --reload + #安装时不开启 + systemctl stop firewalld +fi + +yum groupinstall -y "Development Tools" +yum install -y epel-release + +yum install -y oniguruma oniguruma-devel +#centos8 stream | use dnf +if [ "$?" != "0" ];then + yum install -y dnf dnf-plugins-core + dnf config-manager --set-enabled powertools + yum install -y oniguruma oniguruma-devel + dnf upgrade -y libmodulemd +fi + + +yum install -y libtirpc libtirpc-devel +yum install -y rpcgen +yum install -y openldap openldap-devel +yum install -y bison re2c +yum install -y cmake3 +yum install -y autoconf +yum install -y make cmake gcc gcc-c++ + +yum install -y libmemcached libmemcached-devel +yum install -y curl curl-devel +yum install -y zlib zlib-devel +yum install -y libzip libzip-devel +yum install -y pcre pcre-devel +yum install -y icu libicu-devel +yum install -y freetype freetype-devel + +yum install -y openssl openssl-devel +yum install -y libargon2-devel + +yum install -y graphviz libxml2 libxml2-devel +yum install -y sqlite-devel +yum install -y oniguruma oniguruma-devel +yum install -y ImageMagick ImageMagick-devel + + +yum install -y libzstd-devel +yum install -y libevent libevent-devel unzip zip +yum install -y python-imaging libicu-devel bzip2-devel pcre pcre-devel + +yum install -y gd gd-devel +yum install -y libjpeg-devel libpng-devel libwebp libwebp-devel + +yum install -y net-tools +yum install -y ncurses-devel + + +for yumPack in flex file libtool libtool-libs kernel-devel patch wget glib2 glib2-devel tar bzip2 bzip2-devel libevent libevent-devel ncurses ncurses-devel curl curl-devel libcurl libcurl-devel e2fsprogs e2fsprogs-devel libidn libidn-devel vim-minimal gettext gettext-devel ncurses-devel gmp-devel libcap diffutils ca-certificates net-tools psmisc libXpm-devel git-core c-ares-devel libicu-devel libxslt libxslt-devel zip unzip glibc.i686 libstdc++.so.6 cairo-devel ncurses-devel libaio-devel perl perl-devel perl-Data-Dumper expat-devel readline-devel; +do yum -y install $yumPack;done + + +if [ "$VERSION_ID" -eq "8" ];then + dnf upgrade -y libmodulemd +fi + +if [ "$VERSION_ID" -eq "9" ];then + yum install -y patchelf + dnf --enablerepo=crb install -y libtirpc-devel + dnf --enablerepo=crb install -y libmemcached libmemcached-devel + dnf --enablerepo=crb install -y libtool libtool-libs + dnf --enablerepo=crb install -y gnutls-devel + dnf --enablerepo=crb install -y mysql-devel + + dnf --enablerepo=crb install -y libvpx-devel libXpm-devel libwebp libwebp-devel + dnf --enablerepo=crb install -y oniguruma oniguruma-devel + dnf --enablerepo=crb install -y libzip libzip-devel + # yum remove -y chardet +fi + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + + + diff --git a/scripts/install/arch.sh b/scripts/install/arch.sh new file mode 100644 index 000000000..5131bfddf --- /dev/null +++ b/scripts/install/arch.sh @@ -0,0 +1,113 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + + +echo y | pacman -Sy yaourt + +echo y | pacman -Sy gcc make cmake autoconf +echo y | pacman -Sy pkg-config +echo y | pacman -Sy unrar +echo y | pacman -Sy rar +echo y | pacman -Sy python3 +echo y | pacman -Sy lsof +echo y | pacman -Sy python-pip +echo y | pacman -Sy curl +echo y | pacman -Sy libevent + +echo y | pacman -Sy libzip +echo y | pacman -Sy libxml2 +echo y | pacman -Sy libtirpc +echo y | pacman -Sy bzip2 + +echo y | pacman -Sy cronie +echo y | pacman -Sy vi +echo y | pacman -Sy openssl +echo y | pacman -Sy pcre +echo y | pacman -Sy libmcrypt +echo y | pacman -Sy oniguruma +echo y | pacman -Sy libmemcached +echo y | pacman -Sy bison re2c +echo y | pacman -Sy graphviz +echo y | pacman -Sy mhash +echo y | pacman -Sy ncurses +echo y | pacman -Sy sqlite +echo y | pacman -Sy libtool +echo y | pacman -Sy imagemagick +echo y | pacman -Sy mariadb-clients +echo y | pacman -Sy rpcsvc-proto +echo y | pacman -Sy lemon +echo y | pacman -Sy which +echo y | pacman -Sy expect +echo y | pacman -Sy pv +echo y | pacman -Sy bc + + +## gd start +echo y | pacman -Sy gd +# echo y | pacman -Sy libgd +echo y | pacman -Sy libjpeg +echo y | pacman -Sy libpng +echo y | pacman -Sy libvpx +echo y | pacman -Sy libwebp +echo y | pacman -Sy libxpm +echo y | pacman -Syu freetype2 +echo y | pacman -Syu sshpass +## gd end + +echo y | pacman -Syu icu + +hwclock --systohc + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +# if [ -f /usr/sbin/iptables ];then + +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 888 -j ACCEPT +# service iptables save + +# iptables_status=`service iptables status | grep 'not running'` +# if [ "${iptables_status}" == '' ];then +# service iptables restart +# fi + +# #安装时不开启 +# service iptables stop +# fi + + +if [ ! -f /usr/sbin/firewalld ];then + echo y | pacman -Sy firewalld + systemctl enable firewalld + systemctl start firewalld + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + + sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf + firewall-cmd --reload + #安装时不开启 + systemctl stop firewalld +fi + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data diff --git a/scripts/install/centos.sh b/scripts/install/centos.sh new file mode 100755 index 000000000..12587a87c --- /dev/null +++ b/scripts/install/centos.sh @@ -0,0 +1,161 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +if [ ! -f /usr/bin/applydeltarpm ];then + yum -y provides '*/applydeltarpm' + yum -y install deltarpm +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +setenforce 0 +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +yum install -y wget lsof crontabs +yum install -y rar unrar +yum install -y python3-devel +yum install -y python3-pip +yum install -y python-devel +yum install -y vixie-cron +yum install -y curl-devel +yum install -y libmcrypt +yum install -y libmcrypt-devel +yum install -y mysql-devel +yum install -y expect +yum install -y pv +yum install -y bc +yum install -y bzip2 +yum install -y bzip2-devel +yum install -y ncurses-compat-libs +yum install -y numactl +yum install -y sshpass + +# if [ -f /usr/sbin/iptables ];then + +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 888 -j ACCEPT +# service iptables save + +# iptables_status=`service iptables status | grep 'not running'` +# if [ "${iptables_status}" == '' ];then +# service iptables restart +# fi + +# #安装时不开启 +# service iptables stop +# fi + + +if [ ! -f /usr/sbin/firewalld ];then + yum install firewalld -y + systemctl enable firewalld + #取消服务锁定 + systemctl unmask firewalld + systemctl start firewalld + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + + + sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf + firewall-cmd --reload + #安装时不开启 + systemctl stop firewalld +fi + +yum groupinstall -y "Development Tools" +yum install -y epel-release + +yum install -y oniguruma oniguruma-devel +#centos8 stream | use dnf +if [ "$?" != "0" ];then + yum install -y dnf dnf-plugins-core + dnf config-manager --set-enabled powertools + yum install -y oniguruma oniguruma-devel + dnf upgrade -y libmodulemd +fi + + +yum install -y libtirpc libtirpc-devel +yum install -y rpcgen +yum install -y openldap openldap-devel +yum install -y bison re2c +yum install -y cmake3 +yum install -y autoconf +yum install -y make cmake gcc gcc-c++ + +yum install -y libmemcached libmemcached-devel +yum install -y curl curl-devel +yum install -y zlib zlib-devel +yum install -y libzip libzip-devel +yum install -y pcre pcre-devel +yum install -y icu libicu-devel +yum install -y freetype freetype-devel +yum install -y openssl openssl-devel +yum install -y libxml2 libxml2-devel +yum install -y graphviz +yum install -y sqlite-devel +yum install -y oniguruma oniguruma-devel +yum install -y ImageMagick ImageMagick-devel + + +yum install -y libzstd-devel +yum install -y libevent libevent-devel unzip zip +# yum install -y python-imaging +yum install -y libicu-devel bzip2-devel pcre pcre-devel + +yum install -y gd gd-devel +yum install -y libwebp-tools +yum install -y libjpeg-devel libpng-devel libwebp libwebp-devel +yum install -y net-tools +yum install -y ncurses-devel +yum install -y libXpm-devel +yum install -y libargon2-devel + + +for yumPack in flex file libtool libtool-libs kernel-devel patch wget glib2 glib2-devel tar bzip2 bzip2-devel libevent libevent-devel ncurses ncurses-devel curl curl-devel libcurl libcurl-devel e2fsprogs e2fsprogs-devel libidn libidn-devel vim-minimal gettext gettext-devel ncurses-devel gmp-devel libcap diffutils ca-certificates net-tools psmisc git-core c-ares-devel libicu-devel libxslt libxslt-devel zip unzip glibc.i686 libstdc++.so.6 cairo-devel ncurses-devel libaio-devel perl perl-devel perl-Data-Dumper expat-devel readline-devel; +do yum -y install $yumPack;done + + +if [ "$VERSION_ID" -eq "8" ];then + dnf upgrade -y libmodulemd +fi + +if [ "$VERSION_ID" -eq "9" ];then + yum install -y patchelf + dnf --enablerepo=crb install -y libtirpc-devel + dnf --enablerepo=crb install -y libmemcached libmemcached-devel + dnf --enablerepo=crb install -y libtool libtool-libs + dnf --enablerepo=crb install -y gnutls-devel + dnf --enablerepo=crb install -y mysql-devel + + dnf --enablerepo=crb install -y libvpx-devel libXpm-devel libwebp libwebp-devel + dnf --enablerepo=crb install -y libjpeg-devel libpng-devel + dnf --enablerepo=crb install -y oniguruma oniguruma-devel + dnf --enablerepo=crb install -y libzip libzip-devel + # yum remove -y chardet +fi + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + diff --git a/scripts/install/debian.sh b/scripts/install/debian.sh new file mode 100644 index 000000000..ad2081da6 --- /dev/null +++ b/scripts/install/debian.sh @@ -0,0 +1,290 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +export LANG=en_US.UTF-8 +export DEBIAN_FRONTEND=noninteractive + +function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } +function version_le() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" == "$1"; } +function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" != "$1"; } +function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") + +ln -sf /bin/bash /bin/sh + +__GET_BIT=`getconf LONG_BIT` +if [ "$__GET_BIT" == "32" ];then + # install rust | 32bit need + # curl https://sh.rustup.rs -sSf | sh + apt install -y rustc +fi + +if [ "$VERSION_ID" == "10" ];then + apt install -y rustc +fi + + +# synchronize server +apt install chrony -y + +# synchronize time first +apt install ntpdate -y +# NTPHOST='time.nist.gov' +# if [ ! -z "$cn" ];then +# NTPHOST='ntp1.aliyun.com' +# fi +# ntpdate ntp1.aliyun.com | logger -t NTP +# ntpdate $NTPHOST | logger -t NTP + +apt install -y net-tools + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + + + +# choose lang cmd +# dpkg-reconfigure --frontend=noninteractive locales +# dpkg-reconfigure locales +if [ ! -f /usr/sbin/locale-gen ];then + apt install -y locales + sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen + locale-gen en_US.UTF-8 + locale-gen zh_CN.UTF-8 + localedef -v -c -i en_US -f UTF-8 en_US.UTF-8 > /dev/null 2>&1 + update-locale LANG=en_US.UTF-8 +else + locale-gen en_US.UTF-8 + locale-gen zh_CN.UTF-8 + localedef -v -c -i en_US -f UTF-8 en_US.UTF-8 > /dev/null 2>&1 +fi + +apt update -y +apt autoremove -y + +apt install -y wget curl lsof unzip tar cron expect locate lrzsz +apt install -y xz-utils +apt install -y rar +apt install -y unrar +apt install -y pv +apt install -y bc +apt install -y python3-pip +apt install -y python3-dev +apt install -y python3-venv +apt install -y libncurses5 +apt install -y libncurses5-dev +apt install -y bzip2 +apt install -y p7zip-full + +apt install -y libnuma1 +apt install -y libaio1 +apt install -y libaio-dev +apt install -y libmecab2 +apt install -y libmm-dev + +apt install -y dnsutils +apt install -y apache2-utils +apt install -y numactl +apt install -y xxd +apt install -y sshpass + +P_VER=`python3 -V | awk '{print $2}'` +if version_ge "$P_VER" "3.11.0" ;then + echo -e "\e[1;31mapt install python3.12-venv\e[0m" + apt install -y python3.12-venv +fi + + +if [ -f /usr/sbin/ufw ];then + # look + # ufw status + ufw enable + if [ "$SSH_PORT" != "" ];then + ufw allow $SSH_PORT/tcp + else + ufw allow 22/tcp + fi + + ufw allow 80/tcp + ufw allow 443/tcp + ufw allow 443/udp + # ufw allow 888/tcp +fi + +if [ ! -f /usr/sbin/ufw ];then + # look + # firewall-cmd --list-all + # apt remove -y firewalld + + apt install -y firewalld + systemctl enable firewalld + #取消服务锁定 + systemctl unmask firewalld + + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + + systemctl start firewalld + + # fix:debian10 firewalld faq + # https://kawsing.gitbook.io/opensystem/andoid-shou-ji/untitled/fang-huo-qiang#debian-10-firewalld-0.6.3-error-commandfailed-usrsbinip6tablesrestorewn-failed-ip6tablesrestore-v1.8 + sed -i 's#IndividualCalls=no#IndividualCalls=yes#g' /etc/firewalld/firewalld.conf + + firewall-cmd --reload +fi + +#fix zlib1g-dev fail +echo -e "\e[0;32mfix zlib1g-dev install question start\e[0m" +Install_TmpFile=/tmp/debian-fix-zlib1g-dev.txt +apt install -y zlib1g-dev > ${Install_TmpFile} +if [ "$?" != "0" ];then + ZLIB1G_BASE_VER=$(cat ${Install_TmpFile} | grep zlib1g | awk -F "=" '{print $2}' | awk -F ")" '{print $1}') + ZLIB1G_BASE_VER=`echo ${ZLIB1G_BASE_VER} | sed "s/^[ \s]\{1,\}//g;s/[ \s]\{1,\}$//g"` + # echo "1${ZLIB1G_BASE_VER}1" + echo -e "\e[1;31mapt install zlib1g=${ZLIB1G_BASE_VER} zlib1g-dev\e[0m" + echo "Y" | apt install zlib1g=${ZLIB1G_BASE_VER} zlib1g-dev +fi +rm -rf ${Install_TmpFile} +echo -e "\e[0;32mfix zlib1g-dev install question end\e[0m" + + +#fix libunwind-dev fail +echo -e "\e[0;32mfix libunwind-dev install question start\e[0m" +Install_TmpFile=/tmp/debian-fix-libunwind-dev.txt +apt install -y libunwind-dev > ${Install_TmpFile} +if [ "$?" != "0" ];then + liblzma5_BASE_VER=$(cat ${Install_TmpFile} | grep liblzma-dev | awk -F "=" '{print $2}' | awk -F ")" '{print $1}') + liblzma5_BASE_VER=`echo ${liblzma5_BASE_VER} | sed "s/^[ \s]\{1,\}//g;s/[ \s]\{1,\}$//g"` + echo -e "\e[1;31mapt install liblzma5=${liblzma5_BASE_VER} libunwind-dev\e[0m" + echo "Y" | apt install liblzma5=${liblzma5_BASE_VER} libunwind-dev +fi +rm -rf ${Install_TmpFile} +echo -e "\e[0;32mfix libunwind-dev install question end\e[0m" + + +apt install -y libvpx-dev +apt install -y libxpm-dev +apt install -y libwebp-dev +apt install -y libfreetype6 +apt install -y libfreetype6-dev +apt install -y libjpeg-dev +apt install -y libpng-dev + +localedef -i en_US -f UTF-8 en_US.UTF-8 + +if [ "$VERSION_ID" == "9" ];then + sed "s/flask==2.0.3/flask==1.1.1/g" -i /www/server/mdserver-web/requirements.txt + sed "s/cryptography==3.3.2/cryptography==2.5/g" -i /www/server/mdserver-web/requirements.txt + sed "s/configparser==5.2.0/configparser==4.0.2/g" -i /www/server/mdserver-web/requirements.txt + sed "s/flask-socketio==5.2.0/flask-socketio==4.2.0/g" -i /www/server/mdserver-web/requirements.txt + sed "s/python-engineio==4.3.2/python-engineio==3.9.0/g" -i /www/server/mdserver-web/requirements.txt + # pip3 install -r /www/server/mdserver-web/requirements.txt +fi + +apt install -y build-essential +apt install -y devscripts + +apt install -y autoconf +apt install -y gcc +apt install -y patchelf + +apt install -y libffi-dev +apt install -y cmake +apt install -y automake make + +apt install -y webp scons +apt install -y libwebp-dev +apt install -y lzma lzma-dev +apt install -y libunwind-dev + +apt install -y libpcre3 libpcre3-dev +apt install -y openssl +apt install -y libssl-dev +apt install -y libargon2-dev + +apt install -y libmemcached-dev +apt install -y libsasl2-dev +apt install -y imagemagick +apt install -y libmagickcore-dev +apt install -y libmagickwand-dev + +apt install -y libxml2 libxml2-dev libbz2-dev libmcrypt-dev libpspell-dev librecode-dev +apt install -y libgmp-dev libgmp3-dev libreadline-dev libxpm-dev +apt install -y libpq-dev +apt install -y dia + +apt install -y pkg-config +apt install -y zlib1g-dev + +apt install -y libevent-dev libldap2-dev +apt install -y libzip-dev +apt install -y libicu-dev +apt install -y libyaml-dev + +apt install -y xsltproc + +apt install -y libcurl4-openssl-dev +apt install -y curl libcurl4-gnutls-dev + +# https://www.php.net/manual/zh/mysql-xdevapi.installation.php +apt install -y libprotobuf-dev +apt install -y protobuf-compiler +apt install -y libboost-dev +apt install -y liblz4-tool +apt install -y zstd + + +# Disabled due to dependency issues +#apt install --ignore-missing -y autoconf automake cmake curl dia gcc imagemagick libbz2-dev libcurl4-gnutls-dev\ +# libcurl4-openssl-dev libevent-dev libffi-dev libfreetype6 libfreetype6-dev libgmp-dev libgmp3-dev libicu-dev \ +# libjpeg-dev libldap2-dev libmagickwand-dev libmcrypt-dev libmemcached-dev libncurses5-dev \ +# libpcre3 libpcre3-dev libpng-dev libpspell-dev libreadline-dev librecode-dev libsasl2-dev libssl-dev \ +# libunwind-dev libwebp-dev libxml2 libxml2-dev libxpm-dev libzip-dev lzma lzma-dev make net-tools openssl \ +# pkg-config python3-dev scons webp zlib1g-dev + +if [ "$VERSION_ID" != "9" ]; then + apt install -y libjpeg62-turbo-dev +fi + +#https://blog.csdn.net/qq_36228377/article/details/123154344 +# ln -s /usr/include/x86_64-linux-gnu/curl /usr/include/curl +if [ ! -d /usr/include/curl ];then + SYS_ARCH=`arch` + if [ -f /usr/include/x86_64-linux-gnu/curl ];then + ln -s /usr/include/x86_64-linux-gnu/curl /usr/include/curl + else + ln -s /usr/include/${SYS_ARCH}-linux-gnu/curl /usr/include/curl + fi +fi + +apt install -y graphviz bison re2c flex libsqlite3-dev libonig-dev perl g++ libtool libxslt1-dev +apt install -y libmariadb-dev libmariadb-dev-compat + +#apt install -y libmysqlclient-dev +#apt install -y libmariadbclient-dev + + +# find /usr/lib -name "*libaio*" 2>/dev/null +if [ ! -f /usr/lib/libaio.so.1 ];then + if [ -f /usr/lib/x86_64-linux-gnu/libaio.so.1t64 ];then + ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/libaio.so.1 + fi +fi + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data \ No newline at end of file diff --git a/scripts/install/euler.sh b/scripts/install/euler.sh new file mode 100755 index 000000000..36adf21ee --- /dev/null +++ b/scripts/install/euler.sh @@ -0,0 +1,161 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +if [ ! -f /usr/bin/applydeltarpm ];then + yum -y provides '*/applydeltarpm' + yum -y install deltarpm +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +setenforce 0 +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +yum install -y wget lsof crontabs +yum install -y rar unrar +yum install -y python3-devel +yum install -y python3-pip +yum install -y python-devel +yum install -y vixie-cron +yum install -y curl-devel +yum install -y libmcrypt +yum install -y libmcrypt-devel +yum install -y mysql-devel +yum install -y expect +yum install -y pv +yum install -y bc +yum install -y bzip2 +yum install -y bzip2-devel +yum install -y ncurses-compat-libs +yum install -y numactl +yum install -y sshpass + + +# if [ -f /usr/sbin/iptables ];then + +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 888 -j ACCEPT +# service iptables save + +# iptables_status=`service iptables status | grep 'not running'` +# if [ "${iptables_status}" == '' ];then +# service iptables restart +# fi + +# #安装时不开启 +# service iptables stop +# fi + +if [ ! -f /usr/sbin/firewalld ];then + yum install firewalld -y + systemctl enable firewalld + #取消服务锁定 + systemctl unmask firewalld + systemctl start firewalld + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + + + sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf + firewall-cmd --reload + #安装时不开启 + systemctl stop firewalld +fi + +yum groupinstall -y "Development Tools" +yum install -y epel-release + +yum install -y oniguruma oniguruma-devel +#centos8 stream | use dnf +if [ "$?" != "0" ];then + yum install -y dnf dnf-plugins-core + dnf config-manager --set-enabled powertools + yum install -y oniguruma oniguruma-devel + dnf upgrade -y libmodulemd +fi + + +yum install -y libtirpc libtirpc-devel +yum install -y rpcgen +yum install -y openldap openldap-devel +yum install -y bison re2c +yum install -y cmake3 +yum install -y autoconf +yum install -y make cmake gcc gcc-c++ + +yum install -y libmemcached libmemcached-devel +yum install -y curl curl-devel +yum install -y zlib zlib-devel +yum install -y libzip libzip-devel +yum install -y pcre pcre-devel +yum install -y icu libicu-devel +yum install -y freetype freetype-devel +yum install -y openssl openssl-devel +yum install -y libxml2 libxml2-devel +yum install -y graphviz +yum install -y sqlite-devel +yum install -y oniguruma oniguruma-devel +yum install -y ImageMagick ImageMagick-devel + + +yum install -y libzstd-devel +yum install -y libevent libevent-devel unzip zip +# yum install -y python-imaging +yum install -y libicu-devel bzip2-devel pcre pcre-devel + +yum install -y gd gd-devel +yum install -y libjpeg-devel libpng-devel libwebp libwebp-devel + +yum install -y net-tools +yum install -y ncurses-devel +yum install -y libXpm-devel +yum install -y libargon2-devel + + +for yumPack in flex file libtool libtool-libs kernel-devel patch wget glib2 glib2-devel tar bzip2 bzip2-devel libevent libevent-devel ncurses ncurses-devel curl curl-devel libcurl libcurl-devel e2fsprogs e2fsprogs-devel libidn libidn-devel vim-minimal gettext gettext-devel ncurses-devel gmp-devel libcap diffutils ca-certificates net-tools psmisc git-core c-ares-devel libicu-devel libxslt libxslt-devel zip unzip glibc.i686 libstdc++.so.6 cairo-devel ncurses-devel libaio-devel perl perl-devel perl-Data-Dumper expat-devel readline-devel; +do yum -y install $yumPack;done + + +if [ "$VERSION_ID" -eq "8" ];then + dnf upgrade -y libmodulemd +fi + +if [ "$VERSION_ID" -eq "9" ];then + yum install -y patchelf + dnf --enablerepo=crb install -y libtirpc-devel + dnf --enablerepo=crb install -y libmemcached libmemcached-devel + dnf --enablerepo=crb install -y libtool libtool-libs + dnf --enablerepo=crb install -y gnutls-devel + dnf --enablerepo=crb install -y mysql-devel + + dnf --enablerepo=crb install -y libvpx-devel libXpm-devel libwebp libwebp-devel + dnf --enablerepo=crb install -y libjpeg-devel libpng-devel + dnf --enablerepo=crb install -y oniguruma oniguruma-devel + dnf --enablerepo=crb install -y libzip libzip-devel + # yum remove -y chardet +fi + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + diff --git a/scripts/install/fedora.sh b/scripts/install/fedora.sh new file mode 100644 index 000000000..235d9b037 --- /dev/null +++ b/scripts/install/fedora.sh @@ -0,0 +1,123 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + +if [ ! -f /usr/bin/applydeltarpm ];then + yum -y provides '*/applydeltarpm' + yum -y install deltarpm +fi + + +setenforce 0 +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +yum install -y wget curl lsof unzip +yum install -y expect +yum install -y ncurses-compat-libs +yum install -y numactl +yum install -y sshpass +dnf install crontabs -y + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +# if [ -f /usr/sbin/iptables ];then + +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 888 -j ACCEPT +# # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 7200 -j ACCEPT +# # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -j ACCEPT +# # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 30000:40000 -j ACCEPT +# service iptables save + +# iptables_status=`service iptables status | grep 'not running'` +# if [ "${iptables_status}" == '' ];then +# service iptables restart +# fi + +# #安装时不开启 +# service iptables stop +# fi + + +if [ ! -f /usr/sbin/iptables ];then + yum install firewalld -y + systemctl enable firewalld + systemctl start firewalld + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + firewall-cmd --reload +fi + + +#安装时不开启 +systemctl stop firewalld + + +yum groupinstall -y "Development Tools" +yum install -y epel-release + +yum install -y libevent libevent-devel zip libmcrypt libmcrypt-devel +yum install -y rar unrar +yum install -y pv +yum install -y bc +yum install -y gcc libffi-devel python-devel openssl-devel +yum install -y libmcrypt libmcrypt-devel python3-devel + +yum install -y wget python-devel python-imaging libicu-devel unzip gcc libxml2 libxml2-devel libjpeg-devel libpng-devel libwebp libwebp-devel pcre pcre-devel crontabs +yum install -y net-tools +yum install -y ncurses-devel +yum install -y python-devel +yum install -y MySQL-python +yum install -y python3-devel +yum install -y mysql-devel + +yum install -y bzip2 +yum install -y bzip2-devel + +yum install -y libtirpc libtirpc-devel +yum install -y rpcgen +yum install -y openldap openldap-devel +yum install -y bison re2c +yum install -y cmake cmake3 +yum install -y autoconf +yum install -y libargon2-devel + +yum install -y libmemcached libmemcached-devel +yum install -y curl curl-devel +yum install -y zlib zlib-devel +yum install -y libzip libzip-devel +yum install -y pcre pcre-devel +yum install -y icu libicu-devel +yum install -y freetype freetype-devel +yum install -y openssl openssl-devel +yum install -y graphviz libxml2 libxml2-devel +yum install -y sqlite-devel +yum install -y oniguruma oniguruma-devel +yum install -y ImageMagick ImageMagick-devel + +for yumPack in make cmake gcc gcc-c++ gcc-g77 flex bison file libtool libtool-libs autoconf kernel-devel patch wget libjpeg libjpeg-devel libpng libpng-devel gd gd-devel libxml2 libxml2-devel zlib zlib-devel glib2 glib2-devel tar bzip2 bzip2-devel libevent libevent-devel ncurses ncurses-devel curl curl-devel libcurl libcurl-devel e2fsprogs e2fsprogs-devel krb5 krb5-devel libidn libidn-devel vim-minimal gettext gettext-devel ncurses-devel gmp-devel pspell-devel libcap diffutils ca-certificates net-tools libc-client-devel psmisc libXpm-devel git-core c-ares-devel libicu-devel libxslt libxslt-devel zip unzip glibc.i686 libstdc++.so.6 cairo-devel bison-devel ncurses-devel libaio-devel perl perl-devel perl-Data-Dumper crontabs expat-devel readline-devel; +do yum -y install $yumPack;done + +dnf install libxml2 libxml2-devel -y + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + diff --git a/scripts/install/freebsd.sh b/scripts/install/freebsd.sh new file mode 100644 index 000000000..a2dbb9530 --- /dev/null +++ b/scripts/install/freebsd.sh @@ -0,0 +1,97 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +# -- debug start -- + +# https://www.freebsd.org/where/ + +# 手动升级到,可解决库找不到的问题。 +# freebsd-update -r 13.2-RELEASE upgrade +# freebsd-update -r 14-RELEASE upgrade + +# pkg install -y python39 +# python3 -m ensurepip +# pip3 install --upgrade setuptools +# python3 -m pip install --upgrade pip + +# echo "y" | pkg upgrade + +# -- debug end -- + +if grep -Eq "FreeBSD" /etc/*-release && [ ! -f /bin/bash ]; then + ln -sf /usr/local/bin/bash /bin/bash +fi + + +echo "y" | pkg update +echo "y" | pkg bootstrap -f +echo "y" | freebsd-update install + +pkg install -y python3 +pkg install -y py39-pip + +pkg install -y lsof +pkg install -y rar +pkg install -y unrar +pkg install -y vim +pkg install -y sqlite3 + +pkg install -y py39-sqlite3 + +pkg install -y gcc +pkg install -y autoconf +pkg install -y make +pkg install -y gmake +pkg install -y cmake +pkg install -y libxslt +pkg install -y libunwind +pkg install -y influxpkg-config +pkg install -y expect + +pkg install -y pcre +pkg install -y libmemcached +pkg install -y webp +pkg install -y freetype +pkg install -y oniguruma +pkg install -y brotli +pkg install -y harfbuzz +pkg install -y libevent +pkg install -y pidof +pkg install -y pstree +pkg install -y pv +pkg install -y bc +pkg install -y bzip2 +pkg install -y bzip2-devel +pkg install -y numactl +pkg install -y sshpass + +# curl https://sh.rustup.rs -sSf | sh +pkg install -y rust + +pkg autoremove -y + + +SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep -E "Port d*" | tail -1` +SSH_PORT=${SSH_PORT_LINE/"Port "/""} + +echo "SSH PORT:${SSH_PORT}" + +# 检测防火墙是否开启 +FW_ENABLE=`cat /etc/rc.conf | grep firewall_enable` +if [ "$FW_ENABLE" == "" ];then + sysrc firewall_enable="YES" + sysrc firewall_type="open" + sysrc firewall_script="/etc/ipfw.rules" + sysrc firewall_logging="YES" + sysrc firewall_logif="YES" +fi + +# ipfw list +service ipfw stop + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + diff --git a/scripts/install/macos.sh b/scripts/install/macos.sh new file mode 100755 index 000000000..42a5c3983 --- /dev/null +++ b/scripts/install/macos.sh @@ -0,0 +1,60 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +USER=$(who | sed -n "2,1p" |awk '{print $1}') +DEV="/Users/${USER}/Desktop/mwdev" + + +mkdir -p $DEV +mkdir -p $DEV/wwwroot +mkdir -p $DEV/server +mkdir -p $DEV/wwwlogs +mkdir -p $DEV/backup/database +mkdir -p $DEV/backup/site + +# install brew +which brew +if [ "$?" != "0" ];then + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + brew install python@2 + brew install mysql +fi + +brew install pv +brew install libzip bzip2 gcc openssl re2c cmake +brew install librdkafka +brew install coreutils libxml2 xml2 +brew install md5sum libevent pidof bison +brew install pcre2 libxpm libelf +brew install automake icu4c libmemcached + +if [ ! -d $DEV/server/mdserver-web ]; then + wget -O /tmp/master.zip https://codeload.github.com/midoks/mdserver-web/zip/master + cd /tmp && unzip /tmp/master.zip + mv /tmp/mdserver-web-master $DEV/server/mdserver-web + rm -f /tmp/master.zip + rm -rf /tmp/mdserver-web-master +fi + +if [ ! -d $DEV/server/lib ]; then + cd $DEV/server/mdserver-web/scripts && bash lib.sh +fi + +chmod 755 $DEV/server/mdserver-web/data +if [ -f $DEV/server/mdserver-web/bin/activate ];then + cd $DEV/server/mdserver-web && python3 -m venv $DEV/server/mdserver-web + source $DEV/server/mdserver-web/bin/activate + pip3 install -r $DEV/server/mdserver-web/requirements.txt +else + cd $DEV/server/mdserver-web && pip3 install -r $DEV/server/mdserver-web/requirements.txt +fi + + +cd $DEV/server/mdserver-web && ./cli.sh start +cd $DEV/server/mdserver-web && ./cli.sh stop + +sleep 5 +cd $DEV/server/mdserver-web && ./scripts/init.d/mw default +cd $DEV/server/mdserver-web && ./cli.sh debug \ No newline at end of file diff --git a/scripts/install/opensuse.sh b/scripts/install/opensuse.sh new file mode 100644 index 000000000..0358ed75b --- /dev/null +++ b/scripts/install/opensuse.sh @@ -0,0 +1,96 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +# zypper refresh + + +# systemctl stop SuSEfirewall2 + +# for debug +zypper install -y htop +# for debug end + +zypper install -y openssl openssl-devel +zypper install -y bison re2c make cmake gcc +zypper install -y gcc-c++ +zypper install -y autoconf +zypper install -y python3-pip +zypper install -y pcre pcre-devel +zypper install -y graphviz libxml2 libxml2-devel +zypper install -y curl curl-devel +zypper install -y freetype freetype-devel +zypper install -y mysql-devel +zypper install -y ImageMagick ImageMagick-devel +zypper install -y libjpeg-devel libpng-devel +zypper install -y libevent-devel +zypper install -y libtirpc-devel +zypper install -y rpcgen +zypper install -y libstdc++6 +zypper install -y expect +zypper install -y pv +zypper install -y bc +zypper install -y bzip2 + +zypper install -y libzip libzip-devel +zypper install -y unrar rar +zypper install -y libmemcached libmemcached-devel + +zypper install -y icu libicu-devel +zypper install -y sqlite3 sqlite3-devel +zypper install -y oniguruma-devel + +# zypper install -y libmcrypt libmcrypt-devel +# zypper install -y protobuf +# zypper install -y zlib-devel + +zypper install -y python3-devel +zypper install -y python-devel + +zypper install -y libwebp-devel +zypper install -y libtomcrypt +zypper install -y libtomcrypt-devel + +zypper install -y libXpm-devel +zypper install -y freetype2-devel +zypper install -y libargon2-devel + +zypper install -y net-tools-deprecated +zypper install -y numactl +zypper install -y sshpass + +# zypper install -y php-config + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +if [ ! -f /usr/sbin/firewalld ];then + zypper install -y firewalld + systemctl enable firewalld + systemctl start firewalld + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + + sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf + firewall-cmd --reload + #安装时不开启 + systemctl stop firewalld +fi + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + diff --git a/scripts/install/rhel.sh b/scripts/install/rhel.sh new file mode 100644 index 000000000..dfab7acf2 --- /dev/null +++ b/scripts/install/rhel.sh @@ -0,0 +1,235 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +export LANG=en_US.UTF-8 +SYS_ARCH=`arch` + +if [ ! -f /usr/bin/applydeltarpm ];then + yum -y provides '*/applydeltarpm' + yum -y install deltarpm +fi + +setenforce 0 +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +VERSION_ID=`grep -o -i 'release *[[:digit:]]\+\.*' /etc/redhat-release | grep -o '[[:digit:]]\+' ` + + +isStream=$(grep -o -i 'stream' /etc/redhat-release) + +cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") + +yum -y update +# CentOS Stream +if [ ! -z "$stream" ];then + yum install -y dnf dnf-plugins-core + dnf upgrade -y libmodulemd +fi + +PKGMGR='yum' +if [ $VERSION_ID -ge 8 ];then + PKGMGR='dnf' +fi + +# systemctl status chronyd -l +$PKGMGR install -y chrony + +$PKGMGR install -y curl-devel libmcrypt libmcrypt-devel python3-devel +$PKGMGR install -y net-tools +$PKGMGR install -y unixODBC-devel + +$PKGMGR install -y p7zip +$PKGMGR install -y p7zip-plugins +$PKGMGR install -y mmap-devel + +$PKGMGR install -y libncurses* +$PKGMGR install -y sshpass + +echo "install remi source" +if [ "$VERSION_ID" == "9" ];then + # dnf upgrade --refresh -y + dnf config-manager --set-enabled crb + + echo "install remi start" + if [ ! -f /etc/yum.repos.d/remi.repo ];then + rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-9.rpm + rpm --import http://rpms.famillecollet.com/RPM-GPG-KEY-remi + fi + echo "install remi end" +fi + +#https need +if [ ! -d /root/.acme.sh ];then + curl https://get.acme.sh | sh +fi + + + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +# redhat , iptables no default +# echo "iptables wrap start" +# if [ -f /usr/sbin/iptables ];then +# $PKGMGR install -y iptables-services + +# # iptables -nL --line-number + +# echo "iptables start" +# iptables_status=`systemctl status iptables | grep 'inactive'` +# if [ "${iptables_status}" != '' ];then +# service iptables restart + +# # iptables -P FORWARD DROP +# iptables -P INPUT DROP +# iptables -P OUTPUT ACCEPT +# iptables -A INPUT -p tcp -s 127.0.0.1 -j ACCEPT + +# if [ "$SSH_PORT" != "" ];then +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ${SSH_PORT} -j ACCEPT +# else +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT +# fi + +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT +# iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 888 -j ACCEPT +# # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 7200 -j ACCEPT +# # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -j ACCEPT +# # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 30000:40000 -j ACCEPT +# service iptables save +# fi + +# # 安装时不开启 +# # stop之后清空了所有规则,所以安装是不能stop. +# # 要在代码修复这个问题,开启时,重新执行一下放行端口。 +# #service iptables stop + +# echo "iptables end" +# fi +# echo "iptables wrap start" + + +echo "firewall open common port start" +if [ ! -f /usr/sbin/firewalld ];then + $PKGMGR install firewalld -y + systemctl enable firewalld + #取消服务锁定 + systemctl unmask firewalld + systemctl start firewalld + + sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf + firewall-cmd --reload + + #安装就开启 + systemctl restart firewalld +fi + +if [ -f /usr/sbin/firewalld ];then + # look + # firewall-cmd --list-all + # systemctl status firewalld + # firewall-cmd --zone=public --remove-port=80/tcp --permanent + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + # firewall-cmd --permanent --zone=public --add-port=7200/tcp + # firewall-cmd --permanent --zone=public --add-port=3306/tcp + # firewall-cmd --permanent --zone=public --add-port=30000-40000/tcp + + firewall-cmd --reload + +fi +echo "firewall open common port end" + +$PKGMGR install -y epel-release +if [ ! -z "$cn" ];then + sed -e 's|^metalink=|#metalink=|g' \ + -e 's|^#baseurl=|baseurl=|g' \ + -e 's|//download\.fedoraproject\.org/pub|//mirrors.tuna.tsinghua.edu.cn|g' \ + -e 's|//download\.example/pub|//mirrors.tuna.tsinghua.edu.cn|g' \ + -i.bak /etc/yum.repos.d/epel*.repo +fi +$PKGMGR makecache +$PKGMGR groupinstall -y "Development Tools" + +if [ "$VERSION_ID" -ge "8" ];then + # EL8 及以上 + + # find repo + + REPO_LIST=(remi appstream baseos epel extras crb powertools) + REPOS='--enablerepo=' + for REPO_VAR in ${REPO_LIST[@]} + do + if [ -f /etc/yum.repos.d/${REPO_VAR}.repo ];then + REPOS="${REPOS},${REPO_VAR}" + fi + done + + if [ "$REPOS" == "--enablerepo=" ];then + # if not find, reset emtpy + REPOS='' + fi + + REPOS=${REPOS//=,/=} + echo "REPOS:${REPOS}" + + # if [ $VERSION_ID -ge 9 ];then + # REPOS='--enablerepo=remi,appstream,baseos,epel,extras,crb' + # else + # REPOS='--enablerepo=remi,appstream,baseos,epel,extras,powertools' + # fi + for rpms in gcc gcc-c++ lsof autoconf bzip2 bzip2-devel c-ares-devel \ + ca-certificates cairo-devel cmake crontabs curl curl-devel diffutils e2fsprogs e2fsprogs-devel \ + expat-devel expect file flex gd gd-devel gettext gettext-devel glib2 glib2-devel glibc.i686 \ + gmp-devel kernel-devel libXpm-devel libaio-devel libcap libcurl libcurl-devel libevent libevent-devel \ + libicu-devel libidn libidn-devel libmcrypt libmcrypt-devel libmemcached libmemcached-devel \ + libpng libpng-devel libstdc++.so.6 libtirpc libtirpc-devel libtool libtool-libs libwebp libwebp-devel \ + libxml2 libxml2-devel libxslt libxslt-devel libarchive make mysql-devel ncurses ncurses-devel net-tools \ + oniguruma oniguruma-devel patch pcre pcre-devel perl perl-Data-Dumper perl-devel procps psmisc python3-devel \ + openssl openssl-devel patchelf libargon2-devel \ + ImageMagick ImageMagick-devel libyaml-devel \ + pv bc bind-utils \ + ncurses-compat-libs numactl \ + readline-devel rpcgen sqlite-devel rar unrar tar unzip vim-minimal wget zip zlib zlib-devel; + do + # dnf --enablerepo=remi,appstream,baseos,epel,extras,powertools install -y oniguruma5php-devel + dnf $REPOS install -y $rpms; + if [ "$?" != "0" ];then + dnf install -y $rpms; + fi + done +else + # CentOS 7 + for rpms in gcc gcc-c++ lsof autoconf bison bzip2 bzip2-devel c-ares-devel ca-certificates cairo-devel \ + cmake cmake3 crontabs curl curl-devel diffutils e2fsprogs e2fsprogs-devel expat-devel expect file \ + flex freetype freetype-devel gd gd-devel gettext gettext-devel git-core glib2 glib2-devel \ + glibc.i686 gmp-devel graphviz icu kernel-devel libXpm-devel libaio-devel libcap libcurl libcurl-devel \ + libevent libevent-devel libicu-devel libidn libidn-devel libjpeg-devel libmcrypt libmcrypt-devel \ + libmemcached libmemcached-devel libpng-devel libstdc++.so.6 libtirpc libtirpc-devel libtool libtool-libs \ + libwebp libwebp-devel libxml2 libxml2-devel libxslt libxslt-devel libzip libzip-devel libzstd-devel \ + make mysql-devel ncurses ncurses-devel net-tools oniguruma oniguruma-devel openldap openldap-devel \ + openssl openssl-devel patch pcre pcre-devel perl perl-Data-Dumper perl-devel psmisc python-devel \ + pv bc bind-utils \ + ncurses-compat-libs numactl \ + python3-devel python3-pip re2c readline-devel rpcgen sqlite-devel tar unzip rar unrar vim-minimal vixie-cron \ + wget zip zlib zlib-devel ImageMagick ImageMagick-devel libyaml-devel patchelf libargon2-devel; + do + yum install -y $rpms; + done +fi + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data diff --git a/scripts/install/rocky.sh b/scripts/install/rocky.sh new file mode 100644 index 000000000..d5b2e15c8 --- /dev/null +++ b/scripts/install/rocky.sh @@ -0,0 +1,108 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + +if [ ! -f /usr/bin/applydeltarpm ];then + yum -y provides '*/applydeltarpm' + yum -y install deltarpm +fi + + +setenforce 0 +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +yum install -y wget lsof +yum install -y unrar rar +yum install -y pv +yum install -y bc +yum install -y python3-devel +yum install -y crontabs +yum install -y expect +yum install -y curl curl-devel libcurl libcurl-devel +yum install -y bzip2 +yum install -y bzip2-devel +yum install -y libzip-devel +yum install -y re2c +yum install -y ncurses-compat-libs +yum install -y numactl +apt install -y sshpass + +if [ -f /usr/sbin/iptables ];then + + iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT + iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT + iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT + # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 888 -j ACCEPT + # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 7200 -j ACCEPT + # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -j ACCEPT + # iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 30000:40000 -j ACCEPT + service iptables save + + iptables_status=`service iptables status | grep 'not running'` + if [ "${iptables_status}" == '' ];then + service iptables restart + fi + + #安装时不开启 + service iptables stop +fi + + +if [ ! -f /usr/sbin/iptables ];then + yum install firewalld -y + systemctl enable firewalld + systemctl start firewalld + + firewall-cmd --permanent --zone=public --add-port=22/tcp + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + # firewall-cmd --permanent --zone=public --add-port=7200/tcp + # firewall-cmd --permanent --zone=public --add-port=3306/tcp + # firewall-cmd --permanent --zone=public --add-port=30000-40000/tcp + + + sed -i 's#AllowZoneDrifting=yes#AllowZoneDrifting=no#g' /etc/firewalld/firewalld.conf + firewall-cmd --reload +fi + + +#安装时不开启 +systemctl stop firewalld + +yum groupinstall -y "Development Tools" +yum install -y epel-release + +yum install -y libevent libevent-devel zip unzip libmcrypt libmcrypt-devel +yum install -y wget libicu-devel readline-devel zip bzip2 bzip2-devel libxml2 libxml2-devel +yum install -y libpng libpng-devel libwebp libwebp-devel pcre pcre-devel gd gd-devel zlib zlib-devel gettext gettext-devel +yum install -y net-tools +yum install -y ncurses ncurses-devel mysql-devel make cmake +yum install -y sqlite-devel +yum install -y libargon2-dev + +# python-imaging +# yum install -y MySQL-python + + +yum install -y perl perl-devel perl-Data-Dumper + +for yumPack in gcc gcc-c++ flex file libtool libtool-libs autoconf kernel-devel patch glib2 glib2-devel tar e2fsprogs e2fsprogs-devel libidn libidn-devel vim-minimal gmp-devel libcap diffutils ca-certificates libc-client-devel psmisc libXpm-devel c-ares-devel libxslt libxslt-devel glibc.i686 libstdc++.so.6 cairo-devel libaio-devel expat-devel; +do dnf --enablerepo=powertools install -y $yumPack;done + +yum install -y libtirpc libtirpc-devel +dnf --enablerepo=powertools install -y boost-locale + +dnf --enablerepo=powertools install -y libmemcached libmemcached-devel +dnf --enablerepo=powertools install -y rpcgen +dnf --enablerepo=powertools install -y oniguruma oniguruma-devel +dnf --enablerepo=powertools install -y re2c bison bison-devel +dnf install -y libjpeg-turbo libjpeg-turbo-devel + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +echo "rocky ok" diff --git a/scripts/install/ubuntu.sh b/scripts/install/ubuntu.sh new file mode 100644 index 000000000..38e420b90 --- /dev/null +++ b/scripts/install/ubuntu.sh @@ -0,0 +1,239 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +export LANG=en_US.UTF-8 +export DEBIAN_FRONTEND=noninteractive + +function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } +function version_le() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" == "$1"; } +function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" != "$1"; } +function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } + + +if grep -Eq "Ubuntu" /etc/*-release; then + sudo ln -sf /bin/bash /bin/sh + #sudo dpkg-reconfigure dash +fi + +# synchronize server +# systemctl status chronyd -l +apt install chrony -y +apt install ntpdate -y + +apt update -y +apt autoremove -y + +apt install -y wget curl unzip +apt install -y lsof +apt install -y rar unrar +apt install -y xz-utils +apt install -y python3-pip +apt install -y python3-venv +apt install -y python3-dev +apt install -y expect +apt install -y pv +apt install -y bc +apt install -y cron +apt install -y net-tools +apt install -y libncurses5 +apt install -y libncurses5-dev +apt install -y software-properties-common +apt install -y bzip2 +apt install -y p7zip-full + +apt install -y libnuma1 +apt install -y libaio1 +apt install -y libaio-dev +apt install -y libmecab2 +apt install -y numactl +apt install -y libaio1t64 +apt install -y libmm-dev + +apt install -y dnsutils +apt install -y numactl +apt install -y xxd + +# https://www.php.net/manual/zh/mysql-xdevapi.installation.php +apt install -y libprotobuf-dev +apt install -y protobuf-compiler +apt install -y libboost-dev +apt install -y liblz4-tool +apt install -y zstd +apt install -y sshpass + +P_VER=`python3 -V | awk '{print $2}'` +if version_ge "$P_VER" "3.11.0" ;then + echo -e "\e[1;31mapt install python3.12-venv\e[0m" + apt install -y python3.12-venv +fi + +# choose lang cmd +# dpkg-reconfigure --frontend=noninteractive locales +# dpkg-reconfigure locales +if [ ! -f /usr/sbin/locale-gen ];then + apt install -y locales + sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen + locale-gen en_US.UTF-8 + locale-gen zh_CN.UTF-8 + localedef -v -c -i en_US -f UTF-8 en_US.UTF-8 > /dev/null 2>&1 + update-locale LANG=en_US.UTF-8 +else + locale-gen en_US.UTF-8 + locale-gen zh_CN.UTF-8 + localedef -v -c -i en_US -f UTF-8 en_US.UTF-8 > /dev/null 2>&1 +fi + +SSH_PORT=`netstat -ntpl|grep sshd|grep -v grep | sed -n "1,1p" | awk '{print $4}' | awk -F : '{print $2}'` +if [ "$SSH_PORT" == "" ];then + SSH_PORT_LINE=`cat /etc/ssh/sshd_config | grep "Port \d*" | tail -1` + SSH_PORT=${SSH_PORT_LINE/"Port "/""} +fi +echo "SSH PORT:${SSH_PORT}" + +if [ -f /usr/sbin/ufw ];then + # look + # ufw status + echo 'y' | ufw enable + + if [ "$SSH_PORT" != "" ];then + ufw allow $SSH_PORT/tcp + else + ufw allow 22/tcp + fi + + ufw allow 80/tcp + ufw allow 443/tcp + ufw allow 443/udp + # ufw allow 888/tcp +fi + +if [ ! -f /usr/sbin/ufw ];then + apt install -y firewalld + systemctl enable firewalld + + + if [ "$SSH_PORT" != "" ];then + firewall-cmd --permanent --zone=public --add-port=${SSH_PORT}/tcp + else + firewall-cmd --permanent --zone=public --add-port=22/tcp + fi + + firewall-cmd --permanent --zone=public --add-port=80/tcp + firewall-cmd --permanent --zone=public --add-port=443/tcp + firewall-cmd --permanent --zone=public --add-port=443/udp + # firewall-cmd --permanent --zone=public --add-port=888/tcp + + systemctl start firewalld + + # fix:debian10 firewalld faq + # https://kawsing.gitbook.io/opensystem/andoid-shou-ji/untitled/fang-huo-qiang#debian-10-firewalld-0.6.3-error-commandfailed-usrsbinip6tablesrestorewn-failed-ip6tablesrestore-v1.8 + sed -i 's#IndividualCalls=no#IndividualCalls=yes#g' /etc/firewalld/firewalld.conf + + firewall-cmd --reload + + # #安装时不开启 + # systemctl stop firewalld +fi + +apt install -y devscripts +apt install -y python3-dev +apt install -y autoconf +apt install -y gcc +apt install -y lrzsz + +apt install -y libffi-dev +apt install -y cmake automake make + +apt install -y webp scons +apt install -y libwebp-dev +apt install -y lzma lzma-dev +apt install -y libunwind-dev + +apt install -y libpcre3 libpcre3-dev +apt install -y openssl +apt install -y libssl-dev +apt install -y libargon2-dev + +apt install -y libmemcached-dev +apt install -y libsasl2-dev +apt install -y imagemagick +apt install -y libmagickcore-dev +apt install -y libmagickwand-dev + +apt install -y libxml2 libxml2-dev libbz2-dev libmcrypt-dev libpspell-dev librecode-dev +apt install -y libgmp-dev libgmp3-dev libreadline-dev libxpm-dev +apt install -y libpq-dev +apt install -y dia + +apt install -y pkg-config +apt install -y zlib1g-dev + +apt install -y libjpeg-dev libpng-dev +apt install -y libfreetype6 +apt install -y libjpeg62-turbo-dev +apt install -y libfreetype6-dev +apt install -y libevent-dev libldap2-dev + +apt install -y libzip-dev +apt install -y libicu-dev +apt install -y libyaml-dev + +# mqtt +apt install -y xsltproc + +apt install -y build-essential + +apt install -y libcurl4-openssl-dev +apt install -y libcurl4-nss-dev +apt install -y curl libcurl4-gnutls-dev +#https://blog.csdn.net/qq_36228377/article/details/123154344 +# ln -s /usr/include/x86_64-linux-gnu/curl /usr/include/curl +if [ ! -d /usr/include/curl ];then + SYS_ARCH=`arch` + if [ -f /usr/include/x86_64-linux-gnu/curl ];then + ln -s /usr/include/x86_64-linux-gnu/curl /usr/include/curl + else + # ln -s /usr/include/aarch64-linux-gnu/curl /usr/include/curl + ln -s /usr/include/${SYS_ARCH}-linux-gnu/curl /usr/include/curl + fi +fi + + +apt install -y graphviz bison re2c flex +apt install -y libsqlite3-dev +apt install -y libonig-dev + +apt install -y perl g++ libtool +apt install -y libxslt1-dev + +apt install -y libmariadb-dev +#apt install -y libmysqlclient-dev +apt install -y libmariadb-dev-compat +#apt install -y libmariadbclient-dev + + +# mysql8.0 在ubuntu22需要的库 +apt install -y patchelf + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +if [ "${VERSION_ID}" == "22.04" ];then + apt install -y python3-cffi + pip3 install -U --force-reinstall --no-binary :all: gevent +fi + +# find /usr/lib -name "*libaio*" 2>/dev/null +if [ ! -f /usr/lib/libaio.so.1 ];then + if [ -f /usr/lib/x86_64-linux-gnu/libaio.so.1t64 ];then + ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/libaio.so.1 + fi +fi + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + + +if [ "${VERSION_ID}" == "22.04" ];then + apt install -y python3-cffi + pip3 install -U --force-reinstall --no-binary :all: gevent +fi + diff --git a/scripts/install/unknow.sh b/scripts/install/unknow.sh new file mode 100644 index 000000000..5cea93ad0 --- /dev/null +++ b/scripts/install/unknow.sh @@ -0,0 +1,6 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +echo "unkown server!!!" \ No newline at end of file diff --git a/scripts/install_dev.sh b/scripts/install_dev.sh new file mode 100755 index 000000000..97e16a742 --- /dev/null +++ b/scripts/install_dev.sh @@ -0,0 +1,303 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +PLAIN='\033[0m' +BOLD='\033[1m' +SUCCESS='[\033[32mOK\033[0m]' +COMPLETE='[\033[32mDONE\033[0m]' +WARN='[\033[33mWARN\033[0m]' +ERROR='[\033[31mERROR\033[0m]' +WORKING='[\033[34m*\033[0m]' + +# LANG=en_US.UTF-8 +is64bit=`getconf LONG_BIT` + + +if [ -f /www/server/mdserver-web/tools.py ];then + echo -e "存在旧版代码,不能安装!,已知风险的情况下" + echo -e "rm -rf /www/server/mdserver-web" + echo -e "可安装!" + exit 0 +fi + +echo -e "您正在安装的是\033[31mmdserver-web测试版\033[0m,非开发测试用途请使用正式版 install.sh !" +echo -e "You are installing\033[31m mdserver-web dev version\033[0m, normally use install.sh for production.\n" +sleep 1 + +LOG_FILE=/var/log/mw-install.log + +{ + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn +fi + +if [ "$LOCAL_ADDR" != "common" ];then + declare -A PROXY_URL + PROXY_URL["github_do"]="https://github.do/" + PROXY_URL["gh_llkk_cc"]="https://gh.llkk.cc/https://" + PROXY_URL["gh_felicity_ac_cn"]="https://gh.felicity.ac.cn/https://" + PROXY_URL["ghfast_top"]="https://ghfast.top/" + PROXY_URL["ghproxy_net"]="https://ghproxy.net/" + PROXY_URL["gh_927223_xyz"]="https://gh.927223.xyz/https://" + PROXY_URL["gh_proxy_net"]="https://gh-proxy.net/" + + PROXY_URL["source"]="https://" + + + SOURCE_LIST_KEY_SORT_TMP=$(echo ${!PROXY_URL[@]} | tr ' ' '\n' | sort -n) + SOURCE_LIST_KEY=(${SOURCE_LIST_KEY_SORT_TMP//'\n'/}) + SOURCE_LIST_LEN=${#PROXY_URL[*]} +fi + + +function AutoSizeStr(){ + NAME_STR=$1 + NAME_NUM=$2 + + NAME_STR_LEN=`echo "$NAME_STR" | wc -L` + NAME_NUM_LEN=`echo "$NAME_NUM" | wc -L` + + fix_len=35 + remaining_len=`expr $fix_len - $NAME_STR_LEN - $NAME_NUM_LEN` + FIX_SPACE=' ' + for ((ass_i=1;ass_i<=$remaining_len;ass_i++)) + do + FIX_SPACE="$FIX_SPACE " + done + echo -e " ❖ ${1}${FIX_SPACE}${2})" +} + +function ChooseProxyURL(){ + clear + echo -e '+---------------------------------------------------+' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '| 欢迎使用 Linux 一键安装mdserver-web面板源码 |' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '+---------------------------------------------------+' + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e ' 提供以下国内代理地址可供选择: ' + echo -e '' + echo -e '#####################################################' + echo -e '' + cm_i=0 + for V in ${SOURCE_LIST_KEY[@]}; do + num=`expr $cm_i + 1` + AutoSizeStr "${V}" "$num" + cm_i=`expr $cm_i + 1` + done + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e " 系统时间 ${BLUE}$(date "+%Y-%m-%d %H:%M:%S")${PLAIN}" + echo -e '' + echo -e '#####################################################' + CHOICE_A=$(echo -e "\n${BOLD}└─ 请选择并输入你想使用的代理地址 [ 1-${SOURCE_LIST_LEN} ]:${PLAIN}") + + read -p "${CHOICE_A}" INPUT + # echo $INPUT + if [ "$INPUT" == "" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n默认选择[${BLUE}${INPUT_KEY}${PLAIN}]安装!" + fi + + if [ "$INPUT" -lt "0" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n低于边界错误!选择[${BLUE}${INPUT_KEY}${PLAIN}]安装!" + sleep 2s + fi + + if [ "$INPUT" -gt "${SOURCE_LIST_LEN}" ];then + INPUT=${SOURCE_LIST_LEN} + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n超出边界错误!选择[${BLUE}${INPUT_KEY}${PLAIN}]安装!" + sleep 2s + fi + + INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$INPUT]} + HTTP_PREFIX=${PROXY_URL[$INPUT_KEY]} +} + +if [ "$LOCAL_ADDR" != "common" ];then + ChooseProxyURL + + if [ "$HTTP_PREFIX" != "https://" ];then + DOMAIN=`echo $HTTP_PREFIX | sed 's|https://||g'` + DOMAIN=`echo $DOMAIN | sed 's|/||g'` + ping -c 3 $DOMAIN > /dev/null 2>&1 + if [ "$?" != "0" ];then + echo "无效代理地址:${DOMAIN}" + exit + fi + fi +fi + +if [ -f /etc/motd ];then + echo "welcome to mdserver-web panel" > /etc/motd +fi + +startTime=`date +%s` + +_os=`uname` +echo "use system: ${_os}" + + +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eq "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh + zypper install -y wget curl zip unzip unrar rar +elif grep -Eq "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget curl zip unzip unrar rar +elif grep -Eqi "EulerOS" /etc/*-release || grep -Eqi "openEuler" /etc/*-release; then + OSNAME='euler' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "CentOS" /etc/issue || grep -Eqi "CentOS" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip tar +elif grep -Eqi "Fedora" /etc/issue || grep -Eqi "Fedora" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip tar +elif grep -Eqi "Rocky" /etc/issue || grep -Eqi "Rocky" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "Anolis" /etc/issue || grep -Eqi "Anolis" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eqi "AlmaLinux" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip tar +elif grep -Eqi "Amazon Linux" /etc/issue || grep -Eqi "Amazon Linux" /etc/*-release; then + OSNAME='amazon' + yum install -y wget zip unzip tar +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eqi "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip tar +elif grep -Eqi "Debian" /etc/issue || grep -Eqi "Debian" /etc/*-release; then + OSNAME='debian' + apt update -y + apt install -y devscripts + apt install -y wget zip unzip tar +elif grep -Eqi "Alpine" /etc/issue || grep -Eqi "Alpine" /etc/*-release; then + OSNAME='alpine' + apk update + apk add devscripts -force-broken-world + apk add curl wget zip unzip tar -force-broken-world +else + OSNAME='unknow' +fi + +if [ "$EUID" -ne 0 ] && [ "$OSNAME" != "macos" ];then + echo "Please run as root!" + exit +fi + + +echo "LOCAL:${LOCAL_ADDR}" +echo "OSNAME:${OSNAME}" + +if [ $OSNAME != "macos" ];then + + if id www &> /dev/null ;then + echo "" + else + groupadd www + useradd -g www -s /usr/sbin/nologin www + fi + + mkdir -p /www/server + mkdir -p /www/wwwroot + mkdir -p /www/wwwlogs + mkdir -p /www/backup/database + mkdir -p /www/backup/site + + if [ ! -d /www/server/mdserver-web ];then + echo "downloading ${HTTP_PREFIX}github.com/midoks/mdserver-web/archive/refs/heads/dev.tar.gz" + curl --insecure -sSLo /tmp/dev.tar.gz ${HTTP_PREFIX}github.com/midoks/mdserver-web/archive/refs/heads/dev.tar.gz + cd /tmp && tar -zxvf /tmp/dev.tar.gz + mv -f /tmp/mdserver-web-dev /www/server/mdserver-web + rm -rf /tmp/dev.tar.gz + rm -rf /tmp/mdserver-web-dev + fi + + # install acme.sh + if [ ! -d /root/.acme.sh ];then + if [ "$LOCAL_ADDR" != "common" ];then + curl -sSL -o /tmp/acme.tar.gz ${HTTP_PREFIX}github.com/acmesh-official/acme.sh/archive/master.tar.gz + tar xvzf /tmp/acme.tar.gz -C /tmp + cd /tmp/acme.sh-master + bash acme.sh install + else + curl -fsSL https://get.acme.sh | bash + fi + fi +fi + +echo "use system version: ${OSNAME}" + +if [ "${OSNAME}" == "macos" ];then + curl --insecure -fsSL ${HTTP_PREFIX}raw.githubusercontent.com/midoks/mdserver-web/refs/heads/dev/scripts/install/macos.sh | bash +else + cd /www/server/mdserver-web && bash scripts/install/${OSNAME}.sh +fi + +if [ "${OSNAME}" == "macos" ];then + echo "macos end" + exit 0 +fi + +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [ ! -f /etc/rc.d/init.d/mw ]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done + +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw stop +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw start +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw default + +sleep 2 +if [ ! -e /usr/bin/mw ]; then + if [ -f /etc/rc.d/init.d/mw ];then + ln -s /etc/rc.d/init.d/mw /usr/bin/mw + fi +fi + +endTime=`date +%s` +((outTime=(${endTime}-${startTime})/60)) +echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" + +} 1> >(tee $LOG_FILE) 2>&1 + +echo -e "\nInstall completed. If error occurs, please contact us with the log file mw-install.log ." +echo "安装完毕,如果出现错误,请带上同目录下的安装日志 mw-install.log 联系我们反馈." \ No newline at end of file diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100755 index 000000000..ed3158bc3 --- /dev/null +++ b/scripts/lib.sh @@ -0,0 +1,118 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin + +function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } +function version_le() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" == "$1"; } +function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" != "$1"; } +function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } + +P_VER=`python3 -V | awk '{print $2}'` +echo "python:$P_VER" +sleep 1 + +curPath=`pwd` +rootPath=$(dirname "$curPath") +serverPath=$(dirname "$rootPath") +sourcePath=$serverPath/source/lib +libPath=$serverPath/lib + +mkdir -p $sourcePath +mkdir -p $libPath +rm -rf ${libPath}/lib.pl + + +bash ${rootPath}/scripts/getos.sh +OSNAME=`cat ${rootPath}/data/osname.pl` +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` + +# system judge +if [ "$OSNAME" == "macos" ]; then + brew install libmemcached + brew install curl + brew install zlib + brew install freetype + brew install openssl + brew install libzip +elif [ "$OSNAME" == "opensuse" ];then + echo "opensuse lib" +elif [ "$OSNAME" == "arch" ];then + echo "arch lib" +elif [ "$OSNAME" == "freebsd" ];then + echo "freebsd lib" +elif [ "$OSNAME" == "centos" ];then + echo "centos lib" +elif [ "$OSNAME" == "rocky" ]; then + echo "rocky lib" +elif [ "$OSNAME" == "fedora" ];then + echo "fedora lib" +elif [ "$OSNAME" == "alma" ];then + echo "alma lib" +elif [ "$OSNAME" == "ubuntu" ];then + echo "ubuntu lib" +elif [ "$OSNAME" == "debian" ]; then + echo "debian lib" +else + echo "OK" +fi +echo "system:${OSNAME}:${VERSION_ID}" + + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn +fi + +PIPSRC="https://pypi.python.org/simple" +if [ "$LOCAL_ADDR" != "common" ];then + PIPSRC="https://pypi.tuna.tsinghua.edu.cn/simple" +fi + +echo "local:${LOCAL_ADDR}" +echo "pypi source:$PIPSRC" + +#面板需要的库 +if [ ! -f /usr/local/bin/pip3 ] && [ ! -f /usr/bin/pip3 ];then + python3 -m pip install --upgrade pip setuptools wheel -i $PIPSRC + + which pip3 && pip3 install --upgrade pip -i $PIPSRC + pip3 install --upgrade pip setuptools wheel -i $PIPSRC +fi + +if [ ! -f /www/server/mdserver-web/bin/activate ];then + if version_ge "$P_VER" "3.11.0" ;then + echo "python3 > 3.11" + cd /www/server/mdserver-web && python3 -m venv /www/server/mdserver-web + else + echo "python3 < 3.10" + cd /www/server/mdserver-web && python3 -m venv . + cd /www/server/mdserver-web && pip3 install -r /www/server/mdserver-web/requirements.txt -i $PIPSRC + fi + cd /www/server/mdserver-web && source /www/server/mdserver-web/bin/activate +else + cd /www/server/mdserver-web && source /www/server/mdserver-web/bin/activate +fi + +pip3 install --upgrade pip -i $PIPSRC +pip3 install --upgrade setuptools -i $PIPSRC + + +# repeated attempts +if [ "$LOCAL_ADDR" != "common" ];then + cd /www/server/mdserver-web && pip3 install -r /www/server/mdserver-web/requirements.txt +fi +cd /www/server/mdserver-web && pip3 install -r /www/server/mdserver-web/requirements.txt -i $PIPSRC + + +# Different versions use different python lib +P_VER_D=`echo "$P_VER"|awk -F '.' '{print $1}'` +P_VER_M=`echo "$P_VER"|awk -F '.' '{print $2}'` +NEW_P_VER=${P_VER_D}.${P_VER_M} + +if [ -f /www/server/mdserver-web/version/r${NEW_P_VER}.txt ];then + echo "cd /www/server/mdserver-web && pip3 install -r /www/server/mdserver-web/version/r${NEW_P_VER}.txt" + cd /www/server/mdserver-web && pip3 install -r /www/server/mdserver-web/version/r${NEW_P_VER}.txt -i $PIPSRC +fi + +echo "lib ok!" \ No newline at end of file diff --git a/scripts/logs_backup.py b/scripts/logs_backup.py new file mode 100755 index 000000000..18203c08e --- /dev/null +++ b/scripts/logs_backup.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +# coding: utf-8 +#----------------------------- +# 网站日志切割脚本 +#----------------------------- +import sys +import os +import shutil +import time +import glob + +if sys.platform != 'darwin': + os.chdir('/www/server/mdserver-web') + + +web_dir = os.getcwd() + "/web" +if os.path.exists(web_dir): + sys.path.append(web_dir) + os.chdir(web_dir) + +import core.mw as mw + +print('==================================================================') +print('★[' + time.strftime("%Y/%m/%d %H:%M:%S") + '],切割日志') +print('==================================================================') +print('|--当前保留最新的[' + sys.argv[2] + ']份') +logsPath = mw.getLogsDir() +px = '.log' + + +def split_logs(oldFileName, num): + global logsPath + if not os.path.exists(oldFileName): + print('|---' + oldFileName + '文件不存在!') + return + + logs = sorted(glob.glob(oldFileName + "_*")) + count = len(logs) + num = count - num + + for i in range(count): + if i > num: + break + os.remove(logs[i]) + print('|---多余日志[' + logs[i] + ']已删除!') + + newFileName = oldFileName + '_' + time.strftime("%Y-%m-%d_%H%M%S") + '.log' + shutil.move(oldFileName, newFileName) + print('|---已切割日志到:' + newFileName) + + +def split_all(save): + sites = mw.M('sites').field('name').select() + for site in sites: + oldFileName = logsPath + '/' +site['name'] + px + split_logs(oldFileName, save) + errOldFileName = logsPath + '/' + site['name'] + ".error.log" + split_logs(errOldFileName, save) + +if __name__ == '__main__': + num = int(sys.argv[2]) + if sys.argv[1].find('ALL') == 0: + split_all(num) + else: + siteName = sys.argv[1] + if siteName[-4:] == '.log': + siteName = siteName[:-4] + else: + siteName = siteName.replace("-access_log", '') + oldFileName = logsPath + '/' + sys.argv[1] + errOldFileName = logsPath + '/' + \ + sys.argv[1].strip(".log") + ".error.log" + split_logs(oldFileName, num) + if os.path.exists(errOldFileName): + split_logs(errOldFileName, num) + path = mw.getServerDir() + os.system("kill -USR1 `cat " + path + "/openresty/nginx/logs/nginx.pid`") diff --git a/scripts/old/install.sh b/scripts/old/install.sh new file mode 100644 index 000000000..8971bfea2 --- /dev/null +++ b/scripts/old/install.sh @@ -0,0 +1,178 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +# LANG=en_US.UTF-8 +is64bit=`getconf LONG_BIT` + +VERSION=0.17.3 + +LOG_FILE=/var/log/mw-install.log + +{ + +if [ -f /etc/motd ];then + echo "welcome to mdserver-web panel" > /etc/motd +fi + +startTime=`date +%s` + +_os=`uname` +echo "use system: ${_os}" + +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh + zypper install cron wget curl zip unzip +elif grep -Eqi "FreeBSD" /etc/*-release; then + OSNAME='freebsd' + pkg install -y wget curl zip unzip unrar rar +elif grep -Eqi "EulerOS" /etc/*-release || grep -Eqi "openEuler" /etc/*-release; then + OSNAME='euler' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "CentOS" /etc/issue || grep -Eqi "CentOS" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Fedora" /etc/issue || grep -Eqi "Fedora" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Rocky" /etc/issue || grep -Eqi "Rocky" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eqi "AlmaLinux" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Amazon Linux" /etc/issue || grep -Eqi "Amazon Linux" /etc/*-release; then + OSNAME='amazon' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Debian" /etc/issue || grep -Eqi "Debian" /etc/os-release; then + OSNAME='debian' + apt update -y + apt install -y wget curl zip unzip tar cron +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eqi "Ubuntu" /etc/os-release; then + OSNAME='ubuntu' + apt update -y + apt install -y wget curl zip unzip tar cron +else + OSNAME='unknow' +fi + +if [ "$EUID" -ne 0 ] && [ "$OSNAME" != "macos" ];then + echo "Please run as root!" + exit +fi + + +# HTTP_PREFIX="https://" +# LOCAL_ADDR=common +# ping -c 1 github.com > /dev/null 2>&1 +# if [ "$?" != "0" ];then +# LOCAL_ADDR=cn +# HTTP_PREFIX="https://mirror.ghproxy.com/" +# fi + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + HTTP_PREFIX="https://mirror.ghproxy.com/" +fi + +echo "local:${LOCAL_ADDR}" + +if [ $OSNAME != "macos" ];then + if id www &> /dev/null ;then + echo "" + else + groupadd www + useradd -g www -s /usr/sbin/nologin www + fi + + mkdir -p /www/server + mkdir -p /www/wwwroot + mkdir -p /www/wwwlogs + mkdir -p /www/backup/database + mkdir -p /www/backup/site + + # https://cdn.jsdelivr.net/gh/midoks/mdserver-web@latest/scripts/install.sh + if [ ! -d /www/server/mdserver-web ];then + if [ "$LOCAL_ADDR" == "common" ];then + curl --insecure -sSLo /tmp/master.zip ${HTTP_PREFIX}github.com/midoks/mdserver-web/archive/refs/tags/${VERSION}.zip + cd /tmp && unzip /tmp/master.zip + mv -f /tmp/mdserver-web-${VERSION} /www/server/mdserver-web + rm -rf /tmp/master.zip + rm -rf /tmp/mdserver-web-master + else + # curl --insecure -sSLo /tmp/master.zip https://code.midoks.icu/midoks/mdserver-web/archive/master.zip + wget --no-check-certificate -O /tmp/master.zip https://code.midoks.icu/midoks/mdserver-web/archive/${VERSION}.zip + cd /tmp && unzip /tmp/master.zip + mv -f /tmp/mdserver-web /www/server/mdserver-web + rm -rf /tmp/master.zip + rm -rf /tmp/mdserver-web + fi + + + fi + + # install acme.sh + if [ ! -d /root/.acme.sh ];then + if [ "$LOCAL_ADDR" != "common" ];then + curl --insecure -sSLo /tmp/acme.tar.gz https://gitee.com/neilpang/acme.sh/repository/archive/master.tar.gz + tar xvzf /tmp/acme.tar.gz -C /tmp + cd /tmp/acme.sh-master + bash acme.sh install + fi + + if [ ! -d /root/.acme.sh ];then + curl https://get.acme.sh | sh + fi + fi +fi + +echo "use system version: ${OSNAME}" +if [ "${OSNAME}" == "macos" ];then + curl --insecure -fsSL https://code.midoks.icu/midoks/mdserver-web/raw/branch/master/scripts/install/macos.sh | bash +else + cd /www/server/mdserver-web && bash scripts/install/${OSNAME}.sh +fi + +if [ "${OSNAME}" == "macos" ];then + echo "macos end" + exit 0 +fi + +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [ ! -f /etc/rc.d/init.d/mw ]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done + +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw stop +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw start +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw default + +sleep 2 +if [ ! -e /usr/bin/mw ]; then + if [ -f /etc/rc.d/init.d/mw ];then + ln -s /etc/rc.d/init.d/mw /usr/bin/mw + fi +fi + +endTime=`date +%s` +((outTime=(${endTime}-${startTime})/60)) +echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" + +} 1> >(tee $LOG_FILE) 2>&1 + +echo -e "\nInstall completed. If error occurs, please contact us with the log file mw-install.log ." +echo "安装完毕,如果出现错误,请带上同目录下的安装日志 mw-install.log 联系我们反馈." diff --git a/scripts/old/update.sh b/scripts/old/update.sh new file mode 100644 index 000000000..fb9527f91 --- /dev/null +++ b/scripts/old/update.sh @@ -0,0 +1,122 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +# LANG=en_US.UTF-8 +is64bit=`getconf LONG_BIT` + +startTime=`date +%s` + +VERSION=0.17.3 + +_os=`uname` +echo "use system: ${_os}" + +if [ "$EUID" -ne 0 ] + then echo "Please run as root!" + exit +fi + +if [ ${_os} != "Darwin" ] && [ ! -d /www/server/mdserver-web/logs ]; then + mkdir -p /www/server/mdserver-web/logs +fi + +LOG_FILE=/var/log/mw-update.log + +{ + +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eqi "EulerOS" /etc/*-release || grep -Eqi "openEuler" /etc/*-release; then + OSNAME='euler' +elif grep -Eqi "FreeBSD" /etc/*-release; then + OSNAME='freebsd' +elif grep -Eqi "CentOS" /etc/issue || grep -Eqi "CentOS" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eqi "Fedora" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eqi "Rocky" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eqi "AlmaLinux" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "Amazon Linux" /etc/issue || grep -Eqi "Amazon Linux" /etc/*-release; then + OSNAME='amazon' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eqi "Debian" /etc/*-release; then + OSNAME='debian' + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eqi "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +elif grep -Eqi "Raspbian" /etc/issue || grep -Eqi "Raspbian" /etc/*-release; then + OSNAME='raspbian' +else + OSNAME='unknow' +fi + + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn + HTTP_PREFIX="https://mirror.ghproxy.com/" +fi +echo "local:${LOCAL_ADDR}" + +CP_CMD=/usr/bin/cp +if [ -f /bin/cp ];then + CP_CMD=/bin/cp +fi + +if [ "$LOCAL_ADDR" != "common" ];then + # curl --insecure -sSLo /tmp/master.zip https://code.midoks.icu/midoks/mdserver-web/archive/master.zip + wget --no-check-certificate -O /tmp/master.zip https://github.com/midoks/mdserver-web/archive/refs/tags/${VERSION}.zip + cd /tmp && unzip /tmp/master.zip + + $CP_CMD -rf /tmp/mdserver-web-${VERSION}/* /www/server/mdserver-web + rm -rf /tmp/master.zip + rm -rf /tmp/mdserver-web-${VERSION} + + pip install -r /www/server/mdserver-web/requirements.txt +else + # curl --insecure -sSLo /tmp/master.zip https://github.com/midoks/mdserver-web/archive/refs/tags/0.17.3.zip + curl --insecure -sSLo /tmp/master.zip https://github.com/midoks/mdserver-web/archive/refs/tags/${VERSION}.zip + + cd /tmp && unzip /tmp/master.zip + $CP_CMD -rf /tmp/mdserver-web-${VERSION}/* /www/server/mdserver-web + rm -rf /tmp/master.zip + rm -rf /tmp/mdserver-web-${VERSION} + + pip install -r /www/server/mdserver-web/requirements.txt +fi + + +#pip uninstall public +echo "use system version: ${OSNAME}" +cd /www/server/mdserver-web && bash scripts/update/${OSNAME}.sh + +bash /etc/rc.d/init.d/mw restart +bash /etc/rc.d/init.d/mw default + +if [ -f /usr/bin/mw ];then + rm -rf /usr/bin/mw +fi + +if [ ! -e /usr/bin/mw ]; then + if [ ! -f /usr/bin/mw ];then + ln -s /etc/rc.d/init.d/mw /usr/bin/mw + fi +fi + +endTime=`date +%s` +((outTime=($endTime-$startTime)/60)) +echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" + +} 1> >(tee $LOG_FILE) 2>&1 \ No newline at end of file diff --git a/scripts/pick.sh b/scripts/pick.sh new file mode 100755 index 000000000..75746c354 --- /dev/null +++ b/scripts/pick.sh @@ -0,0 +1,25 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin + +curPath=`pwd` +rootPath=$(dirname "$curPath") + + +#----------------------------- 代码打包 -------------------------# + +echo $rootPath +cd $rootPath +rm -rf ./*.pyc +rm -rf ./*/*.pyc + +startTime=`date +%s` + +zip -r -q -o mdserver-web.zip ./ -x@$curPath/pick_filter.txt + + + +mv mdserver-web.zip $rootPath/scripts + +endTime=`date +%s` +((outTime=($endTime-$startTime))) +echo -e "Time consumed:\033[32m $outTime \033[0mSec!" \ No newline at end of file diff --git a/scripts/pick_filter.txt b/scripts/pick_filter.txt new file mode 100755 index 000000000..e6c398114 --- /dev/null +++ b/scripts/pick_filter.txt @@ -0,0 +1,139 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +scripts/.git/* +.git/* +.github/* +bin/* +lib/* +include/* +pyvenv.cfg +__pycache__ + +docker/* +.mypy_cache/ +.gitignore +.git +.DS_Store +.idea/*.xml +logs/* +tmp/* +version/* +data/json/index.json +data/control.conf +data/default.db +data/system.db +data/default.pl +data/control.conf +data/iplist.txt +data/osname.pl +data/edate.pl +data/backup.pl +data/default_site.pl +data/port.pl +data/api_login.txt +data/json/index.json +data/sessions/* + + + +script/init.d/mw diff --git a/scripts/plugin_compress.sh b/scripts/plugin_compress.sh new file mode 100755 index 000000000..58dd98fe0 --- /dev/null +++ b/scripts/plugin_compress.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +### +### 插件压缩 +### +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 +is64bit=`getconf LONG_BIT` +curPath=`pwd` +rootPath=$(dirname "$curPath") + +startTime=`date +%s` +PLUGIN_NAME='abkill' + +#echo $rootPath/plugins/$PLUGIN_NAME +mkdir -p $rootPath/scripts/tmp +cd $rootPath/plugins/$PLUGIN_NAME && zip $rootPath/scripts/tmp/${PLUGIN_NAME}_${startTime}.zip -r ./* > /tmp/t.log + 2>&1 + +endTime=`date +%s` +((outTime=($endTime-$startTime))) +echo -e "Time consumed:\033[32m $outTime \033[0msecs.!" \ No newline at end of file diff --git a/scripts/quick/app.sh b/scripts/quick/app.sh new file mode 100755 index 000000000..8212b179d --- /dev/null +++ b/scripts/quick/app.sh @@ -0,0 +1,76 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +if [ ! -d /www/server/mdserver-web/logs ]; then + mkdir -p /www/server/mdserver-web/logs +fi + +{ + +echo "welcome to mdserver-web panel" + +startTime=`date +%s` + +if [ ! -d /www/server/mdserver-web ];then + echo "mdserver-web not exist!" + exit 1 +fi + +# openresty +if [ ! -d /www/server/openresty ];then + cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.25.3 +else + echo "openresty alreay exist!" +fi + +# redis +if [ ! -d /www/server/redis ];then + cd /www/server/mdserver-web/plugins/redis && bash install.sh install 7.4.3 +else + echo "redis alreay exist!" +fi + + +# php +if [ ! -d /www/server/php/71 ];then + cd /www/server/mdserver-web/plugins/php && bash install.sh install 71 +else + echo "php71 alreay exist!" +fi + + +# php +if [ ! -d /www/server/php/74 ];then + cd /www/server/mdserver-web/plugins/php && bash install.sh install 74 +else + echo "php74 alreay exist!" +fi + + +# swap +if [ ! -d /www/server/swap ];then + cd /www/server/mdserver-web/plugins/swap && bash install.sh install 1.1 +else + echo "swap alreay exist!" +fi + +# mysql +if [ ! -d /www/server/mysql ];then + cd /www/server/mdserver-web/plugins/mysql && bash install.sh install 5.7 +else + echo "mysql alreay exist!" +fi + +# phpmyadmin +if [ ! -d /www/server/phpmyadmin ];then + cd /www/server/mdserver-web/plugins/phpmyadmin && bash install.sh install 4.4.15 +else + echo "phpmyadmin alreay exist!" +fi + +endTime=`date +%s` +((outTime=(${endTime}-${startTime})/60)) +echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" + +} 1> >(tee /www/server/mdserver-web/logs/mw-app.log) 2>&1 \ No newline at end of file diff --git a/scripts/quick/debug.sh b/scripts/quick/debug.sh new file mode 100755 index 000000000..69b9ca1de --- /dev/null +++ b/scripts/quick/debug.sh @@ -0,0 +1,59 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +if [ ! -d /www/server/mdserver-web/logs ]; then + mkdir -p /www/server/mdserver-web/logs +fi + +{ + +echo "welcome to mdserver-web panel" + +startTime=`date +%s` + +if [ ! -d /www/server/mdserver-web ];then + echo "mdserver-web not exist!" + exit 1 +fi + +# openresty +if [ ! -d /www/server/openresty ];then + cd /www/server/mdserver-web/plugins/openresty && bash install.sh install 1.21.4.1 +fi + + +# php +# if [ ! -d /www/server/php/71 ];then +# cd /www/server/mdserver-web/plugins/php && bash install.sh install 71 +# fi + + +PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82) +# PHP_VER_LIST=(81) +for PHP_VER in ${PHP_VER_LIST[@]}; do + echo "php${PHP_VER} -- start" + if [ ! -d /www/server/php/${PHP_VER} ];then + cd /www/server/mdserver-web/plugins/php && bash install.sh install ${PHP_VER} + fi + echo "php${PHP_VER} -- end" +done + + +# cd /www/server/mdserver-web/plugins/php-yum && bash install.sh install 74 + + +# mysql +if [ ! -d /www/server/mysql ];then + # cd /www/server/mdserver-web/plugins/mysql && bash install.sh install 5.7 + + + cd /www/server/mdserver-web/plugins/mysql && bash install.sh install 5.6 + # cd /www/server/mdserver-web/plugins/mysql && bash install.sh install 8.0 +fi + +endTime=`date +%s` +((outTime=(${endTime}-${startTime})/60)) +echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" + +} 1> >(tee /www/server/mdserver-web/logs/mw-debug.log) 2>&1 \ No newline at end of file diff --git a/scripts/rememory.sh b/scripts/rememory.sh new file mode 100755 index 000000000..5311e7aa6 --- /dev/null +++ b/scripts/rememory.sh @@ -0,0 +1,81 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +#+------------------------------------ +#+ 释放内存脚本 +#+------------------------------------ + +endDate=`date +"%Y-%m-%d %H:%M:%S"` +sysName=`uname` +curPath=`pwd` +rootPath=$(dirname "$curPath") + +log="释放内存!" +echo "★[$endDate] $log" +echo '----------------------------------------------------------------------------' + +if [ $sysName == 'Darwin' ]; then + echo '苹果内存释放!' +else + echo 'do start!' +fi + + +echo "OpenResty -- START" +if [ -f /usr/lib/systemd/system/openresty.service ];then + systemctl reload openresty +elif [ -f $rootPath/openresty/nginx/sbin/nginx ];then + $rootPath/openresty/nginx/sbin/nginx -s reload +else + echo "..." +fi +echo "OpenResty -- END" + + +PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82 83 84) +for PHP_VER in ${PHP_VER_LIST[@]}; do +echo "PHP${PHP_VER} -- START" +if [ -f /usr/lib/systemd/system/php${PHP_VER}.service ];then + systemctl reload php${PHP_VER} +elif [ -f ${rootPath}/php/init.d/php${PHP_VER} ];then + ${rootPath}/php/init.d/php${PHP_VER} reload +else + echo "..." +fi +echo "PHP${PHP_VER} -- END" +done + +echo "MySQL -- START" +if [ -f /usr/lib/systemd/system/mysql.service ];then + systemctl reload mysql +elif [ -f ${rootPath}/php/init.d/mysql ];then + ${rootPath}/mysql/init.d/mysql reload +else + echo "..." +fi +echo "MySQL -- END" + + + +echo "PureFTPD -- START" +if [ -f /usr/lib/systemd/system/pureftp.service ];then + systemctl reload pureftp +elif [ -f ${rootPath}/pureftp/init.d/pureftp ];then + ${rootPath}/pureftp/init.d/pureftp reload +else + echo "..." +fi +echo "PureFTPD -- END" + + +sync +sleep 2 +sync + +if [ $sysName == 'Darwin' ]; then + echo 'done!' +else + echo 3 > /proc/sys/vm/drop_caches +fi + +echo '----------------------------------------------------------------------------' \ No newline at end of file diff --git a/scripts/tools/conntrace/script.sh b/scripts/tools/conntrace/script.sh new file mode 100644 index 000000000..b03184780 --- /dev/null +++ b/scripts/tools/conntrace/script.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# 测试中. + +# /opt/iptables-switch.sh status | disable | enable +ipt_mod_conf="/etc/modprobe.d/iptables.conf" +ipt_mod_list="ip_vs iptable_nat nf_nat_ipv4 ipt_MASQUERADE nf_nat nf_conntrack_ipv4 nf_defrag_ipv4 xt_conntrack nf_conntrack iptable_filter ip_tables xt_tcpudp xt_multiport xt_length xt_addrtype x_tables" +nf_max=$(sysctl -e -n net.nf_conntrack_max) +nf_cur=$(sysctl -e -n net.netfilter.nf_conntrack_count) +ipt_hsize=$(grep 'MemTotal' /proc/meminfo | awk '{printf("%d",$2/16)}') + +fuck_ipt_mod(){ + echo '# disable iptables conntrack modules' > ${ipt_mod_conf} + for ipt_mod in ${ipt_mod_list}; do + echo "blacklist ${ipt_mod}" >> ${ipt_mod_conf} + modprobe -r ${ipt_mod} + done +} + +clean_ipt_rule(){ + iptables -F + iptables -Z + iptables -X + for ipt_table in $(cat /proc/net/ip_tables_names 2>/dev/null); do + iptables -t ${ipt_table} -F + iptables -t ${ipt_table} -Z + iptables -t ${ipt_table} -X + done + iptables -P INPUT ACCEPT + iptables -P OUTPUT ACCEPT + iptables -P FORWARD ACCEPT +} + +ipt_enable(){ + echo "options nf_conntrack hashsize=${ipt_hsize}" > ${ipt_mod_conf} # /sys/module/nf_conntrack/parameters/hashsize + for ipt_mod in ${ipt_mod_list}; do + modprobe -q -r ${ipt_mod} && modprobe -a ${ipt_mod} + done + + dmesg --reltime | grep nf_conntrack | tail -2 2>/dev/null + sysctl -e -w net.nf_conntrack_max=4194304 + sysctl -e -w net.ipv4.netfilter.ip_conntrack_max=4194304 + sysctl -e -w net.netfilter.nf_conntrack_max=4194304 + sysctl -e -w net.netfilter.nf_conntrack_tcp_timeout_established=1200 + sysctl -e -w net.netfilter.nf_conntrack_tcp_timeout_close_wait=60 + sysctl -e -w net.netfilter.nf_conntrack_tcp_timeout_fin_wait=120 + sysctl -e -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=120 +} + +case "$1" in +status) + if [[ -z ${nf_max} ]]; then + echo 'nf_conntrack disabled.' + else + echo "nf_conntrack used: ${nf_cur}/${nf_max}." + fi + ;; +disable) + clean_ipt_rule + fuck_ipt_mod + $0 status + ;; +enable) + ipt_enable + $0 status +;; +*) + echo "Usage: $0 {status|disable|enable}" + exit 2 + ;; +esac +exit 0 \ No newline at end of file diff --git a/scripts/tools/trace/trace.sh b/scripts/tools/trace/trace.sh new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100755 index 000000000..c94c4f67a --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,161 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +export LANG=en_US.UTF-8 +is64bit=`getconf LONG_BIT` + +if [ -f /etc/motd ];then + echo "" > /etc/motd +fi + +startTime=`date +%s` + +_os=`uname` +echo "检测到系统: ${_os}" + +if [ "$EUID" -ne 0 ] + then echo "需要使用 root 权限运行卸载脚本,请用 sudo 或切换到 root 后重试~" + exit +fi + +UNINSTALL_CHECK() +{ + echo -e "----------------------------------------------------" + echo -e "目前支持卸载 OpenResty/PHP/MySQL/Redis/Memcached" + echo -e "其他插件请先手动卸载哦!" + echo -e "----------------------------------------------------" + echo -e "温馨提示:输入 yes 才会继续卸载![yes/no]" + read -p "输入 yes 开始卸载(可随时 Ctrl+C 退出): " yes; + if [ "$yes" != "yes" ];then + echo -e "------------" + echo "已取消卸载,安全第一~" + exit 1 + else + echo "开始卸载!我们走~" + fi +} + + +UNINSTALL_MySQL() +{ + MYSQLD_CHECK=$(ps -ef |grep mysqld | grep -v grep | grep /www/server/mysql) + if [ "$MYSQLD_CHECK" != "" ];then + echo -e "----------------------------------------------------" + echo -e "检测到 MySQL,卸载可能影响站点与数据" + echo -e "----------------------------------------------------" + echo -e "温馨提示:输入 yes 才会继续卸载![yes/no]" + read -p "输入 yes 强制卸载: " yes; + if [ "$yes" != "yes" ];then + echo -e "------------" + echo "已取消卸载 MySQL" + else + cd /www/server/mdserver-web/plugins/mysql && sh install.sh uninstall 8.0 + echo "MySQL 卸载完成!" + fi + fi +} + +UNINSTALL_OP() +{ + if [ -f /www/server/openresty ];then + echo -e "----------------------------------------------------" + echo -e "检测到 OpenResty,卸载可能影响站点与数据" + echo -e "----------------------------------------------------" + echo -e "温馨提示:输入 yes 才会继续卸载![yes/no]" + read -p "输入 yes 强制卸载: " yes; + if [ "$yes" != "yes" ];then + echo -e "------------" + echo "已取消卸载 OpenResty" + else + cd /www/server/mdserver-web/plugins/openresty && sh install.sh uninstall + echo "OpenResty 卸载完成!" + fi + fi +} + +UNINSTALL_PHP() +{ + if [ -d /www/server/php ];then + echo -e "----------------------------------------------------" + echo -e "检测到 PHP,卸载可能影响站点与数据" + echo -e "----------------------------------------------------" + read -p "输入 yes 强制卸载所有 PHP [yes/no]: " yes; + if [ "$yes" != "yes" ];then + echo -e "------------" + echo "已取消卸载 PHP" + else + PHP_VER_LIST=(53 54 55 56 70 71 72 73 74 80 81 82) + for PHP_VER in ${PHP_VER_LIST[@]}; do + if [ -d /www/server/php/${PHP_VER} ];then + cd /www/server/mdserver-web/plugins/php && bash install.sh uninstall ${PHP_VER} + fi + echo "PHP${PHP_VER} 卸载完成!" + done + fi + fi +} + +UNINSTALL_MEMCACHED() +{ + if [ -d /www/server/memcached ];then + echo -e "----------------------------------------------------" + echo -e "检测到 Memcached,卸载可能影响站点与数据" + echo -e "----------------------------------------------------" + read -p "输入 yes 强制卸载所有 Memcached [yes/no]: " yes; + if [ "$yes" != "yes" ];then + echo -e "------------" + echo "已取消卸载 Memcached" + else + cd /www/server/mdserver-web/plugins/memcached && bash install.sh uninstall + echo "Memcached 卸载完成!" + fi + fi +} + +UNINSTALL_REDIS() +{ + if [ -d /www/server/redis ];then + echo -e "----------------------------------------------------" + echo -e "检测到 Redis,卸载可能影响站点与数据" + echo -e "----------------------------------------------------" + read -p "输入 yes 强制卸载所有 Redis [yes/no]: " yes; + if [ "$yes" != "yes" ];then + echo -e "------------" + echo "已取消卸载 Redis" + else + cd /www/server/mdserver-web/plugins/redis && bash install.sh uninstall 7.0.4 + echo "Redis 卸载完成!" + fi + fi +} + +UNINSTALL_MW() +{ + echo -e "----------------------------------------------------" + echo -e "检测到面板环境,卸载可能影响站点与数据" + echo -e "----------------------------------------------------" + read -p "输入 yes 强制卸载面板(仅移除面板,不清理网站/数据库数据): " yes; + if [ "$yes" != "yes" ];then + echo -e "------------" + echo "已取消卸载面板" + else + rm -rf /usr/bin/mw + rm -rf /etc/init.d/mw + systemctl daemon-reload + rm -rf /www/server/mdserver-web + echo "面板卸载完成!" + fi +} + +UNINSTALL_CHECK + +UNINSTALL_OP +UNINSTALL_PHP +UNINSTALL_MySQL +UNINSTALL_MEMCACHED +UNINSTALL_REDIS +UNINSTALL_MW + +endTime=`date +%s` +((outTime=(${endTime}-${startTime})/60)) +echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 000000000..501b935d4 --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,240 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +# LANG=en_US.UTF-8 +is64bit=`getconf LONG_BIT` +REPO_OWNER="AndyXeCM" +REPO_NAME="PowerLinux" +REPO_BRANCH="master" + +startTime=`date +%s` + +if [ -f /www/server/mdserver-web/tools.py ];then + echo -e "检测到旧版代码,为避免翻车先停一下~" + echo -e "如确认继续,请先执行: rm -rf /www/server/mdserver-web" + echo -e "处理完再回来更新吧!需要帮忙可以随时再问我。" + exit 0 +fi + + +_os=`uname` +echo "检测到系统: ${_os}" + +if [ "$EUID" -ne 0 ] + then echo "需要使用 root 权限运行更新脚本,请用 sudo 或切换到 root 后重试~" + exit +fi + +if [ ${_os} != "Darwin" ] && [ ! -d /www/server/mdserver-web/logs ]; then + mkdir -p /www/server/mdserver-web/logs +fi + +LOG_FILE=/var/log/mw-update.log +{ + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn +fi + +if [ "$LOCAL_ADDR" != "common" ];then + declare -A PROXY_URL + PROXY_URL["github_do"]="https://github.do/" + PROXY_URL["gh_llkk_cc"]="https://gh.llkk.cc/https://" + PROXY_URL["gh_felicity_ac_cn"]="https://gh.felicity.ac.cn/https://" + PROXY_URL["ghfast_top"]="https://ghfast.top/" + PROXY_URL["ghproxy_net"]="https://ghproxy.net/" + PROXY_URL["gh_927223_xyz"]="https://gh.927223.xyz/https://" + PROXY_URL["gh_proxy_net"]="https://gh-proxy.net/" + + PROXY_URL["source"]="https://" + + + SOURCE_LIST_KEY_SORT_TMP=$(echo ${!PROXY_URL[@]} | tr ' ' '\n' | sort -n) + SOURCE_LIST_KEY=(${SOURCE_LIST_KEY_SORT_TMP//'\n'/}) + SOURCE_LIST_LEN=${#PROXY_URL[*]} +fi + + +function AutoSizeStr(){ + NAME_STR=$1 + NAME_NUM=$2 + + NAME_STR_LEN=`echo "$NAME_STR" | wc -L` + NAME_NUM_LEN=`echo "$NAME_NUM" | wc -L` + + fix_len=35 + remaining_len=`expr $fix_len - $NAME_STR_LEN - $NAME_NUM_LEN` + FIX_SPACE=' ' + for ((ass_i=1;ass_i<=$remaining_len;ass_i++)) + do + FIX_SPACE="$FIX_SPACE " + done + echo -e " ❖ ${1}${FIX_SPACE}${2})" +} + +function ChooseProxyURL(){ + clear + echo -e '+---------------------------------------------------+' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '| 欢迎使用 Linux 一键更新面板源码,马上开始吧! |' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '+---------------------------------------------------+' + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e ' 提供以下国内代理地址可供选择: ' + echo -e '' + echo -e '#####################################################' + echo -e '' + cm_i=0 + for V in ${SOURCE_LIST_KEY[@]}; do + num=`expr $cm_i + 1` + AutoSizeStr "${V}" "$num" + cm_i=`expr $cm_i + 1` + done + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e " 系统时间 ${BLUE}$(date "+%Y-%m-%d %H:%M:%S")${PLAIN}" + echo -e '' + echo -e '#####################################################' + CHOICE_A=$(echo -e "\n${BOLD}└─ 请选择并输入你想使用的代理地址 [ 1-${SOURCE_LIST_LEN} ](回车默认 1):${PLAIN}") + + read -p "${CHOICE_A}" INPUT + # echo $INPUT + if [ "$INPUT" == "" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n默认选择[${BLUE}${INPUT_KEY}${PLAIN}],准备开始更新~" + fi + + if [ "$INPUT" -lt "0" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n低于边界啦!已切回[${BLUE}${INPUT_KEY}${PLAIN}]继续更新~" + sleep 2s + fi + + if [ "$INPUT" -gt "${SOURCE_LIST_LEN}" ];then + INPUT=${SOURCE_LIST_LEN} + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n超出边界啦!已切回[${BLUE}${INPUT_KEY}${PLAIN}]继续更新~" + sleep 2s + fi + + INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$INPUT]} + HTTP_PREFIX=${PROXY_URL[$INPUT_KEY]} +} + + +if [ "$LOCAL_ADDR" != "common" ];then + ChooseProxyURL + + if [ "$HTTP_PREFIX" != "https://" ];then + DOMAIN=`echo $HTTP_PREFIX | sed 's|https://||g'` + DOMAIN=`echo $DOMAIN | sed 's|/||g'` + ping -c 3 $DOMAIN > /dev/null 2>&1 + if [ "$?" != "0" ];then + echo "无效代理地址:${DOMAIN}" + exit + fi + fi +fi + +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eqi "EulerOS" /etc/*-release || grep -Eqi "openEuler" /etc/*-release; then + OSNAME='euler' +elif grep -Eqi "FreeBSD" /etc/*-release; then + OSNAME='freebsd' +elif grep -Eqi "CentOS" /etc/issue || grep -Eqi "CentOS" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eqi "Fedora" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eqi "Rocky" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eqi "AlmaLinux" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "Anolis" /etc/issue || grep -Eqi "Anolis" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Amazon Linux" /etc/issue || grep -Eqi "Amazon Linux" /etc/*-release; then + OSNAME='amazon' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eqi "Debian" /etc/*-release; then + OSNAME='debian' + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eqi "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +elif grep -Eqi "Raspbian" /etc/issue || grep -Eqi "Raspbian" /etc/*-release; then + OSNAME='raspbian' +elif grep -Eqi "Alpine" /etc/issue || grep -Eqi "Alpine" /etc/*-release; then + OSNAME='alpine' + apk update + apk add devscripts -force-broken-world + apk add wget zip unzip tar -force-broken-world +else + OSNAME='unknow' +fi + +echo "LOCAL:${LOCAL_ADDR}" + +CP_CMD=/usr/bin/cp +if [ -f /bin/cp ];then + CP_CMD=/bin/cp +fi + +echo "开始更新面板源码,冲冲冲~" + +echo "下载 ${REPO_OWNER}/${REPO_NAME}(分支 ${REPO_BRANCH})..." +curl --insecure -sSLo /tmp/master.tar.gz ${HTTP_PREFIX}github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/heads/${REPO_BRANCH}.tar.gz +TARBALL_DIR=$(tar -tf /tmp/master.tar.gz | head -1 | cut -d/ -f1) +cd /tmp && tar -zxvf /tmp/master.tar.gz +$CP_CMD -rf /tmp/${TARBALL_DIR}/* /www/server/mdserver-web +rm -rf /tmp/master.tar.gz +rm -rf /tmp/${TARBALL_DIR} + +echo "源码更新完成,继续收尾~" + + +#pip uninstall public +echo "use system version: ${OSNAME}" +cd /www/server/mdserver-web && bash scripts/update/${OSNAME}.sh + +bash /etc/rc.d/init.d/mw restart +bash /etc/rc.d/init.d/mw default + +if [ -f /usr/bin/mw ];then + rm -rf /usr/bin/mw +fi + +if [ ! -e /usr/bin/mw ]; then + if [ ! -f /usr/bin/mw ];then + ln -s /etc/rc.d/init.d/mw /usr/bin/mw + fi +fi + +endTime=`date +%s` +((outTime=($endTime-$startTime)/60)) +echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" + +} 1> >(tee $LOG_FILE) 2>&1 diff --git a/scripts/update/alma.sh b/scripts/update/alma.sh new file mode 100755 index 000000000..3fecb6298 --- /dev/null +++ b/scripts/update/alma.sh @@ -0,0 +1,56 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=C.UTF-8 + + +if [ -f /etc/motd ];then + echo "welcome to mdserver-web panel" > /etc/motd +fi + +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "stop mw" +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` + +port=7200 +if [ -f /www/server/mdserver-web/data/port.pl ]; then + port=$(cat /www/server/mdserver-web/data/port.pl) +fi + +n=0 +while [[ "$isStart" != "" ]]; +do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done + + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" diff --git a/scripts/update/alpine.sh b/scripts/update/alpine.sh new file mode 100644 index 000000000..d0114e5bd --- /dev/null +++ b/scripts/update/alpine.sh @@ -0,0 +1,33 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + +# systemctl stop SuSEfirewall2 + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" + +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw stop +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw start diff --git a/scripts/update/amazon.sh b/scripts/update/amazon.sh new file mode 100755 index 000000000..e31d9d869 --- /dev/null +++ b/scripts/update/amazon.sh @@ -0,0 +1,58 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + +if [ -f /etc/motd ];then + echo "welcome to mdserver-web panel" > /etc/motd +fi + +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +yum install -y curl-devel libmcrypt libmcrypt-devel python3-devel + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "stop mw" +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` + +port=7200 +if [ -f /www/server/mdserver-web/data/port.pl ]; then + port=$(cat /www/server/mdserver-web/data/port.pl) +fi + +n=0 +while [[ "$isStart" != "" ]]; +do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done + + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" \ No newline at end of file diff --git a/scripts/update/arch.sh b/scripts/update/arch.sh new file mode 100644 index 000000000..edec63b06 --- /dev/null +++ b/scripts/update/arch.sh @@ -0,0 +1,35 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + + +# echo y | pacman -Sy yaourt +# echo y | pacman -Sy python3 + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" + +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw stop +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw start \ No newline at end of file diff --git a/scripts/update/centos.sh b/scripts/update/centos.sh new file mode 100755 index 000000000..1ea9387e1 --- /dev/null +++ b/scripts/update/centos.sh @@ -0,0 +1,58 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +export LANG=en_US.UTF-8 + + +if [ -f /etc/motd ];then + echo "welcome to mdserver-web panel" > /etc/motd +fi + +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +yum install -y curl-devel libmcrypt libmcrypt-devel python3-devel + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "stop mw" +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` + +port=7200 +if [ -f /www/server/mdserver-web/data/port.pl ]; then + port=$(cat /www/server/mdserver-web/data/port.pl) +fi + +n=0 +while [[ "$isStart" != "" ]]; +do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done + + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" \ No newline at end of file diff --git a/scripts/update/debian.sh b/scripts/update/debian.sh new file mode 100644 index 000000000..dcc663cac --- /dev/null +++ b/scripts/update/debian.sh @@ -0,0 +1,85 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +export DEBIAN_FRONTEND=noninteractive + +apt autoremove -y +apt install -y locate +if [ ! -d /usr/share/locale ];then + mkdir -d /usr/share/locale +fi +locale-gen en_US.UTF-8 +locale-gen zh_CN.UTF-8 +localedef -v -c -i en_US -f UTF-8 en_US.UTF-8 > /dev/null 2>&1 +export LC_CTYPE=en_US.UTF-8 +export LC_ALL=en_US.UTF-8 +export LANG=en_US.UTF-8 + +# echo "LC_ALL=en_US.UTF-8" > /etc/default/locale +# echo "LANG=en_US.UTF-8" > /etc/default/locale + + +if grep -Eq "Ubuntu" /etc/*-release; then + sudo ln -sf /bin/bash /bin/sh + #sudo dpkg-reconfigure dash +fi + +VERSION_ID=`cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F "\"" '{print $2}'` +if [ "$VERSION_ID" == "9" ];then + sed "s/flask==2.0.3/flask==1.1.1/g" -i /www/server/mdserver-web/requirements.txt + sed "s/cryptography==3.3.2/cryptography==2.5/g" -i /www/server/mdserver-web/requirements.txt + sed "s/configparser==5.2.0/configparser==4.0.2/g" -i /www/server/mdserver-web/requirements.txt + sed "s/flask-socketio==5.2.0/flask-socketio==4.2.0/g" -i /www/server/mdserver-web/requirements.txt + sed "s/python-engineio==4.3.2/python-engineio==3.9.0/g" -i /www/server/mdserver-web/requirements.txt + # pip3 install -r /www/server/mdserver-web/requirements.txt +fi + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + + +if [ -f /etc/rc.d/init.d/mw ];then + if [ -f /usr/bin/mw ];then + rm -rf /usr/bin/mw + fi + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "stop mw" +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` + +port=7200 +if [ -f /www/server/mdserver-web/data/port.pl ];then + port=$(cat /www/server/mdserver-web/data/port.pl) +fi + +n=0 +while [[ "$isStart" != "" ]]; +do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done + + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" + +systemctl daemon-reload diff --git a/scripts/update/euler.sh b/scripts/update/euler.sh new file mode 100755 index 000000000..1ea9387e1 --- /dev/null +++ b/scripts/update/euler.sh @@ -0,0 +1,58 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +export LANG=en_US.UTF-8 + + +if [ -f /etc/motd ];then + echo "welcome to mdserver-web panel" > /etc/motd +fi + +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +yum install -y curl-devel libmcrypt libmcrypt-devel python3-devel + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "stop mw" +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` + +port=7200 +if [ -f /www/server/mdserver-web/data/port.pl ]; then + port=$(cat /www/server/mdserver-web/data/port.pl) +fi + +n=0 +while [[ "$isStart" != "" ]]; +do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done + + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" \ No newline at end of file diff --git a/scripts/update/fedora.sh b/scripts/update/fedora.sh new file mode 100644 index 000000000..715239e0b --- /dev/null +++ b/scripts/update/fedora.sh @@ -0,0 +1,55 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + +if [ -f /etc/motd ];then + echo "welcome to mdserver-web panel" > /etc/motd +fi + +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "stop mw" +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` + +port=7200 +if [ -f /www/server/mdserver-web/data/port.pl ];then + port=$(cat /www/server/mdserver-web/data/port.pl) +fi + +n=0 +while [[ "$isStart" != "" ]]; +do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done + + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" diff --git a/scripts/update/freebsd.sh b/scripts/update/freebsd.sh new file mode 100644 index 000000000..bf519f07a --- /dev/null +++ b/scripts/update/freebsd.sh @@ -0,0 +1,32 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -aux|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" + +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw stop +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw start diff --git a/scripts/update/macos.sh b/scripts/update/macos.sh new file mode 100644 index 000000000..c90a59b5a --- /dev/null +++ b/scripts/update/macos.sh @@ -0,0 +1,7 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +echo 'The development environment only needs to be downloaded again!' +exit 0 \ No newline at end of file diff --git a/scripts/update/opensuse.sh b/scripts/update/opensuse.sh new file mode 100644 index 000000000..65bce9b2f --- /dev/null +++ b/scripts/update/opensuse.sh @@ -0,0 +1,35 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +# zypper refresh + + +# systemctl stop SuSEfirewall2 + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" + +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw stop +cd /www/server/mdserver-web && bash /etc/rc.d/init.d/mw start diff --git a/scripts/update/rhel.sh b/scripts/update/rhel.sh new file mode 100644 index 000000000..514c2c497 --- /dev/null +++ b/scripts/update/rhel.sh @@ -0,0 +1,52 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +export LANG=en_US.UTF-8 + +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "stop mw" +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` + +port=7200 +if [ -f /www/server/mdserver-web/data/port.pl ]; then + port=$(cat /www/server/mdserver-web/data/port.pl) +fi + +n=0 +while [[ "$isStart" != "" ]]; +do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done + + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" \ No newline at end of file diff --git a/scripts/update/rocky.sh b/scripts/update/rocky.sh new file mode 100755 index 000000000..408ae8915 --- /dev/null +++ b/scripts/update/rocky.sh @@ -0,0 +1,59 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + + +if [ -f /etc/motd ];then + echo "welcome to mdserver-web panel" > /etc/motd +fi + +sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config + + + +yum install -y curl-devel libmcrypt libmcrypt-devel python36-devel + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + + +if [ -f /etc/rc.d/init.d/mw ];then + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "stop mw" +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` + +port=7200 +if [ -f /www/server/mdserver-web/data/port.pl ]; then + port=$(cat /www/server/mdserver-web/data/port.pl) +fi + +n=0 +while [[ "$isStart" != "" ]]; +do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done + + +echo -e "start mw" +cd /www/server/mdserver-web && sh cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/init.d/mw ]]; +do + echo -e ".\c" + sleep 0.5 + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done +echo -e "start mw success" \ No newline at end of file diff --git a/scripts/update/ubuntu.sh b/scripts/update/ubuntu.sh new file mode 100644 index 000000000..1ee4def96 --- /dev/null +++ b/scripts/update/ubuntu.sh @@ -0,0 +1,62 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +export LANG=en_US.UTF-8 +export DEBIAN_FRONTEND=noninteractive + +# localedef -v -c -i en_US -f UTF-8 en_US.UTF-8 + +if grep -Eq "Ubuntu" /etc/*-release; then + sudo ln -sf /bin/bash /bin/sh + #sudo dpkg-reconfigure dash +fi + + +cd /www/server/mdserver-web/scripts && bash lib.sh +chmod 755 /www/server/mdserver-web/data + + +if [ -f /etc/rc.d/init.d/mw ];then + if [ -f /usr/bin/mw ];then + rm -rf /usr/bin/mw + fi + bash /etc/rc.d/init.d/mw stop && rm -rf /www/server/mdserver-web/scripts/init.d/mw && rm -rf /etc/rc.d/init.d/mw +fi + +echo -e "stop mw" +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +port=7200 + +if [ -f /www/server/mdserver-web/data/port.pl ];then + port=$(cat /www/server/mdserver-web/data/port.pl) +fi +n=0 +while [[ "$isStart" != "" ]]; +do + echo -e ".\c" + sleep 0.5 + isStart=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $2}'|xargs) + let n+=1 + if [ $n -gt 15 ];then + break; + fi +done + + +echo -e "start mw" +cd /www/server/mdserver-web && bash cli.sh start +isStart=`ps -ef|grep 'gunicorn -c setting.py app:app' |grep -v grep|awk '{print $2}'` +n=0 +while [[ ! -f /etc/rc.d/init.d/mw ]]; +do + echo -e ".\c" + sleep 1 + let n+=1 + if [ $n -gt 20 ];then + echo -e "start mw fail" + exit 1 + fi +done +echo -e "start mw success" + +systemctl daemon-reload diff --git a/scripts/update/unknow.sh b/scripts/update/unknow.sh new file mode 100644 index 000000000..c9d5b5ca3 --- /dev/null +++ b/scripts/update/unknow.sh @@ -0,0 +1,6 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH +LANG=en_US.UTF-8 + +echo "update unkown server!!!" \ No newline at end of file diff --git a/scripts/update_dev.sh b/scripts/update_dev.sh new file mode 100755 index 000000000..7fc281e8f --- /dev/null +++ b/scripts/update_dev.sh @@ -0,0 +1,253 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin +export PATH + +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +PLAIN='\033[0m' +BOLD='\033[1m' +SUCCESS='[\033[32mOK\033[0m]' +COMPLETE='[\033[32mDONE\033[0m]' +WARN='[\033[33mWARN\033[0m]' +ERROR='[\033[31mERROR\033[0m]' +WORKING='[\033[34m*\033[0m]' + +# LANG=en_US.UTF-8 +is64bit=`getconf LONG_BIT` + +startTime=`date +%s` + +if [ -f /www/server/mdserver-web/tools.py ];then + echo -e "存在旧版代码,不能安装!,已知风险的情况下" + echo -e "rm -rf /www/server/mdserver-web" + echo -e "可安装!" + exit 0 +fi + +_os=`uname` +echo "use system: ${_os}" + +if [ "$EUID" -ne 0 ] + then echo "Please run as root!" + exit +fi + +if [ ${_os} != "Darwin" ] && [ ! -d /www/server/mdserver-web/logs ]; then + mkdir -p /www/server/mdserver-web/logs +fi + +LOG_FILE=/var/log/mw-update.log + +{ + +HTTP_PREFIX="https://" +LOCAL_ADDR=common +cn=$(curl -fsSL -m 10 -s http://ipinfo.io/json | grep "\"country\": \"CN\"") +if [ ! -z "$cn" ] || [ "$?" == "0" ] ;then + LOCAL_ADDR=cn +fi + +if [ "$LOCAL_ADDR" != "common" ];then + declare -A PROXY_URL + PROXY_URL["github_do"]="https://github.do/" + PROXY_URL["gh_llkk_cc"]="https://gh.llkk.cc/https://" + PROXY_URL["gh_felicity_ac_cn"]="https://gh.felicity.ac.cn/https://" + PROXY_URL["ghfast_top"]="https://ghfast.top/" + PROXY_URL["ghproxy_net"]="https://ghproxy.net/" + PROXY_URL["gh_927223_xyz"]="https://gh.927223.xyz/https://" + PROXY_URL["gh_proxy_net"]="https://gh-proxy.net/" + + PROXY_URL["source"]="https://" + + + SOURCE_LIST_KEY_SORT_TMP=$(echo ${!PROXY_URL[@]} | tr ' ' '\n' | sort -n) + SOURCE_LIST_KEY=(${SOURCE_LIST_KEY_SORT_TMP//'\n'/}) + SOURCE_LIST_LEN=${#PROXY_URL[*]} +fi + + +function AutoSizeStr(){ + NAME_STR=$1 + NAME_NUM=$2 + + NAME_STR_LEN=`echo "$NAME_STR" | wc -L` + NAME_NUM_LEN=`echo "$NAME_NUM" | wc -L` + + fix_len=35 + remaining_len=`expr $fix_len - $NAME_STR_LEN - $NAME_NUM_LEN` + FIX_SPACE=' ' + for ((ass_i=1;ass_i<=$remaining_len;ass_i++)) + do + FIX_SPACE="$FIX_SPACE " + done + echo -e " ❖ ${1}${FIX_SPACE}${2})" +} + +function ChooseProxyURL(){ + clear + echo -e '+---------------------------------------------------+' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '| 欢迎使用 Linux 一键安装mdserver-web面板源码 |' + echo -e '| |' + echo -e '| ============================================= |' + echo -e '| |' + echo -e '+---------------------------------------------------+' + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e ' 提供以下国内代理地址可供选择: ' + echo -e '' + echo -e '#####################################################' + echo -e '' + cm_i=0 + for V in ${SOURCE_LIST_KEY[@]}; do + num=`expr $cm_i + 1` + AutoSizeStr "${V}" "$num" + cm_i=`expr $cm_i + 1` + done + echo -e '' + echo -e '#####################################################' + echo -e '' + echo -e " 系统时间 ${BLUE}$(date "+%Y-%m-%d %H:%M:%S")${PLAIN}" + echo -e '' + echo -e '#####################################################' + CHOICE_A=$(echo -e "\n${BOLD}└─ 请选择并输入你想使用的代理地址 [ 1-${SOURCE_LIST_LEN} ]:${PLAIN}") + + read -p "${CHOICE_A}" INPUT + # echo $INPUT + if [ "$INPUT" == "" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n默认选择[${BLUE}${INPUT_KEY}${PLAIN}]安装!" + fi + + if [ "$INPUT" -lt "0" ];then + INPUT=1 + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n低于边界错误!选择[${BLUE}${INPUT_KEY}${PLAIN}]安装!" + sleep 2s + fi + + if [ "$INPUT" -gt "${SOURCE_LIST_LEN}" ];then + INPUT=${SOURCE_LIST_LEN} + TMP_INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$TMP_INPUT]} + echo -e "\n超出边界错误!选择[${BLUE}${INPUT_KEY}${PLAIN}]安装!" + sleep 2s + fi + + INPUT=`expr $INPUT - 1` + INPUT_KEY=${SOURCE_LIST_KEY[$INPUT]} + HTTP_PREFIX=${PROXY_URL[$INPUT_KEY]} +} + + +if [ "$LOCAL_ADDR" != "common" ];then + ChooseProxyURL + + if [ "$HTTP_PREFIX" != "https://" ];then + DOMAIN=`echo $HTTP_PREFIX | sed 's|https://||g'` + DOMAIN=`echo $DOMAIN | sed 's|/||g'` + ping -c 3 $DOMAIN > /dev/null 2>&1 + if [ "$?" != "0" ];then + echo "无效代理地址:${DOMAIN}" + exit + fi + fi +fi + +if [ ${_os} == "Darwin" ]; then + OSNAME='macos' +elif grep -Eqi "openSUSE" /etc/*-release; then + OSNAME='opensuse' + zypper refresh +elif grep -Eqi "EulerOS" /etc/*-release || grep -Eqi "openEuler" /etc/*-release; then + OSNAME='euler' +elif grep -Eqi "FreeBSD" /etc/*-release; then + OSNAME='freebsd' +elif grep -Eqi "CentOS" /etc/issue || grep -Eqi "CentOS" /etc/*-release; then + OSNAME='centos' + yum install -y wget zip unzip +elif grep -Eqi "Fedora" /etc/issue || grep -Eqi "Fedora" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "Rocky" /etc/issue || grep -Eqi "Rocky" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "AlmaLinux" /etc/issue || grep -Eqi "AlmaLinux" /etc/*-release; then + OSNAME='rhel' + yum install -y wget zip unzip +elif grep -Eqi "Anolis" /etc/issue || grep -Eqi "Anolis" /etc/*-release; then + OSNAME='rhel' + yum install -y wget curl zip unzip tar crontabs +elif grep -Eqi "Amazon Linux" /etc/issue || grep -Eqi "Amazon Linux" /etc/*-release; then + OSNAME='amazon' + yum install -y wget zip unzip +elif grep -Eqi "Debian" /etc/issue || grep -Eqi "Debian" /etc/*-release; then + OSNAME='debian' + apt install -y wget zip unzip +elif grep -Eqi "Ubuntu" /etc/issue || grep -Eqi "Ubuntu" /etc/*-release; then + OSNAME='ubuntu' + apt install -y wget zip unzip +elif grep -Eqi "Raspbian" /etc/issue || grep -Eqi "Raspbian" /etc/*-release; then + OSNAME='raspbian' +elif grep -Eqi "Alpine" /etc/issue || grep -Eqi "Alpine" /etc/*-release; then + OSNAME='alpine' +else + OSNAME='unknow' +fi + +echo "local:${LOCAL_ADDR}" + +CP_CMD=/usr/bin/cp +if [ -f /bin/cp ];then + CP_CMD=/bin/cp +fi + +if [ -f /tmp/dev.tar.gz ];then + rm -rf /tmp/dev.tar.gz +fi + +if [ -d /tmp/mdserver-web-dev ];then + rm -rf /tmp/mdserver-web-dev +fi + +echo "update mdserver-web dev code start" + +curl --insecure -sSLo /tmp/dev.tar.gz ${HTTP_PREFIX}github.com/midoks/mdserver-web/archive/refs/heads/dev.tar.gz +cd /tmp && tar -zxvf /tmp/dev.tar.gz +$CP_CMD -rf /tmp/mdserver-web-dev/* /www/server/mdserver-web +rm -rf /tmp/dev.tar.gz +rm -rf /tmp/mdserver-web-dev + +echo "update mdserver-web dev code end" + + +#pip uninstall public +echo "use system version: ${OSNAME}" +cd /www/server/mdserver-web && bash scripts/update/${OSNAME}.sh + +bash /etc/rc.d/init.d/mw restart +bash /etc/rc.d/init.d/mw default + +if [ -f /usr/bin/mw ];then + rm -rf /usr/bin/mw +fi + +if [ ! -e /usr/bin/mw ]; then + if [ ! -f /usr/bin/mw ];then + ln -s /etc/rc.d/init.d/mw /usr/bin/mw + fi +fi + +endTime=`date +%s` +((outTime=($endTime-$startTime)/60)) +echo -e "Time consumed:\033[32m $outTime \033[0mMinute!" + +} 1> >(tee $LOG_FILE) 2>&1 \ No newline at end of file diff --git a/ssl/README.md b/ssl/README.md new file mode 100644 index 000000000..39c380ce8 --- /dev/null +++ b/ssl/README.md @@ -0,0 +1 @@ +# 数据目录 \ No newline at end of file diff --git a/version/r3.6.txt b/version/r3.6.txt new file mode 100644 index 000000000..0863401ee --- /dev/null +++ b/version/r3.6.txt @@ -0,0 +1,39 @@ +cryptography==36.0.1 +flask==2.0.3 +pyOpenSSL==22.0.0 +requests==2.27.1 +gevent==22.10.2 +gunicorn==21.2.0 +setuptools>=33.1.1 +Werkzeug>=1.0.1,<3.0.0 +wheel>=0.37.1 +requests>=2.27.1 +urllib3>=1.21.1 +flask-session==0.3.2 +flask-helper==0.19 +flask-bcrypt==1.0.1 +flask-caching>=1.10.1 +cache==1.0.3 +gevent-websocket==0.10.1 +psutil==5.9.1 +chardet==3.0.4 +flask-sqlalchemy==2.3.2 +configparser==5.2.0 +python-engineio==4.3.2 +python-socketio>=4.2.0 +flask-socketio==5.2.0 +flask-sockets==0.2.1 +zmq==0.0.0 +paramiko>=2.8.0 +pymongo +pymemcache +redis +pillow +Jinja2>=2.11.2 +PyMySQL==1.0.2 +whitenoise==5.3.0 +pyotp +pytz +pyTelegramBotAPI +telebot +pyyaml \ No newline at end of file diff --git a/version/r3.7.txt b/version/r3.7.txt new file mode 100644 index 000000000..0863401ee --- /dev/null +++ b/version/r3.7.txt @@ -0,0 +1,39 @@ +cryptography==36.0.1 +flask==2.0.3 +pyOpenSSL==22.0.0 +requests==2.27.1 +gevent==22.10.2 +gunicorn==21.2.0 +setuptools>=33.1.1 +Werkzeug>=1.0.1,<3.0.0 +wheel>=0.37.1 +requests>=2.27.1 +urllib3>=1.21.1 +flask-session==0.3.2 +flask-helper==0.19 +flask-bcrypt==1.0.1 +flask-caching>=1.10.1 +cache==1.0.3 +gevent-websocket==0.10.1 +psutil==5.9.1 +chardet==3.0.4 +flask-sqlalchemy==2.3.2 +configparser==5.2.0 +python-engineio==4.3.2 +python-socketio>=4.2.0 +flask-socketio==5.2.0 +flask-sockets==0.2.1 +zmq==0.0.0 +paramiko>=2.8.0 +pymongo +pymemcache +redis +pillow +Jinja2>=2.11.2 +PyMySQL==1.0.2 +whitenoise==5.3.0 +pyotp +pytz +pyTelegramBotAPI +telebot +pyyaml \ No newline at end of file diff --git a/web/admin/__init__.py b/web/admin/__init__.py new file mode 100644 index 000000000..0af3e9bc8 --- /dev/null +++ b/web/admin/__init__.py @@ -0,0 +1,212 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import sys +import json +import time +import uuid +import logging + +from datetime import timedelta + +from flask import Flask +from flask import request +from flask import redirect +from flask import Response +from flask import Flask, abort, current_app, session, url_for +from flask import Blueprint, render_template +from flask import render_template_string + +from flask_socketio import SocketIO, emit, send + +from flask_caching import Cache +from werkzeug.local import LocalProxy + + +from admin.common import isLogined + +import core.mw as mw +import config +import utils.config as utils_config +import thisdb + +# 初始化db +from admin import setup +setup.init() + +app = Flask(__name__, template_folder='templates/default') + +# 缓存配置 +cache = Cache(config={'CACHE_TYPE': 'simple'}) +cache.init_app(app, config={'CACHE_TYPE': 'simple'}) + +# 静态文件配置 +from whitenoise import WhiteNoise +app.wsgi_app = WhiteNoise(app.wsgi_app, root="../web/static/", prefix="static/", max_age=604800) +app.jinja_env.trim_blocks = True + +# session配置 +# app.secret_key = uuid.UUID(int=uuid.getnode()).hex[-12:] +app.config['SECRET_KEY'] = uuid.UUID(int=uuid.getnode()).hex[-12:] + +# app.config['sessions'] = dict() +app.config['SESSION_PERMANENT'] = True +app.config['SESSION_USE_SIGNER'] = True +app.config['SESSION_KEY_PREFIX'] = 'MW_:' +app.config['SESSION_COOKIE_NAME'] = "MW_VER_1" +app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=31) +app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 604800 + +# db的配置 +# app.config['SQLALCHEMY_DATABASE_URI'] = mw.getSqitePrefix()+config.SQLITE_PATH+"?timeout=20" # 使用 SQLite 数据库 +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True + +# BASIC AUTH +app.config['BASIC_AUTH_OPEN'] = False +try: + basic_auth = thisdb.getOptionByJson('basic_auth', default={'open':False}) + if basic_auth['open']: + app.config['BASIC_AUTH_OPEN'] = True +except Exception as e: + pass + +# 加载模块 +from .submodules import get_submodules +for module in get_submodules(): + app.logger.info('Registering blueprint module: %s' % module) + if app.blueprints.get(module.name) is None: + app.register_blueprint(module) + +def sendAuthenticated(): + # 发送http认证信息 + request_host = mw.getHostAddr() + result = Response('', 401, {'WWW-Authenticate': 'Basic realm="%s"' % request_host.strip()}) + if not 'login' in session and not 'admin_auth' in session: + session.clear() + return result + +@app.before_request +def requestCheck(): + request.start_time = time.time() + + admin_close = thisdb.getOption('admin_close') + if admin_close == 'yes': + if not request.path.startswith('/close'): + return redirect('/close') + # 自定义basic auth认证 + if app.config['BASIC_AUTH_OPEN']: + basic_auth = thisdb.getOptionByJson('basic_auth', default={'open':False}) + if not basic_auth['open']: + return + + auth = request.authorization + if request.path in ['/download', '/hook', '/down']: + return + if not auth: + return sendAuthenticated() + + salt = basic_auth['salt'] + basic_user = mw.md5(auth.username.strip() + salt) + basic_pwd = mw.md5(auth.password.strip() + salt) + if basic_user != basic_auth['basic_user'] or basic_pwd != basic_auth['basic_pwd']: + return sendAuthenticated() + + # domain_check = mw.checkDomainPanel() + # if domain_check: + # return domain_check + + + +@app.after_request +def requestAfter(response): + response.headers['soft'] = config.APP_NAME + response.headers['mw-version'] = config.APP_VERSION + response.headers['X-Response-Time'] = round(time.time() - request.start_time, 4) + return response + + +@app.errorhandler(404) +def page_unauthorized(error): + from flask import redirect + return redirect('/', code=302) + # return render_template_string('404 not found', error_info=error), 404 + + +# 设置模板全局变量 +@app.context_processor +def inject_global_variables(): + app_ver = config.APP_VERSION + if mw.isDebugMode(): + app_ver = app_ver + str(time.time()) + + data = utils_config.getGlobalVar() + g_config = { + 'version': app_ver, + 'title' : data['title'], + 'ip' : data['ip'] + } + return dict(config=g_config, data=data) + +# webssh +# socketio = SocketIO(manage_session=False, async_mode='threading', +# logger=False, engineio_logger=False, debug=False, +# ping_interval=25, ping_timeout=120) +socketio = SocketIO(logger=False, + engineio_logger=False, + cors_allowed_origins="*") +socketio.init_app(app) + +@socketio.on('webssh_websocketio') +def webssh_websocketio(data): + if not isLogined(): + emit('server_response', {'data': '会话丢失,请重新登陆面板!\r\n'}) + return + import utils.ssh.ssh_terminal as ssh_terminal + shell_client = ssh_terminal.ssh_terminal.instance() + shell_client.run(request.sid, data) + return + + +@socketio.on('webssh') +def webssh(data): + if not isLogined(): + emit('server_response', {'data': '会话丢失,请重新登陆面板!\r\n'}) + return None + + import utils.ssh.ssh_local as ssh_local + shell = ssh_local.ssh_local.instance() + shell.run(data) + return + + +# File logging +logger = logging.getLogger('werkzeug') +logger.setLevel(config.CONSOLE_LOG_LEVEL) + +from utils.enhanced_log_rotation import EnhancedRotatingFileHandler +fh = EnhancedRotatingFileHandler(config.LOG_FILE, + config.LOG_ROTATION_SIZE, + config.LOG_ROTATION_AGE, + config.LOG_ROTATION_MAX_LOG_FILES) +fh.setLevel(config.FILE_LOG_LEVEL) +app.logger.addHandler(fh) +logger.addHandler(fh) + +# Console logging +ch = logging.StreamHandler() +ch.setLevel(config.CONSOLE_LOG_LEVEL) +ch.setFormatter(logging.Formatter(config.CONSOLE_LOG_FORMAT)) + +# Log the startup +app.logger.info('########################################################') +app.logger.info('Starting %s v%s...', config.APP_NAME, config.APP_VERSION) +app.logger.info('########################################################') +app.logger.debug("Python syspath: %s", sys.path) diff --git a/web/admin/common.py b/web/admin/common.py new file mode 100644 index 000000000..235bbbdac --- /dev/null +++ b/web/admin/common.py @@ -0,0 +1,37 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import time + +from admin import session +import thisdb + +def isLogined(): + if 'login' in session and session['login'] == True and 'username' in session: + username = session['username'] + info = thisdb.getUserByName(username) + if info is None: + return False + + # print(userInfo) + if info['name'] != session['username']: + return False + + now_time = int(time.time()) + + if 'overdue' in session and now_time > session['overdue']: + # 自动续期 + session['overdue'] = int(time.time()) + 7 * 24 * 60 * 60 + return False + + if 'tmp_login_expire' in session and now_time > int(session['tmp_login_expire']): + session.clear() + return False + return True \ No newline at end of file diff --git a/web/admin/crontab/__init__.py b/web/admin/crontab/__init__.py new file mode 100644 index 000000000..b43c89e58 --- /dev/null +++ b/web/admin/crontab/__init__.py @@ -0,0 +1,131 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.crontab import crontab as MwCrontab +import core.mw as mw +import thisdb + +blueprint = Blueprint('crontab', __name__, url_prefix='/crontab', template_folder='../../templates') +@blueprint.route('/index', endpoint='index') +@panel_login_required +def index(): + name = thisdb.getOption('template', default='default') + return render_template('%s/crontab.html' % name) + +# 计划任务列表 +@blueprint.route('/list', endpoint='list', methods=['POST']) +@panel_login_required +def list(): + page = request.args.get('p', '1').strip() + limit = request.args.get('limit', '10').strip() + return MwCrontab.instance().getCrontabList(page=int(page),size=int(limit)) + +# 计划任务日志 +@blueprint.route('/logs', endpoint='logs', methods=['POST']) +def logs(): + cron_id = request.form.get('id', '') + return MwCrontab.instance().cronLog(cron_id) + +# 删除计划任务 +@blueprint.route('/del', endpoint='del', methods=['POST']) +def crontab_del(): + cron_id = request.form.get('id', '') + return MwCrontab.instance().delete(cron_id) + +# 删除计划任务日志 +@blueprint.route('/del_logs', endpoint='del_logs', methods=['POST']) +def del_logs(): + cron_id = request.form.get('id', '') + return MwCrontab.instance().delLogs(cron_id) + + +# 设置计划任务状态 +@blueprint.route('/set_cron_status', endpoint='set_cron_status', methods=['POST']) +def set_cron_status(): + cron_id = request.form.get('id', '') + return MwCrontab.instance().setCronStatus(cron_id) + +# 设置计划任务状态 +@blueprint.route('/get_data_list', endpoint='get_data_list', methods=['POST']) +def get_data_list(): + stype = request.form.get('type', '') + return MwCrontab.instance().getDataList(stype) + + +# 获取计划任务 +@blueprint.route('/get_crond_find', endpoint='get_crond_find', methods=['POST']) +def get_crond_find(): + cron_id = request.form.get('id', '') + data = MwCrontab.instance().getCrondFind(cron_id) + return data + +# 修改计划任务 +@blueprint.route('/modify_crond', endpoint='modify_crond', methods=['POST']) +def modify_crond(): + request_data = {} + + request_data['name'] = request.form.get('name', '') + request_data['type'] = request.form.get('type', '') + request_data['week'] = request.form.get('week', '') + request_data['where1'] = request.form.get('where1', '') + request_data['hour'] = request.form.get('hour', '') + request_data['minute'] = request.form.get('minute', '') + request_data['save'] = request.form.get('save', '') + request_data['backup_to'] = request.form.get('backup_to', '') + request_data['stype'] = request.form.get('stype', '') + request_data['sname'] = request.form.get('sname', '') + request_data['sbody'] = request.form.get('sbody', '') + request_data['url_address'] = request.form.get('url_address', '') + request_data['attr'] = request.form.get('attr', '') + cron_id = request.form.get('id', '') + data = MwCrontab.instance().modifyCrond(cron_id,request_data) + return data + +# 执行计划任务 +@blueprint.route('/start_task', endpoint='start_task', methods=['POST']) +def start_task(): + cron_id = request.form.get('id', '') + return MwCrontab.instance().startTask(cron_id) + +# 添加计划任务 +@blueprint.route('/add', endpoint='add', methods=['POST']) +@panel_login_required +def add(): + request_data = {} + request_data['name'] = request.form.get('name', '') + request_data['type'] = request.form.get('type', '') + request_data['week'] = request.form.get('week', '') + request_data['where1'] = request.form.get('where1', '') + request_data['hour'] = request.form.get('hour', '') + request_data['minute'] = request.form.get('minute', '') + request_data['save'] = request.form.get('save', '') + request_data['backup_to'] = request.form.get('backup_to', '') + request_data['stype'] = request.form.get('stype', '') + request_data['sname'] = request.form.get('sname', '') + request_data['sbody'] = request.form.get('sbody', '') + request_data['url_address'] = request.form.get('url_address', '') + request_data['attr'] = request.form.get('attr', '') + + info = thisdb.getCronByName(request_data['name']) + if info is not None: + return mw.returnData(False, '任务名称重复') + + cron_id = MwCrontab.instance().add(request_data) + if cron_id > 0: + return mw.returnData(True, '添加成功') + return mw.returnData(False, '添加失败') + + diff --git a/web/admin/dashboard/__init__.py b/web/admin/dashboard/__init__.py new file mode 100644 index 000000000..77a962a50 --- /dev/null +++ b/web/admin/dashboard/__init__.py @@ -0,0 +1,13 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +from .dashboard import * +from .login import * diff --git a/web/admin/dashboard/dashboard.py b/web/admin/dashboard/dashboard.py new file mode 100644 index 000000000..b047eaf96 --- /dev/null +++ b/web/admin/dashboard/dashboard.py @@ -0,0 +1,140 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import io +import time +import base64 +import json +import os +import sys + +from flask import Blueprint, render_template +from flask import make_response +from flask import redirect +from flask import Response +from flask import request,g + +from admin.common import isLogined +from admin.user_login_check import panel_login_required +from admin import cache,session + +import core.mw as mw +import thisdb +import utils.system as usys + + +blueprint = Blueprint('dashboard', __name__, url_prefix='/', template_folder='../../templates') +@blueprint.route('/', endpoint='index', methods=['GET']) +@panel_login_required +def index(): + name = thisdb.getOption('template', default='default') + return render_template('%s/index.html' % name) + + +@blueprint.route('/overview_stats', endpoint='overview_stats', methods=['GET']) +@panel_login_required +def overview_stats(): + mem_info = usys.getMemInfo() + cpu_info = usys.getCpuInfo(interval=0) + + mem_total_gb = 0 + try: + mem_total_gb = round(float(mem_info.get('memTotal', 0)) / (1024 * 1024 * 1024), 2) + except Exception: + mem_total_gb = 0 + + data = { + 'site_count': thisdb.getSitesCount(), + 'pending_task_count': thisdb.getTaskUnexecutedCount(), + 'crontab_count': mw.M('crontab').count(), + 'firewall_count': mw.M('firewall').count(), + 'enabled_app_count': mw.M('app').where('status=?', (1,)).count(), + 'cpu_num': cpu_info[1] if isinstance(cpu_info, (list, tuple)) and len(cpu_info) > 1 else 0, + 'mem_total_gb': mem_total_gb, + } + return mw.returnData(True, 'ok', data) + +# 安全路径 +@blueprint.route('/',endpoint='admin_safe_path',methods=['GET']) +def admin_safe_path(path): + login = request.args.get('login', '') + if login != '': + try: + # print(login) + login_str = base64.b64decode(login) + login_str = login_str.decode('utf-8') + data = json.loads(login_str) + + time_now = time.time() * 1000 + time_diff = time_now - data['time'] + + if time_diff > 2000: + return redirect('/') + + + info = thisdb.getUserByName(data['username']) + if info is None: + return redirect('/') + + if info['password'] != mw.md5(data['password']): + return redirect('/') + + session['login'] = True + session['username'] = info['name'] + session['overdue'] = int(time.time()) + 7 * 24 * 60 * 60 + + thisdb.updateUserLoginTime() + return redirect('/') + except Exception as e: + pass + + + db_path = thisdb.getOption('admin_path') + name = thisdb.getOption('template', default='default') + if isLogined(): + return redirect('/') + if db_path == path: + return render_template('%s/login.html' % name) + + unauthorized_status = thisdb.getOption('unauthorized_status') + if unauthorized_status == '0': + return render_template('%s/path.html' % name) + return Response(status=int(unauthorized_status)) + +# 仅针对webhook插件 +@blueprint.route("/hook", methods=['POST', 'GET']) +def webhook(): + # 兼容获取关键数据 + access_key = request.args.get('access_key', '').strip() + if access_key == '': + access_key = request.form.get('access_key', '').strip() + + params = request.args.get('params', '').strip() + if params == '': + params = request.form.get('params', '').strip() + + input_args = { + 'access_key': access_key, + 'params': params, + } + + wh_install_path = mw.getServerDir() + '/webhook' + if not os.path.exists(wh_install_path): + return mw.returnData(False, '请先安装WebHook插件!') + + package = mw.getPanelDir() + "/plugins/webhook" + if not package in sys.path: + sys.path.append(package) + + try: + import webhook_index + return webhook_index.runShellArgs(input_args) + except Exception as e: + return str(e) diff --git a/web/admin/dashboard/login.py b/web/admin/dashboard/login.py new file mode 100644 index 000000000..fdeb25508 --- /dev/null +++ b/web/admin/dashboard/login.py @@ -0,0 +1,248 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import io +import time + +from flask import Blueprint, render_template +from flask import make_response +from flask import redirect +from flask import Response +from flask import request,g + +from admin.common import isLogined +from admin.user_login_check import panel_login_required +from admin import cache,session + +import core.mw as mw +import thisdb + +from .dashboard import blueprint + + +def getErrorNum(key, limit=None): + key = mw.md5(key) + num = cache.get(key) + if not num: + num = 0 + if not limit: + return num + if limit > num: + return True + return False + + +def setErrorNum(key, empty=False, expire=3600): + key = mw.md5(key) + num = cache.get(key) + if not num: + num = 0 + else: + if empty: + cache.delete(key) + return True + cache.set(key, num + 1, expire) + return True + +def login_temp_user(token): + if len(token) != 32: + return '错误的参数!' + + skey = mw.getClientIp() + '_temp_login' + if not getErrorNum(skey, 10): + return '连续10次验证失败,禁止1小时' + + stime = int(time.time()) + + tmp_data = thisdb.getTempLoginByToken(token) + if not tmp_data: + setErrorNum(skey) + return '验证失败!' + + if stime > int(tmp_data['expire']): + setErrorNum(skey) + return "过期" + + user_data = thisdb.getUserById(1) + login_addr = mw.getClientIp() + ":" + str(request.environ.get('REMOTE_PORT')) + mw.writeLog('用户临时登录', "登录成功,帐号:{1},登录IP:{2}",(user_data['name'], login_addr)) + + mw.M('temp_login').where('id=?',(tmp_data['id'],)).update({"login_time": stime, 'state': 1, 'login_addr': login_addr}) + + session['login'] = True + session['username'] = user_data['name'] + session['tmp_login'] = True + session['tmp_login_id'] = str(tmp_data['id']) + session['tmp_login_expire'] = int(tmp_data['expire']) + session['uid'] = user_data['id'] + + return redirect('/') + +# 登录页: 当设置了安全路径,本页失效。 +@blueprint.route('/login') +def login(): + name = thisdb.getOption('template', default='default') + + # 临时登录功能 + token = request.args.get('tmp_token', '').strip() + if token != '': + return login_temp_user(token) + + # 注销登录 + signout = request.args.get('signout', '') + if signout == 'True': + session.clear() + session['login'] = False + session['overdue'] = 0 + + admin_path = thisdb.getOption('admin_path') + if admin_path == '': + return render_template('%s/login.html' % name) + else: + unauthorized_status = thisdb.getOption('unauthorized_status') + if unauthorized_status == '0': + return render_template('%s/path.html' % name) + return Response(status=int(unauthorized_status)) + +@blueprint.route('/close') +def close(): + name = thisdb.getOption('template', default='default') + admin_close = thisdb.getOption('admin_close') + if admin_close == 'no': + return redirect('/', code=302) + return render_template('%s/close.html' % name) + + +# 验证码 +@blueprint.route('/code') +def code(): + import utils.vilidate as vilidate + vie = vilidate.vieCode() + codeImage = vie.GetCodeImage(80, 4) + out = io.BytesIO() + codeImage[0].save(out, "png") + session['code'] = mw.md5(''.join(codeImage[1]).lower()) + + img = Response(out.getvalue(), headers={'Content-Type': 'image/png'}) + return make_response(img) + +# 检查是否登录 +@blueprint.route('/check_login',methods=['GET','POST']) +def check_login(): + if isLogined(): + return mw.returnData(True,'已登录') + return mw.returnData(False,'未登录') + +@blueprint.route("/verify_login", methods=['POST']) +def verifyLogin(): + import pyotp + + username = request.form.get('username', '').strip() + password = request.form.get('password', '').strip() + password = mw.md5(password) + + info = thisdb.getUserByRoot() + if info['name'] != username or info['password'] != password: + return mw.returnJson(-1, "密码错误?") + + auth = request.form.get('auth', '').strip() + two_step_verification = thisdb.getOptionByJson('two_step_verification', default={'open':False}) + if two_step_verification['open']: + sec = mw.deDoubleCrypt('mdserver-web', two_step_verification['secret']) + totp = pyotp.TOTP(sec) + if totp.verify(auth): + session['login'] = True + session['username'] = info['name'] + session['overdue'] = int(time.time()) + 7 * 24 * 60 * 60 + + thisdb.updateUserLoginTime() + return mw.returnData(1, '二次验证成功!') + return mw.returnData(-1, '二次验证失败!') + +# 执行登录操作 +@blueprint.route('/do_login', endpoint='do_login', methods=['POST']) +def do_login(): + admin_close = thisdb.getOption('admin_close') + if admin_close == 'yes': + return mw.returnData(False, '面板已经关闭!') + + username = request.form.get('username', '').strip() + password = request.form.get('password', '').strip() + code = request.form.get('code', '').strip() + + login_cache_count = 5 + login_cache_limit = cache.get('login_cache_limit') + + if 'code' in session: + if session['code'] != mw.md5(code): + if login_cache_limit == None: + login_cache_limit = 1 + else: + login_cache_limit = int(login_cache_limit) + 1 + + if login_cache_limit >= login_cache_count: + thisdb.setOption('admin_close', 'yes') + return mw.returnData(False, '面板已经关闭!') + + cache.set('login_cache_limit', login_cache_limit, timeout=10000) + login_cache_limit = cache.get('login_cache_limit') + login_err_msg = mw.getInfo("验证码错误,您还可以尝试[{1}]次!", (str(login_cache_count - login_cache_limit))) + mw.writeLog('用户登录', login_err_msg) + return mw.returnData(False, login_err_msg) + + info = thisdb.getUserByName(username) + password = mw.md5(password) + + if info is None: + msg = mw.getInfo("密码错误,帐号:{1},密码:{2},登录IP:{3}", (username, '******', request.remote_addr)) + if login_cache_limit == None: + login_cache_limit = 1 + else: + login_cache_limit = int(login_cache_limit) + 1 + + if login_cache_limit >= login_cache_count: + thisdb.setOption('admin_close', 'yes') + return mw.returnData(False, '面板已经关闭!') + + cache.set('login_cache_limit', login_cache_limit, timeout=10000) + login_cache_limit = cache.get('login_cache_limit') + mw.writeLog('用户登录', msg) + return mw.returnData(-1, mw.getInfo("用户名或密码错误,您还可以尝试[{1}]次!", (str(login_cache_count - login_cache_limit)))) + + # print(info) + if info['name'] != username or info['password'] != password: + msg = mw.getInfo("密码错误,帐号:{1},密码:{2},登录IP:{3}", (username, '******', request.remote_addr)) + + if login_cache_limit == None: + login_cache_limit = 1 + else: + login_cache_limit = int(login_cache_limit) + 1 + + if login_cache_limit >= login_cache_count: + thisdb.setOption('admin_close', 'yes') + return mw.returnData(False, '面板已经关闭!') + + cache.set('login_cache_limit', login_cache_limit, timeout=10000) + login_cache_limit = cache.get('login_cache_limit') + mw.writeLog('用户登录', msg) + return mw.returnData(-1, mw.getInfo("用户名或密码错误,您还可以尝试[{1}]次!", (str(login_cache_count - login_cache_limit)))) + + cache.delete('login_cache_limit') + # 二步验证密钥 + two_step_verification = thisdb.getOptionByJson('two_step_verification', default={'open':False}) + if two_step_verification['open']: + return mw.returnData(2, '需要两步验证!') + + session['login'] = True + session['username'] = info['name'] + session['overdue'] = int(time.time()) + 7 * 24 * 60 * 60 + + thisdb.updateUserLoginTime() + return mw.returnData(1, '登录成功,正在跳转...') diff --git a/web/admin/files/__init__.py b/web/admin/files/__init__.py new file mode 100644 index 000000000..450673456 --- /dev/null +++ b/web/admin/files/__init__.py @@ -0,0 +1,12 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +from .files import * +from .recycle import * diff --git a/web/admin/files/files.py b/web/admin/files/files.py new file mode 100644 index 000000000..e4debf4f7 --- /dev/null +++ b/web/admin/files/files.py @@ -0,0 +1,334 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import time +import json + +from flask import Blueprint, render_template +from flask import request +from flask import make_response +from flask import send_file +from flask import send_from_directory + +from werkzeug.utils import secure_filename + +from admin.user_login_check import panel_login_required +from admin import session + +import core.mw as mw +import utils.file as file +import thisdb + +blueprint = Blueprint('files', __name__, url_prefix='/files', template_folder='../../templates') +@blueprint.route('/index', endpoint='index') +@panel_login_required +def index(): + name = thisdb.getOption('template', default='default') + return render_template('%s/files.html' % name) + +# 获取文件内容 +@blueprint.route('/check_exists_files', endpoint='check_exists_files', methods=['POST']) +@panel_login_required +def check_exists_files(): + dfile = request.form.get('dfile', '') + filename = request.form.get('filename', '') + data = [] + filesx = [] + if filename == '': + filesx = json.loads(session['selected']['data']) + else: + filesx.append(filename) + + for fn in filesx: + if fn == '.': + continue + filename = dfile + '/' + fn + if os.path.exists(filename): + tmp = {} + stat = os.stat(filename) + tmp['filename'] = fn + tmp['size'] = os.path.getsize(filename) + tmp['mtime'] = str(int(stat.st_mtime)) + data.append(tmp) + return mw.returnData(True, 'ok', data) + + +# 粘贴内容 +@blueprint.route('/batch_paste', endpoint='batch_paste', methods=['POST']) +@panel_login_required +def batch_paste(): + path = request.form.get('path', '') + stype = request.form.get('type', '') + return file.batchPaste(path, stype) + +# 压缩文件 +@blueprint.route('/zip', endpoint='zip', methods=['POST']) +@panel_login_required +def zip(): + sfile = request.form.get('sfile', '') + dfile = request.form.get('dfile', '') + stype = request.form.get('type', '') + path = request.form.get('path', '') + return file.zip(sfile, dfile, stype, path) + +# 文件权限查看 +@blueprint.route('/file_access', endpoint='file_access', methods=['POST']) +@panel_login_required +def file_access(): + filename = request.form.get('filename', '') + data = file.getAccess(filename) + data['sys_users'] = file.getSysUserList() + return data + +# 设置权限 +@blueprint.route('/set_file_access', endpoint='set_file_access', methods=['POST']) +@panel_login_required +def set_file_access(): + if mw.isAppleSystem(): + return mw.returnData(True, '开发机不设置!') + + filename = request.form.get('filename', '') + user = request.form.get('user', '') + access = request.form.get('access', '755') + return file.setFileAccess(filename, user, access) + + +# 复制文件内容 +@blueprint.route('/copy_file', endpoint='copy_file', methods=['POST']) +@panel_login_required +def copy_file(): + sfile = request.form.get('sfile', '') + dfile = request.form.get('dfile', '') + return file.copyFile(sfile, dfile) + +# 获取文件内容 +@blueprint.route('/get_body', endpoint='get_body', methods=['POST']) +@panel_login_required +def get_body(): + path = request.form.get('path', '') + return file.getFileBody(path) + +# 获取文件内容 +@blueprint.route('/save_body', endpoint='save_body', methods=['POST']) +@panel_login_required +def save_body(): + path = request.form.get('path', '') + data = request.form.get('data', '') + encoding = request.form.get('encoding', '') + return file.saveBody(path,data,encoding) + +# 获取文件内容(最新行数) +@blueprint.route('/get_last_body', endpoint='get_file_last_body', methods=['POST']) +@panel_login_required +def get_file_last_body(): + path = request.form.get('path', '') + line = request.form.get('line', '100') + + if not os.path.exists(path): + return mw.returnData(False, '文件不存在', (path,)) + + try: + data = mw.getLastLine(path, int(line)) + return mw.returnData(True, 'OK', data) + except Exception as ex: + return mw.returnData(False, '无法正确读取文件!' + str(ex)) + + +# 获取文件列表 +@blueprint.route('/get_dir', endpoint='get_dir', methods=['POST']) +@panel_login_required +def get_dir(): + path = request.form.get('path', '') + if not os.path.exists(path): + path = mw.getFatherDir() + '/wwwroot' + search = request.form.get('search', '').strip().lower() + search_all = request.form.get('all', '').strip().lower() + page = request.form.get('p', '1').strip().lower() + row = request.form.get('row', '10') + order = request.form.get('order', '') + + if search_all == 'yes' and search != '': + dir_list = file.getAllDirList(path, int(page), int(row), order, search) + else: + dir_list = file.getDirList(path, int(page), int(row), order, search) + + dir_list['page'] = mw.getPage({'p':page, 'row': row, 'tojs':'getFiles', 'count': dir_list['count']}, '1,2,3,4,5,6,7,8') + return dir_list + +# 解压ZIP +@blueprint.route('/unzip', endpoint='unzip', methods=['POST']) +@panel_login_required +def unzip(): + sfile = request.form.get('sfile', '') + dfile = request.form.get('dfile', '') + stype = request.form.get('type', '') + path = request.form.get('path', '') + return file.unzip(sfile, dfile, stype, path) + +# 解压可解压文件 +@blueprint.route('/uncompress', endpoint='uncompress', methods=['POST']) +@panel_login_required +def uncompress(): + sfile = request.form.get('sfile', '') + dfile = request.form.get('dfile', '') + path = request.form.get('path', '') + return file.uncompress(sfile, dfile, path) + +# 批量操作 +@blueprint.route('/set_batch_data', endpoint='set_batch_data', methods=['POST']) +@panel_login_required +def set_batch_data(): + path = request.form.get('path', '') + stype = request.form.get('type', '') + access = request.form.get('access', '') + user = request.form.get('user', '') + data = request.form.get('data') + return file.setBatchData(path, stype, access, user, data) + +# 上传文件 +@blueprint.route('/upload_file', endpoint='upload_file', methods=['POST']) +@panel_login_required +def upload_file(): + path = request.args.get('path', '') + if not os.path.exists(path): + os.makedirs(path) + f = request.files['zunfile'] + filename = os.path.join(path, f.filename) + + s_path = path + if os.path.exists(filename): + s_path = filename + p_stat = os.stat(s_path) + + # print(filename) + f.save(filename) + os.chown(filename, p_stat.st_uid, p_stat.st_gid) + os.chmod(filename, p_stat.st_mode) + + msg = mw.getInfo('上传文件[{1}] 到 [{2}]成功!', (filename, path)) + mw.writeLog('文件管理', msg) + return mw.returnData(True, '上传成功!') + + +# 上传文件 +@blueprint.route('/upload_segment', endpoint='upload_segment', methods=['POST']) +@panel_login_required +def upload_segment(): + path = request.form.get('path', '') + name = request.form.get('name', '') + size = request.form.get('size') + start = request.form.get('start') + dir_mode = request.form.get('dir_mode', '') + file_mode = request.form.get('file_mode', '') + b64_data = request.form.get('b64_data', '0') + upload_files = request.files.getlist("blob") + return file.uploadSegment(path,name,size,start,dir_mode,file_mode,b64_data,upload_files) + + +# 修改文件名 +@blueprint.route('/mv_file', endpoint='mv_file', methods=['POST']) +@panel_login_required +def mv_file(): + sfile = request.form.get('sfile', '') + dfile = request.form.get('dfile', '') + return file.mvFile(sfile, dfile) + +# 创建文件 +@blueprint.route('/create_file', endpoint='create_file', methods=['POST']) +@panel_login_required +def create_file(): + path = request.form.get('path', '') + return file.createFile(path) + + +# 创建目录 +@blueprint.route('/create_dir', endpoint='create_dir', methods=['POST']) +@panel_login_required +def create_dir(): + path = request.form.get('path', '') + return file.createDir(path) + + +# 获取站点日志目录 +@blueprint.route('/get_dir_size', endpoint='get_dir_size', methods=['POST']) +@panel_login_required +def get_dir_size(): + path = request.form.get('path', '') + size = file.getDirSizeByBash(path) + return mw.returnData(True, size) + # size = file.getDirSize(path) + # return mw.returnData(True, mw.toSize(size)) + + +# 删除文件 +@blueprint.route('/delete', endpoint='delete', methods=['POST']) +@panel_login_required +def delete(): + path = request.form.get('path', '') + return file.fileDelete(path) + + +# 删除文件 +@blueprint.route('/delete_dir', endpoint='delete_dir', methods=['POST']) +@panel_login_required +def delete_dir(): + path = request.form.get('path', '') + return file.dirDelete(path) + +# 下载文件 +@blueprint.route('/download', endpoint='download', methods=['GET']) +@panel_login_required +def download(): + filename = request.args.get('filename', '') + if not os.path.exists(filename): + return '' + is_attachment = True + if filename.endswith(".svg"): + is_attachment = False + + response = make_response(send_from_directory(os.path.dirname(filename), os.path.basename(filename), as_attachment=is_attachment)) + return response + +# 远程下载 +@blueprint.route('/download_file', endpoint='download_file', methods=['POST']) +@panel_login_required +def download_file(): + url = request.form.get('url', '') + path = request.form.get('path', '') + filename = request.form.get('filename', '') + + execstr = url + '|mw|' + path + '/' + filename + execstr = execstr.strip() + + title = '下载文件[' + filename + ']' + thisdb.addTaskByDownload(name=title, cmd=execstr) + # self.setFileAccept(path + '/' + filename) + mw.triggerTask() + return mw.returnData(True, '已将下载任务添加到队列!') + +# 日志清空 +@blueprint.route('/close_logs', endpoint='close_logs', methods=['POST']) +@panel_login_required +def close_logs(): + return file.closeLogs() + + + + + + + + + + + + + diff --git a/web/admin/files/recycle.py b/web/admin/files/recycle.py new file mode 100644 index 000000000..e02b40a53 --- /dev/null +++ b/web/admin/files/recycle.py @@ -0,0 +1,67 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os + +from flask import Blueprint, render_template +from flask import request +from flask import make_response +from flask import send_file +from flask import send_from_directory + +from admin.user_login_check import panel_login_required +import core.mw as mw +import utils.file as file + +from .files import blueprint +# 回收站文件列表 +@blueprint.route('/get_recycle_bin', endpoint='get_recycle_bin', methods=['POST']) +@panel_login_required +def get_recycle_bin(): + return file.getRecycleBin() + +# 回收站文件恢复 +@blueprint.route('/re_recycle_bin', endpoint='re_recycle_bin', methods=['POST']) +@panel_login_required +def re_recycle_bin(): + path = request.form.get('path', '') + return file.reRecycleBin(path) + +@blueprint.route('/del_recycle_bin', endpoint='del_recycle_bin', methods=['POST']) +@panel_login_required +def del_recycle_bin(): + path = request.form.get('path', '') + return file.delRecycleBin(path) + +# 回收站文件 +@blueprint.route('/recycle_bin', endpoint='recycle_bin', methods=['POST']) +@panel_login_required +def recycle_bin(): + return file.toggleRecycleBin() + +# 回收站文件 +@blueprint.route('/close_recycle_bin', endpoint='close_recycle_bin', methods=['POST']) +@panel_login_required +def close_recycle_bin(): + return file.closeRecycleBin() + + + + + + + + + + + + + + diff --git a/web/admin/firewall/__init__.py b/web/admin/firewall/__init__.py new file mode 100644 index 000000000..c3f655d1e --- /dev/null +++ b/web/admin/firewall/__init__.py @@ -0,0 +1,125 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.firewall import Firewall as MwFirewall + +import core.mw as mw +import thisdb + +blueprint = Blueprint('firewall', __name__, url_prefix='/firewall', template_folder='../../templates') +@blueprint.route('/index', endpoint='index') +@panel_login_required +def index(): + name = thisdb.getOption('template', default='default') + return render_template('%s/firewall.html' % name) + + +# 防火墙列表 +@blueprint.route('/get_list', endpoint='get_list', methods=['POST']) +@panel_login_required +def get_list(): + p = request.form.get('p', '1').strip() + limit = request.form.get('limit', '10').strip() + return MwFirewall.instance().getList(p,limit) + +# 获取站点日志目录 +@blueprint.route('/get_www_path', endpoint='get_www_path', methods=['POST']) +@panel_login_required +def get_www_path(): + path = mw.getLogsDir() + return {'path': path} + +# 获取ssh信息 +@blueprint.route('/get_ssh_info', endpoint='get_ssh_info', methods=['POST']) +@panel_login_required +def get_ssh_info(): + return MwFirewall.instance().getSshInfo() + + +# 切换ping开关 +@blueprint.route('/set_ping', endpoint='set_ping', methods=['POST']) +@panel_login_required +def set_ping(): + status = request.form.get('status') + return MwFirewall.instance().setPing(status) + +# 修改ssh端口 +@blueprint.route('/set_ssh_port', endpoint='set_ssh_port', methods=['POST']) +@panel_login_required +def set_ssh_port(): + port = request.form.get('port', '1').strip() + return MwFirewall.instance().setSshPort(port) + +# 添加放行端口 +@blueprint.route('/add_accept_port', endpoint='add_accept_port', methods=['POST']) +@panel_login_required +def add_accept_port(): + port = request.form.get('port', '').strip() + ps = request.form.get('ps', '').strip() + protocol = request.form.get('protocol', '').strip() + stype = request.form.get('type', '').strip() + + return MwFirewall.instance().addAcceptPort(port, ps, stype, protocol=protocol) + +# 删除放行端口 +@blueprint.route('/del_accept_port', endpoint='del_accept_port', methods=['POST']) +@panel_login_required +def del_accept_port(): + port = request.form.get('port', '').strip() + firewall_id = request.form.get('id', '').strip() + protocol = request.form.get('protocol', '').strip() + return MwFirewall.instance().delAcceptPort(firewall_id, port, protocol=protocol) + + +# 设置防火墙状态 +@blueprint.route('/set_fw', endpoint='set_fw', methods=['POST']) +@panel_login_required +def set_fw(): + if mw.isAppleSystem(): + return mw.returnData(True, '开发机不能设置!') + status = request.form.get('status', '1') + return MwFirewall.instance().setFw(status) + +@blueprint.route('/set_ssh_root_status', endpoint='set_ssh_root_status', methods=['POST']) +@panel_login_required +def set_ssh_root_status(): + if mw.isAppleSystem(): + return mw.returnData(True, '开发机不能设置!') + status = request.form.get('status', '1') + return MwFirewall.instance().setSshRootStatus(status) + +@blueprint.route('/set_ssh_pass_status', endpoint='set_ssh_pass_status', methods=['POST']) +@panel_login_required +def set_ssh_pass_status(): + if mw.isAppleSystem(): + return mw.returnData(True, '开发机不能设置!') + status = request.form.get('status', '1') + return MwFirewall.instance().setSshPassStatus(status) + +@blueprint.route('/set_ssh_pubkey_status', endpoint='set_ssh_pubkey_status', methods=['POST']) +@panel_login_required +def set_ssh_pubkey_status(): + if mw.isAppleSystem(): + return mw.returnData(True, '开发机不能设置!') + status = request.form.get('status', '1') + return MwFirewall.instance().setSshPubkeyStatus(status) + + + + + + + diff --git a/web/admin/logs/__init__.py b/web/admin/logs/__init__.py new file mode 100644 index 000000000..2d2218616 --- /dev/null +++ b/web/admin/logs/__init__.py @@ -0,0 +1,73 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.adult_log as adult_log +import thisdb + +# 日志页面 +blueprint = Blueprint('logs', __name__, url_prefix='/logs', template_folder='../../templates') +@blueprint.route('/index', endpoint='index') +@panel_login_required +def index(): + name = thisdb.getOption('template', default='default') + return render_template('%s/logs.html' % name) + +# 日志列表 +@blueprint.route('/get_log_list', endpoint='get_log_list', methods=['POST']) +@panel_login_required +def get_log_list(): + p = request.form.get('p', '1').strip() + size = request.form.get('limit', '10').strip() + search = request.form.get('search', '').strip() + + info = thisdb.getLogsList(page=int(p),size=int(size), search=search) + + data = {} + data['data'] = info['list'] + data['page'] = mw.getPage({'count':info['count'],'tojs':'getLogs','p':p,'row':size}) + return data + +# 日志清空 +@blueprint.route('/del_panel_logs', endpoint='del_panel_logs', methods=['POST']) +@panel_login_required +def del_panel_logs(): + thisdb.clearLog() + mw.writeLog('面板设置', '面板操作日志已清空!') + return mw.returnData(True, '面板操作日志已清空!') + +# 系统审计日志列表 +@blueprint.route('/get_audit_logs_files', endpoint='get_audit_logs_files', methods=['POST']) +@panel_login_required +def get_audit_logs_files(): + logs_file = adult_log.getAuditLogsFiles() + return mw.returnData(True, 'ok', logs_file) + +# 系统审计日志列表 +@blueprint.route('/get_audit_file', endpoint='get_audit_file', methods=['POST']) +@panel_login_required +def get_audit_file(): + name = request.form.get('log_name', '').strip() + return adult_log.getAuditLogsName(name) + + + + + + + + + diff --git a/web/admin/monitor/__init__.py b/web/admin/monitor/__init__.py new file mode 100644 index 000000000..044aff3f4 --- /dev/null +++ b/web/admin/monitor/__init__.py @@ -0,0 +1,199 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +import time + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.system as sys + +import thisdb + +blueprint = Blueprint('monitor', __name__, url_prefix='/monitor', template_folder='../../templates') +@blueprint.route('/index', endpoint='index') +@panel_login_required +def index(): + name = thisdb.getOption('template', default='default') + return render_template('%s/monitor.html' % name) + + +def _parse_range(range_key): + mapping = { + '1h': 3600, + '24h': 86400, + '7d': 7 * 86400, + '30d': 30 * 86400, + } + seconds = mapping.get(range_key, 86400) + end_time = int(time.time()) + start_time = end_time - seconds + return start_time, end_time + +def _to_float(value, default=0.0): + try: + return float(value) + except (TypeError, ValueError): + return default + + +def _to_num_or_none(value): + if value is None: + return None + try: + return float(value) + except (TypeError, ValueError): + return None + + +def _build_series(start_time, end_time): + load_data = sys.getLoadAverageByDB(start_time, end_time) + cpu_data = sys.getCpuIoByDB(start_time, end_time) + disk_data = sys.getDiskIoByDB(start_time, end_time) + net_data = sys.getNetworkIoByDB(start_time, end_time) + + def _to_float(value, default=0.0): + try: + return float(value) + except (TypeError, ValueError): + return default + + series = { + 'load': { + 'labels': [item['addtime'] for item in load_data], + 'one': [_to_float(item.get('one')) for item in load_data], + 'five': [_to_float(item.get('five')) for item in load_data], + 'fifteen': [_to_float(item.get('fifteen')) for item in load_data], + }, + 'cpu': { + 'labels': [item['addtime'] for item in cpu_data], + 'cpu': [_to_float(item.get('pro')) for item in cpu_data], + 'mem': [_to_float(item.get('mem')) for item in cpu_data], + }, + 'disk': { + 'labels': [item['addtime'] for item in disk_data], + 'read': [round(_to_float(item.get('read_bytes')) / (1024 * 1024), 2) for item in disk_data], + 'write': [round(_to_float(item.get('write_bytes')) / (1024 * 1024), 2) for item in disk_data], + }, + 'net': { + 'labels': [item['addtime'] for item in net_data], + 'up': [round((_to_float(item.get('up')) * 8) / 1024, 2) for item in net_data], + 'down': [round((_to_float(item.get('down')) * 8) / 1024, 2) for item in net_data], + }, + } + return series, load_data, cpu_data, disk_data, net_data + + +def _is_monitor_open(): + monitor_status = thisdb.getOption('monitor_status', default='open', type='monitor') + return monitor_status == 'open' + + +def _should_collect_sample(load_data, cpu_data, disk_data, net_data): + return not (load_data or cpu_data or disk_data or net_data) + + +def _safe_latest(data, key): + if not data: + return None + return _to_num_or_none(data[-1].get(key)) + + +def _safe_peak(data, key, extra=None): + if not data: + return None + if extra: + values = [extra(item) for item in data] + else: + values = [_to_num_or_none(item.get(key)) for item in data] + + values = [v for v in values if v is not None] + if not values: + return None + return max(values) + + +@blueprint.route('/api/overview', endpoint='api_overview', methods=['GET']) +@panel_login_required +def api_overview(): + range_key = request.args.get('range', '24h') + start_time, end_time = _parse_range(range_key) + + series, load_data, cpu_data, disk_data, net_data = _build_series(start_time, end_time) + if _should_collect_sample(load_data, cpu_data, disk_data, net_data) and _is_monitor_open(): + # 监控未采样或采样任务异常时,页面会出现空图。这里主动补采样并容错,避免接口直接报错。 + try: + sys.monitor.instance().run() + except Exception: + pass + series, load_data, cpu_data, disk_data, net_data = _build_series(start_time, end_time) + + latest_net = None + if net_data: + latest_net = max(float(net_data[-1].get('up', 0)), float(net_data[-1].get('down', 0))) + latest_disk = None + if disk_data: + latest_disk = float(disk_data[-1].get('read_bytes', 0)) + float(disk_data[-1].get('write_bytes', 0)) + + summary = { + 'cpu': { + 'latest': _safe_latest(cpu_data, 'pro'), + 'peak': _safe_peak(cpu_data, 'pro'), + }, + 'mem': { + 'latest': _safe_latest(cpu_data, 'mem'), + 'peak': _safe_peak(cpu_data, 'mem'), + }, + 'net': { + 'latest': latest_net, + 'peak': _safe_peak(net_data, 'up', lambda item: max(float(item.get('up', 0)), float(item.get('down', 0)))), + }, + 'disk': { + 'latest': latest_disk, + 'peak': _safe_peak(disk_data, 'read_bytes', lambda item: float(item.get('read_bytes', 0)) + float(item.get('write_bytes', 0))), + }, + 'load': { + 'latest': _safe_latest(load_data, 'one'), + 'peak': _safe_peak(load_data, 'one'), + }, + } + + events = [] + if cpu_data: + top_cpu = max(cpu_data, key=lambda item: _to_float(item.get('pro'))) + top_cpu_val = round(_to_float(top_cpu.get('pro')), 2) + events.append({'title': f"CPU 峰值 {top_cpu_val}%", 'time': top_cpu['addtime']}) + if cpu_data: + top_mem = max(cpu_data, key=lambda item: _to_float(item.get('mem'))) + top_mem_val = round(_to_float(top_mem.get('mem')), 2) + events.append({'title': f"内存峰值 {top_mem_val}%", 'time': top_mem['addtime']}) + if net_data: + top_net = max(net_data, key=lambda item: max(float(item.get('up', 0)), float(item.get('down', 0)))) + events.append({'title': f"网络峰值 {round((max(float(top_net.get('up', 0)), float(top_net.get('down', 0))) * 8) / 1024, 2)} Mbps", 'time': top_net['addtime']}) + if disk_data: + top_disk = max(disk_data, key=lambda item: float(item.get('read_bytes', 0)) + float(item.get('write_bytes', 0))) + total_mb = round((float(top_disk.get('read_bytes', 0)) + float(top_disk.get('write_bytes', 0))) / (1024 * 1024), 2) + events.append({'title': f"磁盘 IO 峰值 {total_mb} MB", 'time': top_disk['addtime']}) + + data = { + 'range': { + 'start': start_time, + 'end': end_time, + 'key': range_key, + }, + 'summary': summary, + 'events': events, + 'series': series, + } + return mw.returnData(True, 'ok', data) diff --git a/web/admin/plugins/__init__.py b/web/admin/plugins/__init__.py new file mode 100644 index 000000000..d152ae3e3 --- /dev/null +++ b/web/admin/plugins/__init__.py @@ -0,0 +1,211 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json + +from flask import Blueprint, render_template +from flask import request + +from utils.plugin import plugin as MwPlugin +from admin.user_login_check import panel_login_required + + +import core.mw as mw +import utils.config as utils_config +import thisdb + + +blueprint = Blueprint('plugins', __name__, url_prefix='/plugins', template_folder='../../templates') +@blueprint.route('/index', endpoint='index') +@panel_login_required +def index(): + name = thisdb.getOption('template', default='default') + return render_template('%s/plugins.html' % name) + +# 初始化检查,首页提示选择安装 +@blueprint.route('/init', endpoint='init', methods=['POST']) +@panel_login_required +def init(): + return MwPlugin.instance().init() + +# 初始化安装 +@blueprint.route('/init_install', endpoint='init_install', methods=['POST']) +@panel_login_required +def init_install(): + plugin_list = request.form.get('list', '') + return MwPlugin.instance().initInstall(plugin_list) + +# 首页软件展示 +@blueprint.route('/index_list', endpoint='index_list', methods=['GET','POST']) +@panel_login_required +def index_list(): + pg = MwPlugin.instance() + return pg.getIndexList() + +# 插件列表 +@blueprint.route('/list', endpoint='list', methods=['GET']) +@panel_login_required +def list(): + plugins_type = request.args.get('type', '0') + page = request.args.get('p', '1') + search = request.args.get('search', '').lower() + + if not mw.isNumber(plugins_type): + plugins_type = 1 + + if not mw.isNumber(page): + page = 0 + + pg = MwPlugin.instance() + return pg.getList(plugins_type, search, int(page)) + +# 插件设置是否在首页展示 +@blueprint.route('/set_index', endpoint='set_index', methods=['POST']) +@panel_login_required +def set_index(): + name = request.form.get('name', '') + status = request.form.get('status', '0') + version = request.form.get('version', '') + + pg = MwPlugin.instance() + if status == '1': + return pg.addIndex(name, version) + return pg.removeIndex(name, version) + +# 插件安装 +@blueprint.route('/install', endpoint='install', methods=['POST']) +@panel_login_required +def install(): + name = request.form.get('name', '') + version = request.form.get('version', '') + + upgrade = None + if hasattr(request.form, 'upgrade'): + upgrade = True + + pg = MwPlugin.instance() + return pg.install(name, version, upgrade=upgrade) + +# 插件卸载 +@blueprint.route('/uninstall', endpoint='uninstall', methods=['POST']) +@panel_login_required +def uninstall(): + name = request.form.get('name', '') + version = request.form.get('version', '') + pg = MwPlugin.instance() + return pg.uninstall(name, version) + +# 文件读取 +@blueprint.route('/menu', endpoint='menu', methods=['GET']) +@panel_login_required +def menu(): + data = utils_config.getGlobalVar() + pg = MwPlugin.instance() + tag = request.args.get('tag', '') + + hook_menu = thisdb.getOptionByJson('hook_menu',type='hook',default=[]) + content = '' + for menu_data in hook_menu: + if tag == menu_data['name'] and 'path' in menu_data: + t = pg.menuGetAbsPath(tag, menu_data['path']) + content = mw.readFile(t) + #------------------------------------------------------------ + data['hook_tag'] = tag + data['plugin_content'] = content + return render_template('plugin_menu.html', data=data) + +# 文件读取 +@blueprint.route('/file', endpoint='file', methods=['GET']) +@panel_login_required +def file(): + name = request.args.get('name', '') + if name.strip() == '': + return '' + + f = request.args.get('f', '') + if f.strip() == '': + return '' + + file = mw.getPluginDir() + '/' + name + '/' + f + if not os.path.exists(file): + return '' + + suffix = mw.getPathSuffix(file) + if suffix == '.css': + content = mw.readFile(file) + from flask import Response + from flask import make_response + v = Response(content, headers={'Content-Type': 'text/css; charset="utf-8"'}) + return make_response(v) + content = open(file, 'rb').read() + return content + + +# 插件上传 +@blueprint.route('/update_zip', endpoint='update_zip', methods=['POST']) +@panel_login_required +def update_zip(): + request_zip = request.files['plugin_zip'] + return MwPlugin.instance().updateZip(request_zip) + + +@blueprint.route('/input_zip', endpoint='input_zip', methods=['POST']) +@panel_login_required +def input_zip(): + plugin_name = request.form.get('plugin_name', '') + tmp_path = request.form.get('tmp_path', '') + return MwPlugin.instance().inputZipApi(plugin_name,tmp_path) + + +# 插件设置页 +@blueprint.route('/setting', endpoint='setting', methods=['GET']) +@panel_login_required +def setting(): + name = request.args.get('name', '') + html = mw.getPluginDir() + '/' + name + '/index.html' + return mw.readFile(html) + + +# 插件统一回调入口API +@blueprint.route('/run', endpoint='run', methods=['GET','POST']) +@panel_login_required +def run(): + name = request.form.get('name', '') + func = request.form.get('func', '') + version = request.form.get('version', '') + args = request.form.get('args', '') + script = request.form.get('script', 'index') + + pg = MwPlugin.instance() + data = pg.run(name, func, version, args, script) + if data[1] == '': + r = mw.returnData(True, "OK", data[0].strip()) + else: + r = mw.returnData(False, data[1].strip()) + return r + + +# 插件统一回调入口API +@blueprint.route('/callback', endpoint='callback', methods=['GET','POST']) +@panel_login_required +def callback(): + name = request.form.get('name', '') + func = request.form.get('func', '') + args = request.form.get('args', '') + script = request.form.get('script', 'index') + + pg = MwPlugin.instance() + data = pg.callback(name, func, args=args, script=script) + if data[0]: + return mw.returnData(True, "OK", data[1]) + return mw.returnData(False, data[1]) + + diff --git a/web/admin/setting/__init__.py b/web/admin/setting/__init__.py new file mode 100644 index 000000000..1dfb93200 --- /dev/null +++ b/web/admin/setting/__init__.py @@ -0,0 +1,23 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +from .setting import * + +from .temp_login import * +from .timezone import * +from .secondary_verifiy import * + +from .notify_email import * +from .notify_tgbot import * +from .app import * + +from .panel_ssl import * +from .panel_bookmark import * + diff --git a/web/admin/setting/app.py b/web/admin/setting/app.py new file mode 100644 index 000000000..273e19ce8 --- /dev/null +++ b/web/admin/setting/app.py @@ -0,0 +1,92 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import re +import json +import os +import time + +from flask import Blueprint, render_template +from flask import request + +from admin import session +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.config as utils_config + +from .setting import blueprint +import thisdb + +# 设置API +@blueprint.route('/set_panel_api', endpoint='set_panel_api', methods=['POST']) +@panel_login_required +def set_panel_api(): + panel_api = thisdb.getOptionByJson('panel_api', default={'open':False}) + if not panel_api['open']: + panel_api['open'] = True + thisdb.setOption('panel_api', json.dumps(panel_api)) + return mw.returnData(True, '开启API成功!') + else: + panel_api['open'] = False + thisdb.setOption('panel_api', json.dumps(panel_api)) + return mw.returnData(True, '关闭API成功!') + + +# 获取APP列表 +@blueprint.route('/get_app_list', endpoint='get_app_list', methods=['POST']) +@panel_login_required +def get_app_list(): + limit = request.form.get('limit', '5').strip() + page = request.form.get('page', '1').strip() + tojs = request.form.get('tojs', 'getAppList').strip() + + info = thisdb.getAppList(page=int(page),size=int(limit)) + data = {} + data['data'] = info['list'] + data['page'] = mw.getPage({'count':info['count'],'tojs':tojs,'p':page,'row':limit}) + return data + + +# 添加APP列表 +@blueprint.route('/add_app', endpoint='add_app', methods=['POST']) +@panel_login_required +def add_app(): + app_id = request.form.get('app_id', '').strip() + app_secret = request.form.get('app_secret', '1').strip() + limit_addr = request.form.get('limit_addr', '').strip() + if limit_addr == '': + return mw.returnData(False, 'IP限制不能为空!') + + rid = thisdb.addApp(app_id,app_secret,limit_addr) + if rid > 0: + return mw.returnData(True, '添加成功!') + return mw.returnData(False, '添加失败!') + +# 添加APP列表 +@blueprint.route('/toggle_app_status', endpoint='toggle_app_status', methods=['POST']) +@panel_login_required +def toggle_app_status(): + aid = request.form.get('id', '').strip() + rid = thisdb.toggleAppStatus(aid) + if rid > 0: + return mw.returnData(True, '切换成功!') + return mw.returnData(False, '切换失败!') + + +# 获取APP列表 +@blueprint.route('/delete_app', endpoint='delete_app', methods=['POST']) +@panel_login_required +def delete_app(): + aid = request.form.get('id', '').strip() + rid = thisdb.deleteAppById(aid) + if rid > 0: + return mw.returnData(True, '删除成功!') + return mw.returnData(False, '删除失败!') diff --git a/web/admin/setting/notify_email.py b/web/admin/setting/notify_email.py new file mode 100644 index 000000000..9bd8b3f2b --- /dev/null +++ b/web/admin/setting/notify_email.py @@ -0,0 +1,89 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import re +import json +import os +import time + +from flask import Blueprint, render_template +from flask import request + +from admin import session +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.config as utils_config + +from .setting import blueprint +import thisdb + +# 获取邮件信息 +@blueprint.route('/get_notify_email', endpoint='get_notify_email', methods=['POST']) +@panel_login_required +def get_notify_email(): + notify_email = thisdb.getOptionByJson('notify_email', default={'open':False}, type='notify') + + if 'cfg' in notify_email: + decrypt_data = mw.deDoubleCrypt('email', notify_email['cfg']) + notify_email['email'] = json.loads(decrypt_data) + else: + notify_email['email'] = {'smtp_host':'','smtp_port':'','smtp_ssl':'','to_mail_addr':'','username':'','password':''} + + return mw.returnData(True,'ok',notify_email) + + +# 设置邮件信息 +@blueprint.route('/set_notify_email', endpoint='set_notify_email', methods=['POST']) +@panel_login_required +def set_notify_email(): + tag = request.form.get('tag', '').strip() + data = request.form.get('data', '').strip() + + crypt_data = mw.enDoubleCrypt(tag, data) + + notify_email = thisdb.getOptionByJson('notify_email', default={'open':False}, type='notify') + notify_email['cfg'] = crypt_data + + thisdb.setOption('notify_email', json.dumps(notify_email), type='notify') + return mw.returnData(True,'设置成功') + + +# 设置邮件测试 +@blueprint.route('/set_notify_email_test', endpoint='set_notify_email_test', methods=['POST']) +@panel_login_required +def set_notify_email_test(): + tag = request.form.get('tag', '').strip() + tag_data = request.form.get('data', '').strip() + + data = json.loads(tag_data) + test_pass = mw.emailNotifyTest(data) + if test_pass == True: + return mw.returnData(True, '验证成功') + return mw.returnData(False, '验证失败:'+test_pass) + +# 切换邮件开关 +@blueprint.route('/set_notify_email_enable', endpoint='set_notify_email_enable', methods=['POST']) +@panel_login_required +def set_notify_email_enable(): + tag = request.form.get('tag', '').strip() + data = request.form.get('data', '').strip() + + notify_email = thisdb.getOptionByJson('notify_email', default={'open':False}, type='notify') + + if notify_email['open']: + op_action = '关闭' + notify_email['open'] = False + else: + op_action = '开启' + notify_email['open'] = True + + thisdb.setOption('notify_email', json.dumps(notify_email), type='notify') + return mw.returnData(True, op_action+'成功') \ No newline at end of file diff --git a/web/admin/setting/notify_tgbot.py b/web/admin/setting/notify_tgbot.py new file mode 100644 index 000000000..f3bc11a5d --- /dev/null +++ b/web/admin/setting/notify_tgbot.py @@ -0,0 +1,85 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import re +import json +import os +import time + +from flask import Blueprint, render_template +from flask import request + +from admin import session +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.config as utils_config + +from .setting import blueprint +import thisdb + +# 获取邮件信息 +@blueprint.route('/get_notify_tgbot', endpoint='get_notify_tgbot', methods=['POST']) +@panel_login_required +def get_notify_tgbot(): + notify_tgbot = thisdb.getOptionByJson('notify_tgbot', default={'open':False}, type='notify') + if 'cfg' in notify_tgbot: + decrypt_data = mw.deDoubleCrypt('tgbot', notify_tgbot['cfg']) + notify_tgbot['tgbot'] = json.loads(decrypt_data) + else: + notify_tgbot['tgbot'] = [] + return mw.returnData(True,'ok',notify_tgbot) + + +# 设置邮件信息 +@blueprint.route('/set_notify_tgbot', endpoint='set_notify_tgbot', methods=['POST']) +@panel_login_required +def set_notify_tgbot(): + data = request.form.get('data', '').strip() + + crypt_data = mw.enDoubleCrypt('tgbot', data) + + notify_tgbot = thisdb.getOptionByJson('notify_tgbot', default={'open':False}, type='notify') + notify_tgbot['cfg'] = crypt_data + + thisdb.setOption('notify_tgbot', json.dumps(notify_tgbot), type='notify') + return mw.returnData(True,'设置成功') + + +# 设置邮件测试 +@blueprint.route('/set_notify_tgbot_test', endpoint='set_notify_tgbot_test', methods=['POST']) +@panel_login_required +def set_notify_tgbot_test(): + tag_data = request.form.get('data', '').strip() + + tmp = json.loads(tag_data) + test_pass = mw.tgbotNotifyTest(tmp['app_token'], tmp['chat_id']) + if test_pass == True: + return mw.returnData(True, '验证成功') + return mw.returnData(False, '验证失败:'+test_pass) + +# 切换邮件开关 +@blueprint.route('/set_notify_tgbot_enable', endpoint='set_notify_tgbot_enable', methods=['POST']) +@panel_login_required +def set_notify_tgbot_enable(): + tag = request.form.get('tag', '').strip() + data = request.form.get('data', '').strip() + + notify_tgbot = thisdb.getOptionByJson('notify_tgbot', default={'open':False}, type='notify') + + if notify_tgbot['open']: + op_action = '关闭' + notify_tgbot['open'] = False + else: + op_action = '开启' + notify_tgbot['open'] = True + + thisdb.setOption('notify_tgbot', json.dumps(notify_tgbot), type='notify') + return mw.returnData(True, op_action+'成功') \ No newline at end of file diff --git a/web/admin/setting/panel_bookmark.py b/web/admin/setting/panel_bookmark.py new file mode 100644 index 000000000..922269e8f --- /dev/null +++ b/web/admin/setting/panel_bookmark.py @@ -0,0 +1,88 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import re +import json +import os +import time + +from flask import Blueprint, render_template +from flask import request + +from admin import session +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.config as utils_config + +from utils.setting import setting as MwSetting + +from .setting import blueprint +import thisdb + +# 添加面板书签 +@blueprint.route('/add_panel_info', endpoint='add_panel_info', methods=['POST']) +@panel_login_required +def add_panel_info(): + title = request.form.get('title', '') + url = request.form.get('url', '') + username = request.form.get('username', '') + password = request.form.get('password', '') + + # 校验是还是重复 + isAdd = mw.M('panel').where('title=? OR url=?', (title, url)).count() + if isAdd: + return mw.returnData(False, '备注或面板地址重复!') + isRe = mw.M('panel').add('title,url,username,password,click,add_time', + (title, url, username, password, 0, int(time.time()))) + if isRe: + return mw.returnData(True, '添加成功!') + return mw.returnData(False, '添加失败!') + +# 取面板书签列表 +@blueprint.route('/get_panel_list', endpoint='get_panel_list', methods=['GET','POST']) +@panel_login_required +def get_panel_list(): + data = mw.M('panel').field('id,title,url,username,password,click,add_time').order('click desc').select() + return mw.returnData(True, 'ok!', data) + + +# 删除面板书签 +@blueprint.route('/del_panel_info', endpoint='del_panel_info', methods=['GET','POST']) +@panel_login_required +def del_panel_info(): + panel_id = request.form.get('id', '') + isExists = mw.M('panel').where('id=?', (panel_id,)).count() + if not isExists: + return mw.returnData(False, '指定面板资料不存在!') + mw.M('panel').where('id=?', (panel_id,)).delete() + return mw.returnData(True, '删除成功!') + + +# 设置面板域名 +@blueprint.route('/set_panel_info', endpoint='set_panel_info', methods=['POST']) +@panel_login_required +def set_panel_info(): + title = request.form.get('title', '') + url = request.form.get('url', '') + username = request.form.get('username', '') + password = request.form.get('password', '') + panel_id = request.form.get('id', '') + # 校验是还是重复 + isSave = mw.M('panel').where('(title=? OR url=?) AND id!=?', (title, url, panel_id)).count() + if isSave: + return mw.returnData(False, '备注或面板地址重复!') + + # 更新到数据库 + isRe = mw.M('panel').where('id=?', (panel_id,)).save('title,url,username,password', (title, url, username, password)) + if isRe: + return mw.returnData(True, '修改成功!') + return mw.returnData(False, '修改失败!') + diff --git a/web/admin/setting/panel_ssl.py b/web/admin/setting/panel_ssl.py new file mode 100644 index 000000000..19f904dcd --- /dev/null +++ b/web/admin/setting/panel_ssl.py @@ -0,0 +1,80 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import re +import json +import os +import time + +from flask import Blueprint, render_template +from flask import request + +from admin import session +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.config as utils_config + +from utils.setting import setting as MwSetting + +from .setting import blueprint +import thisdb + +# 获取面板证书信息 +@blueprint.route('/get_panel_ssl', endpoint='get_panel_ssl', methods=['POST']) +@panel_login_required +def get_panel_ssl(): + return MwSetting.instance().getPanelSsl() + + +# 获取面板证书信息 +@blueprint.route('/save_panel_ssl', endpoint='save_panel_ssl', methods=['POST']) +@panel_login_required +def save_panel_ssl(): + choose = request.form.get('choose', '').strip() + certPem = request.form.get('certPem', '').strip() + privateKey = request.form.get('privateKey', '').strip() + return MwSetting.instance().savePanelSsl(choose,certPem,privateKey) + +# 获取面板证书信息 +@blueprint.route('/del_panel_ssl', endpoint='del_panel_ssl', methods=['POST']) +@panel_login_required +def del_panel_ssl(): + choose = request.form.get('choose', '').strip() + return MwSetting.instance().delPanelSsl(choose) + +# 开启面板证书 +@blueprint.route('/set_panel_local_ssl', endpoint='set_panel_local_ssl', methods=['POST']) +@panel_login_required +def set_panel_local_ssl(): + cert_type = request.form.get('cert_type', '').strip() + return MwSetting.instance().setPanelLocalSsl(cert_type) + +# 关闭面板证书 +@blueprint.route('/close_panel_ssl', endpoint='close_panel_ssl', methods=['POST']) +@panel_login_required +def close_panel_ssl(): + return MwSetting.instance().closePanelSsl() + +# 设置面板域名 +@blueprint.route('/set_panel_domain', endpoint='set_panel_domain', methods=['POST']) +@panel_login_required +def set_panel_domain(): + domain = request.form.get('domain', '').strip() + return MwSetting.instance().setPanelDomain(domain) + + + + + + + + + diff --git a/web/admin/setting/secondary_verifiy.py b/web/admin/setting/secondary_verifiy.py new file mode 100644 index 000000000..d7bf70feb --- /dev/null +++ b/web/admin/setting/secondary_verifiy.py @@ -0,0 +1,70 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import re +import json +import os +import time + +from flask import Blueprint, render_template +from flask import request + +from admin import session +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.config as utils_config + +from .setting import blueprint +import thisdb + +# 获取二次验证信息 +@blueprint.route('/get_auth_secret', endpoint='get_auth_secret', methods=['POST']) +@panel_login_required +def get_auth_secret(): + import pyotp + + reset = request.form.get('reset', '') + tag = 'mdserver-web' + two_step_verification = thisdb.getOptionByJson('two_step_verification', default={'open':False}) + + if 'secret' in two_step_verification and reset != '1': + secret = mw.deDoubleCrypt(tag, two_step_verification['secret']) + else: + secret = pyotp.random_base32() + crypt_data = mw.enDoubleCrypt(tag, secret) + two_step_verification['secret'] = crypt_data + thisdb.setOption('two_step_verification', json.dumps(two_step_verification)) + + ip = mw.getHostAddr() + url = pyotp.totp.TOTP(secret).provisioning_uri(name=ip, issuer_name=tag) + + rdata = {} + rdata['secret'] = secret + rdata['url'] = url + return mw.returnData(True, '设置成功!', rdata) + + +# 设置二次验证,加强安全登录 +@blueprint.route('/set_auth_secret', endpoint='set_auth_secret', methods=['POST']) +@panel_login_required +def set_auth_secret(): + two_step_verification = thisdb.getOptionByJson('two_step_verification', default={'open':False}) + if two_step_verification['open']: + two_step_verification['open'] = False + thisdb.setOption('two_step_verification', json.dumps(two_step_verification)) + return mw.returnData(True, '关闭成功!', 0) + else: + two_step_verification['open'] = True + thisdb.setOption('two_step_verification', json.dumps(two_step_verification)) + return mw.returnData(True, '开启成功!', 1) + + + \ No newline at end of file diff --git a/web/admin/setting/setting.py b/web/admin/setting/setting.py new file mode 100644 index 000000000..ba8b3fbca --- /dev/null +++ b/web/admin/setting/setting.py @@ -0,0 +1,260 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import re +import json +import os +import time + +from flask import Blueprint, render_template +from flask import request + +from admin import session +from admin.user_login_check import panel_login_required + + +import core.mw as mw +import thisdb +import utils.config as utils_config + + +# 默认页面 +blueprint = Blueprint('setting', __name__, url_prefix='/setting', template_folder='../../templates') +@blueprint.route('/index', endpoint='index') +@panel_login_required +def index(): + name = thisdb.getOption('template', default='default') + return render_template('%s/setting.html' % name) + +# 设置面板名称 +@blueprint.route('/set_webname', endpoint='set_webname', methods=['POST']) +@panel_login_required +def set_webname(): + webname = request.form.get('webname', '') + src_webname = thisdb.getOption('title') + if webname != src_webname: + thisdb.setOption('title', webname) + return mw.returnData(True, '面板别名保存成功!') + +# 设置服务器IP +@blueprint.route('/set_ip', endpoint='set_ip', methods=['POST']) +@panel_login_required +def set_ip(): + host_ip = request.form.get('host_ip', '') + src_host_ip = thisdb.getOption('server_ip') + if host_ip != src_host_ip: + thisdb.setOption('server_ip', host_ip) + return mw.returnData(True, 'IP保存成功!') + +# 默认备份目录 +@blueprint.route('/set_backup_dir', endpoint='set_backup_dir', methods=['POST']) +@panel_login_required +def set_backup_dir(): + backup_path = request.form.get('backup_path', '') + src_backup_path = thisdb.getOption('backup_path') + if backup_path != src_backup_path: + thisdb.setOption('backup_path', backup_path) + return mw.returnData(True, '修改默认备份目录成功!') + +# 设置底栏文字 +@blueprint.route('/set_footer_text', endpoint='set_footer_text', methods=['POST']) +@panel_login_required +def set_footer_text(): + footer_text = request.form.get('footer_text', '').strip() + if footer_text == '': + footer_text = 'PowerPanel 2026' + thisdb.setOption('footer_text', footer_text) + return mw.returnData(True, '底栏文字保存成功!') + +# 默认站点目录 +@blueprint.route('/set_www_dir', endpoint='set_www_dir', methods=['POST']) +@panel_login_required +def set_www_dir(): + sites_path = request.form.get('sites_path', '') + src_sites_path = thisdb.getOption('site_path') + if sites_path != src_sites_path: + thisdb.setOption('site_path', sites_path) + return mw.returnData(True, '修改默认建站目录成功!') + + +# 设置安全入口 +@blueprint.route('/set_admin_path', endpoint='set_admin_path', methods=['POST']) +@panel_login_required +def set_admin_path(): + admin_path = request.form.get('admin_path', '') + admin_path_sensitive = [ + '/', '/close', '/login', + '/do_login', '/site', '/sites', + '/download_file', '/control', '/crontab', + '/firewall', '/files', '/config', '/setting','/monitor' + '/soft', '/system', '/code', + '/ssl', '/plugins', '/hook' + ] + + if admin_path == '': + admin_path = '/' + + if admin_path != '/': + if len(admin_path) < 6: + return mw.returnData(False, '安全入口地址长度不能小于6位!') + if admin_path in admin_path_sensitive: + return mw.returnData(False, '该入口已被面板占用,请使用其它入口!') + if not re.match(r"^/[\w]+$", admin_path): + return mw.returnData(False, '入口地址格式不正确,示例: /mw_rand') + + src_admin_path = thisdb.getOption('admin_path') + if admin_path != src_admin_path: + thisdb.setOption('admin_path', admin_path[1:]) + return mw.returnData(True, '修改成功!') + + + +# 设置BasicAuth认证 +@blueprint.route('/set_basic_auth', endpoint='set_basic_auth', methods=['POST']) +@panel_login_required +def set_basic_auth(): + basic_user = request.form.get('basic_user', '').strip() + basic_pwd = request.form.get('basic_pwd', '').strip() + basic_open = request.form.get('is_open', '').strip() + + __file = mw.getCommonFile() + path = __file['basic_auth'] + + is_open = True + if basic_open == 'false': + is_open = False + + if basic_open == 'false': + thisdb.setOption('basic_auth', json.dumps({'open':False})) + mw.writeLog('面板设置', '设置BasicAuth状态为: %s' % is_open) + return mw.returnData(True, '删除BasicAuth成功!') + + if basic_user == '' or basic_pwd == '': + return mw.returnData(False, '用户和密码不能为空!') + + salt = mw.getRandomString(6) + data = {} + data['salt'] = salt + data['basic_user'] = mw.md5(basic_user + salt) + data['basic_pwd'] = mw.md5(basic_pwd + salt) + data['open'] = is_open + + thisdb.setOption('basic_auth', json.dumps(data)) + mw.writeLog('面板设置', '设置BasicAuth状态为: %s' % is_open) + return mw.returnData(True, '设置成功!') + + +# 设置面板未登录状态 +@blueprint.route('/set_status_code', endpoint='set_status_code', methods=['POST']) +@panel_login_required +def set_status_code(): + status_code = request.form.get('status_code', '').strip() + if re.match(r"^\d+$", status_code): + status_code = int(status_code) + if status_code != 0: + if status_code < 100 or status_code > 999: + return mw.returnData(False, '状态码范围错误!!') + else: + return mw.returnData(False, '状态码范围错误!') + + info = utils_config.getUnauthStatus(code=str(status_code)) + thisdb.setOption('unauthorized_status', str(status_code)) + mw.writeLog('面板设置', '将未授权响应状态码设置为:{0}:{1}'.format(status_code,info['text'])) + return mw.returnData(True, '设置成功!') + +# 设置面板调式模式 +@blueprint.route('/open_debug', endpoint='open_debug', methods=['POST']) +@panel_login_required +def open_debug(): + debug = thisdb.getOption('debug',default='close') + if debug == 'open': + thisdb.setOption('debug','close') + return mw.returnData(True, '开发模式关闭!') + thisdb.setOption('debug','open') + return mw.returnData(True, '开发模式开启!') + + +# 设置面板开关 +@blueprint.route('/close_panel', endpoint='close_panel', methods=['POST']) +@panel_login_required +def close_panel(): + admin_close = thisdb.getOption('admin_close',default='no') + if admin_close == 'no': + thisdb.setOption('admin_close','yes') + return mw.returnData(True, '关闭面板成功!') + thisdb.setOption('admin_close','no') + return mw.returnData(True, '开启面板成功!') + +# 设置IPV6状态 +@blueprint.route('/set_ipv6_status', endpoint='set_ipv6_status', methods=['POST']) +@panel_login_required +def set_ipv6_status(): + __file = mw.getCommonFile() + ipv6_file = __file['ipv6'] + if os.path.exists(ipv6_file): + os.remove(ipv6_file) + mw.writeLog('面板设置', '关闭面板IPv6兼容!') + mw.returnData('面板设置', '关闭面板IPv6兼容!') + else: + mw.writeFile(ipv6_file, 'True') + mw.writeLog('面板设置', '开启面板IPv6兼容!') + mw.restartMw() + return mw.returnData(True, '设置成功!') + +# 设置面板用户 +@blueprint.route('/set_name', endpoint='set_name', methods=['POST']) +@panel_login_required +def set_name(): + name1 = request.form.get('name1', '') + name2 = request.form.get('name2', '') + if name1 != name2: + return mw.returnData(False, '两次输入的用户名不一致,请重新输入!') + if len(name1) < 3: + return mw.returnData(False, '用户名长度不能少于3位') + thisdb.setUserByName(session['username'], name1) + session['username'] = name1 + return mw.returnData(True, '用户修改成功!') + +# 设置面板密码 +@blueprint.route('/set_password', endpoint='set_password', methods=['POST']) +@panel_login_required +def set_password(): + password1 = request.form.get('password1', '') + password2 = request.form.get('password2', '') + if password1 != password2: + return mw.returnData(False, '两次输入的密码不一致,请重新输入!') + if len(password1) < 5: + return mw.returnData(False, '用户密码不能小于5位!') + + thisdb.setUserPwdByName(session['username'], password1) + return mw.returnData(True, '密码修改成功!') + +# 设置面板端口 +@blueprint.route('/set_port', endpoint='set_port', methods=['POST']) +@panel_login_required +def set_port(): + port = request.form.get('port', '') + if port != mw.getHostPort(): + from utils.firewall import Firewall as MwFirewall + + sysCfgDir = mw.systemdCfgDir() + if os.path.exists(sysCfgDir + "/firewalld.service"): + if not MwFirewall.instance().getFwStatus(): + return mw.returnData(False, 'firewalld必须先启动!') + + mw.setHostPort(port) + msg = mw.getInfo('放行端口[{1}]成功', (port,)) + mw.writeLog("防火墙管理", msg) + + MwFirewall.instance().addAcceptPort(port, 'PANEL端口-配置修改', 'port') + mw.restartMw() + + return mw.returnData(True, '端口保存成功!') + diff --git a/web/admin/setting/temp_login.py b/web/admin/setting/temp_login.py new file mode 100644 index 000000000..6aa7633af --- /dev/null +++ b/web/admin/setting/temp_login.py @@ -0,0 +1,86 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import re +import json +import os +import time + +from flask import Blueprint, render_template +from flask import request + +from admin import session +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.config as utils_config +import thisdb + +from .setting import blueprint + + +@blueprint.route('/get_temp_login', endpoint='get_temp_login', methods=['POST']) +@panel_login_required +def get_temp_login(): + limit = request.form.get('limit', '5').strip() + p = request.form.get('page', '1').strip() + tojs = request.form.get('tojs', '').strip() + + info = thisdb.getTempLoginPage(int(p), int(limit)) + + data = {} + data['data'] = info['list'] + data['page'] = mw.getPage({'count':info['count'],'tojs':'setTempAccessReq','p':p,'row':limit}) + return data + +@blueprint.route('/set_temp_login', endpoint='set_temp_login', methods=['POST']) +@panel_login_required +def set_temp_login(): + # if 'tmp_login_expire' in session: + # return mw.returnData(False, '没有权限') + + + thisdb.clearTempLogin() + + start_time = int(time.time()) + expire = start_time+3600 + + rand_str = mw.getRandomString(48) + token = mw.md5(rand_str) + + r = thisdb.addTempLogin(token, expire) + if r > 0: + mw.writeLog('面板设置', '生成临时连接,过期时间:{}'.format(mw.formatDate(times=expire))) + return {'status': True, 'msg': "临时连接已生成", 'token': token, 'expire': expire} + return mw.returnData(False, '连接生成失败') + +@blueprint.route('/remove_temp_login', endpoint='remove_temp_login', methods=['POST']) +@panel_login_required +def remove_temp_login(): + tl_id = request.form.get('id', '10').strip() + r = thisdb.deleteTempLoginById(tl_id) + if r > 0: + mw.writeLog('面板设置', '删除临时登录连接') + return mw.returnData(True, '删除成功') + return mw.returnData(False, '删除失败') + + +@blueprint.route('/get_temp_login_logs', endpoint='get_temp_login_logs', methods=['POST']) +@panel_login_required +def get_temp_login_logs(): + if 'tmp_login_expire' in session: + return mw.returnData(False, '没有权限') + return mw.returnData(False, 'ok', []) + + + + + + diff --git a/web/admin/setting/timezone.py b/web/admin/setting/timezone.py new file mode 100644 index 000000000..bc2aa7996 --- /dev/null +++ b/web/admin/setting/timezone.py @@ -0,0 +1,59 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import re +import json +import os + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.config as utils_config + +from .setting import blueprint + +# 时区相关 +@blueprint.route('/get_timezone_list', endpoint='get_timezone_list', methods=['POST']) +@panel_login_required +def get_timezone_list(): + import pytz + # 获取时区列表 + # pytz.all_timezones | 所有 + # pytz.common_timezones + return mw.returnData(True, 'ok', pytz.all_timezones) + +@blueprint.route('/sync_date', endpoint='sync_date', methods=['POST']) +@panel_login_required +def sync_date(): + if mw.isAppleSystem(): + return mw.returnData(True, '开发系统不必同步时间!') + data = mw.execShell('ntpdate -s time.nist.gov') + if data[0] == '': + return mw.returnData(True, '同步成功!') + return mw.returnData(False, '同步失败:' + data[0]) + +@blueprint.route('/set_timezone', endpoint='set_timezone', methods=['POST']) +@panel_login_required +def set_timezone(): + # 设置时区列表 + timezone = request.form.get('timezone', '').strip() + cmd = 'timedatectl set-timezone "'+timezone+'"' + mw.execShell(cmd) + return mw.returnData(True, '设置成功!') + + + + + + + \ No newline at end of file diff --git a/web/admin/setup/__init__.py b/web/admin/setup/__init__.py new file mode 100644 index 000000000..391223830 --- /dev/null +++ b/web/admin/setup/__init__.py @@ -0,0 +1,46 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os + +from .user import init_admin_user +from .option import init_option +from .init_db_system import init_db_system +from .init_cmd import init_cmd +from .init_cron import init_cron,init_acme_cron, init_auto_update + +from utils.firewall import Firewall as MwFirewall + +import thisdb +import config + +def init(): + + # 检查数据库是否存在。如果没有就创建它。 + if not os.path.isfile(config.SQLITE_PATH): + # 初始化用户信息 + thisdb.initPanelData() + init_admin_user() + init_option() + init_db_system() + + thisdb.reinstallPanelData() + init_cmd() + init_acme_cron() + init_auto_update() + # init_cron() + + + # 自动识别防火墙配置 + firewall_port = thisdb.getOption('setpu_auto_identify_firewall_port', default='no') + if firewall_port == 'no': + MwFirewall.instance().aIF() + thisdb.setOption('setpu_auto_identify_firewall_port', 'yes') + diff --git a/web/admin/setup/init_cmd.py b/web/admin/setup/init_cmd.py new file mode 100644 index 000000000..bb0280a59 --- /dev/null +++ b/web/admin/setup/init_cmd.py @@ -0,0 +1,79 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import shutil + +import core.mw as mw + +def cmdContent(): + script = mw.getPanelDir() + '/scripts/init.d/mw.tpl' + content = mw.readFile(script) + content = content.replace("{$SERVER_PATH}", mw.getPanelDir()) + content += "\n# make:{0}".format(mw.formatDate()) + return content + + +def init_cmd(): + cmd_content = cmdContent() + script_bin = mw.getPanelDir() + '/scripts/init.d/mw' + mw.writeFile(script_bin, cmd_content) + mw.execShell('chmod +x ' + script_bin) + + # 在linux系统中,确保/etc/init.d存在 + if not mw.isAppleSystem() and not os.path.exists("/etc/rc.d/init.d"): + mw.execShell('mkdir -p /etc/rc.d/init.d') + + if not mw.isAppleSystem() and not os.path.exists("/etc/init.d"): + mw.execShell('mkdir -p /etc/init.d') + # initd + if os.path.exists('/etc/rc.d/init.d'): + initd_bin = '/etc/rc.d/init.d/mw' + if not os.path.exists(initd_bin): + mw.writeFile(initd_bin, cmd_content) + mw.execShell('chmod +x ' + initd_bin) + # 加入自启动 + mw.execShell('which chkconfig && chkconfig --add mw') + + + if os.path.exists('/etc/init.d'): + initd_bin = '/etc/init.d/mw' + if not os.path.exists(initd_bin): + mw.writeFile(initd_bin, cmd_content) + mw.execShell('chmod +x ' + initd_bin) + # 加入自启动 + mw.execShell('which update-rc.d && update-rc.d -f mw defaults') + + # sys_name = mw.getOsName() + # if sys_name == 'opensuse': + # init_cmd_systemd() + return True + + +def init_cmd_systemd(): + systemd_dir = mw.systemdCfgDir() + + systemd_mw = systemd_dir + '/mw.service' + systemd_mw_task = systemd_dir + '/mw-task.service' + + systemd_mw_tpl = mw.getPanelDir() + '/scripts/init.d/mw.service.tpl' + systemd_mw_task_tpl = mw.getPanelDir() + '/scripts/init.d/mw-task.service.tpl' + + if os.path.exists(systemd_mw): + os.remove(systemd_mw) + if os.path.exists(systemd_mw_task): + os.remove(systemd_mw_task) + + contentReplace(systemd_mw_tpl, systemd_mw) + contentReplace(systemd_mw_task_tpl, systemd_mw_task) + + mw.execShell('systemctl enable mw') + mw.execShell('systemctl enable mw-task') + mw.execShell('systemctl daemon-reload') \ No newline at end of file diff --git a/web/admin/setup/init_cron.py b/web/admin/setup/init_cron.py new file mode 100644 index 000000000..319028fde --- /dev/null +++ b/web/admin/setup/init_cron.py @@ -0,0 +1,141 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import core.mw as mw +from utils.crontab import crontab +from croniter import croniter +from datetime import datetime +import thisdb + +def cron_todb(data): + # print("------") + rdata = {} + if data[3] == "*" and data[2] != "*" : + rdata['type'] = 'month' + elif data[3] == "*" and data[4] != "*" : + rdata['type'] = 'week' + elif data[3] == "*" and data[4] == "*" and data[2] == "*" : + rdata['type'] = 'day' + elif data[1].find("/") > -1 : + rdata['type'] = 'hour-n' + elif data[0].find("/") > -1 : + rdata['type'] = 'minute-n' + + # print(rdata) + # print(data) + return rdata + # print("------") + +def init_acme_cron(): + name = "[勿删]ACME定时强制更新" + res = mw.M("crontab").field("id, name").where("name=?", (name,)).find() + if res: + return True + + cmd = "/root/.acme.sh/acme.sh --cron --force --standalone" + params = { + 'name': name, + 'type': 'day-n', + 'week': "", + 'where1': "7", + 'hour': 4, + 'minute': 15, + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + 'attr':'', + } + + crontab.instance().add(params) + return True + +def init_auto_update(): + name = "[可删]面板自动更新" + res = mw.M("crontab").field("id, name").where("name=?", (name,)).find() + if res: + return True + + cmd = "mw update" + params = { + 'name': name, + 'type': 'month', + 'week': "", + 'where1': "1", + 'hour': 4, + 'minute': 15, + 'save': "", + 'backup_to': "", + 'stype': "toShell", + 'sname': '', + 'sbody': cmd, + 'url_address': '', + 'attr':'', + } + + crontab.instance().add(params) + return True + +# 识别linux计划任务 +def init_cron(): + file = '' + cron_file = [ + '/var/spool/cron/crontabs/root', + '/var/spool/cron/root', + ] + for i in cron_file: + if os.path.exists(i): + file = i + + if file == "": + return True + + with open(file) as f: + for line in f.readlines(): + cron_line = line.strip() + if cron_line.startswith("#"): + continue + + cron_expression = cron_line.split(maxsplit=5) # 提取前 5 个字段(* * * * *) + command = cron_line.split(maxsplit=5)[5] # 提取命令部分 + + # 面板计划任务过滤 + if command.startswith("/www/server/cron"): + continue + + if command.startswith("\"/root/.acme.sh\""): + # print(cron_expression, command) + info = cron_todb(cron_expression) + + # print(info) + + # add_dbdata = {} + # add_dbdata['name'] = "ACME" + # add_dbdata['type'] = data['type'] + # add_dbdata['where1'] = data['where1'] + # add_dbdata['where_hour'] = data['hour'] + # add_dbdata['where_minute'] = data['minute'] + # add_dbdata['save'] = "" + # add_dbdata['backup_to'] = "" + # add_dbdata['sname'] = "" + # add_dbdata['sbody'] = data['sbody'] + # add_dbdata['stype'] = "toShell" + # add_dbdata['echo'] = command + # add_dbdata['url_address'] = '' + + # thisdb.addCrontab(add_dbdata) + + # print(command) + + # cron_list = content.split("\n") + # print(cron_list) \ No newline at end of file diff --git a/web/admin/setup/init_db_system.py b/web/admin/setup/init_db_system.py new file mode 100644 index 000000000..bcce3db89 --- /dev/null +++ b/web/admin/setup/init_db_system.py @@ -0,0 +1,16 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +from utils.system import monitor + + +def init_db_system(): + monitor.instance().initDBFile() + return True \ No newline at end of file diff --git a/web/admin/setup/option.py b/web/admin/setup/option.py new file mode 100644 index 000000000..ade9ace65 --- /dev/null +++ b/web/admin/setup/option.py @@ -0,0 +1,71 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import json + +import core.mw as mw +import thisdb + +def init_option(): + thisdb.setOption('title', 'PowerLinux 3') + thisdb.setOption('recycle_bin', 'open') + thisdb.setOption('template', 'default') + + # SSL邮件地址 + thisdb.setOption('ssl_email', '') + + # 后台面板是否关闭 + thisdb.setOption('admin_close', 'no') + + # 未认证状态码 + thisdb.setOption('unauthorized_status', '0') + + # 调式模式,默认关闭 + thisdb.setOption('debug', 'close') + + # basic auth 配置 + thisdb.setOption('basic_auth', json.dumps({'open':False})) + + # 二步验证|默认关闭 + thisdb.setOption('two_step_verification', json.dumps({'open':False})) + + # 开启后台任务 + # thisdb.setOption('run_bg_task', 'close') + + # 首页展示初始化 + thisdb.setOption('display_index', '[]') + + # 监控默认配置 + thisdb.setOption('monitor_status', 'open', type='monitor') + thisdb.setOption('monitor_day', '30', type='monitor') + thisdb.setOption('monitor_only_netio', 'open', type='monitor') + + # 初始化安全路径 + thisdb.setOption('admin_path', mw.getRandomString(8)) + + # API是否开启|默认关闭 + thisdb.setOption('panel_api', json.dumps({'open':False})) + + # 获取服务器IP + ip = mw.getLocalIp() + thisdb.setOption('server_ip', ip) + + # 默认备份目录 + thisdb.setOption('backup_path', mw.getFatherDir()+'/backup') + # 默认站点目录 + thisdb.setOption('site_path', mw.getFatherDir()+'/wwwroot') + + + # 异步邮件通知 + thisdb.setOption('notify_email', json.dumps({'open':False}), type='notify') + # 异步Telegram Bot 通知 + thisdb.setOption('notify_tgbot', json.dumps({'open':False}), type='notify') + + return True diff --git a/web/admin/setup/sql/default.sql b/web/admin/setup/sql/default.sql new file mode 100755 index 000000000..bc30063b3 --- /dev/null +++ b/web/admin/setup/sql/default.sql @@ -0,0 +1,164 @@ + +CREATE TABLE IF NOT EXISTS `backup` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pid` INTEGER, + `type` INTEGER, + `name` TEXT, + `filename` TEXT, + `size` INTEGER, + `add_time` TEXT +); + +CREATE TABLE IF NOT EXISTS `binding` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pid` INTEGER, + `port` INTEGER, + `domain` TEXT, + `path` TEXT, + `add_time` TEXT +); + + +CREATE TABLE IF NOT EXISTS `crontab` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `name` TEXT, + `type` TEXT, + `where1` TEXT, + `where_hour` INTEGER, + `where_minute` INTEGER, + `echo` TEXT, + `status` INTEGER DEFAULT '1', + `save` INTEGER DEFAULT '3', + `backup_to` TEXT DEFAULT 'off', + `sname` TEXT, + `sbody` TEXT, + `stype` TEXT, + `url_address` TEXT, + `attr` TEXT DEFAULT '', + `add_time` TEXT, + `update_time` TEXT +); +CREATE UNIQUE INDEX crontab_name_idx ON crontab(name); +ALTER TABLE crontab ADD COLUMN attr TEXT DEFAULT ''; + + +CREATE TABLE IF NOT EXISTS `firewall` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `port` TEXT, + `protocol` TEXT DEFAULT 'tcp', + `ps` TEXT, + `add_time` TEXT, + `update_time` TEXT +); +CREATE UNIQUE INDEX firewall_port_idx ON firewall(port); + +INSERT INTO `firewall` (`id`, `port`, `protocol`, `ps`, `add_time`, `update_time`) VALUES +(1, '80', 'tcp', '网站默认端口', '0000-00-00 00:00:00','0000-00-00 00:00:00'), +(2, '443', 'tcp/udp', 'HTTPS', '0000-00-00 00:00:00','0000-00-00 00:00:00'); + +CREATE TABLE IF NOT EXISTS `logs` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `type` TEXT, + `log` TEXT, + `uid` INTEGER DEFAULT '1', + `add_time` TEXT +); + +CREATE TABLE IF NOT EXISTS `sites` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `name` TEXT, + `path` TEXT, + `status` TEXT, + `index` TEXT, + `type_id` INTEGER, + `ps` TEXT, + `edate` TEXT, + `ssl_effective_date` TEXT, + `ssl_expiration_date` TEXT, + `add_time` TEXT, + `update_time` TEXT +); +CREATE UNIQUE INDEX sites_name_idx ON sites(name); + +CREATE TABLE IF NOT EXISTS `site_types` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `name` TEXT +); +CREATE UNIQUE INDEX site_types_name_idx ON site_types(name); + + +CREATE TABLE IF NOT EXISTS `domain` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pid` INTEGER, + `name` TEXT, + `port` INTEGER, + `main` INTEGER DEFAULT '0', + `add_time` TEXT +); +ALTER TABLE `domain` ADD COLUMN `main` INTEGER DEFAULT '0'; + +CREATE TABLE IF NOT EXISTS `users` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `name` TEXT, + `password` TEXT, + `login_ip` TEXT, + `login_time` TEXT, + `phone` TEXT, + `email` TEXT, + `add_time` TEXT, + `update_time` TEXT +); + +CREATE TABLE IF NOT EXISTS `tasks` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `name` TEXT, + `type` TEXT, + `start` INTEGER, + `end` INTEGER, + `cmd` TEXT, + `status` INTEGER, + `add_time` TEXT +); + +CREATE TABLE IF NOT EXISTS `temp_login` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `token` REAL, + `salt` REAL, + `state` INTEGER, + `login_time` INTEGER, + `login_addr` REAL, + `logout_time` INTEGER, + `expire` INTEGER, + `add_time` TEXT +); + +CREATE TABLE IF NOT EXISTS `panel` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `title` TEXT, + `url` TEXT, + `username` TEXT, + `password` TEXT, + `click` INTEGER, + `add_time` TEXT +); + +CREATE TABLE IF NOT EXISTS `app` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `name` TEXT, + `app_id` TEXT, + `app_secret` TEXT, + `white_list` TEXT, + `status` INTEGER, + `add_time` TEXT, + `update_time` TEXT +); +CREATE UNIQUE INDEX app_name_idx ON app(name); +CREATE UNIQUE INDEX app_id_idx ON app(app_id); + +CREATE TABLE IF NOT EXISTS `option` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `name` TEXT, + `type` TEXT, + `value` TEXT +); +CREATE UNIQUE INDEX option_name_idx ON option(name); diff --git a/web/admin/setup/sql/system.sql b/web/admin/setup/sql/system.sql new file mode 100755 index 000000000..31ed453ee --- /dev/null +++ b/web/admin/setup/sql/system.sql @@ -0,0 +1,41 @@ +CREATE TABLE IF NOT EXISTS `network` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `up` TEXT, + `down` TEXT, + `total_up` INTEGER, + `total_down` INTEGER, + `down_packets` INTEGER, + `up_packets` INTEGER, + `addtime` INTEGER +); +CREATE INDEX network_addtime_idx ON network(`addtime`); + +CREATE TABLE IF NOT EXISTS `cpuio` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pro` TEXT, + `mem` TEXT, + `addtime` INTEGER +); +CREATE INDEX cpuio_addtime_idx ON cpuio(`addtime`); + +CREATE TABLE IF NOT EXISTS `diskio` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `read_count` INTEGER, + `write_count` INTEGER, + `read_bytes` INTEGER, + `write_bytes` INTEGER, + `read_time` INTEGER, + `write_time` INTEGER, + `addtime` INTEGER +); +CREATE INDEX diskio_addtime_idx ON diskio(`addtime`); + +CREATE TABLE IF NOT EXISTS `load_average` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `pro` REAL, + `one` REAL, + `five` REAL, + `fifteen` REAL, + `addtime` INTEGER +); +CREATE INDEX load_average_addtime_idx ON load_average(`addtime`); \ No newline at end of file diff --git a/web/admin/setup/user.py b/web/admin/setup/user.py new file mode 100644 index 000000000..1065ef0c1 --- /dev/null +++ b/web/admin/setup/user.py @@ -0,0 +1,21 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +from flask import request + + +import core.mw as mw +import thisdb + +# 初始化用户信息 +def init_admin_user(): + thisdb.initAdminUser() + return True + diff --git a/web/admin/site/__init__.py b/web/admin/site/__init__.py new file mode 100644 index 000000000..d1128d871 --- /dev/null +++ b/web/admin/site/__init__.py @@ -0,0 +1,24 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +from .site import * +from .site_types import * +from .site_default import * + +from .backup import * + +from .php import * +from .logs import * +from .dir import * +from .redirect import * +from .proxy import * + +from .ssl import * +from .ssl_acme import * \ No newline at end of file diff --git a/web/admin/site/backup.py b/web/admin/site/backup.py new file mode 100644 index 000000000..72b978fcf --- /dev/null +++ b/web/admin/site/backup.py @@ -0,0 +1,54 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites + +import core.mw as mw +import thisdb + +from .site import blueprint + +# 获取备份列表 +@blueprint.route('/get_backup', endpoint='get_backup',methods=['POST']) +@panel_login_required +def get_backup(): + limit = request.form.get('limit', '') + page = request.form.get('p', '') + site_id = request.form.get('search', '') + return MwSites.instance().getBackup(site_id,page=page,size=limit) + +# 设置备份 +@blueprint.route('/to_backup', endpoint='to_backup',methods=['POST']) +@panel_login_required +def to_backup(): + site_id = request.form.get('id', '') + return MwSites.instance().toBackup(site_id) + +# 删除备份 +@blueprint.route('/del_backup', endpoint='del_backup',methods=['POST']) +@panel_login_required +def del_backup(): + site_id = request.form.get('id', '') + return MwSites.instance().delBackup(site_id) + + + + + + + diff --git a/web/admin/site/dir.py b/web/admin/site/dir.py new file mode 100644 index 000000000..5cc7d15a7 --- /dev/null +++ b/web/admin/site/dir.py @@ -0,0 +1,74 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required +from utils.site import sites as MwSites + +import core.mw as mw +import thisdb + +from .site import blueprint + +# 获取网站目录 +@blueprint.route('/get_dir_user_ini', endpoint='get_dir_user_ini',methods=['POST']) +@panel_login_required +def get_dir_user_ini(): + site_id = request.form.get('id', '') + return MwSites.instance().getDirUserIni(site_id) + +# 设置防跨站攻击 +@blueprint.route('/set_dir_user_ini', endpoint='set_dir_user_ini',methods=['POST']) +@panel_login_required +def set_dir_user_ini(): + path = request.form.get('path', '') + run_path = request.form.get('run_path', '') + return MwSites.instance().setDirUserIni(path,run_path) + +# 获取子目录绑定 +@blueprint.route('/get_dir_binding', endpoint='get_dir_binding',methods=['POST']) +@panel_login_required +def get_dir_binding(): + site_id = request.form.get('id', '') + return MwSites.instance().getDirBinding(site_id) + + +# 添加子目录绑定 +@blueprint.route('/add_dir_bind', endpoint='add_dir_bind',methods=['POST']) +@panel_login_required +def add_dir_bind(): + site_id = request.form.get('id', '') + domain = request.form.get('domain', '') + dir_name = request.form.get('dir_name', '') + return MwSites.instance().addDirBind(site_id,domain,dir_name) + + +# 获取目录绑定rewrite +@blueprint.route('/get_dir_bind_rewrite', endpoint='get_dir_bind_rewrite',methods=['POST']) +@panel_login_required +def get_dir_bind_rewrite(): + binding_id = request.form.get('id', '') + add = request.form.get('add', '') + return MwSites.instance().getDirBindingRewrite(binding_id,add) + + +# 获取目录绑定rewrite +@blueprint.route('/del_dir_bind', endpoint='del_dir_bind',methods=['POST']) +@panel_login_required +def del_dir_bind(): + binding_id = request.form.get('id', '') + return MwSites.instance().delDirBinding(binding_id) + + diff --git a/web/admin/site/logs.py b/web/admin/site/logs.py new file mode 100644 index 000000000..e916022a8 --- /dev/null +++ b/web/admin/site/logs.py @@ -0,0 +1,45 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites + +import core.mw as mw +import thisdb + +from .site import blueprint + +@blueprint.route('/get_logs', endpoint='get_logs',methods=['POST']) +@panel_login_required +def get_logs(): + siteName = request.form.get('siteName', '') + return MwSites.instance().getLogs(siteName) + + +@blueprint.route('/get_error_logs', endpoint='get_error_logs',methods=['POST']) +@panel_login_required +def get_error_logs(): + siteName = request.form.get('siteName', '') + return MwSites.instance().getErrorLogs(siteName) + + + + + + + diff --git a/web/admin/site/php.py b/web/admin/site/php.py new file mode 100644 index 000000000..0847fc83e --- /dev/null +++ b/web/admin/site/php.py @@ -0,0 +1,60 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites + +import core.mw as mw +import thisdb + +from .site import blueprint + +@blueprint.route('/get_cli_php_version', endpoint='get_cli_php_version',methods=['POST']) +@panel_login_required +def get_cli_php_version(): + php_dir = mw.getServerDir() + '/php' + if not os.path.exists(php_dir): + return mw.returnData(False, '未安装PHP,无法设置') + + php_bin = '/usr/bin/php' + data = MwSites.instance().getPhpVersion() + php_versions = data['data'] + php_versions = php_versions[1:] + + if len(php_versions) < 1: + return mw.returnData(False, '未安装PHP,无法设置') + + if os.path.exists(php_bin) and os.path.islink(php_bin): + link_re = os.readlink(php_bin) + for v in php_versions: + if link_re.find(v['version']) != -1: + return {"select": v, "versions": php_versions} + + return {"select": php_versions[0],"versions": php_versions} + +@blueprint.route('/set_cli_php_version', endpoint='set_cli_php_version',methods=['POST']) +@panel_login_required +def set_cli_php_version(): + if mw.isAppleSystem(): + return mw.returnData(False, "开发机不可设置!") + version = request.form.get('version', '') + return MwSites.instance().setCliPhpVersion(version) + + + + diff --git a/web/admin/site/proxy.py b/web/admin/site/proxy.py new file mode 100644 index 000000000..2574a681e --- /dev/null +++ b/web/admin/site/proxy.py @@ -0,0 +1,89 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites + +import core.mw as mw +import thisdb + +from .site import blueprint + + +# 获取代理列表 +@blueprint.route('/get_proxy_list', endpoint='get_proxy_list', methods=['POST']) +@panel_login_required +def get_proxy_list(): + site_name = request.form.get("siteName", '') + return MwSites.instance().getProxyList(site_name) + +# 获取代理列表 +@blueprint.route('/set_proxy', endpoint='set_proxy', methods=['POST']) +@panel_login_required +def set_proxy(): + site_name = request.form.get('siteName', '') + site_from = request.form.get('from', '') + to = request.form.get('to', '') + host = request.form.get('host', '') + name = request.form.get('name', '') + open_proxy = request.form.get('open_proxy', '') + open_cors = request.form.get('open_cors','') + open_cache = request.form.get('open_cache', '') + cache_time = request.form.get('cache_time', '') + proxy_id = request.form.get('id', '') + return MwSites.instance().setProxy(site_name,site_from,to,host,name,open_proxy,open_cors,open_cache,cache_time, proxy_id) + +# 设置代理状态 +@blueprint.route('/set_proxy_status', endpoint='set_proxy_status', methods=['POST']) +@panel_login_required +def set_proxy_status(): + site_name = request.form.get("siteName", '') + status = request.form.get("status", '') + proxy_id = request.form.get("id", '') + return MwSites.instance().setProxyStatus(site_name,proxy_id,status) + + +# 获取代理配置 +@blueprint.route('/get_proxy_conf', endpoint='get_proxy_conf', methods=['POST']) +@panel_login_required +def get_proxy_conf(): + site_name = request.form.get("siteName", '') + rid = request.form.get("id", '') + return MwSites.instance().getProxyConf(site_name, rid) + +# 设置代理 +@blueprint.route('/save_proxy_conf', endpoint='save_proxy_conf', methods=['POST']) +@panel_login_required +def save_proxy_conf(): + site_name = request.form.get("siteName", '') + rid = request.form.get("id", '') + config = request.form.get("config", "") + return MwSites.instance().saveProxyConf(site_name, rid, config) + + +# 删除代理配置 +@blueprint.route('/del_proxy', endpoint='del_proxy', methods=['POST']) +@panel_login_required +def del_proxy(): + site_name = request.form.get("siteName", '') + rid = request.form.get("id", '') + return MwSites.instance().delProxy(site_name, rid) + + + + diff --git a/web/admin/site/redirect.py b/web/admin/site/redirect.py new file mode 100644 index 000000000..e02526bd8 --- /dev/null +++ b/web/admin/site/redirect.py @@ -0,0 +1,91 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites + +import core.mw as mw +import thisdb + +from .site import blueprint + +# 获取重定向列表 +@blueprint.route('/get_redirect', endpoint='get_redirect', methods=['POST']) +@panel_login_required +def get_redirect(): + site_name = request.form.get("siteName", '') + return MwSites.instance().getRedirect(site_name) + +# 设置重定向列表 +@blueprint.route('/set_redirect', endpoint='set_redirect', methods=['POST']) +@panel_login_required +def set_redirect(): + site_name = request.form.get("siteName", '') + site_from = request.form.get("from", '') + to = request.form.get("to", '') # redirect to + type = request.form.get("type", '') # path / domain + r_type = request.form.get("r_type", '') # redirect type + keep_path = request.form.get("keep_path", '') # keep path + return MwSites.instance().setRedirect(site_name, site_from, to, type, r_type, keep_path) + + +# 设置重定向状态 +@blueprint.route('/set_redirect_status', endpoint='set_redirect_status', methods=['POST']) +@panel_login_required +def set_redirect_status(): + site_name = request.form.get("siteName", '') + status = request.form.get("status") + redirect_id = request.form.get("id", '') + return MwSites.instance().setRedirectStatus(site_name, redirect_id, status) + +# 获取重定向配置 +@blueprint.route('/get_redirect_conf', endpoint='get_redirect_conf', methods=['POST']) +@panel_login_required +def get_redirect_conf(): + site_name = request.form.get("siteName", '') + redirect_id = request.form.get("id", '') + return MwSites.instance().getRedirectConf(site_name, redirect_id) + +# 设置重定向配置 +@blueprint.route('/save_redirect_conf', endpoint='save_redirect_conf', methods=['POST']) +@panel_login_required +def save_redirect_conf(): + site_name = request.form.get("siteName", '') + redirect_id = request.form.get("id", '') + config = request.form.get("config", "") + return MwSites.instance().saveRedirectConf(site_name, redirect_id, config) + + + + +# 删除重定向配置 +@blueprint.route('/del_redirect', endpoint='del_redirect', methods=['POST']) +@panel_login_required +def del_redirect(): + site_name = request.form.get("siteName", '') + redirect_id = request.form.get("id", '') + return MwSites.instance().delRedirect(site_name, redirect_id) + + + + + + + + + diff --git a/web/admin/site/site.py b/web/admin/site/site.py new file mode 100644 index 000000000..0768e4111 --- /dev/null +++ b/web/admin/site/site.py @@ -0,0 +1,348 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites +import core.mw as mw +import thisdb + +blueprint = Blueprint('site', __name__, url_prefix='/site', template_folder='../../templates/default') +@blueprint.route('/index', endpoint='index') +@panel_login_required +def index(): + return render_template('site.html') + +# 站点列表 +@blueprint.route('/list', endpoint='list', methods=['GET','POST']) +@panel_login_required +def list(): + p = request.form.get('p', '1') + limit = request.form.get('limit', '10') + type_id = request.form.get('type_id', '0').strip() + search = request.form.get('search', '').strip() + order = request.form.get('order', '').strip() + + info = thisdb.getSitesList(page=int(p),size=int(limit),type_id=int(type_id), search=search,order=order) + + data = {} + data['data'] = info['list'] + data['page'] = mw.getPage({'count':info['count'],'tojs':'getWeb','p':p, 'row':limit}) + return data + +# 添加站点 +@blueprint.route('/add', endpoint='add',methods=['POST']) +@panel_login_required +def add(): + webinfo = request.form.get('webinfo', '') + ps = request.form.get('ps', '') + path = request.form.get('path', '') + version = request.form.get('version', '') + port = request.form.get('port', '') + return MwSites.instance().add(webinfo, port, ps, path, version) + +# 站点停止 +@blueprint.route('/stop', endpoint='stop',methods=['POST']) +@panel_login_required +def stop(): + site_id = request.form.get('id', '') + return MwSites.instance().stop(site_id) + +# 站点开启 +@blueprint.route('/start', endpoint='start',methods=['POST']) +@panel_login_required +def start(): + site_id = request.form.get('id', '') + return MwSites.instance().start(site_id) + +# 添加站点 - 域名 +@blueprint.route('/add_domain', endpoint='add_domain',methods=['POST']) +@panel_login_required +def add_domain(): + domain = request.form.get('domain', '') + site_name = request.form.get('site_name', '') + site_id = request.form.get('id', '') + return MwSites.instance().addDomain(site_id, site_name, domain) + +# 删除站点 - 域名 +@blueprint.route('/del_domain', endpoint='del_domain',methods=['POST']) +@panel_login_required +def del_domain(): + site_name = request.form.get('site_name', '') + site_id = request.form.get('id', '') + domain = request.form.get('domain', '') + port = request.form.get('port', '') + return MwSites.instance().delDomain(site_id, site_name, domain, port) + +# 站点删除 +@blueprint.route('/delete', endpoint='delete',methods=['POST']) +@panel_login_required +def delete(): + site_id = request.form.get('id', '') + path = request.form.get('path', '') + return MwSites.instance().delete(site_id, path) + +# 获取站点根目录 +@blueprint.route('/get_root_dir', endpoint='get_root_dir',methods=['POST']) +@panel_login_required +def get_root_dir(): + data = {} + data['dir'] = mw.getWwwDir() + return data + +# 获取站点默认文档 +@blueprint.route('/get_index', endpoint='get_index',methods=['POST']) +@panel_login_required +def get_index(): + site_id = request.form.get('id', '') + data = {} + index = MwSites.instance().getIndex(site_id) + data['index'] = index + return data + +# 获取站点默认文档 +@blueprint.route('/set_index', endpoint='set_index',methods=['POST']) +@panel_login_required +def set_index(): + site_id = request.form.get('id', '') + index = request.form.get('index', '') + return MwSites.instance().setIndex(site_id, index) + +# 获取站点默认文档 +@blueprint.route('/get_limit_net', endpoint='get_limit_net',methods=['POST']) +@panel_login_required +def get_limit_net(): + site_id = request.form.get('id', '') + return MwSites.instance().getLimitNet(site_id) + +# 获取站点默认文档 +@blueprint.route('/set_limit_net', endpoint='set_limit_net',methods=['POST']) +@panel_login_required +def set_limit_net(): + site_id = request.form.get('id', '') + perserver = request.form.get('perserver', '') + perip = request.form.get('perip', '') + limit_rate = request.form.get('limit_rate', '') + return MwSites.instance().setLimitNet(site_id, perserver,perip,limit_rate) + +# 获取站点默认文档 +@blueprint.route('/close_limit_net', endpoint='close_limit_net',methods=['POST']) +@panel_login_required +def close_limit_net(): + site_id = request.form.get('id', '') + return MwSites.instance().closeLimitNet(site_id) + +# 获取站点配置 +@blueprint.route('/get_host_conf', endpoint='get_host_conf',methods=['POST']) +@panel_login_required +def get_host_conf(): + siteName = request.form.get('siteName', '') + host = MwSites.instance().getHostConf(siteName) + return {'host': host} + +# 设置站点配置 +@blueprint.route('/save_host_conf', endpoint='save_host_conf',methods=['POST']) +@panel_login_required +def save_host_conf(): + path = request.form.get('path', '') + data = request.form.get('data', '') + encoding = request.form.get('encoding', '') + return MwSites.instance().saveHostConf(path,data,encoding) + +# 获取站点PHP版本 +@blueprint.route('/get_site_php_version', endpoint='get_site_php_version',methods=['POST']) +@panel_login_required +def get_site_php_version(): + siteName = request.form.get('siteName', '') + return MwSites.instance().getSitePhpVersion(siteName) + +# 获取站点PHP版本 +@blueprint.route('/get_site_domains', endpoint='get_site_domains',methods=['POST']) +@panel_login_required +def get_site_domains(): + site_id = request.form.get('id', '') + data = thisdb.getSitesDomainById(site_id) + return mw.returnData(True, 'OK', data) + +# 设置站点PHP版本 +@blueprint.route('/set_php_version', endpoint='set_php_version',methods=['POST']) +@panel_login_required +def set_php_version(): + siteName = request.form.get('siteName', '') + version = request.form.get('version', '') + return MwSites.instance().setPhpVersion(siteName,version) + +# 检查OpenResty安装/启动状态 +@blueprint.route('/check_web_status', endpoint='check_web_status',methods=['POST']) +@panel_login_required +def check_web_status(): + ''' + 创建站点检查web服务 + ''' + if not mw.isInstalledWeb(): + return mw.returnJson(False, '请安装并启动OpenResty服务!') + + # 这个快点 + pid = mw.getServerDir() + '/openresty/nginx/logs/nginx.pid' + if not os.path.exists(pid): + return mw.returnData(False, '请启动OpenResty服务!') + return mw.returnData(True, 'OK') + +# 获取PHP版本 +@blueprint.route('/get_php_version', endpoint='get_php_version',methods=['POST']) +@panel_login_required +def get_php_version(): + return MwSites.instance().getPhpVersion() + +# 设置网站到期 +@blueprint.route('/set_end_date', endpoint='set_end_date',methods=['POST']) +@panel_login_required +def set_end_date(): + site_id = request.form.get('id', '') + edate = request.form.get('edate', '') + return MwSites.instance().setEndDate(site_id, edate) + + +# 设置网站备注 +@blueprint.route('/set_ps', endpoint='set_ps',methods=['POST']) +@panel_login_required +def set_ps(): + site_id = request.form.get('id', '') + ps = request.form.get('ps', '') + return MwSites.instance().setPs(site_id, ps) + +# 站点绑定域名 +@blueprint.route('/get_domain', endpoint='get_domain',methods=['POST']) +@panel_login_required +def get_domain(): + site_id = request.form.get('pid', '') + return MwSites.instance().getDomain(site_id) + +# 获取默认为静态列表 +@blueprint.route('/get_rewrite_list', endpoint='get_rewrite_list',methods=['POST']) +@panel_login_required +def get_rewrite_list(): + return MwSites.instance().getRewriteList() + +# 获取站点Rewrite配置 +@blueprint.route('/get_rewrite_conf', endpoint='get_rewrite_conf',methods=['POST']) +@panel_login_required +def get_rewrite_conf(): + siteName = request.form.get('siteName', '') + rewrite = MwSites.instance().getRewriteConf(siteName) + return {'rewrite': rewrite} + +# 获取Rewrite模版名 +@blueprint.route('/get_rewrite_tpl', endpoint='get_rewrite_tpl',methods=['POST']) +@panel_login_required +def get_rewrite_tpl(): + tplname = request.form.get('tplname', '') + return MwSites.instance().getRewriteTpl(tplname) + +# 设置站点Rewrite +@blueprint.route('/set_rewrite', endpoint='set_rewrite',methods=['POST']) +@panel_login_required +def set_rewrite(): + data = request.form.get('data', '') + path = request.form.get('path', '') + encoding = request.form.get('encoding', '') + return MwSites.instance().setRewrite(path,data,encoding) + + +# 设置Rewrite模版名 +@blueprint.route('/set_rewrite_tpl', endpoint='set_rewrite_tpl',methods=['POST']) +@panel_login_required +def set_rewrite_tpl(): + name = request.form.get('name', '') + data = request.form.get('data', '') + return MwSites.instance().setRewriteTpl(name,data) + +# 网站日志开关 +@blueprint.route('/logs_open', endpoint='logs_open',methods=['POST']) +@panel_login_required +def logs_open(): + site_id = request.form.get('id', '') + return MwSites.instance().logsOpen(site_id) + +# 设置网站路径 +@blueprint.route('/set_path', endpoint='set_path',methods=['POST']) +@panel_login_required +def set_path(): + site_id = request.form.get('id', '') + path = request.form.get('path', '') + return MwSites.instance().setSitePath(site_id, path) + + +# 设置网站路径 +@blueprint.route('/set_site_run_path', endpoint='set_site_run_path',methods=['POST']) +@panel_login_required +def set_site_run_path(): + site_id = request.form.get('id', '') + run_path = request.form.get('run_path', '') + return MwSites.instance().setSiteRunPath(site_id, run_path) + + +# 设置网站 - 开启密码访问 +@blueprint.route('/set_has_pwd', endpoint='set_has_pwd',methods=['POST']) +@panel_login_required +def set_has_pwd(): + site_id = request.form.get('id', '') + username = request.form.get('username', '') + password = request.form.get('password', '') + return MwSites.instance().setHasPwd(site_id, username, password) + +# 设置网站 - 关闭密码访问 +@blueprint.route('/close_has_pwd', endpoint='close_has_pwd',methods=['POST']) +@panel_login_required +def close_has_pwd(): + site_id = request.form.get('id', '') + return MwSites.instance().closeHasPwd(site_id) + +# 获取防盗链信息 +@blueprint.route('/get_security', endpoint='get_security',methods=['POST']) +@panel_login_required +def get_security(): + site_id = request.form.get('id', '') + return MwSites.instance().getSecurity(site_id) + +# 设置防盗链 +@blueprint.route('/set_security', endpoint='set_security',methods=['POST']) +@panel_login_required +def set_security(): + fix = request.form.get('fix', '') + domains = request.form.get('domains', '') + status = request.form.get('status', '') + name = request.form.get('name', '') + none = request.form.get('none', '') + site_id = request.form.get('id', '') + return MwSites.instance().setSecurity(site_id, fix, domains, status, none) + + +# 设置默认网站信息 +@blueprint.route('/get_default_site', endpoint='get_default_site',methods=['POST']) +@panel_login_required +def get_default_site(): + return MwSites.instance().getDefaultSite() + +# 设置默认站 +@blueprint.route('/set_default_site', endpoint='set_default_site',methods=['POST']) +@panel_login_required +def set_default_site(): + name = request.form.get('name', '') + return MwSites.instance().setDefaultSite(name) + + + diff --git a/web/admin/site/site_default.py b/web/admin/site/site_default.py new file mode 100644 index 000000000..293d128fd --- /dev/null +++ b/web/admin/site/site_default.py @@ -0,0 +1,37 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites + +import core.mw as mw +import thisdb + +from .site import blueprint + +@blueprint.route('/get_site_doc', endpoint='get_site_doc',methods=['POST']) +def get_site_doc(): + stype = request.form.get('type', '0').strip() + vlist = [] + vlist.append('') + vlist.append(mw.getServerDir() +'/openresty/nginx/html/index.html') + vlist.append(mw.getServerDir() + '/openresty/nginx/html/404.html') + vlist.append(mw.getServerDir() +'/openresty/nginx/html/index.html') + vlist.append(mw.getServerDir() + '/web_conf/stop/index.html') + data = {} + data['path'] = vlist[int(stype)] + return mw.returnData(True, 'ok', data) \ No newline at end of file diff --git a/web/admin/site/site_types.py b/web/admin/site/site_types.py new file mode 100644 index 000000000..0f6e34bcb --- /dev/null +++ b/web/admin/site/site_types.py @@ -0,0 +1,84 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites +import core.mw as mw +import thisdb + +from .site import blueprint + +# 获取网站分类 +@blueprint.route('/get_site_types', endpoint='get_site_types',methods=['POST']) +@panel_login_required +def get_site_types(): + data = thisdb.getSiteTypesList() + data.insert(0, {"id": 0, "name": "默认分类"}) + return mw.returnData(True, "ok", data) + + +# 添加网站分类 +@blueprint.route('/add_site_type', endpoint='add_site_type',methods=['POST']) +@panel_login_required +def add_site_type(): + name = request.form.get('name', '').strip() + return MwSites.instance().addSiteTypes(name) + + +# 添加网站分类 +@blueprint.route('/remove_site_type', endpoint='remove_site_type',methods=['POST']) +@panel_login_required +def remove_site_type(): + site_type_id = request.form.get('id', '') + if mw.M('site_types').where('id=?', (site_type_id,)).count() == 0: + return mw.returnData(False, "指定分类不存在!") + mw.M('site_types').where('id=?', (site_type_id,)).delete() + mw.M("sites").where("type_id=?", (site_type_id,)).save("type_id", (0,)) + return mw.returnData(True, "分类已删除!") + +# 修改网站分类 +@blueprint.route('/modify_site_type_name', endpoint='modify_site_type_name',methods=['POST']) +@panel_login_required +def modify_site_type_name(): + name = request.form.get('name', '').strip() + site_type_id = request.form.get('id', '') + if not name: + return mw.returnData(False, "分类名称不能为空") + if len(name) > 18: + return mw.returnData(False, "分类名称长度不能超过6个汉字或18位字母") + if mw.M('site_types').where('id=?', (site_type_id,)).count() == 0: + return mw.returnData(False, "指定分类不存在!") + mw.M('site_types').where('id=?', (site_type_id,)).setField('name', name) + return mw.returnData(True, "修改成功!") + +# 设置网站分类 +@blueprint.route('/set_site_type', endpoint='set_site_type',methods=['POST']) +@panel_login_required +def set_site_type(): + # 设置指定站点的分类 + site_ids = request.form.get('site_ids', '') + site_type_id = request.form.get('id', '') + site_ids = json.loads(site_ids) + for site_id in site_ids: + mw.M('sites').where('id=?', (site_id,)).setField('type_id', site_type_id) + return mw.returnData(True, "设置成功!") + + + + + diff --git a/web/admin/site/ssl.py b/web/admin/site/ssl.py new file mode 100644 index 000000000..17e3895c0 --- /dev/null +++ b/web/admin/site/ssl.py @@ -0,0 +1,120 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites +import core.mw as mw +import thisdb + +from .site import blueprint + + +# 获取证书信息 +@blueprint.route('/get_ssl', endpoint='get_ssl', methods=['POST']) +@panel_login_required +def get_ssl(): + site_name = request.form.get('site_name', '') + ssl_type = request.form.get('ssl_type', '') + return MwSites.instance().getSsl(site_name, ssl_type) + +# 获取证书信息 +@blueprint.route('/set_ssl', endpoint='set_ssl', methods=['POST']) +@panel_login_required +def set_ssl(): + site_name = request.form.get('siteName', '') + key = request.form.get('key', '') + csr = request.form.get('csr', '') + return MwSites.instance().setSsl(site_name, key, csr) + + +# 删除证书 +@blueprint.route('/close_ssl_conf', endpoint='close_ssl_conf', methods=['POST']) +@panel_login_required +def close_ssl_conf(): + site_name = request.form.get('siteName', '') + ssl_type = request.form.get('updateOf', '') + return MwSites.instance().closeSslConf(site_name) + + +# 删除证书 +@blueprint.route('/delete_ssl', endpoint='delete_ssl', methods=['POST']) +@panel_login_required +def delete_ssl(): + site_name = request.form.get('site_name', '') + ssl_type = request.form.get('ssl_type', '') + return MwSites.instance().deleteSsl(site_name, ssl_type) + + +# 获取证书列表 +@blueprint.route('/get_cert_list', endpoint='get_cert_list', methods=['GET','POST']) +@panel_login_required +def get_cert_list(): + return MwSites.instance().getCertList() + + +# 获取DNSAPI +@blueprint.route('/get_dnsapi', endpoint='get_dnsapi', methods=['GET','POST']) +@panel_login_required +def get_dnsapi(): + return MwSites.instance().getDnsapi() + +# 设置DNSAPI +@blueprint.route('/set_dnsapi', endpoint='set_dnsapi', methods=['GET','POST']) +@panel_login_required +def set_dnsapi(): + type = request.form.get('type', '') + data = request.form.get('data') + return MwSites.instance().setDnsapi(type,data) + +# 设置证书到站点 +@blueprint.route('/set_cert_to_site', endpoint='set_cert_to_site', methods=['GET','POST']) +@panel_login_required +def set_cert_to_site(): + site_name = request.form.get('siteName', '') + cert_name = request.form.get('certName', '') + return MwSites.instance().setCertToSite(site_name, cert_name) + +# 删除证书 +@blueprint.route('/remove_cert', endpoint='remove_cert', methods=['GET','POST']) +@panel_login_required +def remove_cert(): + cert_name = request.form.get('certName', '') + return MwSites.instance().removeCert(cert_name) + +# 强制开启HTTPS +@blueprint.route('/http_to_https', endpoint='http_to_https', methods=['GET','POST']) +@panel_login_required +def http_to_https(): + site_name = request.form.get('siteName', '') + return MwSites.instance().httpToHttps(site_name) + +# 强制关闭HTTPS +@blueprint.route('/close_to_https', endpoint='close_to_https', methods=['GET','POST']) +@panel_login_required +def close_to_https(): + site_name = request.form.get('siteName', '') + return MwSites.instance().closeToHttps(site_name) + + + + + + + + + diff --git a/web/admin/site/ssl_acme.py b/web/admin/site/ssl_acme.py new file mode 100644 index 000000000..b665d06db --- /dev/null +++ b/web/admin/site/ssl_acme.py @@ -0,0 +1,59 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json +import re + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites +import core.mw as mw +import thisdb + +from .site import blueprint + + +# 获取ACME日志 +@blueprint.route('/get_acme_logs', endpoint='get_acme_logs', methods=['POST']) +@panel_login_required +def get_acme_logs(): + log_file = MwSites.instance().acmeLogFile() + if not os.path.exists(log_file): + mw.execShell('touch ' + log_file) + return mw.returnData(True, 'OK', log_file) + + +@blueprint.route('/create_acme', endpoint='create_acme', methods=['POST']) +@panel_login_required +def create_acme(): + site_name = request.form.get('siteName', '') + domains = request.form.get('domains', '') + force = request.form.get('force', '') + renew = request.form.get('renew', '') + email = request.form.get('email', '') + wildcard_domain = request.form.get('wildcard_domain','') + apply_type = request.form.get('apply_type', 'file') + apply_ca = request.form.get('apply_ca', 'default') + + dnspai = request.form.get('dnspai','') + dns_alias = request.form.get('dns_alias','') + return MwSites.instance().createAcme(site_name, domains, force, renew, apply_type, apply_ca, dnspai, email, wildcard_domain,dns_alias) + + + + + + + diff --git a/web/admin/site/ssl_let.py b/web/admin/site/ssl_let.py new file mode 100644 index 000000000..dda93e6fa --- /dev/null +++ b/web/admin/site/ssl_let.py @@ -0,0 +1,55 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +from utils.plugin import plugin as MwPlugin +from utils.site import sites as MwSites +import core.mw as mw +import thisdb + +from .site import blueprint + + +# 获取ACME日志 +@blueprint.route('/get_let_logs', endpoint='get_let_logs', methods=['POST']) +@panel_login_required +def get_let_logs(): + log_file = MwSites.instance().letLogFile() + if not os.path.exists(log_file): + mw.execShell('touch ' + log_file) + return mw.returnData(True, 'OK', log_file) + + +@blueprint.route('/create_let', endpoint='create_let', methods=['POST']) +@panel_login_required +def create_let(): + site_name = request.form.get('siteName', '') + domains = request.form.get('domains', '') + force = request.form.get('force', '') + renew = request.form.get('renew', '') + email = request.form.get('email', '') + wildcard_domain = request.form.get('wildcard_domain','') + apply_type = request.form.get('apply_type', 'file') + dnspai = request.form.get('dnspai','') + return MwSites.instance().createLet(site_name, domains, force, renew, apply_type, dnspai, email, wildcard_domain) + + + + + + + diff --git a/web/admin/soft/__init__.py b/web/admin/soft/__init__.py new file mode 100644 index 000000000..21d7bb903 --- /dev/null +++ b/web/admin/soft/__init__.py @@ -0,0 +1,23 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +from flask import Blueprint, render_template + +from admin.user_login_check import panel_login_required + +import thisdb + +blueprint = Blueprint('soft', __name__, url_prefix='/soft', template_folder='../../templates') +@blueprint.route('/index', endpoint='index') +@panel_login_required +def index(): + name = thisdb.getOption('template', default='default') + return render_template('%s/soft.html' % name) \ No newline at end of file diff --git a/web/admin/submodules.py b/web/admin/submodules.py new file mode 100644 index 000000000..62b7277d8 --- /dev/null +++ b/web/admin/submodules.py @@ -0,0 +1,39 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +from .dashboard import blueprint as DashboardModule +from .site import blueprint as SiteModule +from .task import blueprint as TaskModule +from .setting import blueprint as SettingModule +from .logs import blueprint as LogsModule +from .files import blueprint as FilesModule +from .soft import blueprint as SoftModule +from .plugins import blueprint as PluginsModule +from .crontab import blueprint as CrontabModule +from .firewall import blueprint as FirewallModule +from .monitor import blueprint as MonitorModule +from .system import blueprint as SystemModule + +def get_submodules(): + return [ + DashboardModule, + SiteModule, + TaskModule, + LogsModule, + FilesModule, + SoftModule, + PluginsModule, + CrontabModule, + FirewallModule, + MonitorModule, + SystemModule, + SettingModule, + ] diff --git a/web/admin/system/__init__.py b/web/admin/system/__init__.py new file mode 100644 index 000000000..47d29fb71 --- /dev/null +++ b/web/admin/system/__init__.py @@ -0,0 +1,13 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +from .system import * +from .upgrade import * + diff --git a/web/admin/system/system.py b/web/admin/system/system.py new file mode 100644 index 000000000..5d63d4783 --- /dev/null +++ b/web/admin/system/system.py @@ -0,0 +1,179 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required +from utils.system import monitor + +import core.mw as mw +import utils.system as sys +import thisdb + +blueprint = Blueprint('system', __name__, url_prefix='/system', template_folder='../../templates') + +# 获取系统的统计信息 +@blueprint.route('/system_total', endpoint='system_total', methods=['GET','POST']) +@panel_login_required +def system_total(): + data = sys.getMemInfo() + cpu = sys.getCpuInfo(interval=1) + data['cpuNum'] = cpu[1] + data['cpuRealUsed'] = cpu[0] + data['time'] = sys.getBootTime() + data['system'] = sys.getSystemVersion() + data['version'] = '0.0.1' + return data + +# 获取环境信息 +@blueprint.route('/get_env_info', endpoint='get_env_info', methods=['GET','POST']) +@panel_login_required +def get_env_info(): + return sys.getEnvInfo() + +# 获取系统的网络流量信息 +@blueprint.route('/network', endpoint='network') +@panel_login_required +def network(): + stat = {} + stat['cpu'] = sys.getCpuInfo() + stat['load'] = sys.getLoadAverage() + stat['mem'] = sys.getMemInfo() + stat['iostat'] = sys.stats().disk() + stat['network'] = sys.stats().network() + return stat + +# 获取系统的磁盘信息 +@blueprint.route('/disk_info', endpoint='disk_info', methods=['GET','POST']) +@panel_login_required +def disk_info(): + data = sys.getDiskInfo() + return mw.returnData(True, 'ok', data) + +# 获取系统的负载统计信息 +@blueprint.route('/get_load_average', endpoint='get_load_average', methods=['GET']) +@panel_login_required +def get_load_average(): + start = request.args.get('start', '') + end = request.args.get('end', '') + data = sys.getLoadAverageByDB(start, end) + return mw.returnData(True, 'ok', data) + +# 获取系统的磁盘IO统计信息 +@blueprint.route('/get_disk_io', endpoint='get_disk_io', methods=['GET']) +@panel_login_required +def get_disk_io(): + start = request.args.get('start', '') + end = request.args.get('end', '') + data = sys.getDiskIoByDB(start, end) + return mw.returnData(True, 'ok', data) + +# 获取系统的CPU/IO统计信息 +@blueprint.route('/get_cpu_io', endpoint='get_cpu_io', methods=['GET']) +@panel_login_required +def get_cpu_io(): + start = request.args.get('start', '') + end = request.args.get('end', '') + data = sys.getCpuIoByDB(start, end) + return mw.returnData(True, 'ok', data) + +# 获取系统网络IO统计信息 +@blueprint.route('/get_network_io', endpoint='get_network_io', methods=['GET']) +@panel_login_required +def get_network_io(): + start = request.args.get('start', '') + end = request.args.get('end', '') + data = sys.getNetworkIoByDB(start, end) + return mw.returnData(True, 'ok', data) + +# 重启面板 +@blueprint.route('/restart', endpoint='restart', methods=['POST']) +@panel_login_required +def restart(): + mw.restartMw() + return mw.returnData(True, '面板已重启!') + +# 重启面板 +@blueprint.route('/restart_server', endpoint='restart_server', methods=['POST']) +@panel_login_required +def restart_server(): + if mw.isAppleSystem(): + return mw.returnData(False, "开发环境不可重起!") + sys.restartServer() + return mw.returnData(True, '正在重启服务器!') + +# 关机服务器 +@blueprint.route('/shutdown_server', endpoint='shutdown_server', methods=['POST']) +@panel_login_required +def shutdown_server(): + if mw.isAppleSystem(): + return mw.returnData(False, "开发环境不可关机!") + sys.shutdownServer() + return mw.returnData(True, '正在关闭服务器!') + +# 设置 +@blueprint.route('/set_control', endpoint='set_control', methods=['POST']) +@panel_login_required +def set_control(): + stype = request.form.get('type', '') + day = request.form.get('day', '') + + + + if stype == '0': + _day = int(day) + if _day < 1: + return mw.returnData(False, "保存天数异常!") + thisdb.setOption('monitor_day', day, type='monitor') + thisdb.setOption('monitor_status', 'close', type='monitor') + return mw.returnData(True, "关闭监控成功!") + elif stype == '1': + _day = int(day) + if _day < 1: + return mw.returnData(False, "保存天数异常!") + + thisdb.setOption('monitor_day', day, type='monitor') + thisdb.setOption('monitor_status', 'open', type='monitor') + return mw.returnData(True, "开启监控成功!") + elif stype == '2': + thisdb.setOption('monitor_only_netio', 'close', type='monitor') + return mw.returnData(True, "关闭仅统计外网成功!") + elif stype == '3': + thisdb.setOption('monitor_only_netio', 'open', type='monitor') + return mw.returnData(True, "开启仅统计外网成功!") + elif stype == 'del': + if not mw.isRestart(): + return mw.returnData(False, '请等待所有安装任务完成再执行') + monitor.instance().clearDbFile() + return mw.returnData(True, "清空监控记录成功!") + else: + monitor_status = thisdb.getOption('monitor_status', default='open', type='monitor') + monitor_day = thisdb.getOption('monitor_day', default='30', type='monitor') + monitor_only_netio = thisdb.getOption('monitor_only_netio', default='open', type='monitor') + data = {} + data['day'] = monitor_day + if monitor_status == 'open': + data['status'] = True + else: + data['status'] = False + if monitor_only_netio == 'open': + data['stat_all_status'] = True + else: + data['stat_all_status'] = False + + return data + + return mw.returnData(False, "异常!") + + + diff --git a/web/admin/system/upgrade.py b/web/admin/system/upgrade.py new file mode 100644 index 000000000..9418027f7 --- /dev/null +++ b/web/admin/system/upgrade.py @@ -0,0 +1,31 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.system as sys + +from .system import blueprint + +# 升级检测 +@blueprint.route('/update_server', endpoint='update_server') +@panel_login_required +def update_server(): + panel_type = request.args.get('type', 'check') + version = request.args.get('version', '') + return sys.updateServer(panel_type, version) + + + diff --git a/web/admin/task/__init__.py b/web/admin/task/__init__.py new file mode 100644 index 000000000..476a042b9 --- /dev/null +++ b/web/admin/task/__init__.py @@ -0,0 +1,92 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import json +import time + +from flask import Blueprint, render_template +from flask import request + +from admin.user_login_check import panel_login_required + +import core.mw as mw +import utils.task as MwTasks +import thisdb + +blueprint = Blueprint('task', __name__, url_prefix='/task', template_folder='../../templates/default') + + +@blueprint.route('/count', endpoint='task_count',methods=['GET','POST']) +@panel_login_required +def task_count(): + return mw.returnData(True, 'ok',thisdb.getTaskUnexecutedCount()) + +@blueprint.route('/list', endpoint='list', methods=['POST']) +@panel_login_required +def list(): + p = request.form.get('p', '1') + limit = request.form.get('limit', '10').strip() + search = request.form.get('search', '').strip() + return MwTasks.getTaskPage(int(p), int(limit)) + +@blueprint.route('/get_exec_log', endpoint='get_exec_log', methods=['POST']) +@panel_login_required +def get_exec_log(): + file = mw.getPanelTaskExecLog() + return mw.getLastLine(file, 100) + + +@blueprint.route('/get_task_speed', endpoint='get_task_speed', methods=['POST']) +@panel_login_required +def get_task_speed(): + count = thisdb.getTaskUnexecutedCount() + if count == 0: + return mw.returnData(False, '当前没有任务队列在执行-2!') + + row = thisdb.getTaskFirstByRun() + if row is None: + return mw.returnData(False, '当前没有任务队列在执行-3!') + + task_logfile = mw.getPanelTaskExecLog() + + data = {} + data['name'] = row['name'] + data['cmd'] = row['cmd'] + + if row['type'] == 'download': + readLine = '' + for i in range(3): + try: + readLine = mw.readFile(task_logfile) + data['msg'] = json.loads(readLine) + data['isDownload'] = True + except Exception as e: + if i == 2: + thisdb.setTaskStatus(row['id'],0) + return mw.returnData(False, '当前没有任务队列在执行-4:' + str(e)) + time.sleep(0.5) + else: + data['msg'] = mw.getLastLine(task_logfile, 10) + data['isDownload'] = False + + data['count'] = count + data['task'] = thisdb.getTaskRunList(1,6) + return data + +@blueprint.route('/remove_task', endpoint='remove_task', methods=['POST']) +@panel_login_required +def remove_task(): + task_id = request.form.get('id', '') + if task_id == '': + return mw.returnData(False, '任务ID不能为空!') + return MwTasks.removeTask(task_id) + + + \ No newline at end of file diff --git a/web/admin/user_login_check.py b/web/admin/user_login_check.py new file mode 100644 index 000000000..7d7bc2e44 --- /dev/null +++ b/web/admin/user_login_check.py @@ -0,0 +1,46 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +from flask import render_template +from flask import Response +from flask import request + +from functools import wraps + +from admin import session +from admin.common import isLogined + +import thisdb + +def panel_login_required(func): + + @wraps(func) + def wrapper(*args, **kwargs): + # 面板API调用检查 + app_id = request.headers.get('App-Id','') + app_secret = request.headers.get('App-Secret','') + if app_id != '' and app_secret != '': + panel_api = thisdb.getOptionByJson('panel_api', default={"open":True}) + if panel_api['open']: + return_code = 404 + info = thisdb.getAppByAppId(app_id) + if app_secret != info['app_secret']: + return Response(status=int(return_code)) + return func(*args, **kwargs) + + if not isLogined(): + unauthorized_status = thisdb.getOption('unauthorized_status') + if unauthorized_status == '0': + return render_template('default/path.html') + return Response(status=int(unauthorized_status)) + + return func(*args, **kwargs) + return wrapper \ No newline at end of file diff --git a/web/app.py b/web/app.py new file mode 100644 index 000000000..cb1bac80b --- /dev/null +++ b/web/app.py @@ -0,0 +1,33 @@ +# coding=utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + + +import sys +import os + +from admin import app, socketio +import config + + +if sys.version_info < (3, 6): + raise RuntimeError('This application must be run under Python 3.6 or later.') + +# 我们需要在sys.path中包含根目录,以确保我们可以找到在独立运行时运行时所需的一切。 +if sys.path[0] != os.path.dirname(os.path.realpath(__file__)): + sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) + + +def main(): + socketio.run(app,debug=config.DEBUG) + # app.run(debug=True) + +if __name__ == '__main__': + main() diff --git a/web/branding.py b/web/branding.py new file mode 100644 index 000000000..03d693195 --- /dev/null +++ b/web/branding.py @@ -0,0 +1,21 @@ +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 品牌信息 +# --------------------------------------------------------------------------------- + +# UI中显示的应用程序的名称 +APP_NAME = 'PowerLinux 3' +APP_ICON = 'icon' + +APP_LOG_NAME = 'panel' +APP_SQLITE_NAME = 'panel' + +# Copyright string for display in the app +APP_COPYRIGHT = 'Copyright (C) 2018-∞ All rights reserved.' diff --git a/web/config.py b/web/config.py new file mode 100644 index 000000000..0c3fb2e70 --- /dev/null +++ b/web/config.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 配置信息 +# --------------------------------------------------------------------------------- + +import builtins +import logging +import os +import sys + +import core.mw as mw + +from branding import APP_NAME, APP_ICON, APP_COPYRIGHT, APP_LOG_NAME, APP_SQLITE_NAME +from version import APP_VERSION, APP_RELEASE, APP_REVISION, APP_SUFFIX + +DEBUG = False + +# 配置数据库连接池大小。将其设置为0将删除任何限制 +CONFIG_DATABASE_CONNECTION_POOL_SIZE = 20 +# 允许溢出超过连接池大小的连接数 +CONFIG_DATABASE_CONNECTION_MAX_OVERFLOW = 100 + +# 应用程序日志级别-其中之一: +# CRITICAL 50 +# ERROR 40 +# WARNING 30 +# SQL 25 +# INFO 20 +# DEBUG 10 +# NOTSET 0 +CONSOLE_LOG_LEVEL = logging.WARNING +FILE_LOG_LEVEL = logging.WARNING + +# Number of values to trust for X-Forwarded-For +PROXY_X_FOR_COUNT = 1 +# Number of values to trust for X-Forwarded-Proto. +PROXY_X_PROTO_COUNT = 1 +# Number of values to trust for X-Forwarded-Host. +PROXY_X_HOST_COUNT = 0 +# Number of values to trust for X-Forwarded-Port. +PROXY_X_PORT_COUNT = 1 +# Number of values to trust for X-Forwarded-Prefix. +PROXY_X_PREFIX_COUNT = 0 + + +DATA_DIR = mw.getPanelDataDir() + +# 日志文件名。这将进入数据目录,服务器模式下的非Windows平台除外。 +LOG_FILE = os.path.join(mw.getMWLogs(), APP_LOG_NAME + '.log') + +CONSOLE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s' +FILE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s' + +# 日志旋转设置日志文件将根据LOG_ROTATION_SIZE和LOG_ROTATION_AGE的值进行切换。 +# 旋转的文件将以格式命名Y-m-d_H-M-S +LOG_ROTATION_SIZE = 1 # MBs +LOG_ROTATION_AGE = 1440 # minutes +LOG_ROTATION_MAX_LOG_FILES = 5 # 要保留的最大备份数 + +# 用于存储用户帐户和设置的SQLite数据库的默认路径。 +# 此默认设置将文件放置在与此相同的目录中 配置文件,但会生成一个在整个应用程序中使用的绝对路径。 +SQLITE_PATH = os.path.join(DATA_DIR, APP_SQLITE_NAME + '.db') + + +DEFAULT_SERVER = '0.0.0.0' +DEFAULT_SERVER_PORT = 7201 + diff --git a/web/core/db.py b/web/core/db.py new file mode 100755 index 000000000..e59809acd --- /dev/null +++ b/web/core/db.py @@ -0,0 +1,445 @@ +# coding: utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# sqlite3操作 +# --------------------------------------------------------------------------------- + +import os +import sys +import sqlite3 + +import core.mw as mw + +def getPanelDir(): + return os.path.dirname(os.getcwd()) + +def getTracebackInfo(): + import traceback + return traceback.format_exc() + +class Sql(): + #------------------------------ + # 数据库操作类 For sqlite3 + #------------------------------ + __DB_FILE = None # 数据库文件 + __DB_CONN = None # 数据库连接对象 + __DB_TABLE = "" # 被操作的表名称 + __OPT_WHERE = "" # where条件 + __OPT_LIMIT = "" # limit条件 + __OPT_GROUP = "" # group条件 + __OPT_ORDER = "" # order条件 + __OPT_FIELD = "*" # field条件 + __OPT_PARAM = () # where值 + + __debug = False + + def __init__(self): + self.__DB_FILE = getPanelDir()+'/data/panel.db' + + def __getConn(self): + # 取数据库对象 + try: + if self.__DB_CONN == None: + self.__DB_CONN = sqlite3.connect(self.__DB_FILE) + self.__DB_CONN.text_factory = str + except Exception as ex: + print(getTracebackInfo()) + return "error: " + str(ex) + + def changeTextFactoryToBytes(self): + self.__DB_CONN.text_factory = bytes + return True + + def debug(self, debug=False): + self.__debug = debug + return self + + def autoTextFactory(self): + if sys.version_info[0] == 3: + self.__DB_CONN.text_factory = lambda x: str(x, encoding="utf-8", errors='ignore') + else: + self.__DB_CONN.text_factory = lambda x: unicode(x, "utf-8", "ignore") + + def dbfile(self, name): + self.__DB_FILE = getPanelDir()+'/data/' + name + '.db' + return self + + def dbPos(self, path, name, suffix_name = 'db'): + self.__DB_FILE = path + '/' + name + '.' + suffix_name + return self + + def table(self, table): + # 设置表名 + self.__DB_TABLE = table + return self + + def where(self, where, param=()): + # WHERE条件 + if where: + self.__OPT_WHERE = " WHERE " + where + self.__OPT_PARAM = param + return self + + def andWhere(self, where, param): + # WHERE条件 + if where: + self.__OPT_WHERE = self.__OPT_WHERE + " and " + where + # print(param) + # print(self.__OPT_PARAM) + self.__OPT_PARAM = self.__OPT_PARAM + param + return self + + def order(self, order): + # ORDER条件 + if len(order): + self.__OPT_ORDER = " ORDER BY " + order + else: + self.__OPT_ORDER = "" + return self + + def group(self, group): + if len(group): + self.__OPT_GROUP = " GROUP BY " + group + else: + self.__OPT_GROUP = "" + return self + + def limit(self, limit): + # LIMIT条件 + if len(limit): + self.__OPT_LIMIT = " LIMIT " + limit + else: + self.__OPT_LIMIT = "" + return self + + def field(self, field): + # FIELD条件 + if len(field): + self.__OPT_FIELD = field + return self + + def getDbField(self,name): + sql = "PRAGMA table_info(%s)" % name + result = self.__DB_CONN.execute(sql) + data = result.fetchall() + + fields = [] + for i in data: + fields.append(i[1]) + return fields + + def getDbFieldString(self,name): + fields = self.getDbField(name) + return ','.join(fields) + + + def select(self): + # 查询数据集 + self.__getConn() + try: + sql = "SELECT " + self.__OPT_FIELD + " FROM " + self.__DB_TABLE + \ + self.__OPT_WHERE + self.__OPT_GROUP + self.__OPT_ORDER + self.__OPT_LIMIT + + if self.__debug: + print(sql) + print(self.__OPT_PARAM) + + result = self.__DB_CONN.execute(sql, self.__OPT_PARAM) + data = result.fetchall() + if len(data) == 0: + return data + + # 构造字曲系列 + if self.__OPT_FIELD != "*": + field = self.__OPT_FIELD.split(',') + tmp = [] + for row in data: + i = 0 + t = {} + for key in field: + t[key] = row[i] + i += 1 + tmp.append(t) + del(t) + data = tmp + del(tmp) + else: + field = self.getDbField(self.__DB_TABLE) + tmp = [] + for row in data: + i = 0 + t = {} + for key in field: + t[key] = row[i] + i += 1 + tmp.append(t) + del(t) + data = tmp + del(tmp) + # 将元组转换成列表 + # tmp = map(list, data) + # data = tmp + # del(tmp) + self.__close() + return data + except Exception as ex: + # return "error: " + str(ex) + return [] + + def inquiry(self, input_field=''): + # 查询数据集 + # 不清空查询参数 + self.__getConn() + try: + sql = "SELECT " + self.__OPT_FIELD + " FROM " + self.__DB_TABLE + \ + self.__OPT_WHERE + self.__OPT_GROUP + self.__OPT_ORDER + self.__OPT_LIMIT + # if mw.isDebugMode(): + # print(sql, self.__OPT_PARAM) + result = self.__DB_CONN.execute(sql, self.__OPT_PARAM) + data = result.fetchall() + # 构造字曲系列 + if self.__OPT_FIELD != "*": + + if input_field != "": + field = input_field.split(',') + else: + field = self.__OPT_FIELD.split(',') + + tmp = [] + for row in data: + i = 0 + tmp1 = {} + for key in field: + tmp1[key] = row[i] + i += 1 + tmp.append(tmp1) + del(tmp1) + data = tmp + del(tmp) + else: + # 将元组转换成列表 + tmp = map(list, data) + data = tmp + del(tmp) + return data + except Exception as ex: + return "error: " + str(ex) + + def getField(self, keyName): + # 取回指定字段 + result = self.field(keyName).select() + if len(result) == 1: + return result[0][keyName] + return None + + def setField(self, keyName, keyValue): + # 更新指定字段 + return self.save(keyName, (keyValue,)) + + def find(self): + # 取一行数据 + result = self.limit("1").select() + if len(result) == 1: + return result[0] + return None + + def count(self): + # 取行数 + key = "COUNT(*)" + data = self.field(key).select() + try: + return int(data[0][key]) + except: + return 0 + + def add(self, keys, param): + # 插入数据 + self.__getConn() + try: + values = "" + for key in keys.split(','): + values += "?," + values = self.checkInput(values[0:len(values) - 1]) + sql = "INSERT INTO " + self.__DB_TABLE + \ + "(" + keys + ") " + "VALUES(" + values + ")" + result = self.__DB_CONN.execute(sql, param) + last_id = result.lastrowid + self.__close() + self.__DB_CONN.commit() + return last_id + except Exception as ex: + print(str(ex)) + return 0 + + # 插入数据 + def insert(self, pdata): + if not pdata: + return False + keys, param = self.__format_pdata(pdata) + return self.add(keys, param) + + # 更新数据 + def update(self, pdata): + if not pdata: + return False + keys, param = self.__format_pdata(pdata) + return self.save(keys, param) + + # 构造数据 + def __format_pdata(self, pdata): + keys = pdata.keys() + keys_str = ','.join(keys) + param = [] + for k in keys: + param.append(pdata[k]) + return keys_str, tuple(param) + + def checkInput(self, data): + if not data: + return data + if type(data) != str: + return data + checkList = [ + {'d': '<', 'r': '<'}, + {'d': '>', 'r': '>'}, + {'d': '\'', 'r': '‘'}, + {'d': '"', 'r': '“'}, + {'d': '&', 'r': '&'}, + {'d': '#', 'r': '#'}, + {'d': '<', 'r': '<'} + ] + for v in checkList: + data = data.replace(v['d'], v['r']) + return data + + def addAll(self, keys, param): + # 插入数据 + self.__getConn() + try: + values = "" + for key in keys.split(','): + values += "?," + values = values[0:len(values) - 1] + sql = "INSERT INTO " + self.__DB_TABLE + \ + "(" + keys + ") " + "VALUES(" + values + ")" + result = self.__DB_CONN.execute(sql, param) + return True + except Exception as ex: + return "error: " + str(ex) + + def commit(self): + self.__close() + self.__DB_CONN.commit() + + def save(self, keys, param): + # 更新数据 + self.__getConn() + try: + opt = "" + for key in keys.split(','): + opt += key + "=?," + opt = opt[0:len(opt) - 1] + sql = "UPDATE " + self.__DB_TABLE + " SET " + opt + self.__OPT_WHERE + + if self.__debug: + print(sql, param) + + # 处理拼接WHERE与UPDATE参数 + tmp = list(param) + for arg in self.__OPT_PARAM: + tmp.append(arg) + self.__OPT_PARAM = tuple(tmp) + result = self.__DB_CONN.execute(sql, self.__OPT_PARAM) + self.__close() + self.__DB_CONN.commit() + return result.rowcount + except Exception as ex: + return "error: " + str(ex) + + def delete(self, id=None): + # 删除数据 + self.__getConn() + try: + if id: + self.__OPT_WHERE = " WHERE id=?" + self.__OPT_PARAM = (id,) + sql = "DELETE FROM " + self.__DB_TABLE + self.__OPT_WHERE + result = self.__DB_CONN.execute(sql, self.__OPT_PARAM) + self.__close() + self.__DB_CONN.commit() + return result.rowcount + except Exception as ex: + return "error: " + str(ex) + + def originExecute(self, sql, param=()): + self.__getConn() + try: + result = self.__DB_CONN.execute(sql, param) + self.__DB_CONN.commit() + return result + except Exception as ex: + return "error: " + str(ex) + + def execute(self, sql, param=()): + # 执行SQL语句返回受影响行 + self.__getConn() + # print sql, param + try: + result = self.__DB_CONN.execute(sql, param) + self.__DB_CONN.commit() + return result.rowcount + except Exception as ex: + return "error: " + str(ex) + + def query(self, sql, param=()): + # 执行SQL语句返回数据集 + self.__getConn() + try: + result = self.__DB_CONN.execute(sql, param) + # 将元组转换成列表 + # data = map(list, result) + return result + except Exception as ex: + return "error: " + str(ex) + + def create(self, name): + # 创建数据表 + self.__getConn() + import mw + script = mw.readFile('data/' + name + '.sql') + result = self.__DB_CONN.executescript(script) + self.__DB_CONN.commit() + return result.rowcount + + def fofile(self, filename): + # 执行脚本 + self.__getConn() + import mw + script = mw.readFile(filename) + result = self.__DB_CONN.executescript(script) + self.__DB_CONN.commit() + return result.rowcount + + def __close(self): + # 清理条件属性 + self.__OPT_WHERE = "" + self.__OPT_FIELD = "*" + self.__OPT_ORDER = "" + self.__OPT_LIMIT = "" + self.__OPT_PARAM = () + + def close(self): + # 释放资源 + try: + self.__DB_CONN.close() + self.__DB_CONN = None + except: + pass diff --git a/web/core/mw.py b/web/core/mw.py new file mode 100644 index 000000000..cf19bc668 --- /dev/null +++ b/web/core/mw.py @@ -0,0 +1,2069 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 核心方法库 +# --------------------------------------------------------------------------------- + + +import os +import sys +import time +import string +import json +import hashlib +import shlex +import datetime +import subprocess +import glob +import base64 +import re + +from random import Random + +def execShell(cmdstring, cwd=None, timeout=None, shell=True): + + if shell: + cmdstring_list = cmdstring + else: + cmdstring_list = shlex.split(cmdstring) + if timeout: + end_time = datetime.datetime.now() + datetime.timedelta(seconds=timeout) + + sub = subprocess.Popen(cmdstring_list, cwd=cwd, stdin=subprocess.PIPE, + shell=shell, bufsize=4096, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + while sub.poll() is None: + time.sleep(0.1) + if timeout: + if end_time <= datetime.datetime.now(): + raise Exception("Timeout:%s" % cmdstring) + + if sys.version_info[0] == 2: + return sub.communicate() + + data = sub.communicate() + + success = data[0] + error = data[1] + # python3 fix 返回byte数据 + if isinstance(success, bytes): + # success = str(success, encoding='utf-8') + try: + success = success.decode('utf-8') + except Exception as e: + success = str(e) + + if isinstance(error, bytes): + # error = str(error, encoding='utf-8') + try: + error = error.decode('utf-8') + except Exception as e: + error = str(e) + return (success, error) + +def checkBinExist(name): + d = execShell('which ' + name) + if d[0] != '': + return True + return False + + + +def getTracebackInfo(): + import traceback + return traceback.format_exc() + +def getRunDir(): + return os.getcwd() + +def getRootDir(): + return os.path.dirname(getRunDir()) + +def getPanelDir(): + return getRootDir() + +def getFatherDir(): + return os.path.dirname(os.path.dirname(getPanelDir())) + +def getPluginDir(): + return getPanelDir() + '/plugins' + +def getPanelDataDir(): + return getPanelDir() + '/data' + +def getMWLogs(): + return getPanelDir() + '/logs' + +def getPanelLogs(): + return getPanelDir() + '/logs' + +def getPanelTmp(): + return getPanelDir() + '/tmp' + +def getServerDir(): + return getFatherDir() + '/server' + +def getLogsDir(): + return getFatherDir() + '/wwwlogs' + +def getRecycleBinDir(): + rb_dir = getFatherDir() + '/recycle_bin' + if not os.path.exists(rb_dir): + os.system('mkdir -p ' + rb_dir) + return rb_dir + +def getPanelTaskLog(): + return getMWLogs() + '/panel_task.log' + +def getPanelTaskExecLog(): + return getMWLogs() + '/panel_exec.log' + +def getWwwDir(): + import thisdb + site_path = thisdb.getOption('site_path', default=getFatherDir()+'/wwwroot') + return site_path + +def getBackupDir(): + import thisdb + backup_path = thisdb.getOption('backup_path', default=getFatherDir()+'/backup') + return backup_path + +def setBackupDir(bdir): + import thisdb + thisdb.setOption('backup_path', bdir) + return True + +def getPanelPort(): + port_file = getPanelDir()+'/data/port.pl' + port = readFile(port_file).strip() + if not port: + return 7200 + return int(port) + +def getRandomString(length): + # 取随机字符串 + rnd_str = '' + chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789' + chrlen = len(chars) - 1 + random = Random() + for i in range(length): + rnd_str += chars[random.randint(0, chrlen)] + return rnd_str + + +def getUniqueId(): + """ + 根据时间生成唯一ID + :return: + """ + current_time = datetime.datetime.now() + str_time = current_time.strftime('%Y%m%d%H%M%S%f')[:-3] + unique_id = "{0}".format(str_time) + return unique_id + +def getDate(): + # 取格式时间 + import time + return time.strftime('%Y-%m-%d %X', time.localtime()) + + +def getDateFromNow(tf_format="%Y-%m-%d %H:%M:%S", time_zone="Asia/Shanghai"): + # 取格式时间 + import time + os.environ['TZ'] = time_zone + time.tzset() + return time.strftime(tf_format, time.localtime()) + +def getDataFromInt(val): + time_format = '%Y-%m-%d %H:%M:%S' + time_str = time.localtime(val) + return time.strftime(time_format, time_str) + +def getCommonFile(): + # 统一默认配置文件 + base_dir = getPanelDir()+'/' + data = { + 'debug' : base_dir+'data/debug.pl', # DEBUG文件 + 'close' : base_dir+'data/close.pl', # 识别关闭面板文件 + 'basic_auth' : base_dir+'data/basic_auth.json', # 面板Basic验证 + 'ipv6' : base_dir+'data/ipv6.pl', # ipv6识别文件 + 'bind_domain' : base_dir+'data/bind_domain.pl', # 面板域名绑定 + 'auth_secret': base_dir+'data/auth_secret.pl', # 二次验证密钥 + 'ssl': base_dir+'ssl/choose.pl', # ssl设置 + } + return data + +def checkCert(certPath='ssl/certificate.pem'): + # 验证证书 + openssl = '/usr/bin/openssl' + if not os.path.exists(openssl): + openssl = '/usr/local/openssl/bin/openssl' + if not os.path.exists(openssl): + openssl = 'openssl' + certPem = readFile(certPath) + s = "\n-----BEGIN CERTIFICATE-----" + tmp = certPem.strip().split(s) + for tmp1 in tmp: + if tmp1.find('-----BEGIN CERTIFICATE-----') == -1: + tmp1 = s + tmp1 + writeFile(certPath, tmp1) + result = execShell(openssl + " x509 -in " + + certPath + " -noout -subject") + if result[1].find('-bash:') != -1: + return True + if len(result[1]) > 2: + return False + if result[0].find('error:') != -1: + return False + return True + +def sortFileList(path, ftype = 'mtime', sort = 'desc'): + flist = os.listdir(path) + if ftype == 'mtime': + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.getmtime(os.path.join(path,f)), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.getmtime(os.path.join(path,f)), reverse=False) + + if ftype == 'size': + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.getsize(os.path.join(path,f)), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.getsize(os.path.join(path,f)), reverse=False) + + if ftype == 'fname': + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.join(path,f), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.join(path,f), reverse=False) + return flist + + +def sortAllFileList(path, ftype = 'mtime', sort = 'desc', search = '',limit = 3000): + count = 0 + flist = [] + for d_list in os.walk(path): + if count >= limit: + break + + for d in d_list[1]: + if count >= limit: + break + if d.lower().find(search) != -1: + filename = d_list[0] + '/' + d + if not os.path.exists(filename): + continue + count += 1 + flist.append(filename) + + for f in d_list[2]: + if count >= limit: + break + + if f.lower().find(search) != -1: + filename = d_list[0] + '/' + f + if not os.path.exists(filename): + continue + count += 1 + flist.append(filename) + + if ftype == 'mtime': + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.getmtime(f), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.getmtime(f), reverse=False) + + if ftype == 'size': + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.getsize(f), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.getsize(f), reverse=False) + return flist + +def getPathSize(path): + # 取文件或目录大小 + if not os.path.exists(path): + return 0 + if not os.path.isdir(path): + return os.path.getsize(path) + size_total = 0 + for nf in os.walk(path): + for f in nf[2]: + filename = nf[0] + '/' + f + size_total += os.path.getsize(filename) + return size_total + +def toSize(size, middle='') -> str: + """ + 字节单位转换 + """ + units = ('b', 'KB', 'MB', 'GB', 'TB') + s = units[0] + for u in units: + if size < 1024: + return str(round(size, 2)) + middle + u + size = float(size) / 1024.0 + s = u + return str(round(size, 2)) + middle + u + +def returnData(status, msg, data=None): + if data is None: + return {'status': status, 'msg': msg} + return {'status': status, 'msg': msg, 'data': data} + +def returnJson(status, msg, data=None): + if data is None: + return getJson({'status': status, 'msg': msg}) + return getJson({'status': status, 'msg': msg, 'data': data}) + +def readFile(filename): + # 读文件内容 + try: + fp = open(filename, 'r') + fBody = fp.read() + fp.close() + return fBody + except Exception as e: + # print('readFile:',str(e)) + return False + +def writeFile(filename, content, mode='w+'): + # 写文件内容 + try: + fp = open(filename, mode) + fp.write(content) + fp.close() + return True + except Exception as e: + writeFileLog(getTracebackInfo()) + return False + + +def backFile(file, act=None): + """ + @name 备份配置文件 + @param file 需要备份的文件 + @param act 如果存在,则备份一份作为默认配置 + """ + file_type = "_bak" + if act: + file_type = "_def" + + # print("cp -p {0} {1}".format(file, file + file_type)) + execShell("cp -p {0} {1}".format(file, file + file_type)) + + +def removeBackFile(file, act=None): + """ + @name 删除备份配置文件 + @param file 需要删除备份文件 + @param act 如果存在,则还原默认配置 + """ + file_type = "_bak" + if act: + file_type = "_def" + execShell("rm -rf {0}".format(file + file_type)) + + +def restoreFile(file, act=None): + """ + @name 还原配置文件 + @param file 需要还原的文件 + @param act 如果存在,则还原默认配置 + """ + file_type = "_bak" + if act: + file_type = "_def" + execShell("cp -p {1} {0}".format(file, file + file_type)) + +def systemdCfgDir(): + # ubuntu + cfg_dir = '/lib/systemd/system' + if os.path.exists(cfg_dir): + return cfg_dir + + # debian,centos + cfg_dir = '/usr/lib/systemd/system' + if os.path.exists(cfg_dir): + return cfg_dir + + # local test + return "/tmp" + + +def formatDate(format="%Y-%m-%d %H:%M:%S", times=None): + # 格式化指定时间戳 + if not times: + times = int(time.time()) + time_local = time.localtime(times) + return time.strftime(format, time_local) + + +def strfToTime(sdate): + # 转换时间 + import time + return time.strftime('%Y-%m-%d', time.strptime(sdate, '%b %d %H:%M:%S %Y %Z')) + + +def md5(content): + # 生成MD5 + try: + m = hashlib.md5() + m.update(content.encode("utf-8")) + return m.hexdigest() + except Exception as ex: + return False + +def hasPwd(password): + ''' + 加密密码字符 + ''' + # python3 -c "import crypt" + # python3 -c 'import crypt; print(crypt.crypt(""))' + # import crypt + # return crypt.crypt(password, password) + import bcrypt + salt = bcrypt.gensalt() + hpw = bcrypt.hashpw(password.encode('utf-8'), salt) + return hpw.decode('utf-8') + + +def getFileMd5(filename): + # 文件的MD5值 + if not os.path.isfile(filename): + return False + + myhash = hashlib.md5() + f = file(filename, 'rb') + while True: + b = f.read(8096) + if not b: + break + myhash.update(b) + f.close() + return myhash.hexdigest() + + +def getHost(port=False): + from flask import request + host_tmp = request.headers.get('host') + if not host_tmp: + if request.url_root: + tmp = re.findall(r"(https|http)://([\w:\.-]+)", request.url_root) + if tmp: + host_tmp = tmp[0][1] + if not host_tmp: + host_tmp = getLocalIp() + ':' + readFile('data/port.pl').strip() + try: + if host_tmp.find(':') == -1: + host_tmp += ':80' + except: + host_tmp = "127.0.0.1:8888" + h = host_tmp.split(':') + if port: + return h[-1] + return ':'.join(h[0:-1]) + +def getClientIp(): + from flask import request + return request.remote_addr.replace('::ffff:', '') + +def checkDomainPanel(): + import thisdb + from flask import Flask, redirect, request, url_for + + current_host = getHost() + domain = thisdb.getOption('panel_domain', default='') + port = getPanelPort() + scheme = 'http' + + panel_ssl_data = thisdb.getOptionByJson('panel_ssl', default={'open':False}) + if panel_ssl_data['open']: + if not inArray(['local','nginx'], panel_ssl_data['choose']): + return False + scheme = 'https' + + client_ip = getClientIp() + if client_ip in ['127.0.0.1', 'localhost', '::1']: + return False + + ip = getHostAddr() + if isVaildIpV6(ip): + return False + + if domain == '': + if ip in ['127.0.0.1', 'localhost', '::1']: + return False + if current_host.strip().lower() != ip.strip().lower(): + to = scheme + "://" + ip + ":" + str(port) + return redirect(to, code=302) + return False + else: + # print(current_host.strip().lower(), domain.strip().lower()) + if current_host.strip().lower() != domain.strip().lower(): + to = scheme + "://" + domain + ":" + str(port) + return redirect(to, code=302) + return False + +def getLocalIp(): + filename = getPanelDir()+'/data/iplist.txt' + try: + ipaddress = readFile(filename) + if not ipaddress or ipaddress == '127.0.0.1': + cmd = "curl --insecure -4 -sS --connect-timeout 5 -m 60 https://v6r.ipip.net/?format=text" + ip = execShell(cmd) + result = ip[0].strip() + if result == '': + raise Exception("ipv4 is empty!") + writeFile(filename, result) + return result + return ipaddress + except Exception as e: + cmd = "curl --insecure -6 -sS --connect-timeout 5 -m 60 https://v6r.ipip.net/?format=text" + ip = execShell(cmd) + result = ip[0].strip() + if result == '': + return '127.0.0.1' + writeFile(filename, result) + return result + finally: + pass + return '127.0.0.1' + + +def inArray(arrays, searchStr): + # 搜索数据中是否存在 + for key in arrays: + if key == searchStr: + return True + + return False + +def getJson(data): + import json + return json.dumps(data) + +def getObjectByJson(data): + import json + return json.loads(data) + + +def getSslCrt(): + if os.path.exists('/etc/ssl/certs/ca-certificates.crt'): + return '/etc/ssl/certs/ca-certificates.crt' + if os.path.exists('/etc/pki/tls/certs/ca-bundle.crt'): + return '/etc/pki/tls/certs/ca-bundle.crt' + return '' + + +def getOs(): + # python3 -c 'import sys; print(sys.platform)' + return sys.platform + +def getOsName(): + cmd = "cat /etc/*-release | grep PRETTY_NAME |awk -F = '{print $2}' | awk -F '\"' '{print $2}'| awk '{print $1}'" + data = execShell(cmd) + return data[0].strip().lower() + +def getOsID(): + cmd = "cat /etc/*-release | grep VERSION_ID | awk -F = '{print $2}' | awk -F '\"' '{print $2}'" + data = execShell(cmd) + return data[0].strip() + +# 获取文件权限描述 +def getFileStatsDesc(filename, path=None): + import pwd + if path == '' or filename == '': + return ';;;;;' + try: + filename = filename.replace('//', '/') + stat = os.stat(filename) + accept = str(oct(stat.st_mode)[-3:]) + mtime = str(int(stat.st_mtime)) + user = '' + try: + user = str(pwd.getpwuid(stat.st_uid).pw_name) + except: + user = str(stat.st_uid) + + size = str(stat.st_size) + link = '' + if os.path.islink(filename): + link = ' -> ' + os.readlink(filename) + + if path: + tmp_path = (path + '/').replace('//', '/') + filename = filename.replace(tmp_path, '', 1) + + return filename + ';' + size + ';' + mtime + ';' + accept + ';' + user + ';' + link + except Exception as e: + return ';;;;;' + +def getFileSuffix(file): + tmp = file.split('.') + ext = tmp[len(tmp) - 1] + return ext + +def getPathSuffix(path): + return os.path.splitext(path)[-1] + +def getHostAddr(): + ip_text = getPanelDataDir() + '/iplist.txt' + if os.path.exists(ip_text): + return readFile(ip_text).strip() + return '127.0.0.1' + +def checkIp(ip): + # 检查是否为IPv4地址 + import re + p = re.compile(r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$') + if p.match(ip): + return True + else: + return False + +def createLinuxUser(user, group): + execShell("groupadd {}".format(group)) + execShell('useradd -s /sbin/nologin -g {} {}'.format(user, group)) + return True + + +def setOwn(filename, user, group=None): + if isAppleSystem(): + return True + + # 设置用户组 + if not os.path.exists(filename): + return False + from pwd import getpwnam + try: + user_info = getpwnam(user) + user = user_info.pw_uid + if group: + user_info = getpwnam(group) + group = user_info.pw_gid + except: + if user == 'www': + createLinuxUser(user) + # 如果指定用户或组不存在,则使用www + try: + user_info = getpwnam('www') + except: + createLinuxUser(user) + user_info = getpwnam('www') + user = user_info.pw_uid + group = user_info.pw_gid + os.chown(filename, user, group) + return True + +def setMode(filename, mode): + # 设置文件权限 + if not os.path.exists(filename): + return False + mode = int(str(mode), 8) + os.chmod(filename, mode) + return True + +def getSqitePrefix(): + WIN = sys.platform.startswith('win') + if WIN: # 如果是 Windows 系统,使用三个斜线 + prefix = 'sqlite:///' + else: # 否则使用四个斜线 + prefix = 'sqlite:////' + return prefix + +def checkPort(port): + # 检查端口是否合法 + ports = ['21', '443', '888'] + if port in ports: + return False + intport = int(port) + if intport < 1 or intport > 65535: + return False + return True + +def getStrBetween(startStr, endStr, srcStr): + # 字符串取中间 + start = srcStr.find(startStr) + if start == -1: + return None + end = srcStr.find(endStr) + if end == -1: + return None + return srcStr[start + 1:end] + +def getCpuType(): + cpuType = '' + if isAppleSystem(): + cmd = "system_profiler SPHardwareDataType | grep 'Processor Name' | awk -F ':' '{print $2}'" + cpuinfo = execShell(cmd) + return cpuinfo[0].strip() + + current_os = getOs() + if current_os.startswith('freebsd'): + cmd = "sysctl -a | egrep -i 'hw.model' | awk -F ':' '{print $2}'" + cpuinfo = execShell(cmd) + return cpuinfo[0].strip() + + # 取CPU类型 + cpuinfo = open('/proc/cpuinfo', 'r').read() + rep = "model\\s+name\\s+:\\s+(.+)" + tmp = re.search(rep, cpuinfo, re.I) + if tmp: + cpuType = tmp.groups()[0] + else: + cpuinfo = execShell('LANG="en_US.UTF-8" && lscpu')[0] + rep = "Model\\s+name:\\s+(.+)" + tmp = re.search(rep, cpuinfo, re.I) + if tmp: + cpuType = tmp.groups()[0] + return cpuType + + +def getLanguage(): + panel_dir = getPanelDir() + path = panel_dir+'/data/language.pl' + if not os.path.exists(path): + return 'Simplified_Chinese' + return readFile(path).strip() + + +def getStaticJson(name="public"): + file = 'static/language/' + getLanguage() + '/' + name + '.json' + if not os.path.exists(file): + file = 'route/static/language/' + getLanguage() + '/' + name + '.json' + return file + + +def returnMsg(status, msg, args=()): + # 取通用字曲返回 + pjson = getStaticJson('public') + logMessage = json.loads(readFile(pjson)) + keys = logMessage.keys() + + if msg in keys: + msg = logMessage[msg] + for i in range(len(args)): + rep = '{' + str(i + 1) + '}' + msg = msg.replace(rep, args[i]) + return {'status': status, 'msg': msg, 'data': args} + +def getInfo(msg, args=()): + # 取提示消息 + for i in range(len(args)): + rep = '{' + str(i + 1) + '}' + msg = msg.replace(rep, args[i]) + return msg + +def getLastLine(path, num, p=1): + pyVersion = sys.version_info[0] + try: + import html + if not os.path.exists(path): + return "" + start_line = (p - 1) * num + count = start_line + num + fp = open(path, 'rb') + buf = "" + + fp.seek(0, 2) + if fp.read(1) == "\n": + fp.seek(0, 2) + data = [] + b = True + n = 0 + + for i in range(count): + while True: + newline_pos = str.rfind(str(buf), "\n") + pos = fp.tell() + if newline_pos != -1: + if n >= start_line: + line = buf[newline_pos + 1:] + try: + data.insert(0, html.escape(line)) + except Exception as e: + pass + buf = buf[:newline_pos] + n += 1 + break + else: + if pos == 0: + b = False + break + to_read = min(4096, pos) + fp.seek(-to_read, 1) + t_buf = fp.read(to_read) + if pyVersion == 3: + if type(t_buf) == bytes: + t_buf = t_buf.decode("utf-8", "ignore").strip() + buf = t_buf + buf + fp.seek(-to_read, 1) + if pos - to_read == 0: + buf = "\n" + buf + if not b: + break + fp.close() + except Exception as e: + return str(e) + + return "\n".join(data) + +# 获取系统温度 +def getSystemDeviceTemperature(): + import psutil + if not hasattr(psutil, "sensors_temperatures"): + return False, "platform not supported" + temps = psutil.sensors_temperatures() + if not temps: + return False, "can't read any temperature" + for name, entries in temps.items(): + for entry in entries: + return True, entry.label + # print("%-20s %s °C (high = %s °C, critical = %s °C)" % ( + # entry.label or name, entry.current, entry.high, + # entry.critical)) + return False, "" + +def getPage(args, result='1,2,3,4,5,8'): + data = getPageObject(args, result) + return data[0] + + +def getPageObject(args, result='1,2,3,4,5,8'): + # 取分页 + from utils import page + # 实例化分页类 + page = page.Page() + info = {} + + info['count'] = 0 + if 'count' in args: + info['count'] = int(args['count']) + + info['row'] = 10 + if 'row' in args: + info['row'] = int(args['row']) + + info['p'] = 1 + if 'p' in args: + info['p'] = int(args['p']) + info['uri'] = {} + info['return_js'] = '' + if 'tojs' in args: + info['return_js'] = args['tojs'] + + if 'args_tpl' in args: + info['args_tpl'] = args['args_tpl'] + + return (page.GetPage(info, result), page) + + +def getHostPort(): + port_file = getPanelDir() + '/data/port.pl' + if os.path.exists(port_file): + return readFile(port_file).strip() + return '7200' + + +def setHostPort(port): + file = getPanelDir() + '/data/port.pl' + return writeFile(file, port) + +def isAppleSystem(): + if getOs() == 'darwin': + return True + return False + +def isDocker(): + return os.path.exists('/.dockerenv') + +def isSupportSystemctl(): + if isAppleSystem(): + return False + if isDocker(): + return False + + current_os = getOs() + if current_os.startswith("freebsd"): + return False + return True + +def isDebugMode(): + if isAppleSystem(): + return True + + debug = M('option').field('name').where('name=?',('debug',)).getField('value') + if debug == 'open': + return True + return False + +def isNumber(s): + try: + float(s) + return True + except ValueError: + pass + + try: + import unicodedata + unicodedata.numeric(s) + return True + except (TypeError, ValueError): + pass + + return False + +# 检查端口是否占用 +def isOpenPort(port): + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect(('127.0.0.1', int(port))) + s.shutdown(2) + return True + except Exception as e: + return False + +def debugLog(*data): + if isDebugMode(): + print(data) + return True + + +def writeLog(stype, msg, args=()): + # 写日志 + uid = 0 + # try: + # from flask import session + # if 'uid' in session: + # uid = session['uid'] + # except Exception as e: + # print("writeLog:"+str(e)) + # pass + # writeFileLog(getTracebackInfo()) + return writeDbLog(stype, msg, args, uid) + +def writeFileLog(msg, path=None, limit_size=50 * 1024 * 1024, save_limit=3): + log_file = getPanelDir() + '/logs/debug.log' + if path != None: + log_file = path + + if os.path.exists(log_file): + size = os.path.getsize(log_file) + if size > limit_size: + log_file_rename = log_file + "_" + \ + time.strftime("%Y-%m-%d_%H%M%S") + '.log' + os.rename(log_file, log_file_rename) + logs = sorted(glob.glob(log_file + "_*")) + count = len(logs) + save_limit = count - save_limit + for i in range(count): + if i > save_limit: + break + os.remove(logs[i]) + # print('|---多余日志[' + logs[i] + ']已删除!') + + f = open(log_file, 'ab+') + msg += "\n" + if __name__ == '__main__': + print(msg) + f.write(msg.encode('utf-8')) + f.close() + return True + +def writeDbLog(stype, msg, args=(), uid=1): + try: + import thisdb + format_msg = getInfo(msg, args) + thisdb.addLog(stype, format_msg, uid=uid) + return True + except Exception as e: + print("writeDbLog:"+str(e)) + return False + +def writeSpeed(title, used, total, speed=0): + panel_dir = getPanelDir() + speed_file= panel_dir + '/data/panel_speed.pl' + # 写进度 + if not title: + data = {'title': None, 'progress': 0,'total': 0, 'used': 0, 'speed': 0} + else: + progress = int((100.0 * used / total)) + data = {'title': title, 'progress': progress,'total': total, 'used': used, 'speed': speed} + writeFile(speed_file, json.dumps(data)) + return True + + +def getSpeed(): + panel_dir = getPanelDir() + speed_file= panel_dir + '/data/panel_speed.pl' + # 取进度 + path = getPanelDir() + data = readFile(speed_file) + if not data: + data = json.dumps({'title': None, 'progress': 0,'total': 0, 'used': 0, 'speed': 0}) + writeFile(speed_file, data) + return json.loads(data) + + + +def M(table=''): + import core.db as db + sql = db.Sql() + if table == '': + return sql + return sql.table(table) + + +def enDoubleCrypt(key, strings): + # 加密字符串 + try: + import base64 + _key = md5(key).encode('utf-8') + _key = base64.urlsafe_b64encode(_key) + + if type(strings) != bytes: + strings = strings.encode('utf-8') + import cryptography + from cryptography.fernet import Fernet + f = Fernet(_key) + result = f.encrypt(strings) + return result.decode('utf-8') + except: + writeFileLog(getTracebackInfo()) + return strings + + +def deDoubleCrypt(key, strings): + # 解密字符串 + try: + import base64 + _key = md5(key).encode('utf-8') + _key = base64.urlsafe_b64encode(_key) + + if type(strings) != bytes: + strings = strings.encode('utf-8') + from cryptography.fernet import Fernet + f = Fernet(_key) + result = f.decrypt(strings).decode('utf-8') + return result + except: + writeFileLog(getTracebackInfo()) + return strings + +def aesEncrypt(data, key='ABCDEFGHIJKLMNOP', vi='0102030405060708'): + # aes加密 + # @param data 被加密的数据 + # @param key 加解密密匙 16位 + # @param vi 16位 + + from cryptography.hazmat.primitives import padding + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + + if not isinstance(data, bytes): + data = data.encode() + + # AES_CBC_KEY = os.urandom(32) + # AES_CBC_IV = os.urandom(16) + + AES_CBC_KEY = key.encode() + AES_CBC_IV = vi.encode() + + # print("AES_CBC_KEY:", AES_CBC_KEY) + # print("AES_CBC_IV:", AES_CBC_IV) + + padder = padding.PKCS7(algorithms.AES.block_size).padder() + padded_data = padder.update(data) + padder.finalize() + + cipher = Cipher(algorithms.AES(AES_CBC_KEY), + modes.CBC(AES_CBC_IV), + backend=default_backend()) + encryptor = cipher.encryptor() + + edata = encryptor.update(padded_data) + + # print(edata) + # print(str(edata)) + # print(edata.decode()) + return edata + + +def aesDecrypt(data, key='ABCDEFGHIJKLMNOP', vi='0102030405060708'): + # aes加密 + # @param data 被解密的数据 + # @param key 加解密密匙 16位 + # @param vi 16位 + + from cryptography.hazmat.primitives import padding + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + + from cryptography.hazmat.primitives import padding + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.backends import default_backend + + if not isinstance(data, bytes): + data = data.encode() + + AES_CBC_KEY = key.encode() + AES_CBC_IV = vi.encode() + + cipher = Cipher(algorithms.AES(AES_CBC_KEY), + modes.CBC(AES_CBC_IV), + backend=default_backend()) + decryptor = cipher.decryptor() + + ddata = decryptor.update(data) + + unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() + data = unpadder.update(ddata) + + try: + uppadded_data = data + unpadder.finalize() + except ValueError: + raise Exception('无效的加密信息!') + + return uppadded_data + +def aesEncrypt_Crypto(data, key, vi): + # 该方法保留,暂时不使用 + # aes加密 + # @param data 被加密的数据 + # @param key 加解密密匙 16位 + # @param vi 16位 + + from Crypto.Cipher import AES + cryptor = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8')) + # 判断是否含有中文 + zhmodel = re.compile(u'[\u4e00-\u9fff]') + match = zhmodel.search(data) + if match == None: + # 无中文时 + add = 16 - len(data) % 16 + pad = lambda s: s + add * chr(add) + data = pad(data) + enctext = cryptor.encrypt(data.encode('utf8')) + else: + # 含有中文时 + data = data.encode() + add = 16 - len(data) % 16 + data = data + add * (chr(add)).encode() + enctext = cryptor.encrypt(data) + encodestrs = base64.b64encode(enctext).decode('utf8') + return encodestrs + + +def aesDecrypt_Crypto(data, key, vi): + # 该方法保留,暂时不使用 + # aes加密 + # @param data 被加密的数据 + # @param key 加解密密匙 16位 + # @param vi 16位 + + from crypto.Cipher import AES + data = data.encode('utf8') + encodebytes = base64.urlsafe_b64decode(data) + cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8')) + text_decrypted = cipher.decrypt(encodebytes) + # 判断是否含有中文 + zhmodel = re.compile(u'[\u4e00-\u9fff]') + match = zhmodel.search(text_decrypted) + if match == False: + # 无中文时补位 + unpad = lambda s: s[0:-s[-1]] + text_decrypted = unpad(text_decrypted) + text_decrypted = text_decrypted.decode('utf8').rstrip() # 去掉补位的右侧空格 + return text_decrypted + +def getDefault(data,val,def_val=''): + if val in data: + return data[val] + return def_val + +def encodeImage(imgsrc, newsrc): + # 图片加密 + import struct + old_fp = open(imgsrc, 'rb') + imgFile = old_fp.read() + old_fp.close() + + new_fp = open(newsrc,"wb") + for x in imgFile: + value = x ^ 86 + value = hex(value) + s = struct.pack('B',int(value,16)) + new_fp.write(s) + new_fp.close() + return True + +def buildSoftLink(src, dst, force=False): + ''' + 建立软连接 + ''' + if not os.path.exists(src): + return False + + if os.path.exists(dst) and force: + os.remove(dst) + + if not os.path.exists(dst): + execShell('ln -sf "' + src + '" "' + dst + '"') + return True + return False +# ------------------------------ network start ----------------------------- + +def HttpGet(url, timeout=10): + """ + 发送GET请求 + @url 被请求的URL地址(必需) + @timeout 超时时间默认60秒 + return string + """ + if sys.version_info[0] == 2: + try: + import urllib2 + import ssl + if sys.version_info[0] == 2: + reload(urllib2) + reload(ssl) + try: + ssl._create_default_https_context = ssl._create_unverified_context + except: + pass + response = urllib2.urlopen(url, timeout=timeout) + return response.read() + except Exception as ex: + return str(ex) + else: + try: + import urllib.request + import ssl + try: + ssl._create_default_https_context = ssl._create_unverified_context + except: + pass + response = urllib.request.urlopen(url, timeout=timeout) + result = response.read() + if type(result) == bytes: + result = result.decode('utf-8') + return result + except Exception as ex: + return str(ex) + + +def HttpGet2(url, timeout): + import urllib.request + + try: + import ssl + try: + ssl._create_default_https_context = ssl._create_unverified_context + except: + pass + req = urllib.request.urlopen(url, timeout=timeout) + result = req.read().decode('utf-8') + return result + + except Exception as e: + return str(e) + + +def httpGet(url, timeout=10): + return HttpGet2(url, timeout) + + +def HttpPost(url, data, timeout=10): + """ + 发送POST请求 + @url 被请求的URL地址(必需) + @data POST参数,可以是字符串或字典(必需) + @timeout 超时时间默认60秒 + return string + """ + if sys.version_info[0] == 2: + try: + import urllib + import urllib2 + import ssl + ssl._create_default_https_context = ssl._create_unverified_context + data = urllib.urlencode(data) + req = urllib2.Request(url, data) + response = urllib2.urlopen(req, timeout=timeout) + return response.read() + except Exception as ex: + return str(ex) + else: + try: + import urllib.request + import ssl + try: + ssl._create_default_https_context = ssl._create_unverified_context + except: + pass + data = urllib.parse.urlencode(data).encode('utf-8') + req = urllib.request.Request(url, data) + response = urllib.request.urlopen(req, timeout=timeout) + result = response.read() + if type(result) == bytes: + result = result.decode('utf-8') + return result + except Exception as ex: + return str(ex) + + +def httpPost(url, data, timeout=10): + return HttpPost(url, data, timeout) + +# ------------------------------ network end ----------------------------- + +# ------------------------------ panel start ----------------------------- + +def isRestart(): + # 检查是否允许重启 + num = M('tasks').where('status!=?', ('1',)).count() + if num > 0: + return False + return True + +def getAcmeDir(): + acme = '/root/.acme.sh' + if isAppleSystem(): + cmd = "who | sed -n '2, 1p' |awk '{print $1}'" + user = execShell(cmd)[0].strip() + acme = '/Users/' + user + '/.acme.sh' + # if not os.path.exists(acme): + # acme = '/.acme.sh' + return acme + + +def getAcmeDomainDir(domain): + acme_dir = getAcmeDir() + acme_domain = acme_dir + '/' + domain + acme_domain_ecc = acme_domain + '_ecc' + if os.path.exists(acme_domain_ecc): + acme_domain = acme_domain_ecc + return acme_domain + + +def fileNameCheck(filename): + f_strs = [';', '&', '<', '>'] + for fs in f_strs: + if filename.find(fs) != -1: + return False + return True + +def getTriggerTaskLockFile(): + return getPanelDir() + '/logs/panel_task.lock' + +def triggerTask(): + lock_file = getTriggerTaskLockFile() + writeFile(lock_file, 'True') + +def restartTask(): + initd = getPanelDir() + '/scripts/init.d/mw' + if os.path.exists(initd): + cmd = initd + ' ' + 'restart_task' + os.system(cmd) + return True + +def restartMw(): + restart_file = getPanelDir()+'/data/restart.pl' + writeFile(restart_file, 'True') + return True + +def panelCmd(method): + cmd = '/etc/init.d/mw' + if os.path.exists(cmd): + execShell(cmd + ' ' + method) + return + + cmd = getPanelDir() + '/scripts/init.d/mw' + if os.path.exists(cmd): + data = execShell(cmd + ' ' + method) + return + +# ------------------------------ panel end ----------------------------- + +# ------------------------------ openresty start ----------------------------- + +def getOpVer(): + version = '' + version_file_pl = getServerDir() + '/openresty/version.pl' + if os.path.exists(version_file_pl): + version = readFile(version_file_pl) + version = version.strip() + return version + +def checkWebConfig(): + op_dir = getServerDir() + '/openresty/nginx' + # "ulimit -n 10240 && " + + cmd = op_dir + "/sbin/nginx -t -c " + op_dir + "/conf/nginx.conf" + result = execShell(cmd) + searchStr = 'test is successful' + if result[1].find(searchStr) == -1: + msg = getInfo('配置文件错误: {1}', (result[1],)) + writeLog("软件管理", msg) + return result[1] + return True + +def isIpAddr(ip): + check_ip = re.compile(r'^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\\d)$') + if check_ip.match(ip): + return True + else: + return False + +def isVaildIpV4(ip): + import ipaddress + try: + ipaddress.IPv4Address(ip) + return True + except ipaddress.AddressValueError: + return False + +def isVaildIpV6(ip): + import ipaddress + try: + ipaddress.IPv6Address(ip) + return True + except ipaddress.AddressValueError: + return False + +def isVaildIp(ip): + import ipaddress + try: + ipaddress.IPv4Address(ip) + return True + except ipaddress.AddressValueError: + pass + + try: + ipaddress.IPv6Address(ip) + return True + except ipaddress.AddressValueError: + pass + return False + + +def getWebStatus(): + pid = getServerDir() + '/openresty/nginx/logs/nginx.pid' + if os.path.exists(pid): + return True + return False + + +def restartWeb(): + return opWeb("reload") + +def deleteFile(file): + if os.path.exists(file): + os.remove(file) + +def isInstalledWeb(): + path = getServerDir() + '/openresty/nginx/sbin/nginx' + if os.path.exists(path): + return True + return False + +def opWeb(method): + if not isInstalledWeb(): + return False + + # systemd + systemd = systemdCfgDir() + '/openresty.service' + if os.path.exists(systemd): + execShell('systemctl ' + method + ' openresty') + return True + + + sys_initd = '/etc/init.d/openresty' + if os.path.exists(sys_initd): + os.system(sys_initd + ' ' + method) + return True + + # initd + initd = getServerDir() + '/openresty/init.d/openresty' + if os.path.exists(initd): + execShell(initd + ' ' + method) + return True + + return False + +def opLuaMake(cmd_name): + path = getServerDir() + '/web_conf/nginx/lua/lua.conf' + root_dir = getServerDir() + '/web_conf/nginx/lua/' + cmd_name + dst_path = getServerDir() + '/web_conf/nginx/lua/' + cmd_name + '.lua' + def_path = getServerDir() + '/web_conf/nginx/lua/empty.lua' + + if not os.path.exists(root_dir): + execShell('mkdir -p ' + root_dir) + + files = [] + for fl in os.listdir(root_dir): + suffix = getFileSuffix(fl) + if suffix != 'lua': + continue + flpath = os.path.join(root_dir, fl) + files.append(flpath) + + if len(files) > 0: + def_path = dst_path + content = '' + for f in files: + t = readFile(f) + f_base = os.path.basename(f) + content += '-- ' + '*' * 20 + ' ' + f_base + ' start ' + '*' * 20 + "\n" + content += t + content += "\n" + '-- ' + '*' * 20 + ' ' + f_base + ' end ' + '*' * 20 + "\n" + writeFile(dst_path, content) + else: + if os.path.exists(dst_path): + os.remove(dst_path) + + conf = readFile(path) + conf = re.sub(cmd_name + ' (.*);', + cmd_name + " " + def_path + ";", conf) + writeFile(path, conf) + + +def opLuaInitFile(): + opLuaMake('init_by_lua_file') + + +def opLuaInitWorkerFile(): + opLuaMake('init_worker_by_lua_file') + + +def opLuaInitAccessFile(): + opLuaMake('access_by_lua_file') + + +def opLuaMakeAll(): + opLuaInitFile() + opLuaInitWorkerFile() + opLuaInitAccessFile() + +# ------------------------------ openresty end ----------------------------- + +# --------------------------------------------------------------------------------- +# PHP START +# --------------------------------------------------------------------------------- + +def getFpmConfFile(version): + return getServerDir() + '/php/' + version + '/etc/php-fpm.d/www.conf' + +def getFpmAddress(version): + fpm_address = '/tmp/php-cgi-{}.sock'.format(version) + php_fpm_file = getFpmConfFile(version) + try: + content = readFile(php_fpm_file) + tmp = re.findall(r"listen\s*=\s*(.+)", content) + if not tmp: + return fpm_address + if tmp[0].find('sock') != -1: + return fpm_address + if tmp[0].find(':') != -1: + listen_tmp = tmp[0].split(':') + if bind: + fpm_address = (listen_tmp[0], int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(tmp[0])) + return fpm_address + except: + return fpm_address + +def requestFcgiPHP(sock, uri, document_root='/tmp', method='GET', pdata=b''): + # 直接请求到PHP-FPM + # version php版本 + # uri 请求uri + # filename 要执行的php文件 + # args 请求参数 + # method 请求方式 + + import utils.php.fpm as fpm + p = fpm.fpm(sock, document_root) + + if type(pdata) == dict: + pdata = url_encode(pdata) + result = p.load_url_public(uri, pdata, method) + return result +# --------------------------------------------------------------------------------- +# PHP END +# --------------------------------------------------------------------------------- + + +# --------------------------------------------------------------------------------- +# 数据库 START +# --------------------------------------------------------------------------------- + +def getMyORM(): + ''' + 获取MySQL资源的ORM + ''' + import core.orm as orm + o = orm.ORM() + return o +# --------------------------------------------------------------------------------- +# 数据库 START +# --------------------------------------------------------------------------------- + +##################### ssl start ######################################### + +def strfDate(sdate): + return time.strftime('%Y-%m-%d', time.strptime(sdate, '%Y%m%d%H%M%S')) + +# 获取证书名称 +def getCertName(certPath): + if not os.path.exists(certPath): + return None + try: + import OpenSSL + result = {} + x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, readFile(certPath)) + # 取产品名称 + issuer = x509.get_issuer() + result['issuer'] = '' + if hasattr(issuer, 'CN'): + result['issuer'] = issuer.CN + if not result['issuer']: + is_key = [b'0', '0'] + issue_comp = issuer.get_components() + if len(issue_comp) == 1: + is_key = [b'CN', 'CN'] + for iss in issue_comp: + if iss[0] in is_key: + result['issuer'] = iss[1].decode() + break + if not result['issuer']: + if hasattr(issuer, 'O'): + result['issuer'] = issuer.O + # 取到期时间 + result['notAfter'] = strfDate(bytes.decode(x509.get_notAfter())[:-1]) + # 取申请时间 + result['notBefore'] = strfDate(bytes.decode(x509.get_notBefore())[:-1]) + # 取可选名称 + result['dns'] = [] + for i in range(x509.get_extension_count()): + s_name = x509.get_extension(i) + if s_name.get_short_name() in [b'subjectAltName', 'subjectAltName']: + s_dns = str(s_name).split(',') + for d in s_dns: + result['dns'].append(d.split(':')[1]) + subject = x509.get_subject().get_components() + # 取主要认证名称 + if len(subject) == 1: + result['subject'] = subject[0][1].decode() + else: + if not result['dns']: + for sub in subject: + if sub[0] == b'CN': + result['subject'] = sub[1].decode() + break + if 'subject' in result: + result['dns'].append(result['subject']) + else: + result['subject'] = result['dns'][0] + result['endtime'] = int(int(time.mktime(time.strptime( + result['notAfter'], "%Y-%m-%d")) - time.time()) / 86400) + return result + except Exception as e: + writeFileLog(getTracebackInfo()) + return None + +def createLocalSSL(): + pdir = getPanelDir() + local_dir = pdir+'/ssl/local' + if not os.path.exists(local_dir): + execShell('mkdir -p ' + local_dir) + + # 自签证书 + # if os.path.exists('ssl/local/input.pl'): + # return True + + client_ip = getClientIp() + + import OpenSSL + key = OpenSSL.crypto.PKey() + key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) + cert = OpenSSL.crypto.X509() + cert.set_serial_number(0) + + if client_ip == '127.0.0.1': + cert.get_subject().CN = '127.0.0.1' + else: + cert.get_subject().CN = getLocalIp() + + cert.set_issuer(cert.get_subject()) + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(86400 * 3650) + cert.set_pubkey(key) + cert.sign(key, 'md5') + cert_ca = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) + private_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) + if len(cert_ca) > 100 and len(private_key) > 100: + writeFile(local_dir+'/cert.pem', cert_ca, 'wb+') + writeFile(local_dir+'/private.pem', private_key, 'wb+') + return True + return False + + +def getSSHPort(): + try: + file = '/etc/ssh/sshd_config' + conf = readFile(file) + rep = "(#*)?Port\\s+([0-9]+)\\s*\n" + port = re.search(rep, conf).groups(0)[1] + return int(port) + except: + return 22 + + +def getSSHStatus(): + if os.path.exists('/usr/bin/apt-get'): + status = execShell("service ssh status | grep -P '(dead|stop)'") + else: + import system_api + version = system_api.system_api().getSystemVersion() + if version.find(' Mac ') != -1: + return True + if version.find(' 7.') != -1: + status = execShell("systemctl status sshd.service | grep 'dead'") + else: + status = execShell( + "/etc/init.d/sshd status | grep -e 'stopped' -e '已停'") + if len(status[0]) > 3: + status = False + else: + status = True + return status + +##################### ssl end ######################################### + +def getGlibcVersion(): + try: + cmd_result = execShell("ldd --version")[0] + if not cmd_result: return '' + glibc_version = cmd_result.split("\n")[0].split()[-1] + except: + return '' + return glibc_version + +##################### ssh start ######################################### +def getSshDir(): + if isAppleSystem(): + user = execShell("who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + return '/Users/' + user + '/.ssh' + return '/root/.ssh' + + +def processExists(pname, exe=None, cmdline=None): + # 进程是否存在 + try: + import psutil + pids = psutil.pids() + for pid in pids: + try: + p = psutil.Process(pid) + if p.name() == pname: + if not exe and not cmdline: + return True + else: + if exe: + if p.exe() == exe: + return True + if cmdline: + if cmdline in p.cmdline(): + return True + except: + pass + return False + except: + return True + + +def createRsa(): + # ssh-keygen -t rsa -P "" -C "midoks@163.com" + ssh_dir = getSshDir() + # mw.execShell("rm -f /root/.ssh/*") + if not os.path.exists(ssh_dir + '/authorized_keys'): + execShell('touch ' + ssh_dir + '/authorized_keys') + + if not os.path.exists(ssh_dir + '/id_rsa.pub') and os.path.exists(ssh_dir + '/id_rsa'): + execShell('echo y | ssh-keygen -q -t rsa -P "" -f ' + + ssh_dir + '/id_rsa') + else: + execShell('ssh-keygen -q -t rsa -P "" -f ' + ssh_dir + '/id_rsa') + + execShell('cat ' + ssh_dir + '/id_rsa.pub >> ' + + ssh_dir + '/authorized_keys') + execShell('chmod 600 ' + ssh_dir + '/authorized_keys') + + +def createSshInfo(): + ssh_dir = getSshDir() + if not os.path.exists(ssh_dir + '/id_rsa') or not os.path.exists(ssh_dir + '/id_rsa.pub'): + createRsa() + + # 检查是否写入authorized_keys + data = execShell("cat " + ssh_dir + "/id_rsa.pub | awk '{print $3}'") + if data[0] != "": + cmd = "cat " + ssh_dir + "/authorized_keys | grep " + data[0] + ak_data = execShell(cmd) + if ak_data[0] == "": + cmd = 'cat ' + ssh_dir + '/id_rsa.pub >> ' + ssh_dir + '/authorized_keys' + execShell(cmd) + execShell('chmod 600 ' + ssh_dir + '/authorized_keys') + + +def connectSsh(): + import paramiko + ssh = paramiko.SSHClient() + createSshInfo() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + port = getSSHPort() + try: + ssh.connect('127.0.0.1', port, timeout=5) + except Exception as e: + ssh.connect('localhost', port, timeout=5) + except Exception as e: + ssh.connect(getHostAddr(), port, timeout=30) + except Exception as e: + return False + + shell = ssh.invoke_shell(term='xterm', width=83, height=21) + shell.setblocking(0) + return shell + + +def clearSsh(): + # 服务器IP + ip = getHostAddr() + sh = ''' +#!/bin/bash +PLIST=`who | grep localhost | awk '{print $2}'` +for i in $PLIST +do + ps -t /dev/$i |grep -v TTY | awk '{print $1}' | xargs kill -9 +done + +# getHostAddr +PLIST=`who | grep "${ip}" | awk '{print $2}'` +for i in $PLIST +do + ps -t /dev/$i |grep -v TTY | awk '{print $1}' | xargs kill -9 +done +''' + if not isAppleSystem(): + info = execShell(sh) + print(info[0], info[1]) +##################### ssh end ######################################### + +##################### notify start ######################################### + + +def initNotifyConfig(): + p = getNotifyPath() + if not os.path.exists(p): + writeFile(p, '{}') + return True + + +def getNotifyPath(): + path = 'data/notify.json' + return path + + +def getNotifyData(is_parse=False): + initNotifyConfig() + notify_file = getNotifyPath() + notify_data = readFile(notify_file) + + data = json.loads(notify_data) + + if is_parse: + tag_list = ['tgbot', 'email'] + for t in tag_list: + if t in data and 'cfg' in data[t]: + data[t]['data'] = json.loads(deDoubleCrypt(t, data[t]['cfg'])) + return data + + +def writeNotify(data): + p = getNotifyPath() + return writeFile(p, json.dumps(data)) + + +def tgbotNotifyChatID(): + data = getNotifyData(True) + if 'tgbot' in data and 'enable' in data['tgbot']: + if data['tgbot']['enable']: + t = data['tgbot']['data'] + return t['chat_id'] + return '' + + +def tgbotNotifyObject(): + data = getNotifyData(True) + if 'tgbot' in data and 'enable' in data['tgbot']: + if data['tgbot']['enable']: + t = data['tgbot']['data'] + import telebot + bot = telebot.TeleBot(app_token) + return True, bot + return False, None + + +def tgbotNotifyMessage(app_token, chat_id, msg): + import telebot + bot = telebot.TeleBot(app_token) + try: + data = bot.send_message(chat_id, msg) + return True + except Exception as e: + writeFileLog(str(e)) + return False + + +def tgbotNotifyHttpPost(app_token, chat_id, msg): + try: + url = 'https://api.telegram.org/bot' + app_token + '/sendMessage' + post_data = { + 'chat_id': chat_id, + 'text': msg, + } + rdata = httpPost(url, post_data) + return True + except Exception as e: + writeFileLog(str(e)) + return str(e) + return False + + +def tgbotNotifyTest(app_token, chat_id): + msg = 'MW-通知验证测试OK' + return tgbotNotifyHttpPost(app_token, chat_id, msg) + + +def emailNotifyMessage(data): + ''' + 邮件通知 + ''' + import utils.email as email + try: + if data['smtp_ssl'] == 'ssl': + r = email.sendSSL(data['smtp_host'], data['smtp_port'], + data['username'], data['password'], + data['to_mail_addr'], data['subject'], data['content']) + else: + r = email.send(data['smtp_host'], data['smtp_port'], + data['username'], data['password'], + data['to_mail_addr'], data['subject'], data['content']) + + print(r) + return True + except Exception as e: + print(getTracebackInfo()) + return str(e) + return False + + +def emailNotifyTest(data): + # print(data) + data['subject'] = 'MW通知测试' + data['content'] = data['mail_test'] + return emailNotifyMessage(data) + + +def notifyMessageTry(msg, stype='common', trigger_time=300, is_write_log=True): + + lock_file = getPanelTmp() + '/notify_lock.json' + if not os.path.exists(lock_file): + writeFile(lock_file, '{}') + + lock_data = json.loads(readFile(lock_file)) + if stype in lock_data: + diff_time = time.time() - lock_data[stype]['do_time'] + if diff_time >= trigger_time: + lock_data[stype]['do_time'] = time.time() + else: + return False + else: + lock_data[stype] = {'do_time': time.time()} + + writeFile(lock_file, json.dumps(lock_data)) + + if is_write_log: + writeLog("通知管理[" + stype + "]", msg) + + data = getNotifyData(True) + # tag_list = ['tgbot', 'email'] + # tagbot + do_notify = False + if 'tgbot' in data and 'enable' in data['tgbot']: + if data['tgbot']['enable']: + t = data['tgbot']['data'] + i = sys.version_info + + # telebot 在python小于3.7无法使用 + if i[0] < 3 or i[1] < 7: + do_notify = tgbotNotifyHttpPost( + t['app_token'], t['chat_id'], msg) + else: + do_notify = tgbotNotifyMessage( + t['app_token'], t['chat_id'], msg) + + if 'email' in data and 'enable' in data['email']: + if data['email']['enable']: + t = data['email']['data'] + t['subject'] = 'MW通知' + t['content'] = msg + do_notify = emailNotifyMessage(t) + return do_notify + + +def notifyMessage(msg, stype='common', trigger_time=300, is_write_log=True): + try: + return notifyMessageTry(msg, stype, trigger_time, is_write_log) + except Exception as e: + writeFileLog(getTracebackInfo()) + return False + + +##################### notify end ######################################### + +# --------------------------------------------------------------------------------- +# 打印相关 START +# --------------------------------------------------------------------------------- + +def echoStart(tag): + print("=" * 89) + print("★开始{}[{}]".format(tag, formatDate())) + print("=" * 89) + + +def echoEnd(tag): + print("=" * 89) + print("☆{}完成[{}]".format(tag, formatDate())) + print("=" * 89) + + +def echoInfo(msg): + print("|-{}".format(msg)) + +# --------------------------------------------------------------------------------- +# 打印相关 END +# --------------------------------------------------------------------------------- + diff --git a/web/core/orm.py b/web/core/orm.py new file mode 100755 index 000000000..3fa7463f8 --- /dev/null +++ b/web/core/orm.py @@ -0,0 +1,147 @@ +# coding: utf-8 + +import re +import os +import sys + +import pymysql.cursors + + +class ORM: + __DB_PASS = None + __DB_USER = 'root' + __DB_PORT = 3306 + __DB_NAME = '' + __DB_HOST = 'localhost' + __DB_CONN = None + __DB_CUR = None + __DB_ERR = None + __DB_CNF = '/etc/my.cnf' + __DB_TIMEOUT=1 + __DB_SOCKET = '/www/server/mysql/mysql.sock' + + __DB_CHARSET = "utf8" + + def __Conn(self): + # print(self.__DB_HOST, self.__DB_USER, self.__DB_PASS, self.__DB_SOCKET) + '''连接数据库''' + try: + + if self.__DB_HOST != 'localhost': + try: + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=self.__DB_TIMEOUT, + cursorclass=pymysql.cursors.DictCursor) + except Exception as e: + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=self.__DB_TIMEOUT, + cursorclass=pymysql.cursors.DictCursor) + elif os.path.exists(self.__DB_SOCKET): + try: + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=self.__DB_TIMEOUT, + unix_socket=self.__DB_SOCKET, cursorclass=pymysql.cursors.DictCursor) + except Exception as e: + self.__DB_HOST = '127.0.0.1' + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=self.__DB_TIMEOUT, + unix_socket=self.__DB_SOCKET, cursorclass=pymysql.cursors.DictCursor) + else: + try: + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=self.__DB_TIMEOUT, + cursorclass=pymysql.cursors.DictCursor) + except Exception as e: + self.__DB_HOST = '127.0.0.1' + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST, user=self.__DB_USER, passwd=self.__DB_PASS, + database=self.__DB_NAME, + port=int(self.__DB_PORT), charset=self.__DB_CHARSET, connect_timeout=self.__DB_TIMEOUT, + cursorclass=pymysql.cursors.DictCursor) + + self.__DB_CUR = self.__DB_CONN.cursor() + return True + except Exception as e: + self.__DB_ERR = e + return False + + def setDbConf(self, conf): + self.__DB_CNF = conf + + def setSocket(self, sock): + self.__DB_SOCKET = sock + + def setCharset(self, charset): + self.__DB_CHARSET = charset + + def setHost(self, host): + self.__DB_HOST = host + + def setPort(self, port): + self.__DB_PORT = port + + def setUser(self, user): + self.__DB_USER = user + + def setPwd(self, pwd): + self.__DB_PASS = pwd + + def getPwd(self): + return self.__DB_PASS + + def setTimeout(self, timeout = 1): + self.__DB_TIMEOUT = timeout + return True + + def setDbName(self, name): + self.__DB_NAME = name + + def execute(self, sql): + # 执行SQL语句返回受影响行 + if not self.__Conn(): + return self.__DB_ERR + try: + result = self.__DB_CUR.execute(sql) + self.__DB_CONN.commit() + self.__Close() + return result + except Exception as ex: + return ex + + def ping(self): + try: + self.__DB_CONN.ping() + except Exception as e: + print(e) + return True + + def find(self, sql): + d = self.query(sql) + if d is not None: + if len(d) > 0: + return d[0] + return None + + def query(self, sql): + # 执行SQL语句返回数据集 + if not self.__Conn(): + return self.__DB_ERR + try: + self.__DB_CUR.execute(sql) + result = self.__DB_CUR.fetchall() + # print(result) + # 将元组转换成列表 + # data = map(list, result) + self.__Close() + return result + except Exception as ex: + return ex + + def __Close(self): + # 关闭连接 + self.__DB_CUR.close() + self.__DB_CONN.close() diff --git a/web/misc/nginx/rewrite/EmpireCMS.conf b/web/misc/nginx/rewrite/EmpireCMS.conf new file mode 100755 index 000000000..33cdf5791 --- /dev/null +++ b/web/misc/nginx/rewrite/EmpireCMS.conf @@ -0,0 +1,10 @@ +location / { + rewrite ^([^\.]*)/listinfo-(.+?)-(.+?)\.html$ $1/e/action/ListInfo/index.php?classid=$2&page=$3 last; + rewrite ^([^\.]*)/showinfo-(.+?)-(.+?)-(.+?)\.html$ $1/e/action/ShowInfo.php?classid=$2&id=$3&page=$4 last; + rewrite ^([^\.]*)/infotype-(.+?)-(.+?)\.html$ $1/e/action/InfoType/index.php?ttid=$2&page=$3 last; + rewrite ^([^\.]*)/tags-(.+?)-(.+?)\.html$ $1/e/tags/index.php?tagname=$2&page=$3 last; + rewrite ^([^\.]*)/comment-(.+?)-(.+?)-(.+?)-(.+?)-(.+?)-(.+?)\.html$ $1/e/pl/index\.php\?doaction=$2&classid=$3&id=$4&page=$5&myorder=$6&tempid=$7 last; + if (!-e $request_filename) { + return 404; + } +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/cloudfare_real_ip.conf b/web/misc/nginx/rewrite/cloudfare_real_ip.conf new file mode 100644 index 000000000..cd19beb0e --- /dev/null +++ b/web/misc/nginx/rewrite/cloudfare_real_ip.conf @@ -0,0 +1,43 @@ +location / { + + # https://www.cloudflare.com/zh-cn/ips/ + set_real_ip_from 173.245.48.0/20; + set_real_ip_from 103.21.244.0/22; + set_real_ip_from 103.22.200.0/22; + set_real_ip_from 103.31.4.0/22; + set_real_ip_from 141.101.64.0/18; + set_real_ip_from 108.162.192.0/18; + set_real_ip_from 190.93.240.0/20; + set_real_ip_from 188.114.96.0/20; + set_real_ip_from 197.234.240.0/22; + set_real_ip_from 198.41.128.0/17; + set_real_ip_from 162.158.0.0/15; + set_real_ip_from 104.16.0.0/13; + set_real_ip_from 104.24.0.0/14; + set_real_ip_from 172.64.0.0/13; + set_real_ip_from 131.0.72.0/22; + set_real_ip_from 2400:cb00::/32; + set_real_ip_from 2606:4700::/32; + set_real_ip_from 2803:f800::/32; + set_real_ip_from 2405:b500::/32; + set_real_ip_from 2405:8100::/32; + set_real_ip_from 2a06:98c0::/29; + set_real_ip_from 2c0f:f248::/32; + # use any of the following two + real_ip_header CF-Connecting-IP; + #real_ip_header X-Forwarded-For; + + + index index.html index.php; + if (-f $request_filename/index.html){ + rewrite (.*) $1/index.html break; + } + if (-f $request_filename/index.php){ + rewrite (.*) $1/index.php; + } + if (!-f $request_filename){ + rewrite (.*) /index.php; + } +} + +rewrite /wp-admin$ $scheme://$host$uri/ permanent; diff --git a/web/misc/nginx/rewrite/dedecms.conf b/web/misc/nginx/rewrite/dedecms.conf new file mode 100755 index 000000000..9e1d20206 --- /dev/null +++ b/web/misc/nginx/rewrite/dedecms.conf @@ -0,0 +1,12 @@ +location / { + rewrite ^/list-([0-9]+)\.html$ /plus/list.php?tid=$1 last; + rewrite ^/list-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /plus/list.php?tid=$1&totalresult=$2&PageNo=$3 last; + rewrite ^/view-([0-9]+)-1\.html$ /plus/view.php?arcID=$1 last; + rewrite ^/view-([0-9]+)-([0-9]+)\.html$" /plus/view.php?aid=$1&pageno=$2 last; + rewrite ^/plus/list-([0-9]+)\.html$ /plus/list.php?tid=$1 last; + rewrite ^/plus/list-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /plus/list.php?tid=$1&totalresult=$2&PageNo=$3 last; + rewrite ^/plus/view-([0-9]+)-1\.html$ /plus/view.php?arcID=$1 last; + rewrite ^/plus/view-([0-9]+)-([0-9]+)\.html$ /plus/view.php?aid=$1&pageno=$2 last; + rewrite ^/tags.html$ /tags.php last; + rewrite ^/tag-([0-9]+)-([0-9]+)\.html$ /tags.php?/$1/$2/ last; +} diff --git a/web/misc/nginx/rewrite/discuzx.conf b/web/misc/nginx/rewrite/discuzx.conf new file mode 100755 index 000000000..be93e7de0 --- /dev/null +++ b/web/misc/nginx/rewrite/discuzx.conf @@ -0,0 +1,18 @@ +location / { + rewrite ^([^\.]*)/topic-(.+)\.html$ $1/portal.php?mod=topic&topic=$2 last; + rewrite ^([^\.]*)/article-([0-9]+)-([0-9]+)\.html$ $1/portal.php?mod=view&aid=$2&page=$3 last; + rewrite ^([^\.]*)/forum-(\w+)-([0-9]+)\.html$ $1/forum.php?mod=forumdisplay&fid=$2&page=$3 last; + rewrite ^([^\.]*)/thread-([0-9]+)-([0-9]+)-([0-9]+)\.html$ $1/forum.php?mod=viewthread&tid=$2&extra=page%3D$4&page=$3 last; + rewrite ^([^\.]*)/group-([0-9]+)-([0-9]+)\.html$ $1/forum.php?mod=group&fid=$2&page=$3 last; + rewrite ^([^\.]*)/space-(username|uid)-(.+)\.html$ $1/home.php?mod=space&$2=$3 last; + rewrite ^([^\.]*)/blog-([0-9]+)-([0-9]+)\.html$ $1/home.php?mod=space&uid=$2&do=blog&id=$3 last; + rewrite ^([^\.]*)/(fid|tid)-([0-9]+)\.html$ $1/index.php?action=$2&value=$3 last; + rewrite ^([^\.]*)/([a-z]+[a-z0-9_]*)-([a-z0-9_\-]+)\.html$ $1/plugin.php?id=$2:$3 last; + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php last; + } + + #if (!-e $request_filename) { + # return 404; + #} +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/discuzx2.conf b/web/misc/nginx/rewrite/discuzx2.conf new file mode 100755 index 000000000..f4040718b --- /dev/null +++ b/web/misc/nginx/rewrite/discuzx2.conf @@ -0,0 +1,14 @@ +location /bbs/ { + rewrite ^([^\.]*)/topic-(.+)\.html$ $1/portal.php?mod=topic&topic=$2 last; + rewrite ^([^\.]*)/article-([0-9]+)-([0-9]+)\.html$ $1/portal.php?mod=view&aid=$2&page=$3 last; + rewrite ^([^\.]*)/forum-(\w+)-([0-9]+)\.html$ $1/forum.php?mod=forumdisplay&fid=$2&page=$3 last; + rewrite ^([^\.]*)/thread-([0-9]+)-([0-9]+)-([0-9]+)\.html$ $1/forum.php?mod=viewthread&tid=$2&extra=page%3D$4&page=$3 last; + rewrite ^([^\.]*)/group-([0-9]+)-([0-9]+)\.html$ $1/forum.php?mod=group&fid=$2&page=$3 last; + rewrite ^([^\.]*)/space-(username|uid)-(.+)\.html$ $1/home.php?mod=space&$2=$3 last; + rewrite ^([^\.]*)/blog-([0-9]+)-([0-9]+)\.html$ $1/home.php?mod=space&uid=$2&do=blog&id=$3 last; + rewrite ^([^\.]*)/(fid|tid)-([0-9]+)\.html$ $1/index.php?action=$2&value=$3 last; + rewrite ^([^\.]*)/([a-z]+[a-z0-9_]*)-([a-z0-9_\-]+)\.html$ $1/plugin.php?id=$2:$3 last; + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php last; + } +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/discuzx3.conf b/web/misc/nginx/rewrite/discuzx3.conf new file mode 100755 index 000000000..bfc8ef92c --- /dev/null +++ b/web/misc/nginx/rewrite/discuzx3.conf @@ -0,0 +1,15 @@ +location / { + rewrite ^([^\.]*)/topic-(.+)\.html$ $1/portal.php?mod=topic&topic=$2 last; + rewrite ^([^\.]*)/article-([0-9]+)-([0-9]+)\.html$ $1/portal.php?mod=view&aid=$2&page=$3 last; + rewrite ^([^\.]*)/forum-(\w+)-([0-9]+)\.html$ $1/forum.php?mod=forumdisplay&fid=$2&page=$3 last; + rewrite ^([^\.]*)/thread-([0-9]+)-([0-9]+)-([0-9]+)\.html$ $1/forum.php?mod=viewthread&tid=$2&extra=page%3D$4&page=$3 last; + rewrite ^([^\.]*)/group-([0-9]+)-([0-9]+)\.html$ $1/forum.php?mod=group&fid=$2&page=$3 last; + rewrite ^([^\.]*)/space-(username|uid)-(.+)\.html$ $1/home.php?mod=space&$2=$3 last; + rewrite ^([^\.]*)/blog-([0-9]+)-([0-9]+)\.html$ $1/home.php?mod=space&uid=$2&do=blog&id=$3 last; + rewrite ^([^\.]*)/(fid|tid)-([0-9]+)\.html$ $1/index.php?action=$2&value=$3 last; + rewrite ^([^\.]*)/([a-z]+[a-z0-9_]*)-([a-z0-9_\-]+)\.html$ $1/plugin.php?id=$2:$3 last; + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php last; + } +} + diff --git a/web/misc/nginx/rewrite/doh.conf b/web/misc/nginx/rewrite/doh.conf new file mode 100644 index 000000000..4f7c8462b --- /dev/null +++ b/web/misc/nginx/rewrite/doh.conf @@ -0,0 +1,5 @@ +location /dns-query { + proxy_pass http://127.0.0.1:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/drupal.conf b/web/misc/nginx/rewrite/drupal.conf new file mode 100755 index 000000000..2c4bc7b82 --- /dev/null +++ b/web/misc/nginx/rewrite/drupal.conf @@ -0,0 +1,5 @@ +location / { + if (!-e $request_filename) { + rewrite ^/(.*)$ /index.php?q=$1 last; + } +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/ecshop.conf b/web/misc/nginx/rewrite/ecshop.conf new file mode 100755 index 000000000..109c04b6c --- /dev/null +++ b/web/misc/nginx/rewrite/ecshop.conf @@ -0,0 +1,33 @@ +location / { + if (!-e $request_filename) { + rewrite "^/index\.html" /index.php last; + rewrite "^/category$" /index.php last; + rewrite "^/feed-c([0-9]+)\.xml$" /feed.php?cat=$1 last; + rewrite "^/feed-b([0-9]+)\.xml$" /feed.php?brand=$1 last; + rewrite "^/feed\.xml$" /feed.php last; + rewrite "^/category-([0-9]+)-b([0-9]+)-min([0-9]+)-max([0-9]+)-attr([^-]*)-([0-9]+)-(.+)-([a-zA-Z]+)(.*)\.html$" /category.php?id=$1&brand=$2&price_min=$3&price_max=$4&filter_attr=$5&page=$6&sort=$7&order=$8 last; + rewrite "^/category-([0-9]+)-b([0-9]+)-min([0-9]+)-max([0-9]+)-attr([^-]*)(.*)\.html$" /category.php?id=$1&brand=$2&price_min=$3&price_max=$4&filter_attr=$5 last; + rewrite "^/category-([0-9]+)-b([0-9]+)-([0-9]+)-(.+)-([a-zA-Z]+)(.*)\.html$" /category.php?id=$1&brand=$2&page=$3&sort=$4&order=$5 last; + rewrite "^/category-([0-9]+)-b([0-9]+)-([0-9]+)(.*)\.html$" /category.php?id=$1&brand=$2&page=$3 last; + rewrite "^/category-([0-9]+)-b([0-9]+)(.*)\.html$" /category.php?id=$1&brand=$2 last; + rewrite "^/category-([0-9]+)(.*)\.html$" /category.php?id=$1 last; + rewrite "^/goods-([0-9]+)(.*)\.html" /goods.php?id=$1 last; + rewrite "^/article_cat-([0-9]+)-([0-9]+)-(.+)-([a-zA-Z]+)(.*)\.html$" /article_cat.php?id=$1&page=$2&sort=$3&order=$4 last; + rewrite "^/article_cat-([0-9]+)-([0-9]+)(.*)\.html$" /article_cat.php?id=$1&page=$2 last; + rewrite "^/article_cat-([0-9]+)(.*)\.html$" /article_cat.php?id=$1 last; + rewrite "^/article-([0-9]+)(.*)\.html$" /article.php?id=$1 last; + rewrite "^/brand-([0-9]+)-c([0-9]+)-([0-9]+)-(.+)-([a-zA-Z]+)\.html" /brand.php?id=$1&cat=$2&page=$3&sort=$4&order=$5 last; + rewrite "^/brand-([0-9]+)-c([0-9]+)-([0-9]+)(.*)\.html" /brand.php?id=$1&cat=$2&page=$3 last; + rewrite "^/brand-([0-9]+)-c([0-9]+)(.*)\.html" /brand.php?id=$1&cat=$2 last; + rewrite "^/brand-([0-9]+)(.*)\.html" /brand.php?id=$1 last; + rewrite "^/tag-(.*)\.html" /search.php?keywords=$1 last; + rewrite "^/snatch-([0-9]+)\.html$" /snatch.php?id=$1 last; + rewrite "^/group_buy-([0-9]+)\.html$" /group_buy.php?act=view&id=$1 last; + rewrite "^/auction-([0-9]+)\.html$" /auction.php?act=view&id=$1 last; + rewrite "^/exchange-id([0-9]+)(.*)\.html$" /exchange.php?id=$1&act=view last; + rewrite "^/exchange-([0-9]+)-min([0-9]+)-max([0-9]+)-([0-9]+)-(.+)-([a-zA-Z]+)(.*)\.html$" /exchange.php?cat_id=$1&integral_min=$2&integral_max=$3&page=$4&sort=$5&order=$6 last; + rewrite ^/exchange-([0-9]+)-([0-9]+)-(.+)-([a-zA-Z]+)(.*)\.html$" /exchange.php?cat_id=$1&page=$2&sort=$3&order=$4 last; + rewrite "^/exchange-([0-9]+)-([0-9]+)(.*)\.html$" /exchange.php?cat_id=$1&page=$2 last; + rewrite "^/exchange-([0-9]+)(.*)\.html$" /exchange.php?cat_id=$1 last; + } +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/emlog.conf b/web/misc/nginx/rewrite/emlog.conf new file mode 100755 index 000000000..77f1a17d6 --- /dev/null +++ b/web/misc/nginx/rewrite/emlog.conf @@ -0,0 +1,7 @@ +location / { + index index.php index.html; + if (!-e $request_filename) + { + rewrite ^/(.*)$ /index.php last; + } +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/fastdfs.conf b/web/misc/nginx/rewrite/fastdfs.conf new file mode 100644 index 000000000..1b89c6e01 --- /dev/null +++ b/web/misc/nginx/rewrite/fastdfs.conf @@ -0,0 +1,5 @@ +location ~/group([0-9])/M00 { + # Need to cooperate with it + # https://github.com/mw-plugin/fastdfs + ngx_fastdfs_module; +} diff --git a/web/misc/nginx/rewrite/gunicorn.conf b/web/misc/nginx/rewrite/gunicorn.conf new file mode 100755 index 000000000..80709d083 --- /dev/null +++ b/web/misc/nginx/rewrite/gunicorn.conf @@ -0,0 +1,9 @@ + +add_header Access-Control-Allow-Origin *; +add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; + +location / { + proxy_pass http://127.0.0.1:8000; # 这里是指向 gunicorn host 的服务地址 + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/header_cors.conf b/web/misc/nginx/rewrite/header_cors.conf new file mode 100755 index 000000000..61a875c36 --- /dev/null +++ b/web/misc/nginx/rewrite/header_cors.conf @@ -0,0 +1,15 @@ + +add_header 'Access-Control-Allow-Methods' 'GET,OPTIONS,POST' always; +add_header 'Access-Control-Allow-Credentials' 'true' always; +add_header 'Access-Control-Allow-Origin' $http_origin always; +add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With, Cache-Control' always; + + + +#add_header Access-Control-Allow-Origin *; +#add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; + + +#add_header Access-Control-Allow-Origin *; +#add_header Access-Control-Allow-Methods *; +#add_header Access-Control-Allow-Header *; diff --git a/web/misc/nginx/rewrite/laravel5.conf b/web/misc/nginx/rewrite/laravel5.conf new file mode 100755 index 000000000..d550bbb09 --- /dev/null +++ b/web/misc/nginx/rewrite/laravel5.conf @@ -0,0 +1,3 @@ +location / { + try_files $uri $uri/ /index.php$is_args$query_string; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/mvc.conf b/web/misc/nginx/rewrite/mvc.conf new file mode 100755 index 000000000..44d945a14 --- /dev/null +++ b/web/misc/nginx/rewrite/mvc.conf @@ -0,0 +1,6 @@ +location / { + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php/$1 last; + break; + } +} diff --git a/web/misc/nginx/rewrite/nezha.conf b/web/misc/nginx/rewrite/nezha.conf new file mode 100644 index 000000000..fe3631d7a --- /dev/null +++ b/web/misc/nginx/rewrite/nezha.conf @@ -0,0 +1,15 @@ +#PROXY-START/ +# root /www/server/nezha/dashboard/resource; +location / { + proxy_pass http://127.0.0.1:9527; + proxy_set_header Host $http_host; + proxy_set_header Upgrade $http_upgrade; +} +location ~ ^/(ws|terminal/.+)$ { + proxy_pass http://127.0.0.1:9527; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $http_host; +} +#PROXY-END/ \ No newline at end of file diff --git a/web/misc/nginx/rewrite/obf.conf b/web/misc/nginx/rewrite/obf.conf new file mode 100644 index 000000000..9ba36282e --- /dev/null +++ b/web/misc/nginx/rewrite/obf.conf @@ -0,0 +1,28 @@ +# 在http目录下配置 +# lua_shared_dict obf_cache 64m; + +# 混淆配置 +body_filter_by_lua_block { + local obf = require("resty.obf.obf") + obf.process_response() +} + +location / { + + #set $close_close 'true'; # 关闭 关闭开关 + #set $debug_close 'true'; # 关闭 开始调试 + set $obf_js_mode 'inline'; # 解密模式 link:链接, inline:内链模式 + set $obf_js_url 'https://cdn.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js?v=1'; # 自定义解密js地址,tips: forge + set $obf_timeout 600; # 缓存时间 + set $obf_rand_var 'true'; # 随机变量 + set $obf_rand_extra 'true'; # 随机混淆内容 + set $obf_uint8_b64 'false'; # 是Uint8Array,否base64 + #set $obf_prof 'true'; # 测试时间消耗记录 + set $obf_cache_item_max 0; # 缓存多少 + set $obf_cache_max_bytes 16777216; # 缓存字节大小 + + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php/$1 last; + break; + } +} diff --git a/web/misc/nginx/rewrite/obf_proxy.conf b/web/misc/nginx/rewrite/obf_proxy.conf new file mode 100644 index 000000000..29fbe2dce --- /dev/null +++ b/web/misc/nginx/rewrite/obf_proxy.conf @@ -0,0 +1,38 @@ +# 在http目录下配置 +# lua_shared_dict obf_cache 64m; + +# 混淆配置-代理 +body_filter_by_lua_block { + local obf = require("resty.obf.obf") + obf.process_response() +} + +location / { + proxy_pass http://127.0.0.1:8989; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header Content-Encoding; + #gzip off; + proxy_set_header Accept-Encoding ""; + proxy_set_header If-Modified-Since ""; + proxy_set_header If-None-Match ""; + + #set $close_close 'true'; + #set $debug_close 'true'; + set $obf_js_mode 'inline'; + set $obf_js_url ''; + set $obf_timeout 600; + set $obf_rand_var 'true'; + set $obf_rand_extra 'true'; + set $obf_uint8_b64 'true'; + set $obf_prof 'true'; + set $obf_cache_item_max 0; + set $obf_cache_max_bytes 16777216; + set $obf_kdf_raw 'false'; + + header_filter_by_lua_block { + ngx.header.content_length = nil + } +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/phpcms.conf b/web/misc/nginx/rewrite/phpcms.conf new file mode 100755 index 000000000..a6e0df346 --- /dev/null +++ b/web/misc/nginx/rewrite/phpcms.conf @@ -0,0 +1,9 @@ +location / { + ###以下为PHPCMS 伪静态化rewrite法则 + rewrite ^(.*)show-([0-9]+)-([0-9]+)\.html$ $1/show.php?itemid=$2&page=$3; + rewrite ^(.*)list-([0-9]+)-([0-9]+)\.html$ $1/list.php?catid=$2&page=$3; + rewrite ^(.*)show-([0-9]+)\.html$ $1/show.php?specialid=$2; + ####以下为PHPWind 伪静态化rewrite法则 + rewrite ^(.*)-htm-(.*)$ $1.php?$2 last; + rewrite ^(.*)/simple/([a-z0-9\_]+\.html)$ $1/simple/index.php?$2 last; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/phpwind.conf b/web/misc/nginx/rewrite/phpwind.conf new file mode 100755 index 000000000..6dba1e34e --- /dev/null +++ b/web/misc/nginx/rewrite/phpwind.conf @@ -0,0 +1,4 @@ +location / { + rewrite ^(.*)-htm-(.*)$ $1.php?$2 last; + rewrite ^(.*)/simple/([a-z0-9\_]+\.html)$ $1/simple/index.php?$2 last; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/proxy.conf b/web/misc/nginx/rewrite/proxy.conf new file mode 100644 index 000000000..8c34c461c --- /dev/null +++ b/web/misc/nginx/rewrite/proxy.conf @@ -0,0 +1,7 @@ + +location / { + proxy_pass http://localhost:11334/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +} diff --git a/web/misc/nginx/rewrite/proxydg.conf b/web/misc/nginx/rewrite/proxydg.conf new file mode 100644 index 000000000..1a2c1e319 --- /dev/null +++ b/web/misc/nginx/rewrite/proxydg.conf @@ -0,0 +1,8 @@ +location / { + proxy_redirect off; + proxy_pass http://127.0.0.1:12345; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/sablog.conf b/web/misc/nginx/rewrite/sablog.conf new file mode 100755 index 000000000..8009724ef --- /dev/null +++ b/web/misc/nginx/rewrite/sablog.conf @@ -0,0 +1,16 @@ +location / { + rewrite "^/date/([0-9]{6})/?([0-9]+)?/?$" /index.php?action=article&setdate=$1&page=$2 last; + rewrite ^/page/([0-9]+)?/?$ /index.php?action=article&page=$1 last; + rewrite ^/category/([0-9]+)/?([0-9]+)?/?$ /index.php?action=article&cid=$1&page=$2 last; + rewrite ^/category/([^/]+)/?([0-9]+)?/?$ /index.php?action=article&curl=$1&page=$2 last; + rewrite ^/(archives|search|article|links)/?$ /index.php?action=$1 last; + rewrite ^/(comments|tagslist|trackbacks|article)/?([0-9]+)?/?$ /index.php?action=$1&page=$2 last; + rewrite ^/tag/([^/]+)/?([0-9]+)?/?$ /index.php?action=article&item=$1&page=$2 last; + rewrite ^/archives/([0-9]+)/?([0-9]+)?/?$ /index.php?action=show&id=$1&page=$2 last; + rewrite ^/rss/([0-9]+)?/?$ /rss.php?cid=$1 last; + rewrite ^/rss/([^/]+)/?$ /rss.php?url=$1 last; + rewrite ^/uid/([0-9]+)/?([0-9]+)?/?$ /index.php?action=article&uid=$1&page=$2 last; + rewrite ^/user/([^/]+)/?([0-9]+)?/?$ /index.php?action=article&user=$1&page=$2 last; + rewrite sitemap.xml sitemap.php last; + rewrite ^(.*)/([0-9a-zA-Z\-\_]+)/?([0-9]+)?/?$ $1/index.php?action=show&alias=$2&page=$3 last; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/seacms.conf b/web/misc/nginx/rewrite/seacms.conf new file mode 100755 index 000000000..0dc6f3612 --- /dev/null +++ b/web/misc/nginx/rewrite/seacms.conf @@ -0,0 +1,11 @@ +location / { + rewrite ^/frim/index(.+?)\.html$ /list/index.php?$1 last; + rewrite ^/movie/index(.+?)\.html$ /detail/index.php?$1 last; + rewrite ^/play/([0-9]+)-([0-9]+)-([0-9]+)\.html$ /video/index.php?$1-$2-$3 last; + rewrite ^/topic/index(.+?)\.html$ /topic/index.php?$1 last; + rewrite ^/topiclist/index(.+?).html$ /topiclist/index.php?$1 last; + rewrite ^/index\.html$ index.php permanent; + rewrite ^/news\.html$ news/ permanent; + rewrite ^/part/index(.+?)\.html$ /articlelist/index.php?$1 last; + rewrite ^/article/index(.+?)\.html$ /article/index.php?$1 last; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/shopex.conf b/web/misc/nginx/rewrite/shopex.conf new file mode 100755 index 000000000..afc87532d --- /dev/null +++ b/web/misc/nginx/rewrite/shopex.conf @@ -0,0 +1,5 @@ +location / { + if (!-e $request_filename) { + rewrite ^/(.+\.(html|xml|json|htm|php|jsp|asp|shtml))$ /index.php?$1 last; + } +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/sub.conf.tpl b/web/misc/nginx/rewrite/sub.conf.tpl new file mode 100644 index 000000000..5996e01d4 --- /dev/null +++ b/web/misc/nginx/rewrite/sub.conf.tpl @@ -0,0 +1,16 @@ +location / { + set $replace 'tmplate_replace'; + set $spider_request '0'; + + if ($http_user_agent ~* '(baiduspider|360sipder|Sogou Orion spider|Sogou News Spider|Sogou blog|Sogou spider2|Sogou inst spider|Sogou web spider|Sogou spider|trendiction|Yahoo|semrush|Toutiao|Google|qihoobot|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|MSNBot|ia_archiver|Tomato Bot)') { + set $spider_request '1'; + } + + if ($spider_request = '0' ) { + set $replace 'tmplate_replace'; + } + + sub_filter 'tmplate_replace' $replace; + sub_filter_once on; + sub_filter_types *; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/thinkphp.conf b/web/misc/nginx/rewrite/thinkphp.conf new file mode 100755 index 000000000..b8c527532 --- /dev/null +++ b/web/misc/nginx/rewrite/thinkphp.conf @@ -0,0 +1,5 @@ +location / { + if (!-e $request_filename){ + rewrite ^(.*)$ /index.php?s=$1 last; break; + } +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/typecho.conf b/web/misc/nginx/rewrite/typecho.conf new file mode 100755 index 000000000..dcc7c3f1a --- /dev/null +++ b/web/misc/nginx/rewrite/typecho.conf @@ -0,0 +1,13 @@ +location / { + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php$1 last; + } +} + +# 不需要,可以删除 +location /typecho/ { + if (!-e $request_filename) { + rewrite ^(.*)$ /typecho/index.php$1 last; + } +} + diff --git a/web/misc/nginx/rewrite/v2ray.conf b/web/misc/nginx/rewrite/v2ray.conf new file mode 100644 index 000000000..641313279 --- /dev/null +++ b/web/misc/nginx/rewrite/v2ray.conf @@ -0,0 +1,15 @@ +location /{ + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php/$1 last; + break; + } +} + +location /ws { + proxy_redirect off; + proxy_pass http://127.0.0.1:34861; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/walle.conf b/web/misc/nginx/rewrite/walle.conf new file mode 100755 index 000000000..c88ba3d7c --- /dev/null +++ b/web/misc/nginx/rewrite/walle.conf @@ -0,0 +1,34 @@ + +#upstream webservers { +# server 0.0.0.0:5000 weight=1; +#} + +location / { + try_files $uri $uri/ /index.html; + add_header access-control-allow-origin *; +} + +location ^~ /api/ { + add_header access-control-allow-origin *; + proxy_pass http://webservers; + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Origin $host:$server_port; + proxy_set_header Referer $host:$server_port; +} + +location ^~ /socket.io/ { + add_header access-control-allow-origin *; + proxy_pass http://webservers; + proxy_set_header X-Forwarded-Host $host:$server_port; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Origin $host:$server_port; + proxy_set_header Referer $host:$server_port; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + + # WebScoket Support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/whmcs.conf b/web/misc/nginx/rewrite/whmcs.conf new file mode 100755 index 000000000..2cdd0e710 --- /dev/null +++ b/web/misc/nginx/rewrite/whmcs.conf @@ -0,0 +1,16 @@ +location / { + + if (!-e $request_filename) { + rewrite ^/contact/$ /./contact.php last; + rewrite ^/status$ /./pages.php?cate=$1&page=status last; + rewrite ^/act$ /./pages.php?cate=$1&page=actindex last; + rewrite ^/(\w+)/(\w+)/$ /./pages.php?cate=$1&page=$2 last; + rewrite ^/cart/(\w+)$ /./cart.php?a=$1 last; + rewrite ^/console$ /./clientarea.php last; + rewrite ^/sitemap.xml$ /./sitemap.php last; + rewrite ^/console/(\w+)$ /./clientarea.php?action=services&group=$1 last; + rewrite ^/console/ec/([0-9]+)$ /./clientarea.php?action=productdetails&id=$1 last; + rewrite ^/console/ec/(\w+)$ /./clientarea.php?action=services&group=ec&page=$1 last; + rewrite ^ /index.php last; + } +} diff --git a/web/misc/nginx/rewrite/wmcms.conf b/web/misc/nginx/rewrite/wmcms.conf new file mode 100755 index 000000000..386b75085 --- /dev/null +++ b/web/misc/nginx/rewrite/wmcms.conf @@ -0,0 +1,16 @@ +location / { + if (!-e $request_filename) { + rewrite ^/index.html$ /index.php last; + rewrite ^/top/index.html$ /module/novel/topindex.php last; + rewrite ^/(\d*)/list/(\d*).html$ /module/novel/type.php?tid=$1&page=$2 last; + rewrite ^/(.*?)/(.*?)/info.html$ /module/novel/info.php?tid=$1&nid=$2 last; + rewrite ^/(.*?)/(.*?)/read/(\d*).html$ /module/novel/read.php?tid=$1&nid=$2&cid=$3 last; + rewrite ^/(.*?)/(.*?)/menu/(\d*).html$ /module/novel/menu.php?tid=$1&nid=$2&page=$3 last; + rewrite ^/search/(\d*)/(.*?)/(\d*).html$ /module/novel/search.php?type=$1&key=$2&page=$3 last; + + rewrite ^/(\d*)/list/(\d*)_(\d*)_(\d*)_(\d*)_(\d*)_(\d*)_(\d*)_(\d*).html$ /module/novel/type.php?tid=$1&page=$9&process=$2&word=$3&chapter=$4©=$5&cost=$6&letter=$7&order=$8 last; + + rewrite ^/top/list/(\d*)/(\d*)/(\d*).html$ /module/novel/toplist.php?tid=$1&dtype=$2&page=$3 last; + break; + } +} \ No newline at end of file diff --git a/web/misc/nginx/rewrite/wordpress.conf b/web/misc/nginx/rewrite/wordpress.conf new file mode 100755 index 000000000..dfa6381d4 --- /dev/null +++ b/web/misc/nginx/rewrite/wordpress.conf @@ -0,0 +1,14 @@ +location / { + index index.html index.php; + if (-f $request_filename/index.html){ + rewrite (.*) $1/index.html break; + } + if (-f $request_filename/index.php){ + rewrite (.*) $1/index.php; + } + if (!-f $request_filename){ + rewrite (.*) /index.php; + } +} + +rewrite /wp-admin$ $scheme://$host$uri/ permanent; \ No newline at end of file diff --git a/web/misc/nginx/rewrite/zblog.conf b/web/misc/nginx/rewrite/zblog.conf new file mode 100755 index 000000000..37d410519 --- /dev/null +++ b/web/misc/nginx/rewrite/zblog.conf @@ -0,0 +1,11 @@ +location / { + if (-f $request_filename/index.html){ + rewrite (.*) $1/index.html break; + } + if (-f $request_filename/index.php){ + rewrite (.*) $1/index.php; + } + if (!-f $request_filename){ + rewrite (.*) /index.php; + } +} \ No newline at end of file diff --git a/web/misc/nginx/tpl/nginx.conf b/web/misc/nginx/tpl/nginx.conf new file mode 100755 index 000000000..5da306f9d --- /dev/null +++ b/web/misc/nginx/tpl/nginx.conf @@ -0,0 +1,53 @@ +server +{ + # reuseport 只能在一个server出现一次 + listen {$PORT}; + listen [::]:{$PORT}; + server_name {$SERVER_NAME}; + index index.php index.html index.htm default.php default.htm default.html; + root {$ROOT_DIR}; + + #SSL-START + #error_page 404/404.html; + #SSL-END + + #301-START + + #PROXY-START + + #ERROR-PAGE-START + #error_page 404 /404.html; + #error_page 502 /502.html; + #ERROR-PAGE-END + + #PHP-INFO-START + include {$PHP_DIR}/conf/enable-php-{$PHPVER}.conf; + #PHP-INFO-END + + #REWRITE-START + include {$OR_REWRITE}/{$SERVER_NAME}.conf; + #REWRITE-END + + #禁止访问的文件或目录 + location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md) + { + return 404; + } + + #一键申请SSL证书验证目录相关设置 + location ~ \.well-known{ + allow all; + } + + location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|js|css|ttf|woff2)$ + { + expires 30d; + access_log /dev/null; + if ($invalid_referer){ + return 404; + } + } + + access_log {$LOGPATH}/{$SERVER_NAME}.log main; + error_log {$LOGPATH}/{$SERVER_NAME}.error.log; +} \ No newline at end of file diff --git a/web/misc/nginx/tpl/nginx_dirbind.conf b/web/misc/nginx/tpl/nginx_dirbind.conf new file mode 100755 index 000000000..129a7ddd1 --- /dev/null +++ b/web/misc/nginx/tpl/nginx_dirbind.conf @@ -0,0 +1,48 @@ + +#BINDING-{$DIRBIND}-START +server +{ + listen {$PORT}; + server_name {$DIRBIND}; + index index.php index.html index.htm default.php default.htm default.html; + root {$ROOT_DIR}; + + #SSL-START + #error_page 404/404.html; + #SSL-END + + #ERROR-PAGE-START + #error_page 404 /404.html; + #error_page 502 /502.html; + #ERROR-PAGE-END + + #PHP-INFO-START + include {$PHP_DIR}/conf/enable-php-{$PHPVER}.conf; + #PHP-INFO-END + + #REWRITE-START + include {$OR_REWRITE}/{$SERVER_MAIN}.conf; + #REWRITE-END + + #禁止访问的文件或目录 + location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md) + { + return 404; + } + + #一键申请SSL证书验证目录相关设置 + location ~ \.well-known{ + allow all; + } + + location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf|js|css)$ + { + error_log /dev/null; + access_log /dev/null; + expires 30d; + } + + access_log {$LOGPATH}/{$SERVER_MAIN}_{$DIRBIND}.log; + error_log {$LOGPATH}/{$SERVER_MAIN}_{$DIRBIND}.error.log; +} +#BINDING-{$DIRBIND}-END \ No newline at end of file diff --git a/web/misc/nginx/tpl/nginx_panel.conf b/web/misc/nginx/tpl/nginx_panel.conf new file mode 100755 index 000000000..c487094fd --- /dev/null +++ b/web/misc/nginx/tpl/nginx_panel.conf @@ -0,0 +1,23 @@ +server +{ + listen {$PORT}; + listen [::]:{$PORT}; + + server_name {$SERVER_NAME}; + index index.php index.html index.htm default.php default.htm default.html; + + #SSL-START + #error_page 404/404.html; + #SSL-END + + #PROXY-START + location ^~ / { + proxy_pass http://0.0.0.0:{$PANAL_PORT}/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + } + #PROXY-END + + error_log {$LOGPATH}/{$SERVER_NAME}.error.log; +} \ No newline at end of file diff --git a/web/misc/test/api/mw_api.php b/web/misc/test/api/mw_api.php new file mode 100755 index 000000000..76f5db522 --- /dev/null +++ b/web/misc/test/api/mw_api.php @@ -0,0 +1,87 @@ +MW_PANEL = $mw_panel; + } + + if ($app_id) { + $this->MW_APP_ID = $app_id; + } + + if ($app_secret) { + $this->MW_APP_SERECT = $app_secret; + } + } + + /** + * 发起POST请求 + * @param String $url 目标网填,带http:// + * @param Array|String $data 欲提交的数据 + * @return string + */ + private function httpPost($url, $data, $timeout = 60) { + + $ch = curl_init(); + // 设置头部信息 + $headers = [ + 'app-id: ' . $this->MW_APP_ID, + 'app-secret: ' . $this->MW_APP_SERECT, + ]; + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file); + curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + $output = curl_exec($ch); + curl_close($ch); + return $output; + } + + public function panel($endpoint, $data) { + $url = $this->MW_PANEL . $endpoint; + return $this->httpPost($url, $data); + } + + //示例取面板日志 + public function getLogsList() { + $post_data['p'] = '1'; + $post_data['limit'] = 10; + + //请求面板接口 + $data = $this->panel('/logs/get_log_list', $post_data); + + //解析JSON数据 + // $data = json_decode($result, true); + return $data; + } + +} + +//实例化对象 +$api = new mwApi(); +//获取面板日志 +$rdata = $api->getLogsList(); + +// var_dump($rdata); +//输出JSON数据到浏览器 +echo json_encode($rdata); + +?> \ No newline at end of file diff --git a/web/misc/test/api/mw_api.py b/web/misc/test/api/mw_api.py new file mode 100755 index 000000000..b78a5e073 --- /dev/null +++ b/web/misc/test/api/mw_api.py @@ -0,0 +1,57 @@ +# coding: utf-8 +# +----------------------------------------------------------------------------------- +# | MW Linux面板 +# +----------------------------------------------------------------------------------- +# | Copyright (c) 2015-2099 MW(http://github.com/midoks/mdserver) All rights reserved. +# +----------------------------------------------------------------------------------- +# | Author: midoks +# +----------------------------------------------------------------------------------- + +#------------------------------ +# API-Demo of Python +#------------------------------ +import time +import hashlib +import sys +import os +import json + + +class mwApi: + __MW_PANEL = 'http://154.12.53.90:51377/' + __MW_APP_ID = 'yhYkxGssPD' + __MW_APP_SERECT = 'ErmBdr563eJ5GMM5sWbc' + + # 如果希望多台面板,可以在实例化对象时,将面板地址与密钥传入 + def __init__(self, panel_url=None, app_id=None, app_serect=None): + if panel_url: + self.__MW_PANEL = panel_url + self.__MW_APP_ID = app_id + self.__MW_APP_SERECT = app_serect + + def post(self, endpoint, request_data): + import requests + url = self.__MW_PANEL + endpoint + post_data = requests.post(url, data=request_data, headers={ + 'app-id':self.__MW_APP_ID, + 'app-secret':self.__MW_APP_SERECT + }) + try: + return post_data.json() + except Exception as e: + return post_data.text + # 取面板日志 + def getLogs(self): + result = self.post('/task/count',{'limit':10,'p':1}) + return result + + +if __name__ == '__main__': + # 实例化MW-API对象 + api = mwApi() + + # 调用get_logs方法 + rdata = api.getLogs() + + # 打印响应数据 + print(rdata) diff --git a/web/readme.md b/web/readme.md new file mode 100644 index 000000000..25d6030be --- /dev/null +++ b/web/readme.md @@ -0,0 +1,9 @@ + +- 常用开发命令 + +``` +gunicorn -b :7201 -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 app:app +gunicorn -b :7201 -w 1 app:app +gunicorn -c setting.py app:app + +``` \ No newline at end of file diff --git a/web/setting.py b/web/setting.py new file mode 100755 index 000000000..b79719450 --- /dev/null +++ b/web/setting.py @@ -0,0 +1,93 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 配置文件 +# --------------------------------------------------------------------------------- + +from gevent import monkey +monkey.patch_all() + +import time +import sys +import random +import os + +# 初始化db +from admin import setup +setup.init() + +import core.mw as mw +import utils.system as system +import thisdb + +cpu_info = system.getCpuInfo() +workers = cpu_info[1] +# workers = 1 + +panel_dir = mw.getPanelDir() +log_dir = mw.getMWLogs() +if not os.path.exists(log_dir): + os.mkdir(log_dir) + +data_dir = panel_dir+'/data' +if not os.path.exists(data_dir): + os.mkdir(data_dir) + +# default port +panel_port = '7200' +from utils.firewall import Firewall as MwFirewall +default_port_file = panel_dir+'/data/port.pl' +if os.path.exists(default_port_file): + panel_port = mw.readFile(default_port_file) + panel_port.strip() + MwFirewall.instance().addAcceptPort(panel_port,'PANEL端口', 'port') +else: + panel_port = str(random.randint(10000, 65530)) + MwFirewall.instance().addPanelPort(panel_port) + mw.writeFile(default_port_file, panel_port) + +bind = [] +default_ipv6_file = panel_dir+'/data/ipv6.pl' +if os.path.exists(default_ipv6_file): + bind.append('[0:0:0:0:0:0:0:0]:%s' % panel_port) +else: + bind.append('0.0.0.0:%s' % panel_port) + +panel_ssl_data = thisdb.getOptionByJson('panel_ssl', default={'open':False}) +if panel_ssl_data['open']: + choose = panel_ssl_data['choose'] + if mw.inArray(['local','nginx'],choose): + panel_cert = panel_dir+'/ssl/'+choose+'/cert.pem' + panel_private = panel_dir+'/ssl/'+choose+'/private.pem' + if os.path.exists(panel_cert) and os.path.exists(panel_private): + certfile = panel_cert + keyfile = panel_private + ciphers = 'TLSv1 TLSv1.1 TLSv1.2 TLSv1.3' + ssl_version = 2 + +if workers > 2: + workers = 2 + +threads = workers * 2 +backlog = 512 +reload = False +daemon = True +# # worker_class = 'geventwebsocket.gunicorn.workers.GeventWebSocketWorker' +worker_class = 'gevent' +timeout = 600 +keepalive = 60 +preload_app = False +capture_output = True +access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"' +loglevel = 'info' +errorlog = log_dir + '/panel_error.log' +accesslog = log_dir + '/panel.log' +pidfile = log_dir + '/panel.pid' diff --git a/web/static/app/app.js b/web/static/app/app.js new file mode 100644 index 000000000..381d12383 --- /dev/null +++ b/web/static/app/app.js @@ -0,0 +1,151 @@ +function deleteApp(id) { + layer.confirm('您确定要删除吗?', { title: '删除应用', closeBtn: 2, icon: 13, cancel: function () {} }, function () { + $.post('/setting/delete_app', { 'id': id }, function (rdata) { + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + getAppList(); + } + }, 'json'); + }); +} + +function toggleAppstatus(id) { + $.post('/setting/toggle_app_status', { id: id }, function (rdata) { + showMsg(rdata.msg, function () { + if (rdata.status) { + getAppList(); + } + }, { icon: rdata.status ? 1 : 2 }, 2000); + }, 'json'); +} + +function getAppList(page) { + if (typeof (page) == 'undefined') { + page = 1; + } + + $.post('/setting/get_app_list', { page: page }, function (rdata) { + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + var row = rdata.data[i]; + + tbody += ''; + tbody += '' + row['app_id'] + ''; + tbody += '' + row['app_secret'] + ''; + tbody += '' + row['white_list'] + ''; + + if (row['status'] == 1) { + tbody += '已开启'; + } else { + tbody += '已关闭'; + } + + tbody += '' + row['add_time'] + ''; + tbody += ''; + tbody += '删除'; + tbody += ''; + tbody += ''; + } + + $('#app_list_body tbody').html(tbody); + $('#app_list_body .page').html(rdata.page); + }, 'json'); +} + +function addApp() { + layer.open({ + area: '570px', + title: '添加应用', + shift: 0, + type: 1, + content: '
                                      \ +
                                      \ + 应用ID\ +
                                      \ + \ + \ +
                                      \ +
                                      \ +
                                      \ + 应用密钥\ +
                                      \ + \ + \ +
                                      \ +
                                      \ +
                                      \ + IP白名单
                                      (每行1个)
                                      \ +
                                      \ +
                                      \ +
                                      \ + \ +
                                      \ +
                                      \ +
                                        \ +
                                      • 开启API后,必需在IP白名单列表中的IP才能访问面板API接口
                                      • \ +
                                      • 请谨慎在生产环境开启,这可能增加服务器安全风险;
                                      • \ +
                                      \ +
                                      ', + success: function (obj, cur_layer) { + $('input[name="app_id"]').val(getRandomString(10)); + $('input[name="app_secret"]').val(getRandomString(20)); + + $('.app_id').click(function () { + $('input[name="app_id"]').val(getRandomString(10)); + }); + + $('.app_secret').click(function () { + $('input[name="app_secret"]').val(getRandomString(20)); + }); + + $('.save_app_data').click(function () { + var app_id = $('input[name="app_id"]').val(); + var app_secret = $('input[name="app_secret"]').val(); + var limit_addr = $('textarea[name="api_limit_addr"]').val(); + $.post('/setting/add_app', { 'app_id': app_id, 'app_secret': app_secret, 'limit_addr': limit_addr }, function (rdata) { + showMsg(rdata.msg, function () { + if (rdata.status) { + getAppList(); + layer.close(cur_layer); + } + }, { icon: rdata.status ? 1 : 2 }, 2000); + }, 'json'); + }); + } + }); +} + +function appPage() { + layer.open({ + area: ['900px', '380px'], + title: 'APP应用管理', + closeBtn: 1, + shift: 0, + type: 1, + content: "", + success: function () { + getAppList(); + $('.app_add').click(function () { + addApp(); + }); + } + }); +} diff --git a/web/static/app/config.js b/web/static/app/config.js new file mode 100755 index 000000000..6a3f3b22f --- /dev/null +++ b/web/static/app/config.js @@ -0,0 +1,1883 @@ +/** op **/ +// $(".set-submit").click(function(){ +// var data = $("#set_config").serialize(); +// layer.msg('正在保存配置...',{icon:16,time:0,shade: [0.3, '#000']}); +// $.post('/config/set',data,function(rdata){ +// layer.closeAll(); +// layer.msg(rdata.msg,{icon:rdata.status?1:2}); +// if(rdata.status){ +// setTimeout(function(){ +// window.location.href = ((window.location.protocol.indexOf('https') != -1)?'https://':'http://') + rdata.data.host + window.location.pathname; +// },2500); +// } +// },'json'); +// }); + +$('input[name="webname"]').change(function(){ + var webname = $(this).val(); + $('.btn_webname').removeAttr('disabled'); + $('.btn_webname').unbind().click(function(){ + $.post('/setting/set_webname','webname='+webname, function(rdata){ + showMsg(rdata.msg,function(){window.location.reload();},{icon:rdata.status?1:2},2000); + },'json'); + }); +}); + +$('input[name="footer_text"]').change(function(){ + var footerText = $(this).val(); + $('.btn_footer_text').removeAttr('disabled'); + $('.btn_footer_text').unbind().click(function(){ + $.post('/setting/set_footer_text','footer_text='+encodeURIComponent(footerText), function(rdata){ + showMsg(rdata.msg,function(){window.location.reload();},{icon:rdata.status?1:2},2000); + },'json'); + }); +}); + + +$('input[name="host_ip"]').change(function(){ + var host_ip = $(this).val(); + $('.btn_host_ip').removeAttr('disabled'); + $('.btn_host_ip').unbind().click(function(){ + $.post('/setting/set_ip','host_ip='+host_ip, function(rdata){ + showMsg(rdata.msg,function(){window.location.reload();},{icon:rdata.status?1:2},2000); + },'json'); + }); +}); + +$('input[name="port"]').change(function(){ + var port = $(this).val(); + var old_port = $(this).data('port'); + $('.btn_port').removeAttr('disabled'); + $('.btn_port').unbind().click(function(){ + $.post('/setting/set_port','port='+port, function(rdata){ + showMsg(rdata.msg,function(){ + window.location.href = window.location.href.replace(old_port,port); + // window.location.reload(); + },{icon:rdata.status?1:2},5000); + },'json'); + }); +}); + +$('input[name="sites_path"]').change(function(){ + var sites_path = $(this).val(); + $('.btn_sites_path').removeAttr('disabled'); + $('.btn_sites_path').unbind().click(function(){ + $.post('/setting/set_www_dir','sites_path='+sites_path, function(rdata){ + showMsg(rdata.msg,function(){window.location.reload();},{icon:rdata.status?1:2},2000); + },'json'); + }); +}); + + +$('input[name="backup_path"]').change(function(){ + var backup_path = $(this).val(); + $('.btn_backup_path').removeAttr('disabled'); + $('.btn_backup_path').unbind().click(function(){ + $.post('/setting/set_backup_dir','backup_path='+backup_path, function(rdata){ + showMsg(rdata.msg,function(){window.location.reload();},{icon:rdata.status?1:2},2000); + },'json'); + }); +}); + + +$('input[name="bind_domain"]').change(function(){ + var domain = $(this).val(); + $('.btn_bind_domain').removeAttr('disabled'); + $('.btn_bind_domain').unbind().click(function(){ + $.post('/setting/set_panel_domain','domain='+domain, function(rdata){ + showMsg(rdata.msg,function(){ + window.location.href = rdata.data; + },{icon:rdata.status?1:2},5000); + },'json'); + }); +}); + +$('input[name="bind_ssl"]').click(function(){ + var panel_ssl = $(this).prop("checked"); + $(this).prop("checked",!panel_ssl); + + //开启证书 + if (panel_ssl){ + // + layer.open({ + type:1, + closeBtn: 1, + title:"开启SSL证书", + area: ['600px','440px'], + btn: ["开启SSL证书访问"], + maxmin:false, + shadeClose: true, + content: '
                                      \ +
                                      \ +

                                      【开启SSL证书】保护面板访问安全

                                      \ +
                                      \ +
                                        \ +
                                      • 自签证书访问步骤:
                                      • \ +
                                      • 1. 部署SSL证书
                                      • \ +
                                      • 2. 浏览器地址栏修改为https://访问
                                      • \ +
                                      • 3. 如提醒风险(正常现象)点击【高级】或【详情】
                                      • \ +
                                      • 4.【继续访问】或【接收风险并继续】
                                      • \ +
                                      \ +
                                      \ +
                                      \ + 类型\ +
                                      \ + \ +
                                      \ +
                                      \ +
                                        \ +
                                      • 开启后导致面板不能访问,可以点击查看
                                      • \ +
                                      • 自签证书不被浏览器信任,显示不安全是正常现象
                                      • \ +
                                      \ +
                                      \ +
                                      ', + yes: function(){ + + var cert_type = $('select[name=cert_type]').val(); + $.post('/setting/set_panel_local_ssl',{'cert_type':cert_type}, function(rdata){ + // console.log(rdata); + var to_https = window.location.href.replace('http','https'); + showMsg(rdata.msg,function(){ + if (rdata.status){ + window.location.href = to_https; + } + },{icon:rdata.status?1:2},5000); + },'json'); + + } + }); + } else { + //关闭SSL + layer.open({ + type:1, + closeBtn: 1, + title:"关闭SSL证书", + area: ['480px','280px'], + btn: ["确定","取消"], + shadeClose: true, + content: '
                                      \ +
                                      \ +
                                      关闭SSL极易被抓包攻击导致账号密码泄露,请勿关闭
                                      \ +
                                      \ +
                                      \ +
                                      请手动输入【我要关闭】,完成验证
                                      \ + \ +
                                      \ +
                                      ', + yes: function(index){ + var val = $('#prompt_input_box').val(); + if (val != '我要关闭'){ + layer.msg("关闭SSL失败!"); + return; + } + + $.post('/setting/close_panel_ssl',{}, function(rdata){ + var to_http = window.location.href.replace('https','http'); + showMsg(rdata.msg,function(){ + if (rdata.status){ + window.location.href = to_http; + } + },{icon:rdata.status?1:2},5000); + },'json'); + } + }); + } + +}); + +/** op **/ + + +// VIP -- start +function setVipInfo(){ + layer.open({ + type: 1, + area: "400px", + title: 'VIP登录', + closeBtn: 1, + shift: 5, + btn:["登录","关闭"], + shadeClose: false, + content: "
                                      \ +
                                      \ + 用户名\ +
                                      \ +
                                      \ +
                                      \ + 密码\ +
                                      \ +
                                      \ +
                                      ", + yes:function(index){ + var pdata = {}; + + pdata['username'] = $('input[name="username"]').val(); + pdata['password'] = $('input[name="password"]').val(); + + if (pdata['username'] == ''){ + layer.msg('用户名不能为空!', {icon:2}); + return false; + } + + if (pdata['password'] == ''){ + layer.msg('密码不能为空!', {icon:2}); + return false; + } + + $.post('/vip/login',{'username':pdata['username'], 'password':pdata['password']},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + } + },{icon:rdata.status?1:2},2000); + },'json'); + }, + }); +} +// VIP -- end + + +//关闭面板 +function closePanel(){ +layer.confirm('关闭面板会导致您无法访问面板 ,您真的要关闭PowerLinux吗?',{title:'关闭面板',closeBtn:2,icon:13,cancel:function(){ + $("#closePl").prop("checked",false); + }}, function() { + $.post('/setting/close_panel','',function(rdata){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + setTimeout(function(){ + window.location.reload(); + },1000); + },'json'); + },function(){ + $("#closePl").prop("checked",false); + }); +} + +//开发模式 +function debugMode(){ + var loadT = layer.msg('正在发送请求,请稍候...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/setting/open_debug', {}, function (rdata) { + layer.close(loadT); + showMsg(rdata.msg, function(){ + window.location.reload(); + } ,{icon:rdata.status?1:2}, 1000); + },'json'); +} + + +function modifyAuthPath() { + var auth_path = $("#admin_path").val(); + layer.open({ + type: 1, + area: "500px", + title: "修改安全入口", + closeBtn: 1, + shift: 5, + btn:['提交','关闭', '随机生成'], + shadeClose: false, + content: '
                                      \ +
                                      \ + 入口地址\ +
                                      \ + \ +
                                      \ +
                                      \ +
                                      ', + yes:function(index){ + var auth_path = $("input[name='auth_path_set']").val(); + if (auth_path == '/' || auth_path == ''){ + layer.confirm('警告,关闭安全入口等于直接暴露你的后台地址在外网,十分危险, 您真的要这样更改吗?',{title:'安全入口修改',closeBtn:1,icon:13, + cancel:function(){ + }}, function() { + var loadT = layer.msg(lan.config.config_save, { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/setting/set_admin_path', { admin_path: auth_path }, function (rdata) { + showMsg(rdata.msg, function(){ + layer.close(index); + layer.close(loadT); + location.reload(); + },{ icon: rdata.status ? 1 : 2 }, 2000); + },'json'); + }); + return; + } else { + var loadT = layer.msg(lan.config.config_save, { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/setting/set_admin_path', { admin_path: auth_path }, function (rdata) { + showMsg(rdata.msg, function(){ + layer.close(index); + layer.close(loadT); + location.reload(); + },{ icon: rdata.status ? 1 : 2 }, 2000); + },'json'); + } + }, + btn3:function(){ + var rand_str = getRandomString(8); + $("input[name='auth_path_set']").val('/'+rand_str); + return false; + } + }); +} + +function setPassword() { + layer.open({ + type: 1, + area: ["350px",'auto'], + title: '修改密码', + closeBtn: 1, + shift: 5, + shadeClose: false, + btn:["修改","关闭","随机"], + content: "
                                      \ +
                                      \ + 密码\ +
                                      \ +
                                      \ +
                                      \ + 重复\ +
                                      \ +
                                      \ +
                                      ", + yes:function(){ + var p1 = $("#p1").val(); + var p2 = $("#p2").val(); + if(p1 == "" || p1.length < 8) { + layer.msg('面板密码不能少于8位!', {icon: 2}); + return + } + + //准备弱口令匹配元素 + var checks = ['admin888','123123123','12345678','45678910','87654321','asdfghjkl','password','qwerqwer']; + pchecks = 'abcdefghijklmnopqrstuvwxyz1234567890'; + for(var i=0;i\ +
                                      用户名\ +
                                      \ +
                                      \ +
                                      \ + 重复\ +
                                      \ +
                                      \ +
                                    ", + yes: function(){ + p1 = $("#p1").val(); + p2 = $("#p2").val(); + if(p1 == "" || p1.length < 3) { + layer.msg('用户名长度不能少于3位', {icon: 2}); + return; + } + if(p1 != p2) { + layer.msg('两次输入的用户名不一致', {icon: 2}); + return; + } + $.post("/setting/set_name", "name1=" + encodeURIComponent(p1) + "&name2=" + encodeURIComponent(p2), function(b) { + if(b.status) { + layer.closeAll(); + layer.msg(b.msg, {icon: 1}); + $("input[name='username_']").val(p1) + } else { + layer.msg(b.msg, {icon: 2}); + } + },'json'); + return + }, + btn3:function(){ + var pwd = randomStrPwd(12); + $("#p1").val(pwd); + $("#p2").val(pwd); + layer.msg('请在修改前记录好您的用户名!',{time:2000}); + return false; + } + }) +} + +function setTimezone(){ + layer.open({ + type: 1, + area: ["400px","200px"], + title: '设置服务器时区', + closeBtn: 1, + shift: 5, + shadeClose: false, + btn:["确定","取消","同步"], + content: "
                                    \ +
                                    \ + 时区\ +
                                    \ + \ +
                                    \ +
                                    \ +
                                    ", + success:function(){ + var tbody = ''; + $.post('/setting/get_timezone_list', {}, function (data) { + var rdata = data['data']; + for (var i = 0; i < rdata.length; i++) { + + if (rdata[i] == 'Asia/Shanghai'){ + tbody += ''; + } else { + tbody += ''; + } + + } + $('select[name="timezone"]').append(tbody); + },'json'); + }, + yes:function(index){ + var loadT = layer.msg("正在设置时区...", { icon: 16, time: 0, shade: [0.3, '#000'] }); + var timezone = $('select[name="timezone"]').val(); + $.post('/setting/set_timezone', { timezone: timezone }, function (rdata) { + showMsg(rdata.msg, function(){ + layer.close(index); + layer.close(loadT); + location.reload(); + },{ icon: rdata.status ? 1 : 2 }, 2000); + },'json'); + }, + btn3:function(){ + var loadT = layer.msg('正在同步时间...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/setting/sync_date','',function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + setTimeout(function(){window.location.reload();},1500); + },'json'); + } + }) +} + + +function setIPv6() { + var loadT = layer.msg('正在配置,请稍候...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/setting/set_ipv6_status', {}, function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg, {icon:rdata.status?1:2}); + setTimeout(function(){window.location.reload();},5000); + },'json'); +} + + +//设置面板SSL +function setPanelSSL(){ + var status = $("#sshswitch").prop("checked")==true?1:0; + var msg = $("#panelSSL").attr('checked')?'关闭SSL后,必需使用http协议访问面板,继续吗?':'危险!此功能不懂别开启!\ +
                                  • 必须要用到且了解此功能才决定自己是否要开启!
                                  • \ +
                                  • 面板SSL是自签证书,不被浏览器信任,显示不安全是正常现象
                                  • \ +
                                  • 开启后导致面板不能访问,可以点击下面链接了解解决方法
                                  • \ +

                                    \ + \ +

                                    '; + layer.confirm(msg,{title:'设置面板SSL',closeBtn:1,icon:3,area:'550px',cancel:function(){ + $("#panelSSL").prop("checked", status == 0?false:true); + }},function(){ + if(window.location.protocol.indexOf('https') == -1){ + if(!$("#checkSSL").prop('checked')){ + layer.msg(lan.config.ssl_ps,{icon:2}); + return false; + } + } + var loadT = layer.msg('正在安装并设置SSL组件,这需要几分钟时间...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/setting/set_panel_ssl','',function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:5}); + if(rdata.status === true){ + $.post('/system/restart','',function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg); + setTimeout(function(){ + window.location.href = ((window.location.protocol.indexOf('https') != -1)?'http://':'https://') + window.location.host + window.location.pathname; + },3000); + },'json'); + } + },'json'); + },function(){ + if(status == 0){ + $("#panelSSL").prop("checked",false); + }else{ + $("#panelSSL").prop("checked",true); + } + }); +} + +function setNotifyTgbot(obj){ + var enable = $(obj).prop("checked"); + $.post('/setting/set_notify_tgbot_enable', {'enable':enable},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){} + } ,{icon:rdata.status?1:2}, 1000); + },'json'); +} + + +function setNotifyEmail(obj){ + var enable = $(obj).prop("checked"); + $.post('/setting/set_notify_email_enable', {'enable':enable},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){} + } ,{icon:rdata.status?1:2}, 1000); + },'json'); +} + +function getTgbot(){ + var loadT = layer.msg('正在获取TgBot信息...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/setting/get_notify_tgbot',{},function(data){ + layer.close(loadT); + + var app_token = ''; + var chat_id = ''; + + if (data.status){ + if (data['data']['tgbot'].length != 0){ + app_token = data['data']['tgbot']['app_token']; + chat_id = data['data']['tgbot']['chat_id']; + } + } + + layer.open({ + type: 1, + area: "500px", + title: 'TgBot配置', + closeBtn: 1, + shift: 5, + btn:["确定","关闭","验证"], + shadeClose: false, + content: "
                                    \ +
                                    \ + APP_TOKEN\ +
                                    \ +
                                    \ +
                                    \ + CHAT_ID\ +
                                    \ +
                                    \ +
                                    ", + yes:function(index){ + var pdata = {}; + pdata['app_token'] = $('input[name="app_token"]').val(); + pdata['chat_id'] = $('input[name="chat_id"]').val(); + + if (pdata['app_token'] == ''){ + layer.msg('app_token不能为空!', {icon:2}); + return false; + } + + if (pdata['chat_id'] == ''){ + layer.msg('chat_id不能为空!', {icon:2}); + return false; + } + + $.post('/setting/set_notify_tgbot',{'tag':'tgbot', 'data':JSON.stringify(pdata)},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + } + },{icon:rdata.status?1:2},2000); + }); + }, + + btn3:function(index){ + var pdata = {}; + pdata['app_token'] = $('input[name="app_token"]').val(); + pdata['chat_id'] = $('input[name="chat_id"]').val(); + + if (pdata['app_token'] == ''){ + layer.msg('app_token不能为空!', {icon:2}); + return false; + } + + if (pdata['chat_id'] == ''){ + layer.msg('chat_id不能为空!', {icon:2}); + return false; + } + + $.post('/setting/set_notify_tgbot_test',{'tag':'tgbot', 'data':JSON.stringify(pdata)},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + } + },{icon:rdata.status?1:2},2000); + }); + return false; + } + }); + }); +} + +function getEmailCfg(){ + var loadT = layer.msg('正在获取邮件配置信息...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/setting/get_notify_email',{},function(data){ + layer.close(loadT); + + var smtp_host = 'smtp.163.com'; + var smtp_port = '25'; + var username = 'admin'; + var password = ''; + var to_mail_addr = ''; + + var smtp_ssl_no = 'checked'; + var smtp_ssl_yes = ''; + + if (data.status){ + if (typeof(data['data']['email']) !='undefined'){ + smtp_host = data['data']['email']['smtp_host']; + smtp_port = data['data']['email']['smtp_port']; + username = data['data']['email']['username']; + password = data['data']['email']['password']; + to_mail_addr = data['data']['email']['to_mail_addr']; + + var smtp_ssl = data['data']['email']['smtp_ssl']; + if (smtp_ssl == 'ssl'){ + smtp_ssl_no = ''; + smtp_ssl_yes = 'checked'; + } + } + } + + layer.open({ + type: 1, + area: "500px", + title: '邮件配置', + closeBtn: 1, + shift: 5, + btn:["确定","关闭","验证"], + shadeClose: false, + content: "
                                    \ +
                                    \ + SMTP服务器\ +
                                    \ +
                                    \ +
                                    \ + SMTP安全\ +
                                    \ + \ + \ +
                                    \ +
                                    \ +
                                    \ + SMTP端口\ +
                                    \ +
                                    \ +
                                    \ + 用户名\ +
                                    \ +
                                    \ +
                                    \ + 授权码\ +
                                    \ +
                                    \ +
                                    \ + 发送地址\ +
                                    \ +
                                    \ +
                                    \ + 验证测试\ +
                                    \ +
                                    \ +
                                    \ +
                                    ", + yes:function(index){ + var pdata = {}; + pdata['smtp_host'] = $('input[name="smtp_host"]').val(); + pdata['smtp_port'] = $('input[name="smtp_port"]').val(); + pdata['smtp_ssl'] = $('input[name="smtp_ssl"]:checked').val(); + pdata['username'] = $('input[name="username"]').val(); + pdata['password'] = $('input[name="password"]').val(); + pdata['to_mail_addr'] = $('input[name="to_mail_addr"]').val(); + + if (pdata['smtp_host'] == ''){ + layer.msg('SMTP服务器不能为空!', {icon:2}); + return false; + } + + if (pdata['smtp_port'] == ''){ + layer.msg('SMTP端口不能为空!', {icon:2}); + return false; + } + + if (pdata['username'] == ''){ + layer.msg('用户名不能为空!', {icon:2}); + return false; + } + + if (pdata['password'] == ''){ + layer.msg('授权码不能为空!', {icon:2}); + return false; + } + + if (pdata['to_mail_addr'] == ''){ + layer.msg('发送地址不能为空!', {icon:2}); + return false; + } + + $.post('/setting/set_notify_email',{'tag':'email', 'data':JSON.stringify(pdata)},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + } + },{icon:rdata.status?1:2},2000); + },'json'); + }, + + btn3:function(index){ + var pdata = {}; + pdata['smtp_host'] = $('input[name="smtp_host"]').val(); + pdata['smtp_port'] = $('input[name="smtp_port"]').val(); + pdata['smtp_ssl'] = $('input[name="smtp_ssl"]:checked').val(); + pdata['username'] = $('input[name="username"]').val(); + pdata['password'] = $('input[name="password"]').val(); + pdata['to_mail_addr'] = $('input[name="to_mail_addr"]').val(); + pdata['mail_test'] = $('textarea[name="mail_test"]').val(); + + + if (pdata['smtp_host'] == ''){ + layer.msg('SMTP服务器不能为空!', {icon:2}); + return false; + } + + if (pdata['smtp_port'] == ''){ + layer.msg('SMTP端口不能为空!', {icon:2}); + return false; + } + + if (pdata['username'] == ''){ + layer.msg('用户名不能为空!', {icon:2}); + return false; + } + + if (pdata['password'] == ''){ + layer.msg('授权码不能为空!', {icon:2}); + return false; + } + + if (pdata['to_mail_addr'] == ''){ + layer.msg('发送地址不能为空!', {icon:2}); + return false; + } + + if (pdata['mail_test'] == ''){ + layer.msg('验证测试不能为空!', {icon:2}); + return false; + } + $.post('/setting/set_notify_email_test',{'tag':'email', 'data':JSON.stringify(pdata)},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + } + },{icon:rdata.status?1:2},2000); + },'json'); + return false; + } + }); + },'json'); +} + +function getPanelSSL(){ + var loadT = layer.msg('正在获取证书信息...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/setting/get_panel_ssl',{},function(cert){ + layer.close(loadT); + + // console.log(cert); + var choose = cert['choose']; + var choose_local = ''; + var choose_nginx = ''; + + if (choose == 'local'){ + cert = cert['local']; + choose_local = 'selected="selected"'; + } else if (choose == 'nginx') { + cert = cert['nginx']; + choose_nginx = 'selected="selected"'; + } else { + cert = cert['local']; + } + + var cert_data = ''; + + //
                                    \ + // 强制HTTPS:\ + // \ + // \ + // \ + // \ + //
                                    \ + if (cert['info']){ + cert_data = "
                                    \ +
                                    证书品牌:\ + "+cert['info']['issuer']+"
                                    \ +
                                    到期时间:\ + 剩余"+cert['info']['endtime']+"天到期
                                    \ +
                                    \ +
                                    \ +
                                    认证域名:\ + "+cert['info']['subject']+"
                                    \ +
                                    "; + } + + // \ + // \ + var certBody = '
                                    \ +
                                    \ + '+cert_data+'\ +
                                    \ +
                                    密钥(KEY)
                                    \ + \ +
                                    \ +
                                    证书(PEM格式)
                                    \ + \ +
                                    \ +
                                    \ +
                                    \ + \ + \ + \ +
                                    \ +
                                    \ +
                                      \ +
                                    • 粘贴您的*.key以及*.pem内容,然后保存即可。
                                    • \ +
                                    • 如果浏览器提示证书链不完整,请检查是否正确拼接PEM证书
                                    • PEM格式证书 = 域名证书.crt + 根证书(root_bundle).crt
                                    • \ +
                                    \ +
                                    ' + layer.open({ + type: 1, + area: "600px", + title: '自定义面板证书', + closeBtn: 1, + shift: 5, + shadeClose: false, + content:certBody, + success:function(layero, layer_id){ + + + //保存SSL + $('.save-panel-ssl').click(function(){ + var data = { + privateKey:$("#key").val(), + certPem:$("#csr").val() + } + + layer.confirm('选择保存面板SSL方式?', + { + title:'提示', + shade:0.001, + btn: ['本地SSL', '取消'],//'OpenResty' + btn3:function(){ + data['choose'] = 'nginx'; + var loadT = layer.msg('正在安装并设置SSL组件,这需要几分钟时间...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/setting/save_panel_ssl',data,function(rdata){ + layer.close(loadT); + if(rdata.status){ + layer.closeAll(); + } + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + }, + }, + function(index) { + data['choose'] = 'local'; + var loadT = layer.msg('正在安装并设置SSL组件,这需要几分钟时间...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/setting/save_panel_ssl',data,function(rdata){ + layer.close(loadT); + if(rdata.status){ + layer.closeAll(); + } + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + }, + function(index) { + layer.close(index); + }); + }); + + //删除SSL + $('.del-panel-ssl').click(function(){ + + layer.confirm('选择删除面板SSL方式?', + { + title:'提示', + shade:0.001, + btn: ['本地SSL', '取消'],//, 'OpenResty' + btn3:function(){ + var data = {}; + data['choose'] = 'nginx'; + var loadT = layer.msg('正在删除面板SSL【nginx】...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/setting/del_panel_ssl',data,function(rdata){ + layer.close(loadT); + if(rdata.status){ + layer.closeAll(); + } + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + }, + }, + function(index) { + var data = {}; + data['choose'] = 'local'; + var loadT = layer.msg('正在删除面板SSL【本地】...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/setting/del_panel_ssl',data,function(rdata){ + console.log(rdata); + layer.close(loadT); + showMsg(rdata.msg, function(){ + if(rdata.status){ + location.href = rdata.data; + } + },{icon:rdata.status?1:2},3000); + },'json'); + }, + function(index) { + layer.close(index); + }); + + + }); + + // // 设置面板SSL的Http + // $('.set_panel_http_to_https').click(function(){ + // var https = $('#toHttps').prop('checked'); + // $.post('/config/set_panel_http_to_https',{'https':https},function(rdata){ + // layer.close(loadT); + // if(rdata.status){ + // layer.closeAll(); + // } + // layer.msg(rdata.msg,{icon:rdata.status?1:2}); + // },'json'); + // }); + + // //申请Lets证书 + // $('.apply-lets-ssl').click(function(){ + // showSpeedWindow('正在申请...', 'site.get_let_logs', function(layers,index){ + // $.post('/config/apply_panel_acme_ssl',{},function(rdata){ + // layer.close(loadT); + // if(rdata.status){ + // layer.close(index); + // var tdata = rdata['data']; + // $('.ssl_issue').text(tdata['info']['issuer']); + // $('.ssl_endtime').text("剩余"+tdata['info']['endtime']+"天到期"); + // $('.ssl_subject').text(tdata['info']['subject']); + + // $('textarea[name="key"]').val(tdata['info']['privateKey']); + // $('textarea[name="csr"]').val(tdata['info']['certPem']); + // } + // layer.msg(rdata.msg,{icon:rdata.status?1:2}); + // },'json'); + // }); + // }); + } + }); + },'json'); +} + + +function removeTempAccess(id){ + $.post('/setting/remove_temp_login', {id:id}, function(rdata){ + showMsg(rdata.msg, function(){ + setTempAccessReq(); + },{ icon: rdata.status ? 1 : 2 }, 2000); + },'json'); +} + +function getTempAccessLogsReq(id){ + $.post('/setting/get_temp_login_logs', {id:id}, function(rdata){ + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + + tbody += ''; + tbody += '' + (rdata.data[i]['type']) +''; + tbody += '' + rdata.data[i]['addtime'] +''; + tbody += ''+ rdata.data[i]['log'] +''; + tbody += ''; + } + $('#logs_list').html(tbody); + + },'json'); +} + +function getTempAccessLogs(id){ + + layer.open({ + area: ['700px', '250px'], + title: '临时授权管理', + closeBtn:1, + shift: 0, + type: 1, + content: "
                                    \ + \ +
                                    \ + \ + \ + \ + \ + \ +
                                    操作类型操作时间日志
                                    \ +
                                    \ +
                                    ", + success:function(){ + getTempAccessLogsReq(id); + $('.refresh_log').click(function(){ + getTempAccessLogsReq(id); + }); + } + }); +} + +function setTempAccessReq(page){ + if (typeof(page) == 'undefined'){ + page = 1; + } + + $.post('/setting/get_temp_login', {page:page}, function(rdata){ + console.log(rdata); + if ( typeof(rdata.status) !='undefined' && !rdata.status){ + showMsg(rdata.msg,function(){ + layer.closeAll(); + },{icon:2}, 2000); + return; + } + + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + + tbody += ''; + tbody += '' + (rdata.data[i]['login_addr']||'未登陆') +''; + + tbody += ''; + switch (parseInt(rdata.data[i]['state'])) { + case 0: + tbody += '待使用'; + break; + case 1: + tbody += '已使用'; + break; + case -1: + tbody += '已过期'; + break; + } + tbody += ''; + + tbody += '' + (getLocalTime(rdata.data[i]['login_time'])||'未登陆') +''; + tbody += '' + getLocalTime(rdata.data[i]['expire']) +''; + + tbody += ''; + + if (rdata.data[i]['state'] == '1' ){ + tbody += '操作日志'; + } else{ + tbody += '删除'; + } + + tbody += ''; + + tbody += ''; + } + + $('#temp_login_view_tbody').html(tbody); + $('.temp_login_view_page').html(rdata.page); + },'json'); +} + +function setStatusCode(o){ + var code = $(o).data('code'); + layer.open({ + type: 1, + area: ['420px', '220px'], + title: "设置未认证时的响应状态", + closeBtn: 1, + shift: 5, + btn:['提交','关闭'], + shadeClose: false, + content: '
                                    \ +
                                    \ + 相应状态\ +
                                    \ + \ +
                                    \ +
                                    \ +
                                    • 用于未登录且未正确输入安全入口时的响应,用于隐藏面板特征
                                    \ +
                                    ', + success:function(){ + var msg_list = [ + {'code':'0','msg':'默认-安全入口错误提示'}, + {'code':'403','msg':'403-拒绝访问'}, + {'code':'404','msg':'404-页面不存在'}, + {'code':'416','msg':'416-无效的请求'}, + {'code':'408','msg':'408-客户端超时'}, + {'code':'400','msg':'400-客户端请求错误'}, + {'code':'401','msg':'401-未授权访问'}, + ]; + + var tbody = ''; + for(i in msg_list){ + if (msg_list[i]['code'] == code){ + tbody += ''; + } else{ + tbody += ''; + } + + } + $('select[name="status_code"]').append(tbody); + }, + yes:function(index){ + var loadT = layer.msg("正在设置未认证时的响应状态", { icon: 16, time: 0, shade: [0.3, '#000'] }); + var status_code = $('select[name="status_code"]').val(); + $.post('/setting/set_status_code', { status_code: status_code }, function (rdata) { + showMsg(rdata.msg, function(){ + layer.close(index); + layer.close(loadT); + location.reload(); + },{ icon: rdata.status ? 1 : 2 }, 2000); + },'json'); + } + }); +} + +function setTempAccess(){ + layer.open({ + area: ['700px', '380px'], + title: '临时授权管理', + closeBtn:1, + shift: 0, + type: 1, + content: "", + success:function(){ + setTempAccessReq(); + + $('.create_temp_login').click(function(){ + layer.confirm('注意1:滥用临时授权可能导致安全风险。
                                    注意2:请勿在公共场合发布临时授权连接

                                    即将创建临时授权连接,继续吗?',{ + title:'风险提示', + closeBtn:1, + icon:13, + }, function(create_temp_login_layer) { + $.post('/setting/set_temp_login', {}, function(rdata){ + layer.close(create_temp_login_layer); + setTempAccessReq(); + layer.open({ + area: '570px', + title: '创建临时授权', + shift: 0, + type: 1, + content: "
                                    \ +
                                    \ + 临时授权地址\ +
                                    \ + \ +
                                    \ +
                                    \ +
                                    \ +
                                      \ +
                                    • 临时授权生成后1小时内使用有效,为一次性授权,使用后立即失效
                                    • \ +
                                    • 使用临时授权登录面板后1小时内拥有面板所有权限,请勿在公共场合发布临时授权连接
                                    • \ +
                                    • 授权连接信息仅在此处显示一次,若在使用前忘记,请重新生成
                                    • \ +
                                    \ +
                                    ", + success:function(){ + var temp_link = "".concat(location.origin, "/login?tmp_token=").concat(rdata.token); + $('#temp_link').val(temp_link); + + copyText(temp_link); + $('.btn-copy-temp-link').click(function(){ + copyText(temp_link); + }); + } + }); + },'json'); + }); + }); + } + }); +} + +//二步验证 +function setAuthBind(){ + $.post('/setting/get_auth_secret', {}, function(rdata){ + console.log(rdata); + var tip = layer.open({ + area: ['500px', '355px'], + title: '二步验证设置', + closeBtn:1, + shift: 0, + type: 1, + content: '
                                    \ +
                                    \ + 绑定密钥\ +
                                    \ + \ + \ +
                                    \ +
                                    \ +
                                    \ + 二维码\ +
                                    \ +
                                    \ +
                                      \ +
                                    \ +
                                    ', + success:function(layero,index){ + + $('input[name="secret"]').val(rdata.data['secret']); + $('.qrcode').qrcode({ text: rdata.data['url']}); + + $('.reset_secret').click(function(){ + layer.confirm('您确定要重置当前密钥吗?
                                    重置密钥后,已关联密钥产品,将失效,请重新添加新密钥至产品。',{title:'重置密钥',closeBtn:2,icon:13,cancel:function(){ + }}, function() { + $.post('/setting/get_auth_secret', {'reset':"1"},function(rdata){ + showMsg("接口密钥已生成,重置密钥后,已关联密钥产品,将失效,请重新添加新密钥至产品。", function(){ + $('input[name="secret"]').val(rdata.data['secret']); + $('.qrcode').html('').qrcode({ text: rdata.data['url']}); + } ,{icon:1}, 2000); + },'json'); + }); + }); + }, + }); + + },'json'); +} + +function setAuthSecretApi(){ + var cfg_panel_auth = $('#cfg_panel_auth').prop("checked"); + $.post('/setting/set_auth_secret', {'op_type':"2"},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.data == 1){ + setAuthBind(); + } + } ,{icon:rdata.status?1:2}, 1000); + },'json'); +} + +function setBasicAuthTip(callback){ + var tip = layer.open({ + area: ['500px', '385px'], + title: '开启BasicAuth认证提示', + closeBtn:0, + shift: 0, + type: 1, + content: '
                                    \ +
                                    \ +

                                    风险操作!此功能不懂请勿开启!

                                    \ +
                                    \ +
                                      \ +
                                    • 必须要用到且了解此功能才决定自己是否要开启!
                                    • \ +
                                    • 开启后,以任何方式访问面板,将先要求输入BasicAuth用户名和密码
                                    • \ +
                                    • 开启后,能有效防止面板被扫描发现,但并不能代替面板本身的帐号密码
                                    • \ +
                                    • 请牢记BasicAuth密码,一但忘记将无法访问面板
                                    • \ +
                                    • 如忘记密码,可在SSH通过mw命令来关闭BasicAuth验证
                                    • \ +
                                    \ +
                                    \ + \ + \ +
                                    \ +
                                    ', + btn:["确定","取消"], + yes:function(l,index){ + is_agree = $('#agreement_more').prop("checked"); + if (is_agree){ + layer.close(tip); + callback(); + } + return is_agree; + }, + btn2: function(index, layero){ + $('#cfg_basic_auth').prop("checked", false); + } + }); +} + +function setBasicAuth(){ + + var basic_auth = $('#cfg_basic_auth').prop("checked"); + if (!basic_auth){ + setBasicAuthTip(function(){ + var tip = layer.open({ + area: ['500px', '385px'], + title: '配置BasicAuth认证', + closeBtn:1, + shift: 0, + type: 1, + content: '
                                    \ +
                                    \ + 用户名\ +
                                    \ +
                                    \ +
                                    \ + 密码\ +
                                    \ +
                                    \ +
                                    \ + \ +
                                    \ +
                                    \ +
                                      \ +
                                    • 注意:请不要在这里使用您的常用密码,这可能导致密码泄漏!
                                    • \ +
                                    • 开启后,以任何方式访问面板,将先要求输入BasicAuth用户名和密码
                                    • \ +
                                    • 开启后,能有效防止面板被扫描发现,但并不能代替面板本身的帐号密码
                                    • \ +
                                    • 请牢记BasicAuth密码,一但忘记将无法访问面板
                                    • 如忘记密码,可在SSH通过mw命令来关闭BasicAuth验证
                                    • \ +
                                    \ +
                                    ', + success:function(){ + $('.save_auth_cfg').click(function(){ + var basic_user = $('input[name="basic_user"]').val(); + var basic_pwd = $('input[name="basic_pwd"]').val(); + $.post('/setting/set_basic_auth', {'basic_user':basic_user,'basic_pwd':basic_pwd},function(rdata){ + showMsg(rdata.msg, function(){ + window.location.reload(); + } ,{icon:rdata.status?1:2}, 2000); + },'json'); + }); + }, + cancel:function(){ + $('#cfg_basic_auth').prop("checked", false); + }, + }); + }); + } else { + layer.confirm('关闭BasicAuth认证后,面板登录将不再验证BasicAuth基础认证,这将会导致面板安全性下降,继续操作!', + {btn: ['确定', '取消'], title: "是否关闭BasicAuth认证?", icon:13}, function (index) { + var basic_user = ''; + var basic_pwd = ''; + $.post('/setting/set_basic_auth', {'is_open':'false'},function(rdata){ + showMsg(rdata.msg, function(){ + layer.close(index); + window.location.reload(); + } ,{icon:rdata.status?1:2}, 2000); + },'json'); + },function(){ + $('#cfg_basic_auth').prop("checked", true); + }); + } +} + +function showPanelApi(){ + $.post('/setting/get_panel_token', '', function(rdata){ + var tip = layer.open({ + area: ['500px', '355px'], + title: '配置面板API', + closeBtn:1, + shift: 0, + type: 1, + content: '
                                    \ +
                                    \ + 接口密钥\ +
                                    \ + \ + \ +
                                    \ +
                                    \ +
                                    \ + IP白名单
                                    (每行1个)
                                    \ +
                                    \ +
                                    \ +
                                    \ + \ +
                                    \ +
                                    \ +
                                      \ +
                                    • 开启API后,必需在IP白名单列表中的IP才能访问面板API接口
                                    • \ +
                                    • 请不要在生产环境开启,这可能增加服务器安全风险;
                                    • \ +
                                    \ +
                                    ', + success:function(layero,index){ + + $('input[name="token"]').val(rdata.data.token); + $('textarea[name="api_limit_addr"]').val(rdata.data.limit_addr); + + + $('.reset_token').click(function(){ + layer.confirm('您确定要重置当前密钥吗?
                                    重置密钥后,已关联密钥产品,将失效,请重新添加新密钥至产品。',{title:'重置密钥',closeBtn:2,icon:13,cancel:function(){ + }}, function() { + $.post('/config/set_panel_token', {'op_type':"1"},function(rdata){ + showMsg("接口密钥已生成,重置密钥后,已关联密钥产品,将失效,请重新添加新密钥至产品。", function(){ + $('input[name="token"]').val(rdata.data); + } ,{icon:1}, 2000); + },'json'); + }); + }); + + $('.save_api').click(function(){ + var limit_addr = $('textarea[name="api_limit_addr"]').val(); + $.post('/config/set_panel_token', {'op_type':"3",'limit_addr':limit_addr},function(rdata){ + showMsg(rdata.msg, function(){ + } ,{icon:rdata.status?1:2}, 2000); + },'json'); + }); + }, + }); + },'json'); +} + + +function setPanelApi(){ + var cfg_panel_api = $('#cfg_panel_api').prop("checked"); + $.post('/setting/set_panel_api', {},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + addApp(); + } + } ,{icon:rdata.status?1:2}, 1000); + },'json'); +} + + +function deleteApp(id){ + layer.confirm('您确定要删除吗?',{title:'删除应用',closeBtn:2,icon:13,cancel:function(){}}, function() { + $.post('/setting/delete_app', {'id':id},function(rdata){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + if (rdata.status){ + getAppList(); + } + },'json'); + }); +} + +function toggleAppstatus(id){ + $.post('/setting/toggle_app_status', {id:id}, function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + getAppList(); + } + } ,{icon:rdata.status?1:2}, 2000); + },'json'); +} + +function getAppList(page) { + + if (typeof(page) == 'undefined'){ + page = 1; + } + + $.post('/setting/get_app_list', {page:page}, function(rdata){ + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + var row = rdata.data[i]; + + tbody += ''; + + tbody += '' + row['app_id'] +''; + tbody += '' + row['app_secret'] +''; + tbody += '' + row['white_list'] +''; + + if (row['status'] == 1){ + tbody += '已开启'; + } else { + tbody += '已关闭'; + } + + tbody += '' + row['add_time'] +''; + + tbody += ''; + tbody += '删除'; + tbody += ''; + + tbody += ''; + } + + $('#app_list_body tbody').html(tbody); + $('#app_list_body .page').html(rdata.page); + },'json'); +} + +function addApp(){ + layer.open({ + area: '570px', + title: '添加应用', + shift: 0, + type: 1, + content: '
                                    \ +
                                    \ + 应用ID\ +
                                    \ + \ + \ +
                                    \ +
                                    \ +
                                    \ + 应用密钥\ +
                                    \ + \ + \ +
                                    \ +
                                    \ +
                                    \ + IP白名单
                                    (每行1个)
                                    \ +
                                    \ +
                                    \ +
                                    \ + \ +
                                    \ +
                                    \ +
                                      \ +
                                    • 开启API后,必需在IP白名单列表中的IP才能访问面板API接口
                                    • \ +
                                    • 请谨慎在生产环境开启,这可能增加服务器安全风险;
                                    • \ +
                                    \ +
                                    ', + success:function(obj,cur_layer){ + $('input[name="app_id"]').val(getRandomString(10)); + $('input[name="app_secret"]').val(getRandomString(20)); + + $('.app_id').click(function(){ + $('input[name="app_id"]').val(getRandomString(10)); + }); + + $('.app_secret').click(function(){ + $('input[name="app_secret"]').val(getRandomString(20)); + }); + + $('.save_app_data').click(function(){ + var app_id = $('input[name="app_id"]').val(); + var app_secret = $('input[name="app_secret"]').val(); + var limit_addr = $('textarea[name="api_limit_addr"]').val(); + $.post('/setting/add_app', {'app_id':app_id,'app_secret':app_secret,'limit_addr':limit_addr},function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + getAppList(); + layer.close(cur_layer); + } + } ,{icon:rdata.status?1:2}, 2000); + },'json'); + }); + + } + }); +} + +function appPage(){ + layer.open({ + area: ['900px', '380px'], + title: 'APP应用管理', + closeBtn:1, + shift: 0, + type: 1, + content: "", + success:function(){ + getAppList(); + $('.app_add').click(function(){ + addApp(); + }); + } + }); +} + +$(function () { + var themeApi = window.MWTheme || {}; + var themeInput = document.getElementById('mwThemeColor'); + var themeReset = document.getElementById('mwThemeReset'); + var modeContainer = document.getElementById('mwThemeMode'); + var backgroundState = document.getElementById('mwThemeBgState'); + var backgroundOpacityInput = document.getElementById('mwThemeBgOpacity'); + var backgroundOpacityValue = document.getElementById('mwThemeBgOpacityValue'); + var backgroundUrlInput = document.getElementById('mwThemeBgUrl'); + var backgroundFileInput = document.getElementById('mwThemeBgFile'); + var backgroundDefaultBtn = document.getElementById('mwThemeDefaultBg'); + var backgroundClearBtn = document.getElementById('mwThemeClearBg'); + var backgroundUploadBtn = document.getElementById('mwThemeUploadBg'); + var backgroundApplyBtn = document.getElementById('mwThemeApplyBg'); + + function getThemeColor() { + if (themeApi && typeof themeApi.getColor === 'function') { + return themeApi.getColor(); + } + return '#6750a4'; + } + + function syncModeButtons(mode) { + if (!modeContainer) { + return; + } + + var nextMode = mode || 'auto'; + modeContainer.querySelectorAll('.mw-theme-mode-btn').forEach(function (button) { + var buttonMode = button.getAttribute('data-mode'); + button.classList.toggle('is-active', buttonMode === nextMode); + }); + } + + function syncBackgroundState(info) { + if (!backgroundState) { + return; + } + + info = info || {}; + var parts = []; + var mode = info.mode || 'default'; + var kind = info.kind || 'default'; + var opacity = typeof info.opacity === 'number' ? info.opacity : (themeApi && typeof themeApi.getBackgroundOpacity === 'function' ? themeApi.getBackgroundOpacity() : 72); + + if (mode === 'plain') { + parts.push('纯净背景'); + } else { + parts.push('默认背景'); + } + + if (kind === 'upload') { + parts.push('本地图片'); + } else if (kind === 'url') { + parts.push('外链图片'); + } else if (info.value) { + parts.push('自定义图片'); + } + + parts.push('淡化 ' + opacity + '%'); + + var text = '当前:' + parts.join(' / '); + if (info.source && (kind === 'upload' || kind === 'url')) { + text += '(' + info.source + ')'; + } + + backgroundState.textContent = text; + + if (backgroundOpacityInput) { + backgroundOpacityInput.value = String(opacity); + } + if (backgroundOpacityValue) { + backgroundOpacityValue.textContent = opacity + '%'; + } + + if (backgroundUrlInput) { + if (kind === 'url' && info.source) { + backgroundUrlInput.value = info.source; + } else if (kind === 'default' || kind === 'upload') { + backgroundUrlInput.value = ''; + } + } + } + + if (themeInput) { + themeInput.value = getThemeColor(); + + themeInput.addEventListener('input', function () { + if (themeApi && typeof themeApi.applyColor === 'function') { + themeApi.applyColor(themeInput.value); + } + }); + } + + if (themeReset) { + themeReset.addEventListener('click', function () { + if (themeApi && typeof themeApi.reset === 'function') { + themeApi.reset(); + } + if (themeInput) { + themeInput.value = getThemeColor(); + } + }); + } + + Array.prototype.forEach.call(document.querySelectorAll('.mw-theme-preset'), function (button) { + button.addEventListener('click', function () { + var color = button.getAttribute('data-theme-color'); + if (!color) { + return; + } + if (themeInput) { + themeInput.value = color; + } + if (themeApi && typeof themeApi.applyColor === 'function') { + themeApi.applyColor(color); + } + }); + }); + + if (modeContainer) { + syncModeButtons(themeApi && typeof themeApi.getMode === 'function' ? themeApi.getMode() : 'auto'); + Array.prototype.forEach.call(modeContainer.querySelectorAll('.mw-theme-mode-btn'), function (button) { + button.addEventListener('click', function () { + var mode = button.getAttribute('data-mode'); + if (!mode) { + return; + } + if (themeApi && typeof themeApi.setMode === 'function') { + themeApi.setMode(mode); + } + syncModeButtons(mode); + }); + }); + } + + if (backgroundState && themeApi && typeof themeApi.getBackgroundInfo === 'function') { + syncBackgroundState(themeApi.getBackgroundInfo()); + } + + if (backgroundOpacityInput) { + var initialOpacity = themeApi && typeof themeApi.getBackgroundOpacity === 'function' ? themeApi.getBackgroundOpacity() : 72; + backgroundOpacityInput.value = String(initialOpacity); + if (backgroundOpacityValue) { + backgroundOpacityValue.textContent = initialOpacity + '%'; + } + backgroundOpacityInput.addEventListener('input', function () { + var nextOpacity = parseInt(backgroundOpacityInput.value, 10); + if (isNaN(nextOpacity)) { + return; + } + if (themeApi && typeof themeApi.setBackgroundOpacity === 'function') { + themeApi.setBackgroundOpacity(nextOpacity); + } + if (backgroundOpacityValue) { + backgroundOpacityValue.textContent = nextOpacity + '%'; + } + syncBackgroundState(themeApi && typeof themeApi.getBackgroundInfo === 'function' ? themeApi.getBackgroundInfo() : {}); + }); + } + + if (backgroundDefaultBtn) { + backgroundDefaultBtn.addEventListener('click', function () { + if (themeApi && typeof themeApi.setDefaultBackground === 'function') { + themeApi.setDefaultBackground(); + } else if (themeApi && typeof themeApi.clearBackground === 'function') { + themeApi.clearBackground(); + } + syncBackgroundState(themeApi && typeof themeApi.getBackgroundInfo === 'function' ? themeApi.getBackgroundInfo() : {}); + }); + } + + if (backgroundClearBtn) { + backgroundClearBtn.addEventListener('click', function () { + if (themeApi && typeof themeApi.clearBackground === 'function') { + themeApi.clearBackground(); + } + syncBackgroundState(themeApi && typeof themeApi.getBackgroundInfo === 'function' ? themeApi.getBackgroundInfo() : {}); + }); + } + + if (backgroundUploadBtn && backgroundFileInput) { + backgroundUploadBtn.addEventListener('click', function () { + backgroundFileInput.click(); + }); + } + + if (backgroundFileInput) { + backgroundFileInput.addEventListener('change', function () { + var file = backgroundFileInput.files && backgroundFileInput.files[0]; + if (!file) { + return; + } + + if (!file.type || file.type.indexOf('image/') !== 0) { + layer.msg('请选择图片文件', { icon: 2 }); + backgroundFileInput.value = ''; + return; + } + + if (themeApi && typeof themeApi.setBackgroundFile === 'function') { + themeApi.setBackgroundFile(file).then(function () { + backgroundFileInput.value = ''; + syncBackgroundState(themeApi.getBackgroundInfo()); + layer.msg('背景图已应用', { icon: 1 }); + }).catch(function () { + backgroundFileInput.value = ''; + layer.msg('背景图读取失败', { icon: 2 }); + }); + } + }); + } + + if (backgroundApplyBtn && backgroundUrlInput) { + backgroundApplyBtn.addEventListener('click', function () { + var url = backgroundUrlInput.value.trim(); + if (!url) { + layer.msg('请输入图片地址', { icon: 2 }); + return; + } + + if (themeApi && typeof themeApi.setBackgroundUrl === 'function') { + themeApi.setBackgroundUrl(url); + } else if (themeApi && typeof themeApi.setBackground === 'function') { + themeApi.setBackground(url, { kind: 'url', source: url }); + } + + syncBackgroundState(themeApi && typeof themeApi.getBackgroundInfo === 'function' ? themeApi.getBackgroundInfo() : {}); + layer.msg('背景图已应用', { icon: 1 }); + }); + + backgroundUrlInput.addEventListener('keydown', function (event) { + if (event.key === 'Enter') { + event.preventDefault(); + backgroundApplyBtn.click(); + } + }); + } +}); diff --git a/web/static/app/control.js b/web/static/app/control.js new file mode 100755 index 000000000..94c59921f --- /dev/null +++ b/web/static/app/control.js @@ -0,0 +1,1078 @@ +//默认显示7天周期图表 + +setTimeout(function(){ + Wday(0,'getload'); +},100); +setTimeout(function(){ + Wday(0,'cpu'); +},200); +setTimeout(function(){ + Wday(0,'mem'); +},500); +setTimeout(function(){ + Wday(0,'disk'); +},100); +setTimeout(function(){ + Wday(0,'network'); +},1500); + + +$(".searcTime .st").click(function(){ + var status = $(this).data('status'); + if (status == 'show'){ + $(this).next().hide(); + $(this).data('status','hide'); + } else{ + $(this).next().show(); + $(this).data('status','show'); + } +}); + +//自定义时间-切换 +$(".searcTime .st").hover(function(){ + $(this).data('status','show'); + $(this).next().show(); +},function(){ + // $(this).next().hide(); + // $(this).next().hover(function(){ + // $(this).show(); + // },function(){ + // $(this).hide(); + // }) +}) + +$(".searcTime .gt").click(function(){ + $(this).addClass("on").siblings().removeClass("on"); +}) + + +//渲染日期时间范围 start +var render_dlist = [ + 'loadbtn_rtime', + 'cpubtn_rtime', + 'membtn_rtime', + 'diskbtn_rtime', + 'networkbtn_rtime' +]; + +for (var i = 0; i < render_dlist.length; i++) { + + laydate.render({ + elem: '#'+render_dlist[i] + ,type: 'datetime' + ,range: true + }); + + + var b = getBeforeDate(28).replaceAll('/','-') + " 00:00:00"; + var e = getBeforeDate(0).replaceAll('/','-') + " 23:59:59"; + + $('#'+render_dlist[i]).val(b + ' - ' + e); +} +//渲染日期时间范围 end + + +$('.sbtn').click(function(){ + $(".searcTime .st").next().hide(); + + var rtime = $(this).parent().find(".rtime").val(); + var rarr = rtime.split(' - '); + + var b = (new Date(rarr[0]).getTime())/1000; + var e = (new Date(rarr[1]).getTime())/1000; + + b = Math.round(b); + e = Math.round(e); + + if ($(this).hasClass('loadbtn')){ + getload(b,e); + } else if ($(this).hasClass('cpubtn')){ + cpu(b,e); + }else if ($(this).hasClass('membtn')){ + mem(b,e); + } else if ($(this).hasClass('diskbtn')){ + disk(b,e); + } +}); + +//指定天数 +function Wday(day,name){ + var now = (new Date().getTime())/1000; + if(day==0){ + var b = (new Date(getToday() + " 00:00:01").getTime())/1000; + b = Math.round(b); + var e = Math.round(now); + } + if(day==1){ + var b = (new Date(getBeforeDate(day) + " 00:00:01").getTime())/1000; + var e = (new Date(getBeforeDate(day) + " 23:59:59").getTime())/1000; + b = Math.round(b); + e = Math.round(e); + } + else{ + var b = (new Date(getBeforeDate(day) + " 00:00:01").getTime())/1000; + b = Math.round(b); + var e = Math.round(now); + } + switch (name){ + case "cpu": + cpu(b, e); + break; + case "mem": + mem(b, e); + break; + case "disk": + disk(b, e); + break; + case "network": + network(b, e); + break; + case "getload": + getload(b, e); + break; + } +} + +function getToday(){ + var mydate = new Date(); + var str = "" + mydate.getFullYear() + "/"; + str += (mydate.getMonth()+1) + "/"; + str += mydate.getDate(); + return str; +} + +getStatus(); +//取监控状态 +function getStatus(){ + loadT = layer.msg('正在读取,请稍候...',{icon:16,time:0}) + $.post('/system/set_control','type=-1',function(rdata){ + layer.close(loadT); + + if(rdata.status){ + $("#openJK").html(""); + } else { + $("#openJK").html(""); + } + + if(rdata.stat_all_status){ + $("#statAll").html(""); + } else{ + $("#statAll").html(""); + } + + $("#save_day").val(rdata.day); + + },'json'); +} + + +//设置监控状态 +function setControl(act, value=false){ + + if (act == 'openjk'){ + var type = $("#ctswitch").prop('checked')?'0':'1'; + var day = $("#save_day").val(); + if(day < 1){ + layer.msg('保存天数不合法!',{icon:2}); + return; + } + } else if (act == 'stat'){ + var type = $("#stat_witch").prop('checked')?'2':'3'; + } else if (act == 'save_day'){ + var type = $("#ctswitch").prop('checked')?'1':'0'; + var day = $("#save_day").val(); + + if(type == 0){ + layer.msg('先开启监控!',{icon:2}); + return; + } + + if(day < 1){ + layer.msg('保存天数不合法!',{icon:2}); + return; + } + } + + loadT = layer.msg('正在处理,请稍候...',{icon:16,time:0}) + $.post('/system/set_control','type='+type+'&day='+day,function(rdata){ + showMsg(rdata.msg, function(){ + layer.close(loadT); + },{icon:rdata.status?1:2}) + },'json'); +} + + +//清理记录 +function closeControl(){ + layer.confirm('您真的清空所有监控记录吗?',{title:'清空记录',icon:3,closeBtn:1}, function() { + loadT = layer.msg('正在处理,请稍候...',{icon:16,time:0}) + $.post('/system/set_control','type=del',function(rdata){ + showMsg(rdata.msg, function(){ + layer.close(loadT); + },{icon:rdata.status?1:2}) + },'json'); + }); +} + + +//定义周期时间 +function getBeforeDate(n){ + var n = n; + var d = new Date(); + var year = d.getFullYear(); + var mon=d.getMonth()+1; + var day=d.getDate(); + if(day <= n){ + if(mon>1) { + mon = mon-1; + } else { + year = year-1; + mon = 12; + } + } + d.setDate(d.getDate()-n); + year = d.getFullYear(); + mon=d.getMonth()+1; + day=d.getDate(); + s = year+"/"+(mon<10?('0'+mon):mon)+"/"+(day<10?('0'+day):day); + return s; +} +//cpu +function cpu(b,e){ + $.get('/system/get_cpu_io?start='+b+'&end='+e,function(rdata){ + var rdata = rdata.data; + var theme = getChartTheme(); + var xData = []; + var yData = []; + //var zData = []; + + for(var i = 0; i < rdata.length; i++){ + xData.push(rdata[i].addtime); + yData.push(rdata[i].pro); + //zData.push(rdata[i].mem); + } + option = { + backgroundColor: 'transparent', + tooltip: { + trigger: 'axis', + backgroundColor: theme.surface, + borderColor: theme.border, + textStyle: { color: theme.text }, + extraCssText: 'box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); border-radius: 12px; padding: 10px;', + axisPointer: { type: 'line', lineStyle: { color: theme.border } }, + formatter: '{b}
                                    {a}: {c}%' + }, + grid: { + left: '2%', + right: '3%', + bottom: '12%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xData, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted } + }, + yAxis: { + type: 'value', + name: lan.public.pre, + boundaryGap: [0, '100%'], + min:0, + max: 100, + splitLine:{ lineStyle:{ color: theme.border } }, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted }, + nameTextStyle: { color: theme.muted } + }, + dataZoom: [{ + type: 'inside', + start: 0, + end: 100, + zoomLock:true + }, { + type: 'slider', + start: 0, + end: 100, + height: 18, + backgroundColor: applyColorAlpha(theme.border, 0.2), + fillerColor: applyColorAlpha(theme.primary, 0.18), + borderColor: 'transparent', + textStyle: { color: theme.muted }, + handleStyle: { + color: theme.surface, + borderColor: theme.border + } + }], + series: [ + { + name:'CPU', + type:'line', + smooth:true, + showSymbol: false, + sampling: 'average', + lineStyle: { width: 2, color: theme.primary }, + itemStyle: { color: theme.primary }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.primary, 0.35) + }, { + offset: 1, + color: applyColorAlpha(theme.primary, 0.08) + }]) + }, + data: yData + } + ] + }; + initEchartWhenReady('cupview', option); + },'json'); +} + +//内存 +function mem(b,e){ + $.get('/system/get_cpu_io?start='+b+'&end='+e,function(rdata){ + var rdata = rdata.data; + var theme = getChartTheme(); + var xData = []; + //var yData = []; + var zData = []; + + for(var i = 0; i < rdata.length; i++){ + xData.push(rdata[i].addtime); + //yData.push(rdata[i].pro); + zData.push(rdata[i].mem); + } + option = { + backgroundColor: 'transparent', + tooltip: { + trigger: 'axis', + backgroundColor: theme.surface, + borderColor: theme.border, + textStyle: { color: theme.text }, + extraCssText: 'box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); border-radius: 12px; padding: 10px;', + axisPointer: { type: 'line', lineStyle: { color: theme.border } }, + formatter: '{b}
                                    {a}: {c}%' + }, + grid: { + left: '2%', + right: '3%', + bottom: '12%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xData, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted } + }, + yAxis: { + type: 'value', + name: lan.public.pre, + boundaryGap: [0, '100%'], + min:0, + max: 100, + splitLine:{ lineStyle:{ color: theme.border } }, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted }, + nameTextStyle: { color: theme.muted } + }, + dataZoom: [{ + type: 'inside', + start: 0, + end: 100, + zoomLock:true + }, { + type: 'slider', + start: 0, + end: 100, + height: 18, + backgroundColor: applyColorAlpha(theme.border, 0.2), + fillerColor: applyColorAlpha(theme.secondary, 0.18), + borderColor: 'transparent', + textStyle: { color: theme.muted }, + handleStyle: { + color: theme.surface, + borderColor: theme.border + } + }], + series: [ + { + name:lan.index.process_mem, + type:'line', + smooth:true, + showSymbol: false, + sampling: 'average', + lineStyle: { width: 2, color: theme.secondary }, + itemStyle: { color: theme.secondary }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.secondary, 0.32) + }, { + offset: 1, + color: applyColorAlpha(theme.secondary, 0.08) + }]) + }, + data: zData + } + ] + }; + initEchartWhenReady('memview', option); + },'json'); +} + +//磁盘io +function disk(b,e){ + $.get('/system/get_disk_io?start='+b+'&end='+e,function(rdata){ + var rdata = rdata.data; + var theme = getChartTheme(); + var rData = []; + var wData = []; + var xData = []; + //var yData = []; + //var zData = []; + + for(var i = 0; i < rdata.length; i++){ + rData.push((rdata[i].read_bytes/1024/60).toFixed(3)); + wData.push((rdata[i].write_bytes/1024/60).toFixed(3)); + xData.push(rdata[i].addtime); + //yData.push(rdata[i].read_count); + //zData.push(rdata[i].write_count); + } + option = { + backgroundColor: 'transparent', + tooltip: { + trigger: 'axis', + backgroundColor: theme.surface, + borderColor: theme.border, + textStyle: { color: theme.text }, + extraCssText: 'box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); border-radius: 12px; padding: 10px;', + axisPointer: { type: 'line', lineStyle: { color: theme.border } }, + formatter:"时间:{b0}
                                    {a0}: {c0} Kb/s
                                    {a1}: {c1} Kb/s" + }, + legend: { + data:['读取字节数','写入字节数'], + bottom: '2%', + textStyle: { color: theme.muted } + }, + grid: { + left: '2%', + right: '3%', + bottom: '12%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xData, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted } + }, + yAxis: { + type: 'value', + name: '单位:KB/s', + boundaryGap: [0, '100%'], + splitLine:{ lineStyle:{ color: theme.border } }, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted }, + nameTextStyle: { color: theme.muted } + }, + dataZoom: [{ + type: 'inside', + start: 0, + end: 100, + zoomLock:true + }, { + type: 'slider', + start: 0, + end: 100, + height: 18, + backgroundColor: applyColorAlpha(theme.border, 0.2), + fillerColor: applyColorAlpha(theme.primary, 0.18), + borderColor: 'transparent', + textStyle: { color: theme.muted }, + handleStyle: { + color: theme.surface, + borderColor: theme.border + } + }], + series: [ + { + name:'读取字节数', + type:'line', + smooth:true, + showSymbol: false, + sampling: 'average', + lineStyle: { width: 2, color: theme.primary }, + itemStyle: { color: theme.primary }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.primary, 0.28) + }, { + offset: 1, + color: applyColorAlpha(theme.primary, 0.08) + }]) + }, + data: rData + }, + { + name:'写入字节数', + type:'line', + smooth:true, + showSymbol: false, + sampling: 'average', + lineStyle: { width: 2, color: theme.tertiary }, + itemStyle: { color: theme.tertiary }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.tertiary, 0.28) + }, { + offset: 1, + color: applyColorAlpha(theme.tertiary, 0.08) + }]) + }, + data: wData + } + ] + }; + initEchartWhenReady('diskview', option); + },'json'); +} + +//网络Io +function network(b,e){ + $.get('/system/get_network_io?start='+b+'&end='+e,function(rdata){ + var rdata = rdata.data; + var theme = getChartTheme(); + var aData = []; + var bData = []; + var cData = []; + var dData = []; + var xData = []; + var yData = []; + var zData = []; + + for(var i = 0; i < rdata.length; i++){ + aData.push(rdata[i].total_up); + bData.push(rdata[i].total_down); + cData.push(rdata[i].down_packets); + dData.push(rdata[i].up_packets); + xData.push(rdata[i].addtime); + yData.push(rdata[i].up); + zData.push(rdata[i].down); + } + option = { + backgroundColor: 'transparent', + tooltip: { + trigger: 'axis', + backgroundColor: theme.surface, + borderColor: theme.border, + textStyle: { color: theme.text }, + extraCssText: 'box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); border-radius: 12px; padding: 10px;', + axisPointer: { type: 'line', lineStyle: { color: theme.border } }, + formatter:"时间:{b0}
                                    {a0}: {c0} Kb/s
                                    {a1}: {c1} Kb/s" + }, + legend: { + data:[lan.index.net_up,lan.index.net_down], + bottom: '2%', + textStyle: { color: theme.muted } + }, + grid: { + left: '2%', + right: '3%', + bottom: '12%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xData, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted } + }, + yAxis: { + type: 'value', + name: '单位:KB/s', + boundaryGap: [0, '100%'], + splitLine:{ lineStyle:{ color: theme.border } }, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted }, + nameTextStyle: { color: theme.muted } + }, + dataZoom: [{ + type: 'inside', + start: 0, + end: 100, + zoomLock:true + }, { + type: 'slider', + start: 0, + end: 100, + height: 18, + backgroundColor: applyColorAlpha(theme.border, 0.2), + fillerColor: applyColorAlpha(theme.secondary, 0.18), + borderColor: 'transparent', + textStyle: { color: theme.muted }, + handleStyle: { + color: theme.surface, + borderColor: theme.border + } + }], + series: [ + { + name:lan.index.net_up, + type:'line', + smooth:true, + showSymbol: false, + sampling: 'average', + lineStyle: { width: 2, color: theme.primary }, + itemStyle: { color: theme.primary }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.primary, 0.25) + }, { + offset: 1, + color: applyColorAlpha(theme.primary, 0.06) + }]) + }, + data: yData + }, + { + name:lan.index.net_down, + type:'line', + smooth:true, + showSymbol: false, + sampling: 'average', + lineStyle: { width: 2, color: theme.secondary }, + itemStyle: { color: theme.secondary }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.secondary, 0.25) + }, { + offset: 1, + color: applyColorAlpha(theme.secondary, 0.06) + }]) + }, + data: zData + } + ] + }; + initEchartWhenReady('network', option); + },'json'); +} +//负载 +function getload_old(b,e){ + $.get('/system/get_load_average?start='+b+'&end='+e,function(rdata){ + var rdata = data.data; + var aData = []; + var bData = []; + var xData = []; + var yData = []; + var zData = []; + + for(var i = 0; i < rdata.length; i++){ + xData.push(rdata[i].addtime); + yData.push(rdata[i].pro); + zData.push(rdata[i].one); + aData.push(rdata[i].five); + bData.push(rdata[i].fifteen); + } + option = { + tooltip: { + trigger: 'axis' + }, + calculable: true, + legend: { + data:['系统资源使用率','1分钟','5分钟','15分钟'], + selectedMode: 'single', + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xData, + axisLine:{ + lineStyle:{ + color:"#666" + } + } + }, + yAxis: { + type: 'value', + name: '', + boundaryGap: [0, '100%'], + splitLine:{ + lineStyle:{ + color:"#ddd" + } + }, + axisLine:{ + lineStyle:{ + color:"#666" + } + } + }, + dataZoom: [{ + type: 'inside', + start: 0, + end: 100, + zoomLock:true + }, { + start: 0, + end: 100, + handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z', + handleSize: '80%', + handleStyle: { + color: '#fff', + shadowBlur: 3, + shadowColor: 'rgba(0, 0, 0, 0.6)', + shadowOffsetX: 2, + shadowOffsetY: 2 + } + }], + series: [ + { + name:'系统资源使用率', + type:'line', + smooth:true, + symbol: 'none', + sampling: 'average', + itemStyle: { + normal: { + color: 'rgb(255, 140, 0)' + } + }, + data: yData + }, + { + name:'1分钟', + type:'line', + smooth:true, + symbol: 'none', + sampling: 'average', + itemStyle: { + normal: { + color: 'rgb(30, 144, 255)' + } + }, + data: zData + }, + { + name:'5分钟', + type:'line', + smooth:true, + symbol: 'none', + sampling: 'average', + itemStyle: { + normal: { + color: 'rgb(0, 178, 45)' + } + }, + data: aData + }, + { + name:'15分钟', + type:'line', + smooth:true, + symbol: 'none', + sampling: 'average', + itemStyle: { + normal: { + color: 'rgb(147, 38, 255)' + } + }, + data: bData + } + ] + }; + initEchartWhenReady('getloadview', option); + },'json'); +} +//系统负载 +function getload(b,e){ + $.get('/system/get_load_average?start='+b+'&end='+e,function(rdata){ + var rdata = rdata.data; + var theme = getChartTheme(); + var aData = []; + var bData = []; + var xData = []; + var yData = []; + var zData = []; + + for(var i = 0; i < rdata.length; i++){ + xData.push(rdata[i].addtime); + yData.push(rdata[i].pro); + zData.push(rdata[i].one); + aData.push(rdata[i].five); + bData.push(rdata[i].fifteen); + } + option = { + backgroundColor: 'transparent', + animation: false, + tooltip: { + trigger: 'axis', + backgroundColor: theme.surface, + borderColor: theme.border, + textStyle: { color: theme.text }, + extraCssText: 'box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); border-radius: 12px; padding: 10px;', + axisPointer: { type: 'line', lineStyle: { color: theme.border } } + }, + legend: { + data:['1分钟','5分钟','15分钟'], + right:'8%', + top:'10px', + textStyle: { color: theme.muted } + }, + axisPointer: { + link: {xAxisIndex: 'all'}, + lineStyle: { + color: theme.border, + width: 1 + } + }, + grid: [{ + top: '60px', + left: '5%', + right: '55%', + width: '40%', + height: 'auto' + }, + { + top: '60px', + left: '55%', + width: '40%', + height: 'auto' + } + ], + xAxis: [ + { + type: 'category', + axisLine: { lineStyle: { color: theme.border } }, + axisLabel: { color: theme.muted }, + data: xData + }, + { + type: 'category', + gridIndex: 1, + axisLine: { lineStyle: { color: theme.border } }, + axisLabel: { color: theme.muted }, + data: xData + } + ], + yAxis: [{ + scale: true, + name: '资源使用率%', + splitLine: { + show: true, + lineStyle:{ color: theme.border } + }, + nameTextStyle: { + color: theme.muted, + fontSize: 12, + align: 'left' + }, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted } + }, + { + scale: true, + name: '负载详情', + gridIndex: 1, + splitLine: { + show: true, + lineStyle:{ color: theme.border } + }, + nameTextStyle: { + color: theme.muted, + fontSize: 12, + align: 'left' + }, + axisLine:{ lineStyle:{ color: theme.border } }, + axisLabel: { color: theme.muted } + } + ], + dataZoom: [{ + type: 'inside', + start: 0, + end: 100, + xAxisIndex:[0,1], + zoomLock:true + }, { + xAxisIndex: [0, 1], + type: 'slider', + start: 0, + end: 100, + height: 18, + backgroundColor: applyColorAlpha(theme.border, 0.2), + fillerColor: applyColorAlpha(theme.secondary, 0.18), + borderColor: 'transparent', + textStyle: { color: theme.muted }, + handleStyle: { + color: theme.surface, + borderColor: theme.border + }, + left:'5%', + right:'5%' + }], + series: [ + { + name: '资源使用率%', + type: 'line', + smooth: true, + showSymbol: false, + lineStyle: { width: 2, color: theme.primary }, + itemStyle: { color: theme.primary }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.primary, 0.3) + }, { + offset: 1, + color: applyColorAlpha(theme.primary, 0.08) + }]) + }, + data: yData + }, + { + xAxisIndex: 1, + yAxisIndex: 1, + name: '1分钟', + type: 'line', + smooth: true, + showSymbol: false, + lineStyle: { width: 2, color: theme.secondary }, + itemStyle: { color: theme.secondary }, + data: zData + }, + { + xAxisIndex: 1, + yAxisIndex: 1, + name: '5分钟', + type: 'line', + smooth: true, + showSymbol: false, + lineStyle: { width: 2, color: theme.tertiary }, + itemStyle: { color: theme.tertiary }, + data: aData + }, + { + xAxisIndex: 1, + yAxisIndex: 1, + name: '15分钟', + type: 'line', + smooth: true, + showSymbol: false, + lineStyle: { width: 2, color: theme.accent }, + itemStyle: { color: theme.accent }, + data: bData + } + ], + textStyle: { color: theme.muted, fontSize: 12 } + } + initEchartWhenReady('getloadview', option); + },'json'); +} + +function getChartTheme() { + var styles = getComputedStyle(document.documentElement); + function resolveCssVar(value) { + if (!value) { + return value; + } + var trimmed = value.trim(); + if (trimmed.indexOf('var(') !== 0) { + return trimmed; + } + var match = trimmed.match(/var\((--[^,\s)]+)\s*(?:,\s*(.+))?\)/); + if (!match) { + return trimmed; + } + var resolved = styles.getPropertyValue(match[1]).trim(); + if (resolved) { + return resolveCssVar(resolved); + } + if (match[2]) { + return resolveCssVar(match[2].trim()); + } + return trimmed; + } + return { + primary: resolveCssVar(styles.getPropertyValue('--mw-primary')) || '#6750a4', + secondary: resolveCssVar(styles.getPropertyValue('--mdui-color-secondary')) || '#4f8ef7', + tertiary: resolveCssVar(styles.getPropertyValue('--mdui-color-tertiary')) || '#22c55e', + accent: resolveCssVar(styles.getPropertyValue('--mdui-color-primary-container')) || '#a855f7', + border: resolveCssVar(styles.getPropertyValue('--mw-border')) || '#e2e8f0', + muted: resolveCssVar(styles.getPropertyValue('--mw-muted')) || '#64748b', + surface: resolveCssVar(styles.getPropertyValue('--mw-surface')) || '#ffffff', + text: resolveCssVar(styles.getPropertyValue('--mw-text')) || '#1f1f1f' + }; +} + +function applyColorAlpha(color, alpha) { + if (!color) { + return 'rgba(0, 0, 0, ' + alpha + ')'; + } + if (color.indexOf('rgb') === 0) { + var numbers = color.replace(/[^\d,]/g, '').split(','); + if (numbers.length >= 3) { + return 'rgba(' + numbers[0] + ', ' + numbers[1] + ', ' + numbers[2] + ', ' + alpha + ')'; + } + } + if (color.indexOf('#') === 0) { + var hex = color.replace('#', ''); + if (hex.length === 3) { + hex = hex.split('').map(function (item) { return item + item; }).join(''); + } + if (hex.length === 6) { + var r = parseInt(hex.slice(0, 2), 16); + var g = parseInt(hex.slice(2, 4), 16); + var b = parseInt(hex.slice(4, 6), 16); + return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')'; + } + } + return color; +} + +var chartResizeRegistry = {}; + +function initEchartWhenReady(elementId, option, onReady) { + if (typeof echarts === 'undefined') { + return; + } + var element = document.getElementById(elementId); + if (!element) { + return; + } + var attempt = 0; + var maxAttempts = 30; + var raf = window.requestAnimationFrame || function(callback) { + return setTimeout(callback, 16); + }; + function tryInit() { + if (element.clientWidth === 0 || element.clientHeight === 0) { + if (attempt++ < maxAttempts) { + raf(tryInit); + } + return; + } + var chart = echarts.getInstanceByDom(element) || echarts.init(element); + chart.setOption(option); + if (!chartResizeRegistry[elementId]) { + chartResizeRegistry[elementId] = true; + window.addEventListener("resize", function() { + chart.resize(); + }); + } + if (onReady) { + onReady(chart); + } + } + tryInit(); +} diff --git a/web/static/app/crontab.js b/web/static/app/crontab.js new file mode 100755 index 000000000..ddca937bc --- /dev/null +++ b/web/static/app/crontab.js @@ -0,0 +1,1113 @@ +var num = 0; +//查看任务日志 +function getLogs(id){ + + var reqTimer = null; + var reqCount = 0; + + var tips = layer.msg('正在获取,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + var req_log_args = 'id='+id; + function requestLogs(layerIndex){ + + $.post('/crontab/logs', req_log_args, function(rdata){ + + if (reqCount == 0){ + layer.close(tips); + } + + if(!rdata.status) { + layer.close(layerIndex); + layer.msg(rdata.msg,{icon:2, time:2000}); + clearInterval(reqTimer); + return; + }; + + if (rdata.msg == ''){ + rdata.msg = '暂无数据!'; + } + + $("#crontab_log").html(rdata.msg); + //滚动到最低 + var ob = document.getElementById('crontab_log'); + ob.scrollTop = ob.scrollHeight; + reqCount++; + },'json'); + + } + + + layer.open({ + type:1, + title:"任务执行日志", + area: ['60%','500px'], + shadeClose:false, + btn:["清空","关闭"], + closeBtn:1, + end: function(){ + if (reqTimer){ + clearInterval(reqTimer); + } + }, + content:'
                                    ' + +'
                                    '
                                    +			// +'
                                    ' + // +'' + // +'' + // +'
                                    ' + +'
                                    ', + success:function(index,layer_index){ + requestLogs(layer_index); + reqTimer = setInterval(function(){ + requestLogs(layer_index); + },3000); + }, + + yes:function(index){ + clearInterval(reqTimer); + closeLogs(id); + layer.close(index); + }, + }); +} + + +function getBackupName(hook_data, name){ + for (var i = 0; i < hook_data.length; i++) { + if (hook_data[i]['name'] == name){ + return hook_data[i]['title']; + } + } + return name; +} + +function getCronData(page){ + var load = layer.msg(lan.public.the,{icon:16,time:0,shade: [0.3, '#000']}); + $.post("/crontab/list?p="+page,'', function(rdata){ + layer.close(load); + var cbody = ""; + if(rdata.data.length == 0){ + cbody="当前没有计划任务"; + } else { + for(var i=0;i正常' + :'停用'; + + + var cron_save = '--'; + if (rdata.data[i]['save'] != ''){ + cron_save = rdata.data[i]['save']+'份'; + } + + var cron_backupto = '-'; + if (rdata.data[i]['stype'] == 'site' || rdata.data[i]['stype']=='path' || rdata.data[i]['stype']=='database' || rdata.data[i]['stype'].indexOf('database_')>-1 ){ + cron_backupto = '本地磁盘'; + if (rdata.data[i]['backup_to'] != 'localhost'){ + cron_backupto = getBackupName(rdata['backup_hook'],rdata.data[i]['backup_to']); + } + } + + cbody += "\ + "+rdata.data[i].name+"\ + "+status+"\ + "+rdata.data[i].type+"\ + "+rdata.data[i].cycle+"\ + "+cron_save +"\ + "+cron_backupto+"\ + "+rdata.data[i].add_time+"\ + \ + 执行 | \ + 编辑 | \ + 日志 | \ + 删除\ + \ + "; + } + } + $('#cronbody').html(cbody); + $('#softPage').html(rdata.page); + },'json'); +} + +// 设置计划任务状态 +function setTaskStatus(id,status){ + var confirm = layer.confirm(status == '0'?'计划任务暂停后将无法继续运行,您真的要停用这个计划任务吗?':'该计划任务已停用,是否要启用这个计划任务', {title:'提示',icon:3,closeBtn:1},function(index) { + if (index > 0) { + var loadT = layer.msg('正在设置状态,请稍后...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/crontab/set_cron_status',{id:id},function(rdata){ + + if (!rdata.status){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + return; + } + + showMsg(rdata.msg,function(){ + layer.close(loadT); + layer.close(confirm); + getCronData(1); + },{icon:rdata.status?1:2},2000); + + },'json'); + } + }); +} + +//执行任务脚本 +function startTask(id){ + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + var data='id='+id; + $.post('/crontab/start_task',data,function(rdata){ + showMsg(rdata.msg, function(){ + },{icon:rdata.status?1:2,time:2000}); + },'json'); +} + + +//清空日志 +function closeLogs(id){ + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + var data='id='+id; + $.post('/crontab/del_logs',data,function(rdata){ + showMsg(rdata.msg, function(){ + // layer.closeAll(); + },{icon:rdata.status?1:2,time:2000}); + },'json'); +} + + +//删除 +function planDel(id,name){ + safeMessage(lan.get('del',[name]),'您确定要删除该任务吗?',function(){ + var load = layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + var data='id='+id; + $.post('/crontab/del',data,function(rdata){ + showMsg(rdata.msg, function(){ + layer.closeAll(); + getCronData(1); + },{icon:rdata.status?1:2,time:2000}); + },'json'); + }); +} + +function isURL(str_url){ + var strRegex = '^(https|http|ftp|rtsp|mms)?://.+'; + var re=new RegExp(strRegex); + if (re.test(str_url)){ + return (true); + }else{ + return (false); + } +} + + +//提交 +function planAdd(){ + var name = $(".planname input[name='name']").val(); + if(name == ''){ + $(".planname input[name='name']").focus(); + layer.msg('任务名称不能为空!',{icon:2}); + return; + } + $("#cronConfig input[name='name']").val(name); + + var time_type = $(".plancycle").find("b").attr("val"); + $("#cronConfig input[name='type']").val(time_type); + + + var is1; + var is2 = 1; + switch(time_type){ + case 'day-n': + is1=31; + break; + case 'hour-n': + is1=23; + break; + case 'minute-n': + is1=59; + break; + case 'month': + is1=31; + break; + } + + var where1 = $('#excode_week b').attr('val'); + $("#cronConfig input[name='where1']").val(where1); + + if(where1 > is1 || where1 < is2){ + $("#ptime input[name='where1']").focus(); + layer.msg('表单不合法,请重新输入!',{icon:2}); + return; + } + + var hour = $("#ptime input[name='hour']").val(); + if(hour > 23 || hour < 0){ + $("#ptime input[name='hour']").focus(); + layer.msg('小时值不合法!',{icon:2}); + return; + } + $("#cronConfig input[name='hour']").val(hour); + var minute = $("#ptime input[name='minute']").val(); + if(minute > 59 || minute < 0){ + $("#ptime input[name='minute']").focus(); + layer.msg('分钟值不合法!',{icon:2}); + return; + } + $("#cronConfig input[name='minute']").val(minute); + + var save = $("#save").val(); + if(save < 0){ + layer.msg('不能有负数!',{icon:2}); + return; + } + + $("#cronConfig input[name='save']").val(save); + $("#cronConfig input[name='week']").val($(".planweek").find("b").attr("val")); + + var cron_type = $(".planjs").find("b").attr("val"); + var sBody = encodeURIComponent($("#implement textarea[name='sbody']").val()); + + if (cron_type == 'toShell'){ + if(sBody == ''){ + $("#implement textarea[name='sbody']").focus(); + layer.msg('脚本代码不能为空!',{icon:2}); + return; + } + } + + if(cron_type == 'toFile'){ + if($("#viewfile").val() == ''){ + layer.msg('请选择脚本文件!',{icon:2}); + return; + } + } + + var url_address = $("#url_address").val(); + if(cron_type == 'toUrl'){ + if(!isURL(url_address)){ + layer.msg('URL地址不正确!',{icon:2}); + $("implement textarea[name='url_address']").focus(); + return; + } + } + // url_address = encodeURIComponent(url_address); + $("#cronConfig input[name='url_address']").val(url_address); + $("#cronConfig input[name='stype']").val(cron_type); + $("#cronConfig textarea[name='sbody']").val(decodeURIComponent(sBody)); + + if(cron_type == 'site' || cron_type == 'database' || cron_type.indexOf('database_')>-1 || cron_type == 'path'){ + var backupTo = $(".planBackupTo").find("b").attr("val"); + $("#backup_to").val(backupTo); + } + + if (cron_type=='site' || cron_type=='path'){ + var attr = $("#exclude_dir textarea[name='exclude_dir']").val(); + $("#attr").val(attr); + } + + var sname = $("#sname").attr("val"); + $("#cronConfig input[name='sname']").val(sname); + + // if(sName == 'backupAll'){ + // var alist = $("ul[aria-labelledby='backdata'] li a"); + // var dataList = new Array(); + // for(var i=1;i'+rdata.data[i].name+'['+rdata.data[i].ps+']'; + } + + + if (sType != 'path'){ + sOpt = '
                                  • 所有
                                  • ' + sOpt; + } + + var orderOpt = ''; + for (var i=0;i'+rdata.orderOpt[i].title+'' + } + + + var changeDir = ''; + if (sType == 'path'){ + changeDir = ''; + } + + var sBody = '\ + '+ changeDir +'\ + \ +
                                    保留最新
                                    \ + \ + \ +
                                    '; + $("#implement").html(sBody); + getselectname(); + + $('.changePathDir').click(function(){ + changePathCallback($('#sname').val(),function(select_dir){ + $(".planname input[name='name']").val('备份目录['+select_dir+']'); + $('#implement .sname b').attr('val',select_dir).text(select_dir); + }); + }); + + + $(".dropdown ul li a").click(function(){ + var sname = $("#sname").attr("val"); + if(!sname) return; + $(".planname input[name='name']").val(sMsg+'['+sname+']'); + }); + },'json'); + +} + +//备份 +function toBackup(type){ + var sMsg = ""; + switch(type){ + case 'sites': + sMsg = '备份网站'; + sType = "sites"; + break; + case 'database_mariadb': + case 'database_mongodb': + case 'database_postgresql': + case 'database_mysql-apt': + case 'database_mysql-yum': + case 'database': + sMsg = '备份数据库'; + suffix = type.replace('database','') + if (suffix != ''){ + suffix = suffix.replace('_','') + sMsg = '备份数据库['+suffix+']'; + } + sType = type; + break; + case 'logs': + sMsg = '切割日志'; + sType = "logs"; + break; + case 'path': + sMsg = '备份目录'; + sType = "path"; + break; + } + var data = 'type='+sType; + + $.post('/crontab/get_data_list',data,function(rdata){ + $(".planname input[name='name']").css({"background-color":"#f6f6f6","color":"#666"}); + var sOpt = ""; + if(rdata.data.length == 0){ + layer.msg(lan.public.list_empty,{icon:2}) + return; + } + + for(var i=0;i'+rdata.data[i].name+'['+rdata.data[i].ps+']'; + } + + + if (sType != 'path'){ + sOpt = '
                                  • 所有
                                  • ' + sOpt; + } + + var orderOpt = ''; + for (var i=0;i'+rdata.orderOpt[i].title+'' + } + + + var changeDir = ''; + if (sType == 'path'){ + changeDir = ''; + } + + var sBody = '\ + '+ changeDir +'\ +
                                    备份到
                                    \ + \ +
                                    保留最新
                                    \ + \ + \ +
                                    '; + $("#implement").html(sBody); + getselectname(); + + $('.changePathDir').click(function(){ + changePathCallback($('#sname').val(),function(select_dir){ + $(".planname input[name='name']").val('备份目录['+select_dir+']'); + $('#implement .sname b').attr('val',select_dir).text(select_dir); + }); + }); + + + $(".dropdown ul li a").click(function(){ + var sname = $("#sname").attr("val"); + if(!sname) return; + $(".planname input[name='name']").val(sMsg+'['+sname+']'); + }); + },'json'); + +} + + +// 编辑计划任务 +function editTaskInfo(id){ + layer.msg('正在获取,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/crontab/get_crond_find',{id:id},function(rdata){ + layer.closeAll(); + // console.log('get_crond_find:', rdata); + var sTypeName = '',sTypeDom = '',cycleName = '',cycleDom = '',weekName = '',weekDom = '',sNameName ='',sNameDom = '',backupsName = '',backupsDom =''; + obj = { + from:{ + id:rdata.id, + name: rdata.name, + type: rdata['type'], + stype: rdata.stype, + where1: rdata.where1, + hour: rdata.where_hour, + minute: rdata.where_minute, + week: rdata.where1, + sbody: rdata.sbody, + sname: rdata.sname, + backup_to: rdata.backup_to, + save: rdata.save, + url_address: rdata.url_address, + attr:rdata.attr, + }, + sTypeArray:[['toShell','Shell脚本'],['site','备份网站'],['database','备份数据库'],['logs','日志切割'],['path','备份目录'],['rememory','释放内存'],['toUrl','访问URL']], + cycleArray:[['day','每天'],['day-n','N天'],['hour','每小时'],['hour-n','N小时'],['minute-n','N分钟'],['week','每星期'],['month','每月']], + weekArray:[[1,'周一'],[2,'周二'],[3,'周三'],[4,'周四'],[5,'周五'],[6,'周六'],[7,'周日']], + sNameArray:[], + backupsArray:[], + create:function(callback){ + if (obj.from['stype'].indexOf('database_')>-1){ + name = obj.from['stype'].replace('database_',''); + sTypeName = '备份数据库['+name+']'; + sTypeDom += '
                                  • '+ sTypeName +'
                                  • '; + } else { + for(var i = 0; i '+ obj['sTypeArray'][i][1] +''; + } + } + + for(var i = 0; i '+ obj['cycleArray'][i][1] +''; + } + + for(var i = 0; i '+ obj['weekArray'][i][1] +''; + } + + if(obj.from.stype == 'site' || obj.from.stype == 'database' || obj.from.stype == 'path' || obj.from.stype == 'logs' || obj.from['stype'].indexOf('database_')>-1){ + $.post('/crontab/get_data_list',{type:obj.from.stype},function(rdata){ + // console.log(rdata); + obj.sNameArray = rdata.data; + obj.sNameArray.unshift({name:'ALL',ps:'所有'}); + obj.backupsArray = rdata.orderOpt; + obj.backupsArray.unshift({title:'服务器磁盘',name:'localhost'}); + for(var i = 0; i '+ obj['sNameArray'][i]['ps'] +''; + } + for(var i = 0; i '+ obj['backupsArray'][i]['title'] +''; + } + callback(); + },'json'); + }else{ + callback(); + } + } + }; + obj.create(function(){ + + var changeDir = ''; + if (obj.from.stype == 'path'){ + changeDir = ''; + } + + var exclude_dirs_placeholder = "每行一条规则,目录不能以/结尾,示例:\r\n.git \ +\r\nstatic/upload \ +\r\n*.log"; + layer.open({ + type:1, + title:'编辑计划任务-['+rdata.name+']', + area: ['900px','440px'], + skin:'layer-create-content', + shadeClose:false, + closeBtn:1, + content:'
                                    \ +
                                    \ + 任务类型\ + \ +
                                    \ +
                                    \ + 任务名称\ +
                                    \ +
                                    \ +
                                    \ + 执行周期\ + \ +
                                    \ + \ +
                                    \ +
                                    \ +
                                    \ +
                                    \ +
                                    \ + \ +
                                    \ + 脚本内容\ +
                                    \ +
                                    \ +
                                    \ + 排除目录\ +
                                    \ +
                                    \ +
                                    \ + 提示\ +
                                    释放PHP、MYSQL、PURE-FTPD、OpenResty的内存占用,建议在每天半夜执行!
                                    \ +
                                    \ +
                                    \ + URL地址\ +
                                    \ +
                                    \ +
                                    \ +
                                    保存编辑
                                    \ +
                                    \ +
                                    ', + + success:function(){ + + $('.changePathDir').click(function(){ + changePathCallback($('#sName').val(),function(select_dir){ + $('input[name="name"]').val('备份目录['+select_dir+']'); + $('.sName_btn .sname b').attr('val',select_dir).text(select_dir); + obj.from.sname = select_dir; + }); + }); + + if(obj.from.stype == 'toShell'){ + $('.site_list').hide(); + } else if (obj.from.stype == 'rememory') { + $('.site_list').hide(); + } else if ( obj.from.stype == 'toUrl'){ + $('.site_list').hide(); + } else { + $('.site_list').show(); + } + + obj.from.minute = $('.minute_create').val(); + obj.from.hour = $('.hour_create').val(); + obj.from.where1 = $('.where1_create').val(); + + $('.sname_create').blur(function () { + obj.from.name = $(this).val(); + }); + $('.where1_create').blur(function () { + obj.from.where1 = $(this).val(); + }); + + $('.hour_create').blur(function () { + obj.from.hour = $(this).val(); + }); + + $('.minute_create').blur(function () { + obj.from.minute = $(this).val(); + }); + + $('.save_create').blur(function () { + obj.from.save = $(this).val(); + }); + + $('.sbody_create').blur(function () { + obj.from.sbody = $(this).val(); + }); + + $('.attr_create').blur(function () { + obj.from.attr = $(this).val(); + }); + + + $('.url_create').blur(function () { + obj.from.url_address = $(this).val(); + }); + + $('[aria-labelledby="cycle"] a').unbind().click(function () { + $('.cycle_btn').find('b').attr('val',$(this).attr('value')).html($(this).html()); + var type = $(this).attr('value'); + switch(type){ + case 'day': + $('.week_btn').hide(); + $('.where1_input').hide(); + $('.hour_input').show().find('input').val('1'); + $('.minute_input').show().find('input').val('30'); + obj.from.week = ''; + obj.from.type = ''; + obj.from.hour = 1; + obj.from.minute = 30; + break; + case 'day-n': + $('.week_btn').hide(); + $('.where1_input').show().find('input').val('1'); + $('.hour_input').show().find('input').val('1'); + $('.minute_input').show().find('input').val('30'); + obj.from.week = ''; + obj.from.where1 = 1; + obj.from.hour = 1; + obj.from.minute = 30; + break; + case 'hour': + $('.week_btn').hide(); + $('.where1_input').hide(); + $('.hour_input').hide(); + $('.minute_input').show().find('input').val('30'); + obj.from.week = ''; + obj.from.where1 = ''; + obj.from.hour = ''; + obj.from.minute = 30; + break; + case 'hour-n': + $('.week_btn').hide(); + $('.where1_input').hide(); + $('.hour_input').show().find('input').val('1'); + $('.minute_input').show().find('input').val('30'); + obj.from.week = ''; + obj.from.where1 = ''; + obj.from.hour = 1; + obj.from.minute = 30; + break; + case 'minute-n': + $('.week_btn').hide(); + $('.where1_input').hide(); + $('.hour_input').hide(); + $('.minute_input').show(); + obj.from.week = ''; + obj.from.where1 = ''; + obj.from.hour = ''; + obj.from.minute = 30; + console.log(obj.from); + break; + case 'week': + $('.week_btn').show(); + $('.where1_input').hide(); + $('.hour_input').show(); + $('.minute_input').show(); + obj.from.week = 1; + obj.from.where1 = ''; + obj.from.hour = 1; + obj.from.minute = 30; + break; + case 'month': + $('.week_btn').hide(); + $('.where1_input').show(); + $('.hour_input').show(); + $('.minute_input').show(); + obj.from.week = ''; + obj.from.where1 = 1; + obj.from.hour = 1; + obj.from.minute = 30; + break; + } + obj.from.type = $(this).attr('value'); + }); + + $('[aria-labelledby="week"] a').unbind().click(function () { + $('.week_btn').find('b').attr('val',$(this).attr('value')).html($(this).html()); + obj.from.week = $(this).attr('value'); + }); + + $('[aria-labelledby="backupTo"] a').unbind().click(function () { + $('.backup_btn').find('b').attr('val',$(this).attr('value')).html($(this).html()); + obj.from.backup_to = $(this).attr('value'); + }); + $('.plan-submits').unbind().click(function(){ + if(obj.from.type == 'hour-n'){ + obj.from.where1 = obj.from.hour; + obj.from.hour = ''; + } else if(obj.from.type == 'minute-n') { + obj.from.where1 = obj.from.minute; + obj.from.minute = ''; + } else if(obj.from.type == 'week') { + obj.from.where1 = obj.from.week; + } + var loadT = layer.msg('正在保存编辑内容,请稍后...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/crontab/modify_crond',obj.from,function(rdata){ + + if (!rdata.status){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + return; + } + + showMsg(rdata.msg, function(){ + layer.closeAll(); + getCronData(1); + initDropdownMenu(); + },{icon:rdata.status?1:2}, 2000); + + },'json'); + }); + } + ,cancel: function(){ + initDropdownMenu(); + } + }); + }); + },'json'); +} + + +//下拉菜单名称 +function getselectname(){ + $(".dropdown ul li a").click(function(){ + var txt = $(this).text(); + var type = $(this).attr("value"); + $(this).parents(".dropdown").find("button b").text(txt).attr("val",type); + }); +} +//清理 +function closeOpt(){ + $("#ptime").html(''); +} +//星期 +function toWeek(){ + var mBody = ''; + $("#ptime").html(mBody); + getselectname(); +} +//指定1 +function toWhere1(ix){ + var mBody ='
                                    \ + \ + '+ix+'\ +
                                    '; + $("#ptime").append(mBody); +} +//小时 +function toHour(){ + var mBody = '
                                    \ + \ + 小时\ +
                                    '; + $("#ptime").append(mBody); +} + +//分钟 +function toMinute(){ + var mBody = '
                                    \ + \ + 分钟\ +
                                    '; + $("#ptime").append(mBody); + +} + +//从文件 +function toFile(){ + var tBody = '\ + '; + $("#implement").html(tBody); + $(".planname input[name='name']").removeAttr('readonly style').val(""); +} + +//从脚本 +function toShell(){ + var tBody = ""; + $("#implement").html(tBody); + $(".planname input[name='name']").removeAttr('readonly style').val(""); +} + +//从脚本 +function toUrl(){ + var tBody = ""; + $("#implement").html(tBody); + $(".planname input[name='name']").removeAttr('readonly style').val(""); +} + +//释放内存 +function rememory(){ + $(".planname input[name='name']").removeAttr('readonly style').val(""); + $(".planname input[name='name']").val('释放内存'); + $("#implement").html('释放PHP、MYSQL、PURE-FTPD、APACHE、NGINX的内存占用,建议在每天半夜执行!'); + return; +} +//上传 +function fileupload(){ + $("#sFile").change(function(){ + $("#viewfile").val($("#sFile").val()); + }); + $("#sFile").click(); +} \ No newline at end of file diff --git a/web/static/app/files.js b/web/static/app/files.js new file mode 100755 index 000000000..39e27bb0b --- /dev/null +++ b/web/static/app/files.js @@ -0,0 +1,1914 @@ +//判断磁盘数量超出宽度 +function isDiskWidth(){ + $("#comlist").css({ + "width": "auto", + "flex": "1 1 auto", + "display": "flex", + "flex-wrap": "wrap", + "align-items": "center", + "gap": "8px", + "min-width": "180px", + "max-width": "100%", + "height": "auto", + "overflow": "visible" + }); +} + +function syncFileToolbarLayout(){ + isDiskWidth(); +} + +//打开回收站 +function recycleBin(type){ + $.post('/files/get_recycle_bin','',function(data){ + // console.log(rdata); + var rdata = data['data']; + var body = '' + switch(type){ + case 1: + for(var i=0;i 20) shortwebname = shortwebname.substring(0, 20) + "..."; + if(shortpath.length > 20) shortpath = shortpath.substring(0, 20) + "..."; + body += '\ + '+shortwebname+'\ + '+shortpath+'\ + '+toSize(rdata.dirs[i].size)+'\ + '+getLocalTime(rdata.dirs[i].time)+'\ + \ + '+lan.files.recycle_bin_re+'\ + | '+lan.files.recycle_bin_del+'\ + \ + '; + } + for(var i=0;i 20) shortwebname = shortwebname.substring(0, 20) + "..."; + if(shortpath.length > 20) shortpath = shortpath.substring(0, 20) + "..."; + body += '\ + '+shortwebname.replace('BTDB_','')+'\ + mysql://'+shortpath.replace('BTDB_','')+'\ + -\ + '+getLocalTime(rdata.files[i].time)+'\ + \ + '+lan.files.recycle_bin_re+'\ + | '+lan.files.recycle_bin_del+'\ + \ + '; + + continue; + } + var shortwebname = rdata.files[i].name.replace(/'/,"\\'"); + var shortpath = rdata.files[i].dname; + if(shortwebname.length > 20) shortwebname = shortwebname.substring(0, 20) + "..."; + if(shortpath.length > 20) shortpath = shortpath.substring(0, 20) + "..."; + body += '\ + '+shortwebname+'\ + '+shortpath+'\ + '+toSize(rdata.files[i].size)+'\ + '+getLocalTime(rdata.files[i].time)+'\ + \ + '+lan.files.recycle_bin_re+'\ + | '+lan.files.recycle_bin_del+'\ + \ + ' + } + $("#RecycleBody").html(body); + return; + break; + case 2: + for(var i=0;i 20) shortwebname = shortwebname.substring(0, 20) + "..."; + if(shortpath.length > 20) shortpath = shortpath.substring(0, 20) + "..."; + body += '\ + '+shortwebname+'\ + '+shortpath+'\ + '+toSize(rdata.dirs[i].size)+'\ + '+getLocalTime(rdata.dirs[i].time)+'\ + \ + '+lan.files.recycle_bin_re+'\ + | '+lan.files.recycle_bin_del+'\ + \ + ' + } + $("#RecycleBody").html(body); + return; + break; + case 3: + for(var i=0;i 20) shortwebname = shortwebname.substring(0, 20) + "..."; + if(shortpath.length > 20) shortpath = shortpath.substring(0, 20) + "..."; + body += '\ + '+shortwebname+'\ + '+shortpath+'\ + '+toSize(rdata.files[i].size)+'\ + '+getLocalTime(rdata.files[i].time)+'\ + \ + '+lan.files.recycle_bin_re+'\ + | '+lan.files.recycle_bin_del+'\ + \ + '; + } + $("#RecycleBody").html(body); + return; + break; + case 4: + for(var i=0;i 20) shortwebname = shortwebname.substring(0, 20) + "..."; + if(shortpath.length > 20) shortpath = shortpath.substring(0, 20) + "..."; + body += '\ + '+shortwebname+'\ + '+shortpath+'\ + '+toSize(rdata.files[i].size)+'\ + '+getLocalTime(rdata.files[i].time)+'\ + \ + '+lan.files.recycle_bin_re+'\ + | '+lan.files.recycle_bin_del+'\ + \ + '; + } + } + $("#RecycleBody").html(body); + return; + break; + case 5: + for(var i=0;i 20) shortwebname = shortwebname.substring(0, 20) + "..."; + if(shortpath.length > 20) shortpath = shortpath.substring(0, 20) + "..."; + body += '\ + '+shortwebname+'\ + '+shortpath+'\ + '+toSize(rdata.files[i].size)+'\ + '+getLocalTime(rdata.files[i].time)+'\ + \ + '+lan.files.recycle_bin_re+'\ + | '+lan.files.recycle_bin_del+'\ + \ + ' + } + } + $("#RecycleBody").html(body); + return; + } + + + var tablehtml = '
                                    \ +
                                    \ + 文件回收站\ +
                                    \ + \ + \ +
                                    \ +
                                    \ + 注意:关闭回收站,删除的文件无法恢复!\ + \ +
                                    \ +
                                    \ +
                                    \ +

                                    全部

                                    \ +

                                    文件夹

                                    \ +

                                    文件

                                    \ +

                                    图片

                                    \ +

                                    文档

                                    \ +
                                    \ +
                                    \ +
                                    \ + \ + \ + \ + \ + \ + \ + \ + \ + '+body+'\ +
                                    文件名原位置大小删除时间操作
                                    '; + if(type == 'open'){ + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: ['80%','606px'], + title: lan.files.recycle_bin_title, + content: tablehtml + }); + + if(window.location.href.indexOf("database") != -1){ + recycleBin(6); + $(".re-con-menu p:last-child").addClass("on").siblings().removeClass("on"); + }else{ + recycleBin(1); + } + } + $(".re-con-menu p").click(function(){ + $(this).addClass("on").siblings().removeClass("on"); + }) + },'json'); +} + +//去扩展名不处理 +function getFileName(name){ + var text = name.split("/"); + var n = text.length-1; + text = text[n]; + return text; +} + +//判断图片文件 +function reisImage(fileName){ + var exts = ['jpg','jpeg','png','bmp','gif','tiff','ico']; + for(var i=0; i正在删除,请稍候...",{icon:16,time:0,shade: [0.3, '#000']}); + setTimeout(function(){ + getSpeed('.myspeed'); + },1000); + $.post('/files/close_recycle_bin', '', function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:5}); + $("#RecycleBody").html(''); + },'json'); + }); +} + + +//回收站开关 +function setRecycleBin(db){ + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + var data = {} + if(db == 1){ + data = {db:db}; + } + $.post('/files/recycle_bin',data,function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:5}); + },'json'); +} + +function openFilename(obj){ + var path = $(obj).attr('data-path'); + var ext = getSuffixName(path); + + // console.log(path,ext); + if (inArray(ext,['html','htm','php','lua','rs','py','txt','md','js','css','scss','json','c','h','pl','java','log','conf','sh','json','ini', 'yml','yaml'])){ + onlineEditFile(0, path); + } + + if (inArray(ext,['png','jpeg','jpg','gif','webp','bmp','ico'])){ + getImage(path); + } + + if (inArray(ext,['svg'])){ + var url = '/files/download?filename='+path; + layer.open({ + type:1, + closeBtn: 1, + title:"SVG预览", + area: ['600px','500px'], + maxmin:true, + shadeClose: true, + content: '' + }); + } +} + +function searchFile(p){ + getFiles(p); +} + +//处理排序 +function listFileOrder(skey, obj){ + var or = getCookie('file_order'); + var orderType = 'desc'; + if(or){ + var or_arr = or.split('|'); + if(or.split('|')[1] == 'desc'){ + orderType = 'asc'; + } else if (or_arr[1] == 'asc'){ + orderType = 'none'; + } else { + orderType = 'desc'; + } + } + setCookie('file_order',skey + '|' + orderType); + getFiles(1); + // console.log(obj,orderType); + // if(orderType == 'desc'){ + // $(obj).find(".glyphicon-triangle-top").remove(); + // $(obj).append(""); + // } else { + // $(obj).find(".glyphicon-triangle-bottom").remove(); + // $(obj).append(""); + // } +} + +function makeFilePage(showRow, page = ''){ + var rows = ['10','50','100','200','500','1000','2000']; + var rowOption = ''; + for(var i=0;i'+rows[i]+''; + } + + //分页 + $("#filePage").html(page); + $("#filePage div").append("每页"); + $("#filePage .Pcount").css("left","16px"); +} + +//取数据 +function getFiles(Path) { + if(isNaN(Path)){ + var p = 1; + } else { + var p = Path; + Path = getCookie('open_dir_path'); + } + + var post = {}; + post['path'] = Path; + post['p'] = p; + + var file_row = $.cookie('file_row'); + if(!file_row) { + file_row = '100'; + } + post['row'] = file_row; + + var body = ''; + var totalSize = 0; + + var search = ''; + var search_file = $("#search_file").val(); + + if(search_file.length > 0){ + post['search'] = search_file; + } + + var search_all = ''; + var all = $('#search_all').hasClass('active'); + if(all){ + post['all'] = 'yes'; + } + + var file_order = $.cookie('file_order'); + if (file_order){ + post['order'] = file_order.replace('|',' '); + } + + + var loadT = layer.load(); + $.post('/files/get_dir', post, function(rdata) { + layer.close(loadT); + + //构建分页 + makeFilePage(file_row,rdata.page); + + if(rdata.dir == null) { + rdata.dir = []; + } + + for (var i = 0; i < rdata.dir.length; i++) { + var fmp = rdata.dir[i].split(";"); + var cnametext =fmp[0] + fmp[5]; + + fmp[0] = fmp[0].replace(/'/, "\\'"); + if(cnametext.length>20){ + cnametext = cnametext.substring(0,20) + '...'; + } + + if(isChineseChar(cnametext)){ + if(cnametext.length>10){ + cnametext = cnametext.substring(0,10) + '...'; + } + } + + var timetext ='--'; + if(getCookie('rank') == 'a'){ + //列表展示 + $("#set_list").addClass("active"); + $("#set_icon").removeClass("active"); + body += "\ + \ + \ + " + cnametext + "\ + "+toSize(fmp[1])+"\ + "+getLocalTime(fmp[2])+"\ + "+fmp[3]+"\ + "+fmp[4]+"\ + \ + 复制 | \ + 剪切 | \ + 重命名 | \ + 权限 | \ + 压缩 | \ + 删除\ + \ + "; + } else { + //图标展示 + $("#set_icon").addClass("active"); + $("#set_list").removeClass("active"); + body += ""; + } + } + + for (var i = 0; i < rdata.files.length; i++) { + if(rdata.files[i] == null) continue; + var fmp = rdata.files[i].split(";"); + var bodyZip = ''; + var download = ''; + var cnametext =fmp[0] + fmp[5]; + fmp[0] = fmp[0].replace(/'/,"\\'"); + + if(isChineseChar(cnametext)){ + if(cnametext.length>16){ + cnametext = cnametext.substring(0,16) + '...'; + } + } else{ + if( cnametext.length > 48 ){ + cnametext = cnametext.substring(0,48) + '...'; + } + } + + var displayCompress = 1; + if(isCompressFile(fmp[0])){ + bodyZip = "解压 | "; + } else { + bodyZip = "压缩 | "; + } + + if(isText(fmp[0])){ + bodyZip += "编辑 | "; + } + + if(isImage(fmp[0])){ + download = "预览 | "; + } else { + download = "下载 | "; + } + + totalSize += parseInt(fmp[1]); + if(getCookie("rank")=="a"){ + body += "\ + \ + " + cnametext + "\ + " + (toSize(fmp[1])) + "\ + " + ((fmp[2].length > 11)?fmp[2]:getLocalTime(fmp[2])) + "\ + "+fmp[3]+"\ + "+fmp[4]+"\ + \ + 复制 | \ + 剪切 | \ + 重命名 | \ + 权限 | " + + bodyZip + + download + + "删除\ + \ + "; + } + else{ + body += ""; + } + } + var dirInfo = '(共{1}个目录与{2}个文件,大小:'.replace('{1}',rdata.dir.length+'').replace('{2}',rdata.files.length+'')+'' + + (toSize(totalSize))+'获取)'; + $("#dir_info").html(dirInfo); + if( getCookie('rank') == 'a' ){ + + // console.log(post['order']); + var size_icon = ''; + if (post['order'] == 'size desc'){ + size_icon = ''; + } else if (post['order'] == 'size asc'){ + size_icon = ''; + } else { + size_icon = ''; + } + + var mtime_icon = ''; + if (post['order'] == 'mtime desc'){ + mtime_icon = ''; + } else if (post['order'] == 'mtime asc'){ + mtime_icon = ''; + } else { + mtime_icon = ''; + } + + var fname_icon = ''; + if (post['order'] == 'fname desc'){ + fname_icon = ''; + } else if (post['order'] == 'fname asc'){ + fname_icon = ''; + } else { + fname_icon = ''; + } + + var tablehtml = '\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '+body+'\ +
                                    文件名'+fname_icon+'大小'+size_icon+'修改时间'+mtime_icon+'权限所有者操作
                                    '; + $("#fileCon").removeClass("fileList").html(tablehtml); + syncFileToolbarLayout(); + } else { + $("#fileCon").addClass("fileList").html(body); + syncFileToolbarLayout(); + } + $("#DirPathPlace input").val(rdata.path); + var BarTools = '
                                    \ + \ + \ +
                                    '; + if (rdata.path != '/') { + BarTools += ' '; + } + setCookie('open_dir_path',rdata.path); + BarTools += ' \ + '; + var copyName = getCookie('copyFileName'); + var cutName = getCookie('cutFileName'); + var isPaste = (copyName == 'null') ? cutName : copyName; + // console.log('isPaste:',isPaste); + //--- + if (isPaste != 'null' && isPaste != undefined) { + BarTools += ' '; + } + + $("#Batch").html(''); + var batchTools = ''; + var isBatch = getCookie('BatchSelected'); + if (isBatch == 1 || isBatch == '1') { + batchTools += ' '; + } + $("#Batch").html(batchTools); + + $("#setBox").prop("checked", false); + + $("#BarTools").html(BarTools); + + $("input[name=id]").click(function(){ + if($(this).prop("checked")) { + $(this).prop("checked", true); + $(this).parents("tr").addClass("ui-selected"); + } + else{ + $(this).prop("checked", false); + $(this).parents("tr").removeClass("ui-selected"); + } + showSeclect(); + }); + + $("#setBox").click(function() { + if ($(this).prop("checked")) { + $("input[name=id]").prop("checked", true); + $("#filesBody > tr").addClass("ui-selected"); + + } else { + $("input[name=id]").prop("checked", false); + $("#filesBody > tr").removeClass("ui-selected"); + } + showSeclect(); + }); + //阻止冒泡 + $("#filesBody .btlink").click(function(e){ + e.stopPropagation(); + }); + $("input[name=id]").dblclick(function(e){ + e.stopPropagation(); + }); + + + //禁用右键 + $("#fileCon").bind("contextmenu",function(e){ + return false; + }); + bindselect(); + + // //绑定右键 + $("#fileCon").mousedown(function(e){ + var count = totalFile(); + if(e.which == 3) { + if(count>1){ + rightMenuClickAll(e); + } else { + return; + } + } + }); + + $(".folderBox,.folderBoxTr").mousedown(function(e){ + var box = $(this); + var option = rightMenuClick(box.attr("filetype"),box.attr("data-path"),box.find("input").val()); + box.contextify(option); + }); + + //每页行数 + $(".showRow").change(function(){ + setCookie('file_row',$(this).val()); + getFiles(p); + }); + pathPlaceBtn(rdata.path); + },'json'); + // setTimeout(function(){getCookie('open_dir_path');},200); +} + + +//统计选择数量 +function totalFile(){ + var el = $("input[name='id']"); + var len = el.length; + var count = 0; + for(var i=0;i 1){ + batchTools = '\ + \ + \ + \ + '; + }else{ + //setCookie('BatchSelected', null); + } + $("#Batch").html(batchTools); +} + +//滚动条事件 +$(window).scroll(function () { + var toolbar = $("#tipTools"); + if (!toolbar.length) { + return; + } + + if ($(window).scrollTop() > 16){ + toolbar.css({"box-shadow":"0 18px 42px rgba(15, 23, 42, 0.16)"}); + }else{ + toolbar.css({"box-shadow":"none"}); + } +}); +syncFileToolbarLayout(); +window.onresize = function(){ + syncFileToolbarLayout(); + pathLeft(); + isDiskWidth(); +} + +//批量操作 +function batch(type,access){ + var path = $("#DirPathPlace input").val(); + var el = document.getElementsByTagName('input'); + var len = el.length; + var data='path='+path+'&type='+type; + var name = 'data'; + var datas = []; + + var oldType = getCookie('BatchPaste'); + + for(var i=0;i正在处理,请稍候...",{icon:16,time:0,shade: [0.3, '#000']}); + setTimeout(function(){getSpeed('.myspeed');},1000); + // console.log(data); + $.post('/files/set_batch_data',data,function(rdata){ + layer.close(myloadT); + getFiles(path); + layer.msg(rdata.msg,{icon:1}); + },'json'); +} + +//批量粘贴 +function batchPaste(){ + var path = $("#DirPathPlace input").val(); + var type = getCookie('BatchPaste'); + var data = 'type='+type+'&path='+path; + + $.post('/files/check_exists_files',{dfile:path},function(rdata){ + var result = rdata['data']; + if(result.length > 0){ + var tbody = ''; + for(var i=0;i'+toSize(result[i].size)+''+getLocalTime(result[i].mtime)+''; + } + var mbody = '
                                    \ + '+tbody+'\ +
                                    文件名大小最后修改时间
                                    '; + safeMessage('即将覆盖以下文件',mbody,function(){ + batchPasteTo(data,path); + }); + $(".layui-layer-page").css("width","500px"); + }else{ + batchPasteTo(data,path); + } + },'json'); +} + +function batchPasteTo(data,path){ + myloadT = layer.msg("
                                    正在处理,请稍候...
                                    ",{icon:16,time:0,shade: [0.3, '#000']}); + setTimeout(function(){getSpeed('.myspeed');},1000); + $.post('/files/batch_paste',data,function(rdata){ + layer.close(myloadT); + setCookie('BatchSelected', null); + getFiles(path); + layer.msg(rdata.msg,{icon:1}); + },'json'); +} + + +function getSuffixName(fileName){ + var extArr = fileName.split("."); + var exts = ['folder-unempty','sql','c','cpp','cs','flv','css','js', + 'htm','html','java','log','mht','url','xml','ai','bmp','cdr','gif','ico', + 'jpeg','jpg','JPG','png','psd','webp','ape','avi','mkv','mov','mp3','mp4', + 'mpeg','mpg','rm','rmvb','swf','wav','webm','wma','wmv','rtf','docx','fdf','potm', + 'pptx','txt','xlsb','xlsx','7z','cab','iso','rar','zip','gz','bt','file','apk','bookfolder', + 'folder','folder-empty','fromchromefolder','documentfolder','fromphonefolder', + 'mix','musicfolder','picturefolder','videofolder','sefolder','access','mdb','accdb', + 'fla','doc','docm','dotx','dotm','dot','pdf', + 'ppt','pptm','pot','xls','csv','xlsm','scss','svg','pl','py','php','md','json','sh','conf']; + var extLastName = extArr[extArr.length - 1]; + for(var i=0; i tr").hover(function(){ + $(this).addClass("hover"); + },function(){ + $(this).removeClass("hover"); + }).click(function(){ + $(this).addClass("on").siblings().removeClass("on"); + }); +} + +//取磁盘 +function getDisk() { + var LBody = ''; + + $.get('/system/disk_info', function(rdata) { + var rdata = rdata.data; + for (var i = 0; i < rdata.length; i++) { + LBody += "\ +  " + (rdata[i].path=='/'?lan.files.path_root:rdata[i].path) + "(" + rdata[i].size[2] + ")"; + } + var trash = '\ +  回收站'; + $("#comlist").html(LBody+trash); + isDiskWidth(); + },'json'); +} + +//返回上一级 +function backDir() { + var str = $("#DirPathPlace input").val().replace('//','/'); + if(str.substr(str.length-1,1) == '/'){ + str = str.substr(0,str.length-1); + } + var Path = str.split("/"); + var back = '/'; + if (Path.length > 2) { + var count = Path.length - 1; + for (var i = 0; i < count; i++) { + back += Path[i] + '/'; + } + if(back.substr(back.length-1,1) == '/'){ + back = back.substr(0,back.length-1); + } + getFiles(back); + } else { + back += Path[0]; + getFiles(back); + } + setTimeout('pathPlaceBtn(getCookie("open_dir_path"));',200); +} +//新建文件 +function createFile(type, path) { + if (type == 1) { + var fileName = $("#newFileName").val(); + layer.msg(lan.public.the, { + icon: 16, + time: 10000 + }); + $.post('/files/create_file', 'path=' + encodeURIComponent(path + '/' + fileName), function(rdata) { + layer.closeAll(); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2 + }); + if(rdata.status){ + getFiles($("#DirPathPlace input").val()); + onlineEditFile(0,path + '/' + fileName); + } + },'json'); + return; + } + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '320px', + title: '新建空白文件', + content: '
                                    \ +
                                    \ + \ +
                                    \ +
                                    \ + \ + \ +
                                    \ +
                                    ', + success:function(){ + $("#newFileName").focus().keyup(function(e){ + if(e.keyCode == 13) $("#createFileBtn").click(); + }); + } + }); + +} +//新建目录 +function createDir(type, path) { + if (type == 1) { + var dirName = $("#newDirName").val(); + layer.msg('正在处理,请稍候...', { + icon: 16, + time: 10000 + }); + $.post('/files/create_dir', 'path=' + encodeURIComponent(path + '/' + dirName), function(rdata) { + layer.closeAll(); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2 + }); + getFiles($("#DirPathPlace input").val()); + },'json'); + return; + } + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '320px', + title: '新建目录', + content: '
                                    \ +
                                    \ + \ +
                                    \ +
                                    \ + \ + \ +
                                    \ +
                                    ', + success:function(){ + $("#newDirName").focus().keyup(function(e){ + if(e.keyCode == 13) { + $("#createDirBtn").click(); + } + }); + } + }); + +} + +//删除文件 +function deleteFile(fileName){ + layer.confirm(lan.get('recycle_bin_confirm',[fileName]),{title:'删除文件',closeBtn:2,icon:3},function(){ + layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/files/delete', 'path=' + encodeURIComponent(fileName), function(rdata) { + layer.closeAll(); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2, + }); + getFiles($("#DirPathPlace input").val()); + },'json'); + }); +} + +//删除目录 +function deleteDir(dirName){ + layer.confirm(lan.get('recycle_bin_confirm_dir',[dirName]),{title:'删除目录',closeBtn:2,icon:3},function(){ + layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/files/delete_dir', 'path=' + encodeURIComponent(dirName), function(rdata) { + layer.closeAll(); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2 + }); + getFiles($("#DirPathPlace input").val()); + },'json'); + }); +} +//批量删除文件 +function allDeleteFileSub(data,path){ + layer.confirm('您确实要把这些文件放入回收站吗?',{title:'批量删除文件',closeBtn:2,icon:3},function(){ + layer.msg("
                                    正在处理,请稍候...
                                    ",{icon:16,time:0,shade: [0.3, '#000']}); + setTimeout(function(){getSpeed('.myspeed');},1000); + $.post('/files/set_batch_data',data,function(rdata){ + layer.closeAll(); + getFiles(path); + layer.msg(rdata.msg,{icon:1}); + },'json'); + }); +} + +//重载文件列表 +function reloadFiles(){ + setInterval(function(){ + var path = $("#DirPathPlace input").val(); + getFiles(path); + },3000); +} + +//下载文件 +function downloadFile(action){ + + if(action == 1){ + var fUrl = $("#mUrl").val(); + fUrl = encodeURIComponent(fUrl); + + var fpath = $("#dpath").val(); + fname = encodeURIComponent($("#dfilename").val()); + + if (fUrl == "" ){ + layer.msg("URL地址不能为空!",{icon:2}); + return; + } + + layer.closeAll(); + layer.msg(lan.files.down_task,{time:0,icon:16,shade: [0.3, '#000']}); + + $.post('/files/download_file','path='+fpath+'&url='+fUrl+'&filename='+fname,function(rdata){ + layer.closeAll(); + getFiles(fpath); + getTaskCount(); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + return; + } + + var path = $("#DirPathPlace input").val(); + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '500px', + btn:["确定","关闭"], + title: lan.files.down_title, + content: '
                                    \ +
                                    \ + URL地址:\ + \ +
                                    \ +
                                    \ + 下载到:\ + \ +
                                    \ +
                                    \ + 文件名:\ + \ +
                                    \ +
                                    ', + success:function(){ + $("#mUrl").keyup(function(){ + durl = $(this).val(); + tmp = durl.split('/'); + $("#dfilename").val(tmp[tmp.length-1]); + }); + }, + yes:function(){ + downloadFile(1); + } + }); +} + +//重命名 +function reName(type, fileName) { + if (type == 1) { + var path = $("#DirPathPlace input").val(); + var newFileName = encodeURIComponent(path + '/' + $("#newFileName").val()); + var oldFileName = encodeURIComponent(path + '/' + fileName); + layer.msg(lan.public.the, { + icon: 16, + time: 10000 + }); + $.post('/files/mv_file', 'sfile=' + oldFileName + '&dfile=' + newFileName, function(rdata) { + layer.closeAll(); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2 + }); + getFiles(path); + },'json'); + return; + } + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '320px', + title: '重命名', + btn:["确定","取消"], + content: '
                                    \ +
                                    \ + \ +
                                    \ +
                                    ', + success:function(){ + $("#newFileName").focus().keyup(function(e){ + if(e.keyCode == 13) $(".layui-layer-btn0").click(); + }); + }, + yes:function(){ + reName(1,fileName.replace(/'/,"\\'")); + } + }); + +} +//剪切 +function cutFile(fileName) { + var path = $("#DirPathPlace input").val(); + setCookie('cutFileName', fileName); + setCookie('copyFileName', null); + layer.msg('已剪切', { + icon: 1, + time: 1000, + }); + setTimeout(function(){ + getFiles(path); + },1000); +} +//复制 +function copyFile(fileName) { + var path = $("#DirPathPlace input").val(); + setCookie('copyFileName', fileName); + setCookie('cutFileName', null); + layer.msg('已复制', { + icon: 1, + time: 1000, + }); + + setTimeout(function(){ + getFiles(path); + },1000); +} +//粘贴 +function pasteFile(fileName) { + var path = $("#DirPathPlace input").val(); + var copyName = getCookie('copyFileName'); + var cutName = getCookie('cutFileName'); + var filename = copyName; + if(cutName != 'null' && cutName != undefined) filename=cutName; + filename = filename.split('/').pop(); + $.post('/files/check_exists_files',{dfile:path,filename:filename},function(result){ + if(result.length > 0){ + var tbody = ''; + for(var i=0;i'+toSize(result[i].size)+''+getLocalTime(result[i].mtime)+''; + } + var mbody = '
                                    \ + '+tbody+'\ +
                                    文件名大小最后修改时间
                                    '; + safeMessage('即将覆盖以下文件',mbody,function(){ + pasteTo(path,copyName,cutName,fileName); + }); + } else { + pasteTo(path,copyName,cutName,fileName); + } + },'json'); +} + + +function pasteTo(path,copyName,cutName,fileName){ + if (copyName != 'null' && copyName != undefined) { + layer.msg(lan.files.copy_the, { + icon: 16, + time: 0,shade: [0.3, '#000'] + }); + + //同目录下 + if (copyName == path +'/'+ fileName){ + fileName = '__copy__'+ fileName; + } + + $.post('/files/copy_file', 'sfile=' + encodeURIComponent(copyName) + '&dfile=' + encodeURIComponent(path +'/'+ fileName), function(rdata) { + layer.closeAll(); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2 + }); + getFiles(path); + },'json'); + setCookie('copyFileName', null); + setCookie('cutFileName', null); + return; + } + + if (cutName != 'null' && cutName != undefined) { + layer.msg(lan.files.mv_the, { + icon: 16, + time: 0,shade: [0.3, '#000'] + }); + $.post('/files/mv_file', 'sfile=' + encodeURIComponent(cutName) + '&dfile=' + encodeURIComponent(path + '/'+fileName), function(rdata) { + layer.closeAll(); + layer.msg(rdata.msg, {icon: rdata.status ? 1 : 2}); + getFiles(path); + },'json'); + setCookie('copyFileName', null); + setCookie('cutFileName', null); + } +} + + +//压缩目录 +function zip(dirName,submits) { + if(submits != undefined){ + var sfile = $("#sfile").val(); + var path = $("#path").val(); + var ztype = $('select[name="z_type"]').val(); + var dfile = $("#dfile").val(); + + layer.msg('正在压缩,请稍候...', {icon: 16,time: 0,shade: [0.3, '#000']}); + $.post('/files/zip', 'sfile=' + sfile + '&dfile=' + dfile + '&type='+ztype+'&path='+encodeURIComponent(path), function(rdata) { + layer.closeAll(); + if(rdata == null || rdata == undefined){ + layer.msg('服务器正在后台压缩文件,请稍候刷新文件列表查看进度!',{icon:1}); + getFiles($("#DirPathPlace input").val()); + reloadFiles(); + return; + } + + showMsg(rdata.msg, function(){ + if(rdata.status) { + getFiles($("#DirPathPlace input").val()); + } + },{icon: rdata.status ? 1 : 2}); + },'json'); + return + } + + var randStr = getRandomString(6); + + if(dirName.indexOf(',') != -1){ + dirNameArrs = dirName.split(','); + dirNameArr = dirNameArrs[0].split('/'); + fileName = dirNameArr[dirNameArr.length-1]; + sfile = dirName; + pathName = dirNameArrs[0].replace('/'+fileName,''); + } else { + dirNameArr = dirName.split('/'); + fileName = dirNameArr[dirNameArr.length-1]; + sfile = fileName; + pathName = dirName.replace('/'+fileName,''); + } + + var defaultDfile = pathName + '/' + fileName+'_'+randStr+'.tar.gz'; + + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '650px', + title: '压缩文件['+fileName+']', + btn: ['确定','取消'], + content: '
                                    ' + + '
                                    ' + + '压缩类型\ +
                                    \ + \ +
                                    \ +
                                    ' + // + + '
                                    ' + + '' + + '' + + '压缩路径\ + \ + ' + + '
                                    ' + +'
                                    ', + success:function(){ + $('#change_dir').click(function(){ + changePathCallback('dfile', function(val){ + var z_type = $('select[name="z_type"]').val(); + $('#dfile').val(val+'/'+fileName+'_'+randStr+'.'+z_type.replace('_','.')); + $('#path').val(val); + }); + }); + + $('select[name="z_type"]').change(function(){ + var z_type = $(this).val(); + var path = $('#path').val(); + var newPathName = path+'/'+fileName+'_'+randStr; + if (z_type == 'tar_gz') { + $("#dfile").val(newPathName + '.tar.gz'); + } else if (z_type == 'zip') { + $("#dfile").val(newPathName + '.zip'); + } else if (z_type == 'rar') { + $("#dfile").val(newPathName + '.rar'); + } else if (z_type == 'gz') { + $("#dfile").val(newPathName + '.gz'); + } else if (z_type == '7z') { + $("#dfile").val(newPathName + '.7z'); + } else if (z_type == 'xz') { + $("#dfile").val(newPathName + '.xz'); + } else if (z_type == 'bz2') { + $("#dfile").val(newPathName + '.tar.bz2'); + } + }); + + $("#dfile").change(function(){ + var dfile = $(this).val(); + $(this).val(dfile.replace(/\/\//g,'/')); + }); + }, + btn1: function(index){ + zip(dirName,1); + return false; + } + }); + +} + +//解压目录 +function unZip(fileName, type) { + var path = $("#DirPathPlace input").val(); + if(type.length ==3){ + var sfile = encodeURIComponent($("#sfile").val()); + var dfile = encodeURIComponent($("#dfile").val()); + var coding = $("select[name='coding']").val(); + var tip = layer.msg(lan.files.unzip_the, {icon: 16,time: 0,shade: [0.3, '#000']}); + $.post('/files/unzip', 'sfile=' + sfile + '&dfile=' + dfile +'&type=' + type + '&path='+encodeURIComponent(path), function(rdata) { + layer.close(tip); + showMsg(rdata.msg, function(){ + getFiles(path); + },{icon: rdata.status ? 1 : 2},2000); + },'json'); + return; + } + + type = (type == 1) ? 'tar':'zip'; + var umpass = ''; + if(type == 'zip'){ + umpass = '
                                    \ + '+lan.files.zip_pass_title+'\ + \ +
                                    '; + } + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '490px', + title: '解压文件', + content: '
                                    ' + +'
                                    ' + +''+lan.files.unzip_name+'
                                    ' + +'
                                    '+lan.files.unzip_to+'
                                    ' + + umpass +'
                                    '+lan.files.unzip_coding+'' + +'
                                    ' + +'
                                    ' + +'' + +'' + +'
                                    ' + +'
                                    ' + }); +} + +function isCompressFile(fileName){ + var ext = fileName.split('.'); + var extName = ext[ext.length-1].toLowerCase(); + var support = ['zip','gz','tgz','rar','7z','xz','bz2']; + for (x in support) { + if (support[x]==extName){ + return true; + } + } + return false; +} + +function unCompressFile(fileName, type = 0){ + // 解压文件 + var path = $("#DirPathPlace input").val(); + if(type == 3){ + var sfile = encodeURIComponent($("#sfile").val()); + var dfile = encodeURIComponent($("#dfile").val()); + var coding = $("select[name='coding']").val(); + var tip = layer.msg('正在解压,请稍候...', {icon: 16,time: 0,shade: [0.3, '#000']}); + $.post('/files/uncompress', 'sfile=' + sfile + '&dfile=' + dfile +'&type=' + type + '&path='+encodeURIComponent(path), function(rdata) { + layer.close(tip); + showMsg(rdata.msg, function(){ + layer.closeAll(); + getFiles(path); + },{icon: rdata.status ? 1 : 2},2000); + },'json'); + return; + } + + // var umpass = '
                                    \ + // 解压密码\ + // \ + //
                                    '; + layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '490px', + title: '解压文件', + content: '
                                    \ +
                                    \ + 文件名\ + \ +
                                    \ +
                                    \ + 解压到\ + \ +
                                    \ +
                                    \ + 编码\ + \ +
                                    \ +
                                    \ + \ + \ +
                                    \ +
                                    ' + }); +} + +//是否压缩文件 +function isZip(fileName){ + var ext = fileName.split('.'); + var extName = ext[ext.length-1].toLowerCase(); + if( extName == 'zip') return 0; + if( extName == 'gz' || extName == 'tgz') return 1; + return -1; +} + +//是否文本文件 +function isText(fileName){ + var exts = ['rar','zip','tar.gz','gz','iso','xsl','doc','xdoc','jpeg', + 'jpg','png','gif','bmp','tiff','exe','so','7z','bz']; + return isExts(fileName,exts)?false:true; +} + +//是否图片文件 +function isImage(fileName){ + var exts = ['jpg','jpeg','png','bmp','gif','tiff','ico']; + return isExts(fileName,exts); +} + +//是否为指定扩展名 +function isExts(fileName,exts){ + var ext = fileName.split('.'); + if(ext.length < 2) return false; + var extName = ext[ext.length-1].toLowerCase(); + for(var i=0;i' + }); +} + +//获取文件数据 +function getFileBytes(fileName, fileSize){ + window.open('/files/download?filename='+encodeURIComponent(fileName)); +} + + +//上传文件 +function uploadFiles(){ + var path = $("#DirPathPlace input").val()+"/"; + layer.open({ + type:1, + closeBtn: 1, + title:lan.files.up_title, + area: ['500px','300px'], + shadeClose:false, + content:'
                                    \ + \ + \ + \ + \ + \ + \ + 文件编码:\ + \ + \ + \ +
                                      \ +
                                      ' + }); + uploadStart(); +} + +//设置权限 +function setChmod(action,fileName){ + if(action == 1){ + var chmod = $("#access").val(); + var chown = $("#chown").val(); + var data = 'filename='+ encodeURIComponent(fileName)+'&user='+chown+'&access='+chmod; + var loadT = layer.msg('正在设置...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/files/set_file_access',data,function(rdata){ + layer.close(loadT); + if(rdata.status) layer.closeAll(); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + var path = $("#DirPathPlace input").val(); + getFiles(path) + },'json'); + return; + } + + var toExec = fileName == lan.files.all?'batch(3,1)':'setChmod(1,\''+fileName+'\')'; + $.post('/files/file_access','filename='+encodeURIComponent(fileName),function(rdata){ + // console.log(rdata); + var sys_users = rdata.sys_users; + var own_html = ''; + var is_find_own_option = false; + for (var i = 0; i < sys_users.length; i++) { + var own = sys_users[i]; + if (rdata.chown==own){ + is_find_own_option = true; + own_html += ''; + } else { + own_html += ''; + } + } + if (!is_find_own_option){ + own_html += ''; + } + + layer.open({ + type:1, + closeBtn: 1, + title: '设置权限['+fileName+']', + area: '400px', + shadeClose:false, + content:'
                                      \ +
                                      \ + 所有者\ +

                                      读取

                                      \ +

                                      写入

                                      \ +

                                      执行

                                      \ +
                                      \ +
                                      \ + 用户组\ +

                                      读取

                                      \ +

                                      写入

                                      \ +

                                      执行

                                      \ +
                                      \ +
                                      \ + 公共\ +

                                      读取

                                      \ +

                                      写入

                                      \ +

                                      执行

                                      \ +
                                      \ +
                                      权限,\ + 所有者\ +
                                      \ +
                                      \ + \ + \ +
                                      \ +
                                      ' + }); + + onAccess(); + $("#access").keyup(function(){ + onAccess(); + }); + + $("input[type=checkbox]").change(function(){ + var idName = ['owner','group','public']; + var onacc = ''; + for(var n=0;n idName.length) continue; + if(onacc > 7) $("#access").val(access.substr(0,access.length-1)); + switch(onacc){ + case '1': + $("#"+idName[i]+"_x").prop('checked',true); + break; + case '2': + $("#"+idName[i]+"_w").prop('checked',true); + break; + case '3': + $("#"+idName[i]+"_x").prop('checked',true); + $("#"+idName[i]+"_w").prop('checked',true); + break; + case '4': + $("#"+idName[i]+"_r").prop('checked',true); + break; + case '5': + $("#"+idName[i]+"_r").prop('checked',true); + $("#"+idName[i]+"_x").prop('checked',true); + break; + case '6': + $("#"+idName[i]+"_r").prop('checked',true); + $("#"+idName[i]+"_w").prop('checked',true); + break; + case '7': + $("#"+idName[i]+"_r").prop('checked',true); + $("#"+idName[i]+"_w").prop('checked',true); + $("#"+idName[i]+"_x").prop('checked',true); + break; + } + } +} + +function forcePpageRefresh(){ + location.reload(true); +} + +//右键菜单 +function rightMenuClick(type,path,name){ + // console.log(type,path,name); + var displayZip = isZip(type); + var options = {items:[ + {text: "复制", onclick: function() {copyFile(path)}}, + {text: "剪切", onclick: function() {cutFile(path)}}, + {text: "重命名", onclick: function() {reName(0,name)}}, + {text: lan.files.file_menu_auth, onclick: function() {setChmod(0,path)}}, + {text: lan.files.file_menu_zip, onclick: function() {zip(path)}}, + ]}; + if(type == "dir"){ + options.items.push({text: lan.files.file_menu_del, onclick: function() { + deleteDir(path)} + }); + } else if(isText(type)){ + options.items.push({text: lan.files.file_menu_edit, onclick: function() { + onlineEditFile(0,path); + }},{text: lan.files.file_menu_down, onclick: function() { + getFileBytes(path); + }},{ text: lan.files.file_menu_del, onclick: function() { + deleteFile(path); + }}); + } else if(displayZip != -1){ + options.items.push({text: lan.files.file_menu_unzip, onclick: function() { + unZip(path,displayZip); + }},{text: lan.files.file_menu_down, onclick: function() { + getFileBytes(path); + }},{text: lan.files.file_menu_del, onclick: function() { + deleteFile(path); + }}); + } else if(isImage(type)){ + options.items.push({text: lan.files.file_menu_img, onclick: function() { + getImage(path); + }},{text: lan.files.file_menu_down, onclick: function() { + getFileBytes(path); + }},{text: lan.files.file_menu_del, onclick: function() { + deleteFile(path); + }}); + } else { + options.items.push({text: lan.files.file_menu_down, onclick: function() { + getFileBytes(path); + }},{text: lan.files.file_menu_del, onclick: function() { + deleteFile(path); + }}); + } + + options.items.push({text: '强制刷新页面', onclick: function() { + forcePpageRefresh(); + }}); + return options; +} + +//右键批量操作 +function rightMenuClickAll(e){ + var menu = $("#rmenu"); + var windowWidth = $(window).width(), + windowHeight = $(window).height(), + menuWidth = menu.outerWidth(), + menuHeight = menu.outerHeight(), + x = (menuWidth + e.clientX < windowWidth) ? e.clientX : windowWidth - menuWidth, + y = (menuHeight + e.clientY < windowHeight) ? e.clientY : windowHeight - menuHeight; + + menu.css('top', y) + .css('left', x) + .css('position', 'fixed') + .css("z-index","1") + .show(); +} +//取目录大小 +function getPathSize(){ + var path = encodeURIComponent($("#DirPathPlace input").val()); + layer.msg("正在计算,请稍候...",{icon:16,time:0,shade: [0.3, '#000']}) + $.post("/files/get_dir_size","path="+path,function(rdata){ + layer.closeAll(); + $("#pathSize").text(rdata.msg); + },'json'); +} + +$("body").not(".def-log").click(function(){ + $("#rmenu").hide(); +}); + +//指定路径 +$("#DirPathPlace input").keyup(function(e){ + if(e.keyCode == 13) { + var fpath = $(this).val(); + fpath = filterPath(fpath); + getFiles(fpath); + } +}); + +function pathPlaceBtn(path){ + var html = ''; + var title = ''; + if(path == '/'){ + html = '
                                    • '+lan.files.path_root+'
                                    • '; + } else { + var dst_path = path.split("/"); + for(var i = 0; i'+dst_path[i]+''; + } + } + + html = '
                                        '+html+'
                                      '; + $("#PathPlaceBtn").html(html); + $("#PathPlaceBtn ul li a").click(function(e){ + var go_path = $(this).attr("title"); + if(go_path.length>1){ + if(go_path.substr(go_path.length-1,go_path.length) =='/'){ + go_path = go_path.substr(0,go_path.length-1); + } + } + getFiles(go_path); + e.stopPropagation(); + }); + pathLeft(); +} +//计算当前目录偏移 +function pathLeft(){ + var UlWidth = $("#PathPlaceBtn ul").width(); + var SpanPathWidth = $("#PathPlaceBtn").width() - 50; + var Ml = UlWidth - SpanPathWidth; + if(UlWidth > SpanPathWidth ){ + $("#PathPlaceBtn ul").css("left",-Ml); + } + else{ + $("#PathPlaceBtn ul").css("left",0); + } +} + +//路径快捷点击 +$("#PathPlaceBtn").on("click", function(e){ + if($("#DirPathPlace").is(":hidden")){ + $("#DirPathPlace").css("display","inline"); + $("#DirPathPlace input").focus(); + $(this).hide(); + }else{ + $("#DirPathPlace").hide(); + $(this).css("display","inline"); + } + $(document).one("click", function(){ + $("#DirPathPlace").hide(); + $("#PathPlaceBtn").css("display","inline"); + }); + e.stopPropagation(); +}); +$("#DirPathPlace").on("click", function(e){ + e.stopPropagation(); +}); diff --git a/web/static/app/firewall.js b/web/static/app/firewall.js new file mode 100755 index 000000000..415b89005 --- /dev/null +++ b/web/static/app/firewall.js @@ -0,0 +1,455 @@ +setTimeout(function(){ + getSshInfo(); +},500); + +setTimeout(function(){ + showAccept(1); +},1000); + + +$(function(){ + // start + $.post('/firewall/get_www_path',function(data){ + var html ='日志目录\ + 0KB\ + '; + $('#firewall_weblog').html(html); + + $.post('/files/get_dir_size','path='+data['path'], function(rdata){ + $("#logSize").html(rdata.msg); + },'json'); + },'json'); + // end +}); + +function closeLogs(){ + $.post('/files/close_logs','',function(rdata){ + $("#logSize").html(rdata.msg); + layer.msg('已清理!',{icon:1}); + },'json'); +} + +$("#firewalldType").change(function(){ + var type = $(this).val(); + var w = '120px'; + var p = '端口'; + var t = '放行'; + var m = '说明: 支持放行端口范围,如: 3000:3500'; + if(type == 'address'){ + w = '150px'; + p = '欲屏蔽的IP地址'; + t = '屏蔽'; + m = '说明: 支持屏蔽IP段,如: 192.168.0.0/24'; + } + $("#AcceptPort").css("width",w); + $("#AcceptPort").attr('placeholder',p); + $("#toAccept").html(t); + $("#f-ps").html(m); +}); + + +function sshMgr(){ + $.post('/firewall/get_ssh_info', '', function(rdata){ + var ssh_status = rdata.status ? 'checked':''; + var pass_prohibit_status = rdata.pass_prohibit_status ? 'checked':''; + var pubkey_prohibit_status = rdata.pubkey_prohibit_status ? 'checked':''; + var root_prohibit_status = rdata.root_prohibit_status ? 'checked':''; + var con = '
                                      \ +
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                      名称状态
                                      启动SSH\ +
                                      \ + \ + \ +
                                      \ +
                                      禁止root登陆\ +
                                      \ + \ + \ +
                                      \ +
                                      禁止密码登陆\ +
                                      \ + \ + \ +
                                      \ +
                                      禁止密钥登陆\ +
                                      \ + \ + \ +
                                      \ +
                                      \ +
                                      \ +
                                      '; + layer.open({ + type: 1, + title: "SSH管理", + area: ['300px', '310px'], + closeBtn: 1, + shadeClose: false, + content: '
                                      '+con+'
                                      ', + success:function(){ + }, + }); + },'json'); +} + + +function getSshInfo(){ + $.post('/firewall/get_ssh_info', '', function(rdata){ + if(!rdata.status){ + $("#mstscPort").attr('disabled','disabled'); + } + + $("#mstscPort").val(rdata.port); + var isPint = ''; + if(rdata.ping){ + isPing = ""; + }else{ + isPing = ""; + } + $("#is_ping").html(isPing); + + // console.log(rdata.firewall_status); + var fStatus = ''; + if (rdata.firewall_status){ + fStatus = ""; + }else{ + fStatus = ""; + } + $("#firewall_status").html(fStatus); + + // showAccept(1); + + },'json'); +} + + +/** + * 修改远程端口 + */ + +function mstsc(port) { + layer.confirm('更改远程端口时,将会注消所有已登录帐户,您真的要更改远程端口吗?', {title: '远程端口'}, function(index) { + var data = "port=" + port; + var loadT = layer.load({ + shade: true, + shadeClose: false + }); + $.post('/firewall/set_ssh_port', data, function(ret) { + layer.msg(ret.msg,{icon:ret.status?1:2}) + layer.close(loadT); + getSshInfo(); + },'json'); + }); +} + +/** + * 更改禁ping状态 + * @param {Int} state 0.禁ping 1.可ping + */ +function ping(status){ + var msg = status == 1 ? '禁PING后不影响服务器正常使用,但无法ping通服务器,您真的要禁PING吗?' : '解除禁PING状态可能会被黑客发现您的服务器,您真的要解禁吗?'; + layer.confirm(msg,{title:'是否禁ping',closeBtn:2,cancel:function(){ + if(status == 1){ + $("#noping").prop("checked",true); + } else { + $("#noping").prop("checked",false); + } + }},function(){ + layer.msg('正在处理,请稍候...',{icon:16,time:20000}); + $.post('/firewall/set_ping','status='+status, function(data) { + layer.closeAll(); + if (data['status'] == true) { + if(status == 1){ + layer.msg(data['msg'], {icon: 1}); + } else { + layer.msg('已解除禁PING', {icon: 1}); + } + setTimeout(function(){window.location.reload();},3000); + } else { + layer.msg('连接服务器失败', {icon: 2}); + } + },'json'); + },function(){ + if(status == 1){ + $("#noping").prop("checked",true); + } else { + $("#noping").prop("checked",false); + } + }); +} + + + +/** + * 更改防火墙状态 + * @param {Int} state 0,开启 1.禁用 + */ +function firewall(status){ + var msg = status == 1 ? '禁用防火墙会增加服务器不安全性,您真的要禁用防火墙吗?' : '开启防火墙,增加服务器安全!'; + layer.confirm(msg,{title:'是否开启防火墙!',closeBtn:2,cancel:function(){ + if(status == 1){ + $("#firewall_status").prop("checked",true); + } else { + $("#firewall_status").prop("checked",false); + } + }},function(){ + layer.msg('正在处理,请稍候...',{icon:16,time:20000}); + $.post('/firewall/set_fw','status='+status, function(data) { + layer.closeAll(); + if (data['status'] == true) { + layer.msg(data['msg'], {icon: 1}); + setTimeout(function(){window.location.reload();},3000); + } else { + layer.msg('连接服务器失败', {icon: 2}); + } + },'json'); + },function(){ + if(status == 1){ + $("#firewall_status").prop("checked",true); + } else { + $("#firewall_status").prop("checked",false); + } + }); +} + + +/** + * 设置远程服务状态 + * @param {Int} state 0.启用 1.关闭 + */ +function setMstscStatus(){ + status = $("#sshswitch").prop("checked")==true?1:0; + var msg = status==1?'停用SSH服务的同时也将注销所有已登录用户,继续吗?':'确定启用SSH服务吗?'; + layer.confirm(msg,{title:'警告',closeBtn:2,cancel:function(){ + if(status == 0){ + $("#sshswitch").prop("checked",false); + } else { + $("#sshswitch").prop("checked",true); + } + }},function(index){ + if(index > 0){ + layer.msg('正在处理,请稍候...',{icon:16,time:20000}); + $.post('/firewall/set_ssh_status','status='+status,function(rdata){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + } + },function(){ + if(status == 0){ + $("#sshswitch").prop("checked",false); + } else { + $("#sshswitch").prop("checked",true); + } + }); +} + +/** + * 设置远程服务状态 + * @param {Int} state 0.启用 1.关闭 + */ +function setSshRootStatus(){ + status = $("#root_status").prop("checked")==true?1:0; + var msg = status==1?'开启密码登陆,继续吗?':'确定禁止密码登陆吗?'; + layer.confirm(msg,{title:'警告',closeBtn:2,cancel:function(){ + if(status == 0){ + $("#root_status").prop("checked",false); + } else { + $("#root_status").prop("checked",true); + } + }},function(index){ + if(index > 0){ + layer.msg('正在处理,请稍候...',{icon:16,time:20000}); + $.post('/firewall/set_ssh_root_status','status='+status,function(rdata){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + } + },function(){ + if(status == 0){ + $("#root_status").prop("checked",false); + } else { + $("#root_status").prop("checked",true); + } + }); +} + +/** + * 设置远程服务状态 + * @param {Int} state 0.启用 1.关闭 + */ +function setSshPassStatus(){ + status = $("#pass_status").prop("checked")==true?1:0; + var msg = status==1?'开启密码登陆,继续吗?':'确定禁止密码登陆吗?'; + layer.confirm(msg,{title:'警告',closeBtn:2,cancel:function(){ + if(status == 0){ + $("#pass_status").prop("checked",false); + } else { + $("#pass_status").prop("checked",true); + } + }},function(index){ + if(index > 0){ + layer.msg('正在处理,请稍候...',{icon:16,time:20000}); + $.post('/firewall/set_ssh_pass_status','status='+status,function(rdata){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + } + },function(){ + if(status == 0){ + $("#pass_status").prop("checked",false); + } else { + $("#pass_status").prop("checked",true); + } + }); +} + + + +/** + * 设置远程服务状态 + * @param {Int} state 0.启用 1.关闭 + */ +function setSshPubkeyStatus(){ + status = $("#pubkey_status").prop("checked")==true?1:0; + var msg = status==1?'开启密码登陆,继续吗?':'确定禁止密码登陆吗?'; + layer.confirm(msg,{title:'警告',closeBtn:2,cancel:function(){ + if(status == 0){ + $("#pubkey_status").prop("checked",false); + } else { + $("#pubkey_status").prop("checked",true); + } + }},function(index){ + if(index > 0){ + layer.msg('正在处理,请稍候...',{icon:16,time:20000}); + $.post('/firewall/set_ssh_pubkey_status','status='+status,function(rdata){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + } + },function(){ + if(status == 0){ + $("#pubkey_status").prop("checked",false); + } else { + $("#pubkey_status").prop("checked",true); + } + }); +} + + + +/** + * 取回数据 + * @param {Int} page 分页号 + */ +function showAccept(page,search) { + search = search == undefined ? '':search; + var loadT = layer.load(); + $.post('/firewall/get_list','limit=10&p=' + page+"&search="+search, function(data) { + layer.close(loadT); + var body = ''; + for (var i = 0; i < data.data.length; i++) { + var status = ''; + switch(data.data[i].status){ + case 0: + status = '未使用'; + break; + case 1: + status = '外网不通'; + break; + default: + status = '正常'; + break; + } + body += "\ + " + data.data[i].id + "\ + " + data.data[i].protocol + "\ + " + (data.data[i].port.indexOf('.') == -1?'放行端口'+':['+data.data[i].port+']':'屏蔽IP'+':['+data.data[i].port+']') + "\ + " + status + "\ + " + data.data[i].ps + "\ + " + data.data[i].add_time + "\ + 删除\ + "; + } + + if (data.data.length == 0){ + body = '当前没有数据'; + } + + $("#firewall_body").html(body); + $("#firewall_page").html(data.page); + },'json'); +} + +//添加放行 +function addAcceptPort(){ + var type = $("#firewalldType").val(); + var port = $("#AcceptPort").val(); + var ps = $("#Ps").val(); + var protocol = $('select[name="protocol"]').val(); + var action = "add_drop_address"; + if(type == 'port'){ + ports = port.split(':'); + for(var i=0;i 65535 ){ + layer.msg('端口范围不合法!',{icon:5}); + return; + } + } + action = "add_accept_port"; + } + + + if(ps.length < 1){ + layer.msg('备注/说明 不能为空!',{icon:2}); + $("#Ps").focus(); + return; + } + var loadT = layer.msg('正在添加,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}) + $.post('/firewall/'+action,'port='+port+"&ps="+ps+'&type='+type+'&protocol='+protocol,function(rdata){ + layer.close(loadT); + if(rdata.status == true || rdata.status == 'true'){ + layer.msg(rdata.msg,{icon:1}); + showAccept(1); + $("#AcceptPort").val(''); + $("#Ps").val(''); + }else{ + layer.msg(rdata.msg,{icon:2}); + } + + $("#AcceptPort").attr('value',''); + $("#Ps").attr('value',''); + },'json'); +} + +//删除放行 +function delAcceptPort(id, port,protocol) { + var action = "del_drop_address"; + if(port.indexOf('.') == -1){ + action = "del_accept_port"; + } + + layer.confirm(lan.get('confirm_del',[port]), {title: '删除防火墙规则',closeBtn:2}, function(index) { + var loadT = layer.msg('正在删除,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}) + $.post("/firewall/"+action, "id=" + id + "&port=" + port+'&protocol='+protocol, function(ret) { + layer.close(loadT); + layer.msg(ret.msg,{icon:ret.status?1:2}) + showAccept(1); + },'json'); + }); +} diff --git a/web/static/app/index.js b/web/static/app/index.js new file mode 100755 index 000000000..cb42b101d --- /dev/null +++ b/web/static/app/index.js @@ -0,0 +1,1918 @@ +var MW_STAT_SUCCESS = '#35a37b'; +var MW_STAT_SUCCESS_SOFT = '#81c784'; +var MW_STAT_WARNING = '#d48d3a'; +var MW_STAT_DANGER = '#d85c53'; + +function getStatColor(value) { + var num = parseFloat(value); + if (isNaN(num)) { + num = 0; + } + if (num <= 75) { + return MW_STAT_SUCCESS_SOFT; + } + if (num <= 90) { + return MW_STAT_WARNING; + } + if (num <= 95) { + return MW_STAT_DANGER; + } + return MW_STAT_DANGER; +} + +//获取负载 +function getLoad(data) { + $("#Load").text("获取中.."); + setCookie('one', data.one); + setCookie('five', data.five); + setCookie('fifteen', data.fifteen); + var transformLeft, transformRight, LoadColor, Average, Occupy, AverageText, conterError = ''; + var index = $("#LoadList"); + if (Average == undefined) Average = data.one; + setCookie('conterError', conterError); + Occupy = Math.round((Average / data.max) * 100); + if (Occupy > 100) Occupy = 100; + if (Occupy <= 30) { + LoadColor = MW_STAT_SUCCESS_SOFT; + AverageText = '运行流畅'; + } else if (Occupy <= 70) { + LoadColor = MW_STAT_SUCCESS; + AverageText = '运行正常'; + } else if (Occupy <= 90) { + LoadColor = MW_STAT_WARNING; + AverageText = '运行缓慢'; + } else { + LoadColor = MW_STAT_DANGER; + AverageText = '运行堵塞'; + } + index.find('.mask').css({ "color": LoadColor }); + updateLinearProgressValue(document.getElementById('LoadProgress'), Occupy, LoadColor); + $('#Load').html(Occupy); + $('#LoadState').html('' + AverageText + ''); +} + +$('#LoadList .mw-stat-progress').click(function() { + // getNet(); +}); + +$('#LoadList .mw-stat-value').hover(function() { + var one, five, fifteen; + var that = this; + one = getCookie('one'); + five = getCookie('five'); + fifteen = getCookie('fifteen'); + var text = '最近1分钟平均负载:' + one + '
                                      最近5分钟平均负载:' + five + '
                                      最近15分钟平均负载:' + fifteen + ''; + layer.tips(text, that, { time: 0, tips: [1, '#999'] }); +}, function() { + layer.closeAll('tips'); +}); + + +function showCpuTips(rdata){ + $('#cpuChart .mask').unbind().hover(function() { + var cpuText = ''; + if (rdata.cpu[2].length == 1){ + var cpuUse = parseFloat(rdata.cpu[2][0] == 0 ? 0 : rdata.cpu[2][0]).toFixed(1); + cpuText += 'CPU-1:' + cpuUse + '%'; + } else { + for (var i = 1; i < rdata.cpu[2].length + 1; i++) { + var cpuUse = parseFloat(rdata.cpu[2][i - 1] == 0 ? 0 : rdata.cpu[2][i - 1]).toFixed(1); + if (i % 2 != 0) { + cpuText += 'CPU-' + i + ':' + cpuUse + '% | '; + } else { + cpuText += 'CPU-' + i + ':' + cpuUse + '%'; + cpuText += '\n'; + } + } + } + layer.tips(rdata.cpu[3] + "
                                      " + rdata.cpu[5] + "个物理CPU," + (rdata.cpu[4]) + "个物理核心," + rdata.cpu[1] + "个逻辑核心
                                      " + cpuText, this, { time: 0, tips: [1, '#999'] }); + }, function() { + layer.closeAll('tips'); + }); +} + +function getChartTheme() { + var styles = getComputedStyle(document.documentElement); + function resolveCssVar(value) { + if (!value) { + return value; + } + var trimmed = value.trim(); + if (trimmed.indexOf('var(') !== 0) { + return trimmed; + } + var match = trimmed.match(/var\((--[^,\s)]+)\s*(?:,\s*(.+))?\)/); + if (!match) { + return trimmed; + } + var resolved = styles.getPropertyValue(match[1]).trim(); + if (resolved) { + return resolveCssVar(resolved); + } + if (match[2]) { + return resolveCssVar(match[2].trim()); + } + return trimmed; + } + return { + primary: resolveCssVar(styles.getPropertyValue('--mw-primary')) || '#6750a4', + secondary: resolveCssVar(styles.getPropertyValue('--mdui-color-secondary')) || '#4f8ef7', + border: resolveCssVar(styles.getPropertyValue('--mw-border')) || '#e2e8f0', + muted: resolveCssVar(styles.getPropertyValue('--mw-muted')) || '#64748b', + surface: resolveCssVar(styles.getPropertyValue('--mw-surface')) || '#ffffff', + text: resolveCssVar(styles.getPropertyValue('--mw-text')) || '#1f1f1f', + surfaceContainer: resolveCssVar(styles.getPropertyValue('--mw-surface-container')) || '#f2f3f7' + }; +} + +function updateLinearProgressValue(element, value, color) { + if (!element) { + return; + } + var safeValue = Math.max(0, Math.min(100, parseFloat(value) || 0)); + element.value = safeValue; + element.setAttribute('value', safeValue); + if (color) { + element.style.setProperty('--mdui-color-primary', color); + } +} + +function escapeHtml(text) { + return String(text || '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function renderNoteMarkdown(content) { + var text = String(content || '').trim(); + if (!text) { + return ''; + } + if (window.marked && typeof window.marked.parse === 'function') { + return window.marked.parse(text); + } + return escapeHtml(text).replace(/\n/g, '
                                      '); +} + +function initNoteBoard() { + var preview = $('#notePreview'); + if (!preview.length) { + return; + } + var editor = $('#noteEditor'); + var input = $('#noteInput'); + var editButton = $('#noteEditBtn'); + var saveButton = $('#noteSaveBtn'); + var cancelButton = $('#noteCancelBtn'); + var storageKey = 'mw-note-board'; + var fallbackText = '## 欢迎使用便签\n\n- 支持 **Markdown** 语法\n- 记录维护事项、变更说明\n- 点击底部“编辑”开始编写'; + var storedText = ''; + try { + storedText = localStorage.getItem(storageKey) || ''; + } catch (error) { + storedText = ''; + } + var currentText = storedText || fallbackText; + + function updatePreview(text) { + var rendered = renderNoteMarkdown(text); + if (!rendered) { + preview.html('

                                      暂无便签内容,点击“编辑”开始记录。

                                      '); + } else { + preview.html(rendered); + } + } + + function setEditMode(editing) { + if (editing) { + editor.removeClass('hide'); + preview.addClass('hide'); + editButton.addClass('hide'); + } else { + editor.addClass('hide'); + preview.removeClass('hide'); + editButton.removeClass('hide'); + } + } + + updatePreview(currentText); + setEditMode(false); + + editButton.on('click', function () { + input.val(currentText); + setEditMode(true); + input.trigger('focus'); + }); + + saveButton.on('click', function () { + var nextText = input.val(); + currentText = nextText; + try { + localStorage.setItem(storageKey, nextText); + } catch (error) { + console.warn('Failed to save note content', error); + } + updatePreview(currentText); + setEditMode(false); + }); + + cancelButton.on('click', function () { + setEditMode(false); + }); +} + +function applyColorAlpha(color, alpha) { + if (!color) { + return 'rgba(0, 0, 0, ' + alpha + ')'; + } + if (color.indexOf('rgb') === 0) { + var numbers = color.replace(/[^\d,]/g, '').split(','); + if (numbers.length >= 3) { + return 'rgba(' + numbers[0] + ', ' + numbers[1] + ', ' + numbers[2] + ', ' + alpha + ')'; + } + } + if (color.indexOf('#') === 0) { + var hex = color.replace('#', ''); + if (hex.length === 3) { + hex = hex.split('').map(function (item) { return item + item; }).join(''); + } + if (hex.length === 6) { + var r = parseInt(hex.slice(0, 2), 16); + var g = parseInt(hex.slice(2, 4), 16); + var b = parseInt(hex.slice(4, 6), 16); + return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')'; + } + } + return color; +} + +function initEchartWhenReady(elementId, option, onReady) { + if (typeof echarts === 'undefined') { + return null; + } + var element = document.getElementById(elementId); + if (!element) { + return null; + } + var attempt = 0; + var maxAttempts = 30; + var raf = window.requestAnimationFrame || function(callback) { + return setTimeout(callback, 16); + }; + function tryInit() { + if (element.clientWidth === 0 || element.clientHeight === 0) { + if (attempt++ < maxAttempts) { + raf(tryInit); + } + return; + } + var chart = echarts.getInstanceByDom(element) || echarts.init(element); + if (option) { + chart.setOption(option); + } + if (onReady) { + onReady(chart); + } + } + tryInit(); + return null; +} + + +function rocket(sum, m) { + var n = sum - m; + $(".mem-release").find(".mask span").text(n); +} + +//释放内存 +function reMemory() { + var memButton = $("#memReleaseBtn"); + memButton.prop("disabled", true); + $("#memory").text(lan.index.memre_ok_0); + $.post('/system/rememory', '', function(rdata) { + var percent = getPercent(rdata.memRealUsed, rdata.memTotal); + percent = Math.round(parseFloat(percent) || 0); + var memText = Math.round(rdata.memRealUsed) + "/" + Math.round(rdata.memTotal) + " (MB)"; + var prevMemRealUsed = parseFloat(getCookie("memRealUsed")) || 0; + setCookie("mem-before", memText); + setCookie("memRealUsed", rdata.memRealUsed); + $("#left").text(percent); + var memColor = setcolor(percent, "#left", 75, 90, 95); + updateLinearProgressValue(document.getElementById('MemProgress'), percent, memColor); + var memNull = Math.round(prevMemRealUsed - rdata.memRealUsed); + if (prevMemRealUsed > 0 && memNull > 0) { + $("#memory").text(lan.index.memre_ok_1 + " " + memNull + "MB"); + } else { + $("#memory").text(lan.index.memre_ok_2); + } + $(".mem-release").removeClass("mem-action"); + memButton.prop("disabled", false); + },'json').fail(function() { + $(".mem-release").removeClass("mem-action"); + $("#memory").text(lan.index.memre_ok_2); + memButton.prop("disabled", false); + }); +} + +function getPercent(num, total) { + num = parseFloat(num); + total = parseFloat(total); + if (isNaN(num) || isNaN(total)) { + return "-"; + } + return total <= 0 ? "0%" : (Math.round(num / total * 10000) / 100.00); +} + +function getDiskInfo() { + $.get('/system/disk_info', function(rdata) { + var rdata = rdata.data; + var dBody; + for (var i = 0; i < rdata.length; i++) { + var usagePercent = parseInt(rdata[i].size[3].replace('%', '')); + var LoadColor = getStatColor(usagePercent); + + //判断inode信息是否存在 + var inodes = ''; + if ( typeof(rdata[i]['inodes']) !=='undefined' ){ + inodes = ' data="Inode信息
                                      总数:' + rdata[i].inodes[0] + '
                                      已使用:' + rdata[i].inodes[1] + '
                                      可用:' + rdata[i].inodes[2] + '
                                      Inode使用率:' + rdata[i].inodes[3] + '"'; + + var ipre = parseInt(rdata[i].inodes[3].replace('%', '')); + if (ipre > 95) { + $("#messageError").show(); + $("#messageError").append('

                                      分区[' + rdata[i].path + ']当前Inode使用率超过' + ipre + '%,当使用率满100%时将无法在此分区创建文件,请及时清理![清理垃圾]

                                      '); + } + } + + if (rdata[i].path == '/' || rdata[i].path == '/www') { + if (rdata[i].size[2].indexOf('M') != -1) { + $("#messageError").show(); + $("#messageError").append('

                                      ' + lan.get('diskinfo_span_1', [rdata[i].path]) + '[清理垃圾]

                                      '); + } + } + + var diskLabel = rdata[i].path == '/' ? '根分区' : rdata[i].path; + dBody = '
                                    • ' + + '

                                      ' + escapeHtml(diskLabel) + '

                                      ' + + '
                                      分区占用与 Inode 状态
                                      ' + + '
                                      ' + + '' + + '
                                      ' + + '
                                      ' + usagePercent + '%
                                      ' + + '

                                      ' + escapeHtml(rdata[i].size[1] + '/' + rdata[i].size[0]) + '

                                      ' + + '
                                    • ' + $("#systemInfoList").append(dBody); + } + setImg(); + },'json'); +} + +//清理垃圾 +function clearSystem() { + var loadT = layer.msg('正在清理系统垃圾 ', { icon: 16, time: 0, shade: [0.3, "#000"] }); + $.get('/system?action=ClearSystem', function(rdata) { + layer.close(loadT); + layer.msg('清理完成,共清理[' + rdata[0] + ']个文件,释放[' + toSize(rdata[1]) + ']磁盘空间!', { icon: 1 }); + }); +} + +function setMemImg(info){ + + var memRealUsedBytes = parseFloat(info.memRealUsed) || 0; + var memTotalBytes = parseFloat(info.memTotal) || 0; + var gbBytes = 1024 * 1024 * 1024; + var memRealUsedVal; + var memTotalVal; + var unit; + if (memRealUsedBytes < gbBytes) { + memRealUsedVal = (memRealUsedBytes / 1024 / 1024).toFixed(2); + memTotalVal = (memTotalBytes / 1024 / 1024).toFixed(2); + unit = 'MB'; + } else { + memRealUsedVal = (memRealUsedBytes / gbBytes).toFixed(2); + memTotalVal = (memTotalBytes / gbBytes).toFixed(2); + unit = 'GB'; + } + + var mem_txt = memRealUsedVal + '/' + memTotalVal + ' ('+ unit +')'; + setCookie("mem-before", mem_txt); + $("#memory").html(mem_txt); + + var memPre = Math.floor(info.memRealUsed / (info.memTotal / 100)); + $("#left").html(memPre); + var memColor = setcolor(memPre, "#left", 75, 90, 95); + updateLinearProgressValue(document.getElementById('MemProgress'), memPre, memColor); + + var memFree = info.memTotal - info.memRealUsed; + if (memFree/(1024*1024) < 64) { + $("#messageError").show(); + $("#messageError").append('

                                      当前可用物理内存小于64M,这可能导致MySQL自动停止,站点502等错误,请尝试释放内存!

                                      ') + } +} + +function getInfo() { + $.get("/system/system_total", function(info) { + + setMemImg(info); + + $("#info").html(info.system); + $("#running").html(info.time); + var _system = info.system; + if(_system.indexOf("Windows") != -1){ + $(".ico-system").addClass("ico-windows"); + } else if(_system.indexOf("CentOS") != -1) { + $(".ico-system").addClass("ico-centos"); + } else if(_system.indexOf("Ubuntu") != -1) { + $(".ico-system").addClass("ico-ubuntu"); + } else if(_system.indexOf("Debian") != -1) { + $(".ico-system").addClass("ico-debian"); + } else if(_system.indexOf("Fedora") != -1) { + $(".ico-system").addClass("ico-fedora"); + } else if(_system.indexOf("Mac") != -1){ + $(".ico-system").addClass("ico-mac"); + } else { + $(".ico-system").addClass("ico-linux"); + } + $("#core").html(info.cpuNum + ' 核心'); + $("#state").html(info.cpuRealUsed); + var cpuColor = setcolor(info.cpuRealUsed, "#state", 30, 70, 90); + updateLinearProgressValue(document.getElementById('CpuProgress'), info.cpuRealUsed, cpuColor); + + + // if (info.isuser > 0) { + // $("#messageError").show(); + // $("#messageError").append('

                                      ' + lan.index.user_warning + ' [不可忽略] [立即修改]

                                      ') + // } + setImg(); + },'json'); +} + + +function setcolor(pre, s, s1, s2, s3) { + var value = parseFloat(pre); + if (isNaN(value)) { + value = 0; + } + var LoadColor; + if (value <= s1) { + LoadColor = MW_STAT_SUCCESS_SOFT; + } else if (value <= s2) { + LoadColor = MW_STAT_SUCCESS; + } else if (value <= s3) { + LoadColor = MW_STAT_WARNING; + } else { + LoadColor = MW_STAT_DANGER; + } + if (s == false) { + return LoadColor; + } + var co = $(s).closest('.mask'); + co.css("color", LoadColor); + var item = co.closest('.mw-stat-item'); + var progressElement = item.find('mdui-linear-progress').get(0); + updateLinearProgressValue(progressElement, value, LoadColor); + return LoadColor; +} + + +function getNet() { + var up, down; + $.get("/system/network", function(net) { + + console.log(net); + + $("#InterfaceSpeed").html(lan.index.interfacespeed + ": 1.0Gbps"); + $("#upSpeed").html(toSize(net.up)); + $("#downSpeed").html(toSize(net.down)); + $("#downAll").html(toSize(net.downTotal)); + $("#downAll").attr('title', lan.index.package + ':' + net.downPackets) + $("#upAll").html(toSize(net.upTotal)); + $("#upAll").attr('title', lan.index.package + ':' + net.upPackets) + $("#core").html(net.cpu[1] + " " + lan.index.cpu_core); + $("#state").html(net.cpu[0]); + var cpuColor = setcolor(net.cpu[0], "#state", 30, 70, 90); + updateLinearProgressValue(document.getElementById('CpuProgress'), net.cpu[0], cpuColor); + setCookie("upNet", net.up); + setCookie("downNet", net.down); + + //负载 + getLoad(net.load); + + //内存 + setMemImg(net.mem); + + //绑定hover事件 + setImg(); + showCpuTips(net); + + },'json'); +} + +//网络IO +function netImg() { + + var xData = []; + var yData = []; + var zData = []; + + function getTime() { + var now = new Date(); + var hour = now.getHours(); + var minute = now.getMinutes(); + var second = now.getSeconds(); + if (minute < 10) { + minute = "0" + minute; + } + if (second < 10) { + second = "0" + second; + } + var nowdate = hour + ":" + minute + ":" + second; + return nowdate; + } + + function ts(m) { return m < 10 ? '0' + m : m } + + function format(sjc) { + var time = new Date(sjc); + var h = time.getHours(); + var mm = time.getMinutes(); + var s = time.getSeconds(); + return ts(h) + ':' + ts(mm) + ':' + ts(s); + } + + var default_unit = 'KB/s'; + function addData(shift) { + xData.push(getTime()); + + if (getCookie("upNet") > getCookie("downNet") ){ + tmp = getCookie("upNet"); + } else { + tmp = getCookie("downNet"); + } + var tmpSize = toSize(tmp); + default_unit = tmpSize.split(' ')[1] + '/s'; + + + var upNetTmp = toSize(getCookie("upNet")); + var downNetTmp = toSize(getCookie("downNet")); + + var upNetTmpSize = upNetTmp.split(' ')[0]; + var downNetTmp = downNetTmp.split(' ')[0]; + + yData.push(upNetTmpSize); + zData.push(downNetTmp); + if (shift) { + xData.shift(); + yData.shift(); + zData.shift(); + } + } + for (var i = 8; i >= 0; i--) { + var time = (new Date()).getTime(); + xData.push(format(time - (i * 3 * 1000))); + yData.push(0); + zData.push(0); + } + // 指定图表的配置项和数据 + var option = { + title: { + text: "接口流量实时", + left: 'center', + textStyle: { + color: '#888888', + fontStyle: 'normal', + fontFamily: "宋体", + fontSize: 16, + } + }, + tooltip: { + trigger: 'axis' + }, + legend: { + data: [lan.index.net_up, lan.index.net_down], + bottom: '2%' + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xData, + axisLine: { + lineStyle: { + color: "#666" + } + } + }, + yAxis: { + name: '单位 '+ default_unit, + splitLine: { + lineStyle: { color: "#eee" } + }, + axisLine: { + lineStyle: { color: "#666" } + } + }, + series: [{ + name: '上行', + type: 'line', + data: yData, + smooth: true, + showSymbol: false, + symbol: 'circle', + symbolSize: 6, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: 'rgba(255, 140, 0,0.5)' + }, { + offset: 1, + color: 'rgba(255, 140, 0,0.8)' + }], false) + } + }, + itemStyle: { + normal: { + color: '#f7b851' + } + }, + lineStyle: { + normal: { + width: 1 + } + } + }, + { + name: '下行', + type: 'line', + data: zData, + smooth: true, + showSymbol: false, + symbol: 'circle', + symbolSize: 6, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: 'rgba(30, 144, 255,0.5)', + }, { + offset: 1, + color: 'rgba(30, 144, 255,0.8)', + }], false) + } + }, + itemStyle: { + normal: { + color: '#52a9ff', + } + }, + lineStyle: { + normal: { + width: 1, + } + } + }] + }; + + var echartsNetImg = echarts.init(document.getElementById('netImg')); + setInterval(function() { + getNet(); + addData(true); + echartsNetImg.setOption({ + yAxis: { + name: '单位 '+ default_unit, + splitLine: { lineStyle: { color: "#eee" } }, + axisLine: { lineStyle: { color: "#666" } } + }, + xAxis: { + data: xData + }, + series: [{ + name: lan.index.net_up, + data: yData + }, { + name: lan.index.net_down, + data: zData + }] + }); + }, 3000); + + // 使用刚指定的配置项和数据显示图表。 + echartsNetImg.setOption(option); + window.addEventListener("resize", function() { + echartsNetImg.resize(); + }); +} + + +function setImg() { + $('.circle').each(function(index, el) { + var num = $(this).find('span').text() * 3.6; + if (num <= 180) { + $(this).find('.left').css('transform', "rotate(0deg)"); + $(this).find('.right').css('transform', "rotate(" + num + "deg)"); + } else { + $(this).find('.right').css('transform', "rotate(180deg)"); + $(this).find('.left').css('transform', "rotate(" + (num - 180) + "deg)"); + }; + }); + + $('.diskbox .mask').unbind().hover(function() { + layer.closeAll('tips'); + var that = this; + var conterError = $(this).attr("data"); + layer.tips(conterError, that, { time: 0, tips: [1, '#999'] }); + }, function() { + layer.closeAll('tips'); + }); +} + +// 检查更新 +setTimeout(function() { + $.get('/system/update_server?type=check', function(rdata) { + if (rdata.status == false) return; + if (rdata.data != undefined) { + $("#toUpdate").html('更新'); + $('#toUpdate a').html('更新.'); + $('#toUpdate a').css("position","relative"); + return; + } + },'json').error(function() { + }); +}, 3000); + + +//检查更新 +function checkUpdate() { + var loadT = layer.msg(lan.index.update_get, { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.get('/system/update_server?type=check', function(rdata) { + layer.close(loadT); + + if (rdata.data == 'download'){ + updateStatus();return; + } + + if (rdata.status === false) { + layer.confirm(rdata.msg, { title: lan.index.update_check, icon: 1, closeBtn: 1, btn: [lan.public.know, lan.public.close] }); + return; + } + layer.msg(rdata.msg, { icon: 1 }); + if (rdata.data != undefined) updateMsg(); + },'json'); +} + +function updateMsg(){ + $.get('/system/update_server?type=info',function(rdata){ + + if (rdata.data == 'download'){ + updateStatus();return; + } + + var v = rdata.data.version; + var v_info = ''; + if (v.split('.').length>3){ + v_info = "测试版本"; + } else { + v_info = "正式版本"; + } + + layer.open({ + type:1, + title:v_info + '升级到['+rdata.data.version+']', + area: '400px', + shadeClose:false, + closeBtn:2, + content:'
                                      ' + +'

                                      '+rdata.data.content+'

                                      ' + +'
                                      ' + +'' + +'' + +'
                                      ' + +'
                                      ' + }); + },'json'); +} + + +//开始升级 +function updateVersion(version) { + var loadT = layer.msg('正在升级面板..', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.get('/system/update_server?type=update&version='+version, function(rdata) { + + layer.closeAll(); + if (rdata.status === false) { + layer.msg(rdata.msg, { icon: 5, time: 5000 }); + return; + } + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + $("#btversion").html(version); + $("#toUpdate").html(''); + } + },'json').error(function() { + layer.msg('更新失败,请重试!', { icon: 2 }); + setTimeout(function() { + window.location.reload(); + }, 3000); + }); +} + +function pluginIndexService(pname,pfunc, callback){ + $.post('/plugins/run', {name:'openresty', func:pfunc}, function(data) { + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +//重启服务器 +function reBoot() { + layer.open({ + type: 1, + title: '重启服务器或者面板', + area: '330px', + closeBtn: 1, + shadeClose: false, + content: '' + }); + + $('.rebt-con a').click(function () { + var type = $(this).attr('data-id'); + switch (type) { + case 'panel': + layer.confirm('即将重启面板服务,继续吗?', { title: '重启面板服务', closeBtn: 1, icon: 3 }, function () { + var loadT = layer.load(); + $.post('/system/restart','',function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg); + setTimeout(function () { window.location.reload(); }, 3000); + },'json'); + }); + break; + case 'server': + var rebootbox = layer.open({ + type: 1, + title: '安全重启服务器', + area: ['500px', '280px'], + closeBtn: 1, + shadeClose: false, + content: "
                                      \ +
                                      \ +

                                      注意,若您的服务器是一个容器,请取消。

                                      \ +
                                      \ +

                                      安全重启有利于保障文件安全,将执行以下操作:

                                      \ +

                                      1.停止Web服务

                                      \ +

                                      2.停止MySQL服务

                                      \ +

                                      3.开始重启服务器

                                      \ +

                                      4.等待服务器启动

                                      \ +
                                      \ +
                                      \ +
                                      \ + \ + \ +
                                      \ +
                                      " + }); + setTimeout(function () { + $(".btn-reboot").click(function () { + rebootbox.close(); + }) + $(".WSafeRestart").click(function () { + var body = '
                                      '; + $(".bt-window-restart").html(body); + $(".SafeRestartCode").append("

                                      正在停止Web服务

                                      "); + pluginIndexService('openresty', 'stop', function (r1) { + $(".SafeRestartCode p").addClass('c9'); + $(".SafeRestartCode").append("

                                      正在停止MySQL服务...

                                      "); + pluginIndexService('mysql','stop', function (r2) { + $(".SafeRestartCode p").addClass('c9'); + $(".SafeRestartCode").append("

                                      开始重启服务器...

                                      "); + $.post('/system/restart_server', '',function (rdata) { + $(".SafeRestartCode p").addClass('c9'); + $(".SafeRestartCode").append("

                                      等待服务器启动...

                                      "); + var sEver = setInterval(function () { + $.get("/system/system_total", function(info) { + clearInterval(sEver); + $(".SafeRestartCode p").addClass('c9'); + $(".SafeRestartCode").append("

                                      服务器重启成功!...

                                      "); + setTimeout(function () { + layer.closeAll(); + }, 3000); + }) + }, 3000); + }) + }) + }) + }) + }, 100); + break; + } + }); +} + +//关机服务器 +function shutdownServer() { + layer.confirm('确定要关闭服务器吗?此操作将会停止所有服务。', { title: '关机确认', closeBtn: 1, icon: 3 }, function () { + var loadT = layer.load(); + $.post('/system/shutdown_server', '', function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }, 'json').error(function () { + layer.close(loadT); + layer.msg('关机请求失败,请重试。', { icon: 2 }); + }); + }); +} + +var __vipCountdownTimer = null; + +function formatVipRemain(seconds) { + if (seconds <= 0) { + return '永久有效'; + } + var day = Math.floor(seconds / 86400); + var hour = Math.floor((seconds % 86400) / 3600); + var minute = Math.floor((seconds % 3600) / 60); + var second = Math.floor(seconds % 60); + return day + '天 ' + hour + '小时 ' + minute + '分钟 ' + second + '秒'; +} + +function showVipInfo() { + var expireAt = new Date('2038-01-19T03:14:07+08:00').getTime(); + var content = '
                                      ' + + '
                                      PowerLinux 3 · 永久尊享
                                      ' + + '
                                      您已经是永久VIP,感谢长期支持。
                                      ' + + '
                                      ' + + '
                                      会员到期时间:2038年1月19日03:14:07
                                      ' + + '
                                      剩余时长:计算中...
                                      ' + + '
                                      ' + + '
                                      会员权益
                                      ' + + '
                                        ' + + '
                                      • 无限期面板更新(优先体验新版能力)
                                      • ' + + '
                                      • 高级监控与可视化页面持续增强
                                      • ' + + '
                                      • 社区身份标识与优先反馈通道
                                      • ' + + '
                                      • 长期稳定版本与性能优化补丁
                                      • ' + + '
                                      ' + + '
                                      提示:剩余时长将实时刷新显示。
                                      ' + + '
                                      '; + + layer.open({ + type: 1, + title: 'PowerLinux 3 会员信息', + area: ['520px', '430px'], + closeBtn: 1, + icon: 1, + content: content, + success: function () { + if (__vipCountdownTimer) { + clearInterval(__vipCountdownTimer); + __vipCountdownTimer = null; + } + function tick() { + var now = Date.now(); + var left = Math.max(0, Math.floor((expireAt - now) / 1000)); + var remain = formatVipRemain(left); + $('#vipRemainTime').text(remain); + } + tick(); + __vipCountdownTimer = setInterval(tick, 1000); + }, + end: function () { + if (__vipCountdownTimer) { + clearInterval(__vipCountdownTimer); + __vipCountdownTimer = null; + } + } + }); +} + +//修复面板 +function repPanel() { + layer.confirm(lan.index.rep_panel_msg, { title: lan.index.rep_panel_title, closeBtn: 1, icon: 3 }, function() { + var loadT = layer.msg(lan.index.rep_panel_the, { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.get('/system?action=RepPanel', function(rdata) { + layer.close(loadT); + layer.msg(lan.index.rep_panel_ok, { icon: 1 }); + }).error(function() { + layer.close(loadT); + layer.msg(lan.index.rep_panel_ok, { icon: 1 }); + }); + }); +} + +//获取警告信息 +function getWarning() { + $.get('/ajax?action=GetWarning', function(wlist) { + var num = 0; + for (var i = 0; i < wlist.data.length; i++) { + if (wlist.data[i].ignore_count >= wlist.data[i].ignore_limit) continue; + if (wlist.data[i].ignore_time != 0 && (wlist.time - wlist.data[i].ignore_time) < wlist.data[i].ignore_timeout) continue; + var btns = ''; + for (var n = 0; n < wlist.data[i].btns.length; n++) { + if (wlist.data[i].btns[n].type == 'javascript') { + btns += ' ' + wlist.data[i].btns[n].title + '' + } else { + btns += ' ' + wlist.data[i].btns[n].title + '' + } + } + $("#messageError").append('

                                      ' + wlist.data[i].body + btns + '

                                      '); + num++; + } + if (num > 0) $("#messageError").show(); + }); +} + +//按钮调用 +function warningTo(to_url, def) { + var loadT = layer.msg(lan.public.the_get, { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post(to_url, {}, function(rdata) { + layer.close(loadT); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status && def) setTimeout(function() { location.reload(); }, 1000); + },'json'); +} + +function setSafeHide() { + setCookie('safeMsg', '1'); + $("#safeMsg").remove(); +} + +//查看报告 +function showDanger(num, port) { + var atxt = "因未使用安全隔离登录,所有IP都可以尝试连接,存在较高风险,请立即处理。"; + if (port == "22") { + atxt = "因未修改SSH默认22端口,且未使用安全隔离登录,所有IP都可以尝试连接,存在较高风险,请立即处理。"; + } + layer.open({ + type: 1, + area: ['720px', '410px'], + title: '安全提醒(如你想放弃任何安全提醒通知,请删除宝塔安全登录插件)', + closeBtn: 1, + shift: 5, + content: '
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                      风险类型:暴力破解 说明
                                      累计遭遇攻击总数:' + num + ' 详细(数据直接来源本服务器日志)
                                      风险等级:较高风险
                                      风险描述:' + atxt + '
                                      可参考解决方案:

                                      方案一:修改SSH默认端口,修改SSH验证方式为数字证书,清除近期登陆日志。

                                      方案二:购买宝塔企业运维版,一键部署安全隔离服务,高效且方便。

                                      \ +
                                      ' + }); + $(".showDanger td").css("padding", "8px"); +} + +function pluginInit(){ + $.post('/plugins/init', function(data){ + if (!data.status){ + return false; + } + + var rdata = data.data; + var plugin_list = ''; + + for (var i = 0; i < rdata.length; i++) { + var ver = rdata[i]['versions']; + var select_list = ''; + if (typeof(ver)=='string'){ + select_list = ''; + } else { + for (var vi = 0; vi < ver.length; vi++) { + + if (ver[vi] == rdata[i]['default_ver']){ + select_list += ''; + } else { + select_list += ''; + } + } + } + + var pn_checked = ''; + if (rdata[i]['name'] == 'swap'){ + var pn_checked = ''; + } + + plugin_list += '
                                    • \ + \ + \ + \ + '+pn_checked+'
                                    • '; + } + + layer.open({ + type: 1, + title: '推荐安装', + area: ["320px", "400px"], + closeBtn: 2, + shadeClose: false, + content:"\ +
                                      \ +
                                      \ +

                                      推荐以下一键套件,或在软件管理按需选择。

                                      \ + \ +
                                      \ +
                                      \ +

                                      经典LNMP

                                      \ +
                                      \ +
                                        " + plugin_list + "
                                      \ +
                                      一键安装
                                      \ +
                                      \ +
                                      \ +
                                      ", + success:function(l,index){ + $('.rec-box-con .onekey').click(function(){ + var post_data = []; + for (var i = 0; i < rdata.length; i++) { + var key_ver = '#select_'+rdata[i]['name']; + var key_checked = '#data_'+rdata[i]['name']; + + var val_checked = $(key_checked).prop("checked"); + if (val_checked){ + + var tmp = {}; + var val_key = $(key_ver).val(); + + tmp['version'] = val_key; + tmp['name'] = rdata[i]['name']; + post_data.push(tmp); + } + } + + $.post('/plugins/init_install', 'list='+JSON.stringify(post_data), function(data){ + showMsg(data.msg, function(){ + if (data.status){ + layer.closeAll(); + messageBox(); + } + },{ icon: data.status ? 1 : 2 },2000); + },'json'); + }); + }, + cancel:function(){ + layer.confirm('是否不再显示推荐安装套件?', {btn : ['确定', '取消'],title: "不再显示推荐?"}, function() { + $.post('/files/create_dir', 'path=/www/server/php', function(rdata) { + layer.closeAll(); + },'json'); + }); + } + }); + },'json'); +} + +function appendOverviewItem(name, value, href, onclick) { + var linkStart = ''; + var linkEnd = ''; + if (onclick) { + linkStart = ''; + linkEnd = ''; + } else if (href) { + linkStart = ''; + linkEnd = ''; + } + + var html = '
                                    • ' + + '

                                      ' + name + '

                                      ' + + '
                                      ' + linkStart + value + linkEnd + '
                                      ' + + '
                                    • '; + $('#index_overview').append(html); +} + +function loadOverviewSystemStats() { + $.get('/overview_stats', function(res) { + if (!res || !res.status || !res.data) { + return; + } + + var data = res.data; + if ($('#overview_site_count').length) { + $('#overview_site_count').text(data.site_count); + } + + appendOverviewItem('数据库', '0', null, null); + appendOverviewItem('Docker容器', '0', null, null); + appendOverviewItem('待执行任务', data.pending_task_count, '/task/index'); + appendOverviewItem('计划任务', data.crontab_count, '/crontab/index'); + appendOverviewItem('防火墙规则', data.firewall_count, '/firewall/index'); + appendOverviewItem('应用', data.enabled_app_count, '/setting/index'); + }, 'json'); +} + +function loadOverviewDatabaseStats() { + var dbPlugins = ['mysql', 'pgsql', 'mongodb']; + var total = 0; + var done = 0; + + function flushDbTotal() { + if ($('#overview_db_total').length) { + $('#overview_db_total').text(total); + } + } + + if (dbPlugins.length === 0) { + flushDbTotal(); + } + + for (var i = 0; i < dbPlugins.length; i++) { + (function(pname) { + $.post('/plugins/run', {name: pname, func: 'get_total_statistics'}, function(data) { + var rdata; + try { + rdata = $.parseJSON(data['data']); + } catch(e) { + done++; + if (done >= dbPlugins.length) flushDbTotal(); + return; + } + + if (rdata && rdata['status'] && rdata['data']) { + var count = Number(rdata['data']['count'] || 0); + if (!isNaN(count) && count > 0) { + total += count; + } + } + + done++; + if (done >= dbPlugins.length) { + flushDbTotal(); + } + }, 'json').error(function() { + done++; + if (done >= dbPlugins.length) flushDbTotal(); + }); + })(dbPlugins[i]); + } + + // Docker 容器数量(未安装插件或读取失败时保持 0) + $.post('/plugins/run', {name: 'docker', func: 'get_total_statistics'}, function(data) { + var dockerCount = 0; + try { + var rdata = $.parseJSON(data['data']); + if (rdata && rdata['status'] && rdata['data']) { + dockerCount = Number(rdata['data']['count'] || 0); + if (isNaN(dockerCount) || dockerCount < 0) { + dockerCount = 0; + } + } + } catch(e) {} + + if ($('#overview_docker_total').length) { + $('#overview_docker_total').text(dockerCount); + } + }, 'json').error(function() { + if ($('#overview_docker_total').length) { + $('#overview_docker_total').text(0); + } + }); +} + +function loadKeyDataCount(){ + $('#index_overview .mw-overview-dynamic').remove(); + loadOverviewSystemStats(); + loadOverviewDatabaseStats(); +} + + +$(function() { + $("#memReleaseBtn").on("click", function() { + if ($(".mem-release").hasClass("mem-action")) { + return; + } + $(".mem-release").addClass("mem-action"); + reMemory(); + }); + + $("select[name='network-io'],select[name='disk-io']").change(function () { + var key = $(this).val(), type = $(this).attr('name'); + if (type == 'network-io') { + if (key == 'ALL') { + key = ''; + } + setCookie('network_io_key', key); + } else { + if (key == 'ALL') { + key = ''; + } + setCookie('disk_io_key', key); + } + }); + + $('.tabs-nav span').click(function () { + var indexs = $(this).index(); + $(this).addClass('active').siblings().removeClass('active'); + $('.tabs-content .tabs-item:eq(' + indexs + ')').addClass('tabs-active').siblings().removeClass('tabs-active'); + $('.tabs-down select:eq(' + indexs + ')').removeClass('hide').siblings().addClass('hide'); + switch (indexs) { + case 0: + if (index.net.table) { + index.net.table.resize(); + } + break; + case 1: + if (index.iostat.table) { + index.iostat.table.resize(); + } + break; + } + }) +}); + +var index = { + common:{ + ts:function (m) { return m < 10 ? '0' + m : m }, + format:function (sjc) { + var time = new Date(sjc); + var h = time.getHours(); + var mm = time.getMinutes(); + var s = time.getSeconds(); + return h+ ':' + mm + ':' +s; + }, + getTime:function () { + var now = new Date(); + var hour = now.getHours(); + var minute = now.getMinutes(); + var second = now.getSeconds(); + if (minute < 10) { + minute = "0" + minute; + } + if (second < 10) { + second = "0" + second; + } + var nowdate = hour + ":" + minute + ":" + second; + return nowdate; + }, + }, + net: { + table: null, + data: { + xData: [], + yData: [], + zData: [] + }, + default_unit : 'KB/s', + init_select : false, + init: function(){ + for (var i = 8; i >= 0; i--) { + var time = (new Date()).getTime(); + index.net.data.xData.push(index.common.format(time - (i * 3 * 1000))); + index.net.data.yData.push(0); + index.net.data.zData.push(0); + } + + var option = index.net.defaultOption(); + initEchartWhenReady('netImg', option, function (chart) { + index.net.table = chart; + window.addEventListener("resize", function () { + if (index.net.table) { + index.net.table.resize(); + } + }); + }); + }, + render:function(){ + if (!index.net.table) { + return; + } + var theme = getChartTheme(); + index.net.table.setOption({ + yAxis: { + name: '单位 '+ index.net.default_unit, + splitLine: { lineStyle: { color: theme.border } }, + axisLine: { lineStyle: { color: theme.border } }, + axisLabel: { color: theme.muted } + }, + xAxis: { + data: index.net.data.xData, + axisLine: { lineStyle: { color: theme.border } }, + axisLabel: { color: theme.muted } + }, + series: [{ + name: lan.index.net_up, + data: index.net.data.yData + }, { + name: lan.index.net_down, + data: index.net.data.zData + }] + }); + }, + defaultOption:function(){ + var theme = getChartTheme(); + var option = { + backgroundColor: theme.surfaceContainer, + color: [theme.primary, theme.secondary], + title: { + text: "", + left: 'center', + textStyle: { + color: theme.muted, + fontStyle: 'normal', + fontFamily: "Inter, PingFang SC, Microsoft YaHei, sans-serif", + fontSize: 14 + } + }, + tooltip: { + trigger: 'axis', + backgroundColor: theme.surface, + borderColor: theme.border, + textStyle: { color: theme.text }, + extraCssText: 'box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); border-radius: 12px; padding: 10px;', + axisPointer: { + type: 'line', + lineStyle: { color: theme.border } + }, + formatter :function (config) { + var _config = config, _tips = "时间:" + _config[0].axisValue + "
                                      "; + for (var i = 0; i < config.length; i++) { + if (typeof config[i].data == "undefined") { + return false; + } + _tips += '  ' + config[i].seriesName + ':' + config[i].data + ' '+ index.net.default_unit + '
                                      ' + } + return _tips; + } + + }, + legend: { + data: [lan.index.net_up, lan.index.net_down], + bottom: '2%', + textStyle: { color: theme.muted } + }, + grid: { + left: '2%', + right: '3%', + bottom: '15%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: index.net.data.xData, + axisLine: { + lineStyle: { + color: theme.border + } + }, + axisLabel: { color: theme.muted } + }, + yAxis: { + name: '单位 '+ index.net.default_unit, + splitLine: { + lineStyle: { color: theme.border } + }, + axisLine: { + lineStyle: { color: theme.border } + }, + axisLabel: { color: theme.muted } + }, + series: [{ + name: '上行', + type: 'line', + data: index.net.data.yData, + smooth: true, + showSymbol: false, + symbol: 'circle', + symbolSize: 6, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.primary, 0.35) + }, { + offset: 1, + color: applyColorAlpha(theme.primary, 0.08) + }], false) + } + }, + itemStyle: { + normal: { + color: theme.primary + } + }, + lineStyle: { + normal: { + width: 1 + } + } + }, + { + name: '下行', + type: 'line', + data: index.net.data.zData, + smooth: true, + showSymbol: false, + symbol: 'circle', + symbolSize: 6, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.secondary, 0.35) + }, { + offset: 1, + color: applyColorAlpha(theme.secondary, 0.08) + }], false) + } + }, + itemStyle: { + normal: { + color: theme.secondary, + } + }, + lineStyle: { + normal: { + width: 1, + } + } + }] + }; + return option; + }, + + + add: function (up, down) { + var _net = this; + var limit = 8; + var d = new Date() + if (_net.data.xData.length >= limit) _net.data.xData.splice(0, 1); + if (_net.data.yData.length >= limit) _net.data.yData.splice(0, 1); + if (_net.data.zData.length >= limit) _net.data.zData.splice(0, 1); + + _net.data.xData.push(d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds()); + + if (up>down){ + var upTmp = toSizePos(up); + var upTmpSize = upTmp['name'].split(' ')[0]; + index.net.default_unit = upTmp['name'].split(' ')[1] + '/s'; + + var downTmpSize = toSizePos(down,upTmp['pos'])['name'].split(' ')[0]; + // console.log('up',upTmp['pos'],toSizePos(down, upTmp['pos']),downTmpSize); + + _net.data.zData.push(downTmpSize); + _net.data.yData.push(upTmpSize); + } else { + + var downTmp = toSizePos(down); + var downTmpSize = downTmp['name'].split(' ')[0]; + index.net.default_unit = downTmp['name'].split(' ')[1] + '/s'; + + var upTmpSize = toSizePos(up, downTmp['pos'])['name'].split(' ')[0]; + // console.log('down',downTmp['pos'],toSizePos(up, downTmp['pos']),upTmpSize); + + _net.data.zData.push(downTmpSize); + _net.data.yData.push(upTmpSize); + } + + }, + renderSelect:function(net){ + // console.log(net); + if (!index.net.init_select){ + var option = ''; + var network = net.network; + var network_io_key = getCookie('network_io_key'); + + for (var name in network) { + if (name == 'ALL'){ + option += ''; + } else if (network_io_key == name){ + option += ''; + } else { + option += ''; + } + } + $('select[name="network-io"]').html(option); + index.net.init_select = true; + } + } + }, + + iostat:{ + table: null, + data: { + xData: [], + yData: [], + zData: [], + tipsData: [] + }, + init_select:false, + default_unit : 'MB/s', + init:function(){ + for (var i = 8; i >= 0; i--) { + var time = (new Date()).getTime(); + index.iostat.data.xData.push(index.common.format(time - (i * 3 * 1000))); + index.iostat.data.yData.push(0); + index.iostat.data.zData.push(0); + index.iostat.data.tipsData.push({}); + } + + var option = index.iostat.defaultOption(); + initEchartWhenReady('ioStat', option, function (chart) { + index.iostat.table = chart; + window.addEventListener("resize", function () { + if (index.iostat.table) { + index.iostat.table.resize(); + } + }); + }); + }, + + render:function(){ + if (!index.iostat.table) { + return; + } + var theme = getChartTheme(); + index.iostat.table.setOption({ + tooltip: { + trigger: 'axis', + backgroundColor: theme.surface, + borderColor: theme.border, + textStyle: { color: theme.text }, + extraCssText: 'box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); border-radius: 12px; padding: 10px;', + axisPointer: { + type: 'line', + lineStyle: { color: theme.border } + }, + formatter :function (config) { + var _config = config, _tips = "时间:" + _config[0].axisValue + "
                                      ", options = { + read_bytes: '读取字节数', + read_count: '读取次数 ', + read_merged_count: '合并读取次数', + read_time: '读取延迟', + write_bytes: '写入字节数', + write_count: '写入次数', + write_merged_count: '合并写入次数', + write_time: '写入延迟', + }, data = index.iostat.data.tipsData[config[0].dataIndex], list = ['read_count', 'write_count', 'read_merged_count', 'write_merged_count', 'read_time', 'write_time',]; + for (var i = 0; i < config.length; i++) { + if (typeof config[i].data == "undefined") { + return false; + } + _tips += '  ' + config[i].seriesName + ':' + config[i].data + ' MB/s' + '
                                      ' + } + $.each(list, function (index, item) { + + if (typeof data[item] != 'undefined'){ + _tips += '  ' + options[item] + ':' + data[item] + (item.indexOf('time') > -1 ? ' ms' : ' 次/秒') + '
                                      '; + } + }) + return _tips; + } + }, + yAxis: { + name: '单位 '+ index.iostat.default_unit, + splitLine: { lineStyle: { color: theme.border } }, + axisLine: { lineStyle: { color: theme.border } }, + axisLabel: { color: theme.muted } + }, + xAxis: { + data: index.iostat.data.xData, + axisLine: { lineStyle: { color: theme.border } }, + axisLabel: { color: theme.muted } + }, + series: [{ + name: "读取", + data: index.iostat.data.yData + }, { + name: "写入", + data: index.iostat.data.zData + }] + }); + }, + defaultOption:function(){ + var theme = getChartTheme(); + var option = { + backgroundColor: theme.surfaceContainer, + color: [theme.primary, theme.secondary], + title: { + text: "", + left: 'center', + textStyle: { + color: theme.muted, + fontStyle: 'normal', + fontFamily: "Inter, PingFang SC, Microsoft YaHei, sans-serif", + fontSize: 14 + } + }, + tooltip: { + trigger: 'axis', + backgroundColor: theme.surface, + borderColor: theme.border, + textStyle: { color: theme.text }, + extraCssText: 'box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); border-radius: 12px; padding: 10px;', + axisPointer: { + type: 'line', + lineStyle: { color: theme.border } + } + }, + legend: { + data: ["读取", "写入"], + bottom: '2%', + textStyle: { color: theme.muted } + }, + grid: { + left: '2%', + right: '3%', + bottom: '15%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: index.iostat.data.xData, + axisLine: { + lineStyle: { + color: theme.border + } + }, + axisLabel: { color: theme.muted } + }, + yAxis: { + name: '单位 '+ index.iostat.default_unit, + splitLine: { + lineStyle: { color: theme.border } + }, + axisLine: { + lineStyle: { color: theme.border } + }, + axisLabel: { color: theme.muted } + }, + series: [{ + name: '读取', + type: 'line', + data: index.iostat.data.yData, + smooth: true, + showSymbol: false, + symbol: 'circle', + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.primary, 0.3) + }, { + offset: 1, + color: applyColorAlpha(theme.primary, 0.06) + }], false) + } + }, + itemStyle: { + normal: { + color: theme.primary + } + }, + lineStyle: { + normal: { + width: 1, + } + } + }, + { + name: '写入', + type: 'line', + data: index.iostat.data.zData, + smooth: true, + showSymbol: false, + symbol: 'circle', + symbolSize: 6, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ + offset: 0, + color: applyColorAlpha(theme.secondary, 0.3) + }, { + offset: 1, + color: applyColorAlpha(theme.secondary, 0.06) + }], false) + } + }, + itemStyle: { + normal: { + color: theme.secondary + } + }, + lineStyle: { + normal: { + width: 1, + } + } + }] + }; + return option; + }, + + renderSelect:function(data){ + if (!index.iostat.init_select){ + var option = ''; + var iostat = data.iostat; + var disk_io_key = getCookie('disk_io_key'); + + for (var name in iostat) { + if (name == 'ALL'){ + option += ''; + } else if (disk_io_key == name){ + option += ''; + } else { + option += ''; + } + } + $('select[name="disk-io"]').html(option); + index.iostat.init_select = true; + } + }, + add: function (read, write, data) { + var _iostat = this; + var limit = 8; + var d = new Date() + if (_iostat.data.xData.length >= limit) _iostat.data.xData.splice(0, 1); + if (_iostat.data.yData.length >= limit) _iostat.data.yData.splice(0, 1); + if (_iostat.data.zData.length >= limit) _iostat.data.zData.splice(0, 1); + if (_iostat.data.tipsData.length >= limit) _iostat.data.tipsData.splice(0, 1); + + + var readTmpSize = toSizeMB(read).split(' ')[0]; + var writeTmpSize = toSizeMB(write).split(' ')[0]; + + _iostat.data.zData.push(writeTmpSize); + _iostat.data.yData.push(readTmpSize); + _iostat.data.tipsData.push(data); + _iostat.data.xData.push(d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds()); + }, + + }, + getData:function(){ + + $.get("/system/network", function(net) { + + //网络IO + var network_io_key = getCookie('network_io_key'); + var network_data = net.network; + var network_select = network_data['ALL']; + if (network_io_key && network_io_key != ''){ + network_select = network_data[network_io_key]; + } + + index.net.add(network_select.up,network_select.down); + index.net.render(); + index.net.renderSelect(net); + + $("#upSpeed").html(toSize(network_select.up)); + $("#downSpeed").html(toSize(network_select.down)); + + $("#downAll").html(toSize(network_select.downTotal)); + $("#downAll").attr('title','数据包:' + network_select.downPackets) + $("#upAll").html(toSize(network_select.upTotal)); + $("#upAll").attr('title','数据包:' + network_select.upPackets) + + + //磁盘IO + var disk_io_key = getCookie('disk_io_key'); + var iostat_data = net.iostat; + var iostat_select = iostat_data['ALL']; + if (disk_io_key && disk_io_key != ''){ + iostat_select = iostat_data[disk_io_key]; + } + + index.iostat.add(iostat_select.read_bytes,iostat_select.write_bytes, iostat_select); + index.iostat.render(); + index.iostat.renderSelect(net); + + $("#readBytes").html(toSize(iostat_select.read_bytes)); + $("#writeBytes").html(toSize(iostat_select.write_bytes)); + $("#diskIops").html(iostat_select.read_count+":"+iostat_select.write_count+ " 次"); + $("#diskTime").html(iostat_select.read_time+":"+iostat_select.write_time +" ms"); + + + $("#core").html(net.cpu[1] + " " + lan.index.cpu_core); + $("#state").html(net.cpu[0]); + + setcolor(net.cpu[0], "#state", 30, 70, 90); + //负载 + getLoad(net.load); + //内存 + setMemImg(net.mem); + //绑定hover事件 + setImg(); + showCpuTips(net); + + },'json'); + }, + task:function(){ + // index.getData(); + setInterval(function() { + index.getData(); + }, 3000); + }, + init: function(){ + index.net.init(); + index.iostat.init(); + index.task(); + } +} diff --git a/web/static/app/logs.js b/web/static/app/logs.js new file mode 100644 index 000000000..24590f2e2 --- /dev/null +++ b/web/static/app/logs.js @@ -0,0 +1,206 @@ + +$(document).ready(function(){ + logsLoad(); +}); + +function changeLogsViewH(){ + var l = $(window).height(); + $('.container-fluid .tab-view-box').css('height',l-80-40); + + $('#panelLogs').css('height',l-80-40-50); + + $('#logAudit .logAuditTab').css('height',l-80-40-50); + $('#logAudit .logAuditContent').css('height',l-80-40-50); + +} + +function logsLoad(){ + changeLogsViewH(); + $(window).resize(function(){ + changeLogsViewH(); + }); + + getLogs(1); +} + + + +$('#cutTab .tabs-item').click(function(){ + var type = $(this).data('name'); + + $('#cutTab .tabs-item').removeClass('active'); + $(this).addClass('active'); + + + $('.tab-view-box .tab-con').addClass('hide').removeClass('show').removeClass('w-full'); + $('#'+type).addClass('show').addClass('w-full'); + + switch(type){ + case 'panelLogs': + getLogs(1); + break; + case 'logAudit': + getAuditLogsFiles(); + break; + } + +}); + + +$('#panelLogs .refresh').click(function(){ + getLogs(1); +}); + +$('#panelLogs .clear').click(function(){ + delLogs(1); +}); + + +function getAuditLogsFiles(){ + var loadT = layer.msg('正在获取日志审计列表...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/logs/get_audit_logs_files',{}, function(rdata) { + var data = rdata.data; + layer.close(loadT); + var option = ''; + for (var i = 0; i < data.length; i++) { + var tip = data[i]['name'] +' - '+data[i]['title'] + '(' + toSize(data[i]['size']) + ')'; + if (i==0){ + option += '
                                      '+tip+'
                                      '; + } else { + option += '
                                      '+tip+'
                                      '; + } + } + $("#logAudit .logAuditTab").html(option); + + getAuditFile(data[0]['name']); + $('#logAudit .logAuditItem').click(function(){ + $('#logAudit .logAuditItem').removeClass('active'); + $(this).addClass('active'); + getAuditFile($(this).data('file')); + }); + + },'json'); +} + + +function getAuditFile(log_name){ + var loadT = layer.msg('正在获取日志审计内容...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/logs/get_audit_file',{log_name:log_name}, function(data) { + layer.close(loadT); + // console.log(data); + try{ + if (typeof(data) == 'object'){ + var plist = data.data; + + var pre_html ='
                                      \ +
                                      \ +
                                      \ + \ +
                                      \ + \ +
                                      \ +
                                      \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                      用户来源端口时间
                                      root117.139.193.29pts/02023-08-25 13:27 still logged in
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      ' + + // var pre_html = '\ + // \ + // \ + //
                                      时间角色事件
                                      '; + $('#logAudit .logAuditContent').html(pre_html); + + if (plist.length>0){ + var tmp = plist[0]; + var thead = ''; + tbody += '' + for (var i in tmp) { + tbody+=''+ i + ''; + } + tbody += ''; + $('#logAudit .logAuditContent thead').html(tbody); + } + + + var tbody = ''; + for (var i = 0; i < plist.length; i++) { + tbody += ''; + for (var vv in plist[i]) { + tbody+= ''+ plist[i][vv] + '' + } + tbody += ''; + } + $('#logAudit .logAuditContent tbody').html(tbody); + + $('#logAudit .refresh').click(function(){ + getAuditFile(log_name); + }); + } + + if (typeof(data) == 'string'){ + var cc = '
                                      \ +
                                      '+data+'
                                      \ +
                                      '; + $('#logAudit .logAuditContent').html(cc); + } + } catch (e) { + layer.msg(str(e),{icon:2,time:10000,shade: [0.3, '#000']}); + } + }); +} + +function getLogs(page,search) { + search = search == undefined ? '':search; + var loadT = layer.load(); + $.post('/logs/get_log_list','limit=10&p=' + page+"&search="+search, function(data) { + layer.close(loadT); + var body = ''; + for (var i = 0; i < data.data.length; i++) { + body += "\ + " + data.data[i].id + "\ + " + data.data[i].type + "\ + " + data.data[i].log + "\ + " + data.data[i].add_time + "\ + "; + } + $("#operationLog tbody").html(body); + $("#panelLogs .page").html(data.page); + },'json'); +} + +function delLogs(){ + layer.confirm('即将清空面板日志,继续吗?',{title:'清空日志',closeBtn:2},function(){ + var loadT = layer.msg('正在清理,请稍候...',{icon:16}); + $.post('/logs/del_panel_logs','',function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + getLogs(1); + },'json'); + }); +} \ No newline at end of file diff --git a/web/static/app/monitor_observe.js b/web/static/app/monitor_observe.js new file mode 100644 index 000000000..83d83f06d --- /dev/null +++ b/web/static/app/monitor_observe.js @@ -0,0 +1,329 @@ +(function () { + const rangeButtons = document.querySelectorAll('[data-range]'); + let currentRange = '24h'; + let currentStatus = { + monitorOpen: true, + onlyNet: true, + }; + + function setActiveRange(range) { + currentRange = range; + rangeButtons.forEach((btn) => { + btn.classList.toggle('is-active', btn.dataset.range === range); + }); + loadOverview(); + } + + rangeButtons.forEach((btn) => { + btn.addEventListener('click', () => setActiveRange(btn.dataset.range)); + }); + + async function requestJson(url, options) { + const response = await fetch(url, { + cache: 'no-store', + ...options, + }); + if (!response.ok) { + throw new Error('request failed'); + } + return response.json(); + } + + function toPercent(value) { + if (value === null || value === undefined) return '--'; + return `${Number(value).toFixed(1)}%`; + } + + function toMbpsFromKB(value) { + if (value === null || value === undefined) return '--'; + const mbps = (Number(value) * 8) / 1024; + return `${mbps.toFixed(2)} Mbps`; + } + + function toMBFromBytes(value) { + if (value === null || value === undefined) return '--'; + return `${(Number(value) / (1024 * 1024)).toFixed(2)} MB`; + } + + function updateKpi(summary) { + if (!summary) { + return; + } + const cpuEl = document.querySelector('[data-metric="cpu_latest"]'); + const memEl = document.querySelector('[data-metric="mem_latest"]'); + const netEl = document.querySelector('[data-metric="net_latest"]'); + const diskEl = document.querySelector('[data-metric="disk_latest"]'); + + if (cpuEl) { + cpuEl.textContent = toPercent(summary.cpu.latest); + cpuEl.closest('[data-kpi="cpu"]').querySelector('.mw-observe-kpi-desc').textContent = `峰值 ${toPercent(summary.cpu.peak)}`; + updateMeter(cpuEl.closest('[data-kpi="cpu"]'), summary.cpu.latest, 100); + } + + if (memEl) { + memEl.textContent = toPercent(summary.mem.latest); + memEl.closest('[data-kpi="mem"]').querySelector('.mw-observe-kpi-desc').textContent = `峰值 ${toPercent(summary.mem.peak)}`; + updateMeter(memEl.closest('[data-kpi="mem"]'), summary.mem.latest, 100); + } + + if (netEl) { + netEl.textContent = toMbpsFromKB(summary.net.latest); + netEl.closest('[data-kpi="net"]').querySelector('.mw-observe-kpi-desc').textContent = `峰值 ${toMbpsFromKB(summary.net.peak)}`; + updateMeter(netEl.closest('[data-kpi="net"]'), summary.net.latest, summary.net.peak || 1); + } + + if (diskEl) { + diskEl.textContent = toMBFromBytes(summary.disk.latest); + diskEl.closest('[data-kpi="disk"]').querySelector('.mw-observe-kpi-desc').textContent = `峰值 ${toMBFromBytes(summary.disk.peak)}`; + updateMeter(diskEl.closest('[data-kpi="disk"]'), summary.disk.latest, summary.disk.peak || 1); + } + } + + function updateMeter(root, value, max) { + const meter = root.querySelector('.mw-observe-kpi-meter span'); + if (!meter) return; + if (value === null || value === undefined || max === null || max === undefined || Number(max) <= 0) { + meter.style.width = '0%'; + return; + } + const pct = Math.min((Number(value) / Number(max)) * 100, 100); + meter.style.width = `${pct.toFixed(1)}%`; + } + + function updateEvents(events) { + const list = document.getElementById('observeEvents'); + if (!list) return; + list.innerHTML = ''; + if (!events || events.length === 0) { + const item = document.createElement('li'); + const message = currentStatus.monitorOpen ? '监控正在采样,暂未生成峰值' : '监控未开启,暂无峰值记录'; + item.innerHTML = `
                                      暂无可用事件
                                      ${message}
                                      `; + list.appendChild(item); + return; + } + events.forEach((event) => { + const item = document.createElement('li'); + item.innerHTML = `
                                      ${event.title}
                                      ${event.time}
                                      `; + list.appendChild(item); + }); + } + + function initChart(domId) { + const el = document.getElementById(domId); + if (!el || !window.echarts) return null; + return window.echarts.init(el); + } + + const charts = { + load: initChart('getloadview'), + cpu: initChart('cupview'), + mem: initChart('memview'), + disk: initChart('diskview'), + net: initChart('network'), + }; + + function buildLineOption(labels, series, yLabel) { + return { + animation: false, + tooltip: { trigger: 'axis' }, + legend: { + top: 8, + }, + toolbox: { + right: 8, + feature: { + dataZoom: { yAxisIndex: 'none' }, + restore: {}, + saveAsImage: {}, + }, + }, + grid: { left: '3%', right: '4%', bottom: 44, containLabel: true }, + xAxis: { type: 'category', boundaryGap: false, data: labels }, + yAxis: { type: 'value', name: yLabel || '' }, + dataZoom: [ + { type: 'inside', start: 0, end: 100 }, + { type: 'slider', bottom: 6, start: 0, end: 100, height: 18 }, + ], + series, + }; + } + + function buildEmptyOption(message) { + return { + title: { + text: message || '暂无数据', + left: 'center', + top: 'middle', + textStyle: { color: '#9aa0a6', fontSize: 14, fontWeight: 400 }, + }, + xAxis: { show: false }, + yAxis: { show: false }, + series: [], + }; + } + + function renderChart(chart, option, hasData, message) { + if (!chart) return; + if (!hasData) { + chart.setOption(buildEmptyOption(message || '暂无监控数据')); + return; + } + chart.setOption(option); + } + + function getEmptyMessage() { + if (!currentStatus.monitorOpen) { + return '监控未开启,请在左侧开启'; + } + return '监控正在采样,请稍候'; + } + + function renderCharts(series) { + series = series || {}; + const loadSeries = series.load || { labels: [], one: [], five: [], fifteen: [] }; + const cpuSeries = series.cpu || { labels: [], cpu: [], mem: [] }; + const diskSeries = series.disk || { labels: [], read: [], write: [] }; + const netSeries = series.net || { labels: [], up: [], down: [] }; + + const emptyMessage = getEmptyMessage(); + renderChart(charts.load, buildLineOption(loadSeries.labels, [ + { name: '1分钟', type: 'line', data: loadSeries.one, smooth: true }, + { name: '5分钟', type: 'line', data: loadSeries.five, smooth: true }, + { name: '15分钟', type: 'line', data: loadSeries.fifteen, smooth: true }, + ]), loadSeries.labels && loadSeries.labels.length > 0, emptyMessage); + + renderChart(charts.cpu, buildLineOption(cpuSeries.labels, [ + { name: 'CPU使用率', type: 'line', data: cpuSeries.cpu, smooth: true }, + ], '%'), cpuSeries.labels && cpuSeries.labels.length > 0, emptyMessage); + + renderChart(charts.mem, buildLineOption(cpuSeries.labels, [ + { name: '内存使用率', type: 'line', data: cpuSeries.mem, smooth: true }, + ], '%'), cpuSeries.labels && cpuSeries.labels.length > 0, emptyMessage); + + renderChart(charts.disk, buildLineOption(diskSeries.labels, [ + { name: '读取', type: 'line', data: diskSeries.read, smooth: true }, + { name: '写入', type: 'line', data: diskSeries.write, smooth: true }, + ], 'MB'), diskSeries.labels && diskSeries.labels.length > 0, emptyMessage); + + renderChart(charts.net, buildLineOption(netSeries.labels, [ + { name: '上行', type: 'line', data: netSeries.up, smooth: true }, + { name: '下行', type: 'line', data: netSeries.down, smooth: true }, + ], 'Mbps'), netSeries.labels && netSeries.labels.length > 0, emptyMessage); + } + + async function loadOverview() { + try { + const result = await requestJson(`/monitor/api/overview?range=${currentRange}`); + if (!result.status || !result.data) { + renderCharts(); + return; + } + updateKpi(result.data.summary || {}); + updateEvents(result.data.events || []); + renderCharts(result.data.series || {}); + } catch (error) { + renderCharts(); + } + } + + function resizeCharts() { + Object.values(charts).forEach((chart) => { + if (chart) { + chart.resize(); + } + }); + } + + async function getStatus() { + try { + const form = new URLSearchParams(); + form.append('type', '-1'); + const result = await requestJson('/system/set_control', { method: 'POST', body: form }); + currentStatus.monitorOpen = result.status; + currentStatus.onlyNet = result.stat_all_status; + document.getElementById('save_day').value = result.day; + renderSwitches(); + } catch (error) { + // ignore + } + } + + function renderSwitches() { + const openContainer = document.getElementById('openJK'); + const netContainer = document.getElementById('statAll'); + if (openContainer) { + openContainer.innerHTML = ` + + + `; + openContainer.querySelector('input').addEventListener('change', (event) => { + setControl('openjk', event.target.checked); + }); + } + if (netContainer) { + netContainer.innerHTML = ` + + + `; + netContainer.querySelector('input').addEventListener('change', (event) => { + setControl('stat', event.target.checked); + }); + } + } + + window.setControl = async function (act, value) { + let type = ''; + let day = document.getElementById('save_day').value; + + if (act === 'openjk') { + type = value ? '1' : '0'; + if (Number(day) < 1) { + layer.msg('保存天数不合法!', { icon: 2 }); + return; + } + } else if (act === 'stat') { + type = value ? '3' : '2'; + } else if (act === 'save_day') { + type = currentStatus.monitorOpen ? '1' : '0'; + if (Number(day) < 1) { + layer.msg('保存天数不合法!', { icon: 2 }); + return; + } + } + + const form = new URLSearchParams(); + form.append('type', type); + form.append('day', day); + + try { + const result = await requestJson('/system/set_control', { method: 'POST', body: form }); + layer.msg(result.msg, { icon: result.status ? 1 : 2 }); + getStatus(); + } catch (error) { + layer.msg('操作失败', { icon: 2 }); + } + }; + + window.closeControl = async function () { + layer.confirm('您真的清空所有监控记录吗?', { title: '清空记录', icon: 3, closeBtn: 1 }, async function () { + const form = new URLSearchParams(); + form.append('type', 'del'); + try { + const result = await requestJson('/system/set_control', { method: 'POST', body: form }); + layer.msg(result.msg, { icon: result.status ? 1 : 2 }); + loadOverview(); + } catch (error) { + layer.msg('操作失败', { icon: 2 }); + } + }); + }; + + getStatus(); + loadOverview(); + window.addEventListener('resize', resizeCharts); + setInterval(() => { + getStatus(); + loadOverview(); + resizeCharts(); + }, 5000); +})(); diff --git a/web/static/app/public.js b/web/static/app/public.js new file mode 100755 index 000000000..3579d64d1 --- /dev/null +++ b/web/static/app/public.js @@ -0,0 +1,2814 @@ + +$(document).ready(function() { + $(".sub-menu a.sub-menu-a").click(function() { + $(this).next(".sub").slideToggle("slow").siblings(".sub:visible").slideUp("slow"); + }); +}); + +function toSize(a) { + var d = [" B", " KB", " MB", " GB", " TB", " PB"]; + var e = 1024; + for(var b = 0; b < d.length; b++) { + if(a < e) { + return(b == 0 ? a : a.toFixed(2)) + d[b] + } + a /= e; + } +} + +function toSizePos(a, pos = 0) { + var d = [" B", " KB", " MB", " GB", " TB", " PB"]; + var e = 1024; + var r = {}; + for(var b = 0; b < d.length; b++) { + if (pos > 0){ + if (b == pos){ + r['name'] = (b == 0 ? a : a.toFixed(2)) + d[b]; + r['pos'] = b; + return r + } + } else { + if( a < e) { + r['name'] = (b == 0 ? a : a.toFixed(2)) + d[b]; + r['pos'] = b; + return r + } + } + a /= e; + } +} + +function toSizeMB(a) { + var d = [" KB", " MB"]; + var e = 1024; + var i = 0; + for(var b = 0; b < d.length; b++) { + a /= e; + i = b; + } + return a.toFixed(2) + d[i] +} + +function toTrim(x) { + return x.replace(/^\s+|\s+$/gm,''); +} + +function inArray(f, arr){ + for (var i = 0; i < arr.length; i++) { + if (f == arr[i]) { + return true; + } + } + return false; +} + +//表格头固定 +function tableFixed(name) { + var tableName = document.querySelector('#' + name); + tableName.addEventListener('scroll', scrollHandle); +} + +function escapeHTML(a){ + a = "" + a; + return a.replace(/&/g, "&").replace(//g, ">").replace(/"/g, '"'). + replace(/'/g,"‘").replace(/\(/g,"(").replace(/\</g,"<"). + replace(/\>/g,">").replace(/`/g,"`").replace(/=/g,"="); +} + +function scrollHandle(e) { + var scrollTop = this.scrollTop; + //this.querySelector('thead').style.transform = 'translateY(' + scrollTop + 'px)'; + $(this).find("thead").css({ "transform": "translateY(" + scrollTop + "px)", "position": "relative", "z-index": "1" }); +} + + +//转换单们到MB +function toSizeM(byteLen) { + var a = parseInt(byteLen) / 1024 / 1024; + return a || 0; +} + +//字节单位转换MB +function toSizeG(bytes){ + var c = 1024 * 1024; + var b = 0; + if(bytes > 0){ + var b = (bytes/c).toFixed(2); + } + return b; +} + +//to unixtime +function toUnixTime(txt){ + var unix = new Date(Date.parse(txt.replace(/-/g,'/'))).getTime(); + return unix/1000; + } + +function randomStrPwd(b) { + b = b || 32; + // &!@% + var c = "AaBbCcDdEeFfGHhiJjKkLMmNnPpRSrTsWtXwYxZyz2345678"; + var a = c.length; + var d = ""; + for(i = 0; i < b; i++) { + d += c.charAt(Math.floor(Math.random() * a)) + } + return d +} + +function getRandomString(len) { + len = len || 32; + var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; // 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1 + var maxPos = chars.length; + var pwd = ''; + for (i = 0; i < len; i++) { + pwd += chars.charAt(Math.floor(Math.random() * maxPos)); + } + return pwd; +} + +//验证IP地址 +function isValidIP(ip) { + var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/ + return reg.test(ip); +} + +function isContains(str, substr) { + return str.indexOf(substr) >= 0; +} + + +function filterPath(path){ + var path_arr = path.split('/'); + var path_new = []; + for (var i = 0; i < path_arr.length; i++) { + if (path_arr[i]!=''){ + path_new.push(path_arr[i]); + } + } + var rdata = "/"+path_new.join('/'); + return rdata; +} + +function msgTpl(msg, args){ + if (typeof args == 'string'){ + return msg.replace('{1}', args); + } else if (typeof args == 'object'){ + for (var i = 0; i < args.length; i++) { + rep = '{' + (i + 1) + '}'; + msg = msg.replace(rep, args[i]); + } + } + return msg; +} + +function refresh() { + window.location.reload() +} + +function mwsPost(path, args, callback){ + $.post(path, args, function(rdata){ + if(typeof(callback) == 'function'){ + callback(rdata); + } + },'json'); +} + +function syncPost(path, args){ + var retData; + $.ajax({ + type : 'post', + url : path, + data : args, + async : false, + dataType:'json', + success : function(data){ + retData = data; + } + }); + return retData; +} + +function repeatPwd(a) { + $("#MyPassword").val(randomStrPwd(a)) +} + +$(".menu-icon").click(function() { + $(".sidebar-scroll").toggleClass("sidebar-close"); + $(".main-content").toggleClass("main-content-open"); + if($(".sidebar-close")) { + $(".sub-menu").find(".sub").css("display", "none"); + } +}); + +var Upload, percentage; +Date.prototype.format = function(b) { + var c = { + "M+": this.getMonth() + 1, + "d+": this.getDate(), + "h+": this.getHours(), + "m+": this.getMinutes(), + "s+": this.getSeconds(), + "q+": Math.floor((this.getMonth() + 3) / 3), + S: this.getMilliseconds() + }; + if(/(y+)/.test(b)) { + b = b.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)) + } + for(var a in c) { + if(new RegExp("(" + a + ")").test(b)) { + b = b.replace(RegExp.$1, RegExp.$1.length == 1 ? c[a] : ("00" + c[a]).substr(("" + c[a]).length)) + } + } + return b +}; + +function getLocalTime(a) { + a = a.toString(); + if(a.length > 10) { + a = a.substring(0, 10) + } + return new Date(parseInt(a) * 1000).format("yyyy/MM/dd hh:mm:ss") +} + +function getFormatTime(tm, format) { + if (format == undefined) format = "yyyy/MM/dd hh:mm:ss"; + tm = tm.toString(); + if (tm.length > 10) { + tm = tm.substring(0, 10); + } + var data = new Date(parseInt(tm) * 1000); + var o = { + "M+": data.getMonth() + 1, //month + "d+": data.getDate(), //day + "h+": data.getHours(), //hour + "m+": data.getMinutes(), //minute + "s+": data.getSeconds(), //second + "q+": Math.floor((data.getMonth() + 3) / 3), //quarter + "S": data.getMilliseconds() //millisecond + } + if (/(y+)/.test(format)) format = format.replace(RegExp.$1, + (data.getFullYear() + "").substr(4 - RegExp.$1.length)); + for (var k in o) + if (new RegExp("(" + k + ")").test(format)) + format = format.replace(RegExp.$1,RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)); + return format; +} + + + +function changePathCallback(default_dir, callback) { + var c = layer.open({ + type: 1, + area: "650px", + title: '选择目录', + closeBtn: 1, + shift: 5, + shadeClose: false, + content: "
                                      \ +
                                      \ + \ +
                                      当前路径:
                                      \ +
                                      \ +
                                      \ +
                                      \ +
                                      计算机
                                      \ +
                                      \ +
                                      \ +
                                        \ +
                                        \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                        文件名修改时间权限所有者
                                        \ +
                                        \ +
                                        \ +
                                        \ +
                                        \ +
                                        \ + \ + \ + \ +
                                        ", + success:function(layero,layer_index){ + $('.btn-close').click(function(){ + layer.close(layer_index); + }); + + $('.btn-choose').click(function(){ + var a = $("#PathPlace").find("span").text(); + a = a.replace(new RegExp(/(\\)/g), "/"); + a_len = a.length; + if (a[a_len-1] == '/'){ + a = a.substr(0,a_len-1); + } + callback(a); + layer.close(layer_index); + }); + } + }); + getDiskList(default_dir); + activeDisk(); +} + +function changePath(d) { + setCookie('SetId', d); + setCookie('SetName', ''); + var c = layer.open({ + type: 1, + area: "650px", + title: '选择目录', + closeBtn: 1, + shift: 5, + shadeClose: false, + content: "
                                        \ +
                                        \ + \ +
                                        当前路径:
                                        \ +
                                        \ +
                                        \ +
                                        计算机
                                        \ +
                                        \ +
                                          \ +
                                          \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                          文件名修改时间权限所有者
                                          \ +
                                          \ +
                                          \ +
                                          \ +
                                          \ +
                                          \ + \ + \ + \ +
                                          " + }); + setCookie("changePath", c); + var b = $("#" + d).val(); + tmp = b.split("."); + if(tmp[tmp.length - 1] == "gz") { + tmp = b.split("/"); + b = ""; + for(var a = 0; a < tmp.length - 1; a++) { + b += "/" + tmp[a] + } + setCookie("SetName", tmp[tmp.length - 1]) + } + b = b.replace(/\/\//g, "/"); + getDiskList(b); + activeDisk(); +} + +function getDiskList(b) { + var d = ""; + var a = ""; + var c = "path=" + b + "&disk=True&row=1000"; + $.post("/files/get_dir", c, function(h) { + // console.log(h); + // if(h.dir != undefined) { + // for(var f = 0; f < h.dir.length; f++) { + // a += "
                                           " + h.dir[f].path + "
                                          "; + // } + // $("#changecomlist").html(a); + // } + for(var f = 0; f < h.dir.length; f++) { + var g = h.dir[f].split(";"); + var e = g[0]; + + if(isChineseChar(e)) { + if(e.length > 10) { + e = e.substring(0, 10) + "..."; + } + } else{ + if(e.length > 20) { + e = e.substring(0, 20) + "..."; + } + } + + d += "\ + \ + " + e + "" + getLocalTime(g[2]) + "\ + " + g[3] + "\ + " + g[4] + "\ + X\ + "; + } + if(h.files != null && h.files != "") { + for(var f = 0; f < h.files.length; f++) { + var g = h.files[f].split(";"); + var e = g[0]; + if(isChineseChar(e)) { + if(e.length > 10) { + e = e.substring(0, 10) + "..." + } + } else{ + if(e.length > 20) { + e = e.substring(0, 20) + "..." + } + } + + d += "\ + " + e + "\ + " + getLocalTime(g[2]) + "\ + " + g[3] + "\ + " + g[4] + "\ + \ + "; + } + } + $(".default").hide(); + $(".file-list").show(); + $("#tbody").html(d); + if(h.path.substr(h.path.length - 1, 1) != "/") { + h.path += "/"; + } + $("#PathPlace").find("span").html(h.path); + activeDisk(); + return; + },'json'); +} + +function createFolder() { + var a = "\ + \ + \ +   \ + "; + if($("#tbody tr").length == 0) { + $("#tbody").append(a) + } else { + $("#tbody tr:first-child").before(a) + } + $(".newFolderName").focus(); + $("#nameOk").click(function() { + var c = $("#newFolderName").val(); + var b = $("#PathPlace").find("span").text(); + newTxt = b.replace(new RegExp(/(\/\/)/g), "/") + c; + var d = "path=" + newTxt; + $.post("/files/create_dir", d, function(e) { + if(e.status == true) { + layer.msg(e.msg, { + icon: 1 + }) + } else { + layer.msg(e.msg, { + icon: 2 + }) + } + getDiskList(b); + },'json'); + }); + $("#nameNOk").click(function() { + $(this).parents("tr").remove() + }) +} + +function newDelFile(c) { + var a = $("#PathPlace").find("span").text(); + newTxt = c.replace(new RegExp(/(\/\/)/g), "/"); + var b = "path=" + newTxt + "&empty=True"; + $.post("/files/delete_dir", b, function(d) { + if(d.status == true) { + layer.msg(d.msg, { + icon: 1 + }) + } else { + layer.msg(d.msg, { + icon: 2 + }) + } + getDiskList(a); + },'json'); +} + +function activeDisk() { + var a = $("#PathPlace").find("span").text().substring(0, 1); + switch(a) { + case "C": + $(".path-con-left dd:nth-of-type(1)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "D": + $(".path-con-left dd:nth-of-type(2)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "E": + $(".path-con-left dd:nth-of-type(3)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "F": + $(".path-con-left dd:nth-of-type(4)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "G": + $(".path-con-left dd:nth-of-type(5)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "H": + $(".path-con-left dd:nth-of-type(6)").css("background", "#eee").siblings().removeAttr("style"); + break; + default: + $(".path-con-left dd").removeAttr("style") + } +} + +function backMyComputer() { + // $(".default").show(); + // $(".file-list").hide(); + // $("#PathPlace").find("span").html(""); + // activeDisk(); + return; +} + +function backFile() { + var c = $("#PathPlace").find("span").text(); + if(c.substr(c.length - 1, 1) == "/") { + c = c.substr(0, c.length - 1) + } + var d = c.split("/"); + var a = ""; + if(d.length > 1) { + var e = d.length - 1; + for(var b = 0; b < e; b++) { + a += d[b] + "/" + } + getDiskList(a.replace("//", "/")) + } else { + a = d[0] + } + if(d.length == 1) {} +} + +function getfilePath() { + var a = $("#PathPlace").find("span").text(); + a = a.replace(new RegExp(/(\\)/g), "/"); + a_len = a.length; + if (a[a_len-1] == '/'){ + a = a.substr(0,a_len-1); + } + + $("#" + getCookie("SetId")).val(a + getCookie("SetName")); + layer.close(getCookie("changePath")); + return a; +} + +function setCookie(a, c) { + var b = 30; + var d = new Date(); + d.setTime(d.getTime() + b * 24 * 60 * 60 * 1000); + document.cookie = a + "=" + escape(c) + ";path=/;expires=" + d.toGMTString(); +} + +function getCookie(b) { + var a, c = new RegExp("(^| )" + b + "=([^;]*)(;|$)"); + if(a = document.cookie.match(c)) { + return unescape(a[2]) + } else { + return null + } +} + +function autoHeight() { + var a = $("body").height() - 40; + $(".main-content").css("min-height", a); +} + +function showMsg(msg, callback ,icon, time){ + + if (typeof time == 'undefined'){ + time = 2000; + } + + if (typeof icon == 'undefined'){ + icon = {}; + } + + var loadT = layer.msg(msg, icon); + setTimeout(function() { + layer.close(loadT); + if (typeof callback == 'function'){ + callback(); + } + }, time); +} + +function openPath(a) { + setCookie("open_dir_path", a); + window.location.href = "/files/index"; +} + +function onlineEditFile(k, f, callback) { + if(k != 0) { + var l = $("#PathPlace input").val(); + var h = encodeURIComponent($("#textBody").val()); + var a = $("select[name=encoding]").val(); + var loadT = layer.msg('正在保存,请稍候...', {icon: 16,time: 0}); + $.post("/files/save_body", "data=" + h + "&path=" + encodeURIComponent(f) + "&encoding=" + a, function(data) { + if(k == 1) { + layer.close(loadT); + } + layer.msg(data.msg, {icon: data.status ? 1 : 2}); + if (data.status && typeof(callback) == 'function'){ + callback(k, f); + } + },'json'); + return + } + + var g = f.split("."); + var b = g[g.length - 1]; + var d; + switch(b) { + case "html": + var j = { + name: "htmlmixed", + scriptTypes: [{ + matches: /\/x-handlebars-template|\/x-mustache/i, + mode: null + }, { + matches: /(text|application)\/(x-)?vb(a|script)/i, + mode: "vbscript" + }] + }; + d = j; + break; + case "htm": + var j = { + name: "htmlmixed", + scriptTypes: [{ + matches: /\/x-handlebars-template|\/x-mustache/i, + mode: null + }, { + matches: /(text|application)\/(x-)?vb(a|script)/i, + mode: "vbscript" + }] + }; + d = j; + break; + case "js": + d = "text/javascript"; + break; + case "json": + d = "application/ld+json"; + break; + case "css": + d = "text/css"; + break; + case "php": + d = "application/x-httpd-php"; + break; + case "tpl": + d = "application/x-httpd-php"; + break; + case "xml": + d = "application/xml"; + break; + case "sql": + d = "text/x-sql"; + break; + case "conf": + d = "text/x-nginx-conf"; + break; + default: + var j = { + name: "htmlmixed", + scriptTypes: [ + {matches: /\/x-handlebars-template|\/x-mustache/i,mode: null}, + {matches: /(text|application)\/(x-)?vb(a|script)/i,mode: "vbscript"} + ] + }; + d = j; + } + + + + var codding = ["utf-8", "GBK", "GB2312", "BIG5"]; + var code_mirror = null; + var code_timer = null; + + function getBody(callback){ + $.post("/files/get_body", "path=" + encodeURIComponent(f), function(rdata) { + if (typeof(callback) == 'function'){ + callback(rdata); + } + },'json'); + } + + + function renderBody(callback){ + getBody(function(rdata){ + if(rdata.status === false){ + layer.close(r); + layer.msg(rdata.msg,{icon:5}); + return; + } + + if (typeof(callback) == 'function'){ + callback(rdata); + } + + var coding_html = ""; + for(var p = 0; p < codding.length; p++) { + var coding_selected = rdata.data.encoding == codding[p] ? "selected" : ""; + coding_html += '"; + } + + $("select[name=encoding]").html(coding_html); + }); + } + + var r = layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: ["90%", "90%"], + btn:['保存', '关闭', '刷新', '开启自动刷新', '关闭自动刷新'], + title: "在线编辑[" + f + "]", + shade: 0.0000001, + content: '
                                          \ +
                                          \ +

                                          提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!\ + \ +

                                          \ + '; + $(".taskcon").html(lbody); + var ob = document.getElementById('exec_log'); + ob.scrollTop = ob.scrollHeight; + }); +} + +/** + * 获取时分秒 + * @param {Number} seconds 总秒数 + * @param {String} dateFormat 返回的日期格式,默认为'H:i:s' + */ +function getSFM(seconds, dateFormat = 'H:i:s') { + + var obj = {}; + obj.H = Number.parseInt(seconds / 3600); + obj.i = Number.parseInt((seconds - obj.H * 3600) / 60); + obj.s = Number.parseInt(seconds - obj.H * 3600 - obj.i * 60); + if (obj.H < 10) { + obj.H = '0' + obj.H; + } + if (obj.i < 10) { + obj.i = '0' + obj.i; + } + if (obj.s < 10) { + obj.s = '0' + obj.s; + } + + // 3.解析 + var rs = dateFormat.replace('H', obj.H).replace('i', obj.i).replace('s', obj.s); + return rs; +} + +function remind(a){ + a = a == undefined ? 1 : a; + $(".taskcon").html(''); + $.post("/task/list", "table=tasks&result=2,4,6,8&limit=10&p=" + a, function(g) { + var e = ''; + var f = false; + for(var d = 0; d < g.data.length; d++) { + var status = g.data[d].status; + var status_text = '已经完成'; + var cos_text = ''; + if (status == '1'){ + status_text = '完成'; + cos_text = '耗时['+getSFM(g.data[d].end - g.data[d].start)+']' + } else if (status == '0'){ + status_text = '正在处理'; + cos_text = '等待中..'; + } else if (status == '-1'){ + status_text = '安装中'; + cos_text = '..'; + } + + e += '\ + \ + \ +
                                          '+g.data[d].name+'\ + 【'+status_text+'】\ + '+cos_text+'\ +
                                          \ + \ + '+g.data[d].add_time+'\ + '; + } + var con = '
                                          \ + \ + \ + \ + \ + \ + \ + '+e+'\ +
                                          '+lan.bt.task_name+''+lan.bt.task_time+'
                                          \ +
                                          \ +
                                          \ + \ +
                                          \ +
                                          '; + $(".taskcon").html(con); + + $(".msg_count").text(g.count); + $("#taskPage").html(g.page); + $("#Rs-checkAll").click(function(){ + if($(this).prop("checked")){ + $("#remind").find("input").prop("checked",true); + } else { + $("#remind").find("input").prop("checked",false); + } + }); + },'json'); +} + +function getReloads() { + var mm = $("#msg_box .bt-w-menu .bgw").html(); + if(mm == undefined || mm.indexOf('任务列表') == -1) { + clearInterval(speed); + speed = null; + return + } + if(speed) {return;} + + function renderRunTask(){ + var mm = $("#msg_box .bt-w-menu .bgw").html(); + if(mm == undefined || mm.indexOf('任务列表') == -1) { + clearInterval(speed); + speed = null; + a = 0; + return + } + $.post('/task/get_task_speed', '', function(h) { + if(h.task == undefined) { + $(".task_count").text(0); + $(".cmdlist").html('当前没有任务!'); + return; + } + var b = ''; + var d = ''; + for(var g = 0; g < h.task.length; g++) { + if(h.task[g].status == "-1") { + if(h.task[g].type != "download") { + var c = ""; + var f = h.msg.split("\n"); + for(var e = 0; e < f.length; e++) { + c += f[e] + "
                                          "; + } + if(h.task[g].name.indexOf("扫描") != -1) { + b = "
                                        • \ + " + h.task[g].name + "\ + 正在扫描 | 关闭\ + \ +
                                          " + c + "
                                          \ +
                                        • "; + } else { + b = "
                                        • \ + " + h.task[g].name + "\ + 正在安装 | 关闭\ +
                                          " + c + "
                                          \ +
                                        • "; + } + } else { + b = "
                                        • \ +
                                          \ + " + h.task[g].name + "" + (toSize(h.msg.used) + "/" + toSize(h.msg.total)) + "\ + " + h.msg.pre + "%\ + 下载中 | "+lan.public.close+"\ +
                                        • " + } + } else { + d += "
                                        • " + h.task[g].name + "等待 | 删除
                                        • ' + } + } + $("#task").text(h.count); + $(".task_count").text(h.count); + $(".cmdlist").html(b + d); + $(".cmd").html(c); + try{ + if($(".cmd")[0].scrollHeight) $(".cmd").scrollTop($(".cmd")[0].scrollHeight); + }catch(e){ + return; + } + },'json').error(function(){}); + } + + renderRunTask(); + speed = setInterval(function() { + renderRunTask(); + }, 2000); +} + +//检查选中项 +function RscheckSelect(){ + setTimeout(function(){ + var checkList = $("#remind").find("input"); + var count = 0; + for(var i=0;i 0){ + $(".buttongroup .btn").removeAttr("disabled"); + }else{ + $(".rs-del,.rs-read").attr("disabled","disabled"); + } + },5); +} + +function activeDisk() { + var a = $("#PathPlace").find("span").text().substring(0, 1); + switch(a) { + case "C": + $(".path-con-left dd:nth-of-type(1)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "D": + $(".path-con-left dd:nth-of-type(2)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "E": + $(".path-con-left dd:nth-of-type(3)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "F": + $(".path-con-left dd:nth-of-type(4)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "G": + $(".path-con-left dd:nth-of-type(5)").css("background", "#eee").siblings().removeAttr("style"); + break; + case "H": + $(".path-con-left dd:nth-of-type(6)").css("background", "#eee").siblings().removeAttr("style"); + break; + default: + $(".path-con-left dd").removeAttr("style"); + } +} + + +//检查登陆状态 +function check_login(){ + $.post('/check_login',{},function(rdata){ + if(!rdata.status){ + location.reload(); + } + },'json'); +} + +//登陆跳转 +function to_login(){ + layer.confirm('您的登陆状态已过期,请重新登陆!',{title:'会话已过期',icon:2,closeBtn: 1,shift: 5},function(){ + location.reload(); + }); +} +//表格头固定 +function table_fixed(name){ + var tableName = document.querySelector('#'+name); + tableName.addEventListener('scroll',scroll_handle); +} +function scroll_handle(e){ + var scrollTop = this.scrollTop; + $(this).find("thead").css({"transform":"translateY("+scrollTop+"px)","position":"relative","z-index":"1"}); +} + +function asyncLoadImage(obj, url){ + + if (typeof(url) == 'undefined'){ + return; + } + + function loadImage(obj,url,callback){ + var img = new Image(); + img.src = url; + + if(img.complete){ + callback.call(img,obj); + return; + } + img.onload = function(){ + callback.call(img,obj); + } + } + + function showImage(obj){ + obj.src = this.src; + } + loadImage(obj, url, showImage); +} + +function loadImage(){ + $('img').each(function(i){ + // console.log($(this).attr('data-src')); + if ($(this).attr('data-src') != ''){ + asyncLoadImage(this, $(this).attr('data-src')); + } + }); +} + +var socket, gterm; +function webShell() { + var termCols = 83; + var termRows = 21; + var sendTotal = 0; + if(!socket)socket = io.connect(); + var term = new Terminal({ cols: termCols, rows: termRows, screenKeys: true, useStyle: true}); + + term.open(); + term.setOption('cursorBlink', true); + term.setOption('fontSize', 14); + gterm = term; + + socket.on('server_response', function (data) { + term.write(data.data); + if (data.data == '\r\n登出\r\n' || + data.data == '登出\r\n' || + data.data == '\r\nlogout\r\n' || + data.data == 'logout\r\n') { + setTimeout(function () { + layer.closeAll(); + term.destroy(); + clearInterval(interval); + }, 500); + } + }); + + $(window).unload(function(){ +   term.destroy(); + clearInterval(interval); + }); + + if (socket) { + socket.emit('webssh', ''); + interval = setInterval(function () { + socket.emit('webssh', ''); + }, 500); + } + + term.on('data', function (data) { + socket.emit('webssh', data); + }); + + + var term_box = layer.open({ + type: 1, + title: "本地终端", + area: ['685px','463px'], + closeBtn: 1, + shadeClose: false, + content: '
                                          \ +
                                          \ + \ + \ +
                                            \ +
                                          • 此处为'+ _name + version +'主配置文件,若您不了解配置规则,请勿随意修改。
                                          • \ +
                                          '; + + + var loadT = layer.msg('配置文件路径获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + var editor; + $.post('/plugins/run', {name:_name, func:func_name,version:version},function (data) { + layer.close(loadT); + + try{ + var jdata = $.parseJSON(data.data); + if (!jdata['status']){ + layer.msg(jdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + }catch(err){/*console.log(err);*/} + + $(".soft-man-con").html(con); + + var loadT2 = layer.msg('文件内容获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + var fileName = data.data; + $.post('/files/get_body', 'path=' + fileName, function(rdata) { + layer.close(loadT2); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + $("#textBody").empty().text(rdata.data.data); + $(".CodeMirror").remove(); + + function saveDataFunc(){ + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName); + } + + editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + lineNumbers: true, + matchBrackets:true, + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + saveDataFunc(); + }, + "Cmd-S":function() { + saveDataFunc(); + } + } + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").click(function(){ + saveDataFunc(); + }); + },'json'); + },'json'); +} + + +//配置修改模版 --- start +function pluginConfigTpl(_name, version, func, config_tpl_func, read_config_tpl_func, save_callback_func){ + if ( typeof(version) == 'undefined' ){ + version = ''; + } + + var func_name = 'conf'; + if ( typeof(func) != 'undefined' ){ + func_name = func; + } + + var _config_tpl_func = 'config_tpl'; + if ( typeof(config_tpl_func) != 'undefined' ){ + _config_tpl_func = config_tpl_func; + } + + var _read_config_tpl_func = 'read_config_tpl'; + if ( typeof(read_config_tpl_func) != 'undefined' ){ + _read_config_tpl_func = read_config_tpl_func; + } + + + var con = '

                                          提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!

                                          \ + \ + \ + \ +
                                            \ +
                                          • 此处为【'+ _name + version +'】主配置文件,若您不了解配置规则,请勿随意修改。
                                          • \ +
                                          '; + $(".soft-man-con").html(con); + + function getFileName(file){ + var list = file.split('/'); + var f = list[list.length-1]; + return f + } + + var editor; + function saveDataFunc(){ + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName,save_callback_func); + } + + + var fileName = ''; + $.post('/plugins/run',{name:_name, func:_config_tpl_func,version:version}, function(data){ + var rdata = $.parseJSON(data.data); + for (var i = 0; i < rdata.length; i++) { + $('#config_tpl').append(''); + } + + $('#config_tpl').change(function(){ + var selected = $(this).val(); + if (selected != '0'){ + var loadT = layer.msg('配置模版获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + + var _args = JSON.stringify({file:selected}); + + + $.post('/plugins/run', {name:_name, func:_read_config_tpl_func,version:version,args:_args}, function(data){ + layer.close(loadT); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + + + $("#textBody").empty().text(rdata.data); + $(".CodeMirror").remove(); + editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + lineNumbers: true, + matchBrackets:true, + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + saveDataFunc(); + }, + "Cmd-S":function() { + saveDataFunc(); + } + } + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").unbind('click').click(function(){ + saveDataFunc(); + }); + },'json'); + } + }); + + },'json'); + + var loadT = layer.msg('配置文件路径获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/plugins/run', {name:_name, func:func_name,version:version}, function (data) { + layer.close(loadT); + + var loadT2 = layer.msg('文件内容获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + fileName = data.data; + $.post('/files/get_body', 'path=' + fileName, function(rdata) { + layer.close(loadT2); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + $("#textBody").empty().text(rdata.data.data); + $(".CodeMirror").remove(); + editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + saveDataFunc(); + }, + "Cmd-S":function() { + saveDataFunc(); + } + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").click(function(){ + saveDataFunc(); + }); + },'json'); + },'json'); +} + +//配置模版列表 --- start +function pluginConfigListTpl(_name, version, config_tpl_func, read_config_tpl_func){ + if ( typeof(version) == 'undefined' ){ + version = ''; + } + + var _config_tpl_func = 'config_tpl'; + if ( typeof(config_tpl_func) != 'undefined' ){ + _config_tpl_func = config_tpl_func; + } + + var _read_config_tpl_func = 'read_config_tpl'; + if ( typeof(read_config_tpl_func) != 'undefined' ){ + _read_config_tpl_func = read_config_tpl_func; + } + + + var con = '

                                          提示:Ctrl+F 搜索关键字,Ctrl+G 查找下一个,Ctrl+S 保存,Ctrl+Shift+R 查找替换!

                                          \ + \ + \ + \ +
                                            \ +
                                          • 此处为'+ _name + version +'主配置文件,若您不了解配置规则,请勿随意修改。
                                          • \ +
                                          '; + $(".soft-man-con").html(con); + + function getFileName(file){ + var list = file.split('/'); + var f = list[list.length-1]; + return f + } + + var editor; + function saveDataFunc(){ + $("#textBody").text(editor.getValue()); + pluginConfigSave(fileName); + } + + function loadTextBody(fileName){ + $.post('/files/get_body', 'path=' + fileName, function(rdata) { + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + $("#textBody").empty().text(rdata.data.data); + $(".CodeMirror").remove(); + editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + saveDataFunc(); + }, + "Cmd-S": function() { + saveDataFunc(); + } + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").click(function(){ + saveDataFunc(); + }); + },'json'); + } + + + + var fileName = ''; + $.post('/plugins/run',{name:_name, func:_config_tpl_func,version:version}, function(data){ + var rdata = $.parseJSON(data.data); + for (var i = 0; i < rdata.length; i++) { + $('#config_tpl').append(''); + } + + if (rdata.length>0){ + loadTextBody(rdata[0]); + } + + $('#config_tpl').change(function(){ + var selected = $(this).val(); + fileName = selected; + + var loadT = layer.msg('配置模版获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + + var _args = JSON.stringify({file:selected}); + $.post('/plugins/run', {name:_name, func:_read_config_tpl_func,version:version,args:_args}, function(data){ + layer.close(loadT); + var rdata = $.parseJSON(data.data); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + $("#textBody").empty().text(rdata.data); + $(".CodeMirror").remove(); + editor = CodeMirror.fromTextArea(document.getElementById("textBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + saveDataFunc(); + }, + "Cmd-S":function() { + saveDataFunc(); + } + }, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").unbind('click').click(function(){ + saveDataFunc(); + }); + },'json'); + + }); + + },'json'); +} + + +//配置保存 +function pluginConfigSave(fileName, callback) { + var data = encodeURIComponent($("#textBody").val()); + var encoding = 'utf-8'; + var loadT = layer.msg('保存中...', {icon: 16,time: 0}); + $.post('/files/save_body', 'data=' + data + '&path=' + fileName + '&encoding=' + encoding, function(rdata) { + layer.close(loadT); + + showMsg(rdata.msg, function(){ + if ( rdata.status && typeof(callback) == 'function'){ + callback(); + } + },{icon: rdata.status ? 1 : 2}); + + },'json'); +} + +function pluginInitD(_name, _version, _suffix_name=''){ + if (typeof _version == 'undefined'){ + _version = ''; + } + + var default_name = 'initd_status'; + if ( _suffix_name != '' ){ + default_name = 'initd_status_'+_suffix_name; + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:_name, func:default_name, version : _version}, function(data) { + layer.close(loadT); + if( !data.status ){ + layer.msg(data.msg,{icon:0,time:3000,shade: [0.3, '#000']}); + return; + } + if( data.data!='ok' && data.data!='fail' ){ + layer.msg(data.data,{icon:0,time:3000,shade: [0.3, '#000']}); + return; + } + if (data.data == 'ok'){ + pluginSetInitD(_name, _version, true, _suffix_name); + } else { + pluginSetInitD(_name, _version, false, _suffix_name); + } + },'json'); +} + +function pluginSetInitD(_name, _version, status,_suffix_name=''){ + + var default_name = (status?'initd_uninstall':'initd_install'); + if ( _suffix_name != '' ){ + default_name = default_name + '_' + _suffix_name; + } + + var serviceCon ='

                                          当前状态:'+(status ? '已加载' : '未加载' )+ + '

                                          \ + \ +
                                          '; + $(".soft-man-con").html(serviceCon); +} + +function pluginOpInitD(a, _version, b, _suffix_name='') { + var c = "name=" + a + "&func=" + b + "&version="+_version; + var d = ""; + b = b.split('_'+_suffix_name)[0]; + switch(b) { + case "initd_install":d = '加载';break; + case "initd_uninstall":d = '卸载';break; + } + + _ver = _version; + if(_version != ''){ + _ver = '【' + _version + '】'; + } + + layer.confirm( msgTpl('您真的要{1}{2}{3}服务吗?', [d,a,_ver]), {icon:3,closeBtn: 1}, function() { + var e = layer.msg(msgTpl('正在{1}{2}{3}服务,请稍候...',[d,a,_ver]), {icon: 16,time: 0}); + $.post("/plugins/run", c, function(g) { + layer.close(e); + var f = g.data == 'ok' ? msgTpl('{1}{3}服务已{2}',[a,d,_ver]) : msgTpl('{1}{3}服务{2}失败!',[a,d,_ver]); + layer.msg(f, {icon: g.data == 'ok' ? 1 : 2}); + + if ( b == 'initd_install' && g.data == 'ok' ) { + pluginSetInitD(a, _version, true); + }else{ + pluginSetInitD(a, _version, false); + } + if(g.data != 'ok') { + layer.msg(g.data, {icon: 2,time: 0,shade: 0.3,shadeClose: true}); + } + },'json').error(function() { + layer.close(e); + layer.msg('系统异常!', {icon: 0}); + }); + }) +} + +function pluginLogs(_name, version, func, line){ + + var _this = this; + if ( typeof(version) == 'undefined' ){ + version = ''; + } + + var func_name = 'error_log'; + if ( typeof(func) != 'undefined' ){ + func_name = func; + } + + var file_line = 100; + if ( typeof(line) != 'undefined' ){ + file_line = line; + } + + + var loadT = layer.msg('日志路径获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/plugins/run', {name:_name, func:func_name, version:version},function (data) { + layer.close(loadT); + + try{ + var jdata = $.parseJSON(data.data); + if (!jdata['status']){ + layer.msg(jdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + }catch(err){/*console.log(err);*/} + + + var loadT2 = layer.msg('文件内容获取中...',{icon:16,time:0,shade: [0.3, '#000']}); + var fileName = data.data; + $.post('/files/get_last_body', 'path=' + fileName+'&line='+file_line, function(rdata) { + layer.close(loadT2); + if (!rdata.status){ + layer.msg(rdata.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(rdata.data == '') { + rdata.data = '当前没有日志!'; + } + + + var h = parseInt($('.bt-w-menu').css('height')) - 40; + var ebody = ''; + $(".soft-man-con").html(ebody); + var ob = document.getElementById('info_log'); + ob.scrollTop = ob.scrollHeight; + },'json'); + },'json'); +} + + +function pluginRollingLogs(_name, version, func, _args, line){ + if ( typeof(version) == 'undefined' ){ + version = ''; + } + + var func_name = 'error_log'; + if ( typeof(func) != 'undefined' ){ + func_name = func; + } + + var file_line = 100; + if ( typeof(line) != 'undefined' ){ + file_line = line; + } + + var reqTimer = null; + + function requestLogs(fileName){ + $.post('/files/get_last_body', 'path=' + fileName+'&line='+file_line, function(rdata) { + if (!rdata.status){ + return; + } + + if(rdata.data == '') { + rdata.data = '当前没有日志!'; + } + var ebody = ''; + $("#plugins_rolling_logs").html(ebody); + var ob = document.getElementById('roll_info_log'); + ob.scrollTop = ob.scrollHeight; + },'json'); + } + + + layer.open({ + type: 1, + title: _name + '日志', + area: '640px', + end: function(){ + if (reqTimer){ + clearInterval(reqTimer); + } + }, + content:'
                                          \ + \ +
                                          ', + success:function(){ + $.post('/plugins/run', {name:_name, func:func_name, version:version, args:_args},function (data) { + var fileName = data.data; + requestLogs(fileName); + reqTimer = setInterval(function(){ + requestLogs(fileName); + },1000); + },'json'); + } + }); +} + + +function pluginStandAloneLogs(_name, version, func, _args, line){ + if ( typeof(version) == 'undefined' ){ + version = ''; + } + + var func_name = 'error_log'; + if ( typeof(func) != 'undefined' ){ + func_name = func; + } + + var file_line = 100; + if ( typeof(line) != 'undefined' ){ + file_line = line; + } + + + layer.open({ + type: 1, + title: _name + '日志', + area: '640px', + content:'
                                          \ + \ +
                                          ' + }); + + $.post('/plugins/run', {name:_name, func:func_name, version:version,args:_args},function (data) { + var fileName = data.data; + $.post('/files/get_last_body', 'path=' + fileName+'&line='+file_line, function(rdata) { + if (!rdata.status){ + return; + } + + if(rdata.data == '') { + rdata.data = '当前没有日志!'; + } + var ebody = ''; + $("#plugins_stand_alone_logs").html(ebody); + var ob = document.getElementById('plugins_stand_alone_logs'); + ob.scrollTop = ob.scrollHeight; + },'json'); + },'json'); +} +/*** 其中功能,针对插件通过库使用 end ***/ + +$(function() { + setInterval(function(){check_login();},6000); + autoHeight(); +}); +$(window).resize(function() { + autoHeight(); +}); \ No newline at end of file diff --git a/web/static/app/site.js b/web/static/app/site.js new file mode 100755 index 000000000..b7cc338e9 --- /dev/null +++ b/web/static/app/site.js @@ -0,0 +1,3290 @@ + + +$("#site_search_input").keyup(function(event){ + if(event.keyCode == 13) { + getWeb(1, -1, $(this).val()); + } +}); + +$('#site_search').click(function(){ + getWeb(1, -1, $('#site_search_input').val()); +}); + +//设置到期日期 +function getDate(a) { + var dd = new Date(); + dd.setTime(dd.getTime() + (a == undefined || isNaN(parseInt(a)) ? 0 : parseInt(a)) * 86400000); + var y = dd.getFullYear(); + var m = dd.getMonth() + 1; + var d = dd.getDate(); + return y + "-" + (m < 10 ? ('0' + m) : m) + "-" + (d < 10 ? ('0' + d) : d); +} + +/** + * 取回网站数据列表 + * @param {Number} page 当前页 + * @param {String} search 搜索条件 + */ + function getWeb(page, type_id, search) { + if ( typeof(search) == 'undefined' ){ + search = $('#site_search_input').val(); + } + + var page = page == undefined ? '1':page; + var order = getCookie('order'); + if(order){ + order = '&order=' + order; + } else { + order = ''; + } + + var type = ''; + if ( typeof(type_id) == 'undefined' ){ + type = '&type_id=-1'; + } else { + type = '&type_id='+type_id; + } + + var pdata = 'limit=10&p=' + page + '&search=' + search + order + type; + var loadT = layer.load(); + //取回数据 + $.post('/site/list', pdata, function(data) { + layer.close(loadT); + //构造数据列表 + var body = ''; + $("#webBody").html(body); + var list = data.data; + for (var i = 0; i < list.length; i++) { + //当前站点状态 + if (list[i].status == '正在运行' || list[i].status == '1') { + var status = "运行中"; + } else { + var status = "已停止"; + } + + //是否有备份 + if (list[i].backup_count > 0) { + var backup = "有备份"; + } else { + var backup = "无备份"; + } + //是否设置有效期 + var web_end_time = (list[i].edate == "0000-00-00") ? '永久': list[i].edate; + //表格主体 + var shortwebname = list[i].name; + var shortpath = list[i].path; + if(list[i].name.length > 30) { + shortwebname = list[i].name.substring(0, 30) + "..."; + } + if(list[i].path.length > 30){ + shortpath = list[i].path.substring(0, 30) + "..."; + } + var idname = list[i].name.replace(/\./g,'_'); + + body = "\ + " + shortwebname + "\ + " + status + "\ + " + backup + "\ + " + shortpath + "\ + " + web_end_time + "\ + " + list[i].ps + "\ + \ + 设置\ + | 删除\ + " + + $("#webBody").append(body); + $('#webBody').on('click','#site_'+ list[i].id,function(){ + var _this = $(this); + var id = $(this).attr('data-ids'); + laydate.render({ + elem: '#site_'+ id, + min:getDate(-1), + max:'9999-12-31', + vlue:getDate(365), + type:'date', + format :'yyyy-MM-dd', + trigger:'click', + btns:['perpetual', 'confirm'], + theme:'#20a53a', + done:function(dates){ + if(_this.html() == '永久'){ + dates = '0000-00-00'; + } + var loadT = layer.msg('正在保存...', { icon: 16, time: 0, shade: [0.3, "#000"]}); + $.post('/site/set_end_date','id='+id+'&edate='+dates,function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:5}); + },'json'); + } + }); + this.click(); + }); + } + if(body.length < 10){ + body = "当前没有站点数据"; + $("#webBody").html(body); + } + //输出数据列表 + $(".btn-more").hover(function(){ + $(this).addClass("open"); + },function(){ + $(this).removeClass("open"); + }); + + //输出分页 + $("#webPage").html(data.page); + + $(".btlinkbed").click(function(){ + var dataid = $(this).attr("data-id"); + var databak = $(this).text(); + if(databak == null){ + databak = ''; + } + $(this).hide().after(""); + $(".baktext").focus(); + }); + + readerTableChecked(); + },'json'); +} + + +function getBakPost(b) { + $(".baktext").hide().prev().show(); + var id = $(".baktext").attr("data-id"); + var page = $(".baktext").attr("data-page"); + var a = $(".baktext").val(); + if(a == "") { + a = '空'; + } + setWebPs(b, id, a,page); + $("a[data-id='" + id + "']").html(a); + $(".baktext").remove(); +} + +function setWebPs(b, id, ps,page) { + var d = layer.load({shade: true,shadeClose: false}); + var ps = 'ps=' + ps; + $.post('/site/set_ps', 'id=' + id + "&" + ps, function(data) { + if(data['status']) { + getWeb(page); + layer.closeAll(); + layer.msg('修改成功!', {icon: 1}); + } else { + layer.closeAll(); + layer.msg('修改失败!', {icon: 2}); + } + },'json'); +} + +//创建站点前,检查服务是否开启 +function webAdd(type){ + loading = layer.msg('正在检查是否开启OpenResty服务!',{icon:16,time:0,shade: [0.3, "#000"]}) + $.post('/site/check_web_status', function(data){ + layer.close(loading); + if (data.status){ + webAddPage(type) + } else { + layer.msg(data.msg,{icon:0,time:3000,shade: [0.3, "#000"]}) + } + },'json'); +} + +//添加站点 +function webAddPage(type) { + + if (type == 1) { + var array; + var str=""; + var domainlist=''; + var domain = array = $("#mainDomain").val().replace('http://','').replace('https://','').split("\n"); + var webport=[]; + var checkDomain = domain[0].split('.'); + if(checkDomain.length < 1){ + layer.msg(lan.site.domain_err_txt,{icon:2}); + return; + } + for(var i=1; i
                                          "; + layer.open({ + type: 1, + skin: 'demo-class', + area: '640px', + title: '添加网站', + closeBtn: 1, + shift: 0, + shadeClose: false, + content: "\ +
                                          \ + "+lan.site.domain+"\ +
                                          \ + \ +
                                          \ +
                                          \ +
                                          \ + 备注\ +
                                          \ + \ +
                                          \ +
                                          \ +
                                          \ + 根目录\ +
                                          \ + \ + \ +
                                          \ +
                                          \ + "+php_version+"\ +
                                          \ + \ + \ +
                                          \ + ", + }); + + $(function() { + var placeholder = "
                                          "+lan.site.domain_help+"
                                          "; + $('#mainDomain').after(placeholder); + $(".placeholder").click(function(){ + $(this).hide(); + $('#mainDomain').focus(); + }) + $('#mainDomain').focus(function() { + $(".placeholder").hide(); + }); + + $('#mainDomain').blur(function() { + if($(this).val().length==0){ + $(".placeholder").show(); + } + }); + + //验证PHP版本 + $("select[name='version']").change(function(){ + if($(this).val() == '52'){ + var msgerr = 'PHP5.2在您的站点有漏洞时有跨站风险,请尽量使用PHP5.3以上版本!'; + $('#php_w').text(msgerr); + }else{ + $('#php_w').text(''); + } + }) + + $('#mainDomain').on('input', function() { + var array; + var res,ress; + var str = $(this).val().replace('http://','').replace('https://',''); + var len = str.replace(/[^\x00-\xff]/g, "**").length; + array = str.split("\n"); + ress =array[0].split(":")[0]; + res = ress.replace(new RegExp(/([-.])/g), '_'); + if(res.length > 15){ + res = res.substr(0,15); + } + + var placeholder = $("#inputPath").attr('placeholder'); + $("#inputPath").val(placeholder+'/'+ress); + + if(res.length > 15){ + res = res.substr(0,15); + } + + $("#Wbeizhu").val(ress); + }) + + //备注 + $('#Wbeizhu').on('input', function() { + var str = $(this).val(); + var len = str.replace(/[^\x00-\xff]/g, "**").length; + if (len > 20) { + str = str.substring(0, 20); + $(this).val(str); + layer.msg('不能超出20个字符!', {icon: 0}); + } + }) + //获取当前时间时间戳,截取后6位 + var timestamp = new Date().getTime().toString(); + var dtpw = timestamp.substring(7); + }); + }, 'json'); +} + +//修改网站目录 +function webPathEdit(id){ + $.post('/site/get_dir_user_ini','id='+id, function(data){ + var data = data['data']; + var site_path = data['path']; + var site_name = data['name']; + var run_path = data['run_path']['run_path']; + var user_ini_checked = data.user_ini?'checked':''; + var logs_checked = data.logs?'checked':''; + var opt = ''; + var selected = ''; + for(var i=0;i'+ data.run_path.dirs[i] +''; + } + var content = "
                                          \ +
                                          \ + \ + \ +
                                          \ +
                                          \ + 网站目录\ + \ + \ + \ +
                                          \ +
                                          \ + 运行目录\ + \ + \ +
                                          \ +
                                            \ +
                                          • 部分程序需要指定二级目录作为运行目录,如ThinkPHP5,Laravel
                                          • \ +
                                          • 选择您的运行目录,点保存即可
                                          • \ +
                                          " + +'
                                          ' + +'密码访问' + +'' + +'' + +'' + +'
                                          ' + +'
                                          ' + +'

                                          授权账号

                                          ' + +'

                                          访问密码

                                          ' + +'

                                          重复密码

                                          ' + +'

                                          ' + +'
                                          ' + +'
                                          '; + + $("#webedit-con").html(content); + $("#userini").change(function(){ + $.post('/site/set_dir_user_ini',{'path':site_path,'run_path':run_path,},function(userini){ + layer.msg(data.msg+'

                                          注意:设置防跨站需要重启PHP才能生效!

                                          ',{icon:data.status?1:2}); + tryRestartPHP(site_name); + },'json'); + }); + + $("#logs").change(function(){ + var loadT = layer.msg("正在设置中...",{icon:16,time:10000,shade: [0.3, '#000']}); + $.post('/site/logs_open','id='+id, function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + }); + + },'json'); +} + +//是否设置访问密码 +function pathSafe(id){ + var isPass = $('#pathSafe').prop('checked'); + if(!isPass){ + $(".user_pw").show(); + } else { + var loadT = layer.msg(lan.public.the,{icon:16,time:10000,shade: [0.3, '#000']}); + $.post('/site/close_has_pwd',{id:id},function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + $(".user_pw").hide(); + },'json'); + } +} + +//设置访问密码 +function setPathSafe(id){ + var username = $("#username_get").val(); + var pass1 = $("#password_get_1").val(); + var pass2 = $("#password_get_2").val(); + if(pass1 != pass2){ + layer.msg('两次输入的密码不一致!',{icon:2}); + return; + } + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:10000,shade: [0.3, '#000']}); + $.post('/site/set_has_pwd',{id:id,username:username,password:pass1},function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); +} + +//提交运行目录 +function setSiteRunPath(id){ + var NewPath = $("#runPath").val(); + var loadT = layer.msg(lan.public.the,{icon:16,time:10000,shade: [0.3, '#000']}); + $.post('/site/set_site_run_path','id='+id+'&run_path='+NewPath,function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); +} + +//提交网站目录 +function setSitePath(id){ + var NewPath = $("#inputPath").val(); + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:10000,shade: [0.3, '#000']}); + $.post('/site/set_path','id='+id+'&path='+NewPath,function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); +} + +//修改网站备注 +function webBakEdit(id){ + $.post("/data?action=getKey','table=sites&key=ps&id="+id,function(rdata){ + var webBakHtml = "
                                          \ +
                                          \ + \ +
                                          \ + \ +

                                          \ +
                                          \ +
                                          "; + $("#webedit-con").html(webBakHtml); + }); +} + + +//设置默认文档 +function setIndexEdit(id){ + $.post('/site/get_index','id='+id,function(data){ + var rdata = data['index']; + rdata = rdata.replace(new RegExp(/(,)/g), "\n"); + var setIndexHtml = "
                                          \ +
                                          \ + \ + \ +
                                          \ +
                                            \ +
                                          • 默认文档,每行一个,优先级由上至下。
                                          • \ +
                                          \ +
                                          "; + $("#webedit-con").html(setIndexHtml); + },'json'); +} + +/** + * 停止一个站点 + * @param {Int} wid 网站ID + * @param {String} wname 网站名称 + */ +function webStop(wid, wname) { + layer.confirm('站点停用后将无法访问,您真的要停用这个站点吗?', {icon:3,closeBtn:2},function(index) { + if (index > 0) { + var loadT = layer.load(); + $.post("/site/stop","id=" + wid + "&name=" + wname, function(ret) { + layer.msg(ret.msg,{icon:ret.status?1:2}) + layer.close(loadT); + getWeb(1); + },'json'); + } + }); +} + +/** + * 启动一个网站 + * @param {Number} wid 网站ID + * @param {String} wname 网站名称 + */ +function webStart(wid, wname) { + layer.confirm('即将启动站点,您真的要启用这个站点吗?',{icon:3,closeBtn:2}, function(index) { + if (index > 0) { + var loadT = layer.load() + $.post("/site/start","id=" + wid + "&name=" + wname, function(ret) { + layer.msg(ret.msg,{icon:ret.status?1:2}) + layer.close(loadT); + getWeb(1); + },'json'); + } + }); +} + +/** + * 删除一个网站 + * @param {Number} wid 网站ID + * @param {String} wname 网站名称 + */ +function webDelete(wid, wname){ + var thtml = "
                                          \ + \ +
                                          "; + var info = '是否要删除同名根目录'; + safeMessage('删除站点'+"["+wname+"]",info, function(){ + var path=''; + if($("#delpath").is(":checked")){ + path='&path=1'; + } + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:10000,shade: [0.3, '#000']}); + $.post("/site/delete","id=" + wid + "&webname=" + wname + path, function(ret){ + layer.closeAll(); + layer.msg(ret.msg,{icon:ret.status?1:2}) + getWeb(1); + },'json'); + },thtml); +} + + +//批量删除 +function allDeleteSite(){ + var checkList = $("input[name=id]"); + var dataList = new Array(); + for(var i=0;i"+lan.site.all_del_info+"\ +
                                          "; + safeMessage(lan.site.all_del_site,""+lan.get('del_all_site',[dataList.length])+"",function(){ + layer.closeAll(); + var path = ''; + if($("#delpath").is(":checked")){ + path='&path=1'; + } + syncDeleteSite(dataList, 0,'',path); + },thtml); +} + +//模拟同步开始批量删除 +function syncDeleteSite(dataList,successCount,errorMsg,path){ + if(dataList.length < 1) { + showMsg(lan.get('del_all_site_ok',[successCount]), function(){ + // location.reload(); + },{icon:1}); + return; + } + var loadT = layer.msg(lan.get('del_all_task_the',[dataList[0].name]),{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/delete', 'id='+dataList[0].id+'&webname='+dataList[0].name+path , function(rdata){ + layer.close(loadT); + if(rdata.status){ + successCount++; + $("input[title='"+dataList[0].name+"']").parents("tr").remove(); + } else { + if(!errorMsg){ + errorMsg = '

                                          '+lan.site.del_err+':

                                          '; + } + errorMsg += '
                                        • '+dataList[0].name+' -> '+rdata.msg+'
                                        • ' + } + dataList.splice(0,1); + syncDeleteSite(dataList,successCount,errorMsg,path); + },'json'); +} + + +/** + * 域名管理 + * @param {Int} id 网站ID + */ +function domainEdit(id, name, msg, status) { + $.post('/site/get_domain' ,{pid:id}, function(data) { + var domain = data.data; + + var echoHtml = ""; + for (var i = 0; i < domain.length; i++) { + echoHtml += "\ + " + domain[i].name + "\ + " + domain[i].port + "\ + \ + "; + } + var bodyHtml = "\ + \ + \ +
                                          \ + \ + \ + " + echoHtml + "\ +
                                          "+lan.site.domain+"端口操作
                                          \ +
                                          "; + $("#webedit-con").html(bodyHtml); + if(msg != undefined){ + layer.msg(msg,{icon:status?1:5}); + } + var placeholder = "
                                          每行填写一个域名,默认为80端口
                                          泛解析添加方法 *.domain.com
                                          如另加端口格式为 www.domain.com:88
                                          "; + $('#newdomain').after(placeholder); + $(".placeholder").click(function(){ + $(this).hide(); + $('#newdomain').focus(); + }) + $('#newdomain').focus(function() { + $(".placeholder").hide(); + }); + + $('#newdomain').blur(function() { + if($(this).val().length==0){ + $(".placeholder").show(); + } + }); + $("#newdomain").on("input",function(){ + var str = $(this).val(); + if(isChineseChar(str)) { + $('.btn-zhm').show(); + } else{ + $('.btn-zhm').hide(); + } + }) + //checkDomain(); + },'json'); +} + +//编辑域名/端口 +function cancelSend(){ + $(".changeDomain,.changePort").hide().prev().show(); + $(".changeDomain,.changePort").remove(); +} +//遍历域名 +function checkDomain() { + $("#checkDomain tr").each(function() { + var $this = $(this); + var domain = $(this).find("td:first-child").text(); + $(this).find("td:first-child").append(""); + }); +} + +/** + * 添加域名 + * @param {Int} id 网站ID + * @param {String} webname 主域名 + */ +function domainAdd(id, webname, type) { + var Domain = $("#newdomain").val().split("\n"); + + var domainlist = ''; + for(var i=0; i下载 | "; + body += ""+frdata.data[i].name+"\ + " + (toSize(frdata.data[i].size)) + "\ + " + frdata.data[i].add_time + "\ + "+ ftpdown + "删除\ + " + } + + var ftpdown = ''; + frdata.page = frdata.page.replace(/'/g,'"').replace(/getBackup\(/g,"getBackup(" + id + ",0,"); + + if(name == 0){ + var sBody = "\ + \ + "+body+"\ +
                                          文件名称文件大小打包时间操作
                                          " + $("#webBackupList").html(sBody); + $(".page").html(frdata.page); + return; + } + layer.closeAll(); + layer.open({ + type: 1, + skin: 'demo-class', + area: '700px', + title: '打包备份', + closeBtn: 1, + shift: 0, + shadeClose: false, + content: "
                                          \ + \ +
                                          \ + \ + \ + \ + \ + " + body + "\ +
                                          文件名称文件大小打包时间操作
                                          \ +
                                          " + frdata.page + "
                                          \ +
                                          \ +
                                          " + }); + },'json'); +} + +function goSet(num) { + //取选中对象 + var el = document.getElementsByTagName('input'); + var len = el.length; + var data = ''; + var a = ''; + var count = 0; + //构造POST数据 + for (var i = 0; i < len; i++) { + if (el[i].checked == true && el[i].value != 'on') { + data += a + count + '=' + el[i].value; + a = '&'; + count++; + } + } + //判断操作类别 + if(num==1){ + reAdd(data); + } + else if(num==2){ + shift(data); + } +} + + +//设置默认文档 +function setIndex(id){ + var quanju = (id==undefined)?lan.site.public_set:lan.site.local_site; + var data=id==undefined?"":"id="+id; + $.post('/site?action=GetIndex',data,function(rdata){ + rdata= rdata.replace(new RegExp(/(,)/g), "\n"); + layer.open({ + type: 1, + area: '500px', + title: lan.site.setindex, + closeBtn: 1, + shift: 5, + shadeClose: true, + content:"
                                          " + +"
                                          " + +" "+lan.site.default_doc+"" + +"
                                          " + +" " + +"

                                          "+quanju+lan.site.default_doc_help+"

                                          " + +"
                                          " + +"
                                          " + +"
                                          " + +" " + +" " + +"
                                          " + +"
                                          " + }); + }); +} + +//设置默认站点 +function setDefaultSite(){ + var name = $("#default_site").val(); + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_default_site','name='+name,function(rdata){ + layer.closeAll(); + layer.msg(rdata.msg,{icon:rdata.status?1:5}); + },'json'); +} + + +//默认站点 +function getDefaultSite(){ + $.post('/site/get_default_site','',function(rdata){ + var opt = ''; + var selected = ''; + for(var i=0;i' + rdata.sites[i].name + ''; + } + + layer.open({ + type: 1, + area: '530px', + title: '设置默认站点', + closeBtn: 1, + shift: 5, + shadeClose: true, + content:'
                                          \ +

                                          \ + 默认站点\ + \ +

                                          \ +
                                            \ +
                                          • 设置默认站点后,所有未绑定的域名和IP都被定向到默认站点
                                          • \ +
                                          • 可有效防止恶意解析
                                          • \ +
                                          \ +
                                          \ + \ + \ +
                                          \ +
                                          ' + }); + },'json'); +} + +function setPHPVer(){ + $.post('/site/get_cli_php_version','',function(rdata){ + if(typeof(rdata['status'])!='undefined'){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + return; + } + + var opt = ''; + var selected = ''; + for(var i=0;i-1){ + continue; + } + + if (rdata.versions[i].version.indexOf("apt")>-1){ + continue; + } + + opt += ''; + } + + var phpver_layer = layer.open({ + type: 1, + area: '530px', + title: '设置PHP-CLI(命令行)版本', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["确定","取消"], + content:'
                                          \ +

                                          \ + PHP-CLI版本\ + \ +

                                          \ +
                                            \ +
                                          • 此处可设置命令行运行php时使用的PHP版本
                                          • \ +
                                          • 安装新的PHP版本后此处需要重新设置
                                          • \ +
                                          \ +
                                          ', + yes:function(layero,index){ + var version = $("#default_ver").val(); + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_cli_php_version','version='+version,function(rdata){ + layer.close(loadT); + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.close(phpver_layer); + } + },{icon:rdata.status?1:5},2000); + },'json'); + }, + }); + },'json'); +} + +function setIndexList(id){ + var Dindex = $("#Dindex").val().replace(new RegExp(/(\n)/g), ","); + if(id == undefined ){ + var data="id=&index="+Dindex; + } else{ + var data="id="+id+"&index="+Dindex; + } + var loadT= layer.load(2); + $.post('/site/set_index',data,function(rdata){ + layer.close(loadT); + var ico = rdata.status? 1:5; + layer.msg(rdata.msg,{icon:ico}); + },'json'); +} + + +/* 站点修改 */ +function webEdit(id,website,endTime,addtime){ + // 暂时关闭 - 子目录绑定 + //

                                          子目录绑定

                                          \ + layer.open({ + type: 1, + area: ['700px','603px'], + title: '站点修改['+website+'] -- 添加时间['+addtime+']', + closeBtn: 1, + shift: 0, + content: "
                                          \ +
                                          \ +

                                          域名管理

                                          \ +

                                          子目录绑定

                                          \ +

                                          网站目录

                                          \ +

                                          流量限制

                                          \ +

                                          伪静态

                                          \ +

                                          默认文档

                                          \ +

                                          配置文件

                                          \ +

                                          SSL

                                          \ +

                                          PHP版本

                                          \ +

                                          重定向

                                          \ +

                                          反向代理

                                          \ +

                                          防盗链

                                          \ +

                                          响应日志

                                          \ +

                                          错误日志

                                          \ +
                                          \ +
                                          \ +
                                          ", + success:function(){ + //域名输入提示 + var placeholder = "
                                          每行填写一个域名,默认为80端口
                                          泛解析添加方法 *.domain.com
                                          如另加端口格式为 www.domain.com:88
                                          "; + $('#newdomain').after(placeholder); + $(".placeholder").click(function(){ + $(this).hide(); + $('#newdomain').focus(); + }); + + $('#newdomain').focus(function() { + $(".placeholder").hide(); + }); + + $('#newdomain').blur(function() { + if($(this).val().length == 0){ + $(".placeholder").show(); + } + }); + + //切换 + $(".bt-w-menu p").click(function(){ + $(this).addClass("bgw").siblings().removeClass("bgw"); + }); + + domainEdit(id,website); + } + }); +} + +//取网站日志pluginLogs +function getSiteLogs(siteName){ + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/get_logs',{siteName:siteName},function(logs){ + layer.close(loadT); + if(logs.status !== true){ + logs.msg = ''; + } + if (logs.msg == '') logs.msg = '当前没有日志.'; + var h = parseInt($('.bt-w-menu').css('height')) - 35; + var con = ''; + $("#webedit-con").html(con); + var ob = document.getElementById('site_log'); + ob.scrollTop = ob.scrollHeight; + },'json'); +} + +//取网站错误日志 +function getSiteErrorLogs(siteName){ + var loadT = layer.msg('正在处理,请稍候...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/get_error_logs',{siteName:siteName},function(logs){ + layer.close(loadT); + if(logs.status !== true){ + logs.msg = ''; + } + if (logs.msg == '') logs.msg = '当前没有日志.'; + var h = parseInt($('.bt-w-menu').css('height')) - 35; + var con = ''; + $("#webedit-con").html(con); + var ob = document.getElementById('error_log'); + ob.scrollTop = ob.scrollHeight; + },'json'); +} + + +//防盗链 +function security(id,name){ + var loadT = layer.msg('正在提交任务...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/get_security',{id:id,name:name},function(rdata){ + layer.close(loadT); + var mbody = '
                                          ' + +'

                                          URL后缀

                                          ' + +'

                                          许可域名

                                          ' + +'
                                          ' + +'
                                          ' + +'
                                            ' + +'
                                          • 默认允许资源被直接访问,即不限制HTTP_REFERER为空的请求
                                          • ' + +'
                                          • 多个URL后缀与域名请使用逗号(,)隔开,如: png,jpeg,zip,js
                                          • ' + +'
                                          • 当触发防盗链时,将直接返回404状态
                                          • ' + +'
                                          ' + +'
                                          ' + $("#webedit-con").html(mbody); + },'json'); +} + +//设置防盗链 +function setSecurity(name,id, none){ + setTimeout(function(){ + var data = { + fix:$("input[name='sec_fix']").val(), + domains:$("input[name='sec_domains']").val(), + status:$("input[name='sec_status']").prop("checked"), + none:$("input[name='sec_none_status']").prop("checked"), + name:name, + id:id + } + var loadT = layer.msg(lan.site.the_msg,{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_security',data,function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + if(rdata.status) setTimeout(function(){security(id,name);},1000); + },'json'); + },100); +} + + +//流量限制 +function limitNet(id){ + $.post('/site/get_limit_net','id='+id, function(rdata){ + var status_selected = rdata.perserver != 0?'checked':''; + if(rdata.perserver == 0){ + rdata.perserver = 300; + rdata.perip = 25; + rdata.limit_rate = 512; + } + var limitList = "" + +"" + +"" + +"" + +"" + +"" + +"" + var body = "
                                          " + +'

                                          ' + +"

                                          "+lan.site.limit_net_9+":

                                          " + +"

                                          "+lan.site.limit_net_10+":

                                          " + +"

                                          "+lan.site.limit_net_12+":

                                          " + +"

                                          "+lan.site.limit_net_14+":

                                          " + +"" + +"
                                          " + +"
                                          • "+lan.site.limit_net_11+"
                                          • "+lan.site.limit_net_13+"
                                          • "+lan.site.limit_net_15+"
                                          " + $("#webedit-con").html(body); + + $("select[name='limit']").change(function(){ + var type = $(this).val(); + perserver = 300; + perip = 25; + limit_rate = 512; + switch(type){ + case '1': + perserver = 300; + perip = 25; + limit_rate = 512; + break; + case '2': + perserver = 200; + perip = 10; + limit_rate = 1024; + break; + case '3': + perserver = 50; + perip = 3; + limit_rate = 2048; + break; + case '4': + perserver = 500; + perip = 10; + limit_rate = 2048; + break; + case '5': + perserver = 400; + perip = 15; + limit_rate = 1024; + break; + case '6': + perserver = 60; + perip = 10; + limit_rate = 512; + break; + case '7': + perserver = 150; + perip = 4; + limit_rate = 1024; + break; + } + + $("input[name='perserver']").val(perserver); + $("input[name='perip']").val(perip); + $("input[name='limit_rate']").val(limit_rate); + }); + },'json'); +} + + +//保存流量限制配置 +function saveLimitNet(id, type){ + var isChecked = $("input[name='status']").attr('checked'); + if(isChecked == undefined || type == 1 ){ + var data = 'id='+id+'&perserver='+$("input[name='perserver']").val()+'&perip='+$("input[name='perip']").val()+'&limit_rate='+$("input[name='limit_rate']").val(); + var loadT = layer.msg(lan.public.config,{icon:16,time:10000}) + $.post('/site/set_limit_net',data,function(rdata){ + layer.close(loadT); + limitNet(id); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + }else{ + var loadT = layer.msg(lan.public.config,{icon:16,time:10000}) + $.post('/site/close_limit_net',{id:id},function(rdata){ + layer.close(loadT); + limitNet(id); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + } +} + + +//子目录绑定 +function dirBinding(id){ + $.post('/site/get_dir_binding',{'id':id},function(data){ + var rdata = data['data']; + var echoHtml = ''; + for(var i=0;i"+rdata.binding[i].port+""+rdata.binding[i].path+"伪静态 | 删除"; + } + + var dirList = ''; + for(var n=0;n"+rdata.dirs[n]+""; + } + + var body = "
                                          " + + "域名:" + + "子目录:" + + "" + + "
                                          " + + "
                                          " + + "" + + "" + echoHtml + "" + + "
                                          域名端口子目录操作
                                          "; + + $("#webedit-con").html(body); + },'json'); +} + +//子目录伪静态 +function setDirRewrite(id){ + $.post('/site/get_dir_bind_rewrite','id='+id,function(rdata){ + if(!rdata.status){ + var confirmObj = layer.confirm('你真的要为这个子目录创建独立的伪静态规则吗?',{icon:3,closeBtn:2},function(){ + $.post('/site/get_dir_bind_rewrite','id='+id+'&add=1',function(rdata){ + layer.close(confirmObj); + showRewrite(rdata); + },'json'); + }); + return; + } + showRewrite(rdata); + },'json'); +} + +//显示伪静态 +function showRewrite(rdata){ + var rList = ''; + for(var i=0;i"+rdata.rlist[i]+""; + } + var webBakHtml = "
                                          \ +
                                          \ + \ + \ +
                                          \ + \ +
                                            \ +
                                          • 请选择您的应用,若设置伪静态后,网站无法正常访问,请尝试设置回default
                                          • \ +
                                          • 您可以对伪静态规则进行修改,修改完后保存即可。
                                          • \ +
                                          \ +
                                          "; + layer.open({ + type: 1, + area: '500px', + title: '配置伪静态规则', + closeBtn: 1, + shift: 5, + shadeClose: true, + content:webBakHtml, + success:function(){ + + $("#myRewrite").change(function(){ + var rewriteName = $(this).val(); + $.post('/files/get_body','path='+rdata['rewrite_dir']+'/'+rewriteName+'.conf',function(fileBody){ + $("#rewriteBody").val(fileBody.data.data); + },'json'); + }); + + $('#setRewriteBtn').click(function(){ + var data = $("#rewriteBody").val(); + setRewrite(rdata.filename, encodeURIComponent(data)); + }); + } + }); +} + +//添加子目录绑定 +function addDirBinding(id){ + var domain = $("input[name='domain']").val(); + var dir_name = $("select[name='dirName']").val(); + if(domain == '' || dir_name == '' || dir_name == null){ + layer.msg(lan.site.d_s_empty,{icon:2}); + return; + } + + var data = 'id='+id+'&domain='+domain+'&dir_name='+dir_name; + $.post('/site/add_dir_bind',data,function(rdata){ + dirBinding(id); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); +} + +//删除子目录绑定 +function delDirBind(id,siteId){ + layer.confirm(lan.site.s_bin_del,{icon:3,closeBtn:2},function(){ + $.post('/site/del_dir_bind','id='+id,function(rdata){ + dirBinding(siteId); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + }); +} + +//301重定向 +function to301(siteName, type, obj){ + + // 设置 页面展示 + if(type == 1) { + var obj = { + type: 1, + keep_path: 1, + to: 'http://', + from: '', + r_type: '', + type: 'path' + }; + + var keep_path_ht = obj.keep_path == 1 ? 'checked="checked"' : ''; + var redirect_title = type == 1 ? '创建重定向' : '修改重定向[' + obj.redirectname + ']'; + layer.open({ + type: 1, + area: ['650px','270px'], + title: redirect_title, + closeBtn: 1, + shift: 5, + btn: ['提交','关闭'], + shadeClose: false, + content: "
                                          \ +
                                          \ +
                                          \ + 保留URI参数\ + \ + \ +
                                          \ +
                                          \ +
                                          重定向类型\ +
                                          \ + \ + 重定向方式\ + \ +
                                          \ +
                                          \ +
                                          \ + 重定向源\ +
                                          \ + \ + 目标URL\ + \ +
                                          \ +
                                          \ +
                                          ", + success:function(index,layero){ + }, + yes:function(index,index1){ + var keep_path = $('[name="keep_path"]').prop('checked') ? 1 : 0; + var r_type = $('[name="r_type"]').val(); + var type = $('[name="type"]').val(); + var from = $('[name="from"]').val(); + var to = $('[name="to"]').val(); + + var pdata = {siteName: siteName, type: type,r_type: r_type,from: from,to: to,keep_path: keep_path}; + $.post('/site/set_redirect', pdata, function(data) { + if (data.status) { + layer.close(index); + to301(siteName); + } else { + layer.msg(data.msg, {icon: 2}); + } + },'json'); + } + }); + } + + if (type == 2) { + $.post('/site/del_redirect', { + siteName: siteName, + id: obj, + }, function(res) { + if (res.status == true) { + layer.msg('删除成功', {time: 1000,icon: 1}); + to301(siteName); + } else { + layer.msg(res.msg, {time: 1000,icon: 2}); + } + },'json'); + return + } + + if (type == 3) { + var laoding = layer.load(); + var data = {siteName: siteName,id: obj}; + $.post('/site/get_redirect_conf', data, function(res) { + layer.close(laoding); + if (res.status == true) { + var mBody = "
                                          \ + \ +
                                          \ +
                                            \ +
                                          • 此处为重定向配置文件,若您不了解配置规则,请勿随意修改.
                                          • \ +
                                          \ +
                                          \ +
                                          "; + var editor; + var index = layer.open({ + type: 1, + title: '编辑配置文件', + closeBtn: 1, + shadeClose: true, + area: ['500px', '500px'], + btn: ['提交','关闭'], + content: mBody, + success: function () { + editor = CodeMirror.fromTextArea(document.getElementById("configRedirectBody"), { + extraKeys: {"Ctrl-Space": "autocomplete"}, + lineNumbers: true, + matchBrackets:true, + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").unbind('click'); + }, + yes:function(index,layero){ + $("#configRedirectBody").empty().text(editor.getValue()); + var load = layer.load(); + var data = { + siteName: siteName, + id: obj, + config: editor.getValue(), + }; + $.post('/site/save_redirect_conf', data, function(res) { + layer.close(load) + if (res.status == true) { + layer.msg('保存成功', {icon: 1}); + layer.close(index); + } else { + layer.msg(res.msg, {time: 3000,icon: 2}); + } + },'json'); + return true; + }, + }); + + } else { + layer.msg('请求错误!!', {time: 3000,icon: 2}); + } + }); + return + } + + var body = '
                                          \ +
                                          \ + \ +
                                          \ +
                                          \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                          重定向类型重定向方式保留URL参数状态操作
                                          \ +
                                          \ +
                                          '; + $("#webedit-con").html(body); + + var loadT = layer.msg(lan.site.the_msg,{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/get_redirect','siteName='+siteName, function(res) { + layer.close(loadT); + $("#md-301-loading").remove(); + if (res.status) { + var data = res.data.result; + data.forEach(function(item){ + var lan_r_type = item.r_type == 0 ? "临时重定向" : "永久重定向"; + var keep_path = item.keep_path == 0 ? "不保留" : "保留"; + + var switchProxy = ''; + if (!item['status']){ + switchProxy = ''; + } + + let tmp = '\ + '+item.r_from+'\ + '+lan_r_type+'\ + '+keep_path+'\ + '+switchProxy+'\ + \ + 详细 | \ + 删除\ + \ + '; + $("#md-301-body").append(tmp); + }) + } else { + layer.msg(res.msg, {icon:2}); + } + },'json'); +} + + +function toRedirect(siteName, redirect_id, type){ + if (type == 10 || type == 11) { + //[11]启动 或 停止[10] + var status = type == 10 ? '0' : '1'; + var loading = layer.msg(lan.site.the_msg,{icon:16,time:0,shade: [0.3, '#000']}); + + var pdata = {siteName: siteName, 'status':status,'id':redirect_id }; + $.post('/site/set_redirect_status', pdata, function(rdata) { + layer.close(loading); + if (!rdata.status){ + layer.msg(res.msg, {time: 3000,icon: 2}); + return; + } + + showMsg("设置成功",function(){ + to301(siteName); + },{icon: 1,time:2000}); + },'json'); + return; + } +} + +//反向代理 +function toProxy(siteName, type, obj) { + // 设置 页面展示 + if(type == 1) { + var proxy_title = "创建反向代理"; + if (typeof(obj) != 'undefined'){ + proxy_title = "编辑反向代理"; + } + + layer.open({ + type: 1, + area: '650px', + title: proxy_title, + closeBtn: 1, + shift: 5, + shadeClose: false, + btn: ['提交','关闭'], + content: "
                                          \ +
                                          \ + 开启代理\ +
                                          \ + \ + \ +
                                          \ + 是否缓存\ + \ + \ +
                                          \ +
                                          \ + 是否跨域\ + \ + \ +
                                          \ +
                                          \ +
                                          \ +
                                          \ + 名称\ +
                                          \ + \ +
                                          \ +
                                          \ + \ +
                                          \ + 代理目录\ +
                                          \ + \ +
                                          \ +
                                          \ +
                                          \ + 目标URL\ +
                                          \ + \ + 发送域名\ + \ +
                                          \ +
                                          \ + \ +
                                          \ +
                                            \ +
                                          • 代理目录:访问这个目录时将会把目标URL的内容返回并显示
                                          • \ +
                                          • 目标URL:可以填写你需要代理的站点,目标URL必须为可正常访问的URL,否则将返回错误
                                          • \ +
                                          • 发送域名:将域名添加到请求头传递到代理服务器,默认为目标URL域名,若设置不当可能导致代理无法正常运行
                                          • \ +
                                          \ +
                                          \ +
                                          ", + success:function(){ + + if (typeof(obj) != 'undefined'){ + console.log(obj); + $('input[name="name"]').val(obj['name']).attr('readonly','readonly').addClass('disabled'); + if (obj['open_cache'] == 'on'){ + $("input[name='open_cache']").prop("checked",true); + $('#cache_time').show(); + } + + if (obj['open_cors'] == 'on'){ + $("input[name='open_cors']").prop("checked",true); + } + + if (obj['open_proxy'] == 'on'){ + $("input[name='open_proxy']").prop("checked",true); + } + + $('input[name="from"]').val(obj['from']); + $('input[name="to"]').val(obj['to']); + + var url = obj['to']; + var ip_reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; + url = url.replace(/^http[s]?:\/\//, ''); + url = url.replace(/(:|\?|\/|\\)(.*)$/, ''); + if (ip_reg.test(url)) { + $("[name='host']").val('$host'); + } else { + $("[name='host']").val(url); + } + + $('input[name="id"]').val(obj['id']); + $('input[name="cache_time"]').val(obj['cache_time']); + } + + + $('input[name="to"]').on('keyup', function(){ + var url = $(this).val(); + var ip_reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; + url = url.replace(/^http[s]?:\/\//, ''); + url = url.replace(/(:|\?|\/|\\)(.*)$/, ''); + if (ip_reg.test(url)) { + $("[name='host']").val('$host'); + } else { + $("[name='host']").val(url); + } + }); + + $("#open_proxy").click(function(){ + var status = $("input[name='open_proxy']").prop("checked")==true?1:0; + if(status==1){ + $("input[name='open_proxy']").prop("checked",false); + }else{ + $("input[name='open_proxy']").prop("checked",true); + } + }); + + $('#open_cache').click(function(){ + var status = $("input[name='open_cache']").prop("checked")==true?1:0; + if(status==1){ + $('#cache_time').hide(); + $("input[name='open_cache']").prop("checked",false); + }else{ + $('#cache_time').show(); + $("input[name='open_cache']").prop("checked",true); + } + }); + + $('#open_cors').click(function(){ + var status = $("input[name='open_cors']").prop("checked")==true?1:0; + if(status==1){ + $("input[name='open_cors']").prop("checked",false); + }else{ + $("input[name='open_cors']").prop("checked",true); + } + }); + }, + yes:function(index,layer_ro){ + var data = $('#form_proxy').serializeArray(); + var t = {}; + t['name'] = 'siteName'; + t['value'] = siteName; + data.push(t); + + // console.log(data); + var loading = layer.msg('正在'+proxy_title+'...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_proxy',data, function(res) { + layer.close(loading); + if (!res.status){ + layer.msg(res.msg, {icon: 2,time:10000}); + return; + } + + showMsg(proxy_title+"成功!",function(){ + layer.close(index); + toProxy(siteName); + },{icon: 1, time:2000}); + },'json'); + } + }); + } + + if (type == 2) { + var loading = layer.msg('正在删除中...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/del_proxy', {siteName: siteName,id: obj,}, function(res) { + layer.close(loading); + if (res.status == true) { + showMsg('删除成功', function(){ + toProxy(siteName); + },{time: 1000,icon: 1}); + } else { + layer.msg(res.msg, {time: 1000,icon: 2}); + } + },'json'); + return + } + + + if (type == 3) { + var laoding = layer.load(); + var data = {siteName: siteName,id: obj}; + $.post('/site/get_proxy_conf', data, function(res) { + layer.close(laoding); + if (!res.status){ + layer.msg('请求错误!!', {time: 3000,icon: 2}); + return; + } + + var mBody = "
                                          \ + \ +
                                          \ +
                                            \ +
                                          • 此处为反向代理配置文件,若您不了解配置规则,请勿随意修改.
                                          • \ +
                                          \ +
                                          \ +
                                          "; + var editor; + function saveDataFunc(){ + $("#configProxyBody").empty().text(editor.getValue()); + var load = layer.load(); + var data = { + siteName: siteName, + id: obj, + config: editor.getValue(), + }; + + $.post('/site/save_proxy_conf', data, function(res) { + layer.close(load) + if (res.status == true) { + layer.msg('保存成功', {icon: 1}); + layer.close(index); + } else { + layer.msg(res.msg, {time: 3000,icon: 2}); + } + },'json'); + } + var index = layer.open({ + type: 1, + title: '编辑配置文件', + closeBtn: 1, + shadeClose: true, + area: ['500px', '500px'], + btn: ['提交','关闭'], + content: mBody, + success: function () { + editor = CodeMirror.fromTextArea(document.getElementById("configProxyBody"), { + lineNumbers: true, + matchBrackets:true, + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + saveDataFunc(); + }, + "Cmd-S":function() { + saveDataFunc(); + } + } + }); + editor.focus(); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#onlineEditFileBtn").unbind('click'); + }, + yes:function(index,layero){ + saveDataFunc(); + return true; + }, + }); + },'json'); + return; + } + + if (type == 10 || type == 11) { + //[11]启动 或 停止[10] + status = type==10 ? '0' : '1'; + var loading = layer.msg(lan.site.the_msg,{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_proxy_status', {siteName: siteName,'status':status,'id':obj}, function(rdata) { + layer.close(loading); + if (!rdata.status){ + layer.msg(res.msg, {time: 3000,icon: 2}); + return; + } + + showMsg("设置成功",function(){ + toProxy(siteName); + },{icon: 1,time:2000}); + },'json'); + return; + } + + if (type == 20 || type == 21) { + //[20] 开始缓存 或 [21] 停止缓存 + var status = type == 20 ? 'on' : ''; + obj['open_cache'] = status; + obj['siteName'] = siteName; + + var loading = layer.msg('正在提交请求...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_proxy',obj, function(rdata) { + layer.close(loading); + if (!rdata.status){ + layer.msg(rdata.msg, {icon: 2,time:2000}); + return; + } + + showMsg("设置成功!",function(){ + toProxy(siteName); + },{icon: 1, time:2000}); + },'json'); + return; + } + + var body = '
                                          \ +
                                          \ + \ +
                                          \ +
                                          \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                          名称代理目录目标地址缓存状态操作
                                          \ +
                                          \ +
                                          '; + $("#webedit-con").html(body); + + var loading = layer.msg(lan.site.the_msg,{icon:16,time:0,shade: [0.3, '#000']}); + $.post("/site/get_proxy_list", {siteName: siteName},function (res) { + layer.close(loading); + if (!res.status){ + layer.msg(res.msg, {icon:2}); + return; + } + + var data = res.data.result; + for (var i = 0; i < data.length; i++) { + var item = data[i]; + + var switchProxy = ''; + if (!item['status']){ + switchProxy = ''; + } + + var openCache = '未开启'; + if (item['open_cache'] == 'on'){ + openCache = '已开启'; + } + + let tmp = '\ + '+item.name+'\ + '+item.from+'\ + '+item.to+'\ + '+openCache+'\ + '+switchProxy+'\ + \ + 详细 |\ + 编辑 |\ + 删除\ + \ + '; + $("#md-301-body").append(tmp); + } + + $('#md-301-body .detail').click(function(){ + var index = $(this).data('index'); + toProxy(siteName, 3 ,data[index]['id']); + }); + + $('#md-301-body .edit').click(function(){ + var index = $(this).data('index'); + toProxy(siteName, 1 ,data[index]); + }); + + $('#md-301-body .delete').click(function(){ + var index = $(this).data('index'); + toProxy(siteName, 2 ,data[index]['id']); + }); + + $('#md-301-body .cache').click(function(){ + var index = $(this).data('index'); + if ($(this).hasClass('on')){ + toProxy(siteName, 21 ,data[index]); + } else{ + toProxy(siteName, 20 ,data[index]); + } + }); + + },'json'); +///////// +} + +//证书夹 +function sslAdmin(siteName){ + var loadT = layer.msg('正在提交任务...',{icon:16,time:0,shade: [0.3, '#000']}); + $.get('/site/get_cert_list',function(data){ + layer.close(loadT); + var rdata = data['data']; + var tbody = ''; + for(var i=0;i\ + '+rdata[i].dns.join('
                                          ')+'\ + '+rdata[i].notAfter+'\ + '+rdata[i].issuer.split(' ')[0]+'\ + 部署 | 删除\ + ' + } + var txt = '
                                          \ + \ +
                                          \ + '+tbody+'\ +
                                          域名信任名称到期时间品牌操作
                                          '; + $(".tab-con").html(txt); + },'json'); +} + +//删除证书 +function removeSsl(certName){ + safeMessage('删除证书','您真的要从证书夹删除证书吗?',function(){ + var loadT = layer.msg(lan.site.the_msg,{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/remove_cert',{certName:certName},function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + $("#ssl_admin").click(); + },'json'); + }); +} + +//从证书夹部署 +function setCertSsl(certName,siteName){ + var loadT = layer.msg('正在部署证书...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_cert_to_site',{certName:certName,siteName:siteName},function(rdata){ + layer.close(loadT); + showMsg(rdata.msg, function(){ + $(".tab-nav span:first-child").click(); + },{icon:rdata.status?1:2},2000); + },'json'); +} + +//ssl +function setSSL(id,siteName){ + // Let\'s Encrypt + // 暂时关闭 Let 申请模式 + var sslHtml = '\ +
                                          \ + 当前证书 - [未部署SSL]\ + \ + ACME\ + 证书夹' + + '
                                          \ +
                                          ' + + '
                                          '; + $("#webedit-con").html(sslHtml); + $(".tab-nav span").click(function(){ + $(this).addClass("on").siblings().removeClass("on"); + }); + opSSL('now',id,siteName); +} + +//设置httpToHttps +function httpToHttps(siteName){ + var isHttps = $("#toHttps").prop('checked'); + if(isHttps){ + layer.confirm('关闭强制HTTPS后需要清空浏览器缓存才能看到效果,继续吗?',{icon:3,title:"关闭强制HTTPS"},function(){ + $.post('/site/close_to_https','siteName='+siteName,function(rdata){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + }); + }else{ + $.post('/site/http_to_https','siteName='+siteName,function(rdata){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); + } +} + + +function deleteSSL(type,id,siteName){ + $.post('/site/delete_ssl','site_name='+siteName+'&ssl_type='+type,function(rdata){ + showMsg(rdata.msg, function(){ + opSSL(type,id,siteName); + },{icon:rdata.status?1:2}, 2000); + },'json'); +} + +function deploySSL(type,id,siteName){ + $.post('/site/deploy_ssl','site_name='+siteName+'&ssl_type='+type,function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + $('#now_ssl').click(); + } else{ + opSSL(type,id,siteName); + } + },{icon:rdata.status?1:2}, 2000); + },'json'); +} + +function renewSSL(type,id,siteName){ + showSpeedWindow('正在续签...', 'site.get_let_logs', function(layers,index){ + $.post('/site/renew_ssl','site_name='+siteName+'&ssl_type='+type,function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + opSSL(type,id,siteName); + } + },{icon:rdata.status?1:2}, 2000); + },'json'); + }); +} + + + +function renderDnsapiHtml(data){ + var fields = data.data; + var fields_html = ''; + + for (var d in fields) { + fields_html += ""+d+"\ +
                                          \ + \ +
                                          "; + } + + layer.open({ + type: 1, + area: '500px', + title: '设置'+data['title']+'接口', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["确定","取消"], + content: "
                                          \ +
                                          \ + DNSAPI类型\ +
                                          \ + \ +
                                          \ +
                                          \ +
                                          \ + "+fields_html+"\ +
                                          \ +
                                          \ +
                                          \ +
                                            \ +
                                          • 使用【"+data['title']+"】的API接口自动解析申请SSL
                                          • \ +
                                          \ +
                                          \ +
                                          \ +
                                          ", + success:function(){ + $('[data-toggle="tooltip"]').tooltip(); + }, + yes:function(index,l) { + var type_name = $('select[name="type_name"]').val(); + var data_field = {}; + for (var d in fields) { + data_field[d] = $('input[name="'+d+'"]').val(); + } + + $.post('/site/set_dnsapi', {'type':type_name,'data':JSON.stringify(data_field)}, function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + renderDnsapi(); + } + },{icon:rdata.status?1:2}); + },'json'); + } + }); + +} + +function renderDnsapi(){ + $('#dnsapi_set').css('display', 'none'); + + $.post('/site/get_dnsapi', {}, function(data){ + var dnsapi_option = ''; + for (var i = 0; i < data.length; i++) { + dnsapi_option+=''; + } + + $('#dnsapi_option select').html(dnsapi_option); + $('#dnsapi_option select').on('change',function(){ + var val = $(this).val(); + var index = $('#dnsapi_option option:selected').attr('index'); + if (val == 'none'){ + $('#dnsapi_option button').css('display','none'); + } else { + $('#dnsapi_option button').css('display','inline-block'); + if (!(data[index]['title'].indexOf('[')>0)){ + renderDnsapiHtml(data[index]); + } + } + }); + + $('#dnsapi_set').unbind('click').on('click', function(){ + var index = $('#dnsapi_option option:selected').attr('index'); + renderDnsapiHtml(data[index]); + }); + },'json'); +} + +function opSSLNow(type, id, siteName, callback){ + var now = '
                                          \ + \ +
                                          \ +
                                          密钥(KEY)
                                          \ +
                                          证书(PEM格式)
                                          \ +
                                          \ +
                                          \ + \ +
                                          \ +
                                          \ +
                                            \ +
                                          • 粘贴您的*.key以及*.pem内容,然后保存即可。
                                          • \ +
                                          • 如果浏览器提示证书链不完整,请检查是否正确拼接PEM证书
                                          • PEM格式证书 = 域名证书.crt + 根证书(root_bundle).crt
                                          • \ +
                                          • 在未指定SSL默认站点时,未开启SSL的站点使用HTTPS会直接访问到已开启SSL的站点
                                          • \ +
                                          '; + + $(".tab-con").html(now); + var key = ''; + var csr = ''; + var loadT = layer.msg('正在提交任务...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/get_ssl','site_name='+siteName,function(data){ + layer.close(loadT); + var rdata = data['data']; + + if (rdata['cert_data']){ + var issuer = rdata['cert_data']['issuer'].split(" "); + var domains = rdata['cert_data']['dns'].join("、"); + + var cert_data = "
                                          \ +
                                          证书品牌:"+issuer[0]+"
                                          \ +
                                          到期时间:剩余"+rdata['cert_data']['endtime']+"天到期
                                          \ +
                                          \ +
                                          \ +
                                          认证域名:"+domains+"
                                          \ +
                                          强制HTTPS:\ + \ +
                                          \ +
                                          "; + $(".ssl_state_info").html(cert_data); + $(".ssl_state_info").css('display','block'); + } + + if(rdata.key == false){ + rdata.key = ''; + } else { + $(".ssl-btn").append(''); + } + + if(rdata.csr == false){ + rdata.csr = ''; + } + $("#key").val(rdata.key); + $("#csr").val(rdata.csr); + + $("#toHttps").attr('checked',rdata.httpTohttps); + if(rdata.status){ + $('.warning_info').css('display','none'); + + $(".ssl-btn").append(""); + $('#now_ssl').html('当前证书 - [已部署SSL]'); + } else{ + $('.warning_info').css('display','block'); + $('#now_ssl').html('当前证书 - [未部署SSL]'); + } + + + if (typeof (callback) != 'undefined'){ + callback(rdata); + } + },'json'); +} + +function opSSLAcme(type, id, siteName, callback){ + var acme = '
                                          \ +
                                          \ +
                                          \ + 验证方式\ +
                                          \ + \ + \ + \ + \ +
                                          \ +
                                          \ +
                                          \ + 证书\ +
                                          \ + \ + \ + \ + \ + zerossl\ + buypass\ +
                                          \ +
                                          \ + \ + \ +
                                          \ +
                                          \ + \ + \ +
                                          \ +
                                          \ +
                                          \ +
                                          \ + 邮箱\ + \ +
                                          \ + \ +
                                          \ + 域名\ +
                                            \ +
                                            \ +
                                            \ + \ +
                                            \ +
                                              \ +
                                            • 申请之前,请确保域名已解析,如未解析会导致审核失败
                                            • \ +
                                            • 由ACME免费申请证书,有效期3个月,支持多域名。默认会自动续签
                                            • \ +
                                            • 若您的站点使用了CDN或301重定向会导致续签失败
                                            • \ +
                                            • 在未指定SSL默认站点时,未开启SSL的站点使用HTTPS会直接访问到已开启SSL的站点
                                            \ +
                                          \ + '; + + $(".tab-con").html(acme); + + $('input[name="apply_type"]').on('change', function(){ + var val = $(this).val(); + if (val == 'file'){ + $('#dnsapi_option').css('display','none'); + $('#wildcard_domain_block').css('display','none'); + $('#dns_alias').css('display','none'); + } else { + $('#dnsapi_option').css('display','block'); + $('#wildcard_domain_block').css('display','block'); + + // 关闭,咱不开发,没有验证通过 + $('#dns_alias').css('display','block'); + } + }); + + renderDnsapi(); + + $.post('/site/get_ssl','site_name='+siteName+'&ssl_type=acme', function(data){ + var rdata = data['data']; + if(rdata.csr == false){ + $.post('/site/get_site_domains','id='+id, function(rdata) { + var data = rdata['data']; + var opt=''; + for(var i=0;i'+data.domains[i].name + +''; + } + } + $("input[name='admin_email']").val(data.email); + $("#ymlist").html(opt); + $("#ymlist li input").click(function(e){ + e.stopPropagation(); + }) + $("#ymlist li").click(function(){ + var o = $(this).find("input"); + if(o.prop("checked")){ + o.prop("checked",false) + } + else{ + o.prop("checked",true); + } + }) + $(".letsApply").click(function(){ + var c = $("#ymlist input[type='checkbox']"); + var str = []; + var domains = ''; + for(var i=0; i\ +
                                          证书(PEM格式)
                                          \ + \ +
                                          \ + \ + \ +
                                          \ + \ +
                                            \ +
                                          • 已为您自动生成ACME免费证书
                                          • \ +
                                          • 由ACME免费申请证书,有效期3个月,支持多域名。默认会自动续签
                                          • \ +
                                          • 如需使用其他SSL,请切换其他证书后粘贴您的KEY以及PEM内容,然后保存即可。
                                          • \ +
                                          '; + $(".tab-con").html(acme); + + if (rdata['cert_data']){ + var issuer = rdata['cert_data']['issuer'].split(" "); + var domains = rdata['cert_data']['dns'].join("、"); + + var cert_data = "
                                          \ +
                                          证书品牌:"+issuer[0]+"
                                          \ +
                                          到期时间:剩余"+rdata['cert_data']['endtime']+"天到期
                                          \ +
                                          \ +
                                          \ +
                                          认证域名:"+domains+"
                                          \ +
                                          "; + $(".ssl_state_info").html(cert_data); + $(".ssl_state_info").css('display','block'); + } + },'json'); +} + +function opSSLLet(type, id, siteName, callback){ + var lets = '
                                          \ +
                                          \ +
                                          \ + 验证方式\ +
                                          \ + \ + \ + \ + \ +
                                          \ +
                                          \ + \ + \ +
                                          \ +
                                          \ + \ + \ +
                                          \ +
                                          \ +
                                          \ +
                                          \ + 邮箱\ + \ +
                                          \ +
                                          \ + 域名\ +
                                            \ +
                                            \ +
                                            \ + \ +
                                            \ +
                                              \ +
                                            • 申请之前,请确保域名已解析,如未解析会导致审核失败
                                            • \ +
                                            • Let\'s Encrypt免费证书,有效期3个月,支持多域名。默认会自动续签
                                            • \ +
                                            • 若您的站点使用了CDN或301重定向会导致续签失败
                                            • \ +
                                            • 在未指定SSL默认站点时,未开启SSL的站点使用HTTPS会直接访问到已开启SSL的站点
                                            • \ +
                                            \ +
                                            '; + + $(".tab-con").html(lets); + + $('input[name="apply_type"]').on('change', function(){ + var val = $(this).val(); + if (val == 'file'){ + $('#dnsapi_option').css('display','none'); + $('#wildcard_domain_block').css('display','none'); + } else { + $('#dnsapi_option').css('display','block'); + $('#wildcard_domain_block').css('display','block'); + } + }); + + renderDnsapi(); + + $.post('/site/get_ssl', 'site_name='+siteName+'&ssl_type=lets', function(data){ + var rdata = data['data']; + if(rdata.csr == false){ + $.post('/site/get_site_domains','id='+id, function(rdata) { + var data = rdata['data']; + var opt=''; + for(var i=0;i'+data.domains[i].name+'' + } + } + $("input[name='admin_email']").val(data.email); + $("#ymlist").html(opt); + $("#ymlist li input").click(function(e){ + e.stopPropagation(); + }) + $("#ymlist li").click(function(){ + var o = $(this).find("input"); + if(o.prop("checked")){ + o.prop("checked",false) + } + else{ + o.prop("checked",true); + } + }) + $(".letsApply").click(function(){ + var c = $("#ymlist input[type='checkbox']"); + var str = []; + var domains = ''; + for(var i=0; i\ +
                                            证书(PEM格式)
                                            \ + \ +
                                            \ + \ + \ + \ +
                                            \ + \ +
                                              \ +
                                            • 已为您自动生成Let\'s Encrypt免费证书
                                            • \ +
                                            • 由Let\'s Encrypt免费申请证书,有效期3个月,支持多域名。默认会自动续签
                                            • \ +
                                            • 如需使用其他SSL,请切换其他证书后粘贴您的KEY以及PEM内容,然后保存即可。
                                            • \ +
                                            '; + $(".tab-con").html(lets); + + if (rdata['cert_data']){ + var issuer = rdata['cert_data']['issuer'].split(" "); + var domains = rdata['cert_data']['dns'].join("、"); + + var cert_data = "
                                            \ +
                                            证书品牌:"+issuer[0]+"
                                            \ +
                                            到期时间:剩余"+rdata['cert_data']['endtime']+"天到期
                                            \ +
                                            \ +
                                            \ +
                                            认证域名:"+domains+"
                                            \ +
                                            "; + $(".ssl_state_info").html(cert_data); + $(".ssl_state_info").css('display','block'); + } + },'json'); +} + +//SSL +function opSSL(type, id, siteName, callback){ + switch(type){ + case 'lets':opSSLLet(type, id, siteName, callback);break; + case 'acme':opSSLAcme(type, id, siteName, callback);break; + case 'now':opSSLNow(type, id, siteName, callback);break; + default:layer.msg("错误类型", {icon:5});break; + } +} + + +//开启与关闭SSL +function ocSSL(action,siteName){ + var loadT = layer.msg('正在获取证书列表,请稍后..',{icon:16,time:0,shade: [0.3, '#000']}); + $.post("/site/"+action,'siteName='+siteName+'&updateOf=1',function(rdata){ + layer.close(loadT) + + if(!rdata.status){ + if(!rdata.out){ + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + setSSL(siteName); + return; + } + data = "

                                            证书获取失败:


                                            " + for(var i=0;i" + + "

                                            错误类型: "+rdata.out[i].Type+"

                                            " + + "

                                            详情: "+rdata.out[i].Detail+"

                                            " + + "
                                            "; + } + layer.msg(data,{icon:2,time:0,shade:0.3,shadeClose:true}); + return; + } + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + if(action == 'close_ssl_conf'){ + layer.msg('已关闭SSL,请务必清除浏览器缓存后再访问站点!',{icon:1,time:5000}); + } + $(".tab-nav .on").click(); + },'json'); +} + +//生成SSL +function newSSL(siteName, id, domains){ + showSpeedWindow('正在申请...', 'site.get_let_logs', function(layers,index){ + var pdata = {}; + pdata['siteName'] = siteName; + pdata['domains'] = domains; + pdata['email'] = $("input[name='admin_email']").val(); + + if($("#checkDomain").prop("checked")){ + pdata['force'] = 'true'; + } + + if($("#wildcard_domain").prop("checked")){ + pdata['wildcard_domain'] = 'true'; + } + + var apply_type = $('input[name="apply_type"]:checked').val(); + pdata['apply_type'] = apply_type; + if (apply_type == 'dns'){ + pdata['dnspai'] = $('#dnsapi_option option:selected').val(); + } + + $.post('/site/create_let',pdata,function(rdata){ + showMsg(rdata.msg, function(){ + layer.close(index); + if(rdata.status){ + $(".tab-nav span:first-child").click(); + } + },{icon:rdata.status?1:2}, 3000); + },'json'); + }); +} + +// 手动申请dns提示 +function newAcmeHandApplyNotice(siteName, id, domains, data){ + // console.log(siteName, id, domains, data); + layer.open({ + type: 1, + area: '700px', + title: '手动解析TXT记录', + closeBtn: 1, + shift: 5, + shadeClose: true, + btn:["验证", "取消"], + content:'
                                            \ +
                                            请按以下列表做TXT解析:
                                            \ +
                                            \ +
                                            \ + \ + \ + \ +
                                            解析域名记录值类型必需
                                            \ +
                                            \ +
                                            \ +
                                              \ +
                                            • 解析域名需要一定时间来生效,完成所以上所有解析操作后,请等待1分钟后再点击【验证】按钮
                                            • \ +
                                            • 可通过CMD命令来手动验证域名解析是否生效: nslookup -q=txt _acme-challenge.xx.cn
                                            • \ +
                                            • 若您使用的是阿里云DNS,DnsPod等等作为DNS,可使用DNS接口自动解析
                                            • \ +
                                            \ +
                                            ', + success:function(){ + var list = ''; + for (var i = 0; i < data.length; i++) { + list += ''; + list += ''+data[i]['domain']+''; + list += ''+data[i]['val']+''; + list += ''+data[i]['type']+''; + + if (data[i]['must']){ + list += '必需'; + } else{ + list += '可选'; + } + list += ''; + } + $('#acme_hand_ssl_notice tbody').html(list); + + if (data.length>0){ + var help_txt = "可通过CMD命令来手动验证域名解析是否生效: nslookup -q=txt "+data[0]['domain']; + $('#acme_hand_ssl_notice_help li:eq(1)').text(help_txt); + } + }, + yes:function(layero,index){ + layer.close(layero); + showSpeedWindow('正在由ACME申请手动SSL...', 'site.get_acme_logs', function(layers,index){ + var pdata = {}; + pdata['siteName'] = siteName; + pdata['domains'] = domains; + pdata['email'] = $("input[name='admin_email']").val(); + + if($("#checkDomain").prop("checked")){ + pdata['force'] = 'true'; + } + + if($("#wildcard_domain").prop("checked")){ + pdata['wildcard_domain'] = 'true'; + } + + var apply_type = $('input[name="apply_type"]:checked').val(); + pdata['apply_type'] = apply_type; + + var apply_ca = $('input[name="apply_ca"]:checked').val(); + pdata['apply_ca'] = apply_ca; + + if (apply_type == 'dns'){ + pdata['dnspai'] = $('#dnsapi_option option:selected').val(); + } + pdata['dns_alias'] = $("input[name='dns_alias']").val(); + + pdata['renew'] = 'true'; + $.post('/site/create_acme',pdata,function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + $(".tab-nav span:first-child").click(); + } + },{icon:rdata.status?1:2}, 3000); + },'json'); + }); + } + }); +} + +function newAcmeSSL(siteName, id, domains){ + showSpeedWindow('正在由ACME申请...', 'site.get_acme_logs', function(layers,index){ + var pdata = {}; + pdata['siteName'] = siteName; + pdata['domains'] = domains; + pdata['email'] = $("input[name='admin_email']").val(); + + if($("#checkDomain").prop("checked")){ + pdata['force'] = 'true'; + } + + if($("#wildcard_domain").prop("checked")){ + pdata['wildcard_domain'] = 'true'; + } + + var apply_type = $('input[name="apply_type"]:checked').val(); + pdata['apply_type'] = apply_type; + + var apply_ca = $('input[name="apply_ca"]:checked').val(); + pdata['apply_ca'] = apply_ca; + + if (apply_type == 'dns'){ + pdata['dnspai'] = $('#dnsapi_option option:selected').val(); + } + + pdata['dns_alias'] = $("input[name='dns_alias']").val(); + $.post('/site/create_acme',pdata,function(rdata){ + showMsg(rdata.msg, function(){ + if (rdata.status){ + layer.close(index); + if (rdata.msg == '手动解析'){ + newAcmeHandApplyNotice(siteName, id, domains, rdata.data); + } else{ + $(".tab-nav span:first-child").click(); + } + } + },{icon:rdata.status?1:2}, 3000); + },'json'); + }); +} + +//保存SSL +function saveSSL(siteName){ + var data = 'type=1&siteName='+siteName+'&key='+encodeURIComponent($("#key").val())+'&csr='+encodeURIComponent($("#csr").val()); + var loadT = layer.msg(lan.site.saving_txt,{icon:16,time:20000,shade: [0.3, '#000']}) + $.post('/site/set_ssl',data,function(rdata){ + layer.close(loadT); + if(rdata.status){ + layer.msg(rdata.msg,{icon:1}); + $(".ssl-btn").find(".btn-default").remove(); + $(".ssl-btn").append(""); + } else { + layer.msg(rdata.msg,{icon:2,time:0,shade:0.3,shadeClose:true}); + } + },'json'); +} + +//PHP版本 +function phpVersion(siteName){ + $.post('/site/get_site_php_version','siteName='+siteName,function(version){ + // console.log(version); + if(version.status === false){ + layer.msg(version.msg,{icon:5}); + return; + } + $.post('/site/get_php_version',function(data){ + var rdata = data.data; + var versionSelect = "
                                            \ +
                                            \ + PHP版本\ +
                                            \ + \ + \ +
                                            \ + \ +
                                            \ +
                                              \ +
                                            • 请根据您的程序需求选择版本
                                            • \ +
                                            • 若非必要,请尽量不要使用PHP5.2,这会降低您的服务器安全性;
                                            • \ +
                                            • PHP7不支持mysql扩展,默认安装mysqli以及mysql-pdo。
                                            • \ +
                                            \ +
                                            \ + "; + $("#webedit-con").html(versionSelect); + //验证PHP版本 + $("select[name='phpVersion']").change(function(){ + if($(this).val() == '52'){ + var msgerr = 'PHP5.2在您的站点有漏洞时有跨站风险,请尽量使用PHP5.3以上版本!'; + $('#php_w').text(msgerr); + }else{ + $('#php_w').text(''); + } + }) + },'json'); + },'json'); +} + + +//设置PHP版本 +function setPHPVersion(siteName){ + var data = 'version='+$("#phpVersion").val()+'&siteName='+siteName; + var loadT = layer.msg('正在保存...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_php_version',data,function(rdata){ + layer.close(loadT); + layer.msg(rdata.msg,{icon:rdata.status?1:2}); + },'json'); +} + +//配置文件 +function configFile(webSite){ + var info = syncPost('/site/get_host_conf', {siteName:webSite}); + $.post('/files/get_body','path='+info['host'],function(rdata){ + var mBody = "
                                            \ + \ +
                                            \ + \ +
                                              \ +
                                            • 此处为站点主配置文件,若您不了解配置规则,请勿随意修改.
                                            • \ +
                                            \ +
                                            \ +
                                            "; + $("#webedit-con").html(mBody); + var editor = CodeMirror.fromTextArea(document.getElementById("configBody"), { + extraKeys: {"Ctrl-Space": "autocomplete"}, + lineNumbers: true, + matchBrackets:true, + }); + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#SaveConfigFileBtn").click(function(){ + $("#configBody").empty(); + $("#configBody").text(editor.getValue()); + saveConfigFile(webSite,rdata.data.encoding, info['host']); + }) + },'json'); +} + +//保存配置文件 +function saveConfigFile(webSite,encoding,path){ + var data = 'encoding='+encoding+'&data='+encodeURIComponent($("#configBody").val())+'&path='+path; + var loadT = layer.msg('保存中...',{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/save_host_conf',data,function(rdata){ + layer.close(loadT); + if(rdata.status){ + layer.msg(rdata.msg,{icon:1}); + }else{ + layer.msg(rdata.msg,{icon:2,time:0,shade:0.3,shadeClose:true}); + } + },'json'); +} + +//伪静态 +function rewrite(siteName){ + $.post("/site/get_rewrite_list", 'siteName='+siteName,function(rdata){ + var info = syncPost('/site/get_rewrite_conf', {siteName:siteName}); + var filename = info['rewrite']; + $.post('/files/get_body','path='+filename,function(fileBody){ + var centent = fileBody['data']['data']; + var rList = ''; + for(var i=0;i"; + } else { + rList += ""; + } + } + var webBakHtml = "
                                            \ +
                                            \ + \ +
                                            \ + \ + \ +
                                              \ +
                                            • 请选择您的应用,若设置伪静态后,网站无法正常访问,请尝试设置回default
                                            • \ +
                                            • 您可以对伪静态规则进行修改,修改完后保存即可。
                                            • \ +
                                            \ +
                                            "; + $("#webedit-con").html(webBakHtml); + + var editor = CodeMirror.fromTextArea(document.getElementById("rewriteBody"), { + extraKeys: { + "Ctrl-Space": "autocomplete", + "Ctrl-F": "findPersistent", + "Ctrl-H": "replaceAll", + "Ctrl-S": function() { + $("#rewriteBody").empty(); + $("#rewriteBody").text(editor.getValue()); + setRewrite(filename, encodeURIComponent(editor.getValue())); + }, + "Cmd-S":function() { + $("#rewriteBody").empty(); + $("#rewriteBody").text(editor.getValue()); + setRewrite(filename, encodeURIComponent(editor.getValue())); + }, + }, + lineNumbers: true, + matchBrackets:true, + }); + + $(".CodeMirror-scroll").css({"height":"300px","margin":0,"padding":0}); + $("#SetRewriteBtn").click(function(){ + $("#rewriteBody").empty(); + $("#rewriteBody").text(editor.getValue()); + setRewrite(filename, encodeURIComponent(editor.getValue())); + }); + $("#SetRewriteBtnTel").click(function(){ + $("#rewriteBody").empty(); + $("#rewriteBody").text(editor.getValue()); + setRewriteTel(); + }); + + $("#myRewrite").change(function(){ + var rewriteName = $(this).val(); + if(rewriteName == '0'){ + rpath = filename; + }else{ + var info = syncPost('/site/get_rewrite_tpl', {tplname:rewriteName}); + if (!info['status']){ + layer.msg(info['msg']); + return; + } + rpath = info['data']; + } + + $.post('/files/get_body','path='+rpath,function(fileBody){ + $("#rewriteBody").val(fileBody['data']['data']); + editor.setValue(fileBody['data']['data']); + },'json'); + }); + },'json'); + },'json'); +} + + +//设置伪静态 +function setRewrite(filename,data){ + var data = 'data='+data+'&path='+filename+'&encoding=utf-8'; + var loadT = layer.msg(lan.site.saving_txt,{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_rewrite',data,function(rdata){ + layer.close(loadT); + if(rdata.status){ + layer.msg(rdata.msg,{icon:1}); + }else{ + layer.msg(rdata.msg,{icon:2,time:0,shade:0.3,shadeClose:true}); + } + },'json'); +} +var aindex = null; + +//保存为模板 +function setRewriteTel(act){ + aindex = layer.open({ + type: 1, + shift: 5, + closeBtn: 1, + area: '320px', //宽高 + title: '保存为Rewrite模板', + btn:[lan.public.ok,lan.public.cancel], + content: '
                                            \ +
                                            \ + \ +
                                            \ +
                                            ', + success:function(index){ + $("#rewriteName").focus().keyup(function(e){ + if(e.keyCode == 13) $("#rewriteNameBtn").click(); + }); + }, + yes:function(index){ + name = $("#rewriteName").val(); + if(name == ''){ + layer.msg(lan.site.template_empty,{icon:5}); + return; + } + var data = 'data='+encodeURIComponent($("#rewriteBody").val())+'&name='+name; + var loadT = layer.msg(lan.site.saving_txt,{icon:16,time:0,shade: [0.3, '#000']}); + $.post('/site/set_rewrite_tpl',data,function(rdata){ + layer.close(loadT); + layer.close(index); + layer.msg(rdata.msg, {icon:rdata.status?1:5}); + },'json'); + return; + } + }); +} +//修改默认页 +function siteDefaultPage(){ + stype = getCookie('serverType'); + layer.open({ + type: 1, + area: '460px', + title: '修改默认页', + closeBtn: 1, + shift: 0, + content: '
                                            \ + \ + \ + \ + \ +
                                            ' + }); +} + +function changeDefault(type, obj){ + $(obj).attr('disabled', true); + $.post('/site/get_site_doc','type='+type, function(rdata){ + setTimeout(function(){ + $(obj).attr('disabled', false); + },3000); + if (rdata.status){ + var path = rdata.data.path; + onlineEditFile(0,path); + } + },'json'); +} + +function getClassType(){ + var select = $('.site_type > select'); + $.post('/site/get_site_types',function(rdata){ + var rdata = rdata.data; + $(select).html(''); + $(select).append(''); + for (var i = 0; i'+rdata[i]['name']+''); + } + + $(select).bind('change',function(){ + var select_id = $(this).val(); + getWeb(1,select_id, ''); + }) + },'json'); +} +getClassType(); + +function setClassType(){ + $.post('/site/get_site_types',function(rdata){ + var rdata = rdata.data; + + var list = ''; + for (var i = 0; i\ + 编辑 | 删除\ + '; + } + + layer.open({ + type: 1, + area: '350px', + title: '网站分类管理', + closeBtn: 1, + shift: 0, + content: '
                                            \ +
                                            \ +
                                            \ +
                                            \ +
                                            \ + \ + \ + '+list+'\ +
                                            名称操作
                                            \ +
                                            \ +
                                            ' + }); + },'json'); +} + +function addClassType(){ + var name = $("input[name=type_name]").val(); + $.post('/site/add_site_type','name='+name, function(rdata){ + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.closeAll(); + setClassType(); + getClassType(); + } + },{icon:rdata.status?1:2}); + },'json'); +} + +function removeClassType(id,name){ + if (id == 0){ + layer.msg('默认分类不可删除/不可编辑!',{icon:2}); + return; + } + layer.confirm('是否确定删除分类?',{title: '删除分类【'+ name +'】' }, function(){ + $.post('/site/remove_site_type','id='+id, function(rdata){ + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.closeAll(); + setClassType(); + getClassType(); + } + },{icon:rdata.status?1:2}); + },'json'); + }); +} + +function editClassType(id,name){ + if (id == 0){ + layer.msg('默认分类不可删除/不可编辑!',{icon:2}); + return; + } + + layer.open({ + type: 1, + area: '350px', + title: '修改分类管理【' + name + '】', + closeBtn: 1, + shift: 0, + content: "
                                            \ +
                                            \ + 分类名称\ +
                                            \ +
                                            \ +
                                            \ + \ +
                                            \ +
                                            " + }); + + $('#site_type_mod').unbind().click(function(){ + var _name = $('input[name=site_type_mod]').val(); + $.post('/site/modify_site_type_name','id='+id+'&name='+_name, function(rdata){ + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.closeAll(); + setClassType(); + getClassType(); + } + },{icon:rdata.status?1:2}); + },'json'); + + }); +} + + +function moveClassTYpe(){ + $.post('/site/get_site_types',function(rdata){ + var option = ''; + for (var i = 0; i'+rdata[i]['name']+''; + } + + layer.open({ + type: 1, + area: '350px', + title: '设置站点分类', + closeBtn: 1, + shift: 0, + content: '
                                            \ +
                                            \ +
                                            默认站点\ +
                                            \ + \ +
                                            \ +
                                            \ +
                                            \ +
                                            \ +
                                            ' + }); + },'json'); +} + + +function setSizeClassType(){ + var data = {}; + data['id'] = $('select[name=type_id]').val(); + var ids = []; + $('table').find('td').find('input').each(function(i,obj){ + checked = $(this).prop('checked'); + if (checked) { + ids.push($(this).val()); + } + }); + data['site_ids'] = JSON.stringify(ids); + $.post('/site/set_site_type',data, function(rdata){ + showMsg(rdata.msg,function(){ + if (rdata.status){ + layer.closeAll(); + } + },{icon:rdata.status?1:2}); + },'json'); +} + + +// 尝试重启PHP +function tryRestartPHP(siteName){ + $.post('/site/get_site_php_version','siteName='+siteName,function(data){ + var phpversion = data.phpversion; + + if (phpversion == "00"){ + return; + } + + var php_sign = 'php'; + if (phpversion.indexOf('yum') > -1){ + php_sign = 'php-yum'; + phpversion = phpversion.replace('yum',''); + } + + if (phpversion.indexOf('apt') > -1){ + php_sign = 'php-apt'; + phpversion = phpversion.replace('apt',''); + } + + var reqData = {name: php_sign, func:'restart'} + reqData['version'] = phpversion; + + var loadT = layer.msg('尝试自动重启PHP['+phpversion+']...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', reqData, function(data) { + layer.close(loadT); + layer.msg( 'PHP['+phpversion+']'+(data.status?'重启成功!':'重启失败!'),{icon:data.status?1:2,time:3000,shade: [0.3, '#000']}); + },'json'); + },'json'); +} diff --git a/web/static/app/soft.js b/web/static/app/soft.js new file mode 100755 index 000000000..c2d619f93 --- /dev/null +++ b/web/static/app/soft.js @@ -0,0 +1,589 @@ + + +//重置插件弹出框宽度 +function resetPluginWinWidth(width){ + $("div[id^='layui-layer'][class*='layui-layer-page']").width(width); +} + +//重置插件弹出框宽度 +function resetPluginWinHeight(height){ + $("div[id^='layui-layer'][class*='layui-layer-page']").height(height); + $(".bt-form .bt-w-main").height(height-42); +} + +//软件管理窗口 +function softMain(name, title, version) { + + var _title = title.replace('-'+version,'') + + var loadT = layer.msg("正在处理,请稍后...", { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.get('/plugins/setting?name='+name, function(rdata) { + layer.close(loadT); + layer.open({ + type: 1, + area: '640px', + title: _title + '【' + version + "】管理", + closeBtn: 1, + shift: 0, + content: rdata + }); + $(".bt-w-menu p").click(function() { + $(this).addClass("bgw").siblings().removeClass("bgw"); + }); + + //version to + $(".plugin_version").attr('version',version).hide(); + }); +} + +function renderSoftEmptyState(message) { + return '' + + '
                                            ' + + 'inventory_2' + + '' + message + '' + + '

                                            通过“添加插件”导入软件,或在首页开启展示后,这里会出现软件卡片。

                                            ' + + '
                                            ' + + ''; +} + +//取软件列表 +function getSList(isdisplay) { + var loadT = null; + if (isdisplay !== true) { + loadT = layer.msg('正在获取列表...', { icon: 16, time: 0, shade: [0.3, '#000'] }) + } + if (!isdisplay || isdisplay === true) + isdisplay = getCookie('p' + getCookie('soft_type')); + if (isdisplay == true || isdisplay == 'true') isdisplay = 1; + + var search = $("#SearchValue").val(); + if (search != '') { + search = '&search=' + search; + } + var type = ''; + var istype = getCookie('soft_type'); + if (istype == 'undefined' || istype == 'null' || !istype) { + istype = '0'; + } + + type = '&type=' + istype; + var page = ''; + if (isdisplay) { + page = '&p=' + isdisplay; + setCookie('p' + getCookie('soft_type'), isdisplay); + } + + var condition = (search + type + page).slice(1); + $.get('/plugins/list?' + condition, '', function(rdata) { + if (loadT) { + layer.close(loadT); + } + rdata = rdata || {}; + var softTypes = Array.isArray(rdata.type) ? rdata.type : []; + var softList = Array.isArray(rdata.data) ? rdata.data : []; + var tBody = ''; + var sBody = ''; + var pBody = ''; + + if (!softTypes.length) { + tBody = '全部'; + } + for (var i = 0; i < softTypes.length; i++) { + var c = ''; + if (istype == softTypes[i].type) { + c = 'class="on"'; + } + tBody += '' + softTypes[i].title + ''; + } + + $(".softtype").html(tBody); + $("#softPage").html(rdata.list || ''); + $("#softPage .Pcount").css({ "position": "absolute", "left": "0" }) + + if ($(".task").length) { + $(".task").text(softList.length ? softList[softList.length - 1] : ''); + } + + if (!softList.length) { + $("#softList").html(renderSoftEmptyState('暂无软件插件')); + $(".menu-sub span").click(function() { + setCookie('soft_type', $(this).attr('typeid')); + $(this).addClass("on").siblings().removeClass("on"); + getSList(); + }); + return; + } + + for (var i = 0; i < softList.length; i++) { + var plugin = softList[i] || {}; + if (!plugin.name) { + continue; + } + var pluginVersions = plugin.versions || []; + var len = pluginVersions.length || 0; + var version_info = ''; + var version = ''; + var softPath = ''; + var titleClick = ''; + var state = ''; + var indexshow = ''; + var checked = ''; + + checked = plugin.display ? 'checked' : ''; + + if (typeof(plugin.versions) == "string" ){ + version_info += plugin.versions + '|'; + } else { + for (var j = 0; j < len; j++) { + version_info += pluginVersions[j] + '|'; + } + } + if (version_info != '') { + version_info = version_info.substring(0, version_info.length - 1); + } + + var handle = '安装'; + + if (plugin.setup == true) { + + var mupdate = '';//(plugin.versions[n] == plugin.updates[n]) '' : '更新 | '; + // if (plugin.versions[n] == '') mupdate = ''; + handle = mupdate + '设置 | 卸载'; + titleClick = 'onclick="softMain(\'' + plugin.name + '\',\'' + plugin.title + '\',\'' + plugin.setup_version + '\')" style="cursor:pointer"'; + + softPath = ''; + if (plugin.coexist){ + indexshow = '
                                            \ + \ + \ +
                                            '; + } else { + indexshow = '
                                            \ + \ + \ +
                                            '; + } + + if (plugin.status == true) { + state = '' + } else { + state = '' + } + } + + if (plugin.task == '-2') { + handle = '正在卸载...'; + } else if (plugin.task == '-1') { + handle = '正在安装...'; + } else if (plugin.task == '0') { + handle = '等待中...'; + } + + var plugin_title = plugin.title; + if (plugin.setup && !plugin.coexist){ + plugin_title = plugin.title + ' ' + plugin.setup_version; + } + + icon_link = "/plugins/file?name="+plugin.name+"&f=ico.png"; + if (plugin.icon != ''){ + icon_link = "/plugins/file?name="+plugin.name+"&f="+plugin.icon; + } + + sBody += '' + + ''+ + '' + plugin_title + '' + + '' + plugin.ps + '' + + '' + softPath + '' + + '' + state + '' + + '' + indexshow + '' + + '' + handle + '' + + ''; + } + + sBody += pBody; + + + $("#softList").html(sBody); + $(".menu-sub span").click(function() { + setCookie('soft_type', $(this).attr('typeid')); + $(this).addClass("on").siblings().removeClass("on"); + getSList(); + }); + + loadImage(); + }, 'json').fail(function() { + if (loadT) { + layer.close(loadT); + } + $(".softtype").html('全部'); + $("#softList").html(renderSoftEmptyState('软件列表加载失败')); + $("#softPage").html(''); + }); +} + +function installPreInspection(name, ver, callback){ + var loading = layer.msg('正在检查安装环境...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post("/plugins/run", {'name':name,'version':ver,'func':'install_pre_inspection'}, function(rdata) { + layer.close(loading); + if (rdata.status){ + if (rdata.data == 'ok'){ + callback(); + } else { + layer.msg(rdata.data, { icon: 2 }); + } + } else { + layer.msg(rdata.data, { icon: rdata.status ? 1 : 2 }); + } + },'json'); +} + +function runInstall(data){ + var loadT = layer.msg('正在添加到安装器...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post("/plugins/install", data, function(rdata) { + layer.closeAll(); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + getSList(); + },'json'); +} + +function addVersion(name, ver, type, obj, title, install_pre_inspection) { + var option = ''; + var titlename = title.replace("-"+ver,""); + if (ver.indexOf('|') >= 0){ + var veropt = ver.split("|"); + var selectVersion = ''; + for (var i = 0; i < veropt.length; i++) { + selectVersion += ''; + } + option = ""; + } else { + option = '【' + titlename + '】 ' + ver + ''; + } + + layer.open({ + type: 1, + title: "【"+titlename + "】软件安装", + area: '350px', + closeBtn: 1, + shadeClose: true, + btn: ['提交','关闭'], + content: "
                                            \ +
                                            安装版本:" + option + "
                                            \ +
                                            ", + success:function(){ + $('.fangshi input').click(function() { + $(this).attr('checked', 'checked').parent().siblings().find("input").removeAttr('checked'); + }); + installTips(); + }, + yes:function(index,layero){ + var info = $("#selectVersion").val().toLowerCase(); + if (info == ''){ + info = $("#selectVersion").attr('val').toLowerCase(); + } + var info_split = info.split(' '); + var name = info_split[0]; + var version = info_split[1]; + + var type = $('.fangshi').prop("checked") ? '1' : '0'; + var request_args = "name=" + name + "&version=" + version + "&type=" + type; + + if (install_pre_inspection){ + //安装检查 + installPreInspection(name, version, function(){ + runInstall(request_args); + flySlow('layui-layer-btn0'); + }); + return; + } + + runInstall(request_args); + flySlow('layui-layer-btn0'); + } + }); +} + +//卸载软件 +function uninstallPreInspection(name, title, ver, callback){ + var loading = layer.msg('正在检查卸载环境...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post("/plugins/run", {'name':name,'version':ver,'func':'uninstall_pre_inspection'}, function(rdata) { + layer.close(loading); + if (rdata.status){ + if (rdata.data == 'ok'){ + if (typeof(callback) == 'function'){ + callback(); + } + } else { + layer.msg(rdata.data, { icon: 2 , time: 6666}); + } + } else { + layer.msg(rdata.data, { icon: rdata.status ? 1 : 2, time: 6666 }); + } + },'json'); +} + + +function runUninstallVersion(name, title, version){ + var title = title.replace("-"+version,""); + layer.confirm(msgTpl('您真的要卸载【{1}-{2}】吗?', [title, version]), { icon: 3, closeBtn: 1 }, function() { + var data = 'name=' + name + '&version=' + version; + var loadT = layer.msg('正在处理,请稍候...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/plugins/uninstall', data, function(rdata) { + layer.close(loadT) + getSList(); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + },'json'); + }); +} + + +function uninstallVersion(name, title, version, uninstall_pre_inspection) { + if (uninstall_pre_inspection) { + uninstallPreInspection(name,title,version,function(){ + runUninstallVersion(name,title,version); + }); + } else { + runUninstallVersion(name,title,version); + } +} + + +//首页显示 +function toIndexDisplay(name, version, coexist) { + + var status = $("#index_" + name).prop("checked") ? "0" : "1"; + if (coexist == "true") { + var verinfo = version.replace(/\./, ""); + status = $("#index_" + name + verinfo).prop("checked") ? "0" : "1"; + } + + var data = "name=" + name + "&status=" + status + "&version=" + version; + $.post("/plugins/set_index", data, function(rdata) { + if (rdata.status) { + layer.msg(rdata.msg, { icon: 1 }) + } else { + layer.msg(rdata.msg, { icon: 2 }) + } + },'json'); +} + +function indexListHtml(callback){ + $("#indexsoft").html(''); + + // var loadT = layer.msg('正在获取列表...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.get('/plugins/index_list', function(rdata) { + var plugins = (rdata && Array.isArray(rdata.data)) ? rdata.data : []; + // layer.close(loadT); + $("#indexsoft").html(''); + if (!plugins.length) { + $("#indexsoft").html('
                                            ' + + 'apps' + + '暂无首页软件卡片' + + '

                                            到软件管理里安装插件并开启首页展示后,软件快捷卡片会显示在这里。

                                            ' + + '去软件管理' + + '
                                            '); + if (typeof callback=='function'){ + callback(); + } + return; + } + var con = ''; + for (var i = 0; i < plugins.length; i++) { + var plugin = plugins[i] || {}; + if (!plugin.name) { + continue; + } + var len = plugin.versions ? plugin.versions.length : 0; + var version_info = ''; + + if (typeof plugin.versions == "string"){ + version_info += plugin.versions + '|'; + } else { + for (var j = 0; j < len; j++) { + version_info += plugin.versions[j] + '|'; + } + } + if (version_info != '') { + version_info = version_info.substring(0, version_info.length - 1); + } + + var versionLabel = plugin.setup_version; + if (!versionLabel) { + if (Array.isArray(plugin.versions) && plugin.versions.length > 0) { + versionLabel = plugin.versions[0]; + } else if (plugin.versions) { + versionLabel = plugin.versions; + } else { + versionLabel = '未安装'; + } + } + var softVersion = plugin.setup_version || versionLabel; + var pluginSummary = plugin.ps ? plugin.ps : '点击进入软件设置页'; + var stateText = '已停止'; + var stateClass = 'mw-soft-home-state--off'; + if (plugin.status == true) { + stateText = '运行中'; + stateClass = 'mw-soft-home-state--ok'; + } + + var data_id = plugin.name + '-' + softVersion; + if (plugin.coexist) { + data_id = plugin.name + '-' + (Array.isArray(plugin.versions) ? plugin.versions.join('-') : plugin.versions); + } + + con += '
                                            \ + \ +
                                            \ +
                                            \ +
                                            \ +
                                            \ +
                                            ' + escapeHtml(plugin.title) + '
                                            \ +
                                            ' + escapeHtml(versionLabel) + '
                                            \ +
                                            ' + escapeHtml(pluginSummary) + '
                                            \ +
                                            \ +
                                            \ + \ +
                                            \ +
                                            '; + + // loadImage(); + } + + $("#indexsoft").html(con); + + if (typeof callback=='function'){ + callback(); + } + },'json').fail(function() { + $("#indexsoft").html('
                                            ' + + 'warning' + + '软件卡片加载失败' + + '

                                            请检查插件接口是否可用,稍后重试。

                                            ' + + '
                                            '); + if (typeof callback=='function'){ + callback(); + } + }); +} + + +//首页软件列表 +function indexSoft() { + indexListHtml(function(){ + if ($("#indexsoft > div[data-id]").length > 1) { + $("#indexsoft").dragsort({ + dragSelector: ".spanmove", + dragBetween: true, + dragEnd: saveOrder, + placeHolderTemplate: "
                                            " + }); + } + }); + + function saveOrder() { + var data = $("#indexsoft > div").map(function() { return $(this).attr("data-id"); }).get(); + tmp = []; + for(i in data){ + // console.log(data[i]); + if (data[i] != ''){ + tmp.push($.trim(data[i])); + } + } + var ssort = tmp.join("|"); + $("input[name=list1SortOrder]").val(ssort); + $.post("/plugins/index_sort", 'ssort=' + ssort, function(rdata) { + if (!rdata.status){ + showMsg('设置失败:'+ rdata.msg, function(){ + indexListHtml(); + }, { icon: 16, time: 0, shade: [0.3, '#000'] }); + } + },'json'); + }; +} + + +function importPluginOpen(){ + $("#update_zip").on("change", function () { + var files = $("#update_zip")[0].files; + if (files.length == 0) { + return; + } + importPlugin(files[0]); + $("#update_zip").val('') + }); + $("#update_zip").click(); +} + + +function importPlugin(file){ + var formData = new FormData(); + formData.append("plugin_zip", file); + $.ajax({ + url: "/plugins/update_zip", + type: "POST", + data: formData, + processData: false, + dataType:'json', + contentType: false, + success: function (data) { + if (data.status === false) { + layer.msg(data.msg, { icon: 2 }); + return; + } + var loadT = layer.open({ + type: 1, + area: "500px", + title: "安装第三方插件包", + closeBtn: 1, + shift: 5, + shadeClose: false, + content: '\ +
                                            \ + \ +
                                              \ +
                                            • 此为第三方开发的插件,无法验证其可靠性!
                                            • \ +
                                            • 安装过程可能需要几分钟时间,请耐心等候!
                                            • \ +
                                            • 如果已存在此插件,将被替换!
                                            • \ +
                                            \ +
                                            \ +
                                            ' + }); + + },error: function (responseStr) { + layer.msg('上传失败2!:' + responseStr, { icon: 2 }); + } + }); +} + + +function importPluginInstall(plugin_name, tmp_path) { + layer.msg('正在安装,这可能需要几分钟时间...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/plugins/input_zip', { plugin_name: plugin_name, tmp_path: tmp_path }, function (rdata) { + layer.closeAll() + if (rdata.status) { + getSList(true); + } + setTimeout(function () { layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }) }, 1000); + },'json'); +} + +$(function() { + if (window.document.location.pathname == '/soft/') { + setInterval(function() { getSList(); }, 8000); + } +}); diff --git a/web/static/app/theme.js b/web/static/app/theme.js new file mode 100644 index 000000000..253765cde --- /dev/null +++ b/web/static/app/theme.js @@ -0,0 +1,414 @@ +(function () { + var COLOR_KEY = 'mw-color-scheme'; + var MODE_KEY = 'mw-theme-mode'; + var BACKGROUND_KEY = 'mw-theme-background'; + var BACKGROUND_KIND_KEY = 'mw-theme-background-kind'; + var BACKGROUND_SOURCE_KEY = 'mw-theme-background-source'; + var BACKGROUND_MODE_KEY = 'mw-theme-background-mode'; + var BACKGROUND_OPACITY_KEY = 'mw-theme-background-opacity'; + var DEFAULT_COLOR = '#6750a4'; + var DEFAULT_MODE = 'auto'; + var DEFAULT_BACKGROUND_OPACITY = 72; + var THEME_CLASSES = ['mdui-theme-light', 'mdui-theme-dark', 'mdui-theme-auto']; + var MEMORY_STORE = {}; + + function getRoot() { + return document.documentElement; + } + + function normalizeMode(mode) { + if (mode === 'light' || mode === 'dark' || mode === 'auto') { + return mode; + } + return DEFAULT_MODE; + } + + function safeGet(key) { + if (Object.prototype.hasOwnProperty.call(MEMORY_STORE, key)) { + return MEMORY_STORE[key]; + } + try { + var value = window.localStorage.getItem(key); + if (value !== null) { + return value; + } + } catch (error) { + } + return null; + } + + function safeSet(key, value) { + try { + window.localStorage.setItem(key, value); + } catch (error) {} + MEMORY_STORE[key] = value; + } + + function safeRemove(key) { + try { + window.localStorage.removeItem(key); + } catch (error) {} + MEMORY_STORE[key] = null; + } + + function escapeCssUrl(value) { + return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + } + + function normalizeOpacity(value, fallback) { + var parsed = parseInt(value, 10); + if (isNaN(parsed)) { + parsed = typeof fallback === 'number' ? fallback : DEFAULT_BACKGROUND_OPACITY; + } + if (parsed < 0) { + return 0; + } + if (parsed > 100) { + return 100; + } + return parsed; + } + + function hexToRgbTriplet(color) { + if (!color) { + return null; + } + + var value = String(color).trim().replace(/^#/, ''); + if (value.length === 3) { + value = value.split('').map(function (ch) { + return ch + ch; + }).join(''); + } + + if (value.length !== 6) { + return null; + } + + var red = parseInt(value.slice(0, 2), 16); + var green = parseInt(value.slice(2, 4), 16); + var blue = parseInt(value.slice(4, 6), 16); + if (isNaN(red) || isNaN(green) || isNaN(blue)) { + return null; + } + + return red + ', ' + green + ', ' + blue; + } + + function normalizeBackgroundValue(value) { + if (!value) { + return ''; + } + + var trimmed = String(value).trim(); + if (!trimmed) { + return ''; + } + + if (trimmed.toLowerCase() === 'none') { + return ''; + } + + if (/^(url|linear-gradient|radial-gradient|repeating-linear-gradient|repeating-radial-gradient)\(/i.test(trimmed)) { + return trimmed; + } + + return 'url("' + escapeCssUrl(trimmed) + '")'; + } + + function getBackgroundOpacity() { + return normalizeOpacity(safeGet(BACKGROUND_OPACITY_KEY), DEFAULT_BACKGROUND_OPACITY); + } + + function setBackgroundOpacity(value) { + var next = normalizeOpacity(value, getBackgroundOpacity()); + safeSet(BACKGROUND_OPACITY_KEY, String(next)); + applyBackgroundAppearance(); + return next; + } + + function isDarkAppearance() { + var root = getRoot(); + if (root.classList.contains('mdui-theme-dark')) { + return true; + } + if (root.classList.contains('mdui-theme-light')) { + return false; + } + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + return true; + } + return false; + } + + function buildBackgroundOverlay() { + var opacity = getBackgroundOpacity() / 100; + if (isDarkAppearance()) { + var darkStart = 0.54 + (0.26 * opacity); + var darkMiddle = Math.min(0.96, darkStart + 0.04); + var darkEnd = Math.min(0.98, darkMiddle + 0.03); + return [ + 'radial-gradient(circle at 14% 16%, color-mix(in srgb, var(--mw-primary) 18%, transparent) 0%, transparent 30%)', + 'radial-gradient(circle at 84% 12%, rgba(45, 212, 191, 0.10) 0%, transparent 28%)', + 'radial-gradient(circle at 50% 100%, rgba(59, 130, 246, 0.06) 0%, transparent 40%)', + 'linear-gradient(180deg, rgba(6, 10, 18, ' + darkStart.toFixed(2) + ') 0%, rgba(10, 14, 23, ' + darkMiddle.toFixed(2) + ') 55%, rgba(6, 9, 15, ' + darkEnd.toFixed(2) + ') 100%)' + ].join(', '); + } + + var lightStart = 0.20 + (0.46 * opacity); + var lightMiddle = Math.min(0.92, lightStart + 0.04); + var lightEnd = Math.min(0.96, lightMiddle + 0.03); + return [ + 'radial-gradient(circle at 12% 12%, rgba(15, 118, 110, 0.08) 0%, transparent 28%)', + 'radial-gradient(circle at 86% 8%, rgba(59, 130, 246, 0.06) 0%, transparent 30%)', + 'linear-gradient(180deg, rgba(248, 250, 252, ' + lightStart.toFixed(2) + ') 0%, rgba(238, 243, 248, ' + lightMiddle.toFixed(2) + ') 100%)' + ].join(', '); + } + + function applyBackgroundAppearance() { + var root = getRoot(); + var backgroundMode = getBackgroundMode(); + + if (backgroundMode === 'plain') { + root.style.setProperty('--mw-app-background-overlay', 'none'); + return; + } + + if (!safeGet(BACKGROUND_KEY)) { + root.style.removeProperty('--mw-app-background-overlay'); + return; + } + + root.style.setProperty('--mw-app-background-overlay', buildBackgroundOverlay()); + } + + function applyColorScheme(color) { + if (!color) { + return; + } + + if (window.mdui && typeof window.mdui.setColorScheme === 'function') { + window.mdui.setColorScheme(color); + } + + getRoot().style.setProperty('--mw-theme-primary', color); + var rgb = hexToRgbTriplet(color); + if (rgb) { + getRoot().style.setProperty('--mw-theme-primary-rgb', rgb); + } + } + + function resetColorScheme() { + if (window.mdui && typeof window.mdui.removeColorScheme === 'function') { + window.mdui.removeColorScheme(); + } + + getRoot().style.removeProperty('--mw-theme-primary'); + getRoot().style.removeProperty('--mw-theme-primary-rgb'); + } + + function applyMode(mode) { + var nextMode = normalizeMode(mode); + var root = getRoot(); + + if (window.mdui && typeof window.mdui.setTheme === 'function') { + window.mdui.setTheme(nextMode, root); + return; + } + + for (var i = 0; i < THEME_CLASSES.length; i++) { + root.classList.remove(THEME_CLASSES[i]); + } + root.classList.add('mdui-theme-' + nextMode); + applyBackgroundAppearance(); + } + + function clearBackgroundValue() { + safeRemove(BACKGROUND_KEY); + safeRemove(BACKGROUND_KIND_KEY); + safeRemove(BACKGROUND_SOURCE_KEY); + getRoot().style.removeProperty('--mw-app-background-image'); + } + + function applyBackgroundMode(mode) { + var nextMode = mode === 'plain' ? 'plain' : 'default'; + safeSet(BACKGROUND_MODE_KEY, nextMode); + applyBackgroundAppearance(); + } + + function getBackgroundMode() { + return safeGet(BACKGROUND_MODE_KEY) || 'default'; + } + + function setBackgroundMode(mode) { + var nextMode = mode === 'plain' ? 'plain' : 'default'; + safeSet(BACKGROUND_MODE_KEY, nextMode); + applyBackgroundAppearance(); + } + + function applyBackgroundValue(background) { + var value = normalizeBackgroundValue(background); + if (!value) { + clearBackgroundValue(); + applyBackgroundAppearance(); + return; + } + + safeSet(BACKGROUND_KEY, value); + getRoot().style.setProperty('--mw-app-background-image', value); + applyBackgroundAppearance(); + } + + function setBackground(background, meta) { + var value = normalizeBackgroundValue(background); + if (!value) { + clearBackgroundValue(); + return; + } + + safeSet(BACKGROUND_KEY, value); + safeSet(BACKGROUND_MODE_KEY, 'default'); + safeSet(BACKGROUND_KIND_KEY, meta && meta.kind ? meta.kind : 'custom'); + if (meta && meta.source) { + safeSet(BACKGROUND_SOURCE_KEY, meta.source); + } else { + safeRemove(BACKGROUND_SOURCE_KEY); + } + getRoot().style.setProperty('--mw-app-background-image', value); + applyBackgroundAppearance(); + } + + function setBackgroundUrl(url) { + if (!url) { + return; + } + + setBackground(url, { + kind: 'url', + source: url + }); + } + + function setBackgroundFile(file) { + return new Promise(function (resolve, reject) { + if (!file) { + resolve(null); + return; + } + + var reader = new FileReader(); + reader.onload = function () { + var result = reader.result || ''; + setBackground(result, { + kind: 'upload', + source: file.name || 'upload' + }); + resolve({ + value: result, + kind: 'upload', + source: file.name || 'upload' + }); + }; + reader.onerror = function () { + reject(reader.error || new Error('failed to read background file')); + }; + reader.readAsDataURL(file); + }); + } + + function clearBackground() { + setBackgroundMode('plain'); + clearBackgroundValue(); + } + + function setDefaultBackground() { + clearBackgroundValue(); + setBackgroundMode('default'); + } + + function getMode() { + return normalizeMode(safeGet(MODE_KEY)); + } + + function setMode(mode) { + var nextMode = normalizeMode(mode); + safeSet(MODE_KEY, nextMode); + applyMode(nextMode); + } + + function getBackgroundInfo() { + return { + value: safeGet(BACKGROUND_KEY) || '', + kind: safeGet(BACKGROUND_KIND_KEY) || 'default', + source: safeGet(BACKGROUND_SOURCE_KEY) || '', + mode: getBackgroundMode(), + opacity: getBackgroundOpacity() + }; + } + + function initTheme() { + var storedColor = safeGet(COLOR_KEY); + var storedMode = normalizeMode(safeGet(MODE_KEY)); + var storedBackground = safeGet(BACKGROUND_KEY); + var storedBackgroundMode = getBackgroundMode(); + + applyMode(storedMode); + applyBackgroundMode(storedBackgroundMode); + + if (storedColor) { + applyColorScheme(storedColor); + } + + if (storedBackground) { + getRoot().style.setProperty('--mw-app-background-image', storedBackground); + if (!safeGet(BACKGROUND_KIND_KEY)) { + safeSet(BACKGROUND_KIND_KEY, 'custom'); + } + } + + applyBackgroundAppearance(); + } + + window.MWTheme = { + init: initTheme, + applyColor: function (color) { + if (!color) { + return; + } + safeSet(COLOR_KEY, color); + applyColorScheme(color); + }, + reset: function () { + safeRemove(COLOR_KEY); + resetColorScheme(); + }, + getStoredColor: function () { + return safeGet(COLOR_KEY); + }, + getColor: function () { + return safeGet(COLOR_KEY) || DEFAULT_COLOR; + }, + setMode: setMode, + getMode: getMode, + setBackground: setBackground, + setBackgroundUrl: setBackgroundUrl, + setBackgroundFile: setBackgroundFile, + setDefaultBackground: setDefaultBackground, + clearBackground: clearBackground, + applyBackground: applyBackgroundValue, + getBackground: function () { + return safeGet(BACKGROUND_KEY); + }, + getBackgroundInfo: getBackgroundInfo, + getBackgroundMode: getBackgroundMode, + setBackgroundMode: setBackgroundMode, + getBackgroundOpacity: getBackgroundOpacity, + setBackgroundOpacity: setBackgroundOpacity, + getBackgroundKind: function () { + return safeGet(BACKGROUND_KIND_KEY) || 'default'; + }, + getBackgroundSource: function () { + return safeGet(BACKGROUND_SOURCE_KEY) || ''; + } + }; +})(); diff --git a/web/static/app/upload.js b/web/static/app/upload.js new file mode 100755 index 000000000..cdeaddd4b --- /dev/null +++ b/web/static/app/upload.js @@ -0,0 +1,238 @@ +function MyAjax() { + this.serverdata = this.erromsg = this.timeout = this.stop = this.xmlhttp = !1, this.transit = !0 +} +MyAjax.prototype.handle = function(d, f) { + if(4 == d.readyState) { + if(this.stop === !0) { + return + } + if(this.transit = !0, f.timeout && f.async && (clearTimeout(this.timeout), this.timeout = !1), 200 == d.status) { + var e = this.serverdata = d.responseText.replace(/(^\s*)|(\s*$)/g, ""); + "function" == typeof f.success && f.success(e) + } else { + this.erromsg = d.status, "function" == typeof f.error && f.error(d.status) + } + } else { + if(0 == d.readyState) { + if(this.stop === !0) { + return + } + f.timeout && f.async && (clearTimeout(this.timeout), this.timeout = !1), this.erromsg = d.readyState, this.transit = !0, "function" == typeof f.error && f.error(d.readyState) + } + } +}, MyAjax.prototype.out = function(b) { + this.transit = !0, this.erromsg = 504, this.stop = !0, "function" == typeof b.error && b.error(504) +}, MyAjax.prototype.carry = function(j) { + var i, h, g, l; + if(j.lock && !this.transit) { + return !1 + } + this.transit = !1, this.stop = this.erromsg = !1, i = window.XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject("Microsoft.XMLHTTP"), j.type = j.type.toUpperCase(), h = function() {}, "string" == typeof j.data ? (j.data = j.data.replace(/(^\s*)|(\s*$)/g, ""), h = function() { + i.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") + }) : "[object FormData]" !== Object.prototype.toString.call(j.data) ? (j.data = "", h = function() { + i.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") + }) : ("function" == typeof j.progress && i.upload.addEventListener("progress", j.progress, !1), j.type = "POST"), g = "" == j.data ? [null, ""] : [j.data, "?" + j.data], l = this, "function" == typeof j.complete && j.complete(), j.timeout && j.async && (this.timeout = setTimeout(function() { + l.out(j) + }, j.timeout)), j.async === !0 && (i.onreadystatechange = function() { + l.handle(i, j) + }); + try { + switch(j.type) { + case "POST": + i.open("POST", j.url, j.async), h(); + break; + default: + i.open("GET", j.url + g[1], j.async), j.cache === !0 || i.setRequestHeader("If-Modified-Since", "0") + } + } catch(k) { + return this.erromsg = 505, j.timeout && j.async && (clearTimeout(this.timeout), this.timeout = !1), this.transit = !0, "function" == typeof j.error && j.error(505), void 0 + } + i.send(g[0]), j.async === !1 && l.handle(i, j) +}; + +function uploadStart(d) { + var a = function(e) { + this.uptype = e.UpType; + this.url = e.url; + this.oldurl = e.url; + this.filessize = e.FilesSize, this.MaxUpNum = e.MaxUpNum, this.str = new MyAjax(); + this.file_input = document.getElementById("file_input"); + this.opt = document.getElementById("opt"); + this.up = document.getElementById("up"); + this.up_box = document.getElementById("up_box"); + this.filesalllength = this.FilesArrayLength = this.up_box_li = FileProgress = 0; + this.FilesArray = new Array(); + this.num = 0 + }; + a.prototype = { + SelectFile: function() { + if(this.FilesArrayLength === 0) { + this.up_box.innerHTML = ""; + this.un() + } + var h = this.file_input.files, + e, g, f = h.length; + if(this.filesalllength + f > this.MaxUpNum) { + f = this.MaxUpNum - this.filesalllength; + layer.msg(lan.get('update_num',[this.MaxUpNum]), { + icon: 5 + }) + } + for(var j = 0; j < f; j++) { + e = h[j]; + g = e.name.split("."); + g = Object.prototype.toString.call(g) === "[object Array]" ? g[g.length - 1] : ""; + if(d) { + if(g != "sql" && g != "zip" && g != "gz" && g != "tgz") { + layer.msg(lan.upload.file_type_err, { + icon: 5 + }); + return + } + } + if(!e) { + this.up_box.insertAdjacentHTML("beforeEnd", "
                                          • " + e.name + ""+lan.upload.file_err+"
                                          • ") + } else { + if(this.uptype.length > 0 && this.uptype.indexOf(g.toLowerCase()) === -1) { + this.up_box.insertAdjacentHTML("beforeEnd", "
                                          • " + e.name + ""+lan.upload.file_type_err+"
                                          • ") + } else { + if(e.size <= 0) { + this.up_box.insertAdjacentHTML("beforeEnd", "
                                          • " + e.name + ""+lan.upload.file_err_empty+"
                                          • ") + } else { + this.up_box.insertAdjacentHTML("beforeEnd", "
                                          • " + e.name + "" + (toSize(e.size)) + ""+lan.upload.up_sleep+"
                                          • "); + this.FilesArray.push([e, (this.filesalllength - 1 < 0 ? 0 : this.filesalllength) + j]) + } + } + } + } + this.filesalllength += f; + this.FilesArrayLength = this.FilesArray.length + }, + read: function() { + if(this.filesalllength == 0) { + layer.msg(lan.upload.select_file, { + icon: 5 + }); + return + } + this.url = this.oldurl + "&codeing=" + document.getElementById("fileCodeing").value; + if(this.FilesArrayLength > 0) { + this.opt.disabled = true; + this.up.disabled = true; + this.file_input.disabled = true; + this.up_box_li = this.up_box.getElementsByTagName("li"); + this.ready(this.FilesArray, 0, this.FilesArrayLength - 1) + } else { + layer.msg(an.upload.select_empty, { + icon: 5 + }) + } + }, + un: function() { + this.opt.disabled = this.up.disabled = this.file_input.disabled; + this.filesalllength = this.FilesArrayLength = this.up_box_li = 0; + this.FilesArray = new Array() + }, + ready: function(i, f, h) { + if(f > h) { + this.un(); + return + } + try { + var g = new FormData(); + g.append("zunfile", i[f][0]) + } catch(e) { + this.opt.disabled = true; + this.up.disabled = true; + this.file_input.disabled = true; + layer.msg(lan.upload.ie_err, { + icon: 5 + }) + } + if(i.length > 1) { + $("#totalProgress").html("

                                            "+lan.upload.up_the+ this.num + "/" + i.length + "

                                            "); + $(".cancel").css("visibility", "hidden") + } + this.send(g, i, f, h) + }, + SetTxt: function(f, g, h) { + var e = this.up_box_li[f].getElementsByTagName("em")[0]; + e.style.color = h; + e.innerHTML = g + }, + send: function(f, i, e, g) { + if(!this.up_box_li[e].getElementsByTagName("em")[0]) { + this.ready(i, e + 1, g); + this.num++; + return + } + var h = this; + this.FileProgress = 0; + this.str.carry({ + url: this.url, + data: f, + type: "get", + timeout: 86400000, + async: true, + lock: true, + complete: false, + progress: function(j) { + h.FileProgress = Math.floor(j.loaded / j.total * 100) + "%"; + if(h.FileProgress == "100%") { + h.FileProgress = '正在保存..'; + } + h.SetTxt(i[e][1], '上传进度:' + h.FileProgress, "#005100") + }, + success: function(j) { + h.str.serverdata = false; + h.SetTxt(i[e][1], '已上传成功', "#005100"); + h.ready(i, e + 1, g); + h.num++; + if(i.length > 1) { + var k = (h.num == i.length) ? '上传完成': '已上传'; + $("#totalProgress").html("

                                            " + k + h.num + "/" + i.length + "

                                            ") + } + if(h.num == i.length) { + c.opt.disabled = false; + c.up.disabled = false; + c.file_input.disabled = false; + h.num = 0 + } + if(!d) { + getFiles(getCookie("open_dir_path")); + } else{ + d(); + } + }, + error: function(j) { + h.SetTxt(i[e][1], lan.upload.up_err, "red"); + h.str.serverdata = false; + h.ready(i, e + 1, g) + }, + cache: false + }) + } + }; + try { + var c = new a({ + UpType: new Array(), + FilesSize: 5242880000, + MaxUpNum: 100, + url: "/files/upload_file?path=" + encodeURIComponent(document.getElementById("input-val").value) + }); + c.opt.addEventListener("click", function() { + c.file_input.click() + }, false); + c.up.addEventListener("click", function() { + c.read(); + }, false); + c.file_input.addEventListener("change", function() { + c.SelectFile(); + }, false) + } catch(b) { + c.opt.disabled = true; + c.up.disabled = true; + c.file_input.disabled = true; + layer.msg('抱歉,IE 6/7/8 不支持请更换浏览器再上传', {icon: 5}); + } +}; \ No newline at end of file diff --git a/web/static/bootstrap-3.3.5/css/bootstrap-theme.min.css b/web/static/bootstrap-3.3.5/css/bootstrap-theme.min.css new file mode 100755 index 000000000..61358b13d --- /dev/null +++ b/web/static/bootstrap-3.3.5/css/bootstrap-theme.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} \ No newline at end of file diff --git a/web/static/bootstrap-3.3.5/css/bootstrap.min.css b/web/static/bootstrap-3.3.5/css/bootstrap.min.css new file mode 100755 index 000000000..0336abcd4 --- /dev/null +++ b/web/static/bootstrap-3.3.5/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before,.glyphicon-True:before{content:"\e072"}.glyphicon-pause:before,.glyphicon-False:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:none}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:none}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:none}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#555;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#fff;background-color:#10952a;border-color:#398439}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#fff;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#20a53a;border-color:#20a53a}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#10952a;border-color:#255625}.btn-success:hover{color:#fff;background-color:#10952a;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#10952a;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#20a53a;border-color:#20a53a}.btn-success .badge{color:#20a53a;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#20a53a}.label-success[href]:focus,.label-success[href]:hover{background-color:#10952a}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#20a53a}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/web/static/bootstrap-3.3.5/css/index.html b/web/static/bootstrap-3.3.5/css/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/bootstrap-3.3.5/css/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.eot b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.eot new file mode 100755 index 000000000..b93a4953f Binary files /dev/null and b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.eot differ diff --git a/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.svg b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.svg new file mode 100755 index 000000000..f9635d9c1 --- /dev/null +++ b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.ttf b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.ttf new file mode 100755 index 000000000..1413fc609 Binary files /dev/null and b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.ttf differ diff --git a/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff new file mode 100755 index 000000000..9e612858f Binary files /dev/null and b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff differ diff --git a/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff2 b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff2 new file mode 100755 index 000000000..64539b54c Binary files /dev/null and b/web/static/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/web/static/bootstrap-3.3.5/fonts/index.html b/web/static/bootstrap-3.3.5/fonts/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/bootstrap-3.3.5/fonts/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/bootstrap-3.3.5/index.html b/web/static/bootstrap-3.3.5/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/bootstrap-3.3.5/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/build/addons/attach/attach.js b/web/static/build/addons/attach/attach.js new file mode 100755 index 000000000..847cb62e8 --- /dev/null +++ b/web/static/build/addons/attach/attach.js @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + * + * Implements the attach method, that attaches the terminal to a WebSocket stream. + */ + +(function (attach) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = attach(require('../../Terminal').Terminal); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../xterm'], attach); + } else { + /* + * Plain browser environment + */ + attach(window.Terminal); + } +})(function (Terminal) { + 'use strict'; + + var exports = {}; + + /** + * Attaches the given terminal to the given socket. + * + * @param {Terminal} term - The terminal to be attached to the given socket. + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + exports.attach = function (term, socket, bidirectional, buffered) { + bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; + term.socket = socket; + + term._flushBuffer = function () { + term.write(term._attachSocketBuffer); + term._attachSocketBuffer = null; + }; + + term._pushToBuffer = function (data) { + if (term._attachSocketBuffer) { + term._attachSocketBuffer += data; + } else { + term._attachSocketBuffer = data; + setTimeout(term._flushBuffer, 10); + } + }; + + var myTextDecoder; + + term._getMessage = function (ev) { + var str; + if (typeof ev.data === "object") { + if (ev.data instanceof ArrayBuffer) { + if (!myTextDecoder) { + myTextDecoder = new TextDecoder(); + } + + str = myTextDecoder.decode( ev.data ); + } + else { + throw "TODO: handle Blob?"; + } + } + + if (buffered) { + term._pushToBuffer(str || ev.data); + } else { + term.write(str || ev.data); + } + }; + + term._sendData = function (data) { + socket.send(data); + }; + + socket.addEventListener('message', term._getMessage); + + if (bidirectional) { + term.on('data', term._sendData); + } + + socket.addEventListener('close', term.detach.bind(term, socket)); + socket.addEventListener('error', term.detach.bind(term, socket)); + }; + + /** + * Detaches the given terminal from the given socket + * + * @param {Terminal} term - The terminal to be detached from the given socket. + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + exports.detach = function (term, socket) { + term.off('data', term._sendData); + + socket = (typeof socket == 'undefined') ? term.socket : socket; + + if (socket) { + socket.removeEventListener('message', term._getMessage); + } + + delete term.socket; + }; + + /** + * Attaches the current terminal to the given socket + * + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + Terminal.prototype.attach = function (socket, bidirectional, buffered) { + return exports.attach(this, socket, bidirectional, buffered); + }; + + /** + * Detaches the current terminal from the given socket. + * + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + Terminal.prototype.detach = function (socket) { + return exports.detach(this, socket); + }; + + return exports; +}); diff --git a/web/static/build/addons/attach/index.html b/web/static/build/addons/attach/index.html new file mode 100755 index 000000000..4a41fbde2 --- /dev/null +++ b/web/static/build/addons/attach/index.html @@ -0,0 +1,93 @@ + + + + + + + + + + +
                                            + +

                                            + xterm.js: socket attach +

                                            +

                                            + Attach the terminal to a WebSocket terminal stream with ease. Perfect for attaching to your + Docker containers. +

                                            +

                                            + Socket information +

                                            +
                                            + + +
                                            +
                                            + +
                                            + + + \ No newline at end of file diff --git a/web/static/build/addons/attach/package.json b/web/static/build/addons/attach/package.json new file mode 100755 index 000000000..e8f6b2ca2 --- /dev/null +++ b/web/static/build/addons/attach/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.attach", + "main": "attach.js", + "private": true +} diff --git a/web/static/build/addons/fit/fit.js b/web/static/build/addons/fit/fit.js new file mode 100755 index 000000000..01e0c2f21 --- /dev/null +++ b/web/static/build/addons/fit/fit.js @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + * + * Fit terminal columns and rows to the dimensions of its DOM element. + * + * ## Approach + * + * Rows: Truncate the division of the terminal parent element height by the + * terminal row height. + * Columns: Truncate the division of the terminal parent element width by the + * terminal character width (apply display: inline at the terminal + * row and truncate its width with the current number of columns). + */ + +(function (fit) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = fit(require('../../Terminal').Terminal); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../xterm'], fit); + } else { + /* + * Plain browser environment + */ + fit(window.Terminal); + } +})(function (Terminal) { + var exports = {}; + + exports.proposeGeometry = function (term) { + if (!term.element.parentElement) { + return null; + } + var parentElementStyle = window.getComputedStyle(term.element.parentElement); + var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')); + var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17); + var elementStyle = window.getComputedStyle(term.element); + var elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')); + var elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')); + var availableHeight = parentElementHeight - elementPaddingVer; + var availableWidth = parentElementWidth - elementPaddingHor; + var geometry = { + cols: Math.floor(availableWidth / term.charMeasure.width), + rows: Math.floor(availableHeight / Math.floor(term.charMeasure.height * term.getOption('lineHeight'))) + }; + + return geometry; + }; + + exports.fit = function (term) { + // Wrap fit in a setTimeout as charMeasure needs time to get initialized + // after calling Terminal.open + setTimeout(function () { + var geometry = exports.proposeGeometry(term); + + if (geometry) { + // Force a full render + if (term.rows !== geometry.rows || term.cols !== geometry.cols) { + term.renderer.clear(); + term.resize(geometry.cols, geometry.rows); + } + } + }, 0); + }; + + Terminal.prototype.proposeGeometry = function () { + return exports.proposeGeometry(this); + }; + + Terminal.prototype.fit = function () { + return exports.fit(this); + }; + + return exports; +}); diff --git a/web/static/build/addons/fit/package.json b/web/static/build/addons/fit/package.json new file mode 100755 index 000000000..aec1bbd6d --- /dev/null +++ b/web/static/build/addons/fit/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.fit", + "main": "fit.js", + "private": true +} diff --git a/web/static/build/addons/fullscreen/fullscreen.css b/web/static/build/addons/fullscreen/fullscreen.css new file mode 100755 index 000000000..37229932f --- /dev/null +++ b/web/static/build/addons/fullscreen/fullscreen.css @@ -0,0 +1,10 @@ +.xterm.fullscreen { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: auto; + height: auto; + z-index: 255; +} diff --git a/web/static/build/addons/fullscreen/fullscreen.js b/web/static/build/addons/fullscreen/fullscreen.js new file mode 100755 index 000000000..f1dc56a26 --- /dev/null +++ b/web/static/build/addons/fullscreen/fullscreen.js @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * @license MIT + */ + +(function (fullscreen) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = fullscreen(require('../../Terminal').Terminal); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../xterm'], fullscreen); + } else { + /* + * Plain browser environment + */ + fullscreen(window.Terminal); + } +})(function (Terminal) { + var exports = {}; + + /** + * Toggle the given terminal's fullscreen mode. + * @param {Terminal} term - The terminal to toggle full screen mode + * @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false) + */ + exports.toggleFullScreen = function (term, fullscreen) { + var fn; + + if (typeof fullscreen == 'undefined') { + fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add'; + } else if (!fullscreen) { + fn = 'remove'; + } else { + fn = 'add'; + } + + term.element.classList[fn]('fullscreen'); + }; + + Terminal.prototype.toggleFullscreen = function (fullscreen) { + exports.toggleFullScreen(this, fullscreen); + }; + + return exports; +}); diff --git a/web/static/build/addons/fullscreen/package.json b/web/static/build/addons/fullscreen/package.json new file mode 100755 index 000000000..9c359a052 --- /dev/null +++ b/web/static/build/addons/fullscreen/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.fullscreen", + "main": "fullscreen.js", + "private": true +} diff --git a/web/static/build/addons/search/search.js b/web/static/build/addons/search/search.js new file mode 100755 index 000000000..088e28347 --- /dev/null +++ b/web/static/build/addons/search/search.js @@ -0,0 +1,116 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0; y--) { + result = this._findInLine(term, y); + if (result) { + break; + } + } + if (!result) { + for (var y = this._terminal.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) { + result = this._findInLine(term, y); + if (result) { + break; + } + } + } + return this._selectResult(result); + }; + SearchHelper.prototype._findInLine = function (term, y) { + var lowerStringLine = this._terminal.buffer.translateBufferLineToString(y, true).toLowerCase(); + var lowerTerm = term.toLowerCase(); + var searchIndex = lowerStringLine.indexOf(lowerTerm); + if (searchIndex >= 0) { + return { + term: term, + col: searchIndex, + row: y + }; + } + }; + SearchHelper.prototype._selectResult = function (result) { + if (!result) { + return false; + } + this._terminal.selectionManager.setSelection(result.col, result.row, result.term.length); + this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp, false); + return true; + }; + return SearchHelper; +}()); +exports.SearchHelper = SearchHelper; + + + +},{}],2:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SearchHelper_1 = require("./SearchHelper"); +(function (addon) { + if (typeof window !== 'undefined' && 'Terminal' in window) { + addon(window.Terminal); + } + else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = addon(require('../../Terminal').Terminal); + } + else if (typeof define === 'function') { + define(['../../xterm'], addon); + } +})(function (Terminal) { + Terminal.prototype.findNext = function (term) { + if (!this._searchHelper) { + this.searchHelper = new SearchHelper_1.SearchHelper(this); + } + return this.searchHelper.findNext(term); + }; + Terminal.prototype.findPrevious = function (term) { + if (!this._searchHelper) { + this.searchHelper = new SearchHelper_1.SearchHelper(this); + } + return this.searchHelper.findPrevious(term); + }; +}); + + + +},{"../../Terminal":undefined,"./SearchHelper":1}]},{},[2]) +//# sourceMappingURL=search.js.map diff --git a/web/static/build/addons/search/search.js.map b/web/static/build/addons/search/search.js.map new file mode 100755 index 000000000..baa06e886 --- /dev/null +++ b/web/static/build/addons/search/search.js.map @@ -0,0 +1 @@ +{"version":3,"file":"search.js","sources":["../../../src/addons/search/search.ts","../../../src/addons/search/SearchHelper.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { SearchHelper } from './SearchHelper';\n\ndeclare var exports: any;\ndeclare var module: any;\ndeclare var define: any;\ndeclare var require: any;\ndeclare var window: any;\n\n(function (addon) {\n if (typeof window !== 'undefined' && 'Terminal' in window) {\n /**\n * Plain browser environment\n */\n addon(window.Terminal);\n } else if (typeof exports === 'object' && typeof module === 'object') {\n /**\n * CommonJS environment\n */\n module.exports = addon(require('../../Terminal').Terminal);\n } else if (typeof define === 'function') {\n /**\n * Require.js is available\n */\n define(['../../xterm'], addon);\n }\n})((Terminal: any) => {\n /**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n Terminal.prototype.findNext = function(term: string): boolean {\n if (!this._searchHelper) {\n this.searchHelper = new SearchHelper(this);\n }\n return (this.searchHelper).findNext(term);\n };\n\n /**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n Terminal.prototype.findPrevious = function(term: string): boolean {\n if (!this._searchHelper) {\n this.searchHelper = new SearchHelper(this);\n }\n return (this.searchHelper).findPrevious(term);\n };\n});\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// import { ITerminal } from '../../Interfaces';\n// import { translateBufferLineToString } from '../../utils/BufferLine';\n\ninterface ISearchResult {\n term: string;\n col: number;\n row: number;\n}\n\n/**\n * A class that knows how to search the terminal and how to display the results.\n */\nexport class SearchHelper {\n constructor(private _terminal: any) {\n // TODO: Search for multiple instances on 1 line\n // TODO: Don't use the actual selection, instead use a \"find selection\" so multiple instances can be highlighted\n // TODO: Highlight other instances in the viewport\n // TODO: Support regex, case sensitivity, etc.\n }\n\n /**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n public findNext(term: string): boolean {\n if (!term || term.length === 0) {\n return false;\n }\n\n let result: ISearchResult;\n\n let startRow = this._terminal.buffer.ydisp;\n if (this._terminal.selectionManager.selectionEnd) {\n // Start from the selection end if there is a selection\n startRow = this._terminal.selectionManager.selectionEnd[1];\n }\n\n // Search from ydisp + 1 to end\n for (let y = startRow + 1; y < this._terminal.buffer.ybase + this._terminal.rows; y++) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n\n // Search from the top to the current ydisp\n if (!result) {\n for (let y = 0; y < startRow; y++) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n public findPrevious(term: string): boolean {\n if (!term || term.length === 0) {\n return false;\n }\n\n let result: ISearchResult;\n\n let startRow = this._terminal.buffer.ydisp;\n if (this._terminal.selectionManager.selectionStart) {\n // Start from the selection end if there is a selection\n startRow = this._terminal.selectionManager.selectionStart[1];\n }\n\n // Search from ydisp + 1 to end\n for (let y = startRow - 1; y >= 0; y--) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n\n // Search from the top to the current ydisp\n if (!result) {\n for (let y = this._terminal.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Searches a line for a search term.\n * @param term Tne search term.\n * @param y The line to search.\n * @return The search result if it was found.\n */\n private _findInLine(term: string, y: number): ISearchResult {\n const lowerStringLine = this._terminal.buffer.translateBufferLineToString(y, true).toLowerCase();\n const lowerTerm = term.toLowerCase();\n const searchIndex = lowerStringLine.indexOf(lowerTerm);\n if (searchIndex >= 0) {\n return {\n term,\n col: searchIndex,\n row: y\n };\n }\n }\n\n /**\n * Selects and scrolls to a result.\n * @param result The result to select.\n * @return Whethera result was selected.\n */\n private _selectResult(result: ISearchResult): boolean {\n if (!result) {\n return false;\n }\n this._terminal.selectionManager.setSelection(result.col, result.row, result.term.length);\n this._terminal.scrollLines(result.row - this._terminal.buffer.ydisp, false);\n return true;\n }\n}\n",null],"names":[],"mappings":"AEAA;;;ADiBA;AACA;AAAA;AAKA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA1Ha;;;;;;;ADZb;AAQA;AACA;AAIA;AACA;AAAA;AAIA;AACA;AAAA;AAIA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;;;"} \ No newline at end of file diff --git a/web/static/build/addons/terminado/package.json b/web/static/build/addons/terminado/package.json new file mode 100755 index 000000000..9af1851bc --- /dev/null +++ b/web/static/build/addons/terminado/package.json @@ -0,0 +1,5 @@ +{ + "name": "xterm.terminado", + "main": "terminado.js", + "private": true +} diff --git a/web/static/build/addons/terminado/terminado.js b/web/static/build/addons/terminado/terminado.js new file mode 100755 index 000000000..702351b9c --- /dev/null +++ b/web/static/build/addons/terminado/terminado.js @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + * + * This module provides methods for attaching a terminal to a terminado + * WebSocket stream. + */ + +(function (attach) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = attach(require('../../Terminal').Terminal); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../xterm'], attach); + } else { + /* + * Plain browser environment + */ + attach(window.Terminal); + } +})(function (Terminal) { + 'use strict'; + + var exports = {}; + + /** + * Attaches the given terminal to the given socket. + * + * @param {Terminal} term - The terminal to be attached to the given socket. + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + exports.terminadoAttach = function (term, socket, bidirectional, buffered) { + bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; + term.socket = socket; + + term._flushBuffer = function () { + term.write(term._attachSocketBuffer); + term._attachSocketBuffer = null; + }; + + term._pushToBuffer = function (data) { + if (term._attachSocketBuffer) { + term._attachSocketBuffer += data; + } else { + term._attachSocketBuffer = data; + setTimeout(term._flushBuffer, 10); + } + }; + + term._getMessage = function (ev) { + var data = JSON.parse(ev.data) + if( data[0] == "stdout" ) { + if (buffered) { + term._pushToBuffer(data[1]); + } else { + term.write(data[1]); + } + } + }; + + term._sendData = function (data) { + socket.send(JSON.stringify(['stdin', data])); + }; + + term._setSize = function (size) { + socket.send(JSON.stringify(['set_size', size.rows, size.cols])); + }; + + socket.addEventListener('message', term._getMessage); + + if (bidirectional) { + term.on('data', term._sendData); + } + term.on('resize', term._setSize); + + socket.addEventListener('close', term.terminadoDetach.bind(term, socket)); + socket.addEventListener('error', term.terminadoDetach.bind(term, socket)); + }; + + /** + * Detaches the given terminal from the given socket + * + * @param {Xterm} term - The terminal to be detached from the given socket. + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + exports.terminadoDetach = function (term, socket) { + term.off('data', term._sendData); + + socket = (typeof socket == 'undefined') ? term.socket : socket; + + if (socket) { + socket.removeEventListener('message', term._getMessage); + } + + delete term.socket; + }; + + /** + * Attaches the current terminal to the given socket + * + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + Terminal.prototype.terminadoAttach = function (socket, bidirectional, buffered) { + return exports.terminadoAttach(this, socket, bidirectional, buffered); + }; + + /** + * Detaches the current terminal from the given socket. + * + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + Terminal.prototype.terminadoDetach = function (socket) { + return exports.terminadoDetach(this, socket); + }; + + return exports; +}); diff --git a/web/static/build/addons/winptyCompat/winptyCompat.js b/web/static/build/addons/winptyCompat/winptyCompat.js new file mode 100755 index 000000000..89c0d8795 --- /dev/null +++ b/web/static/build/addons/winptyCompat/winptyCompat.js @@ -0,0 +1,29 @@ +(function (addon) { + if (typeof window !== 'undefined' && 'Terminal' in window) { + addon(window.Terminal); + } + else if (typeof exports === 'object' && typeof module === 'object') { + module.exports = addon(require('../../Terminal').Terminal); + } + else if (typeof define === 'function') { + define(['../../xterm'], addon); + } +})(function (Terminal) { + Terminal.prototype.winptyCompatInit = function () { + var _this = this; + var isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; + if (!isWindows) { + return; + } + this.on('lineFeed', function () { + var line = _this.buffer.lines.get(_this.buffer.ybase + _this.buffer.y - 1); + var lastChar = line[_this.cols - 1]; + if (lastChar[3] !== 32) { + var nextLine = _this.buffer.lines.get(_this.buffer.ybase + _this.buffer.y); + nextLine.isWrapped = true; + } + }); + }; +}); + +//# sourceMappingURL=winptyCompat.js.map diff --git a/web/static/build/addons/winptyCompat/winptyCompat.js.map b/web/static/build/addons/winptyCompat/winptyCompat.js.map new file mode 100755 index 000000000..826e69714 --- /dev/null +++ b/web/static/build/addons/winptyCompat/winptyCompat.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/addons/winptyCompat/winptyCompat.ts"],"names":[],"mappings":"AAQA,CAAC,UAAU,KAAK;IACd,EAAE,CAAC,CAAC,OAAO,MAAM,KAAK,WAAW,IAAI,UAAU,IAAI,MAAM,CAAC,CAAC,CAAC;QAI1D,KAAK,CAAO,MAAO,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC;QAIrE,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC7D,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC;QAIxC,MAAM,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;AACH,CAAC,CAAC,CAAC,UAAC,QAAa;IACf,QAAQ,CAAC,SAAS,CAAC,gBAAgB,GAAG;QAAA,iBAyBrC;QAvBC,IAAM,SAAS,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC1F,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,CAAC;QACT,CAAC;QAYD,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE;YAClB,IAAM,IAAI,GAAG,KAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,KAAI,CAAC,MAAM,CAAC,KAAK,GAAG,KAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1E,IAAM,QAAQ,GAAG,IAAI,CAAC,KAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;YACrC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,EAAY,CAAC,CAAC,CAAC;gBACjC,IAAM,QAAQ,GAAG,KAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,KAAI,CAAC,MAAM,CAAC,KAAK,GAAG,KAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACpE,QAAS,CAAC,SAAS,GAAG,IAAI,CAAC;YACnC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","file":"winptyCompat.js","sourceRoot":"."} \ No newline at end of file diff --git a/web/static/build/addons/zmodem/demo/app.js b/web/static/build/addons/zmodem/demo/app.js new file mode 100755 index 000000000..13a40a6d2 --- /dev/null +++ b/web/static/build/addons/zmodem/demo/app.js @@ -0,0 +1,87 @@ +var express = require('express'); +var app = express(); +var expressWs = require('express-ws')(app); +var os = require('os'); +var pty = require('node-pty'); + +var terminals = {}, + logs = {}; + +app.use('/build', express.static(__dirname + '/../../../../build')); +app.use('/demo', express.static(__dirname + '/../../../../demo')); +app.use('/zmodemjs', express.static(__dirname + '/../../../../node_modules/zmodem.js/dist')); + +app.get('/', function(req, res){ + res.sendFile(__dirname + '/index.html'); +}); + +app.get('/style.css', function(req, res){ + res.sendFile(__dirname + '/style.css'); +}); + +app.get('/main.js', function(req, res){ + res.sendFile(__dirname + '/main.js'); +}); + +app.post('/terminals', function (req, res) { + var cols = parseInt(req.query.cols), + rows = parseInt(req.query.rows), + term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], { + encoding: null, + name: 'xterm-color', + cols: cols || 80, + rows: rows || 24, + cwd: process.env.PWD, + env: process.env + }); + + console.log('Created terminal with PID: ' + term.pid); + terminals[term.pid] = term; + logs[term.pid] = ''; + term.on('data', function(data) { + logs[term.pid] += data; + }); + res.send(term.pid.toString()); + res.end(); +}); + +app.post('/terminals/:pid/size', function (req, res) { + var pid = parseInt(req.params.pid), + cols = parseInt(req.query.cols), + rows = parseInt(req.query.rows), + term = terminals[pid]; + + term.resize(cols, rows); + console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.'); + res.end(); +}); + +app.ws('/terminals/:pid', function (ws, req) { + var term = terminals[parseInt(req.params.pid)]; + console.log('Connected to terminal ' + term.pid); + ws.send(logs[term.pid]); + + term.on('data', function(data) { + try { + ws.send(data); + } catch (ex) { + // The WebSocket is not open, ignore + } + }); + ws.on('message', function(msg) { + term.write(msg); + }); + ws.on('close', function () { + term.kill(); + console.log('Closed terminal ' + term.pid); + // Clean things up + delete terminals[term.pid]; + delete logs[term.pid]; + }); +}); + +var port = process.env.PORT || 3000, + host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0'; + +console.log('App listening to http://' + host + ':' + port); +app.listen(port, host); diff --git a/web/static/build/addons/zmodem/demo/index.html b/web/static/build/addons/zmodem/demo/index.html new file mode 100755 index 000000000..0b38cf4f1 --- /dev/null +++ b/web/static/build/addons/zmodem/demo/index.html @@ -0,0 +1,128 @@ + + + + xterm.js demo + + + + + + + + + + + + + + + + +

                                            xterm.js: xterm, in the browser

                                            + +
                                            + +
                                            + + + + + + + + + +
                                            + +
                                            +

                                            Actions

                                            +

                                            + + +

                                            +
                                            +
                                            +

                                            Options

                                            +

                                            + +

                                            +

                                            + +

                                            +

                                            + +

                                            +

                                            + +

                                            +

                                            + +

                                            +

                                            + +

                                            +
                                            +

                                            Size

                                            +
                                            +
                                            + + +
                                            +
                                            + + +
                                            +
                                            +
                                            +
                                            +

                                            Attention: The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.

                                            +

                                            * ZMODEM file transfers are supported via an addon. To try it out, install lrzsz onto the remote peer, then run rz to send from your browser or sz <file> to send from the remote peer.

                                            + + + diff --git a/web/static/build/addons/zmodem/demo/main.js b/web/static/build/addons/zmodem/demo/main.js new file mode 100755 index 000000000..aca441355 --- /dev/null +++ b/web/static/build/addons/zmodem/demo/main.js @@ -0,0 +1,383 @@ +"use strict"; + +var term, + protocol, + socketURL, + socket, + pid; + +var terminalContainer = document.getElementById('terminal-container'), + actionElements = { + findNext: document.querySelector('#find-next'), + findPrevious: document.querySelector('#find-previous') + }, + optionElements = { + cursorBlink: document.querySelector('#option-cursor-blink'), + cursorStyle: document.querySelector('#option-cursor-style'), + scrollback: document.querySelector('#option-scrollback'), + tabstopwidth: document.querySelector('#option-tabstopwidth'), + bellStyle: document.querySelector('#option-bell-style') + }, + colsElement = document.getElementById('cols'), + rowsElement = document.getElementById('rows'); + +function setTerminalSize() { + var cols = parseInt(colsElement.value, 10); + var rows = parseInt(rowsElement.value, 10); + var viewportElement = document.querySelector('.xterm-viewport'); + var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth; + var width = (cols * term.charMeasure.width + 20 /*room for scrollbar*/).toString() + 'px'; + var height = (rows * term.charMeasure.height).toString() + 'px'; + + terminalContainer.style.width = width; + terminalContainer.style.height = height; + term.resize(cols, rows); +} + +colsElement.addEventListener('change', setTerminalSize); +rowsElement.addEventListener('change', setTerminalSize); + +actionElements.findNext.addEventListener('keypress', function (e) { + if (e.key === "Enter") { + e.preventDefault(); + term.findNext(actionElements.findNext.value); + } +}); +actionElements.findPrevious.addEventListener('keypress', function (e) { + if (e.key === "Enter") { + e.preventDefault(); + term.findPrevious(actionElements.findPrevious.value); + } +}); + +optionElements.cursorBlink.addEventListener('change', function () { + term.setOption('cursorBlink', optionElements.cursorBlink.checked); +}); +optionElements.cursorStyle.addEventListener('change', function () { + term.setOption('cursorStyle', optionElements.cursorStyle.value); +}); +optionElements.bellStyle.addEventListener('change', function () { + term.setOption('bellStyle', optionElements.bellStyle.value); +}); +optionElements.scrollback.addEventListener('change', function () { + term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10)); +}); +optionElements.tabstopwidth.addEventListener('change', function () { + term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10)); +}); + +createTerminal(); + +function createTerminal() { + // Clean terminal + while (terminalContainer.children.length) { + terminalContainer.removeChild(terminalContainer.children[0]); + } + term = new Terminal({ + cursorBlink: optionElements.cursorBlink.checked, + scrollback: parseInt(optionElements.scrollback.value, 10), + tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10) + }); + term.on('resize', function (size) { + if (!pid) { + return; + } + var cols = size.cols, + rows = size.rows, + url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows; + + fetch(url, {method: 'POST'}); + }); + protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://'; + socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; + + term.open(terminalContainer); + term.fit(); + + // fit is called within a setTimeout, cols and rows need this. + setTimeout(function () { + colsElement.value = term.cols; + rowsElement.value = term.rows; + + // Set terminal size again to set the specific dimensions on the demo + setTerminalSize(); + + fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) { + + res.text().then(function (pid) { + window.pid = pid; + socketURL += pid; + socket = new WebSocket(socketURL); + socket.onopen = runRealTerminal; + socket.onclose = runFakeTerminal; + socket.onerror = runFakeTerminal; + + term.zmodemAttach(socket, { + noTerminalWriteOutsideSession: true, + } ); + + term.on("zmodemRetract", () => { + start_form.style.display = "none"; + start_form.onsubmit = null; + }); + + term.on("zmodemDetect", (detection) => { + function do_zmodem() { + term.detach(); + let zsession = detection.confirm(); + + var promise; + + if (zsession.type === "receive") { + promise = _handle_receive_session(zsession); + } + else { + promise = _handle_send_session(zsession); + } + + promise.catch( console.error.bind(console) ).then( () => { + term.attach(socket); + } ); + } + + if (_auto_zmodem()) { + do_zmodem(); + } + else { + start_form.style.display = ""; + start_form.onsubmit = function(e) { + start_form.style.display = "none"; + + if (document.getElementById("zmstart_yes").checked) { + do_zmodem(); + } + else { + detection.deny(); + } + }; + } + }); + }); + }); + }, 0); +} + +//---------------------------------------------------------------------- +// UI STUFF + +function _show_file_info(xfer) { + var file_info = xfer.get_details(); + + document.getElementById("name").textContent = file_info.name; + document.getElementById("size").textContent = file_info.size; + document.getElementById("mtime").textContent = file_info.mtime; + document.getElementById("files_remaining").textContent = file_info.files_remaining; + document.getElementById("bytes_remaining").textContent = file_info.bytes_remaining; + + document.getElementById("mode").textContent = "0" + file_info.mode.toString(8); + + var xfer_opts = xfer.get_options(); + ["conversion", "management", "transport", "sparse"].forEach( (lbl) => { + document.getElementById(`zfile_${lbl}`).textContent = xfer_opts[lbl]; + } ); + + document.getElementById("zm_file").style.display = ""; +} +function _hide_file_info() { + document.getElementById("zm_file").style.display = "none"; +} + +function _save_to_disk(xfer, buffer) { + return Zmodem.Browser.save_to_disk(buffer, xfer.get_details().name); +} + +var skipper_button = document.getElementById("zm_progress_skipper"); +var skipper_button_orig_text = skipper_button.textContent; + +function _show_progress() { + skipper_button.disabled = false; + skipper_button.textContent = skipper_button_orig_text; + + document.getElementById("bytes_received").textContent = 0; + document.getElementById("percent_received").textContent = 0; + + document.getElementById("zm_progress").style.display = ""; +} + +function _update_progress(xfer) { + var total_in = xfer.get_offset(); + + document.getElementById("bytes_received").textContent = total_in; + + var percent_received = 100 * total_in / xfer.get_details().size; + document.getElementById("percent_received").textContent = percent_received.toFixed(2); +} + +function _hide_progress() { + document.getElementById("zm_progress").style.display = "none"; +} + +var start_form = document.getElementById("zm_start"); + +function _auto_zmodem() { + return document.getElementById("zmodem-auto").checked; +} + +// END UI STUFF +//---------------------------------------------------------------------- + +function _handle_receive_session(zsession) { + zsession.on("offer", function(xfer) { + current_receive_xfer = xfer; + + _show_file_info(xfer); + + var offer_form = document.getElementById("zm_offer"); + + function on_form_submit() { + offer_form.style.display = "none"; + + //START + //if (offer_form.zmaccept.value) { + if (_auto_zmodem() || document.getElementById("zmaccept_yes").checked) { + _show_progress(); + + var FILE_BUFFER = []; + xfer.on("input", (payload) => { + _update_progress(xfer); + FILE_BUFFER.push( new Uint8Array(payload) ); + }); + xfer.accept().then( + () => { + _save_to_disk(xfer, FILE_BUFFER); + }, + console.error.bind(console) + ); + } + else { + xfer.skip(); + } + //END + } + + if (_auto_zmodem()) { + on_form_submit(); + } + else { + offer_form.onsubmit = on_form_submit; + offer_form.style.display = ""; + } + } ); + + var promise = new Promise( (res) => { + zsession.on("session_end", () => { + _hide_file_info(); + _hide_progress(); + res(); + } ); + } ); + + zsession.start(); + + return promise; +} + +function _handle_send_session(zsession) { + var choose_form = document.getElementById("zm_choose"); + choose_form.style.display = ""; + + var file_el = document.getElementById("zm_files"); + + var promise = new Promise( (res) => { + file_el.onchange = function(e) { + choose_form.style.display = "none"; + + var files_obj = file_el.files; + + Zmodem.Browser.send_files( + zsession, + files_obj, + { + on_offer_response(obj, xfer) { + if (xfer) _show_progress(); + //console.log("offer", xfer ? "accepted" : "skipped"); + }, + on_progress(obj, xfer) { + _update_progress(xfer); + }, + on_file_complete(obj) { + //console.log("COMPLETE", obj); + _hide_progress(); + }, + } + ).then(_hide_progress).then( + zsession.close.bind(zsession), + console.error.bind(console) + ).then( () => { + _hide_file_info(); + _hide_progress(); + res(); + } ); + }; + } ); + + return promise; +} + +//This is here to allow canceling of an in-progress ZMODEM transfer. +var current_receive_xfer; + +//Called from HTML directly. +function skip_current_file() { + current_receive_xfer.skip(); + + skipper_button.disabled = true; + skipper_button.textContent = "Waiting for server to acknowledge skip …"; +} + +function runRealTerminal() { + term.attach(socket); + + term._initialized = true; +} + +function runFakeTerminal() { + if (term._initialized) { + return; + } + + term._initialized = true; + + var shellprompt = '$ '; + + term.prompt = function () { + term.write('\r\n' + shellprompt); + }; + + term.writeln('Welcome to xterm.js'); + term.writeln('This is a local terminal emulation, without a real terminal in the back-end.'); + term.writeln('Type some keys and commands to play around.'); + term.writeln(''); + term.prompt(); + + term.on('key', function (key, ev) { + var printable = ( + !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey + ); + + if (ev.keyCode == 13) { + term.prompt(); + } else if (ev.keyCode == 8) { + // Do not delete the prompt + if (term.x > 2) { + term.write('\b \b'); + } + } else if (printable) { + term.write(key); + } + }); + + term.on('paste', function (data, ev) { + term.write(data); + }); +} diff --git a/web/static/build/addons/zmodem/zmodem.js b/web/static/build/addons/zmodem/zmodem.js new file mode 100755 index 000000000..aeb2fd5f0 --- /dev/null +++ b/web/static/build/addons/zmodem/zmodem.js @@ -0,0 +1,108 @@ +/** + * + * Allow xterm.js to handle ZMODEM uploads and downloads. + * + * This addon is a wrapper around zmodem.js. It adds the following to the + * Terminal class: + * + * - function `zmodemAttach(, )` - creates a Zmodem.Sentry + * on the passed WebSocket object. The Object passed is optional and + * can contain: + * - noTerminalWriteOutsideSession: Suppress writes from the Sentry + * object to the Terminal while there is no active Session. This + * is necessary for compatibility with, for example, the + * `attach.js` addon. + * + * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback. + * Passes the zmodem.js Detection object. + * + * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback. + * + * You’ll need to provide logic to handle uploads and downloads. + * See zmodem.js’s documentation for more details. + * + * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have + * used the `attach` or `terminado` addons, you’ll need to suspend their + * operation for the duration of the ZMODEM session. (The demo does this + * via `detach()` and a re-`attach()`.) + */ +(function (addon) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = addon(require('../../Terminal').Terminal); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../xterm'], addon); + } else { + /* + * Plain browser environment + */ + addon(window.Terminal); + } +})(function _zmodemAddon(Terminal) { + Object.assign( + Terminal.prototype, + { + zmodemAttach: function zmodemAttach(ws, opts) { + var term = this; + + if (!opts) opts = {}; + + var senderFunc = function _ws_sender_func(octets) { + ws.send( new Uint8Array(octets) ); + }; + + var zsentry; + + function _shouldWrite() { + return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession; + } + + zsentry = new Zmodem.Sentry( { + to_terminal: function _to_terminal(octets) { + if (_shouldWrite()) { + term.write( + String.fromCharCode.apply(String, octets) + ); + } + }, + + sender: senderFunc, + + on_retract: function _on_retract() { + term.emit("zmodemRetract"); + }, + + on_detect: function _on_detect(detection) { + term.emit("zmodemDetect", detection); + }, + } ); + + function handleWSMessage(evt) { + + //In testing with xterm.js’s demo the first message was + //always text even if the rest were binary. While that + //may be specific to xterm.js’s demo, ultimately we + //should reject anything that isn’t binary. + if (typeof evt.data === "string") { + if (_shouldWrite()) { + term.write(evt.data); + } + } + else { + zsentry.consume(evt.data); + } + } + + ws.binaryType = "arraybuffer"; + ws.addEventListener("message", handleWSMessage); + }, + + zmodemBrowser: Zmodem.Browser, + } + ); +}); diff --git a/web/static/build/xterm.css b/web/static/build/xterm.css new file mode 100755 index 000000000..c668af403 --- /dev/null +++ b/web/static/build/xterm.css @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * https://github.com/chjj/term.js + * @license MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + */ + +/** + * Default styles for xterm.js + */ + +.xterm { + font-family: courier-new, courier, monospace; + font-feature-settings: "liga" 0; + position: relative; + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; +} + +.xterm.focus, +.xterm:focus { + outline: none; +} + +.xterm .xterm-helpers { + position: absolute; + top: 0; + /** + * The z-index of the helpers must be higher than the canvases in order for + * IMEs to appear on top. + */ + z-index: 10; +} + +.xterm .xterm-helper-textarea { + /* + * HACK: to fix IE's blinking cursor + * Move textarea out of the screen to the far left, so that the cursor is not visible. + */ + position: absolute; + opacity: 0; + left: -9999em; + top: 0; + width: 0; + height: 0; + z-index: -10; + /** Prevent wrapping so the IME appears against the textarea at the correct position */ + white-space: nowrap; + overflow: hidden; + resize: none; +} + +.xterm .composition-view { + /* TODO: Composition position got messed up somewhere */ + background: #333; + color: #FFF; + display: none; + position: absolute; + white-space: nowrap; + z-index: 1; +} + +.xterm .composition-view.active { + display: block; +} + +.xterm .xterm-viewport { + /* On OS X this is required in order for the scroll bar to appear fully opaque */ + background-color: #333; + overflow-y: scroll; +} + +.xterm canvas { + position: absolute; + left: 0; + top: 0; +} + +.xterm .xterm-scroll-area { + visibility: hidden; +} + +.xterm .xterm-char-measure-element { + display: inline-block; + visibility: hidden; + position: absolute; + left: -9999em; +} + +.xterm.enable-mouse-events { + /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ + cursor: default; +} + +.xterm:not(.enable-mouse-events) { + cursor: text; +} diff --git a/web/static/build/xterm.js b/web/static/build/xterm.js new file mode 100755 index 000000000..80b0ba5d3 --- /dev/null +++ b/web/static/build/xterm.js @@ -0,0 +1,6241 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Terminal = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o this._terminal.rows; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Buffer.prototype, "isCursorInViewport", { + get: function () { + var absoluteY = this.ybase + this.y; + var relativeY = absoluteY - this.ydisp; + return (relativeY >= 0 && relativeY < this._terminal.rows); + }, + enumerable: true, + configurable: true + }); + Buffer.prototype._getCorrectBufferLength = function (rows) { + if (!this._hasScrollback) { + return rows; + } + var correctBufferLength = rows + this._terminal.options.scrollback; + return correctBufferLength > exports.MAX_BUFFER_SIZE ? exports.MAX_BUFFER_SIZE : correctBufferLength; + }; + Buffer.prototype.fillViewportRows = function () { + if (this._lines.length === 0) { + var i = this._terminal.rows; + while (i--) { + this.lines.push(this._terminal.blankLine()); + } + } + }; + Buffer.prototype.clear = function () { + this.ydisp = 0; + this.ybase = 0; + this.y = 0; + this.x = 0; + this._lines = new CircularList_1.CircularList(this._getCorrectBufferLength(this._terminal.rows)); + this.scrollTop = 0; + this.scrollBottom = this._terminal.rows - 1; + this.setupTabStops(); + }; + Buffer.prototype.resize = function (newCols, newRows) { + var newMaxLength = this._getCorrectBufferLength(newRows); + if (newMaxLength > this._lines.maxLength) { + this._lines.maxLength = newMaxLength; + } + if (this._lines.length > 0) { + if (this._terminal.cols < newCols) { + var ch = [this._terminal.defAttr, ' ', 1, 32]; + for (var i = 0; i < this._lines.length; i++) { + if (this._lines.get(i) === undefined) { + this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols)); + } + while (this._lines.get(i).length < newCols) { + this._lines.get(i).push(ch); + } + } + } + var addToY = 0; + if (this._terminal.rows < newRows) { + for (var y = this._terminal.rows; y < newRows; y++) { + if (this._lines.length < newRows + this.ybase) { + if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) { + this.ybase--; + addToY++; + if (this.ydisp > 0) { + this.ydisp--; + } + } + else { + this._lines.push(this._terminal.blankLine(undefined, undefined, newCols)); + } + } + } + } + else { + for (var y = this._terminal.rows; y > newRows; y--) { + if (this._lines.length > newRows + this.ybase) { + if (this._lines.length > this.ybase + this.y + 1) { + this._lines.pop(); + } + else { + this.ybase++; + this.ydisp++; + } + } + } + } + if (newMaxLength < this._lines.maxLength) { + var amountToTrim = this._lines.length - newMaxLength; + if (amountToTrim > 0) { + this._lines.trimStart(amountToTrim); + this.ybase = Math.max(this.ybase - amountToTrim, 0); + this.ydisp = Math.max(this.ydisp - amountToTrim, 0); + } + this._lines.maxLength = newMaxLength; + } + if (this.y >= newRows) { + this.y = newRows - 1; + } + if (addToY) { + this.y += addToY; + } + if (this.x >= newCols) { + this.x = newCols - 1; + } + this.scrollTop = 0; + } + this.scrollBottom = newRows - 1; + }; + Buffer.prototype.translateBufferLineToString = function (lineIndex, trimRight, startCol, endCol) { + if (startCol === void 0) { startCol = 0; } + if (endCol === void 0) { endCol = null; } + var lineString = ''; + var line = this.lines.get(lineIndex); + if (!line) { + return ''; + } + var startIndex = startCol; + endCol = endCol || line.length; + var endIndex = endCol; + for (var i = 0; i < line.length; i++) { + var char = line[i]; + lineString += char[exports.CHAR_DATA_CHAR_INDEX]; + if (char[exports.CHAR_DATA_WIDTH_INDEX] === 0) { + if (startCol >= i) { + startIndex--; + } + if (endCol >= i) { + endIndex--; + } + } + else { + if (char[exports.CHAR_DATA_CHAR_INDEX].length > 1) { + if (startCol > i) { + startIndex += char[exports.CHAR_DATA_CHAR_INDEX].length - 1; + } + if (endCol > i) { + endIndex += char[exports.CHAR_DATA_CHAR_INDEX].length - 1; + } + } + } + } + if (trimRight) { + var rightWhitespaceIndex = lineString.search(/\s+$/); + if (rightWhitespaceIndex !== -1) { + endIndex = Math.min(endIndex, rightWhitespaceIndex); + } + if (endIndex <= startIndex) { + return ''; + } + } + return lineString.substring(startIndex, endIndex); + }; + Buffer.prototype.setupTabStops = function (i) { + if (i != null) { + if (!this.tabs[i]) { + i = this.prevStop(i); + } + } + else { + this.tabs = {}; + i = 0; + } + for (; i < this._terminal.cols; i += this._terminal.options.tabStopWidth) { + this.tabs[i] = true; + } + }; + Buffer.prototype.prevStop = function (x) { + if (x == null) { + x = this.x; + } + while (!this.tabs[--x] && x > 0) + ; + return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x; + }; + Buffer.prototype.nextStop = function (x) { + if (x == null) { + x = this.x; + } + while (!this.tabs[++x] && x < this._terminal.cols) + ; + return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x; + }; + return Buffer; +}()); +exports.Buffer = Buffer; + + + +},{"./utils/CircularList":30}],2:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Buffer_1 = require("./Buffer"); +var EventEmitter_1 = require("./EventEmitter"); +var BufferSet = (function (_super) { + __extends(BufferSet, _super); + function BufferSet(_terminal) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._normal = new Buffer_1.Buffer(_this._terminal, true); + _this._normal.fillViewportRows(); + _this._alt = new Buffer_1.Buffer(_this._terminal, false); + _this._activeBuffer = _this._normal; + _this.setupTabStops(); + return _this; + } + Object.defineProperty(BufferSet.prototype, "alt", { + get: function () { + return this._alt; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferSet.prototype, "active", { + get: function () { + return this._activeBuffer; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferSet.prototype, "normal", { + get: function () { + return this._normal; + }, + enumerable: true, + configurable: true + }); + BufferSet.prototype.activateNormalBuffer = function () { + this._alt.clear(); + this._activeBuffer = this._normal; + this.emit('activate', this._normal); + }; + BufferSet.prototype.activateAltBuffer = function () { + this._alt.fillViewportRows(); + this._activeBuffer = this._alt; + this.emit('activate', this._alt); + }; + BufferSet.prototype.resize = function (newCols, newRows) { + this._normal.resize(newCols, newRows); + this._alt.resize(newCols, newRows); + }; + BufferSet.prototype.setupTabStops = function (i) { + this._normal.setupTabStops(i); + this._alt.setupTabStops(i); + }; + return BufferSet; +}(EventEmitter_1.EventEmitter)); +exports.BufferSet = BufferSet; + + + +},{"./Buffer":1,"./EventEmitter":7}],3:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.wcwidth = (function (opts) { + var COMBINING_BMP = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB], + ]; + var COMBINING_HIGH = [ + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + function bisearch(ucs, data) { + var min = 0; + var max = data.length - 1; + var mid; + if (ucs < data[0][0] || ucs > data[max][1]) + return false; + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) + min = mid + 1; + else if (ucs < data[mid][0]) + max = mid - 1; + else + return true; + } + return false; + } + function wcwidthBMP(ucs) { + if (ucs === 0) + return opts.nul; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return opts.control; + if (bisearch(ucs, COMBINING_BMP)) + return 0; + if (isWideBMP(ucs)) { + return 2; + } + return 1; + } + function isWideBMP(ucs) { + return (ucs >= 0x1100 && (ucs <= 0x115f || + ucs === 0x2329 || + ucs === 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || + (ucs >= 0xac00 && ucs <= 0xd7a3) || + (ucs >= 0xf900 && ucs <= 0xfaff) || + (ucs >= 0xfe10 && ucs <= 0xfe19) || + (ucs >= 0xfe30 && ucs <= 0xfe6f) || + (ucs >= 0xff00 && ucs <= 0xff60) || + (ucs >= 0xffe0 && ucs <= 0xffe6))); + } + function wcwidthHigh(ucs) { + if (bisearch(ucs, COMBINING_HIGH)) + return 0; + if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { + return 2; + } + return 1; + } + var control = opts.control | 0; + var table = null; + function init_table() { + var CODEPOINTS = 65536; + var BITWIDTH = 2; + var ITEMSIZE = 32; + var CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE; + var CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH; + table = (typeof Uint32Array === 'undefined') + ? new Array(CONTAINERSIZE) + : new Uint32Array(CONTAINERSIZE); + for (var i = 0; i < CONTAINERSIZE; ++i) { + var num = 0; + var pos = CODEPOINTS_PER_ITEM; + while (pos--) + num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos); + table[i] = num; + } + return table; + } + return function (num) { + num = num | 0; + if (num < 32) + return control | 0; + if (num < 127) + return 1; + var t = table || init_table(); + if (num < 65536) + return t[num >> 4] >> ((num & 15) << 1) & 3; + return wcwidthHigh(num); + }; +})({ nul: 0, control: 0 }); + + + +},{}],4:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CHARSETS = {}; +exports.DEFAULT_CHARSET = exports.CHARSETS['B']; +exports.CHARSETS['0'] = { + '`': '\u25c6', + 'a': '\u2592', + 'b': '\u0009', + 'c': '\u000c', + 'd': '\u000d', + 'e': '\u000a', + 'f': '\u00b0', + 'g': '\u00b1', + 'h': '\u2424', + 'i': '\u000b', + 'j': '\u2518', + 'k': '\u2510', + 'l': '\u250c', + 'm': '\u2514', + 'n': '\u253c', + 'o': '\u23ba', + 'p': '\u23bb', + 'q': '\u2500', + 'r': '\u23bc', + 's': '\u23bd', + 't': '\u251c', + 'u': '\u2524', + 'v': '\u2534', + 'w': '\u252c', + 'x': '\u2502', + 'y': '\u2264', + 'z': '\u2265', + '{': '\u03c0', + '|': '\u2260', + '}': '\u00a3', + '~': '\u00b7' +}; +exports.CHARSETS['A'] = { + '#': '£' +}; +exports.CHARSETS['B'] = null; +exports.CHARSETS['4'] = { + '#': '£', + '@': '¾', + '[': 'ij', + '\\': '½', + ']': '|', + '{': '¨', + '|': 'f', + '}': '¼', + '~': '´' +}; +exports.CHARSETS['C'] = + exports.CHARSETS['5'] = { + '[': 'Ä', + '\\': 'Ö', + ']': 'Å', + '^': 'Ü', + '`': 'é', + '{': 'ä', + '|': 'ö', + '}': 'å', + '~': 'ü' + }; +exports.CHARSETS['R'] = { + '#': '£', + '@': 'à', + '[': '°', + '\\': 'ç', + ']': '§', + '{': 'é', + '|': 'ù', + '}': 'è', + '~': '¨' +}; +exports.CHARSETS['Q'] = { + '@': 'à', + '[': 'â', + '\\': 'ç', + ']': 'ê', + '^': 'î', + '`': 'ô', + '{': 'é', + '|': 'ù', + '}': 'è', + '~': 'û' +}; +exports.CHARSETS['K'] = { + '@': '§', + '[': 'Ä', + '\\': 'Ö', + ']': 'Ü', + '{': 'ä', + '|': 'ö', + '}': 'ü', + '~': 'ß' +}; +exports.CHARSETS['Y'] = { + '#': '£', + '@': '§', + '[': '°', + '\\': 'ç', + ']': 'é', + '`': 'ù', + '{': 'à', + '|': 'ò', + '}': 'è', + '~': 'ì' +}; +exports.CHARSETS['E'] = + exports.CHARSETS['6'] = { + '@': 'Ä', + '[': 'Æ', + '\\': 'Ø', + ']': 'Å', + '^': 'Ü', + '`': 'ä', + '{': 'æ', + '|': 'ø', + '}': 'å', + '~': 'ü' + }; +exports.CHARSETS['Z'] = { + '#': '£', + '@': '§', + '[': '¡', + '\\': 'Ñ', + ']': '¿', + '{': '°', + '|': 'ñ', + '}': 'ç' +}; +exports.CHARSETS['H'] = + exports.CHARSETS['7'] = { + '@': 'É', + '[': 'Ä', + '\\': 'Ö', + ']': 'Å', + '^': 'Ü', + '`': 'é', + '{': 'ä', + '|': 'ö', + '}': 'å', + '~': 'ü' + }; +exports.CHARSETS['='] = { + '#': 'ù', + '@': 'à', + '[': 'é', + '\\': 'ç', + ']': 'ê', + '^': 'î', + '_': 'è', + '`': 'ô', + '{': 'ä', + '|': 'ö', + '}': 'ü', + '~': 'û' +}; + + + +},{}],5:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var CompositionHelper = (function () { + function CompositionHelper(textarea, compositionView, terminal) { + this.textarea = textarea; + this.compositionView = compositionView; + this.terminal = terminal; + this.isComposing = false; + this.isSendingComposition = false; + this.compositionPosition = { start: null, end: null }; + } + CompositionHelper.prototype.compositionstart = function () { + this.isComposing = true; + this.compositionPosition.start = this.textarea.value.length; + this.compositionView.textContent = ''; + this.compositionView.classList.add('active'); + }; + CompositionHelper.prototype.compositionupdate = function (ev) { + var _this = this; + this.compositionView.textContent = ev.data; + this.updateCompositionElements(); + setTimeout(function () { + _this.compositionPosition.end = _this.textarea.value.length; + }, 0); + }; + CompositionHelper.prototype.compositionend = function () { + this.finalizeComposition(true); + }; + CompositionHelper.prototype.keydown = function (ev) { + if (this.isComposing || this.isSendingComposition) { + if (ev.keyCode === 229) { + return false; + } + else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) { + return false; + } + else { + this.finalizeComposition(false); + } + } + if (ev.keyCode === 229) { + this.handleAnyTextareaChanges(); + return false; + } + return true; + }; + CompositionHelper.prototype.finalizeComposition = function (waitForPropogation) { + var _this = this; + this.compositionView.classList.remove('active'); + this.isComposing = false; + this.clearTextareaPosition(); + if (!waitForPropogation) { + this.isSendingComposition = false; + var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end); + this.terminal.handler(input); + } + else { + var currentCompositionPosition_1 = { + start: this.compositionPosition.start, + end: this.compositionPosition.end, + }; + this.isSendingComposition = true; + setTimeout(function () { + if (_this.isSendingComposition) { + _this.isSendingComposition = false; + var input = void 0; + if (_this.isComposing) { + input = _this.textarea.value.substring(currentCompositionPosition_1.start, currentCompositionPosition_1.end); + } + else { + input = _this.textarea.value.substring(currentCompositionPosition_1.start); + } + _this.terminal.handler(input); + } + }, 0); + } + }; + CompositionHelper.prototype.handleAnyTextareaChanges = function () { + var _this = this; + var oldValue = this.textarea.value; + setTimeout(function () { + if (!_this.isComposing) { + var newValue = _this.textarea.value; + var diff = newValue.replace(oldValue, ''); + if (diff.length > 0) { + _this.terminal.handler(diff); + } + } + }, 0); + }; + CompositionHelper.prototype.updateCompositionElements = function (dontRecurse) { + var _this = this; + if (!this.isComposing) { + return; + } + if (this.terminal.buffer.isCursorInViewport) { + var cellHeight = Math.ceil(this.terminal.charMeasure.height * this.terminal.options.lineHeight); + var cursorTop = this.terminal.buffer.y * cellHeight; + var cursorLeft = this.terminal.buffer.x * this.terminal.charMeasure.width; + this.compositionView.style.left = cursorLeft + 'px'; + this.compositionView.style.top = cursorTop + 'px'; + this.compositionView.style.height = cellHeight + 'px'; + this.compositionView.style.lineHeight = cellHeight + 'px'; + var compositionViewBounds = this.compositionView.getBoundingClientRect(); + this.textarea.style.left = cursorLeft + 'px'; + this.textarea.style.top = cursorTop + 'px'; + this.textarea.style.width = compositionViewBounds.width + 'px'; + this.textarea.style.height = compositionViewBounds.height + 'px'; + this.textarea.style.lineHeight = compositionViewBounds.height + 'px'; + } + if (!dontRecurse) { + setTimeout(function () { return _this.updateCompositionElements(true); }, 0); + } + }; + ; + CompositionHelper.prototype.clearTextareaPosition = function () { + this.textarea.style.left = ''; + this.textarea.style.top = ''; + }; + ; + return CompositionHelper; +}()); +exports.CompositionHelper = CompositionHelper; + + + +},{}],6:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var C0; +(function (C0) { + C0.NUL = '\x00'; + C0.SOH = '\x01'; + C0.STX = '\x02'; + C0.ETX = '\x03'; + C0.EOT = '\x04'; + C0.ENQ = '\x05'; + C0.ACK = '\x06'; + C0.BEL = '\x07'; + C0.BS = '\x08'; + C0.HT = '\x09'; + C0.LF = '\x0a'; + C0.VT = '\x0b'; + C0.FF = '\x0c'; + C0.CR = '\x0d'; + C0.SO = '\x0e'; + C0.SI = '\x0f'; + C0.DLE = '\x10'; + C0.DC1 = '\x11'; + C0.DC2 = '\x12'; + C0.DC3 = '\x13'; + C0.DC4 = '\x14'; + C0.NAK = '\x15'; + C0.SYN = '\x16'; + C0.ETB = '\x17'; + C0.CAN = '\x18'; + C0.EM = '\x19'; + C0.SUB = '\x1a'; + C0.ESC = '\x1b'; + C0.FS = '\x1c'; + C0.GS = '\x1d'; + C0.RS = '\x1e'; + C0.US = '\x1f'; + C0.SP = '\x20'; + C0.DEL = '\x7f'; +})(C0 = exports.C0 || (exports.C0 = {})); +; + + + +},{}],7:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter = (function () { + function EventEmitter() { + this._events = this._events || {}; + } + EventEmitter.prototype.on = function (type, listener) { + this._events[type] = this._events[type] || []; + this._events[type].push(listener); + }; + EventEmitter.prototype.off = function (type, listener) { + if (!this._events[type]) { + return; + } + var obj = this._events[type]; + var i = obj.length; + while (i--) { + if (obj[i] === listener || obj[i].listener === listener) { + obj.splice(i, 1); + return; + } + } + }; + EventEmitter.prototype.removeAllListeners = function (type) { + if (this._events[type]) { + delete this._events[type]; + } + }; + EventEmitter.prototype.once = function (type, listener) { + function on() { + var args = Array.prototype.slice.call(arguments); + this.off(type, on); + listener.apply(this, args); + } + on.listener = listener; + this.on(type, on); + }; + EventEmitter.prototype.emit = function (type) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + if (!this._events[type]) { + return; + } + var obj = this._events[type]; + for (var i = 0; i < obj.length; i++) { + obj[i].apply(this, args); + } + }; + EventEmitter.prototype.listeners = function (type) { + return this._events[type] || []; + }; + EventEmitter.prototype.destroy = function () { + this._events = {}; + }; + return EventEmitter; +}()); +exports.EventEmitter = EventEmitter; + + + +},{}],8:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var EscapeSequences_1 = require("./EscapeSequences"); +var Charsets_1 = require("./Charsets"); +var Buffer_1 = require("./Buffer"); +var Types_1 = require("./renderer/Types"); +var CharWidth_1 = require("./CharWidth"); +var InputHandler = (function () { + function InputHandler(_terminal) { + this._terminal = _terminal; + } + InputHandler.prototype.addChar = function (char, code) { + if (char >= ' ') { + var ch_width = CharWidth_1.wcwidth(code); + if (this._terminal.charset && this._terminal.charset[char]) { + char = this._terminal.charset[char]; + } + var row = this._terminal.buffer.y + this._terminal.buffer.ybase; + if (!ch_width && this._terminal.buffer.x) { + if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) { + if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][Buffer_1.CHAR_DATA_WIDTH_INDEX]) { + if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) { + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][Buffer_1.CHAR_DATA_CHAR_INDEX] += char; + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][3] = char.charCodeAt(0); + } + } + else { + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][Buffer_1.CHAR_DATA_CHAR_INDEX] += char; + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][3] = char.charCodeAt(0); + } + this._terminal.updateRange(this._terminal.buffer.y); + } + return; + } + if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) { + if (this._terminal.wraparoundMode) { + this._terminal.buffer.x = 0; + this._terminal.buffer.y++; + if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { + this._terminal.buffer.y--; + this._terminal.scroll(true); + } + else { + this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true; + } + } + else { + if (ch_width === 2) + return; + } + } + row = this._terminal.buffer.y + this._terminal.buffer.ybase; + if (this._terminal.insertMode) { + for (var moves = 0; moves < ch_width; ++moves) { + var removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop(); + if (removed[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0 + && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] + && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][Buffer_1.CHAR_DATA_WIDTH_INDEX] === 2) { + this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]; + } + this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]); + } + } + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width, char.charCodeAt(0)]; + this._terminal.buffer.x++; + this._terminal.updateRange(this._terminal.buffer.y); + if (ch_width === 2) { + this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0, undefined]; + this._terminal.buffer.x++; + } + } + }; + InputHandler.prototype.bell = function () { + this._terminal.bell(); + }; + InputHandler.prototype.lineFeed = function () { + if (this._terminal.convertEol) { + this._terminal.buffer.x = 0; + } + this._terminal.buffer.y++; + if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { + this._terminal.buffer.y--; + this._terminal.scroll(); + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + this._terminal.emit('lineFeed'); + }; + InputHandler.prototype.carriageReturn = function () { + this._terminal.buffer.x = 0; + }; + InputHandler.prototype.backspace = function () { + if (this._terminal.buffer.x > 0) { + this._terminal.buffer.x--; + } + }; + InputHandler.prototype.tab = function () { + this._terminal.buffer.x = this._terminal.buffer.nextStop(); + }; + InputHandler.prototype.shiftOut = function () { + this._terminal.setgLevel(1); + }; + InputHandler.prototype.shiftIn = function () { + this._terminal.setgLevel(0); + }; + InputHandler.prototype.insertChars = function (params) { + var param = params[0]; + if (param < 1) + param = 1; + var row = this._terminal.buffer.y + this._terminal.buffer.ybase; + var j = this._terminal.buffer.x; + var ch = [this._terminal.eraseAttr(), ' ', 1, 32]; + while (param-- && j < this._terminal.cols) { + this._terminal.buffer.lines.get(row).splice(j++, 0, ch); + this._terminal.buffer.lines.get(row).pop(); + } + }; + InputHandler.prototype.cursorUp = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y -= param; + if (this._terminal.buffer.y < 0) { + this._terminal.buffer.y = 0; + } + }; + InputHandler.prototype.cursorDown = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y += param; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + }; + InputHandler.prototype.cursorForward = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x += param; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.cursorBackward = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + this._terminal.buffer.x -= param; + if (this._terminal.buffer.x < 0) { + this._terminal.buffer.x = 0; + } + }; + InputHandler.prototype.cursorNextLine = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y += param; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + this._terminal.buffer.x = 0; + }; + InputHandler.prototype.cursorPrecedingLine = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y -= param; + if (this._terminal.buffer.y < 0) { + this._terminal.buffer.y = 0; + } + this._terminal.buffer.x = 0; + }; + InputHandler.prototype.cursorCharAbsolute = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x = param - 1; + }; + InputHandler.prototype.cursorPosition = function (params) { + var col; + var row = params[0] - 1; + if (params.length >= 2) { + col = params[1] - 1; + } + else { + col = 0; + } + if (row < 0) { + row = 0; + } + else if (row >= this._terminal.rows) { + row = this._terminal.rows - 1; + } + if (col < 0) { + col = 0; + } + else if (col >= this._terminal.cols) { + col = this._terminal.cols - 1; + } + this._terminal.buffer.x = col; + this._terminal.buffer.y = row; + }; + InputHandler.prototype.cursorForwardTab = function (params) { + var param = params[0] || 1; + while (param--) { + this._terminal.buffer.x = this._terminal.buffer.nextStop(); + } + }; + InputHandler.prototype.eraseInDisplay = function (params) { + var j; + switch (params[0]) { + case 0: + this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); + j = this._terminal.buffer.y + 1; + for (; j < this._terminal.rows; j++) { + this._terminal.eraseLine(j); + } + break; + case 1: + this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); + j = this._terminal.buffer.y; + while (j--) { + this._terminal.eraseLine(j); + } + break; + case 2: + j = this._terminal.rows; + while (j--) + this._terminal.eraseLine(j); + break; + case 3: + var scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows; + if (scrollBackSize > 0) { + this._terminal.buffer.lines.trimStart(scrollBackSize); + this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0); + this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0); + this._terminal.emit('scroll', 0); + } + break; + } + }; + InputHandler.prototype.eraseInLine = function (params) { + switch (params[0]) { + case 0: + this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); + break; + case 1: + this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); + break; + case 2: + this._terminal.eraseLine(this._terminal.buffer.y); + break; + } + }; + InputHandler.prototype.insertLines = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var row = this._terminal.buffer.y + this._terminal.buffer.ybase; + var scrollBottomRowsOffset = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; + var scrollBottomAbsolute = this._terminal.rows - 1 + this._terminal.buffer.ybase - scrollBottomRowsOffset + 1; + while (param--) { + this._terminal.buffer.lines.splice(scrollBottomAbsolute - 1, 1); + this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true)); + } + this._terminal.updateRange(this._terminal.buffer.y); + this._terminal.updateRange(this._terminal.buffer.scrollBottom); + }; + InputHandler.prototype.deleteLines = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var row = this._terminal.buffer.y + this._terminal.buffer.ybase; + var j; + j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom; + j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j; + while (param--) { + this._terminal.buffer.lines.splice(row, 1); + this._terminal.buffer.lines.splice(j, 0, this._terminal.blankLine(true)); + } + this._terminal.updateRange(this._terminal.buffer.y); + this._terminal.updateRange(this._terminal.buffer.scrollBottom); + }; + InputHandler.prototype.deleteChars = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var row = this._terminal.buffer.y + this._terminal.buffer.ybase; + var ch = [this._terminal.eraseAttr(), ' ', 1, 32]; + while (param--) { + this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1); + this._terminal.buffer.lines.get(row).push(ch); + } + this._terminal.updateRange(this._terminal.buffer.y); + }; + InputHandler.prototype.scrollUp = function (params) { + var param = params[0] || 1; + while (param--) { + this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1); + this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine()); + } + this._terminal.updateRange(this._terminal.buffer.scrollTop); + this._terminal.updateRange(this._terminal.buffer.scrollBottom); + }; + InputHandler.prototype.scrollDown = function (params) { + var param = params[0] || 1; + while (param--) { + this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1); + this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine()); + } + this._terminal.updateRange(this._terminal.buffer.scrollTop); + this._terminal.updateRange(this._terminal.buffer.scrollBottom); + }; + InputHandler.prototype.eraseChars = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var row = this._terminal.buffer.y + this._terminal.buffer.ybase; + var j = this._terminal.buffer.x; + var ch = [this._terminal.eraseAttr(), ' ', 1, 32]; + while (param-- && j < this._terminal.cols) { + this._terminal.buffer.lines.get(row)[j++] = ch; + } + }; + InputHandler.prototype.cursorBackwardTab = function (params) { + var param = params[0] || 1; + while (param--) { + this._terminal.buffer.x = this._terminal.buffer.prevStop(); + } + }; + InputHandler.prototype.charPosAbsolute = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x = param - 1; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.HPositionRelative = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x += param; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.repeatPrecedingCharacter = function (params) { + var param = params[0] || 1; + var line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y); + var ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1, 32]; + while (param--) { + line[this._terminal.buffer.x++] = ch; + } + }; + InputHandler.prototype.sendDeviceAttributes = function (params) { + if (params[0] > 0) { + return; + } + if (!this._terminal.prefix) { + if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[?1;2c'); + } + else if (this._terminal.is('linux')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[?6c'); + } + } + else if (this._terminal.prefix === '>') { + if (this._terminal.is('xterm')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[>0;276;0c'); + } + else if (this._terminal.is('rxvt-unicode')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[>85;95;0c'); + } + else if (this._terminal.is('linux')) { + this._terminal.send(params[0] + 'c'); + } + else if (this._terminal.is('screen')) { + this._terminal.send(EscapeSequences_1.C0.ESC + '[>83;40003;0c'); + } + } + }; + InputHandler.prototype.linePosAbsolute = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y = param - 1; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + }; + InputHandler.prototype.VPositionRelative = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y += param; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + }; + InputHandler.prototype.HVPosition = function (params) { + if (params[0] < 1) + params[0] = 1; + if (params[1] < 1) + params[1] = 1; + this._terminal.buffer.y = params[0] - 1; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + this._terminal.buffer.x = params[1] - 1; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.tabClear = function (params) { + var param = params[0]; + if (param <= 0) { + delete this._terminal.buffer.tabs[this._terminal.buffer.x]; + } + else if (param === 3) { + this._terminal.buffer.tabs = {}; + } + }; + InputHandler.prototype.setMode = function (params) { + if (params.length > 1) { + for (var i = 0; i < params.length; i++) { + this.setMode([params[i]]); + } + return; + } + if (!this._terminal.prefix) { + switch (params[0]) { + case 4: + this._terminal.insertMode = true; + break; + case 20: + break; + } + } + else if (this._terminal.prefix === '?') { + switch (params[0]) { + case 1: + this._terminal.applicationCursor = true; + break; + case 2: + this._terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); + this._terminal.setgCharset(1, Charsets_1.DEFAULT_CHARSET); + this._terminal.setgCharset(2, Charsets_1.DEFAULT_CHARSET); + this._terminal.setgCharset(3, Charsets_1.DEFAULT_CHARSET); + break; + case 3: + this._terminal.savedCols = this._terminal.cols; + this._terminal.resize(132, this._terminal.rows); + break; + case 6: + this._terminal.originMode = true; + break; + case 7: + this._terminal.wraparoundMode = true; + break; + case 12: + break; + case 66: + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + this._terminal.viewport.syncScrollArea(); + break; + case 9: + case 1000: + case 1002: + case 1003: + this._terminal.x10Mouse = params[0] === 9; + this._terminal.vt200Mouse = params[0] === 1000; + this._terminal.normalMouse = params[0] > 1000; + this._terminal.mouseEvents = true; + this._terminal.element.classList.add('enable-mouse-events'); + this._terminal.selectionManager.disable(); + this._terminal.log('Binding to mouse events.'); + break; + case 1004: + this._terminal.sendFocus = true; + break; + case 1005: + this._terminal.utfMouse = true; + break; + case 1006: + this._terminal.sgrMouse = true; + break; + case 1015: + this._terminal.urxvtMouse = true; + break; + case 25: + this._terminal.cursorHidden = false; + break; + case 1049: + case 47: + case 1047: + this._terminal.buffers.activateAltBuffer(); + this._terminal.selectionManager.setBuffer(this._terminal.buffer); + this._terminal.viewport.syncScrollArea(); + this._terminal.showCursor(); + break; + case 2004: + this._terminal.bracketedPasteMode = true; + break; + } + } + }; + InputHandler.prototype.resetMode = function (params) { + if (params.length > 1) { + for (var i = 0; i < params.length; i++) { + this.resetMode([params[i]]); + } + return; + } + if (!this._terminal.prefix) { + switch (params[0]) { + case 4: + this._terminal.insertMode = false; + break; + case 20: + break; + } + } + else if (this._terminal.prefix === '?') { + switch (params[0]) { + case 1: + this._terminal.applicationCursor = false; + break; + case 3: + if (this._terminal.cols === 132 && this._terminal.savedCols) { + this._terminal.resize(this._terminal.savedCols, this._terminal.rows); + } + delete this._terminal.savedCols; + break; + case 6: + this._terminal.originMode = false; + break; + case 7: + this._terminal.wraparoundMode = false; + break; + case 12: + break; + case 66: + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + this._terminal.viewport.syncScrollArea(); + break; + case 9: + case 1000: + case 1002: + case 1003: + this._terminal.x10Mouse = false; + this._terminal.vt200Mouse = false; + this._terminal.normalMouse = false; + this._terminal.mouseEvents = false; + this._terminal.element.classList.remove('enable-mouse-events'); + this._terminal.selectionManager.enable(); + break; + case 1004: + this._terminal.sendFocus = false; + break; + case 1005: + this._terminal.utfMouse = false; + break; + case 1006: + this._terminal.sgrMouse = false; + break; + case 1015: + this._terminal.urxvtMouse = false; + break; + case 25: + this._terminal.cursorHidden = true; + break; + case 1049: + case 47: + case 1047: + this._terminal.buffers.activateNormalBuffer(); + this._terminal.selectionManager.setBuffer(this._terminal.buffer); + this._terminal.refresh(0, this._terminal.rows - 1); + this._terminal.viewport.syncScrollArea(); + this._terminal.showCursor(); + break; + case 2004: + this._terminal.bracketedPasteMode = false; + break; + } + } + }; + InputHandler.prototype.charAttributes = function (params) { + if (params.length === 1 && params[0] === 0) { + this._terminal.curAttr = this._terminal.defAttr; + return; + } + var l = params.length; + var flags = this._terminal.curAttr >> 18; + var fg = (this._terminal.curAttr >> 9) & 0x1ff; + var bg = this._terminal.curAttr & 0x1ff; + var p; + for (var i = 0; i < l; i++) { + p = params[i]; + if (p >= 30 && p <= 37) { + fg = p - 30; + } + else if (p >= 40 && p <= 47) { + bg = p - 40; + } + else if (p >= 90 && p <= 97) { + p += 8; + fg = p - 90; + } + else if (p >= 100 && p <= 107) { + p += 8; + bg = p - 100; + } + else if (p === 0) { + flags = this._terminal.defAttr >> 18; + fg = (this._terminal.defAttr >> 9) & 0x1ff; + bg = this._terminal.defAttr & 0x1ff; + } + else if (p === 1) { + flags |= Types_1.FLAGS.BOLD; + } + else if (p === 4) { + flags |= Types_1.FLAGS.UNDERLINE; + } + else if (p === 5) { + flags |= Types_1.FLAGS.BLINK; + } + else if (p === 7) { + flags |= Types_1.FLAGS.INVERSE; + } + else if (p === 8) { + flags |= Types_1.FLAGS.INVISIBLE; + } + else if (p === 2) { + flags |= Types_1.FLAGS.DIM; + } + else if (p === 22) { + flags &= ~Types_1.FLAGS.BOLD; + flags &= ~Types_1.FLAGS.DIM; + } + else if (p === 24) { + flags &= ~Types_1.FLAGS.UNDERLINE; + } + else if (p === 25) { + flags &= ~Types_1.FLAGS.BLINK; + } + else if (p === 27) { + flags &= ~Types_1.FLAGS.INVERSE; + } + else if (p === 28) { + flags &= ~Types_1.FLAGS.INVISIBLE; + } + else if (p === 39) { + fg = (this._terminal.defAttr >> 9) & 0x1ff; + } + else if (p === 49) { + bg = this._terminal.defAttr & 0x1ff; + } + else if (p === 38) { + if (params[i + 1] === 2) { + i += 2; + fg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); + if (fg === -1) + fg = 0x1ff; + i += 2; + } + else if (params[i + 1] === 5) { + i += 2; + p = params[i] & 0xff; + fg = p; + } + } + else if (p === 48) { + if (params[i + 1] === 2) { + i += 2; + bg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff); + if (bg === -1) + bg = 0x1ff; + i += 2; + } + else if (params[i + 1] === 5) { + i += 2; + p = params[i] & 0xff; + bg = p; + } + } + else if (p === 100) { + fg = (this._terminal.defAttr >> 9) & 0x1ff; + bg = this._terminal.defAttr & 0x1ff; + } + else { + this._terminal.error('Unknown SGR attribute: %d.', p); + } + } + this._terminal.curAttr = (flags << 18) | (fg << 9) | bg; + }; + InputHandler.prototype.deviceStatus = function (params) { + if (!this._terminal.prefix) { + switch (params[0]) { + case 5: + this._terminal.send(EscapeSequences_1.C0.ESC + '[0n'); + break; + case 6: + this._terminal.send(EscapeSequences_1.C0.ESC + '[' + + (this._terminal.buffer.y + 1) + + ';' + + (this._terminal.buffer.x + 1) + + 'R'); + break; + } + } + else if (this._terminal.prefix === '?') { + switch (params[0]) { + case 6: + this._terminal.send(EscapeSequences_1.C0.ESC + '[?' + + (this._terminal.buffer.y + 1) + + ';' + + (this._terminal.buffer.x + 1) + + 'R'); + break; + case 15: + break; + case 25: + break; + case 26: + break; + case 53: + break; + } + } + }; + InputHandler.prototype.softReset = function (params) { + this._terminal.cursorHidden = false; + this._terminal.insertMode = false; + this._terminal.originMode = false; + this._terminal.wraparoundMode = true; + this._terminal.applicationKeypad = false; + this._terminal.viewport.syncScrollArea(); + this._terminal.applicationCursor = false; + this._terminal.buffer.scrollTop = 0; + this._terminal.buffer.scrollBottom = this._terminal.rows - 1; + this._terminal.curAttr = this._terminal.defAttr; + this._terminal.buffer.x = this._terminal.buffer.y = 0; + this._terminal.charset = null; + this._terminal.glevel = 0; + this._terminal.charsets = [null]; + }; + InputHandler.prototype.setCursorStyle = function (params) { + var param = params[0] < 1 ? 1 : params[0]; + switch (param) { + case 1: + case 2: + this._terminal.setOption('cursorStyle', 'block'); + break; + case 3: + case 4: + this._terminal.setOption('cursorStyle', 'underline'); + break; + case 5: + case 6: + this._terminal.setOption('cursorStyle', 'bar'); + break; + } + var isBlinking = param % 2 === 1; + this._terminal.setOption('cursorBlink', isBlinking); + }; + InputHandler.prototype.setScrollRegion = function (params) { + if (this._terminal.prefix) + return; + this._terminal.buffer.scrollTop = (params[0] || 1) - 1; + this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1; + this._terminal.buffer.x = 0; + this._terminal.buffer.y = 0; + }; + InputHandler.prototype.saveCursor = function (params) { + this._terminal.buffer.savedX = this._terminal.buffer.x; + this._terminal.buffer.savedY = this._terminal.buffer.y; + }; + InputHandler.prototype.restoreCursor = function (params) { + this._terminal.buffer.x = this._terminal.buffer.savedX || 0; + this._terminal.buffer.y = this._terminal.buffer.savedY || 0; + }; + return InputHandler; +}()); +exports.InputHandler = InputHandler; + + + +},{"./Buffer":1,"./CharWidth":3,"./Charsets":4,"./EscapeSequences":6,"./renderer/Types":27}],9:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("./Types"); +var MouseZoneManager_1 = require("./input/MouseZoneManager"); +var EventEmitter_1 = require("./EventEmitter"); +var protocolClause = '(https?:\\/\\/)'; +var domainCharacterSet = '[\\da-z\\.-]+'; +var negatedDomainCharacterSet = '[^\\da-z\\.-]+'; +var domainBodyClause = '(' + domainCharacterSet + ')'; +var tldClause = '([a-z\\.]{2,6})'; +var ipClause = '((\\d{1,3}\\.){3}\\d{1,3})'; +var localHostClause = '(localhost)'; +var portClause = '(:\\d{1,5})'; +var hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?'; +var pathClause = '(\\/[\\/\\w\\.\\-%~]*)*'; +var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*'; +var queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?'; +var hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?'; +var negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+'; +var bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause; +var start = '(?:^|' + negatedDomainCharacterSet + ')('; +var end = ')($|' + negatedPathCharacterSet + ')'; +var strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end); +var HYPERTEXT_LINK_MATCHER_ID = 0; +var Linkifier = (function (_super) { + __extends(Linkifier, _super); + function Linkifier(_terminal) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._linkMatchers = []; + _this._nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID; + _this._rowsToLinkify = { + start: null, + end: null + }; + _this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 }); + return _this; + } + Linkifier.prototype.attachToDom = function (mouseZoneManager) { + this._mouseZoneManager = mouseZoneManager; + }; + Linkifier.prototype.linkifyRows = function (start, end) { + var _this = this; + if (!this._mouseZoneManager) { + return; + } + if (!this._rowsToLinkify.start) { + this._rowsToLinkify.start = start; + this._rowsToLinkify.end = end; + } + else { + this._rowsToLinkify.start = this._rowsToLinkify.start < start ? this._rowsToLinkify.start : start; + this._rowsToLinkify.end = this._rowsToLinkify.end > end ? this._rowsToLinkify.end : end; + } + this._mouseZoneManager.clearAll(start, end); + if (this._rowsTimeoutId) { + clearTimeout(this._rowsTimeoutId); + } + this._rowsTimeoutId = setTimeout(function () { return _this._linkifyRows(); }, Linkifier.TIME_BEFORE_LINKIFY); + }; + Linkifier.prototype._linkifyRows = function () { + this._rowsTimeoutId = null; + for (var i = this._rowsToLinkify.start; i <= this._rowsToLinkify.end; i++) { + this._linkifyRow(i); + } + this._rowsToLinkify.start = null; + this._rowsToLinkify.end = null; + }; + Linkifier.prototype.setHypertextLinkHandler = function (handler) { + this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler; + }; + Linkifier.prototype.setHypertextValidationCallback = function (callback) { + this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback; + }; + Linkifier.prototype.registerLinkMatcher = function (regex, handler, options) { + if (options === void 0) { options = {}; } + if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) { + throw new Error('handler must be defined'); + } + var matcher = { + id: this._nextLinkMatcherId++, + regex: regex, + handler: handler, + matchIndex: options.matchIndex, + validationCallback: options.validationCallback, + hoverTooltipCallback: options.tooltipCallback, + hoverLeaveCallback: options.leaveCallback, + priority: options.priority || 0 + }; + this._addLinkMatcherToList(matcher); + return matcher.id; + }; + Linkifier.prototype._addLinkMatcherToList = function (matcher) { + if (this._linkMatchers.length === 0) { + this._linkMatchers.push(matcher); + return; + } + for (var i = this._linkMatchers.length - 1; i >= 0; i--) { + if (matcher.priority <= this._linkMatchers[i].priority) { + this._linkMatchers.splice(i + 1, 0, matcher); + return; + } + } + this._linkMatchers.splice(0, 0, matcher); + }; + Linkifier.prototype.deregisterLinkMatcher = function (matcherId) { + for (var i = 1; i < this._linkMatchers.length; i++) { + if (this._linkMatchers[i].id === matcherId) { + this._linkMatchers.splice(i, 1); + return true; + } + } + return false; + }; + Linkifier.prototype._linkifyRow = function (rowIndex) { + var absoluteRowIndex = this._terminal.buffer.ydisp + rowIndex; + if (absoluteRowIndex >= this._terminal.buffer.lines.length) { + return; + } + var text = this._terminal.buffer.translateBufferLineToString(absoluteRowIndex, false); + for (var i = 0; i < this._linkMatchers.length; i++) { + this._doLinkifyRow(rowIndex, text, this._linkMatchers[i]); + } + }; + Linkifier.prototype._doLinkifyRow = function (rowIndex, text, matcher, offset) { + var _this = this; + if (offset === void 0) { offset = 0; } + var result = []; + var isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID; + var match = text.match(matcher.regex); + if (!match || match.length === 0) { + return; + } + var uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; + var index = text.indexOf(uri); + if (matcher.validationCallback) { + matcher.validationCallback(uri, function (isValid) { + if (_this._rowsTimeoutId) { + return; + } + if (isValid) { + _this._addLink(offset + index, rowIndex, uri, matcher); + } + }); + } + else { + this._addLink(offset + index, rowIndex, uri, matcher); + } + var remainingStartIndex = index + uri.length; + var remainingText = text.substr(remainingStartIndex); + if (remainingText.length > 0) { + this._doLinkifyRow(rowIndex, remainingText, matcher, offset + remainingStartIndex); + } + }; + Linkifier.prototype._addLink = function (x, y, uri, matcher) { + var _this = this; + this._mouseZoneManager.add(new MouseZoneManager_1.MouseZone(x + 1, x + 1 + uri.length, y + 1, function (e) { + if (matcher.handler) { + return matcher.handler(e, uri); + } + window.open(uri, '_blank'); + }, function (e) { + _this.emit(Types_1.LinkHoverEventTypes.HOVER, { x: x, y: y, length: uri.length }); + _this._terminal.element.style.cursor = 'pointer'; + }, function (e) { + _this.emit(Types_1.LinkHoverEventTypes.TOOLTIP, { x: x, y: y, length: uri.length }); + if (matcher.hoverTooltipCallback) { + matcher.hoverTooltipCallback(e, uri); + } + }, function () { + _this.emit(Types_1.LinkHoverEventTypes.LEAVE, { x: x, y: y, length: uri.length }); + _this._terminal.element.style.cursor = ''; + if (matcher.hoverLeaveCallback) { + matcher.hoverLeaveCallback(); + } + })); + }; + Linkifier.TIME_BEFORE_LINKIFY = 200; + return Linkifier; +}(EventEmitter_1.EventEmitter)); +exports.Linkifier = Linkifier; + + + +},{"./EventEmitter":7,"./Types":14,"./input/MouseZoneManager":17}],10:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var EscapeSequences_1 = require("./EscapeSequences"); +var Charsets_1 = require("./Charsets"); +var normalStateHandler = {}; +normalStateHandler[EscapeSequences_1.C0.BEL] = function (parser, handler) { return handler.bell(); }; +normalStateHandler[EscapeSequences_1.C0.LF] = function (parser, handler) { return handler.lineFeed(); }; +normalStateHandler[EscapeSequences_1.C0.VT] = normalStateHandler[EscapeSequences_1.C0.LF]; +normalStateHandler[EscapeSequences_1.C0.FF] = normalStateHandler[EscapeSequences_1.C0.LF]; +normalStateHandler[EscapeSequences_1.C0.CR] = function (parser, handler) { return handler.carriageReturn(); }; +normalStateHandler[EscapeSequences_1.C0.BS] = function (parser, handler) { return handler.backspace(); }; +normalStateHandler[EscapeSequences_1.C0.HT] = function (parser, handler) { return handler.tab(); }; +normalStateHandler[EscapeSequences_1.C0.SO] = function (parser, handler) { return handler.shiftOut(); }; +normalStateHandler[EscapeSequences_1.C0.SI] = function (parser, handler) { return handler.shiftIn(); }; +normalStateHandler[EscapeSequences_1.C0.ESC] = function (parser, handler) { return parser.setState(ParserState.ESCAPED); }; +var escapedStateHandler = {}; +escapedStateHandler['['] = function (parser, terminal) { + terminal.params = []; + terminal.currentParam = 0; + parser.setState(ParserState.CSI_PARAM); +}; +escapedStateHandler[']'] = function (parser, terminal) { + terminal.params = []; + terminal.currentParam = 0; + parser.setState(ParserState.OSC); +}; +escapedStateHandler['P'] = function (parser, terminal) { + terminal.params = []; + terminal.currentParam = 0; + parser.setState(ParserState.DCS); +}; +escapedStateHandler['_'] = function (parser, terminal) { + parser.setState(ParserState.IGNORE); +}; +escapedStateHandler['^'] = function (parser, terminal) { + parser.setState(ParserState.IGNORE); +}; +escapedStateHandler['c'] = function (parser, terminal) { + terminal.reset(); +}; +escapedStateHandler['E'] = function (parser, terminal) { + terminal.buffer.x = 0; + terminal.index(); + parser.setState(ParserState.NORMAL); +}; +escapedStateHandler['D'] = function (parser, terminal) { + terminal.index(); + parser.setState(ParserState.NORMAL); +}; +escapedStateHandler['M'] = function (parser, terminal) { + terminal.reverseIndex(); + parser.setState(ParserState.NORMAL); +}; +escapedStateHandler['%'] = function (parser, terminal) { + terminal.setgLevel(0); + terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); + parser.setState(ParserState.NORMAL); + parser.skipNextChar(); +}; +escapedStateHandler[EscapeSequences_1.C0.CAN] = function (parser) { return parser.setState(ParserState.NORMAL); }; +var csiParamStateHandler = {}; +csiParamStateHandler['?'] = function (parser) { return parser.setPrefix('?'); }; +csiParamStateHandler['>'] = function (parser) { return parser.setPrefix('>'); }; +csiParamStateHandler['!'] = function (parser) { return parser.setPrefix('!'); }; +csiParamStateHandler['0'] = function (parser) { return parser.setParam(parser.getParam() * 10); }; +csiParamStateHandler['1'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 1); }; +csiParamStateHandler['2'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 2); }; +csiParamStateHandler['3'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 3); }; +csiParamStateHandler['4'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 4); }; +csiParamStateHandler['5'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 5); }; +csiParamStateHandler['6'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 6); }; +csiParamStateHandler['7'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 7); }; +csiParamStateHandler['8'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 8); }; +csiParamStateHandler['9'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 9); }; +csiParamStateHandler['$'] = function (parser) { return parser.setPostfix('$'); }; +csiParamStateHandler['"'] = function (parser) { return parser.setPostfix('"'); }; +csiParamStateHandler[' '] = function (parser) { return parser.setPostfix(' '); }; +csiParamStateHandler['\''] = function (parser) { return parser.setPostfix('\''); }; +csiParamStateHandler[';'] = function (parser) { return parser.finalizeParam(); }; +csiParamStateHandler[EscapeSequences_1.C0.CAN] = function (parser) { return parser.setState(ParserState.NORMAL); }; +var csiStateHandler = {}; +csiStateHandler['@'] = function (handler, params, prefix) { return handler.insertChars(params); }; +csiStateHandler['A'] = function (handler, params, prefix) { return handler.cursorUp(params); }; +csiStateHandler['B'] = function (handler, params, prefix) { return handler.cursorDown(params); }; +csiStateHandler['C'] = function (handler, params, prefix) { return handler.cursorForward(params); }; +csiStateHandler['D'] = function (handler, params, prefix) { return handler.cursorBackward(params); }; +csiStateHandler['E'] = function (handler, params, prefix) { return handler.cursorNextLine(params); }; +csiStateHandler['F'] = function (handler, params, prefix) { return handler.cursorPrecedingLine(params); }; +csiStateHandler['G'] = function (handler, params, prefix) { return handler.cursorCharAbsolute(params); }; +csiStateHandler['H'] = function (handler, params, prefix) { return handler.cursorPosition(params); }; +csiStateHandler['I'] = function (handler, params, prefix) { return handler.cursorForwardTab(params); }; +csiStateHandler['J'] = function (handler, params, prefix) { return handler.eraseInDisplay(params); }; +csiStateHandler['K'] = function (handler, params, prefix) { return handler.eraseInLine(params); }; +csiStateHandler['L'] = function (handler, params, prefix) { return handler.insertLines(params); }; +csiStateHandler['M'] = function (handler, params, prefix) { return handler.deleteLines(params); }; +csiStateHandler['P'] = function (handler, params, prefix) { return handler.deleteChars(params); }; +csiStateHandler['S'] = function (handler, params, prefix) { return handler.scrollUp(params); }; +csiStateHandler['T'] = function (handler, params, prefix) { + if (params.length < 2 && !prefix) { + handler.scrollDown(params); + } +}; +csiStateHandler['X'] = function (handler, params, prefix) { return handler.eraseChars(params); }; +csiStateHandler['Z'] = function (handler, params, prefix) { return handler.cursorBackwardTab(params); }; +csiStateHandler['`'] = function (handler, params, prefix) { return handler.charPosAbsolute(params); }; +csiStateHandler['a'] = function (handler, params, prefix) { return handler.HPositionRelative(params); }; +csiStateHandler['b'] = function (handler, params, prefix) { return handler.repeatPrecedingCharacter(params); }; +csiStateHandler['c'] = function (handler, params, prefix) { return handler.sendDeviceAttributes(params); }; +csiStateHandler['d'] = function (handler, params, prefix) { return handler.linePosAbsolute(params); }; +csiStateHandler['e'] = function (handler, params, prefix) { return handler.VPositionRelative(params); }; +csiStateHandler['f'] = function (handler, params, prefix) { return handler.HVPosition(params); }; +csiStateHandler['g'] = function (handler, params, prefix) { return handler.tabClear(params); }; +csiStateHandler['h'] = function (handler, params, prefix) { return handler.setMode(params); }; +csiStateHandler['l'] = function (handler, params, prefix) { return handler.resetMode(params); }; +csiStateHandler['m'] = function (handler, params, prefix) { return handler.charAttributes(params); }; +csiStateHandler['n'] = function (handler, params, prefix) { return handler.deviceStatus(params); }; +csiStateHandler['p'] = function (handler, params, prefix) { + switch (prefix) { + case '!': + handler.softReset(params); + break; + } +}; +csiStateHandler['q'] = function (handler, params, prefix, postfix) { + if (postfix === ' ') { + handler.setCursorStyle(params); + } +}; +csiStateHandler['r'] = function (handler, params) { return handler.setScrollRegion(params); }; +csiStateHandler['s'] = function (handler, params) { return handler.saveCursor(params); }; +csiStateHandler['u'] = function (handler, params) { return handler.restoreCursor(params); }; +csiStateHandler[EscapeSequences_1.C0.CAN] = function (handler, params, prefix, postfix, parser) { return parser.setState(ParserState.NORMAL); }; +var ParserState; +(function (ParserState) { + ParserState[ParserState["NORMAL"] = 0] = "NORMAL"; + ParserState[ParserState["ESCAPED"] = 1] = "ESCAPED"; + ParserState[ParserState["CSI_PARAM"] = 2] = "CSI_PARAM"; + ParserState[ParserState["CSI"] = 3] = "CSI"; + ParserState[ParserState["OSC"] = 4] = "OSC"; + ParserState[ParserState["CHARSET"] = 5] = "CHARSET"; + ParserState[ParserState["DCS"] = 6] = "DCS"; + ParserState[ParserState["IGNORE"] = 7] = "IGNORE"; +})(ParserState = exports.ParserState || (exports.ParserState = {})); +var Parser = (function () { + function Parser(_inputHandler, _terminal) { + this._inputHandler = _inputHandler; + this._terminal = _terminal; + this._state = ParserState.NORMAL; + } + Parser.prototype.parse = function (data) { + var l = data.length; + var j; + var cs; + var ch; + var code; + var low; + var cursorStartX = this._terminal.buffer.x; + var cursorStartY = this._terminal.buffer.y; + if (this._terminal.debug) { + this._terminal.log('data: ' + data); + } + this._position = 0; + if (this._terminal.surrogate_high) { + data = this._terminal.surrogate_high + data; + this._terminal.surrogate_high = ''; + } + for (; this._position < l; this._position++) { + ch = data[this._position]; + code = data.charCodeAt(this._position); + if (0xD800 <= code && code <= 0xDBFF) { + low = data.charCodeAt(this._position + 1); + if (isNaN(low)) { + this._terminal.surrogate_high = ch; + continue; + } + code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + ch += data.charAt(this._position + 1); + } + if (0xDC00 <= code && code <= 0xDFFF) + continue; + switch (this._state) { + case ParserState.NORMAL: + if (ch in normalStateHandler) { + normalStateHandler[ch](this, this._inputHandler); + } + else { + this._inputHandler.addChar(ch, code); + } + break; + case ParserState.ESCAPED: + if (ch in escapedStateHandler) { + escapedStateHandler[ch](this, this._terminal); + break; + } + switch (ch) { + case '(': + case ')': + case '*': + case '+': + case '-': + case '.': + switch (ch) { + case '(': + this._terminal.gcharset = 0; + break; + case ')': + this._terminal.gcharset = 1; + break; + case '*': + this._terminal.gcharset = 2; + break; + case '+': + this._terminal.gcharset = 3; + break; + case '-': + this._terminal.gcharset = 1; + break; + case '.': + this._terminal.gcharset = 2; + break; + } + this._state = ParserState.CHARSET; + break; + case '/': + this._terminal.gcharset = 3; + this._state = ParserState.CHARSET; + this._position--; + break; + case 'N': + break; + case 'O': + break; + case 'n': + this._terminal.setgLevel(2); + break; + case 'o': + this._terminal.setgLevel(3); + break; + case '|': + this._terminal.setgLevel(3); + break; + case '}': + this._terminal.setgLevel(2); + break; + case '~': + this._terminal.setgLevel(1); + break; + case '7': + this._inputHandler.saveCursor(); + this._state = ParserState.NORMAL; + break; + case '8': + this._inputHandler.restoreCursor(); + this._state = ParserState.NORMAL; + break; + case '#': + this._state = ParserState.NORMAL; + this._position++; + break; + case 'H': + this._terminal.tabSet(); + this._state = ParserState.NORMAL; + break; + case '=': + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + this._state = ParserState.NORMAL; + break; + case '>': + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + this._state = ParserState.NORMAL; + break; + default: + this._state = ParserState.NORMAL; + this._terminal.error('Unknown ESC control: %s.', ch); + break; + } + break; + case ParserState.CHARSET: + if (ch in Charsets_1.CHARSETS) { + cs = Charsets_1.CHARSETS[ch]; + if (ch === '/') { + this.skipNextChar(); + } + } + else { + cs = Charsets_1.DEFAULT_CHARSET; + } + this._terminal.setgCharset(this._terminal.gcharset, cs); + this._terminal.gcharset = null; + this._state = ParserState.NORMAL; + break; + case ParserState.OSC: + if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) { + if (ch === EscapeSequences_1.C0.ESC) + this._position++; + this._terminal.params.push(this._terminal.currentParam); + switch (this._terminal.params[0]) { + case 0: + case 1: + case 2: + if (this._terminal.params[1]) { + this._terminal.title = this._terminal.params[1]; + this._terminal.handleTitle(this._terminal.title); + } + break; + case 3: + break; + case 4: + case 5: + break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + break; + case 46: + break; + case 50: + break; + case 51: + break; + case 52: + break; + case 104: + case 105: + case 110: + case 111: + case 112: + case 113: + case 114: + case 115: + case 116: + case 117: + case 118: + break; + } + this._terminal.params = []; + this._terminal.currentParam = 0; + this._state = ParserState.NORMAL; + } + else { + if (!this._terminal.params.length) { + if (ch >= '0' && ch <= '9') { + this._terminal.currentParam = + this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48; + } + else if (ch === ';') { + this._terminal.params.push(this._terminal.currentParam); + this._terminal.currentParam = ''; + } + } + else { + this._terminal.currentParam += ch; + } + } + break; + case ParserState.CSI_PARAM: + if (ch in csiParamStateHandler) { + csiParamStateHandler[ch](this); + break; + } + this.finalizeParam(); + this._state = ParserState.CSI; + case ParserState.CSI: + if (ch in csiStateHandler) { + if (this._terminal.debug) { + this._terminal.log("CSI " + (this._terminal.prefix ? this._terminal.prefix : '') + " " + (this._terminal.params ? this._terminal.params.join(';') : '') + " " + (this._terminal.postfix ? this._terminal.postfix : '') + " " + ch); + } + csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this); + } + else { + this._terminal.error('Unknown CSI code: %s.', ch); + } + this._state = ParserState.NORMAL; + this._terminal.prefix = ''; + this._terminal.postfix = ''; + break; + case ParserState.DCS: + if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) { + if (ch === EscapeSequences_1.C0.ESC) + this._position++; + var pt = void 0; + var valid = void 0; + switch (this._terminal.prefix) { + case '': + break; + case '$q': + pt = this._terminal.currentParam; + valid = false; + switch (pt) { + case '"q': + pt = '0"q'; + break; + case '"p': + pt = '61"p'; + break; + case 'r': + pt = '' + + (this._terminal.buffer.scrollTop + 1) + + ';' + + (this._terminal.buffer.scrollBottom + 1) + + 'r'; + break; + case 'm': + pt = '0m'; + break; + default: + this._terminal.error('Unknown DCS Pt: %s.', pt); + pt = ''; + break; + } + this._terminal.send(EscapeSequences_1.C0.ESC + 'P' + +valid + '$r' + pt + EscapeSequences_1.C0.ESC + '\\'); + break; + case '+p': + break; + case '+q': + pt = this._terminal.currentParam; + valid = false; + this._terminal.send(EscapeSequences_1.C0.ESC + 'P' + +valid + '+r' + pt + EscapeSequences_1.C0.ESC + '\\'); + break; + default: + this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix); + break; + } + this._terminal.currentParam = 0; + this._terminal.prefix = ''; + this._state = ParserState.NORMAL; + } + else if (!this._terminal.currentParam) { + if (!this._terminal.prefix && ch !== '$' && ch !== '+') { + this._terminal.currentParam = ch; + } + else if (this._terminal.prefix.length === 2) { + this._terminal.currentParam = ch; + } + else { + this._terminal.prefix += ch; + } + } + else { + this._terminal.currentParam += ch; + } + break; + case ParserState.IGNORE: + if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) { + if (ch === EscapeSequences_1.C0.ESC) + this._position++; + this._state = ParserState.NORMAL; + } + break; + } + } + if (this._terminal.buffer.x !== cursorStartX || this._terminal.buffer.y !== cursorStartY) { + this._terminal.emit('cursormove'); + } + return this._state; + }; + Parser.prototype.setState = function (state) { + this._state = state; + }; + Parser.prototype.setPrefix = function (prefix) { + this._terminal.prefix = prefix; + }; + Parser.prototype.setPostfix = function (postfix) { + this._terminal.postfix = postfix; + }; + Parser.prototype.setParam = function (param) { + this._terminal.currentParam = param; + }; + Parser.prototype.getParam = function () { + return this._terminal.currentParam; + }; + Parser.prototype.finalizeParam = function () { + this._terminal.params.push(this._terminal.currentParam); + this._terminal.currentParam = 0; + }; + Parser.prototype.skipNextChar = function () { + this._position++; + }; + return Parser; +}()); +exports.Parser = Parser; + + + +},{"./Charsets":4,"./EscapeSequences":6}],11:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var MouseHelper_1 = require("./utils/MouseHelper"); +var Browser = require("./utils/Browser"); +var EventEmitter_1 = require("./EventEmitter"); +var SelectionModel_1 = require("./SelectionModel"); +var Buffer_1 = require("./Buffer"); +var DRAG_SCROLL_MAX_THRESHOLD = 50; +var DRAG_SCROLL_MAX_SPEED = 15; +var DRAG_SCROLL_INTERVAL = 50; +var WORD_SEPARATORS = ' ()[]{}\'"'; +var NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); +var ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); +var SelectionMode; +(function (SelectionMode) { + SelectionMode[SelectionMode["NORMAL"] = 0] = "NORMAL"; + SelectionMode[SelectionMode["WORD"] = 1] = "WORD"; + SelectionMode[SelectionMode["LINE"] = 2] = "LINE"; +})(SelectionMode || (SelectionMode = {})); +var SelectionManager = (function (_super) { + __extends(SelectionManager, _super); + function SelectionManager(_terminal, _buffer, _charMeasure) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._buffer = _buffer; + _this._charMeasure = _charMeasure; + _this._enabled = true; + _this._initListeners(); + _this.enable(); + _this._model = new SelectionModel_1.SelectionModel(_terminal); + _this._activeSelectionMode = SelectionMode.NORMAL; + return _this; + } + SelectionManager.prototype._initListeners = function () { + var _this = this; + this._mouseMoveListener = function (event) { return _this._onMouseMove(event); }; + this._mouseUpListener = function (event) { return _this._onMouseUp(event); }; + this._buffer.lines.on('trim', function (amount) { return _this._onTrim(amount); }); + }; + SelectionManager.prototype.disable = function () { + this.clearSelection(); + this._enabled = false; + }; + SelectionManager.prototype.enable = function () { + this._enabled = true; + }; + SelectionManager.prototype.setBuffer = function (buffer) { + this._buffer = buffer; + this.clearSelection(); + }; + Object.defineProperty(SelectionManager.prototype, "selectionStart", { + get: function () { return this._model.finalSelectionStart; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "selectionEnd", { + get: function () { return this._model.finalSelectionEnd; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "hasSelection", { + get: function () { + var start = this._model.finalSelectionStart; + var end = this._model.finalSelectionEnd; + if (!start || !end) { + return false; + } + return start[0] !== end[0] || start[1] !== end[1]; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "selectionText", { + get: function () { + var start = this._model.finalSelectionStart; + var end = this._model.finalSelectionEnd; + if (!start || !end) { + return ''; + } + var startRowEndCol = start[1] === end[1] ? end[0] : null; + var result = []; + result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol)); + for (var i = start[1] + 1; i <= end[1] - 1; i++) { + var bufferLine = this._buffer.lines.get(i); + var lineText = this._buffer.translateBufferLineToString(i, true); + if (bufferLine.isWrapped) { + result[result.length - 1] += lineText; + } + else { + result.push(lineText); + } + } + if (start[1] !== end[1]) { + var bufferLine = this._buffer.lines.get(end[1]); + var lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]); + if (bufferLine.isWrapped) { + result[result.length - 1] += lineText; + } + else { + result.push(lineText); + } + } + var formattedResult = result.map(function (line) { + return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' '); + }).join(Browser.isMSWindows ? '\r\n' : '\n'); + return formattedResult; + }, + enumerable: true, + configurable: true + }); + SelectionManager.prototype.clearSelection = function () { + this._model.clearSelection(); + this._removeMouseDownListeners(); + this.refresh(); + }; + SelectionManager.prototype.refresh = function (isNewSelection) { + var _this = this; + if (!this._refreshAnimationFrame) { + this._refreshAnimationFrame = window.requestAnimationFrame(function () { return _this._refresh(); }); + } + if (Browser.isLinux && isNewSelection) { + var selectionText = this.selectionText; + if (selectionText.length) { + this.emit('newselection', this.selectionText); + } + } + }; + SelectionManager.prototype._refresh = function () { + this._refreshAnimationFrame = null; + this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd }); + }; + SelectionManager.prototype.selectAll = function () { + this._model.isSelectAllActive = true; + this.refresh(); + this.emit('selection'); + }; + SelectionManager.prototype._onTrim = function (amount) { + var needsRefresh = this._model.onTrim(amount); + if (needsRefresh) { + this.refresh(); + } + }; + SelectionManager.prototype._getMouseBufferCoords = function (event) { + var coords = this._terminal.mouseHelper.getCoords(event, this._terminal.element, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true); + if (!coords) { + return null; + } + coords[0]--; + coords[1]--; + coords[1] += this._terminal.buffer.ydisp; + return coords; + }; + SelectionManager.prototype._getMouseEventScrollAmount = function (event) { + var offset = MouseHelper_1.MouseHelper.getCoordsRelativeToElement(event, this._terminal.element)[1]; + var terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight); + if (offset >= 0 && offset <= terminalHeight) { + return 0; + } + if (offset > terminalHeight) { + offset -= terminalHeight; + } + offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD); + offset /= DRAG_SCROLL_MAX_THRESHOLD; + return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1)); + }; + SelectionManager.prototype.shouldForceSelection = function (event) { + return Browser.isMac ? event.altKey : event.shiftKey; + }; + SelectionManager.prototype.onMouseDown = function (event) { + if (event.button === 2 && this.hasSelection) { + return; + } + if (event.button !== 0) { + return; + } + if (!this._enabled) { + if (!this.shouldForceSelection(event)) { + return; + } + event.stopPropagation(); + } + event.preventDefault(); + this._dragScrollAmount = 0; + if (this._enabled && event.shiftKey) { + this._onIncrementalClick(event); + } + else { + if (event.detail === 1) { + this._onSingleClick(event); + } + else if (event.detail === 2) { + this._onDoubleClick(event); + } + else if (event.detail === 3) { + this._onTripleClick(event); + } + } + this._addMouseDownListeners(); + this.refresh(true); + }; + SelectionManager.prototype._addMouseDownListeners = function () { + var _this = this; + this._terminal.element.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.ownerDocument.addEventListener('mouseup', this._mouseUpListener); + this._dragScrollIntervalTimer = setInterval(function () { return _this._dragScroll(); }, DRAG_SCROLL_INTERVAL); + }; + SelectionManager.prototype._removeMouseDownListeners = function () { + this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); + clearInterval(this._dragScrollIntervalTimer); + this._dragScrollIntervalTimer = null; + }; + SelectionManager.prototype._onIncrementalClick = function (event) { + if (this._model.selectionStart) { + this._model.selectionEnd = this._getMouseBufferCoords(event); + } + }; + SelectionManager.prototype._onSingleClick = function (event) { + this._model.selectionStartLength = 0; + this._model.isSelectAllActive = false; + this._activeSelectionMode = SelectionMode.NORMAL; + this._model.selectionStart = this._getMouseBufferCoords(event); + if (!this._model.selectionStart) { + return; + } + this._model.selectionEnd = null; + var line = this._buffer.lines.get(this._model.selectionStart[1]); + if (!line) { + return; + } + if (line.length >= this._model.selectionStart[0]) { + return; + } + var char = line[this._model.selectionStart[0]]; + if (char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + this._model.selectionStart[0]++; + } + }; + SelectionManager.prototype._onDoubleClick = function (event) { + var coords = this._getMouseBufferCoords(event); + if (coords) { + this._activeSelectionMode = SelectionMode.WORD; + this._selectWordAt(coords); + } + }; + SelectionManager.prototype._onTripleClick = function (event) { + var coords = this._getMouseBufferCoords(event); + if (coords) { + this._activeSelectionMode = SelectionMode.LINE; + this._selectLineAt(coords[1]); + } + }; + SelectionManager.prototype._onMouseMove = function (event) { + event.stopImmediatePropagation(); + var previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null; + this._model.selectionEnd = this._getMouseBufferCoords(event); + if (!this._model.selectionEnd) { + this.refresh(true); + return; + } + if (this._activeSelectionMode === SelectionMode.LINE) { + if (this._model.selectionEnd[1] < this._model.selectionStart[1]) { + this._model.selectionEnd[0] = 0; + } + else { + this._model.selectionEnd[0] = this._terminal.cols; + } + } + else if (this._activeSelectionMode === SelectionMode.WORD) { + this._selectToWordAt(this._model.selectionEnd); + } + this._dragScrollAmount = this._getMouseEventScrollAmount(event); + if (this._dragScrollAmount > 0) { + this._model.selectionEnd[0] = this._terminal.cols; + } + else if (this._dragScrollAmount < 0) { + this._model.selectionEnd[0] = 0; + } + if (this._model.selectionEnd[1] < this._buffer.lines.length) { + var char = this._buffer.lines.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]]; + if (char && char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + this._model.selectionEnd[0]++; + } + } + if (!previousSelectionEnd || + previousSelectionEnd[0] !== this._model.selectionEnd[0] || + previousSelectionEnd[1] !== this._model.selectionEnd[1]) { + this.refresh(true); + } + }; + SelectionManager.prototype._dragScroll = function () { + if (this._dragScrollAmount) { + this._terminal.scrollLines(this._dragScrollAmount, false); + if (this._dragScrollAmount > 0) { + this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows]; + } + else { + this._model.selectionEnd = [0, this._terminal.buffer.ydisp]; + } + this.refresh(); + } + }; + SelectionManager.prototype._onMouseUp = function (event) { + this._removeMouseDownListeners(); + if (this.hasSelection) + this.emit('selection'); + }; + SelectionManager.prototype._convertViewportColToCharacterIndex = function (bufferLine, coords) { + var charIndex = coords[0]; + for (var i = 0; coords[0] >= i; i++) { + var char = bufferLine[i]; + if (char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + charIndex--; + } + else if (char[Buffer_1.CHAR_DATA_CHAR_INDEX].length > 1 && coords[0] !== i) { + charIndex += char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + } + } + return charIndex; + }; + SelectionManager.prototype.setSelection = function (col, row, length) { + this._model.clearSelection(); + this._removeMouseDownListeners(); + this._model.selectionStart = [col, row]; + this._model.selectionStartLength = length; + this.refresh(); + }; + SelectionManager.prototype._getWordAt = function (coords) { + var bufferLine = this._buffer.lines.get(coords[1]); + if (!bufferLine) { + return null; + } + var line = this._buffer.translateBufferLineToString(coords[1], false); + var startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords); + var endIndex = startIndex; + var charOffset = coords[0] - startIndex; + var leftWideCharCount = 0; + var rightWideCharCount = 0; + var leftLongCharOffset = 0; + var rightLongCharOffset = 0; + if (line.charAt(startIndex) === ' ') { + while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') { + startIndex--; + } + while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') { + endIndex++; + } + } + else { + var startCol = coords[0]; + var endCol = coords[0]; + if (bufferLine[startCol][Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + leftWideCharCount++; + startCol--; + } + if (bufferLine[endCol][Buffer_1.CHAR_DATA_WIDTH_INDEX] === 2) { + rightWideCharCount++; + endCol++; + } + if (bufferLine[endCol][Buffer_1.CHAR_DATA_CHAR_INDEX].length > 1) { + rightLongCharOffset += bufferLine[endCol][Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + endIndex += bufferLine[endCol][Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + } + while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine[startCol - 1])) { + var char = bufferLine[startCol - 1]; + if (char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + leftWideCharCount++; + startCol--; + } + else if (char[Buffer_1.CHAR_DATA_CHAR_INDEX].length > 1) { + leftLongCharOffset += char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + startIndex -= char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + } + startIndex--; + startCol--; + } + while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine[endCol + 1])) { + var char = bufferLine[endCol + 1]; + if (char[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 2) { + rightWideCharCount++; + endCol++; + } + else if (char[Buffer_1.CHAR_DATA_CHAR_INDEX].length > 1) { + rightLongCharOffset += char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + endIndex += char[Buffer_1.CHAR_DATA_CHAR_INDEX].length - 1; + } + endIndex++; + endCol++; + } + } + endIndex++; + var start = startIndex + + charOffset + - leftWideCharCount + + leftLongCharOffset; + var length = Math.min(this._terminal.cols, endIndex + - startIndex + + leftWideCharCount + + rightWideCharCount + - leftLongCharOffset + - rightLongCharOffset); + return { start: start, length: length }; + }; + SelectionManager.prototype._selectWordAt = function (coords) { + var wordPosition = this._getWordAt(coords); + if (wordPosition) { + this._model.selectionStart = [wordPosition.start, coords[1]]; + this._model.selectionStartLength = wordPosition.length; + } + }; + SelectionManager.prototype._selectToWordAt = function (coords) { + var wordPosition = this._getWordAt(coords); + if (wordPosition) { + this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; + } + }; + SelectionManager.prototype._isCharWordSeparator = function (charData) { + if (charData[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 0) { + return false; + } + return WORD_SEPARATORS.indexOf(charData[Buffer_1.CHAR_DATA_CHAR_INDEX]) >= 0; + }; + SelectionManager.prototype._selectLineAt = function (line) { + this._model.selectionStart = [0, line]; + this._model.selectionStartLength = this._terminal.cols; + }; + return SelectionManager; +}(EventEmitter_1.EventEmitter)); +exports.SelectionManager = SelectionManager; + + + +},{"./Buffer":1,"./EventEmitter":7,"./SelectionModel":12,"./utils/Browser":28,"./utils/MouseHelper":32}],12:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SelectionModel = (function () { + function SelectionModel(_terminal) { + this._terminal = _terminal; + this.clearSelection(); + } + SelectionModel.prototype.clearSelection = function () { + this.selectionStart = null; + this.selectionEnd = null; + this.isSelectAllActive = false; + this.selectionStartLength = 0; + }; + Object.defineProperty(SelectionModel.prototype, "finalSelectionStart", { + get: function () { + if (this.isSelectAllActive) { + return [0, 0]; + } + if (!this.selectionEnd || !this.selectionStart) { + return this.selectionStart; + } + return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionModel.prototype, "finalSelectionEnd", { + get: function () { + if (this.isSelectAllActive) { + return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1]; + } + if (!this.selectionStart) { + return null; + } + if (!this.selectionEnd || this.areSelectionValuesReversed()) { + return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]]; + } + if (this.selectionStartLength) { + if (this.selectionEnd[1] === this.selectionStart[1]) { + return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]]; + } + } + return this.selectionEnd; + }, + enumerable: true, + configurable: true + }); + SelectionModel.prototype.areSelectionValuesReversed = function () { + var start = this.selectionStart; + var end = this.selectionEnd; + if (!start || !end) { + return false; + } + return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]); + }; + SelectionModel.prototype.onTrim = function (amount) { + if (this.selectionStart) { + this.selectionStart[1] -= amount; + } + if (this.selectionEnd) { + this.selectionEnd[1] -= amount; + } + if (this.selectionEnd && this.selectionEnd[1] < 0) { + this.clearSelection(); + return true; + } + if (this.selectionStart && this.selectionStart[1] < 0) { + this.selectionStart[1] = 0; + } + return false; + }; + return SelectionModel; +}()); +exports.SelectionModel = SelectionModel; + + + +},{}],13:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BufferSet_1 = require("./BufferSet"); +var Buffer_1 = require("./Buffer"); +var CompositionHelper_1 = require("./CompositionHelper"); +var EventEmitter_1 = require("./EventEmitter"); +var Viewport_1 = require("./Viewport"); +var Clipboard_1 = require("./handlers/Clipboard"); +var EscapeSequences_1 = require("./EscapeSequences"); +var InputHandler_1 = require("./InputHandler"); +var Parser_1 = require("./Parser"); +var Renderer_1 = require("./renderer/Renderer"); +var Linkifier_1 = require("./Linkifier"); +var SelectionManager_1 = require("./SelectionManager"); +var CharMeasure_1 = require("./utils/CharMeasure"); +var Browser = require("./utils/Browser"); +var MouseHelper_1 = require("./utils/MouseHelper"); +var Sounds_1 = require("./utils/Sounds"); +var ColorManager_1 = require("./renderer/ColorManager"); +var MouseZoneManager_1 = require("./input/MouseZoneManager"); +var CharAtlas_1 = require("./renderer/CharAtlas"); +var document = (typeof window !== 'undefined') ? window.document : null; +var WRITE_BUFFER_PAUSE_THRESHOLD = 5; +var WRITE_BATCH_SIZE = 300; +var DEFAULT_OPTIONS = { + convertEol: false, + termName: 'xterm', + geometry: [80, 24], + cursorBlink: false, + cursorStyle: 'block', + bellSound: Sounds_1.BellSound, + bellStyle: 'none', + enableBold: true, + fontFamily: 'courier-new, courier, monospace', + fontSize: 15, + lineHeight: 1.0, + letterSpacing: 0, + scrollback: 1000, + screenKeys: false, + debug: false, + cancelEvents: false, + disableStdin: false, + useFlowControl: false, + tabStopWidth: 8, + theme: null +}; +var Terminal = (function (_super) { + __extends(Terminal, _super); + function Terminal(options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this) || this; + _this.browser = Browser; + _this.options = options; + _this.setup(); + return _this; + } + Terminal.prototype.setup = function () { + var _this = this; + Object.keys(DEFAULT_OPTIONS).forEach(function (key) { + if (_this.options[key] == null) { + _this.options[key] = DEFAULT_OPTIONS[key]; + } + _this[key] = _this.options[key]; + }); + this.parent = document ? document.body : null; + this.cols = this.options.cols || this.options.geometry[0]; + this.rows = this.options.rows || this.options.geometry[1]; + this.geometry = [this.cols, this.rows]; + if (this.options.handler) { + this.on('data', this.options.handler); + } + this.cursorState = 0; + this.cursorHidden = false; + this.sendDataQueue = ''; + this.customKeyEventHandler = null; + this.applicationKeypad = false; + this.applicationCursor = false; + this.originMode = false; + this.insertMode = false; + this.wraparoundMode = true; + this.bracketedPasteMode = false; + this.charset = null; + this.gcharset = null; + this.glevel = 0; + this.charsets = [null]; + this.readable = true; + this.writable = true; + this.defAttr = (0 << 18) | (257 << 9) | (256 << 0); + this.curAttr = (0 << 18) | (257 << 9) | (256 << 0); + this.params = []; + this.currentParam = 0; + this.prefix = ''; + this.postfix = ''; + this.writeBuffer = []; + this.writeInProgress = false; + this.xoffSentToCatchUp = false; + this.writeStopped = false; + this.surrogate_high = ''; + this.userScrolling = false; + this.inputHandler = new InputHandler_1.InputHandler(this); + this.parser = new Parser_1.Parser(this.inputHandler, this); + this.renderer = this.renderer || null; + this.selectionManager = this.selectionManager || null; + this.linkifier = this.linkifier || new Linkifier_1.Linkifier(this); + this._mouseZoneManager = this._mouseZoneManager || null; + this.buffers = new BufferSet_1.BufferSet(this); + this.buffer = this.buffers.active; + this.buffers.on('activate', function (buffer) { + _this.buffer = buffer; + }); + if (this.selectionManager) { + this.selectionManager.setBuffer(this.buffer); + } + }; + Terminal.prototype.eraseAttr = function () { + return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff); + }; + Terminal.prototype.focus = function () { + this.textarea.focus(); + }; + Object.defineProperty(Terminal.prototype, "isFocused", { + get: function () { + return document.activeElement === this.textarea; + }, + enumerable: true, + configurable: true + }); + Terminal.prototype.getOption = function (key) { + if (!(key in DEFAULT_OPTIONS)) { + throw new Error('No option with key "' + key + '"'); + } + if (typeof this.options[key] !== 'undefined') { + return this.options[key]; + } + return this[key]; + }; + Terminal.prototype.setOption = function (key, value) { + if (!(key in DEFAULT_OPTIONS)) { + throw new Error('No option with key "' + key + '"'); + } + switch (key) { + case 'bellStyle': + if (!value) { + value = 'none'; + } + break; + case 'cursorStyle': + if (!value) { + value = 'block'; + } + break; + case 'lineHeight': + if (value < 1) { + console.warn(key + " cannot be less than 1, value: " + value); + return; + } + case 'tabStopWidth': + if (value < 1) { + console.warn(key + " cannot be less than 1, value: " + value); + return; + } + break; + case 'theme': + if (this.renderer) { + this._setTheme(value); + return; + } + break; + case 'scrollback': + value = Math.min(value, Buffer_1.MAX_BUFFER_SIZE); + if (value < 0) { + console.warn(key + " cannot be less than 0, value: " + value); + return; + } + if (this.options[key] !== value) { + var newBufferLength = this.rows + value; + if (this.buffer.lines.length > newBufferLength) { + var amountToTrim = this.buffer.lines.length - newBufferLength; + var needsRefresh = (this.buffer.ydisp - amountToTrim < 0); + this.buffer.lines.trimStart(amountToTrim); + this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0); + this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0); + if (needsRefresh) { + this.refresh(0, this.rows - 1); + } + } + } + break; + } + this[key] = value; + this.options[key] = value; + switch (key) { + case 'fontFamily': + case 'fontSize': + this.renderer.clear(); + this.charMeasure.measure(this.options); + break; + case 'enableBold': + case 'letterSpacing': + case 'lineHeight': + this.renderer.clear(); + this.renderer.onResize(this.cols, this.rows, false); + this.refresh(0, this.rows - 1); + case 'scrollback': + this.buffers.resize(this.cols, this.rows); + this.viewport.syncScrollArea(); + break; + case 'tabStopWidth': + this.buffers.setupTabStops(); + break; + case 'bellSound': + case 'bellStyle': + this.syncBellSound(); + break; + } + if (this.renderer) { + this.renderer.onOptionsChanged(); + } + }; + Terminal.prototype._onTextAreaFocus = function () { + if (this.sendFocus) { + this.send(EscapeSequences_1.C0.ESC + '[I'); + } + this.element.classList.add('focus'); + this.showCursor(); + this.emit('focus'); + }; + ; + Terminal.prototype.blur = function () { + return this.textarea.blur(); + }; + Terminal.prototype._onTextAreaBlur = function () { + this.refresh(this.buffer.y, this.buffer.y); + if (this.sendFocus) { + this.send(EscapeSequences_1.C0.ESC + '[O'); + } + this.element.classList.remove('focus'); + this.emit('blur'); + }; + Terminal.prototype.initGlobal = function () { + var _this = this; + this.bindKeys(); + on(this.element, 'copy', function (event) { + if (!_this.hasSelection()) { + return; + } + Clipboard_1.copyHandler(event, _this, _this.selectionManager); + }); + var pasteHandlerWrapper = function (event) { return Clipboard_1.pasteHandler(event, _this); }; + on(this.textarea, 'paste', pasteHandlerWrapper); + on(this.element, 'paste', pasteHandlerWrapper); + if (Browser.isFirefox) { + on(this.element, 'mousedown', function (event) { + if (event.button === 2) { + Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager); + } + }); + } + else { + on(this.element, 'contextmenu', function (event) { + Clipboard_1.rightClickHandler(event, _this.textarea, _this.selectionManager); + }); + } + if (Browser.isLinux) { + on(this.element, 'auxclick', function (event) { + if (event.button === 1) { + Clipboard_1.moveTextAreaUnderMouseCursor(event, _this.textarea); + } + }); + } + }; + Terminal.prototype.bindKeys = function () { + var _this = this; + var self = this; + on(this.element, 'keydown', function (ev) { + if (document.activeElement !== this) { + return; + } + self._keyDown(ev); + }, true); + on(this.element, 'keypress', function (ev) { + if (document.activeElement !== this) { + return; + } + self._keyPress(ev); + }, true); + on(this.element, 'keyup', function (ev) { + if (!wasMondifierKeyOnlyEvent(ev)) { + _this.focus(); + } + }, true); + on(this.textarea, 'keydown', function (ev) { + _this._keyDown(ev); + }, true); + on(this.textarea, 'keypress', function (ev) { + _this._keyPress(ev); + _this.textarea.value = ''; + }, true); + on(this.textarea, 'compositionstart', function () { return _this.compositionHelper.compositionstart(); }); + on(this.textarea, 'compositionupdate', function (e) { return _this.compositionHelper.compositionupdate(e); }); + on(this.textarea, 'compositionend', function () { return _this.compositionHelper.compositionend(); }); + this.on('refresh', function () { return _this.compositionHelper.updateCompositionElements(); }); + this.on('refresh', function (data) { return _this.queueLinkification(data.start, data.end); }); + }; + Terminal.prototype.open = function (parent) { + var _this = this; + var i = 0; + var div; + this.parent = parent || this.parent; + if (!this.parent) { + throw new Error('Terminal requires a parent element.'); + } + this.context = this.parent.ownerDocument.defaultView; + this.document = this.parent.ownerDocument; + this.body = this.document.body; + CharAtlas_1.initialize(this.document); + this.element = this.document.createElement('div'); + this.element.classList.add('terminal'); + this.element.classList.add('xterm'); + this.element.setAttribute('tabindex', '0'); + this.viewportElement = document.createElement('div'); + this.viewportElement.classList.add('xterm-viewport'); + this.element.appendChild(this.viewportElement); + this.viewportScrollArea = document.createElement('div'); + this.viewportScrollArea.classList.add('xterm-scroll-area'); + this.viewportElement.appendChild(this.viewportScrollArea); + this.syncBellSound(); + this._mouseZoneManager = new MouseZoneManager_1.MouseZoneManager(this); + this.on('scroll', function () { return _this._mouseZoneManager.clearAll(); }); + this.linkifier.attachToDom(this._mouseZoneManager); + this.helperContainer = document.createElement('div'); + this.helperContainer.classList.add('xterm-helpers'); + this.element.appendChild(this.helperContainer); + this.textarea = document.createElement('textarea'); + this.textarea.classList.add('xterm-helper-textarea'); + this.textarea.setAttribute('autocorrect', 'off'); + this.textarea.setAttribute('autocapitalize', 'off'); + this.textarea.setAttribute('spellcheck', 'false'); + this.textarea.tabIndex = 0; + this.textarea.addEventListener('focus', function () { return _this._onTextAreaFocus(); }); + this.textarea.addEventListener('blur', function () { return _this._onTextAreaBlur(); }); + this.helperContainer.appendChild(this.textarea); + this.compositionView = document.createElement('div'); + this.compositionView.classList.add('composition-view'); + this.compositionHelper = new CompositionHelper_1.CompositionHelper(this.textarea, this.compositionView, this); + this.helperContainer.appendChild(this.compositionView); + this.charSizeStyleElement = document.createElement('style'); + this.helperContainer.appendChild(this.charSizeStyleElement); + this.parent.appendChild(this.element); + this.charMeasure = new CharMeasure_1.CharMeasure(document, this.helperContainer); + this.renderer = new Renderer_1.Renderer(this, this.options.theme); + this.options.theme = null; + this.viewport = new Viewport_1.Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); + this.viewport.onThemeChanged(this.renderer.colorManager.colors); + this.on('cursormove', function () { return _this.renderer.onCursorMove(); }); + this.on('resize', function () { return _this.renderer.onResize(_this.cols, _this.rows, false); }); + this.on('blur', function () { return _this.renderer.onBlur(); }); + this.on('focus', function () { return _this.renderer.onFocus(); }); + window.addEventListener('resize', function () { return _this.renderer.onWindowResize(window.devicePixelRatio); }); + this.charMeasure.on('charsizechanged', function () { return _this.renderer.onResize(_this.cols, _this.rows, true); }); + this.renderer.on('resize', function (dimensions) { return _this.viewport.syncScrollArea(); }); + this.selectionManager = new SelectionManager_1.SelectionManager(this, this.buffer, this.charMeasure); + this.element.addEventListener('mousedown', function (e) { return _this.selectionManager.onMouseDown(e); }); + this.selectionManager.on('refresh', function (data) { return _this.renderer.onSelectionChanged(data.start, data.end); }); + this.selectionManager.on('newselection', function (text) { + _this.textarea.value = text; + _this.textarea.focus(); + _this.textarea.select(); + }); + this.on('scroll', function () { + _this.viewport.syncScrollArea(); + _this.selectionManager.refresh(); + }); + this.viewportElement.addEventListener('scroll', function () { return _this.selectionManager.refresh(); }); + this.mouseHelper = new MouseHelper_1.MouseHelper(this.renderer); + this.charMeasure.measure(this.options); + this.refresh(0, this.rows - 1); + this.initGlobal(); + this.bindMouse(); + }; + Terminal.prototype._setTheme = function (theme) { + var colors = this.renderer.setTheme(theme); + if (this.viewport) { + this.viewport.onThemeChanged(colors); + } + }; + Terminal.loadAddon = function (addon, callback) { + if (typeof exports === 'object' && typeof module === 'object') { + return require('./addons/' + addon + '/' + addon); + } + else if (typeof define === 'function') { + return require(['./addons/' + addon + '/' + addon], callback); + } + else { + console.error('Cannot load a module without a CommonJS or RequireJS environment.'); + return false; + } + }; + Terminal.prototype.bindMouse = function () { + var _this = this; + var el = this.element; + var self = this; + var pressed = 32; + function sendButton(ev) { + var button; + var pos; + button = getButton(ev); + pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + if (!pos) + return; + sendEvent(button, pos); + switch (ev.overrideType || ev.type) { + case 'mousedown': + pressed = button; + break; + case 'mouseup': + pressed = 32; + break; + case 'wheel': + break; + } + } + function sendMove(ev) { + var button = pressed; + var pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + if (!pos) + return; + button += 32; + sendEvent(button, pos); + } + function encode(data, ch) { + if (!self.utfMouse) { + if (ch === 255) { + data.push(0); + return; + } + if (ch > 127) + ch = 127; + data.push(ch); + } + else { + if (ch === 2047) { + data.push(0); + return; + } + if (ch < 127) { + data.push(ch); + } + else { + if (ch > 2047) + ch = 2047; + data.push(0xC0 | (ch >> 6)); + data.push(0x80 | (ch & 0x3F)); + } + } + } + function sendEvent(button, pos) { + if (self.vt300Mouse) { + button &= 3; + pos.x -= 32; + pos.y -= 32; + var data_1 = EscapeSequences_1.C0.ESC + '[24'; + if (button === 0) + data_1 += '1'; + else if (button === 1) + data_1 += '3'; + else if (button === 2) + data_1 += '5'; + else if (button === 3) + return; + else + data_1 += '0'; + data_1 += '~[' + pos.x + ',' + pos.y + ']\r'; + self.send(data_1); + return; + } + if (self.decLocator) { + button &= 3; + pos.x -= 32; + pos.y -= 32; + if (button === 0) + button = 2; + else if (button === 1) + button = 4; + else if (button === 2) + button = 6; + else if (button === 3) + button = 3; + self.send(EscapeSequences_1.C0.ESC + '[' + + button + + ';' + + (button === 3 ? 4 : 0) + + ';' + + pos.y + + ';' + + pos.x + + ';' + + pos.page || 0 + + '&w'); + return; + } + if (self.urxvtMouse) { + pos.x -= 32; + pos.y -= 32; + pos.x++; + pos.y++; + self.send(EscapeSequences_1.C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); + return; + } + if (self.sgrMouse) { + pos.x -= 32; + pos.y -= 32; + self.send(EscapeSequences_1.C0.ESC + '[<' + + (((button & 3) === 3 ? button & ~3 : button) - 32) + + ';' + + pos.x + + ';' + + pos.y + + ((button & 3) === 3 ? 'm' : 'M')); + return; + } + var data = []; + encode(data, button); + encode(data, pos.x); + encode(data, pos.y); + self.send(EscapeSequences_1.C0.ESC + '[M' + String.fromCharCode.apply(String, data)); + } + function getButton(ev) { + var button; + var shift; + var meta; + var ctrl; + var mod; + switch (ev.overrideType || ev.type) { + case 'mousedown': + button = ev.button != null + ? +ev.button + : ev.which != null + ? ev.which - 1 + : null; + if (Browser.isMSIE) { + button = button === 1 ? 0 : button === 4 ? 1 : button; + } + break; + case 'mouseup': + button = 3; + break; + case 'DOMMouseScroll': + button = ev.detail < 0 + ? 64 + : 65; + break; + case 'wheel': + button = ev.wheelDeltaY > 0 + ? 64 + : 65; + break; + } + shift = ev.shiftKey ? 4 : 0; + meta = ev.metaKey ? 8 : 0; + ctrl = ev.ctrlKey ? 16 : 0; + mod = shift | meta | ctrl; + if (self.vt200Mouse) { + mod &= ctrl; + } + else if (!self.normalMouse) { + mod = 0; + } + button = (32 + (mod << 2)) + button; + return button; + } + on(el, 'mousedown', function (ev) { + ev.preventDefault(); + _this.focus(); + if (!_this.mouseEvents || _this.selectionManager.shouldForceSelection(ev)) { + return; + } + sendButton(ev); + if (_this.vt200Mouse) { + ev.overrideType = 'mouseup'; + sendButton(ev); + return _this.cancel(ev); + } + if (_this.normalMouse) + on(_this.document, 'mousemove', sendMove); + if (!_this.x10Mouse) { + var handler_1 = function (ev) { + sendButton(ev); + if (_this.normalMouse) + off(_this.document, 'mousemove', sendMove); + off(_this.document, 'mouseup', handler_1); + return _this.cancel(ev); + }; + on(_this.document, 'mouseup', handler_1); + } + return _this.cancel(ev); + }); + on(el, 'wheel', function (ev) { + if (!_this.mouseEvents) + return; + if (_this.x10Mouse || _this.vt300Mouse || _this.decLocator) + return; + sendButton(ev); + ev.preventDefault(); + }); + on(el, 'wheel', function (ev) { + if (_this.mouseEvents) + return; + _this.viewport.onWheel(ev); + return _this.cancel(ev); + }); + on(el, 'touchstart', function (ev) { + if (_this.mouseEvents) + return; + _this.viewport.onTouchStart(ev); + return _this.cancel(ev); + }); + on(el, 'touchmove', function (ev) { + if (_this.mouseEvents) + return; + _this.viewport.onTouchMove(ev); + return _this.cancel(ev); + }); + }; + Terminal.prototype.destroy = function () { + _super.prototype.destroy.call(this); + this.readable = false; + this.writable = false; + this.handler = function () { }; + this.write = function () { }; + if (this.element && this.element.parentNode) { + this.element.parentNode.removeChild(this.element); + } + }; + Terminal.prototype.refresh = function (start, end) { + if (this.renderer) { + this.renderer.queueRefresh(start, end); + } + }; + Terminal.prototype.queueLinkification = function (start, end) { + if (this.linkifier) { + this.linkifier.linkifyRows(start, end); + } + }; + Terminal.prototype.showCursor = function () { + if (!this.cursorState) { + this.cursorState = 1; + this.refresh(this.buffer.y, this.buffer.y); + } + }; + Terminal.prototype.scroll = function (isWrapped) { + var newLine = this.blankLine(undefined, isWrapped); + var topRow = this.buffer.ybase + this.buffer.scrollTop; + var bottomRow = this.buffer.ybase + this.buffer.scrollBottom; + if (this.buffer.scrollTop === 0) { + var willBufferBeTrimmed = this.buffer.lines.length === this.buffer.lines.maxLength; + if (bottomRow === this.buffer.lines.length - 1) { + this.buffer.lines.push(newLine); + } + else { + this.buffer.lines.splice(bottomRow + 1, 0, newLine); + } + if (!willBufferBeTrimmed) { + this.buffer.ybase++; + if (!this.userScrolling) { + this.buffer.ydisp++; + } + } + else { + if (this.userScrolling) { + this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); + } + } + } + else { + var scrollRegionHeight = bottomRow - topRow + 1; + this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); + this.buffer.lines.set(bottomRow, newLine); + } + if (!this.userScrolling) { + this.buffer.ydisp = this.buffer.ybase; + } + this.updateRange(this.buffer.scrollTop); + this.updateRange(this.buffer.scrollBottom); + this.emit('scroll', this.buffer.ydisp); + }; + Terminal.prototype.scrollLines = function (disp, suppressScrollEvent) { + if (disp < 0) { + if (this.buffer.ydisp === 0) { + return; + } + this.userScrolling = true; + } + else if (disp + this.buffer.ydisp >= this.buffer.ybase) { + this.userScrolling = false; + } + var oldYdisp = this.buffer.ydisp; + this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0); + if (oldYdisp === this.buffer.ydisp) { + return; + } + if (!suppressScrollEvent) { + this.emit('scroll', this.buffer.ydisp); + } + this.refresh(0, this.rows - 1); + }; + Terminal.prototype.scrollPages = function (pageCount) { + this.scrollLines(pageCount * (this.rows - 1)); + }; + Terminal.prototype.scrollToTop = function () { + this.scrollLines(-this.buffer.ydisp); + }; + Terminal.prototype.scrollToBottom = function () { + this.scrollLines(this.buffer.ybase - this.buffer.ydisp); + }; + Terminal.prototype.write = function (data) { + var _this = this; + this.writeBuffer.push(data); + if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { + this.send(EscapeSequences_1.C0.DC3); + this.xoffSentToCatchUp = true; + } + if (!this.writeInProgress && this.writeBuffer.length > 0) { + this.writeInProgress = true; + setTimeout(function () { + _this.innerWrite(); + }); + } + }; + Terminal.prototype.innerWrite = function () { + var _this = this; + var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); + while (writeBatch.length > 0) { + var data = writeBatch.shift(); + if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { + this.send(EscapeSequences_1.C0.DC1); + this.xoffSentToCatchUp = false; + } + this.refreshStart = this.buffer.y; + this.refreshEnd = this.buffer.y; + var state = this.parser.parse(data); + this.parser.setState(state); + this.updateRange(this.buffer.y); + this.refresh(this.refreshStart, this.refreshEnd); + } + if (this.writeBuffer.length > 0) { + setTimeout(function () { return _this.innerWrite(); }, 0); + } + else { + this.writeInProgress = false; + } + }; + Terminal.prototype.writeln = function (data) { + this.write(data + '\r\n'); + }; + Terminal.prototype.attachCustomKeyEventHandler = function (customKeyEventHandler) { + this.customKeyEventHandler = customKeyEventHandler; + }; + Terminal.prototype.setHypertextLinkHandler = function (handler) { + if (!this.linkifier) { + throw new Error('Cannot attach a hypertext link handler before Terminal.open is called'); + } + this.linkifier.setHypertextLinkHandler(handler); + this.refresh(0, this.rows - 1); + }; + Terminal.prototype.setHypertextValidationCallback = function (callback) { + if (!this.linkifier) { + throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called'); + } + this.linkifier.setHypertextValidationCallback(callback); + this.refresh(0, this.rows - 1); + }; + Terminal.prototype.registerLinkMatcher = function (regex, handler, options) { + if (this.linkifier) { + var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); + this.refresh(0, this.rows - 1); + return matcherId; + } + return 0; + }; + Terminal.prototype.deregisterLinkMatcher = function (matcherId) { + if (this.linkifier) { + if (this.linkifier.deregisterLinkMatcher(matcherId)) { + this.refresh(0, this.rows - 1); + } + } + }; + Terminal.prototype.hasSelection = function () { + return this.selectionManager ? this.selectionManager.hasSelection : false; + }; + Terminal.prototype.getSelection = function () { + return this.selectionManager ? this.selectionManager.selectionText : ''; + }; + Terminal.prototype.clearSelection = function () { + if (this.selectionManager) { + this.selectionManager.clearSelection(); + } + }; + Terminal.prototype.selectAll = function () { + if (this.selectionManager) { + this.selectionManager.selectAll(); + } + }; + Terminal.prototype._keyDown = function (ev) { + if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { + return false; + } + if (!this.compositionHelper.keydown(ev)) { + if (this.buffer.ybase !== this.buffer.ydisp) { + this.scrollToBottom(); + } + return false; + } + var result = this._evaluateKeyEscapeSequence(ev); + if (result.key === EscapeSequences_1.C0.DC3) { + this.writeStopped = true; + } + else if (result.key === EscapeSequences_1.C0.DC1) { + this.writeStopped = false; + } + if (result.scrollLines) { + this.scrollLines(result.scrollLines); + return this.cancel(ev, true); + } + if (isThirdLevelShift(this.browser, ev)) { + return true; + } + if (result.cancel) { + this.cancel(ev, true); + } + if (!result.key) { + return true; + } + this.emit('keydown', ev); + this.emit('key', result.key, ev); + this.showCursor(); + this.handler(result.key); + return this.cancel(ev, true); + }; + Terminal.prototype._evaluateKeyEscapeSequence = function (ev) { + var result = { + cancel: false, + key: undefined, + scrollLines: undefined + }; + var modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0); + switch (ev.keyCode) { + case 0: + if (ev.key === 'UIKeyInputUpArrow') { + if (this.applicationCursor) { + result.key = EscapeSequences_1.C0.ESC + 'OA'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[A'; + } + } + else if (ev.key === 'UIKeyInputLeftArrow') { + if (this.applicationCursor) { + result.key = EscapeSequences_1.C0.ESC + 'OD'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[D'; + } + } + else if (ev.key === 'UIKeyInputRightArrow') { + if (this.applicationCursor) { + result.key = EscapeSequences_1.C0.ESC + 'OC'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[C'; + } + } + else if (ev.key === 'UIKeyInputDownArrow') { + if (this.applicationCursor) { + result.key = EscapeSequences_1.C0.ESC + 'OB'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[B'; + } + } + break; + case 8: + if (ev.shiftKey) { + result.key = EscapeSequences_1.C0.BS; + break; + } + result.key = EscapeSequences_1.C0.DEL; + break; + case 9: + if (ev.shiftKey) { + result.key = EscapeSequences_1.C0.ESC + '[Z'; + break; + } + result.key = EscapeSequences_1.C0.HT; + result.cancel = true; + break; + case 13: + result.key = EscapeSequences_1.C0.CR; + result.cancel = true; + break; + case 27: + result.key = EscapeSequences_1.C0.ESC; + result.cancel = true; + break; + case 37: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'D'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3D') { + result.key = (this.browser.isMac) ? EscapeSequences_1.C0.ESC + 'b' : EscapeSequences_1.C0.ESC + '[1;5D'; + } + } + else if (this.applicationCursor) { + result.key = EscapeSequences_1.C0.ESC + 'OD'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[D'; + } + break; + case 39: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'C'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3C') { + result.key = (this.browser.isMac) ? EscapeSequences_1.C0.ESC + 'f' : EscapeSequences_1.C0.ESC + '[1;5C'; + } + } + else if (this.applicationCursor) { + result.key = EscapeSequences_1.C0.ESC + 'OC'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[C'; + } + break; + case 38: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'A'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3A') { + result.key = EscapeSequences_1.C0.ESC + '[1;5A'; + } + } + else if (this.applicationCursor) { + result.key = EscapeSequences_1.C0.ESC + 'OA'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[A'; + } + break; + case 40: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'B'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3B') { + result.key = EscapeSequences_1.C0.ESC + '[1;5B'; + } + } + else if (this.applicationCursor) { + result.key = EscapeSequences_1.C0.ESC + 'OB'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[B'; + } + break; + case 45: + if (!ev.shiftKey && !ev.ctrlKey) { + result.key = EscapeSequences_1.C0.ESC + '[2~'; + } + break; + case 46: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[3;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[3~'; + } + break; + case 36: + if (modifiers) + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'H'; + else if (this.applicationCursor) + result.key = EscapeSequences_1.C0.ESC + 'OH'; + else + result.key = EscapeSequences_1.C0.ESC + '[H'; + break; + case 35: + if (modifiers) + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'F'; + else if (this.applicationCursor) + result.key = EscapeSequences_1.C0.ESC + 'OF'; + else + result.key = EscapeSequences_1.C0.ESC + '[F'; + break; + case 33: + if (ev.shiftKey) { + result.scrollLines = -(this.rows - 1); + } + else { + result.key = EscapeSequences_1.C0.ESC + '[5~'; + } + break; + case 34: + if (ev.shiftKey) { + result.scrollLines = this.rows - 1; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[6~'; + } + break; + case 112: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'P'; + } + else { + result.key = EscapeSequences_1.C0.ESC + 'OP'; + } + break; + case 113: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'Q'; + } + else { + result.key = EscapeSequences_1.C0.ESC + 'OQ'; + } + break; + case 114: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'R'; + } + else { + result.key = EscapeSequences_1.C0.ESC + 'OR'; + } + break; + case 115: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'S'; + } + else { + result.key = EscapeSequences_1.C0.ESC + 'OS'; + } + break; + case 116: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[15;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[15~'; + } + break; + case 117: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[17;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[17~'; + } + break; + case 118: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[18;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[18~'; + } + break; + case 119: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[19;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[19~'; + } + break; + case 120: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[20;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[20~'; + } + break; + case 121: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[21;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[21~'; + } + break; + case 122: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[23;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[23~'; + } + break; + case 123: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[24;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[24~'; + } + break; + default: + if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { + if (ev.keyCode >= 65 && ev.keyCode <= 90) { + result.key = String.fromCharCode(ev.keyCode - 64); + } + else if (ev.keyCode === 32) { + result.key = String.fromCharCode(0); + } + else if (ev.keyCode >= 51 && ev.keyCode <= 55) { + result.key = String.fromCharCode(ev.keyCode - 51 + 27); + } + else if (ev.keyCode === 56) { + result.key = String.fromCharCode(127); + } + else if (ev.keyCode === 219) { + result.key = String.fromCharCode(27); + } + else if (ev.keyCode === 220) { + result.key = String.fromCharCode(28); + } + else if (ev.keyCode === 221) { + result.key = String.fromCharCode(29); + } + } + else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) { + if (ev.keyCode >= 65 && ev.keyCode <= 90) { + result.key = EscapeSequences_1.C0.ESC + String.fromCharCode(ev.keyCode + 32); + } + else if (ev.keyCode === 192) { + result.key = EscapeSequences_1.C0.ESC + '`'; + } + else if (ev.keyCode >= 48 && ev.keyCode <= 57) { + result.key = EscapeSequences_1.C0.ESC + (ev.keyCode - 48); + } + } + else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { + if (ev.keyCode === 65) { + this.selectAll(); + } + } + break; + } + return result; + }; + Terminal.prototype.setgLevel = function (g) { + this.glevel = g; + this.charset = this.charsets[g]; + }; + Terminal.prototype.setgCharset = function (g, charset) { + this.charsets[g] = charset; + if (this.glevel === g) { + this.charset = charset; + } + }; + Terminal.prototype._keyPress = function (ev) { + var key; + if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { + return false; + } + this.cancel(ev); + if (ev.charCode) { + key = ev.charCode; + } + else if (ev.which == null) { + key = ev.keyCode; + } + else if (ev.which !== 0 && ev.charCode !== 0) { + key = ev.which; + } + else { + return false; + } + if (!key || ((ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this.browser, ev))) { + return false; + } + key = String.fromCharCode(key); + this.emit('keypress', key, ev); + this.emit('key', key, ev); + this.showCursor(); + this.handler(key); + return true; + }; + Terminal.prototype.send = function (data) { + var _this = this; + if (!this.sendDataQueue) { + setTimeout(function () { + _this.handler(_this.sendDataQueue); + _this.sendDataQueue = ''; + }, 1); + } + this.sendDataQueue += data; + }; + Terminal.prototype.bell = function () { + var _this = this; + this.emit('bell'); + if (this.soundBell()) + this.bellAudioElement.play(); + if (this.visualBell()) { + this.element.classList.add('visual-bell-active'); + clearTimeout(this.visualBellTimer); + this.visualBellTimer = window.setTimeout(function () { + _this.element.classList.remove('visual-bell-active'); + }, 200); + } + }; + Terminal.prototype.log = function (text, data) { + if (!this.options.debug) + return; + if (!this.context.console || !this.context.console.log) + return; + this.context.console.log(text, data); + }; + Terminal.prototype.error = function (text, data) { + if (!this.options.debug) + return; + if (!this.context.console || !this.context.console.error) + return; + this.context.console.error(text, data); + }; + Terminal.prototype.resize = function (x, y) { + if (isNaN(x) || isNaN(y)) { + return; + } + if (x === this.cols && y === this.rows) { + if (!this.charMeasure.width || !this.charMeasure.height) { + this.charMeasure.measure(this.options); + } + return; + } + if (x < 1) + x = 1; + if (y < 1) + y = 1; + this.buffers.resize(x, y); + this.cols = x; + this.rows = y; + this.buffers.setupTabStops(this.cols); + this.charMeasure.measure(this.options); + this.refresh(0, this.rows - 1); + this.geometry = [this.cols, this.rows]; + this.emit('resize', { cols: x, rows: y }); + }; + Terminal.prototype.updateRange = function (y) { + if (y < this.refreshStart) + this.refreshStart = y; + if (y > this.refreshEnd) + this.refreshEnd = y; + }; + Terminal.prototype.maxRange = function () { + this.refreshStart = 0; + this.refreshEnd = this.rows - 1; + }; + Terminal.prototype.eraseRight = function (x, y) { + var line = this.buffer.lines.get(this.buffer.ybase + y); + if (!line) { + return; + } + var ch = [this.eraseAttr(), ' ', 1, 32]; + for (; x < this.cols; x++) { + line[x] = ch; + } + this.updateRange(y); + }; + Terminal.prototype.eraseLeft = function (x, y) { + var line = this.buffer.lines.get(this.buffer.ybase + y); + if (!line) { + return; + } + var ch = [this.eraseAttr(), ' ', 1, 32]; + x++; + while (x--) { + line[x] = ch; + } + this.updateRange(y); + }; + Terminal.prototype.clear = function () { + if (this.buffer.ybase === 0 && this.buffer.y === 0) { + return; + } + this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)); + this.buffer.lines.length = 1; + this.buffer.ydisp = 0; + this.buffer.ybase = 0; + this.buffer.y = 0; + for (var i = 1; i < this.rows; i++) { + this.buffer.lines.push(this.blankLine()); + } + this.refresh(0, this.rows - 1); + this.emit('scroll', this.buffer.ydisp); + }; + Terminal.prototype.eraseLine = function (y) { + this.eraseRight(0, y); + }; + Terminal.prototype.blankLine = function (cur, isWrapped, cols) { + var attr = cur ? this.eraseAttr() : this.defAttr; + var ch = [attr, ' ', 1, 32]; + var line = []; + if (isWrapped) { + line.isWrapped = isWrapped; + } + cols = cols || this.cols; + for (var i = 0; i < cols; i++) { + line[i] = ch; + } + return line; + }; + Terminal.prototype.ch = function (cur) { + if (cur) { + return [this.eraseAttr(), ' ', 1, 32]; + } + return [this.defAttr, ' ', 1, 32]; + }; + Terminal.prototype.is = function (term) { + return (this.options.termName + '').indexOf(term) === 0; + }; + Terminal.prototype.handler = function (data) { + if (this.options.disableStdin) { + return; + } + if (this.selectionManager && this.selectionManager.hasSelection) { + this.selectionManager.clearSelection(); + } + if (this.buffer.ybase !== this.buffer.ydisp) { + this.scrollToBottom(); + } + this.emit('data', data); + }; + Terminal.prototype.handleTitle = function (title) { + this.emit('title', title); + }; + Terminal.prototype.index = function () { + this.buffer.y++; + if (this.buffer.y > this.buffer.scrollBottom) { + this.buffer.y--; + this.scroll(); + } + if (this.buffer.x >= this.cols) { + this.buffer.x--; + } + }; + Terminal.prototype.reverseIndex = function () { + if (this.buffer.y === this.buffer.scrollTop) { + var scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop; + this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1); + this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true)); + this.updateRange(this.buffer.scrollTop); + this.updateRange(this.buffer.scrollBottom); + } + else { + this.buffer.y--; + } + }; + Terminal.prototype.reset = function () { + this.options.rows = this.rows; + this.options.cols = this.cols; + var customKeyEventHandler = this.customKeyEventHandler; + var inputHandler = this.inputHandler; + var buffers = this.buffers; + this.setup(); + this.customKeyEventHandler = customKeyEventHandler; + this.inputHandler = inputHandler; + this.buffers = buffers; + this.refresh(0, this.rows - 1); + this.viewport.syncScrollArea(); + }; + Terminal.prototype.tabSet = function () { + this.buffer.tabs[this.buffer.x] = true; + }; + Terminal.prototype.cancel = function (ev, force) { + if (!this.options.cancelEvents && !force) { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + return false; + }; + Terminal.prototype.matchColor = function (r1, g1, b1) { + return matchColor_(r1, g1, b1); + }; + Terminal.prototype.visualBell = function () { + return this.options.bellStyle === 'visual' || + this.options.bellStyle === 'both'; + }; + Terminal.prototype.soundBell = function () { + return this.options.bellStyle === 'sound' || + this.options.bellStyle === 'both'; + }; + Terminal.prototype.syncBellSound = function () { + if (this.soundBell() && this.bellAudioElement) { + this.bellAudioElement.setAttribute('src', this.options.bellSound); + } + else if (this.soundBell()) { + this.bellAudioElement = document.createElement('audio'); + this.bellAudioElement.setAttribute('preload', 'auto'); + this.bellAudioElement.setAttribute('src', this.options.bellSound); + this.helperContainer.appendChild(this.bellAudioElement); + } + else if (this.bellAudioElement) { + this.helperContainer.removeChild(this.bellAudioElement); + } + }; + return Terminal; +}(EventEmitter_1.EventEmitter)); +exports.Terminal = Terminal; +function globalOn(el, type, handler, capture) { + if (!Array.isArray(el)) { + el = [el]; + } + el.forEach(function (element) { + element.addEventListener(type, handler, capture || false); + }); +} +var on = globalOn; +function off(el, type, handler, capture) { + if (capture === void 0) { capture = false; } + el.removeEventListener(type, handler, capture); +} +function isThirdLevelShift(browser, ev) { + var thirdLevelKey = (browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) || + (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); + if (ev.type === 'keypress') { + return thirdLevelKey; + } + return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); +} +function wasMondifierKeyOnlyEvent(ev) { + return ev.keyCode === 16 || + ev.keyCode === 17 || + ev.keyCode === 18; +} +var vcolors = (function () { + var result = ColorManager_1.DEFAULT_ANSI_COLORS.map(function (c) { + c = c.substring(1); + return [ + parseInt(c.substring(0, 2), 16), + parseInt(c.substring(2, 4), 16), + parseInt(c.substring(4, 6), 16) + ]; + }); + var r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + for (var i = 0; i < 216; i++) { + result.push([ + r[(i / 36) % 6 | 0], + r[(i / 6) % 6 | 0], + r[i % 6] + ]); + } + var c; + for (var i = 0; i < 24; i++) { + c = 8 + i * 10; + result.push([c, c, c]); + } + return result; +})(); +var matchColorCache = {}; +function matchColorDistance(r1, g1, b1, r2, g2, b2) { + return Math.pow(30 * (r1 - r2), 2) + + Math.pow(59 * (g1 - g2), 2) + + Math.pow(11 * (b1 - b2), 2); +} +; +function matchColor_(r1, g1, b1) { + var hash = (r1 << 16) | (g1 << 8) | b1; + if (matchColorCache[hash] != null) { + return matchColorCache[hash]; + } + var ldiff = Infinity; + var li = -1; + var i = 0; + var c; + var r2; + var g2; + var b2; + var diff; + for (; i < vcolors.length; i++) { + c = vcolors[i]; + r2 = c[0]; + g2 = c[1]; + b2 = c[2]; + diff = matchColorDistance(r1, g1, b1, r2, g2, b2); + if (diff === 0) { + li = i; + break; + } + if (diff < ldiff) { + ldiff = diff; + li = i; + } + } + return matchColorCache[hash] = li; +} + + + +},{"./Buffer":1,"./BufferSet":2,"./CompositionHelper":5,"./EscapeSequences":6,"./EventEmitter":7,"./InputHandler":8,"./Linkifier":9,"./Parser":10,"./SelectionManager":11,"./Viewport":15,"./handlers/Clipboard":16,"./input/MouseZoneManager":17,"./renderer/CharAtlas":19,"./renderer/ColorManager":20,"./renderer/Renderer":24,"./utils/Browser":28,"./utils/CharMeasure":29,"./utils/MouseHelper":32,"./utils/Sounds":33}],14:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var LinkHoverEventTypes; +(function (LinkHoverEventTypes) { + LinkHoverEventTypes["HOVER"] = "linkhover"; + LinkHoverEventTypes["TOOLTIP"] = "linktooltip"; + LinkHoverEventTypes["LEAVE"] = "linkleave"; +})(LinkHoverEventTypes = exports.LinkHoverEventTypes || (exports.LinkHoverEventTypes = {})); +; + + + +},{}],15:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Viewport = (function () { + function Viewport(terminal, viewportElement, scrollArea, charMeasure) { + var _this = this; + this.terminal = terminal; + this.viewportElement = viewportElement; + this.scrollArea = scrollArea; + this.charMeasure = charMeasure; + this.currentRowHeight = 0; + this.lastRecordedBufferLength = 0; + this.lastRecordedViewportHeight = 0; + this.lastRecordedBufferHeight = 0; + this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); + setTimeout(function () { return _this.syncScrollArea(); }, 0); + } + Viewport.prototype.onThemeChanged = function (colors) { + this.viewportElement.style.backgroundColor = colors.background; + }; + Viewport.prototype.refresh = function () { + if (this.charMeasure.height > 0) { + this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio; + if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) { + this.lastRecordedViewportHeight = this.terminal.renderer.dimensions.canvasHeight; + this.viewportElement.style.height = this.lastRecordedViewportHeight + 'px'; + } + var newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength); + if (this.lastRecordedBufferHeight !== newBufferHeight) { + this.lastRecordedBufferHeight = newBufferHeight; + this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px'; + } + } + }; + Viewport.prototype.syncScrollArea = function () { + if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) { + this.lastRecordedBufferLength = this.terminal.buffer.lines.length; + this.refresh(); + } + else if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) { + this.refresh(); + } + else { + if (this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this.currentRowHeight) { + this.refresh(); + } + } + var scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight; + if (this.viewportElement.scrollTop !== scrollTop) { + this.viewportElement.scrollTop = scrollTop; + } + }; + Viewport.prototype.onScroll = function (ev) { + var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); + var diff = newRow - this.terminal.buffer.ydisp; + this.terminal.scrollLines(diff, true); + }; + Viewport.prototype.onWheel = function (ev) { + if (ev.deltaY === 0) { + return; + } + var multiplier = 1; + if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { + multiplier = this.currentRowHeight; + } + else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + multiplier = this.currentRowHeight * this.terminal.rows; + } + this.viewportElement.scrollTop += ev.deltaY * multiplier; + ev.preventDefault(); + }; + ; + Viewport.prototype.onTouchStart = function (ev) { + this.lastTouchY = ev.touches[0].pageY; + }; + ; + Viewport.prototype.onTouchMove = function (ev) { + var deltaY = this.lastTouchY - ev.touches[0].pageY; + this.lastTouchY = ev.touches[0].pageY; + if (deltaY === 0) { + return; + } + this.viewportElement.scrollTop += deltaY; + ev.preventDefault(); + }; + ; + return Viewport; +}()); +exports.Viewport = Viewport; + + + +},{}],16:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function prepareTextForTerminal(text, isMSWindows) { + if (isMSWindows) { + return text.replace(/\r?\n/g, '\r'); + } + return text; +} +exports.prepareTextForTerminal = prepareTextForTerminal; +function bracketTextForPaste(text, bracketedPasteMode) { + if (bracketedPasteMode) { + return '\x1b[200~' + text + '\x1b[201~'; + } + return text; +} +exports.bracketTextForPaste = bracketTextForPaste; +function copyHandler(ev, term, selectionManager) { + if (term.browser.isMSIE) { + window.clipboardData.setData('Text', selectionManager.selectionText); + } + else { + ev.clipboardData.setData('text/plain', selectionManager.selectionText); + } + ev.preventDefault(); +} +exports.copyHandler = copyHandler; +function pasteHandler(ev, term) { + ev.stopPropagation(); + var text; + var dispatchPaste = function (text) { + text = prepareTextForTerminal(text, term.browser.isMSWindows); + text = bracketTextForPaste(text, term.bracketedPasteMode); + term.handler(text); + term.textarea.value = ''; + term.emit('paste', text); + term.cancel(ev); + }; + if (term.browser.isMSIE) { + if (window.clipboardData) { + text = window.clipboardData.getData('Text'); + dispatchPaste(text); + } + } + else { + if (ev.clipboardData) { + text = ev.clipboardData.getData('text/plain'); + dispatchPaste(text); + } + } +} +exports.pasteHandler = pasteHandler; +function moveTextAreaUnderMouseCursor(ev, textarea) { + textarea.style.position = 'fixed'; + textarea.style.width = '20px'; + textarea.style.height = '20px'; + textarea.style.left = (ev.clientX - 10) + 'px'; + textarea.style.top = (ev.clientY - 10) + 'px'; + textarea.style.zIndex = '1000'; + textarea.focus(); + setTimeout(function () { + textarea.style.position = null; + textarea.style.width = null; + textarea.style.height = null; + textarea.style.left = null; + textarea.style.top = null; + textarea.style.zIndex = null; + }, 4); +} +exports.moveTextAreaUnderMouseCursor = moveTextAreaUnderMouseCursor; +function rightClickHandler(ev, textarea, selectionManager) { + moveTextAreaUnderMouseCursor(ev, textarea); + textarea.value = selectionManager.selectionText; + textarea.select(); +} +exports.rightClickHandler = rightClickHandler; + + + +},{}],17:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var HOVER_DURATION = 500; +var MouseZoneManager = (function () { + function MouseZoneManager(_terminal) { + var _this = this; + this._terminal = _terminal; + this._zones = []; + this._areZonesActive = false; + this._tooltipTimeout = null; + this._currentZone = null; + this._lastHoverCoords = [null, null]; + this._terminal.element.addEventListener('mousedown', function (e) { return _this._onMouseDown(e); }); + this._mouseMoveListener = function (e) { return _this._onMouseMove(e); }; + this._clickListener = function (e) { return _this._onClick(e); }; + } + MouseZoneManager.prototype.add = function (zone) { + this._zones.push(zone); + if (this._zones.length === 1) { + this._activate(); + } + }; + MouseZoneManager.prototype.clearAll = function (start, end) { + if (this._zones.length === 0) { + return; + } + if (!end) { + start = 0; + end = this._terminal.rows - 1; + } + for (var i = 0; i < this._zones.length; i++) { + var zone = this._zones[i]; + if (zone.y > start && zone.y <= end + 1) { + if (this._currentZone && this._currentZone === zone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + } + this._zones.splice(i--, 1); + } + } + if (this._zones.length === 0) { + this._deactivate(); + } + }; + MouseZoneManager.prototype._activate = function () { + if (!this._areZonesActive) { + this._areZonesActive = true; + this._terminal.element.addEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.addEventListener('click', this._clickListener); + } + }; + MouseZoneManager.prototype._deactivate = function () { + if (this._areZonesActive) { + this._areZonesActive = false; + this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.removeEventListener('click', this._clickListener); + } + }; + MouseZoneManager.prototype._onMouseMove = function (e) { + if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) { + this._onHover(e); + this._lastHoverCoords = [e.pageX, e.pageY]; + } + }; + MouseZoneManager.prototype._onHover = function (e) { + var _this = this; + var zone = this._findZoneEventAt(e); + if (zone === this._currentZone) { + return; + } + if (this._currentZone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + if (this._tooltipTimeout) { + clearTimeout(this._tooltipTimeout); + } + } + if (!zone) { + return; + } + this._currentZone = zone; + if (zone.hoverCallback) { + zone.hoverCallback(e); + } + this._tooltipTimeout = setTimeout(function () { return _this._onTooltip(e); }, HOVER_DURATION); + }; + MouseZoneManager.prototype._onTooltip = function (e) { + this._tooltipTimeout = null; + var zone = this._findZoneEventAt(e); + if (zone && zone.tooltipCallback) { + zone.tooltipCallback(e); + } + }; + MouseZoneManager.prototype._onMouseDown = function (e) { + if (!this._areZonesActive) { + return; + } + var zone = this._findZoneEventAt(e); + if (zone) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + }; + MouseZoneManager.prototype._onClick = function (e) { + var zone = this._findZoneEventAt(e); + if (zone) { + zone.clickCallback(e); + e.preventDefault(); + e.stopImmediatePropagation(); + } + }; + MouseZoneManager.prototype._findZoneEventAt = function (e) { + var coords = this._terminal.mouseHelper.getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); + if (!coords) { + return null; + } + for (var i = 0; i < this._zones.length; i++) { + var zone = this._zones[i]; + if (zone.y === coords[1] && zone.x1 <= coords[0] && zone.x2 > coords[0]) { + return zone; + } + } + ; + return null; + }; + return MouseZoneManager; +}()); +exports.MouseZoneManager = MouseZoneManager; +var MouseZone = (function () { + function MouseZone(x1, x2, y, clickCallback, hoverCallback, tooltipCallback, leaveCallback) { + this.x1 = x1; + this.x2 = x2; + this.y = y; + this.clickCallback = clickCallback; + this.hoverCallback = hoverCallback; + this.tooltipCallback = tooltipCallback; + this.leaveCallback = leaveCallback; + } + return MouseZone; +}()); +exports.MouseZone = MouseZone; + + + +},{}],18:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var CharAtlas_1 = require("./CharAtlas"); +var Buffer_1 = require("../Buffer"); +exports.INVERTED_DEFAULT_COLOR = -1; +var DIM_OPACITY = 0.5; +var BaseRenderLayer = (function () { + function BaseRenderLayer(container, id, zIndex, _alpha, _colors) { + this._alpha = _alpha; + this._colors = _colors; + this._scaledCharWidth = 0; + this._scaledCharHeight = 0; + this._scaledCellWidth = 0; + this._scaledCellHeight = 0; + this._scaledCharLeft = 0; + this._scaledCharTop = 0; + this._canvas = document.createElement('canvas'); + this._canvas.id = "xterm-" + id + "-layer"; + this._canvas.style.zIndex = zIndex.toString(); + this._ctx = this._canvas.getContext('2d', { alpha: _alpha }); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + if (!_alpha) { + this.clearAll(); + } + container.appendChild(this._canvas); + } + BaseRenderLayer.prototype.onOptionsChanged = function (terminal) { }; + BaseRenderLayer.prototype.onBlur = function (terminal) { }; + BaseRenderLayer.prototype.onFocus = function (terminal) { }; + BaseRenderLayer.prototype.onCursorMove = function (terminal) { }; + BaseRenderLayer.prototype.onGridChanged = function (terminal, startRow, endRow) { }; + BaseRenderLayer.prototype.onSelectionChanged = function (terminal, start, end) { }; + BaseRenderLayer.prototype.onThemeChanged = function (terminal, colorSet) { + this._refreshCharAtlas(terminal, colorSet); + }; + BaseRenderLayer.prototype._refreshCharAtlas = function (terminal, colorSet) { + var _this = this; + if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { + return; + } + this._charAtlas = null; + var result = CharAtlas_1.acquireCharAtlas(terminal, this._colors, this._scaledCharWidth, this._scaledCharHeight); + if (result instanceof HTMLCanvasElement) { + this._charAtlas = result; + } + else { + result.then(function (bitmap) { return _this._charAtlas = bitmap; }); + } + }; + BaseRenderLayer.prototype.resize = function (terminal, dim, charSizeChanged) { + this._scaledCellWidth = dim.scaledCellWidth; + this._scaledCellHeight = dim.scaledCellHeight; + this._scaledCharWidth = dim.scaledCharWidth; + this._scaledCharHeight = dim.scaledCharHeight; + this._scaledCharLeft = dim.scaledCharLeft; + this._scaledCharTop = dim.scaledCharTop; + this._canvas.width = dim.scaledCanvasWidth; + this._canvas.height = dim.scaledCanvasHeight; + this._canvas.style.width = dim.canvasWidth + "px"; + this._canvas.style.height = dim.canvasHeight + "px"; + if (!this._alpha) { + this.clearAll(); + } + if (charSizeChanged) { + this._refreshCharAtlas(terminal, this._colors); + } + }; + BaseRenderLayer.prototype.fillCells = function (x, y, width, height) { + this._ctx.fillRect(x * this._scaledCellWidth, y * this._scaledCellHeight, width * this._scaledCellWidth, height * this._scaledCellHeight); + }; + BaseRenderLayer.prototype.fillBottomLineAtCells = function (x, y, width) { + if (width === void 0) { width = 1; } + this._ctx.fillRect(x * this._scaledCellWidth, (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1, width * this._scaledCellWidth, window.devicePixelRatio); + }; + BaseRenderLayer.prototype.fillLeftLineAtCell = function (x, y) { + this._ctx.fillRect(x * this._scaledCellWidth, y * this._scaledCellHeight, window.devicePixelRatio, this._scaledCellHeight); + }; + BaseRenderLayer.prototype.strokeRectAtCell = function (x, y, width, height) { + this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.strokeRect(x * this._scaledCellWidth + window.devicePixelRatio / 2, y * this._scaledCellHeight + (window.devicePixelRatio / 2), width * this._scaledCellWidth - window.devicePixelRatio, (height * this._scaledCellHeight) - window.devicePixelRatio); + }; + BaseRenderLayer.prototype.clearAll = function () { + if (this._alpha) { + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } + else { + this._ctx.fillStyle = this._colors.background; + this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + } + }; + BaseRenderLayer.prototype.clearCells = function (x, y, width, height) { + if (this._alpha) { + this._ctx.clearRect(x * this._scaledCellWidth, y * this._scaledCellHeight, width * this._scaledCellWidth, height * this._scaledCellHeight); + } + else { + this._ctx.fillStyle = this._colors.background; + this._ctx.fillRect(x * this._scaledCellWidth, y * this._scaledCellHeight, width * this._scaledCellWidth, height * this._scaledCellHeight); + } + }; + BaseRenderLayer.prototype.fillCharTrueColor = function (terminal, charData, x, y) { + this._ctx.font = terminal.options.fontSize * window.devicePixelRatio + "px " + terminal.options.fontFamily; + this._ctx.textBaseline = 'top'; + this._clipRow(terminal, y); + this._ctx.fillText(charData[Buffer_1.CHAR_DATA_CHAR_INDEX], x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); + }; + BaseRenderLayer.prototype.drawChar = function (terminal, char, code, width, x, y, fg, bg, bold, dim) { + var colorIndex = 0; + if (fg < 256) { + colorIndex = fg + 2; + } + else { + if (bold && terminal.options.enableBold) { + colorIndex = 1; + } + } + var isAscii = code < 256; + var isBasicColor = (colorIndex > 1 && fg < 16) && (fg < 8 || bold); + var isDefaultColor = fg >= 256; + var isDefaultBackground = bg >= 256; + if (this._charAtlas && isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground) { + var charAtlasCellWidth = this._scaledCharWidth + CharAtlas_1.CHAR_ATLAS_CELL_SPACING; + var charAtlasCellHeight = this._scaledCharHeight + CharAtlas_1.CHAR_ATLAS_CELL_SPACING; + if (dim) { + this._ctx.globalAlpha = DIM_OPACITY; + } + if (bold && !terminal.options.enableBold) { + if (colorIndex > 1) { + colorIndex -= 8; + } + } + this._ctx.drawImage(this._charAtlas, code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, charAtlasCellWidth, this._scaledCharHeight, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop, charAtlasCellWidth, this._scaledCharHeight); + } + else { + this._drawUncachedChar(terminal, char, width, fg, x, y, bold, dim); + } + }; + BaseRenderLayer.prototype._drawUncachedChar = function (terminal, char, width, fg, x, y, bold, dim) { + this._ctx.save(); + this._ctx.font = terminal.options.fontSize * window.devicePixelRatio + "px " + terminal.options.fontFamily; + if (bold && terminal.options.enableBold) { + this._ctx.font = "bold " + this._ctx.font; + } + this._ctx.textBaseline = 'top'; + if (fg === exports.INVERTED_DEFAULT_COLOR) { + this._ctx.fillStyle = this._colors.background; + } + else if (fg < 256) { + this._ctx.fillStyle = this._colors.ansi[fg]; + } + else { + this._ctx.fillStyle = this._colors.foreground; + } + this._clipRow(terminal, y); + if (dim) { + this._ctx.globalAlpha = DIM_OPACITY; + } + this._ctx.fillText(char, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); + this._ctx.restore(); + }; + BaseRenderLayer.prototype._clipRow = function (terminal, y) { + this._ctx.beginPath(); + this._ctx.rect(0, y * this._scaledCellHeight, terminal.cols * this._scaledCellWidth, this._scaledCellHeight); + this._ctx.clip(); + }; + return BaseRenderLayer; +}()); +exports.BaseRenderLayer = BaseRenderLayer; + + + +},{"../Buffer":1,"./CharAtlas":19}],19:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Browser_1 = require("../utils/Browser"); +exports.CHAR_ATLAS_CELL_SPACING = 1; +var charAtlasCache = []; +function acquireCharAtlas(terminal, colors, scaledCharWidth, scaledCharHeight) { + var newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); + for (var i = 0; i < charAtlasCache.length; i++) { + var entry = charAtlasCache[i]; + var ownedByIndex = entry.ownedBy.indexOf(terminal); + if (ownedByIndex >= 0) { + if (configEquals(entry.config, newConfig)) { + return entry.bitmap; + } + else { + if (entry.ownedBy.length === 1) { + charAtlasCache.splice(i, 1); + } + else { + entry.ownedBy.splice(ownedByIndex, 1); + } + break; + } + } + } + for (var i = 0; i < charAtlasCache.length; i++) { + var entry = charAtlasCache[i]; + if (configEquals(entry.config, newConfig)) { + entry.ownedBy.push(terminal); + return entry.bitmap; + } + } + var newEntry = { + bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.background, colors.foreground, colors.ansi), + config: newConfig, + ownedBy: [terminal] + }; + charAtlasCache.push(newEntry); + return newEntry.bitmap; +} +exports.acquireCharAtlas = acquireCharAtlas; +function generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors) { + var clonedColors = { + foreground: colors.foreground, + background: colors.background, + cursor: null, + cursorAccent: null, + selection: null, + ansi: colors.ansi.slice(0, 16) + }; + return { + scaledCharWidth: scaledCharWidth, + scaledCharHeight: scaledCharHeight, + fontFamily: terminal.options.fontFamily, + fontSize: terminal.options.fontSize, + colors: clonedColors + }; +} +function configEquals(a, b) { + for (var i = 0; i < a.colors.ansi.length; i++) { + if (a.colors.ansi[i] !== b.colors.ansi[i]) { + return false; + } + } + return a.fontFamily === b.fontFamily && + a.fontSize === b.fontSize && + a.scaledCharWidth === b.scaledCharWidth && + a.scaledCharHeight === b.scaledCharHeight && + a.colors.foreground === b.colors.foreground && + a.colors.background === b.colors.background; +} +var generator; +function initialize(document) { + if (!generator) { + generator = new CharAtlasGenerator(document); + } +} +exports.initialize = initialize; +var CharAtlasGenerator = (function () { + function CharAtlasGenerator(_document) { + this._document = _document; + this._canvas = this._document.createElement('canvas'); + this._ctx = this._canvas.getContext('2d', { alpha: false }); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + } + CharAtlasGenerator.prototype.generate = function (scaledCharWidth, scaledCharHeight, fontSize, fontFamily, background, foreground, ansiColors) { + var cellWidth = scaledCharWidth + exports.CHAR_ATLAS_CELL_SPACING; + var cellHeight = scaledCharHeight + exports.CHAR_ATLAS_CELL_SPACING; + this._canvas.width = 255 * cellWidth; + this._canvas.height = (2 + 16) * cellHeight; + this._ctx.fillStyle = background; + this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + this._ctx.save(); + this._ctx.fillStyle = foreground; + this._ctx.font = fontSize * window.devicePixelRatio + "px " + fontFamily; + this._ctx.textBaseline = 'top'; + for (var i = 0; i < 256; i++) { + this._ctx.save(); + this._ctx.beginPath(); + this._ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); + this._ctx.clip(); + this._ctx.fillText(String.fromCharCode(i), i * cellWidth, 0); + this._ctx.restore(); + } + this._ctx.save(); + this._ctx.font = "bold " + this._ctx.font; + for (var i = 0; i < 256; i++) { + this._ctx.save(); + this._ctx.beginPath(); + this._ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); + this._ctx.clip(); + this._ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight); + this._ctx.restore(); + } + this._ctx.restore(); + this._ctx.font = fontSize * window.devicePixelRatio + "px " + fontFamily; + for (var colorIndex = 0; colorIndex < 16; colorIndex++) { + if (colorIndex === 8) { + this._ctx.font = "bold " + this._ctx.font; + } + var y = (colorIndex + 2) * cellHeight; + for (var i = 0; i < 256; i++) { + this._ctx.save(); + this._ctx.beginPath(); + this._ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + this._ctx.clip(); + this._ctx.fillStyle = ansiColors[colorIndex]; + this._ctx.fillText(String.fromCharCode(i), i * cellWidth, y); + this._ctx.restore(); + } + } + this._ctx.restore(); + if (!('createImageBitmap' in window) || Browser_1.isFirefox) { + var result = this._canvas; + this._canvas = this._document.createElement('canvas'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + return result; + } + var charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); + var r = parseInt(background.substr(1, 2), 16); + var g = parseInt(background.substr(3, 2), 16); + var b = parseInt(background.substr(5, 2), 16); + this._clearColor(charAtlasImageData, r, g, b); + var promise = window.createImageBitmap(charAtlasImageData); + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + return promise; + }; + CharAtlasGenerator.prototype._clearColor = function (imageData, r, g, b) { + for (var offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } + } + }; + return CharAtlasGenerator; +}()); + + + +},{"../utils/Browser":28}],20:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var DEFAULT_FOREGROUND = '#ffffff'; +var DEFAULT_BACKGROUND = '#333333'; +var DEFAULT_CURSOR = '#ffffff'; +var DEFAULT_CURSOR_ACCENT = '#333333'; +var DEFAULT_SELECTION = 'rgba(255, 255, 255, 0.3)'; +exports.DEFAULT_ANSI_COLORS = [ + '#2e3436', + '#cc0000', + '#4e9a06', + '#c4a000', + '#3465a4', + '#75507b', + '#06989a', + '#d3d7cf', + '#555753', + '#ef2929', + '#8ae234', + '#fce94f', + '#729fcf', + '#ad7fa8', + '#34e2e2', + '#eeeeec' +]; +function generate256Colors(first16Colors) { + var colors = first16Colors.slice(); + var v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + for (var i = 0; i < 216; i++) { + var r = toPaddedHex(v[(i / 36) % 6 | 0]); + var g = toPaddedHex(v[(i / 6) % 6 | 0]); + var b = toPaddedHex(v[i % 6]); + colors.push("#" + r + g + b); + } + for (var i = 0; i < 24; i++) { + var c = toPaddedHex(8 + i * 10); + colors.push("#" + c + c + c); + } + return colors; +} +function toPaddedHex(c) { + var s = c.toString(16); + return s.length < 2 ? '0' + s : s; +} +var ColorManager = (function () { + function ColorManager() { + this.colors = { + foreground: DEFAULT_FOREGROUND, + background: DEFAULT_BACKGROUND, + cursor: DEFAULT_CURSOR, + cursorAccent: DEFAULT_CURSOR_ACCENT, + selection: DEFAULT_SELECTION, + ansi: generate256Colors(exports.DEFAULT_ANSI_COLORS) + }; + } + ColorManager.prototype.setTheme = function (theme) { + this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND; + this.colors.background = this._validateColor(theme.background, DEFAULT_BACKGROUND); + this.colors.cursor = theme.cursor || DEFAULT_CURSOR; + this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT; + this.colors.selection = theme.selection || DEFAULT_SELECTION; + this.colors.ansi[0] = theme.black || exports.DEFAULT_ANSI_COLORS[0]; + this.colors.ansi[1] = theme.red || exports.DEFAULT_ANSI_COLORS[1]; + this.colors.ansi[2] = theme.green || exports.DEFAULT_ANSI_COLORS[2]; + this.colors.ansi[3] = theme.yellow || exports.DEFAULT_ANSI_COLORS[3]; + this.colors.ansi[4] = theme.blue || exports.DEFAULT_ANSI_COLORS[4]; + this.colors.ansi[5] = theme.magenta || exports.DEFAULT_ANSI_COLORS[5]; + this.colors.ansi[6] = theme.cyan || exports.DEFAULT_ANSI_COLORS[6]; + this.colors.ansi[7] = theme.white || exports.DEFAULT_ANSI_COLORS[7]; + this.colors.ansi[8] = theme.brightBlack || exports.DEFAULT_ANSI_COLORS[8]; + this.colors.ansi[9] = theme.brightRed || exports.DEFAULT_ANSI_COLORS[9]; + this.colors.ansi[10] = theme.brightGreen || exports.DEFAULT_ANSI_COLORS[10]; + this.colors.ansi[11] = theme.brightYellow || exports.DEFAULT_ANSI_COLORS[11]; + this.colors.ansi[12] = theme.brightBlue || exports.DEFAULT_ANSI_COLORS[12]; + this.colors.ansi[13] = theme.brightMagenta || exports.DEFAULT_ANSI_COLORS[13]; + this.colors.ansi[14] = theme.brightCyan || exports.DEFAULT_ANSI_COLORS[14]; + this.colors.ansi[15] = theme.brightWhite || exports.DEFAULT_ANSI_COLORS[15]; + }; + ColorManager.prototype._validateColor = function (color, fallback) { + if (!color) { + return fallback; + } + if (color.length === 7 && color.charAt(0) === '#') { + return color; + } + if (color.length === 4 && color.charAt(0) === '#') { + var r = color.charAt(1); + var g = color.charAt(2); + var b = color.charAt(3); + return "#" + r + r + g + g + b + b; + } + return fallback; + }; + return ColorManager; +}()); +exports.ColorManager = ColorManager; + + + +},{}],21:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Buffer_1 = require("../Buffer"); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var BLINK_INTERVAL = 600; +var CursorRenderLayer = (function (_super) { + __extends(CursorRenderLayer, _super); + function CursorRenderLayer(container, zIndex, colors) { + var _this = _super.call(this, container, 'cursor', zIndex, true, colors) || this; + _this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null, + }; + _this._cursorRenderers = { + 'bar': _this._renderBarCursor.bind(_this), + 'block': _this._renderBlockCursor.bind(_this), + 'underline': _this._renderUnderlineCursor.bind(_this) + }; + return _this; + } + CursorRenderLayer.prototype.resize = function (terminal, dim, charSizeChanged) { + _super.prototype.resize.call(this, terminal, dim, charSizeChanged); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null, + }; + }; + CursorRenderLayer.prototype.reset = function (terminal) { + this._clearCursor(); + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + this.onOptionsChanged(terminal); + } + }; + CursorRenderLayer.prototype.onBlur = function (terminal) { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.pause(); + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + }; + CursorRenderLayer.prototype.onFocus = function (terminal) { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.resume(terminal); + } + else { + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + }; + CursorRenderLayer.prototype.onOptionsChanged = function (terminal) { + var _this = this; + if (terminal.options.cursorBlink) { + if (!this._cursorBlinkStateManager) { + this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, function () { + _this._render(terminal, true); + }); + } + } + else { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + }; + CursorRenderLayer.prototype.onCursorMove = function (terminal) { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + }; + CursorRenderLayer.prototype.onGridChanged = function (terminal, startRow, endRow) { + if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { + this._render(terminal, false); + } + }; + CursorRenderLayer.prototype._render = function (terminal, triggeredByAnimationFrame) { + if (!terminal.cursorState || terminal.cursorHidden) { + this._clearCursor(); + return; + } + var cursorY = terminal.buffer.ybase + terminal.buffer.y; + var viewportRelativeCursorY = cursorY - terminal.buffer.ydisp; + if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) { + this._clearCursor(); + return; + } + var charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; + if (!charData) { + return; + } + if (!terminal.isFocused) { + this._clearCursor(); + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor; + this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData); + this._ctx.restore(); + this._state.x = terminal.buffer.x; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.options.cursorStyle; + this._state.width = charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]; + return; + } + if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + this._clearCursor(); + return; + } + if (this._state) { + if (this._state.x === terminal.buffer.x && + this._state.y === viewportRelativeCursorY && + this._state.isFocused === terminal.isFocused && + this._state.style === terminal.options.cursorStyle && + this._state.width === charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]) { + return; + } + this._clearCursor(); + } + this._ctx.save(); + this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData); + this._ctx.restore(); + this._state.x = terminal.buffer.x; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.options.cursorStyle; + this._state.width = charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]; + }; + CursorRenderLayer.prototype._clearCursor = function () { + if (this._state) { + this.clearCells(this._state.x, this._state.y, this._state.width, 1); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null, + }; + } + }; + CursorRenderLayer.prototype._renderBarCursor = function (terminal, x, y, charData) { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor; + this.fillLeftLineAtCell(x, y); + this._ctx.restore(); + }; + CursorRenderLayer.prototype._renderBlockCursor = function (terminal, x, y, charData) { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor; + this.fillCells(x, y, charData[Buffer_1.CHAR_DATA_WIDTH_INDEX], 1); + this._ctx.fillStyle = this._colors.cursorAccent; + this.fillCharTrueColor(terminal, charData, x, y); + this._ctx.restore(); + }; + CursorRenderLayer.prototype._renderUnderlineCursor = function (terminal, x, y, charData) { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor; + this.fillBottomLineAtCells(x, y); + this._ctx.restore(); + }; + CursorRenderLayer.prototype._renderBlurCursor = function (terminal, x, y, charData) { + this._ctx.save(); + this._ctx.strokeStyle = this._colors.cursor; + this.strokeRectAtCell(x, y, charData[Buffer_1.CHAR_DATA_WIDTH_INDEX], 1); + this._ctx.restore(); + }; + return CursorRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.CursorRenderLayer = CursorRenderLayer; +var CursorBlinkStateManager = (function () { + function CursorBlinkStateManager(terminal, renderCallback) { + this.renderCallback = renderCallback; + this.isCursorVisible = true; + if (terminal.isFocused) { + this._restartInterval(); + } + } + Object.defineProperty(CursorBlinkStateManager.prototype, "isPaused", { + get: function () { return !(this._blinkStartTimeout || this._blinkInterval); }, + enumerable: true, + configurable: true + }); + CursorBlinkStateManager.prototype.dispose = function () { + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + }; + CursorBlinkStateManager.prototype.restartBlinkAnimation = function (terminal) { + var _this = this; + if (this.isPaused) { + return; + } + this._animationTimeRestarted = Date.now(); + this.isCursorVisible = true; + if (!this._animationFrame) { + this._animationFrame = window.requestAnimationFrame(function () { + _this.renderCallback(); + _this._animationFrame = null; + }); + } + }; + CursorBlinkStateManager.prototype._restartInterval = function (timeToStart) { + var _this = this; + if (timeToStart === void 0) { timeToStart = BLINK_INTERVAL; } + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + } + this._blinkStartTimeout = setTimeout(function () { + if (_this._animationTimeRestarted) { + var time = BLINK_INTERVAL - (Date.now() - _this._animationTimeRestarted); + _this._animationTimeRestarted = null; + if (time > 0) { + _this._restartInterval(time); + return; + } + } + _this.isCursorVisible = false; + _this._animationFrame = window.requestAnimationFrame(function () { + _this.renderCallback(); + _this._animationFrame = null; + }); + _this._blinkInterval = setInterval(function () { + if (_this._animationTimeRestarted) { + var time = BLINK_INTERVAL - (Date.now() - _this._animationTimeRestarted); + _this._animationTimeRestarted = null; + _this._restartInterval(time); + return; + } + _this.isCursorVisible = !_this.isCursorVisible; + _this._animationFrame = window.requestAnimationFrame(function () { + _this.renderCallback(); + _this._animationFrame = null; + }); + }, BLINK_INTERVAL); + }, timeToStart); + }; + CursorBlinkStateManager.prototype.pause = function () { + this.isCursorVisible = true; + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + }; + CursorBlinkStateManager.prototype.resume = function (terminal) { + this._animationTimeRestarted = null; + this._restartInterval(); + this.restartBlinkAnimation(terminal); + }; + return CursorBlinkStateManager; +}()); + + + +},{"../Buffer":1,"./BaseRenderLayer":18}],22:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var GridCache = (function () { + function GridCache() { + this.cache = []; + } + GridCache.prototype.resize = function (width, height) { + for (var x = 0; x < width; x++) { + if (this.cache.length <= x) { + this.cache.push([]); + } + for (var y = this.cache[x].length; y < height; y++) { + this.cache[x].push(null); + } + this.cache[x].length = height; + } + this.cache.length = width; + }; + GridCache.prototype.clear = function () { + for (var x = 0; x < this.cache.length; x++) { + for (var y = 0; y < this.cache[x].length; y++) { + this.cache[x][y] = null; + } + } + }; + return GridCache; +}()); +exports.GridCache = GridCache; + + + +},{}],23:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var Types_1 = require("../Types"); +var LinkRenderLayer = (function (_super) { + __extends(LinkRenderLayer, _super); + function LinkRenderLayer(container, zIndex, colors, terminal) { + var _this = _super.call(this, container, 'link', zIndex, true, colors) || this; + _this._state = null; + terminal.linkifier.on(Types_1.LinkHoverEventTypes.HOVER, function (e) { return _this._onLinkHover(e); }); + terminal.linkifier.on(Types_1.LinkHoverEventTypes.LEAVE, function (e) { return _this._onLinkLeave(e); }); + return _this; + } + LinkRenderLayer.prototype.resize = function (terminal, dim, charSizeChanged) { + _super.prototype.resize.call(this, terminal, dim, charSizeChanged); + this._state = null; + }; + LinkRenderLayer.prototype.reset = function (terminal) { + this._clearCurrentLink(); + }; + LinkRenderLayer.prototype._clearCurrentLink = function () { + if (this._state) { + this.clearCells(this._state.x, this._state.y, this._state.length, 1); + this._state = null; + } + }; + LinkRenderLayer.prototype._onLinkHover = function (e) { + this._ctx.fillStyle = this._colors.foreground; + this.fillBottomLineAtCells(e.x, e.y, e.length); + this._state = e; + }; + LinkRenderLayer.prototype._onLinkLeave = function (e) { + this._clearCurrentLink(); + }; + return LinkRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.LinkRenderLayer = LinkRenderLayer; + + + +},{"../Types":14,"./BaseRenderLayer":18}],24:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var TextRenderLayer_1 = require("./TextRenderLayer"); +var SelectionRenderLayer_1 = require("./SelectionRenderLayer"); +var CursorRenderLayer_1 = require("./CursorRenderLayer"); +var ColorManager_1 = require("./ColorManager"); +var LinkRenderLayer_1 = require("./LinkRenderLayer"); +var EventEmitter_1 = require("../EventEmitter"); +var Renderer = (function (_super) { + __extends(Renderer, _super); + function Renderer(_terminal, theme) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._refreshRowsQueue = []; + _this._refreshAnimationFrame = null; + _this.colorManager = new ColorManager_1.ColorManager(); + if (theme) { + _this.colorManager.setTheme(theme); + } + _this._renderLayers = [ + new TextRenderLayer_1.TextRenderLayer(_this._terminal.element, 0, _this.colorManager.colors), + new SelectionRenderLayer_1.SelectionRenderLayer(_this._terminal.element, 1, _this.colorManager.colors), + new LinkRenderLayer_1.LinkRenderLayer(_this._terminal.element, 2, _this.colorManager.colors, _this._terminal), + new CursorRenderLayer_1.CursorRenderLayer(_this._terminal.element, 3, _this.colorManager.colors) + ]; + _this.dimensions = { + scaledCharWidth: null, + scaledCharHeight: null, + scaledCellWidth: null, + scaledCellHeight: null, + scaledCharLeft: null, + scaledCharTop: null, + scaledCanvasWidth: null, + scaledCanvasHeight: null, + canvasWidth: null, + canvasHeight: null, + actualCellWidth: null, + actualCellHeight: null + }; + _this._devicePixelRatio = window.devicePixelRatio; + return _this; + } + Renderer.prototype.onWindowResize = function (devicePixelRatio) { + if (this._devicePixelRatio !== devicePixelRatio) { + this._devicePixelRatio = devicePixelRatio; + this.onResize(this._terminal.cols, this._terminal.rows, true); + } + }; + Renderer.prototype.setTheme = function (theme) { + var _this = this; + this.colorManager.setTheme(theme); + this._renderLayers.forEach(function (l) { + l.onThemeChanged(_this._terminal, _this.colorManager.colors); + l.reset(_this._terminal); + }); + this._terminal.refresh(0, this._terminal.rows - 1); + return this.colorManager.colors; + }; + Renderer.prototype.onResize = function (cols, rows, didCharSizeChange) { + var _this = this; + if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { + return; + } + this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); + this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2); + this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); + this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; + this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; + this._renderLayers.forEach(function (l) { return l.resize(_this._terminal, _this.dimensions, didCharSizeChange); }); + this._terminal.refresh(0, this._terminal.rows - 1); + this.emit('resize', { + width: this.dimensions.canvasWidth, + height: this.dimensions.canvasHeight + }); + }; + Renderer.prototype.onCharSizeChanged = function () { + this.onResize(this._terminal.cols, this._terminal.rows, true); + }; + Renderer.prototype.onBlur = function () { + var _this = this; + this._renderLayers.forEach(function (l) { return l.onBlur(_this._terminal); }); + }; + Renderer.prototype.onFocus = function () { + var _this = this; + this._renderLayers.forEach(function (l) { return l.onFocus(_this._terminal); }); + }; + Renderer.prototype.onSelectionChanged = function (start, end) { + var _this = this; + this._renderLayers.forEach(function (l) { return l.onSelectionChanged(_this._terminal, start, end); }); + }; + Renderer.prototype.onCursorMove = function () { + var _this = this; + this._renderLayers.forEach(function (l) { return l.onCursorMove(_this._terminal); }); + }; + Renderer.prototype.onOptionsChanged = function () { + var _this = this; + this._renderLayers.forEach(function (l) { return l.onOptionsChanged(_this._terminal); }); + }; + Renderer.prototype.clear = function () { + var _this = this; + this._renderLayers.forEach(function (l) { return l.reset(_this._terminal); }); + }; + Renderer.prototype.queueRefresh = function (start, end) { + this._refreshRowsQueue.push({ start: start, end: end }); + if (!this._refreshAnimationFrame) { + this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); + } + }; + Renderer.prototype._refreshLoop = function () { + var _this = this; + var start; + var end; + if (this._refreshRowsQueue.length > 4) { + start = 0; + end = this._terminal.rows - 1; + } + else { + start = this._refreshRowsQueue[0].start; + end = this._refreshRowsQueue[0].end; + for (var i = 1; i < this._refreshRowsQueue.length; i++) { + if (this._refreshRowsQueue[i].start < start) { + start = this._refreshRowsQueue[i].start; + } + if (this._refreshRowsQueue[i].end > end) { + end = this._refreshRowsQueue[i].end; + } + } + } + this._refreshRowsQueue = []; + this._refreshAnimationFrame = null; + start = Math.max(start, 0); + end = Math.min(end, this._terminal.rows - 1); + this._renderLayers.forEach(function (l) { return l.onGridChanged(_this._terminal, start, end); }); + this._terminal.emit('refresh', { start: start, end: end }); + }; + return Renderer; +}(EventEmitter_1.EventEmitter)); +exports.Renderer = Renderer; + + + +},{"../EventEmitter":7,"./ColorManager":20,"./CursorRenderLayer":21,"./LinkRenderLayer":23,"./SelectionRenderLayer":25,"./TextRenderLayer":26}],25:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var SelectionRenderLayer = (function (_super) { + __extends(SelectionRenderLayer, _super); + function SelectionRenderLayer(container, zIndex, colors) { + var _this = _super.call(this, container, 'selection', zIndex, true, colors) || this; + _this._state = { + start: null, + end: null + }; + return _this; + } + SelectionRenderLayer.prototype.resize = function (terminal, dim, charSizeChanged) { + _super.prototype.resize.call(this, terminal, dim, charSizeChanged); + this._state = { + start: null, + end: null + }; + }; + SelectionRenderLayer.prototype.reset = function (terminal) { + if (this._state.start && this._state.end) { + this._state = { + start: null, + end: null + }; + this.clearAll(); + } + }; + SelectionRenderLayer.prototype.onSelectionChanged = function (terminal, start, end) { + if (this._state.start === start || this._state.end === end) { + return; + } + this.clearAll(); + if (!start || !end) { + return; + } + var viewportStartRow = start[1] - terminal.buffer.ydisp; + var viewportEndRow = end[1] - terminal.buffer.ydisp; + var viewportCappedStartRow = Math.max(viewportStartRow, 0); + var viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + return; + } + var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; + var startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; + this._ctx.fillStyle = this._colors.selection; + this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); + var middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0); + this.fillCells(0, viewportCappedStartRow + 1, terminal.cols, middleRowsCount); + if (viewportCappedStartRow !== viewportCappedEndRow) { + var endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols; + this.fillCells(0, viewportCappedEndRow, endCol, 1); + } + this._state.start = [start[0], start[1]]; + this._state.end = [end[0], end[1]]; + }; + return SelectionRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.SelectionRenderLayer = SelectionRenderLayer; + + + +},{"./BaseRenderLayer":18}],26:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Buffer_1 = require("../Buffer"); +var Types_1 = require("./Types"); +var GridCache_1 = require("./GridCache"); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var OVERLAP_OWNED_CHAR_DATA = [null, '', 0, -1]; +var TextRenderLayer = (function (_super) { + __extends(TextRenderLayer, _super); + function TextRenderLayer(container, zIndex, colors) { + var _this = _super.call(this, container, 'text', zIndex, false, colors) || this; + _this._characterOverlapCache = {}; + _this._state = new GridCache_1.GridCache(); + return _this; + } + TextRenderLayer.prototype.resize = function (terminal, dim, charSizeChanged) { + _super.prototype.resize.call(this, terminal, dim, charSizeChanged); + var terminalFont = terminal.options.fontSize * window.devicePixelRatio + "px " + terminal.options.fontFamily; + if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) { + this._characterWidth = dim.scaledCharWidth; + this._characterFont = terminalFont; + this._characterOverlapCache = {}; + } + this._state.clear(); + this._state.resize(terminal.cols, terminal.rows); + }; + TextRenderLayer.prototype.reset = function (terminal) { + this._state.clear(); + this.clearAll(); + }; + TextRenderLayer.prototype.onGridChanged = function (terminal, startRow, endRow) { + if (this._state.cache.length === 0) { + return; + } + for (var y = startRow; y <= endRow; y++) { + var row = y + terminal.buffer.ydisp; + var line = terminal.buffer.lines.get(row); + this.clearCells(0, y, terminal.cols, 1); + for (var x = 0; x < terminal.cols; x++) { + var charData = line[x]; + var code = charData[Buffer_1.CHAR_DATA_CODE_INDEX]; + var char = charData[Buffer_1.CHAR_DATA_CHAR_INDEX]; + var attr = charData[Buffer_1.CHAR_DATA_ATTR_INDEX]; + var width = charData[Buffer_1.CHAR_DATA_WIDTH_INDEX]; + if (width === 0) { + continue; + } + if (code === 32) { + if (x > 0) { + var previousChar = line[x - 1]; + if (this._isOverlapping(previousChar)) { + continue; + } + } + } + var flags = attr >> 18; + var bg = attr & 0x1ff; + var isDefaultBackground = bg >= 256; + var isInvisible = flags & Types_1.FLAGS.INVISIBLE; + var isInverted = flags & Types_1.FLAGS.INVERSE; + if (!code || (code === 32 && isDefaultBackground && !isInverted) || isInvisible) { + continue; + } + if (width !== 0 && this._isOverlapping(charData)) { + if (x < line.length - 1 && line[x + 1][Buffer_1.CHAR_DATA_CODE_INDEX] === 32) { + width = 2; + } + } + var fg = (attr >> 9) & 0x1ff; + if (isInverted) { + var temp = bg; + bg = fg; + fg = temp; + if (fg === 256) { + fg = BaseRenderLayer_1.INVERTED_DEFAULT_COLOR; + } + if (bg === 257) { + bg = BaseRenderLayer_1.INVERTED_DEFAULT_COLOR; + } + } + if (width === 2) { + } + if (bg < 256) { + this._ctx.save(); + this._ctx.fillStyle = (bg === BaseRenderLayer_1.INVERTED_DEFAULT_COLOR ? this._colors.foreground : this._colors.ansi[bg]); + this.fillCells(x, y, width, 1); + this._ctx.restore(); + } + this._ctx.save(); + if (flags & Types_1.FLAGS.BOLD) { + this._ctx.font = "bold " + this._ctx.font; + if (fg < 8) { + fg += 8; + } + } + if (flags & Types_1.FLAGS.UNDERLINE) { + if (fg === BaseRenderLayer_1.INVERTED_DEFAULT_COLOR) { + this._ctx.fillStyle = this._colors.background; + } + else if (fg < 256) { + this._ctx.fillStyle = this._colors.ansi[fg]; + } + else { + this._ctx.fillStyle = this._colors.foreground; + } + this.fillBottomLineAtCells(x, y); + } + this.drawChar(terminal, char, code, width, x, y, fg, bg, !!(flags & Types_1.FLAGS.BOLD), !!(flags & Types_1.FLAGS.DIM)); + this._ctx.restore(); + } + } + }; + TextRenderLayer.prototype._isOverlapping = function (charData) { + if (charData[Buffer_1.CHAR_DATA_WIDTH_INDEX] !== 1) { + return false; + } + var code = charData[Buffer_1.CHAR_DATA_CODE_INDEX]; + if (code < 256) { + return false; + } + var char = charData[Buffer_1.CHAR_DATA_CHAR_INDEX]; + if (this._characterOverlapCache.hasOwnProperty(char)) { + return this._characterOverlapCache[char]; + } + this._ctx.save(); + this._ctx.font = this._characterFont; + var overlaps = Math.floor(this._ctx.measureText(char).width) > this._characterWidth; + this._ctx.restore(); + this._characterOverlapCache[char] = overlaps; + return overlaps; + }; + TextRenderLayer.prototype._clearChar = function (x, y) { + var colsToClear = 1; + var state = this._state.cache[x][y]; + if (state && state[Buffer_1.CHAR_DATA_WIDTH_INDEX] === 2) { + colsToClear = 2; + } + this.clearCells(x, y, colsToClear, 1); + }; + return TextRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.TextRenderLayer = TextRenderLayer; + + + +},{"../Buffer":1,"./BaseRenderLayer":18,"./GridCache":22,"./Types":27}],27:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var FLAGS; +(function (FLAGS) { + FLAGS[FLAGS["BOLD"] = 1] = "BOLD"; + FLAGS[FLAGS["UNDERLINE"] = 2] = "UNDERLINE"; + FLAGS[FLAGS["BLINK"] = 4] = "BLINK"; + FLAGS[FLAGS["INVERSE"] = 8] = "INVERSE"; + FLAGS[FLAGS["INVISIBLE"] = 16] = "INVISIBLE"; + FLAGS[FLAGS["DIM"] = 32] = "DIM"; +})(FLAGS = exports.FLAGS || (exports.FLAGS = {})); +; + + + +},{}],28:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Generic_1 = require("./Generic"); +var isNode = (typeof navigator === 'undefined') ? true : false; +var userAgent = (isNode) ? 'node' : navigator.userAgent; +var platform = (isNode) ? 'node' : navigator.platform; +exports.isFirefox = !!~userAgent.indexOf('Firefox'); +exports.isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident'); +exports.isMac = Generic_1.contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform); +exports.isIpad = platform === 'iPad'; +exports.isIphone = platform === 'iPhone'; +exports.isMSWindows = Generic_1.contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform); +exports.isLinux = platform.indexOf('Linux') >= 0; + + + +},{"./Generic":31}],29:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter_1 = require("../EventEmitter"); +var CharMeasure = (function (_super) { + __extends(CharMeasure, _super); + function CharMeasure(document, parentElement) { + var _this = _super.call(this) || this; + _this._document = document; + _this._parentElement = parentElement; + return _this; + } + Object.defineProperty(CharMeasure.prototype, "width", { + get: function () { + return this._width; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CharMeasure.prototype, "height", { + get: function () { + return this._height; + }, + enumerable: true, + configurable: true + }); + CharMeasure.prototype.measure = function (options) { + var _this = this; + if (!this._measureElement) { + this._measureElement = this._document.createElement('span'); + this._measureElement.style.position = 'absolute'; + this._measureElement.style.top = '0'; + this._measureElement.style.left = '-9999em'; + this._measureElement.style.lineHeight = 'normal'; + this._measureElement.textContent = 'W'; + this._measureElement.setAttribute('aria-hidden', 'true'); + this._parentElement.appendChild(this._measureElement); + setTimeout(function () { return _this._doMeasure(options); }, 0); + } + else { + this._doMeasure(options); + } + }; + CharMeasure.prototype._doMeasure = function (options) { + this._measureElement.style.fontFamily = options.fontFamily; + this._measureElement.style.fontSize = options.fontSize + "px"; + var geometry = this._measureElement.getBoundingClientRect(); + if (geometry.width === 0 || geometry.height === 0) { + return; + } + if (this._width !== geometry.width || this._height !== geometry.height) { + this._width = geometry.width; + this._height = Math.ceil(geometry.height); + this.emit('charsizechanged'); + } + }; + return CharMeasure; +}(EventEmitter_1.EventEmitter)); +exports.CharMeasure = CharMeasure; + + + +},{"../EventEmitter":7}],30:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter_1 = require("../EventEmitter"); +var CircularList = (function (_super) { + __extends(CircularList, _super); + function CircularList(_maxLength) { + var _this = _super.call(this) || this; + _this._maxLength = _maxLength; + _this._array = new Array(_this._maxLength); + _this._startIndex = 0; + _this._length = 0; + return _this; + } + Object.defineProperty(CircularList.prototype, "maxLength", { + get: function () { + return this._maxLength; + }, + set: function (newMaxLength) { + if (this._maxLength === newMaxLength) { + return; + } + var newArray = new Array(newMaxLength); + for (var i = 0; i < Math.min(newMaxLength, this.length); i++) { + newArray[i] = this._array[this._getCyclicIndex(i)]; + } + this._array = newArray; + this._maxLength = newMaxLength; + this._startIndex = 0; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CircularList.prototype, "length", { + get: function () { + return this._length; + }, + set: function (newLength) { + if (newLength > this._length) { + for (var i = this._length; i < newLength; i++) { + this._array[i] = undefined; + } + } + this._length = newLength; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CircularList.prototype, "forEach", { + get: function () { + var _this = this; + return function (callbackfn) { + var i = 0; + var length = _this.length; + for (var i_1 = 0; i_1 < length; i_1++) { + callbackfn(_this.get(i_1), i_1); + } + }; + }, + enumerable: true, + configurable: true + }); + CircularList.prototype.get = function (index) { + return this._array[this._getCyclicIndex(index)]; + }; + CircularList.prototype.set = function (index, value) { + this._array[this._getCyclicIndex(index)] = value; + }; + CircularList.prototype.push = function (value) { + this._array[this._getCyclicIndex(this._length)] = value; + if (this._length === this._maxLength) { + this._startIndex++; + if (this._startIndex === this._maxLength) { + this._startIndex = 0; + } + this.emit('trim', 1); + } + else { + this._length++; + } + }; + CircularList.prototype.pop = function () { + return this._array[this._getCyclicIndex(this._length-- - 1)]; + }; + CircularList.prototype.splice = function (start, deleteCount) { + var items = []; + for (var _i = 2; _i < arguments.length; _i++) { + items[_i - 2] = arguments[_i]; + } + if (deleteCount) { + for (var i = start; i < this._length - deleteCount; i++) { + this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; + } + this._length -= deleteCount; + } + if (items && items.length) { + for (var i = this._length - 1; i >= start; i--) { + this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)]; + } + for (var i = 0; i < items.length; i++) { + this._array[this._getCyclicIndex(start + i)] = items[i]; + } + if (this._length + items.length > this.maxLength) { + var countToTrim = (this._length + items.length) - this.maxLength; + this._startIndex += countToTrim; + this._length = this.maxLength; + this.emit('trim', countToTrim); + } + else { + this._length += items.length; + } + } + }; + CircularList.prototype.trimStart = function (count) { + if (count > this._length) { + count = this._length; + } + this._startIndex += count; + this._length -= count; + this.emit('trim', count); + }; + CircularList.prototype.shiftElements = function (start, count, offset) { + if (count <= 0) { + return; + } + if (start < 0 || start >= this._length) { + throw new Error('start argument out of range'); + } + if (start + offset < 0) { + throw new Error('Cannot shift elements in list beyond index 0'); + } + if (offset > 0) { + for (var i = count - 1; i >= 0; i--) { + this.set(start + i + offset, this.get(start + i)); + } + var expandListBy = (start + count + offset) - this._length; + if (expandListBy > 0) { + this._length += expandListBy; + while (this._length > this.maxLength) { + this._length--; + this._startIndex++; + this.emit('trim', 1); + } + } + } + else { + for (var i = 0; i < count; i++) { + this.set(start + i + offset, this.get(start + i)); + } + } + }; + CircularList.prototype._getCyclicIndex = function (index) { + return (this._startIndex + index) % this.maxLength; + }; + return CircularList; +}(EventEmitter_1.EventEmitter)); +exports.CircularList = CircularList; + + + +},{"../EventEmitter":7}],31:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function contains(arr, el) { + return arr.indexOf(el) >= 0; +} +exports.contains = contains; +; + + + +},{}],32:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var MouseHelper = (function () { + function MouseHelper(_renderer) { + this._renderer = _renderer; + } + MouseHelper.getCoordsRelativeToElement = function (event, element) { + if (event.pageX == null) { + return null; + } + var originalElement = element; + var x = event.pageX; + var y = event.pageY; + while (element) { + x -= element.offsetLeft; + y -= element.offsetTop; + element = 'offsetParent' in element ? element.offsetParent : element.parentElement; + } + element = originalElement; + while (element && element !== element.ownerDocument.body) { + x += element.scrollLeft; + y += element.scrollTop; + element = element.parentElement; + } + return [x, y]; + }; + MouseHelper.prototype.getCoords = function (event, element, charMeasure, lineHeight, colCount, rowCount, isSelection) { + if (!charMeasure.width || !charMeasure.height) { + return null; + } + var coords = MouseHelper.getCoordsRelativeToElement(event, element); + if (!coords) { + return null; + } + coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderer.dimensions.actualCellWidth / 2 : 0)) / this._renderer.dimensions.actualCellWidth); + coords[1] = Math.ceil(coords[1] / this._renderer.dimensions.actualCellHeight); + coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0)); + coords[1] = Math.min(Math.max(coords[1], 1), rowCount); + return coords; + }; + MouseHelper.prototype.getRawByteCoords = function (event, element, charMeasure, lineHeight, colCount, rowCount) { + var coords = this.getCoords(event, element, charMeasure, lineHeight, colCount, rowCount); + var x = coords[0]; + var y = coords[1]; + x += 32; + y += 32; + return { x: x, y: y }; + }; + return MouseHelper; +}()); +exports.MouseHelper = MouseHelper; + + + +},{}],33:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BellSound = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg=='; + + + +},{}],34:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Terminal_1 = require("./Terminal"); +module.exports = Terminal_1.Terminal; + + + +},{"./Terminal":13}]},{},[34])(34) +}); +//# sourceMappingURL=xterm.js.map diff --git a/web/static/build/xterm.js.map b/web/static/build/xterm.js.map new file mode 100755 index 000000000..9716c5e22 --- /dev/null +++ b/web/static/build/xterm.js.map @@ -0,0 +1 @@ +{"version":3,"file":"xterm.js","sources":["../src/xterm.ts","../src/utils/Sounds.ts","../src/utils/MouseHelper.ts","../src/utils/Generic.ts","../src/utils/CircularList.ts","../src/utils/CharMeasure.ts","../src/utils/Browser.ts","../src/renderer/Types.ts","../src/renderer/TextRenderLayer.ts","../src/renderer/SelectionRenderLayer.ts","../src/renderer/Renderer.ts","../src/renderer/LinkRenderLayer.ts","../src/renderer/GridCache.ts","../src/renderer/CursorRenderLayer.ts","../src/renderer/ColorManager.ts","../src/renderer/CharAtlas.ts","../src/renderer/BaseRenderLayer.ts","../src/input/MouseZoneManager.ts","../src/handlers/Clipboard.ts","../src/Viewport.ts","../src/Types.ts","../src/Terminal.ts","../src/SelectionModel.ts","../src/SelectionManager.ts","../src/Parser.ts","../src/Linkifier.ts","../src/InputHandler.ts","../src/EventEmitter.ts","../src/EscapeSequences.ts","../src/CompositionHelper.ts","../src/Charsets.ts","../src/CharWidth.ts","../src/BufferSet.ts","../src/Buffer.ts","../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This file is the entry point for browserify.\n */\n\nimport { Terminal } from './Terminal';\n\nmodule.exports = Terminal;\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n// Source: https://freesound.org/people/altemark/sounds/45759/\n// This sound is released under the Creative Commons Attribution 3.0 Unported\n// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been\n// made, apart from the conversion to base64.\nexport const BellSound = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICharMeasure } from '../Interfaces';\nimport { IRenderer } from '../renderer/Interfaces';\n\nexport class MouseHelper {\n constructor(private _renderer: IRenderer) {}\n\n public static getCoordsRelativeToElement(event: {pageX: number, pageY: number}, element: HTMLElement): [number, number] {\n // Ignore browsers that don't support MouseEvent.pageX\n if (event.pageX == null) {\n return null;\n }\n\n const originalElement = element;\n let x = event.pageX;\n let y = event.pageY;\n\n // Converts the coordinates from being relative to the document to being\n // relative to the terminal.\n while (element) {\n x -= element.offsetLeft;\n y -= element.offsetTop;\n element = 'offsetParent' in element ? element.offsetParent : element.parentElement;\n }\n element = originalElement;\n while (element && element !== element.ownerDocument.body) {\n x += element.scrollLeft;\n y += element.scrollTop;\n element = element.parentElement;\n }\n return [x, y];\n }\n\n /**\n * Gets coordinates within the terminal for a particular mouse event. The result\n * is returned as an array in the form [x, y] instead of an object as it's a\n * little faster and this function is used in some low level code.\n * @param event The mouse event.\n * @param element The terminal's container element.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows n the terminal.\n * @param isSelection Whether the request is for the selection or not. This will\n * apply an offset to the x value such that the left half of the cell will\n * select that cell and the right half will select the next cell.\n */\n public getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {\n // Coordinates cannot be measured if charMeasure has not been initialized\n if (!charMeasure.width || !charMeasure.height) {\n return null;\n }\n\n const coords = MouseHelper.getCoordsRelativeToElement(event, element);\n if (!coords) {\n return null;\n }\n\n coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderer.dimensions.actualCellWidth / 2 : 0)) / this._renderer.dimensions.actualCellWidth);\n coords[1] = Math.ceil(coords[1] / this._renderer.dimensions.actualCellHeight);\n\n // Ensure coordinates are within the terminal viewport. Note that selections\n // need an addition point of precision to cover the end point (as characters\n // cover half of one char and half of the next).\n coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));\n coords[1] = Math.min(Math.max(coords[1], 1), rowCount);\n\n return coords;\n }\n\n /**\n * Gets coordinates within the terminal for a particular mouse event, wrapping\n * them to the bounds of the terminal and adding 32 to both the x and y values\n * as expected by xterm.\n * @param event The mouse event.\n * @param element The terminal's container element.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows in the terminal.\n */\n public getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number } {\n const coords = this.getCoords(event, element, charMeasure, lineHeight, colCount, rowCount);\n let x = coords[0];\n let y = coords[1];\n\n // xterm sends raw bytes and starts at 32 (SP) for each.\n x += 32;\n y += 32;\n\n return { x, y };\n }\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/**\n * Return if the given array contains the given element\n * @param {Array} array The array to search for the given element.\n * @param {Object} el The element to look for into the array\n */\nexport function contains(arr: any[], el: any): boolean {\n return arr.indexOf(el) >= 0;\n};\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { EventEmitter } from '../EventEmitter';\nimport { ICircularList } from '../Interfaces';\n\n/**\n * Represents a circular list; a list with a maximum size that wraps around when push is called,\n * overriding values at the start of the list.\n */\nexport class CircularList extends EventEmitter implements ICircularList {\n protected _array: T[];\n private _startIndex: number;\n private _length: number;\n\n constructor(\n private _maxLength: number\n ) {\n super();\n this._array = new Array(this._maxLength);\n this._startIndex = 0;\n this._length = 0;\n }\n\n public get maxLength(): number {\n return this._maxLength;\n }\n\n public set maxLength(newMaxLength: number) {\n // There was no change in maxLength, return early.\n if (this._maxLength === newMaxLength) {\n return;\n }\n\n // Reconstruct array, starting at index 0. Only transfer values from the\n // indexes 0 to length.\n let newArray = new Array(newMaxLength);\n for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {\n newArray[i] = this._array[this._getCyclicIndex(i)];\n }\n this._array = newArray;\n this._maxLength = newMaxLength;\n this._startIndex = 0;\n }\n\n public get length(): number {\n return this._length;\n }\n\n public set length(newLength: number) {\n if (newLength > this._length) {\n for (let i = this._length; i < newLength; i++) {\n this._array[i] = undefined;\n }\n }\n this._length = newLength;\n }\n\n public get forEach(): (callbackfn: (value: T, index: number) => void) => void {\n return (callbackfn: (value: T, index: number) => void) => {\n let i = 0;\n let length = this.length;\n for (let i = 0; i < length; i++) {\n callbackfn(this.get(i), i);\n }\n };\n }\n\n /**\n * Gets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index of the value to get.\n * @return The value corresponding to the index.\n */\n public get(index: number): T {\n return this._array[this._getCyclicIndex(index)];\n }\n\n /**\n * Sets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index to set.\n * @param value The value to set.\n */\n public set(index: number, value: T): void {\n this._array[this._getCyclicIndex(index)] = value;\n }\n\n /**\n * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0\n * if the maximum length is reached.\n * @param value The value to push onto the list.\n */\n public push(value: T): void {\n this._array[this._getCyclicIndex(this._length)] = value;\n if (this._length === this._maxLength) {\n this._startIndex++;\n if (this._startIndex === this._maxLength) {\n this._startIndex = 0;\n }\n this.emit('trim', 1);\n } else {\n this._length++;\n }\n }\n\n /**\n * Removes and returns the last value on the list.\n * @return The popped value.\n */\n public pop(): T {\n return this._array[this._getCyclicIndex(this._length-- - 1)];\n }\n\n /**\n * Deletes and/or inserts items at a particular index (in that order). Unlike\n * Array.prototype.splice, this operation does not return the deleted items as a new array in\n * order to save creating a new array. Note that this operation may shift all values in the list\n * in the worst case.\n * @param start The index to delete and/or insert.\n * @param deleteCount The number of elements to delete.\n * @param items The items to insert.\n */\n public splice(start: number, deleteCount: number, ...items: T[]): void {\n // Delete items\n if (deleteCount) {\n for (let i = start; i < this._length - deleteCount; i++) {\n this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];\n }\n this._length -= deleteCount;\n }\n\n if (items && items.length) {\n // Add items\n for (let i = this._length - 1; i >= start; i--) {\n this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];\n }\n for (let i = 0; i < items.length; i++) {\n this._array[this._getCyclicIndex(start + i)] = items[i];\n }\n\n // Adjust length as needed\n if (this._length + items.length > this.maxLength) {\n const countToTrim = (this._length + items.length) - this.maxLength;\n this._startIndex += countToTrim;\n this._length = this.maxLength;\n this.emit('trim', countToTrim);\n } else {\n this._length += items.length;\n }\n }\n }\n\n /**\n * Trims a number of items from the start of the list.\n * @param count The number of items to remove.\n */\n public trimStart(count: number): void {\n if (count > this._length) {\n count = this._length;\n }\n this._startIndex += count;\n this._length -= count;\n this.emit('trim', count);\n }\n\n public shiftElements(start: number, count: number, offset: number): void {\n if (count <= 0) {\n return;\n }\n if (start < 0 || start >= this._length) {\n throw new Error('start argument out of range');\n }\n if (start + offset < 0) {\n throw new Error('Cannot shift elements in list beyond index 0');\n }\n\n if (offset > 0) {\n for (let i = count - 1; i >= 0; i--) {\n this.set(start + i + offset, this.get(start + i));\n }\n const expandListBy = (start + count + offset) - this._length;\n if (expandListBy > 0) {\n this._length += expandListBy;\n while (this._length > this.maxLength) {\n this._length--;\n this._startIndex++;\n this.emit('trim', 1);\n }\n }\n } else {\n for (let i = 0; i < count; i++) {\n this.set(start + i + offset, this.get(start + i));\n }\n }\n }\n\n /**\n * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the\n * backing array to get the element associated with the regular index.\n * @param index The regular index.\n * @returns The cyclic index.\n */\n private _getCyclicIndex(index: number): number {\n return (this._startIndex + index) % this.maxLength;\n }\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { EventEmitter } from '../EventEmitter';\nimport { ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces';\n\n/**\n * Utility class that measures the size of a character. Measurements are done in\n * the DOM rather than with a canvas context because support for extracting the\n * height of characters is patchy across browsers.\n */\nexport class CharMeasure extends EventEmitter implements ICharMeasure {\n private _document: Document;\n private _parentElement: HTMLElement;\n private _measureElement: HTMLElement;\n private _width: number;\n private _height: number;\n\n constructor(document: Document, parentElement: HTMLElement) {\n super();\n this._document = document;\n this._parentElement = parentElement;\n }\n\n public get width(): number {\n return this._width;\n }\n\n public get height(): number {\n return this._height;\n }\n\n public measure(options: ITerminalOptions): void {\n if (!this._measureElement) {\n this._measureElement = this._document.createElement('span');\n this._measureElement.style.position = 'absolute';\n this._measureElement.style.top = '0';\n this._measureElement.style.left = '-9999em';\n this._measureElement.style.lineHeight = 'normal';\n this._measureElement.textContent = 'W';\n this._measureElement.setAttribute('aria-hidden', 'true');\n this._parentElement.appendChild(this._measureElement);\n // Perform _doMeasure async if the element was just attached as sometimes\n // getBoundingClientRect does not return accurate values without this.\n setTimeout(() => this._doMeasure(options), 0);\n } else {\n this._doMeasure(options);\n }\n }\n\n private _doMeasure(options: ITerminalOptions): void {\n this._measureElement.style.fontFamily = options.fontFamily;\n this._measureElement.style.fontSize = `${options.fontSize}px`;\n const geometry = this._measureElement.getBoundingClientRect();\n // The element is likely currently display:none, we should retain the\n // previous value.\n if (geometry.width === 0 || geometry.height === 0) {\n return;\n }\n if (this._width !== geometry.width || this._height !== geometry.height) {\n this._width = geometry.width;\n this._height = Math.ceil(geometry.height);\n this.emit('charsizechanged');\n }\n }\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { contains } from './Generic';\n\nconst isNode = (typeof navigator === 'undefined') ? true : false;\nconst userAgent = (isNode) ? 'node' : navigator.userAgent;\nconst platform = (isNode) ? 'node' : navigator.platform;\n\nexport const isFirefox = !!~userAgent.indexOf('Firefox');\nexport const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');\n\n// Find the users platform. We use this to interpret the meta key\n// and ISO third level shifts.\n// http://stackoverflow.com/q/19877924/577598\nexport const isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);\nexport const isIpad = platform === 'iPad';\nexport const isIphone = platform === 'iPhone';\nexport const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);\nexport const isLinux = platform.indexOf('Linux') >= 0;\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n /**\n * Flags used to render terminal text properly.\n */\nexport enum FLAGS {\n BOLD = 1,\n UNDERLINE = 2,\n BLINK = 4,\n INVERSE = 8,\n INVISIBLE = 16,\n DIM = 32\n};\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColorSet, IRenderDimensions } from './Interfaces';\nimport { IBuffer, ICharMeasure, ITerminal } from '../Interfaces';\nimport { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer';\nimport { FLAGS } from './Types';\nimport { GridCache } from './GridCache';\nimport { CharData } from '../Types';\nimport { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';\n\n/**\n * This CharData looks like a null character, which will forc a clear and render\n * when the character changes (a regular space ' ' character may not as it's\n * drawn state is a cleared cell).\n */\nconst OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1];\n\nexport class TextRenderLayer extends BaseRenderLayer {\n private _state: GridCache;\n private _characterWidth: number;\n private _characterFont: string;\n private _characterOverlapCache: { [key: string]: boolean } = {};\n\n constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {\n super(container, 'text', zIndex, false, colors);\n this._state = new GridCache();\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {\n super.resize(terminal, dim, charSizeChanged);\n\n // Clear the character width cache if the font or width has changed\n const terminalFont = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;\n if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) {\n this._characterWidth = dim.scaledCharWidth;\n this._characterFont = terminalFont;\n this._characterOverlapCache = {};\n }\n // Resizing the canvas discards the contents of the canvas so clear state\n this._state.clear();\n this._state.resize(terminal.cols, terminal.rows);\n }\n\n public reset(terminal: ITerminal): void {\n this._state.clear();\n this.clearAll();\n }\n\n public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {\n // Resize has not been called yet\n if (this._state.cache.length === 0) {\n return;\n }\n\n for (let y = startRow; y <= endRow; y++) {\n const row = y + terminal.buffer.ydisp;\n const line = terminal.buffer.lines.get(row);\n\n this.clearCells(0, y, terminal.cols, 1);\n // for (let x = 0; x < terminal.cols; x++) {\n // this._state.cache[x][y] = null;\n // }\n\n for (let x = 0; x < terminal.cols; x++) {\n const charData = line[x];\n const code: number = charData[CHAR_DATA_CODE_INDEX];\n const char: string = charData[CHAR_DATA_CHAR_INDEX];\n const attr: number = charData[CHAR_DATA_ATTR_INDEX];\n let width: number = charData[CHAR_DATA_WIDTH_INDEX];\n\n // The character to the left is a wide character, drawing is owned by\n // the char at x-1\n if (width === 0) {\n // this._state.cache[x][y] = null;\n continue;\n }\n\n // If the character is a space and the character to the left is an\n // overlapping character, skip the character and allow the overlapping\n // char to take full control over this character's cell.\n if (code === 32 /*' '*/) {\n if (x > 0) {\n const previousChar: CharData = line[x - 1];\n if (this._isOverlapping(previousChar)) {\n continue;\n }\n }\n }\n\n // Skip rendering if the character is identical\n // const state = this._state.cache[x][y];\n // if (state && state[CHAR_DATA_CHAR_INDEX] === char && state[CHAR_DATA_ATTR_INDEX] === attr) {\n // // Skip render, contents are identical\n // this._state.cache[x][y] = charData;\n // continue;\n // }\n\n // Clear the old character was not a space with the default background\n // const wasInverted = !!(state && state[CHAR_DATA_ATTR_INDEX] && state[CHAR_DATA_ATTR_INDEX] >> 18 & FLAGS.INVERSE);\n // if (state && !(state[CHAR_DATA_CODE_INDEX] === 32 /*' '*/ && (state[CHAR_DATA_ATTR_INDEX] & 0x1ff) >= 256 && !wasInverted)) {\n // this._clearChar(x, y);\n // }\n // this._state.cache[x][y] = charData;\n\n const flags = attr >> 18;\n let bg = attr & 0x1ff;\n\n // Skip rendering if the character is invisible\n const isDefaultBackground = bg >= 256;\n const isInvisible = flags & FLAGS.INVISIBLE;\n const isInverted = flags & FLAGS.INVERSE;\n if (!code || (code === 32 /*' '*/ && isDefaultBackground && !isInverted) || isInvisible) {\n continue;\n }\n\n // If the character is an overlapping char and the character to the right is a\n // space, take ownership of the cell to the right.\n if (width !== 0 && this._isOverlapping(charData)) {\n // If the character is overlapping, we want to force a re-render on every\n // frame. This is specifically to work around the case where two\n // overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a\n // space is added. Without this, the first half of `b` would never\n // get removed, and `a` would not re-render because it thinks it's\n // already in the correct state.\n // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA;\n if (x < line.length - 1 && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) {\n width = 2;\n // this._clearChar(x + 1, y);\n // The overlapping char's char data will force a clear and render when the\n // overlapping char is no longer to the left of the character and also when\n // the space changes to another character.\n // this._state.cache[x + 1][y] = OVERLAP_OWNED_CHAR_DATA;\n }\n }\n\n let fg = (attr >> 9) & 0x1ff;\n\n // If inverse flag is on, the foreground should become the background.\n if (isInverted) {\n const temp = bg;\n bg = fg;\n fg = temp;\n if (fg === 256) {\n fg = INVERTED_DEFAULT_COLOR;\n }\n if (bg === 257) {\n bg = INVERTED_DEFAULT_COLOR;\n }\n }\n\n // Clear the cell next to this character if it's wide\n if (width === 2) {\n // this.clearCells(x + 1, y, 1, 1);\n }\n\n // Draw background\n if (bg < 256) {\n this._ctx.save();\n this._ctx.fillStyle = (bg === INVERTED_DEFAULT_COLOR ? this._colors.foreground : this._colors.ansi[bg]);\n this.fillCells(x, y, width, 1);\n this._ctx.restore();\n }\n\n this._ctx.save();\n if (flags & FLAGS.BOLD) {\n this._ctx.font = `bold ${this._ctx.font}`;\n // Convert the FG color to the bold variant\n if (fg < 8) {\n fg += 8;\n }\n }\n\n if (flags & FLAGS.UNDERLINE) {\n if (fg === INVERTED_DEFAULT_COLOR) {\n this._ctx.fillStyle = this._colors.background;\n } else if (fg < 256) {\n // 256 color support\n this._ctx.fillStyle = this._colors.ansi[fg];\n } else {\n this._ctx.fillStyle = this._colors.foreground;\n }\n this.fillBottomLineAtCells(x, y);\n }\n\n this.drawChar(terminal, char, code, width, x, y, fg, bg, !!(flags & FLAGS.BOLD), !!(flags & FLAGS.DIM));\n\n this._ctx.restore();\n }\n }\n }\n\n\t/**\n\t * Whether a character is overlapping to the next cell.\n\t */\n private _isOverlapping(charData: CharData): boolean {\n // Only single cell characters can be overlapping, rendering issues can\n // occur without this check\n if (charData[CHAR_DATA_WIDTH_INDEX] !== 1) {\n return false;\n }\n\n // We assume that any ascii character will not overlap\n const code = charData[CHAR_DATA_CODE_INDEX];\n if (code < 256) {\n return false;\n }\n\n // Deliver from cache if available\n const char = charData[CHAR_DATA_CHAR_INDEX];\n if (this._characterOverlapCache.hasOwnProperty(char)) {\n return this._characterOverlapCache[char];\n }\n\n // Setup the font\n this._ctx.save();\n this._ctx.font = this._characterFont;\n\n // Measure the width of the character, but Math.floor it\n // because that is what the renderer does when it calculates\n // the character dimensions we are comparing against\n const overlaps = Math.floor(this._ctx.measureText(char).width) > this._characterWidth;\n\n // Restore the original context\n this._ctx.restore();\n\n // Cache and return\n this._characterOverlapCache[char] = overlaps;\n return overlaps;\n }\n\n /**\n * Clear the charcater at the cell specified.\n * @param x The column of the char.\n * @param y The row of the char.\n */\n private _clearChar(x: number, y: number): void {\n let colsToClear = 1;\n // Clear the adjacent character if it was wide\n const state = this._state.cache[x][y];\n if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) {\n colsToClear = 2;\n }\n this.clearCells(x, y, colsToClear, 1);\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColorSet, IRenderDimensions } from './Interfaces';\nimport { IBuffer, ICharMeasure, ITerminal } from '../Interfaces';\nimport { CHAR_DATA_ATTR_INDEX } from '../Buffer';\nimport { GridCache } from './GridCache';\nimport { FLAGS } from './Types';\nimport { BaseRenderLayer } from './BaseRenderLayer';\n\nexport class SelectionRenderLayer extends BaseRenderLayer {\n private _state: {start: [number, number], end: [number, number]};\n\n constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {\n super(container, 'selection', zIndex, true, colors);\n this._state = {\n start: null,\n end: null\n };\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {\n super.resize(terminal, dim, charSizeChanged);\n // Resizing the canvas discards the contents of the canvas so clear state\n this._state = {\n start: null,\n end: null\n };\n }\n\n public reset(terminal: ITerminal): void {\n if (this._state.start && this._state.end) {\n this._state = {\n start: null,\n end: null\n };\n this.clearAll();\n }\n }\n\n public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void {\n // Selection has not changed\n if (this._state.start === start || this._state.end === end) {\n return;\n }\n\n // Remove all selections\n this.clearAll();\n\n // Selection does not exist\n if (!start || !end) {\n return;\n }\n\n // Translate from buffer position to viewport position\n const viewportStartRow = start[1] - terminal.buffer.ydisp;\n const viewportEndRow = end[1] - terminal.buffer.ydisp;\n const viewportCappedStartRow = Math.max(viewportStartRow, 0);\n const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);\n\n // No need to draw the selection\n if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) {\n return;\n }\n\n // Draw first row\n const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;\n const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols;\n this._ctx.fillStyle = this._colors.selection;\n this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);\n\n // Draw middle rows\n const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);\n this.fillCells(0, viewportCappedStartRow + 1, terminal.cols, middleRowsCount);\n\n // Draw final row\n if (viewportCappedStartRow !== viewportCappedEndRow) {\n // Only draw viewportEndRow if it's not the same as viewportStartRow\n const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols;\n this.fillCells(0, viewportCappedEndRow, endCol, 1);\n }\n\n // Save state for next render\n this._state.start = [start[0], start[1]];\n this._state.end = [end[0], end[1]];\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, ITheme } from '../Interfaces';\nimport { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';\nimport { TextRenderLayer } from './TextRenderLayer';\nimport { SelectionRenderLayer } from './SelectionRenderLayer';\nimport { CursorRenderLayer } from './CursorRenderLayer';\nimport { ColorManager } from './ColorManager';\nimport { BaseRenderLayer } from './BaseRenderLayer';\nimport { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Interfaces';\nimport { LinkRenderLayer } from './LinkRenderLayer';\nimport { EventEmitter } from '../EventEmitter';\n\nexport class Renderer extends EventEmitter implements IRenderer {\n /** A queue of the rows to be refreshed */\n private _refreshRowsQueue: {start: number, end: number}[] = [];\n private _refreshAnimationFrame = null;\n\n private _renderLayers: IRenderLayer[];\n private _devicePixelRatio: number;\n\n public colorManager: ColorManager;\n public dimensions: IRenderDimensions;\n\n constructor(private _terminal: ITerminal, theme: ITheme) {\n super();\n this.colorManager = new ColorManager();\n if (theme) {\n this.colorManager.setTheme(theme);\n }\n this._renderLayers = [\n new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors),\n new SelectionRenderLayer(this._terminal.element, 1, this.colorManager.colors),\n new LinkRenderLayer(this._terminal.element, 2, this.colorManager.colors, this._terminal),\n new CursorRenderLayer(this._terminal.element, 3, this.colorManager.colors)\n ];\n this.dimensions = {\n scaledCharWidth: null,\n scaledCharHeight: null,\n scaledCellWidth: null,\n scaledCellHeight: null,\n scaledCharLeft: null,\n scaledCharTop: null,\n scaledCanvasWidth: null,\n scaledCanvasHeight: null,\n canvasWidth: null,\n canvasHeight: null,\n actualCellWidth: null,\n actualCellHeight: null\n };\n this._devicePixelRatio = window.devicePixelRatio;\n }\n\n public onWindowResize(devicePixelRatio: number): void {\n // If the device pixel ratio changed, the char atlas needs to be regenerated\n // and the terminal needs to refreshed\n if (this._devicePixelRatio !== devicePixelRatio) {\n this._devicePixelRatio = devicePixelRatio;\n this.onResize(this._terminal.cols, this._terminal.rows, true);\n }\n }\n\n public setTheme(theme: ITheme): IColorSet {\n this.colorManager.setTheme(theme);\n\n // Clear layers and force a full render\n this._renderLayers.forEach(l => {\n l.onThemeChanged(this._terminal, this.colorManager.colors);\n l.reset(this._terminal);\n });\n\n this._terminal.refresh(0, this._terminal.rows - 1);\n\n return this.colorManager.colors;\n }\n\n public onResize(cols: number, rows: number, didCharSizeChange: boolean): void {\n if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) {\n return;\n }\n\n // Calculate the scaled character width. Width is floored as it must be\n // drawn to an integer grid in order for the CharAtlas \"stamps\" to not be\n // blurry. When text is drawn to the grid not using the CharAtlas, it is\n // clipped to ensure there is no overlap with the next cell.\n this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio);\n\n // Calculate the scaled character height. Height is ceiled in case\n // devicePixelRatio is a floating point number in order to ensure there is\n // enough space to draw the character to the cell.\n this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio);\n\n // Calculate the scaled cell height, if lineHeight is not 1 then the value\n // will be floored because since lineHeight can never be lower then 1, there\n // is a guarentee that the scaled line height will always be larger than\n // scaled char height.\n this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);\n\n // Calculate the y coordinate within a cell that text should draw from in\n // order to draw in the center of a cell.\n this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);\n\n // Calculate the scaled cell width, taking the letterSpacing into account.\n this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);\n\n // Calculate the x coordinate with a cell that text should draw from in\n // order to draw in the center of a cell.\n this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2);\n\n // Recalculate the canvas dimensions; scaled* define the actual number of\n // pixel in the canvas\n this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;\n this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;\n\n // The the size of the canvas on the page. It's very important that this\n // rounds to nearest integer and not ceils as browsers often set\n // window.devicePixelRatio as something like 1.100000023841858, when it's\n // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1\n // pixel too large for the canvas element size.\n this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);\n this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);\n\n // Get the _actual_ dimensions of an individual cell. This needs to be\n // derived from the canvasWidth/Height calculated above which takes into\n // account window.devicePixelRatio. CharMeasure.width/height by itself is\n // insufficient when the page is not at 100% zoom level as CharMeasure is\n // measured in CSS pixels, but the actual char size on the canvas can\n // differ.\n this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;\n this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;\n\n // Resize all render layers\n this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions, didCharSizeChange));\n\n // Force a refresh\n this._terminal.refresh(0, this._terminal.rows - 1);\n\n this.emit('resize', {\n width: this.dimensions.canvasWidth,\n height: this.dimensions.canvasHeight\n });\n }\n\n public onCharSizeChanged(): void {\n this.onResize(this._terminal.cols, this._terminal.rows, true);\n }\n\n public onBlur(): void {\n this._renderLayers.forEach(l => l.onBlur(this._terminal));\n }\n\n public onFocus(): void {\n this._renderLayers.forEach(l => l.onFocus(this._terminal));\n }\n\n public onSelectionChanged(start: [number, number], end: [number, number]): void {\n this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end));\n }\n\n public onCursorMove(): void {\n this._renderLayers.forEach(l => l.onCursorMove(this._terminal));\n }\n\n public onOptionsChanged(): void {\n this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal));\n }\n\n public clear(): void {\n this._renderLayers.forEach(l => l.reset(this._terminal));\n }\n\n /**\n * Queues a refresh between two rows (inclusive), to be done on next animation\n * frame.\n * @param {number} start The start row.\n * @param {number} end The end row.\n */\n public queueRefresh(start: number, end: number): void {\n this._refreshRowsQueue.push({ start: start, end: end });\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));\n }\n }\n\n /**\n * Performs the refresh loop callback, calling refresh only if a refresh is\n * necessary before queueing up the next one.\n */\n private _refreshLoop(): void {\n let start;\n let end;\n if (this._refreshRowsQueue.length > 4) {\n // Just do a full refresh when 5+ refreshes are queued\n start = 0;\n end = this._terminal.rows - 1;\n } else {\n // Get start and end rows that need refreshing\n start = this._refreshRowsQueue[0].start;\n end = this._refreshRowsQueue[0].end;\n for (let i = 1; i < this._refreshRowsQueue.length; i++) {\n if (this._refreshRowsQueue[i].start < start) {\n start = this._refreshRowsQueue[i].start;\n }\n if (this._refreshRowsQueue[i].end > end) {\n end = this._refreshRowsQueue[i].end;\n }\n }\n }\n this._refreshRowsQueue = [];\n this._refreshAnimationFrame = null;\n\n // Render\n start = Math.max(start, 0);\n end = Math.min(end, this._terminal.rows - 1);\n this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));\n this._terminal.emit('refresh', {start, end});\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColorSet, IRenderDimensions } from './Interfaces';\nimport { IBuffer, ICharMeasure, ITerminal, ILinkifierAccessor } from '../Interfaces';\nimport { CHAR_DATA_ATTR_INDEX } from '../Buffer';\nimport { GridCache } from './GridCache';\nimport { FLAGS } from './Types';\nimport { BaseRenderLayer, INVERTED_DEFAULT_COLOR } from './BaseRenderLayer';\nimport { LinkHoverEvent, LinkHoverEventTypes } from '../Types';\n\nexport class LinkRenderLayer extends BaseRenderLayer {\n private _state: LinkHoverEvent = null;\n\n constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) {\n super(container, 'link', zIndex, true, colors);\n terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: LinkHoverEvent) => this._onLinkHover(e));\n terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: LinkHoverEvent) => this._onLinkLeave(e));\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {\n super.resize(terminal, dim, charSizeChanged);\n // Resizing the canvas discards the contents of the canvas so clear state\n this._state = null;\n }\n\n public reset(terminal: ITerminal): void {\n this._clearCurrentLink();\n }\n\n private _clearCurrentLink(): void {\n if (this._state) {\n this.clearCells(this._state.x, this._state.y, this._state.length, 1);\n this._state = null;\n }\n }\n\n private _onLinkHover(e: LinkHoverEvent): void {\n this._ctx.fillStyle = this._colors.foreground;\n this.fillBottomLineAtCells(e.x, e.y, e.length);\n this._state = e;\n }\n\n private _onLinkLeave(e: LinkHoverEvent): void {\n this._clearCurrentLink();\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport class GridCache {\n public cache: T[][];\n\n public constructor() {\n this.cache = [];\n }\n\n public resize(width: number, height: number): void {\n for (let x = 0; x < width; x++) {\n if (this.cache.length <= x) {\n this.cache.push([]);\n }\n for (let y = this.cache[x].length; y < height; y++) {\n this.cache[x].push(null);\n }\n this.cache[x].length = height;\n }\n this.cache.length = width;\n }\n\n public clear(): void {\n for (let x = 0; x < this.cache.length; x++) {\n for (let y = 0; y < this.cache[x].length; y++) {\n this.cache[x][y] = null;\n }\n }\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColorSet, IRenderDimensions } from './Interfaces';\nimport { IBuffer, ICharMeasure, ITerminal, ITerminalOptions } from '../Interfaces';\nimport { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';\nimport { GridCache } from './GridCache';\nimport { FLAGS } from './Types';\nimport { BaseRenderLayer } from './BaseRenderLayer';\nimport { CharData } from '../Types';\n\ninterface CursorState {\n x: number;\n y: number;\n isFocused: boolean;\n style: string;\n width: number;\n}\n\n/**\n * The time between cursor blinks.\n */\nconst BLINK_INTERVAL = 600;\n\nexport class CursorRenderLayer extends BaseRenderLayer {\n private _state: CursorState;\n private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, charData: CharData) => void};\n private _cursorBlinkStateManager: CursorBlinkStateManager;\n private _isFocused: boolean;\n\n constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {\n super(container, 'cursor', zIndex, true, colors);\n this._state = {\n x: null,\n y: null,\n isFocused: null,\n style: null,\n width: null,\n };\n this._cursorRenderers = {\n 'bar': this._renderBarCursor.bind(this),\n 'block': this._renderBlockCursor.bind(this),\n 'underline': this._renderUnderlineCursor.bind(this)\n };\n // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {\n super.resize(terminal, dim, charSizeChanged);\n // Resizing the canvas discards the contents of the canvas so clear state\n this._state = {\n x: null,\n y: null,\n isFocused: null,\n style: null,\n width: null,\n };\n }\n\n public reset(terminal: ITerminal): void {\n this._clearCursor();\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.dispose();\n this._cursorBlinkStateManager = null;\n this.onOptionsChanged(terminal);\n }\n }\n\n public onBlur(terminal: ITerminal): void {\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.pause();\n }\n terminal.refresh(terminal.buffer.y, terminal.buffer.y);\n }\n\n public onFocus(terminal: ITerminal): void {\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.resume(terminal);\n } else {\n terminal.refresh(terminal.buffer.y, terminal.buffer.y);\n }\n }\n\n public onOptionsChanged(terminal: ITerminal): void {\n if (terminal.options.cursorBlink) {\n if (!this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => {\n this._render(terminal, true);\n });\n }\n } else {\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.dispose();\n this._cursorBlinkStateManager = null;\n }\n // Request a refresh from the terminal as management of rendering is being\n // moved back to the terminal\n terminal.refresh(terminal.buffer.y, terminal.buffer.y);\n }\n }\n\n public onCursorMove(terminal: ITerminal): void {\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.restartBlinkAnimation(terminal);\n }\n }\n\n public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {\n // Only render if the animation frame is not active\n if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) {\n this._render(terminal, false);\n }\n }\n\n private _render(terminal: ITerminal, triggeredByAnimationFrame: boolean): void {\n // Don't draw the cursor if it's hidden\n if (!terminal.cursorState || terminal.cursorHidden) {\n this._clearCursor();\n return;\n }\n\n const cursorY = terminal.buffer.ybase + terminal.buffer.y;\n const viewportRelativeCursorY = cursorY - terminal.buffer.ydisp;\n\n // Don't draw the cursor if it's off-screen\n if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) {\n this._clearCursor();\n return;\n }\n\n const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x];\n if (!charData) {\n return;\n }\n\n if (!terminal.isFocused) {\n this._clearCursor();\n this._ctx.save();\n this._ctx.fillStyle = this._colors.cursor;\n this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, charData);\n this._ctx.restore();\n this._state.x = terminal.buffer.x;\n this._state.y = viewportRelativeCursorY;\n this._state.isFocused = false;\n this._state.style = terminal.options.cursorStyle;\n this._state.width = charData[CHAR_DATA_WIDTH_INDEX];\n return;\n }\n\n // Don't draw the cursor if it's blinking\n if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) {\n this._clearCursor();\n return;\n }\n\n if (this._state) {\n // The cursor is already in the correct spot, don't redraw\n if (this._state.x === terminal.buffer.x &&\n this._state.y === viewportRelativeCursorY &&\n this._state.isFocused === terminal.isFocused &&\n this._state.style === terminal.options.cursorStyle &&\n this._state.width === charData[CHAR_DATA_WIDTH_INDEX]) {\n return;\n }\n this._clearCursor();\n }\n\n this._ctx.save();\n this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, charData);\n this._ctx.restore();\n\n this._state.x = terminal.buffer.x;\n this._state.y = viewportRelativeCursorY;\n this._state.isFocused = false;\n this._state.style = terminal.options.cursorStyle;\n this._state.width = charData[CHAR_DATA_WIDTH_INDEX];\n }\n\n private _clearCursor(): void {\n if (this._state) {\n this.clearCells(this._state.x, this._state.y, this._state.width, 1);\n this._state = {\n x: null,\n y: null,\n isFocused: null,\n style: null,\n width: null,\n };\n }\n }\n\n private _renderBarCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {\n this._ctx.save();\n this._ctx.fillStyle = this._colors.cursor;\n this.fillLeftLineAtCell(x, y);\n this._ctx.restore();\n }\n\n private _renderBlockCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {\n this._ctx.save();\n this._ctx.fillStyle = this._colors.cursor;\n this.fillCells(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1);\n this._ctx.fillStyle = this._colors.cursorAccent;\n this.fillCharTrueColor(terminal, charData, x, y);\n this._ctx.restore();\n }\n\n private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {\n this._ctx.save();\n this._ctx.fillStyle = this._colors.cursor;\n this.fillBottomLineAtCells(x, y);\n this._ctx.restore();\n }\n\n private _renderBlurCursor(terminal: ITerminal, x: number, y: number, charData: CharData): void {\n this._ctx.save();\n this._ctx.strokeStyle = this._colors.cursor;\n this.strokeRectAtCell(x, y, charData[CHAR_DATA_WIDTH_INDEX], 1);\n this._ctx.restore();\n }\n}\n\nclass CursorBlinkStateManager {\n public isCursorVisible: boolean;\n\n private _animationFrame: number;\n private _blinkStartTimeout: number;\n private _blinkInterval: number;\n\n /**\n * The time at which the animation frame was restarted, this is used on the\n * next render to restart the timers so they don't need to restart the timers\n * multiple times over a short period.\n */\n private _animationTimeRestarted: number;\n\n constructor(\n terminal: ITerminal,\n private renderCallback: () => void\n ) {\n this.isCursorVisible = true;\n if (terminal.isFocused) {\n this._restartInterval();\n }\n }\n\n public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); }\n\n public dispose(): void {\n if (this._blinkInterval) {\n window.clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n if (this._blinkStartTimeout) {\n window.clearTimeout(this._blinkStartTimeout);\n this._blinkStartTimeout = null;\n }\n if (this._animationFrame) {\n window.cancelAnimationFrame(this._animationFrame);\n this._animationFrame = null;\n }\n }\n\n public restartBlinkAnimation(terminal: ITerminal): void {\n if (this.isPaused) {\n return;\n }\n // Save a timestamp so that the restart can be done on the next interval\n this._animationTimeRestarted = Date.now();\n // Force a cursor render to ensure it's visible and in the correct position\n this.isCursorVisible = true;\n if (!this._animationFrame) {\n this._animationFrame = window.requestAnimationFrame(() => {\n this.renderCallback();\n this._animationFrame = null;\n });\n }\n }\n\n private _restartInterval(timeToStart: number = BLINK_INTERVAL): void {\n // Clear any existing interval\n if (this._blinkInterval) {\n window.clearInterval(this._blinkInterval);\n }\n\n // Setup the initial timeout which will hide the cursor, this is done before\n // the regular interval is setup in order to support restarting the blink\n // animation in a lightweight way (without thrashing clearInterval and\n // setInterval).\n this._blinkStartTimeout = setTimeout(() => {\n // Check if another animation restart was requested while this was being\n // started\n if (this._animationTimeRestarted) {\n const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);\n this._animationTimeRestarted = null;\n if (time > 0) {\n this._restartInterval(time);\n return;\n }\n }\n\n // Hide the cursor\n this.isCursorVisible = false;\n this._animationFrame = window.requestAnimationFrame(() => {\n this.renderCallback();\n this._animationFrame = null;\n });\n\n // Setup the blink interval\n this._blinkInterval = setInterval(() => {\n // Adjust the animation time if it was restarted\n if (this._animationTimeRestarted) {\n // calc time diff\n // Make restart interval do a setTimeout initially?\n const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);\n this._animationTimeRestarted = null;\n this._restartInterval(time);\n return;\n }\n\n // Invert visibility and render\n this.isCursorVisible = !this.isCursorVisible;\n this._animationFrame = window.requestAnimationFrame(() => {\n this.renderCallback();\n this._animationFrame = null;\n });\n }, BLINK_INTERVAL);\n }, timeToStart);\n }\n\n public pause(): void {\n this.isCursorVisible = true;\n if (this._blinkInterval) {\n window.clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n if (this._blinkStartTimeout) {\n window.clearTimeout(this._blinkStartTimeout);\n this._blinkStartTimeout = null;\n }\n if (this._animationFrame) {\n window.cancelAnimationFrame(this._animationFrame);\n this._animationFrame = null;\n }\n }\n\n public resume(terminal: ITerminal): void {\n this._animationTimeRestarted = null;\n this._restartInterval();\n this.restartBlinkAnimation(terminal);\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColorSet, IColorManager } from './Interfaces';\nimport { ITheme } from '../Interfaces';\n\nconst DEFAULT_FOREGROUND = '#ffffff';\nconst DEFAULT_BACKGROUND = '#000000';\nconst DEFAULT_CURSOR = '#ffffff';\nconst DEFAULT_CURSOR_ACCENT = '#000000';\nconst DEFAULT_SELECTION = 'rgba(255, 255, 255, 0.3)';\nexport const DEFAULT_ANSI_COLORS = [\n // dark:\n '#2e3436',\n '#cc0000',\n '#4e9a06',\n '#c4a000',\n '#3465a4',\n '#75507b',\n '#06989a',\n '#d3d7cf',\n // bright:\n '#555753',\n '#ef2929',\n '#8ae234',\n '#fce94f',\n '#729fcf',\n '#ad7fa8',\n '#34e2e2',\n '#eeeeec'\n];\n\n/**\n * Fills an existing 16 length string with the remaining 240 ANSI colors.\n * @param first16Colors The first 16 ANSI colors.\n */\nfunction generate256Colors(first16Colors: string[]): string[] {\n let colors = first16Colors.slice();\n\n // Generate colors (16-231)\n let v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];\n for (let i = 0; i < 216; i++) {\n const r = toPaddedHex(v[(i / 36) % 6 | 0]);\n const g = toPaddedHex(v[(i / 6) % 6 | 0]);\n const b = toPaddedHex(v[i % 6]);\n colors.push(`#${r}${g}${b}`);\n }\n\n // Generate greys (232-255)\n for (let i = 0; i < 24; i++) {\n const c = toPaddedHex(8 + i * 10);\n colors.push(`#${c}${c}${c}`);\n }\n\n return colors;\n}\n\nfunction toPaddedHex(c: number): string {\n let s = c.toString(16);\n return s.length < 2 ? '0' + s : s;\n}\n\n/**\n * Manages the source of truth for a terminal's colors.\n */\nexport class ColorManager implements IColorManager {\n public colors: IColorSet;\n\n constructor() {\n this.colors = {\n foreground: DEFAULT_FOREGROUND,\n background: DEFAULT_BACKGROUND,\n cursor: DEFAULT_CURSOR,\n cursorAccent: DEFAULT_CURSOR_ACCENT,\n selection: DEFAULT_SELECTION,\n ansi: generate256Colors(DEFAULT_ANSI_COLORS)\n };\n }\n\n /**\n * Sets the terminal's theme.\n * @param theme The theme to use. If a partial theme is provided then default\n * colors will be used where colors are not defined.\n */\n public setTheme(theme: ITheme): void {\n this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND;\n this.colors.background = this._validateColor(theme.background, DEFAULT_BACKGROUND);\n this.colors.cursor = theme.cursor || DEFAULT_CURSOR;\n this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT;\n this.colors.selection = theme.selection || DEFAULT_SELECTION;\n this.colors.ansi[0] = theme.black || DEFAULT_ANSI_COLORS[0];\n this.colors.ansi[1] = theme.red || DEFAULT_ANSI_COLORS[1];\n this.colors.ansi[2] = theme.green || DEFAULT_ANSI_COLORS[2];\n this.colors.ansi[3] = theme.yellow || DEFAULT_ANSI_COLORS[3];\n this.colors.ansi[4] = theme.blue || DEFAULT_ANSI_COLORS[4];\n this.colors.ansi[5] = theme.magenta || DEFAULT_ANSI_COLORS[5];\n this.colors.ansi[6] = theme.cyan || DEFAULT_ANSI_COLORS[6];\n this.colors.ansi[7] = theme.white || DEFAULT_ANSI_COLORS[7];\n this.colors.ansi[8] = theme.brightBlack || DEFAULT_ANSI_COLORS[8];\n this.colors.ansi[9] = theme.brightRed || DEFAULT_ANSI_COLORS[9];\n this.colors.ansi[10] = theme.brightGreen || DEFAULT_ANSI_COLORS[10];\n this.colors.ansi[11] = theme.brightYellow || DEFAULT_ANSI_COLORS[11];\n this.colors.ansi[12] = theme.brightBlue || DEFAULT_ANSI_COLORS[12];\n this.colors.ansi[13] = theme.brightMagenta || DEFAULT_ANSI_COLORS[13];\n this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14];\n this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15];\n }\n\n private _validateColor(color: string, fallback: string): string {\n if (!color) {\n return fallback;\n }\n if (color.length === 7 && color.charAt(0) === '#') {\n return color;\n }\n if (color.length === 4 && color.charAt(0) === '#') {\n const r = color.charAt(1);\n const g = color.charAt(2);\n const b = color.charAt(3);\n return `#${r}${r}${g}${g}${b}${b}`;\n }\n return fallback;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, ITheme } from '../Interfaces';\nimport { IColorSet } from '../renderer/Interfaces';\nimport { isFirefox } from '../utils/Browser';\n\nexport const CHAR_ATLAS_CELL_SPACING = 1;\n\ninterface ICharAtlasConfig {\n fontSize: number;\n fontFamily: string;\n scaledCharWidth: number;\n scaledCharHeight: number;\n colors: IColorSet;\n}\n\ninterface ICharAtlasCacheEntry {\n bitmap: HTMLCanvasElement | Promise;\n config: ICharAtlasConfig;\n ownedBy: ITerminal[];\n}\n\nlet charAtlasCache: ICharAtlasCacheEntry[] = [];\n\n/**\n * Acquires a char atlas, either generating a new one or returning an existing\n * one that is in use by another terminal.\n * @param terminal The terminal.\n * @param colors The colors to use.\n */\nexport function acquireCharAtlas(terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number): HTMLCanvasElement | Promise {\n const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);\n\n // Check to see if the terminal already owns this config\n for (let i = 0; i < charAtlasCache.length; i++) {\n const entry = charAtlasCache[i];\n const ownedByIndex = entry.ownedBy.indexOf(terminal);\n if (ownedByIndex >= 0) {\n if (configEquals(entry.config, newConfig)) {\n return entry.bitmap;\n } else {\n // The configs differ, release the terminal from the entry\n if (entry.ownedBy.length === 1) {\n charAtlasCache.splice(i, 1);\n } else {\n entry.ownedBy.splice(ownedByIndex, 1);\n }\n break;\n }\n }\n }\n\n // Try match a char atlas from the cache\n for (let i = 0; i < charAtlasCache.length; i++) {\n const entry = charAtlasCache[i];\n if (configEquals(entry.config, newConfig)) {\n // Add the terminal to the cache entry and return\n entry.ownedBy.push(terminal);\n return entry.bitmap;\n }\n }\n\n const newEntry: ICharAtlasCacheEntry = {\n bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.background, colors.foreground, colors.ansi),\n config: newConfig,\n ownedBy: [terminal]\n };\n charAtlasCache.push(newEntry);\n return newEntry.bitmap;\n}\n\nfunction generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {\n const clonedColors = {\n foreground: colors.foreground,\n background: colors.background,\n cursor: null,\n cursorAccent: null,\n selection: null,\n ansi: colors.ansi.slice(0, 16)\n };\n return {\n scaledCharWidth,\n scaledCharHeight,\n fontFamily: terminal.options.fontFamily,\n fontSize: terminal.options.fontSize,\n colors: clonedColors\n };\n}\n\nfunction configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {\n for (let i = 0; i < a.colors.ansi.length; i++) {\n if (a.colors.ansi[i] !== b.colors.ansi[i]) {\n return false;\n }\n }\n return a.fontFamily === b.fontFamily &&\n a.fontSize === b.fontSize &&\n a.scaledCharWidth === b.scaledCharWidth &&\n a.scaledCharHeight === b.scaledCharHeight &&\n a.colors.foreground === b.colors.foreground &&\n a.colors.background === b.colors.background;\n}\n\nlet generator: CharAtlasGenerator;\n\n/**\n * Initializes the char atlas generator.\n * @param document The document.\n */\nexport function initialize(document: Document): void {\n if (!generator) {\n generator = new CharAtlasGenerator(document);\n }\n}\n\nclass CharAtlasGenerator {\n private _canvas: HTMLCanvasElement;\n private _ctx: CanvasRenderingContext2D;\n\n constructor(private _document: Document) {\n this._canvas = this._document.createElement('canvas');\n this._ctx = this._canvas.getContext('2d', {alpha: false});\n this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);\n }\n\n public generate(scaledCharWidth: number, scaledCharHeight: number, fontSize: number, fontFamily: string, background: string, foreground: string, ansiColors: string[]): HTMLCanvasElement | Promise {\n const cellWidth = scaledCharWidth + CHAR_ATLAS_CELL_SPACING;\n const cellHeight = scaledCharHeight + CHAR_ATLAS_CELL_SPACING;\n this._canvas.width = 255 * cellWidth;\n this._canvas.height = (/*default+default bold*/2 + /*0-15*/16) * cellHeight;\n\n this._ctx.fillStyle = background;\n this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);\n\n this._ctx.save();\n this._ctx.fillStyle = foreground;\n this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`;\n this._ctx.textBaseline = 'top';\n\n // Default color\n for (let i = 0; i < 256; i++) {\n this._ctx.save();\n this._ctx.beginPath();\n this._ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);\n this._ctx.clip();\n this._ctx.fillText(String.fromCharCode(i), i * cellWidth, 0);\n this._ctx.restore();\n }\n // Default color bold\n this._ctx.save();\n this._ctx.font = `bold ${this._ctx.font}`;\n for (let i = 0; i < 256; i++) {\n this._ctx.save();\n this._ctx.beginPath();\n this._ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);\n this._ctx.clip();\n this._ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight);\n this._ctx.restore();\n }\n this._ctx.restore();\n\n // Colors 0-15\n this._ctx.font = `${fontSize * window.devicePixelRatio}px ${fontFamily}`;\n for (let colorIndex = 0; colorIndex < 16; colorIndex++) {\n // colors 8-15 are bold\n if (colorIndex === 8) {\n this._ctx.font = `bold ${this._ctx.font}`;\n }\n const y = (colorIndex + 2) * cellHeight;\n // Draw ascii characters\n for (let i = 0; i < 256; i++) {\n this._ctx.save();\n this._ctx.beginPath();\n this._ctx.rect(i * cellWidth, y, cellWidth, cellHeight);\n this._ctx.clip();\n this._ctx.fillStyle = ansiColors[colorIndex];\n this._ctx.fillText(String.fromCharCode(i), i * cellWidth, y);\n this._ctx.restore();\n }\n }\n this._ctx.restore();\n\n // Support is patchy for createImageBitmap at the moment, pass a canvas back\n // if support is lacking as drawImage works there too. Firefox is also\n // included here as ImageBitmap appears both buggy and has horrible\n // performance (tested on v55).\n if (!('createImageBitmap' in window) || isFirefox) {\n // Regenerate canvas and context as they are now owned by the char atlas\n const result = this._canvas;\n this._canvas = this._document.createElement('canvas');\n this._ctx = this._canvas.getContext('2d');\n this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);\n return result;\n }\n\n const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height);\n\n // Remove the background color from the image so characters may overlap\n const r = parseInt(background.substr(1, 2), 16);\n const g = parseInt(background.substr(3, 2), 16);\n const b = parseInt(background.substr(5, 2), 16);\n this._clearColor(charAtlasImageData, r, g, b);\n\n const promise = window.createImageBitmap(charAtlasImageData);\n // Clear the rect while the promise is in progress\n this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);\n return promise;\n }\n\n private _clearColor(imageData: ImageData, r: number, g: number, b: number): void {\n for (let offset = 0; offset < imageData.data.length; offset += 4) {\n if (imageData.data[offset] === r &&\n imageData.data[offset + 1] === g &&\n imageData.data[offset + 2] === b) {\n imageData.data[offset + 3] = 0;\n }\n }\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderLayer, IColorSet, IRenderDimensions } from './Interfaces';\nimport { ITerminal, ITerminalOptions } from '../Interfaces';\nimport { acquireCharAtlas, CHAR_ATLAS_CELL_SPACING } from './CharAtlas';\nimport { CharData } from '../Types';\nimport { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';\n\nexport const INVERTED_DEFAULT_COLOR = -1;\nconst DIM_OPACITY = 0.5;\n\nexport abstract class BaseRenderLayer implements IRenderLayer {\n private _canvas: HTMLCanvasElement;\n protected _ctx: CanvasRenderingContext2D;\n private _scaledCharWidth: number = 0;\n private _scaledCharHeight: number = 0;\n private _scaledCellWidth: number = 0;\n private _scaledCellHeight: number = 0;\n private _scaledCharLeft: number = 0;\n private _scaledCharTop: number = 0;\n\n private _charAtlas: HTMLCanvasElement | ImageBitmap;\n\n constructor(\n container: HTMLElement,\n id: string,\n zIndex: number,\n private _alpha: boolean,\n protected _colors: IColorSet\n ) {\n this._canvas = document.createElement('canvas');\n this._canvas.id = `xterm-${id}-layer`;\n this._canvas.style.zIndex = zIndex.toString();\n this._ctx = this._canvas.getContext('2d', {alpha: _alpha});\n this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);\n // Draw the background if this is an opaque layer\n if (!_alpha) {\n this.clearAll();\n }\n container.appendChild(this._canvas);\n }\n\n public onOptionsChanged(terminal: ITerminal): void {}\n public onBlur(terminal: ITerminal): void {}\n public onFocus(terminal: ITerminal): void {}\n public onCursorMove(terminal: ITerminal): void {}\n public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {}\n public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void {}\n\n public onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void {\n this._refreshCharAtlas(terminal, colorSet);\n }\n\n /**\n * Refreshes the char atlas, aquiring a new one if necessary.\n * @param terminal The terminal.\n * @param colorSet The color set to use for the char atlas.\n */\n private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void {\n if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {\n return;\n }\n this._charAtlas = null;\n const result = acquireCharAtlas(terminal, this._colors, this._scaledCharWidth, this._scaledCharHeight);\n if (result instanceof HTMLCanvasElement) {\n this._charAtlas = result;\n } else {\n result.then(bitmap => this._charAtlas = bitmap);\n }\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {\n this._scaledCellWidth = dim.scaledCellWidth;\n this._scaledCellHeight = dim.scaledCellHeight;\n this._scaledCharWidth = dim.scaledCharWidth;\n this._scaledCharHeight = dim.scaledCharHeight;\n this._scaledCharLeft = dim.scaledCharLeft;\n this._scaledCharTop = dim.scaledCharTop;\n this._canvas.width = dim.scaledCanvasWidth;\n this._canvas.height = dim.scaledCanvasHeight;\n this._canvas.style.width = `${dim.canvasWidth}px`;\n this._canvas.style.height = `${dim.canvasHeight}px`;\n\n // Draw the background if this is an opaque layer\n if (!this._alpha) {\n this.clearAll();\n }\n\n if (charSizeChanged) {\n this._refreshCharAtlas(terminal, this._colors);\n }\n }\n\n public abstract reset(terminal: ITerminal): void;\n\n /**\n * Fills 1+ cells completely. This uses the existing fillStyle on the context.\n * @param x The column to start at.\n * @param y The row to start at\n * @param width The number of columns to fill.\n * @param height The number of rows to fill.\n */\n protected fillCells(x: number, y: number, width: number, height: number): void {\n this._ctx.fillRect(\n x * this._scaledCellWidth,\n y * this._scaledCellHeight,\n width * this._scaledCellWidth,\n height * this._scaledCellHeight);\n }\n\n /**\n * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the\n * existing fillStyle on the context.\n * @param x The column to fill.\n * @param y The row to fill.\n */\n protected fillBottomLineAtCells(x: number, y: number, width: number = 1): void {\n this._ctx.fillRect(\n x * this._scaledCellWidth,\n (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,\n width * this._scaledCellWidth,\n window.devicePixelRatio);\n }\n\n /**\n * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the\n * existing fillStyle on the context.\n * @param x The column to fill.\n * @param y The row to fill.\n */\n protected fillLeftLineAtCell(x: number, y: number): void {\n this._ctx.fillRect(\n x * this._scaledCellWidth,\n y * this._scaledCellHeight,\n window.devicePixelRatio,\n this._scaledCellHeight);\n }\n\n /**\n * Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing\n * strokeStyle on the context.\n * @param x The column to fill.\n * @param y The row to fill.\n */\n protected strokeRectAtCell(x: number, y: number, width: number, height: number): void {\n this._ctx.lineWidth = window.devicePixelRatio;\n this._ctx.strokeRect(\n x * this._scaledCellWidth + window.devicePixelRatio / 2,\n y * this._scaledCellHeight + (window.devicePixelRatio / 2),\n width * this._scaledCellWidth - window.devicePixelRatio,\n (height * this._scaledCellHeight) - window.devicePixelRatio);\n }\n\n /**\n * Clears the entire canvas.\n */\n protected clearAll(): void {\n if (this._alpha) {\n this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);\n } else {\n this._ctx.fillStyle = this._colors.background;\n this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);\n }\n }\n\n /**\n * Clears 1+ cells completely.\n * @param x The column to start at.\n * @param y The row to start at.\n * @param width The number of columns to clear.\n * @param height The number of rows to clear.\n */\n protected clearCells(x: number, y: number, width: number, height: number): void {\n if (this._alpha) {\n this._ctx.clearRect(\n x * this._scaledCellWidth,\n y * this._scaledCellHeight,\n width * this._scaledCellWidth,\n height * this._scaledCellHeight);\n } else {\n this._ctx.fillStyle = this._colors.background;\n this._ctx.fillRect(\n x * this._scaledCellWidth,\n y * this._scaledCellHeight,\n width * this._scaledCellWidth,\n height * this._scaledCellHeight);\n }\n }\n\n /**\n * Draws a truecolor character at the cell. The character will be clipped to\n * ensure that it fits with the cell, including the cell to the right if it's\n * a wide character. This uses the existing fillStyle on the context.\n * @param terminal The terminal.\n * @param charData The char data for the character to draw.\n * @param x The column to draw at.\n * @param y The row to draw at.\n * @param color The color of the character.\n */\n protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {\n this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;\n this._ctx.textBaseline = 'top';\n this._clipRow(terminal, y);\n this._ctx.fillText(\n charData[CHAR_DATA_CHAR_INDEX],\n x * this._scaledCellWidth + this._scaledCharLeft,\n y * this._scaledCellHeight + this._scaledCharTop);\n }\n\n /**\n * Draws a character at a cell. If possible this will draw using the character\n * atlas to reduce draw time.\n * @param terminal The terminal.\n * @param char The character.\n * @param code The character code.\n * @param width The width of the character.\n * @param x The column to draw at.\n * @param y The row to draw at.\n * @param fg The foreground color, in the format stored within the attributes.\n * @param bg The background color, in the format stored within the attributes.\n * This is used to validate whether a cached image can be used.\n * @param bold Whether the text is bold.\n */\n protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean): void {\n let colorIndex = 0;\n if (fg < 256) {\n colorIndex = fg + 2;\n } else {\n // If default color and bold\n if (bold && terminal.options.enableBold) {\n colorIndex = 1;\n }\n }\n const isAscii = code < 256;\n // A color is basic if it is one of the standard normal or bold weight\n // colors of the characters held in the char atlas. Note that this excludes\n // the normal weight _light_ color characters.\n const isBasicColor = (colorIndex > 1 && fg < 16) && (fg < 8 || bold);\n const isDefaultColor = fg >= 256;\n const isDefaultBackground = bg >= 256;\n if (this._charAtlas && isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground) {\n // ImageBitmap's draw about twice as fast as from a canvas\n const charAtlasCellWidth = this._scaledCharWidth + CHAR_ATLAS_CELL_SPACING;\n const charAtlasCellHeight = this._scaledCharHeight + CHAR_ATLAS_CELL_SPACING;\n\n // Apply alpha to dim the character\n if (dim) {\n this._ctx.globalAlpha = DIM_OPACITY;\n }\n\n // Draw the non-bold version of the same color if bold is not enabled\n if (bold && !terminal.options.enableBold) {\n // Ignore default color as it's not touched above\n if (colorIndex > 1) {\n colorIndex -= 8;\n }\n }\n\n this._ctx.drawImage(this._charAtlas,\n code * charAtlasCellWidth,\n colorIndex * charAtlasCellHeight,\n charAtlasCellWidth,\n this._scaledCharHeight,\n x * this._scaledCellWidth + this._scaledCharLeft,\n y * this._scaledCellHeight + this._scaledCharTop,\n charAtlasCellWidth,\n this._scaledCharHeight);\n } else {\n this._drawUncachedChar(terminal, char, width, fg, x, y, bold, dim);\n }\n // This draws the atlas (for debugging purposes)\n // this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);\n // this._ctx.drawImage(this._charAtlas, 0, 0);\n }\n\n /**\n * Draws a character at a cell. The character will be clipped to\n * ensure that it fits with the cell, including the cell to the right if it's\n * a wide character.\n * @param terminal The terminal.\n * @param char The character.\n * @param width The width of the character.\n * @param fg The foreground color, in the format stored within the attributes.\n * @param x The column to draw at.\n * @param y The row to draw at.\n */\n private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean): void {\n this._ctx.save();\n this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;\n if (bold && terminal.options.enableBold) {\n this._ctx.font = `bold ${this._ctx.font}`;\n }\n this._ctx.textBaseline = 'top';\n\n if (fg === INVERTED_DEFAULT_COLOR) {\n this._ctx.fillStyle = this._colors.background;\n } else if (fg < 256) {\n // 256 color support\n this._ctx.fillStyle = this._colors.ansi[fg];\n } else {\n this._ctx.fillStyle = this._colors.foreground;\n }\n\n this._clipRow(terminal, y);\n\n // Apply alpha to dim the character\n if (dim) {\n this._ctx.globalAlpha = DIM_OPACITY;\n }\n // Draw the character\n this._ctx.fillText(\n char,\n x * this._scaledCellWidth + this._scaledCharLeft,\n y * this._scaledCellHeight + this._scaledCharTop);\n this._ctx.restore();\n }\n\n /**\n * Clips a row to ensure no pixels will be drawn outside the cells in the row.\n * @param terminal The terminal.\n * @param y The row to clip.\n */\n private _clipRow(terminal: ITerminal, y: number): void {\n this._ctx.beginPath();\n this._ctx.rect(\n 0,\n y * this._scaledCellHeight,\n terminal.cols * this._scaledCellWidth,\n this._scaledCellHeight);\n this._ctx.clip();\n }\n}\n\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IMouseZoneManager, IMouseZone } from './Interfaces';\nimport { ITerminal } from '../Interfaces';\n\nconst HOVER_DURATION = 500;\n\n/**\n * The MouseZoneManager allows components to register zones within the terminal\n * that trigger hover and click callbacks.\n *\n * This class was intentionally made not so robust initially as the only case it\n * needed to support was single-line links which never overlap. Improvements can\n * be made in the future.\n */\nexport class MouseZoneManager implements IMouseZoneManager {\n private _zones: IMouseZone[] = [];\n\n private _areZonesActive: boolean = false;\n private _mouseMoveListener: (e: MouseEvent) => any;\n private _clickListener: (e: MouseEvent) => any;\n\n private _tooltipTimeout: number = null;\n private _currentZone: IMouseZone = null;\n private _lastHoverCoords: [number, number] = [null, null];\n\n constructor(\n private _terminal: ITerminal\n ) {\n this._terminal.element.addEventListener('mousedown', e => this._onMouseDown(e));\n\n // These events are expensive, only listen to it when mouse zones are active\n this._mouseMoveListener = e => this._onMouseMove(e);\n this._clickListener = e => this._onClick(e);\n }\n\n public add(zone: IMouseZone): void {\n this._zones.push(zone);\n if (this._zones.length === 1) {\n this._activate();\n }\n }\n\n public clearAll(start?: number, end?: number): void {\n // Exit if there's nothing to clear\n if (this._zones.length === 0) {\n return;\n }\n\n // Clear all if start/end weren't set\n if (!end) {\n start = 0;\n end = this._terminal.rows - 1;\n }\n\n // Iterate through zones and clear them out if they're within the range\n for (let i = 0; i < this._zones.length; i++) {\n const zone = this._zones[i];\n if (zone.y > start && zone.y <= end + 1) {\n if (this._currentZone && this._currentZone === zone) {\n this._currentZone.leaveCallback();\n this._currentZone = null;\n }\n this._zones.splice(i--, 1);\n }\n }\n\n // Deactivate the mouse zone manager if all the zones have been removed\n if (this._zones.length === 0) {\n this._deactivate();\n }\n }\n\n private _activate(): void {\n if (!this._areZonesActive) {\n this._areZonesActive = true;\n this._terminal.element.addEventListener('mousemove', this._mouseMoveListener);\n this._terminal.element.addEventListener('click', this._clickListener);\n }\n }\n\n private _deactivate(): void {\n if (this._areZonesActive) {\n this._areZonesActive = false;\n this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener);\n this._terminal.element.removeEventListener('click', this._clickListener);\n }\n }\n\n private _onMouseMove(e: MouseEvent): void {\n // TODO: Ideally this would only clear the hover state when the mouse moves\n // outside of the mouse zone\n if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) {\n this._onHover(e);\n // Record the current coordinates\n this._lastHoverCoords = [e.pageX, e.pageY];\n }\n }\n\n private _onHover(e: MouseEvent): void {\n const zone = this._findZoneEventAt(e);\n\n // Do nothing if the zone is the same\n if (zone === this._currentZone) {\n return;\n }\n\n // Fire the hover end callback and cancel any existing timer if a new zone\n // is being hovered\n if (this._currentZone) {\n this._currentZone.leaveCallback();\n this._currentZone = null;\n if (this._tooltipTimeout) {\n clearTimeout(this._tooltipTimeout);\n }\n }\n\n // Exit if there is not zone\n if (!zone) {\n return;\n }\n this._currentZone = zone;\n\n // Trigger the hover callback\n if (zone.hoverCallback) {\n zone.hoverCallback(e);\n }\n\n // Restart the tooltip timeout\n this._tooltipTimeout = setTimeout(() => this._onTooltip(e), HOVER_DURATION);\n }\n\n private _onTooltip(e: MouseEvent): void {\n this._tooltipTimeout = null;\n const zone = this._findZoneEventAt(e);\n if (zone && zone.tooltipCallback) {\n zone.tooltipCallback(e);\n }\n }\n\n private _onMouseDown(e: MouseEvent): void {\n // Ignore the event if there are no zones active\n if (!this._areZonesActive) {\n return;\n }\n\n // Find the active zone, prevent event propagation if found to prevent other\n // components from handling the mouse event.\n const zone = this._findZoneEventAt(e);\n if (zone) {\n // TODO: When link modifier support is added, the event should only be\n // cancelled when the modifier is held (see #1021)\n e.preventDefault();\n e.stopImmediatePropagation();\n }\n }\n\n private _onClick(e: MouseEvent): void {\n // Find the active zone and click it if found\n const zone = this._findZoneEventAt(e);\n if (zone) {\n zone.clickCallback(e);\n e.preventDefault();\n e.stopImmediatePropagation();\n }\n }\n\n private _findZoneEventAt(e: MouseEvent): IMouseZone {\n const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows);\n if (!coords) {\n return null;\n }\n for (let i = 0; i < this._zones.length; i++) {\n const zone = this._zones[i];\n if (zone.y === coords[1] && zone.x1 <= coords[0] && zone.x2 > coords[0]) {\n return zone;\n }\n };\n return null;\n }\n}\n\nexport class MouseZone implements IMouseZone {\n constructor(\n public x1: number,\n public x2: number,\n public y: number,\n public clickCallback: (e: MouseEvent) => any,\n public hoverCallback?: (e: MouseEvent) => any,\n public tooltipCallback?: (e: MouseEvent) => any,\n public leaveCallback?: () => void\n ) {\n }\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, ISelectionManager } from '../Interfaces';\n\ninterface IWindow extends Window {\n clipboardData?: {\n getData(format: string): string;\n setData(format: string, data: string): void;\n };\n}\n\ndeclare var window: IWindow;\n\n/**\n * Prepares text to be pasted into the terminal by normalizing the line endings\n * @param text The pasted text that needs processing before inserting into the terminal\n */\nexport function prepareTextForTerminal(text: string, isMSWindows: boolean): string {\n if (isMSWindows) {\n return text.replace(/\\r?\\n/g, '\\r');\n }\n return text;\n}\n\n/**\n * Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste\n * @param text The pasted text to bracket\n */\nexport function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string {\n if (bracketedPasteMode) {\n return '\\x1b[200~' + text + '\\x1b[201~';\n }\n return text;\n}\n\n/**\n * Binds copy functionality to the given terminal.\n * @param {ClipboardEvent} ev The original copy event to be handled\n */\nexport function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void {\n if (term.browser.isMSIE) {\n window.clipboardData.setData('Text', selectionManager.selectionText);\n } else {\n ev.clipboardData.setData('text/plain', selectionManager.selectionText);\n }\n\n // Prevent or the original text will be copied.\n ev.preventDefault();\n}\n\n/**\n * Redirect the clipboard's data to the terminal's input handler.\n * @param {ClipboardEvent} ev The original paste event to be handled\n * @param {Terminal} term The terminal on which to apply the handled paste event\n */\nexport function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {\n ev.stopPropagation();\n\n let text: string;\n\n let dispatchPaste = function(text: string): void {\n text = prepareTextForTerminal(text, term.browser.isMSWindows);\n text = bracketTextForPaste(text, term.bracketedPasteMode);\n term.handler(text);\n term.textarea.value = '';\n term.emit('paste', text);\n term.cancel(ev);\n };\n\n if (term.browser.isMSIE) {\n if (window.clipboardData) {\n text = window.clipboardData.getData('Text');\n dispatchPaste(text);\n }\n } else {\n if (ev.clipboardData) {\n text = ev.clipboardData.getData('text/plain');\n dispatchPaste(text);\n }\n }\n}\n\n/**\n * Moves the textarea under the mouse cursor and focuses it.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n */\nexport function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement): void {\n // Bring textarea at the cursor position\n textarea.style.position = 'fixed';\n textarea.style.width = '20px';\n textarea.style.height = '20px';\n textarea.style.left = (ev.clientX - 10) + 'px';\n textarea.style.top = (ev.clientY - 10) + 'px';\n textarea.style.zIndex = '1000';\n\n textarea.focus();\n\n // Reset the terminal textarea's styling\n setTimeout(() => {\n textarea.style.position = null;\n textarea.style.width = null;\n textarea.style.height = null;\n textarea.style.left = null;\n textarea.style.top = null;\n textarea.style.zIndex = null;\n }, 4);\n}\n\n/**\n * Bind to right-click event and allow right-click copy and paste.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n * @param selectionManager The terminal's selection manager.\n */\nexport function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager): void {\n moveTextAreaUnderMouseCursor(ev, textarea);\n\n // Get textarea ready to copy from the context menu\n textarea.value = selectionManager.selectionText;\n textarea.select();\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, IViewport } from './Interfaces';\nimport { CharMeasure } from './utils/CharMeasure';\nimport { IColorSet } from './renderer/Interfaces';\n\n/**\n * Represents the viewport of a terminal, the visible area within the larger buffer of output.\n * Logic for the virtual scroll bar is included in this object.\n */\nexport class Viewport implements IViewport {\n private currentRowHeight: number = 0;\n private lastRecordedBufferLength: number = 0;\n private lastRecordedViewportHeight: number = 0;\n private lastRecordedBufferHeight: number = 0;\n private lastTouchY: number;\n\n /**\n * Creates a new Viewport.\n * @param terminal The terminal this viewport belongs to.\n * @param viewportElement The DOM element acting as the viewport.\n * @param scrollArea The DOM element acting as the scroll area.\n * @param charMeasure A DOM element used to measure the character size of. the terminal.\n */\n constructor(\n private terminal: ITerminal,\n private viewportElement: HTMLElement,\n private scrollArea: HTMLElement,\n private charMeasure: CharMeasure\n ) {\n this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));\n\n // Perform this async to ensure the CharMeasure is ready.\n setTimeout(() => this.syncScrollArea(), 0);\n }\n\n public onThemeChanged(colors: IColorSet): void {\n this.viewportElement.style.backgroundColor = colors.background;\n }\n\n /**\n * Refreshes row height, setting line-height, viewport height and scroll area height if\n * necessary.\n */\n private refresh(): void {\n if (this.charMeasure.height > 0) {\n this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;\n\n if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) {\n this.lastRecordedViewportHeight = this.terminal.renderer.dimensions.canvasHeight;\n this.viewportElement.style.height = this.lastRecordedViewportHeight + 'px';\n }\n\n const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength);\n if (this.lastRecordedBufferHeight !== newBufferHeight) {\n this.lastRecordedBufferHeight = newBufferHeight;\n this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px';\n }\n }\n }\n\n /**\n * Updates dimensions and synchronizes the scroll area if necessary.\n */\n public syncScrollArea(): void {\n if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {\n // If buffer height changed\n this.lastRecordedBufferLength = this.terminal.buffer.lines.length;\n this.refresh();\n } else if (this.lastRecordedViewportHeight !== (this.terminal).renderer.dimensions.canvasHeight) {\n // If viewport height changed\n this.refresh();\n } else {\n // If size has changed, refresh viewport\n if (this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this.currentRowHeight) {\n this.refresh();\n }\n }\n\n // Sync scrollTop\n const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;\n if (this.viewportElement.scrollTop !== scrollTop) {\n this.viewportElement.scrollTop = scrollTop;\n }\n }\n\n /**\n * Handles scroll events on the viewport, calculating the new viewport and requesting the\n * terminal to scroll to it.\n * @param ev The scroll event.\n */\n private onScroll(ev: Event): void {\n const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);\n const diff = newRow - this.terminal.buffer.ydisp;\n this.terminal.scrollLines(diff, true);\n }\n\n /**\n * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual\n * scrolling to `onScroll`, this event needs to be attached manually by the consumer of\n * `Viewport`.\n * @param ev The mouse wheel event.\n */\n public onWheel(ev: WheelEvent): void {\n if (ev.deltaY === 0) {\n // Do nothing if it's not a vertical scroll event\n return;\n }\n // Fallback to WheelEvent.DOM_DELTA_PIXEL\n let multiplier = 1;\n if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {\n multiplier = this.currentRowHeight;\n } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n multiplier = this.currentRowHeight * this.terminal.rows;\n }\n this.viewportElement.scrollTop += ev.deltaY * multiplier;\n // Prevent the page from scrolling when the terminal scrolls\n ev.preventDefault();\n };\n\n /**\n * Handles the touchstart event, recording the touch occurred.\n * @param ev The touch event.\n */\n public onTouchStart(ev: TouchEvent): void {\n this.lastTouchY = ev.touches[0].pageY;\n };\n\n /**\n * Handles the touchmove event, scrolling the viewport if the position shifted.\n * @param ev The touch event.\n */\n public onTouchMove(ev: TouchEvent): void {\n let deltaY = this.lastTouchY - ev.touches[0].pageY;\n this.lastTouchY = ev.touches[0].pageY;\n if (deltaY === 0) {\n return;\n }\n this.viewportElement.scrollTop += deltaY;\n ev.preventDefault();\n };\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport type LinkMatcher = {\n id: number,\n regex: RegExp,\n handler: LinkMatcherHandler,\n hoverTooltipCallback?: LinkMatcherHandler,\n hoverLeaveCallback?: () => void,\n matchIndex?: number,\n validationCallback?: LinkMatcherValidationCallback,\n priority?: number\n};\nexport type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;\nexport type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;\n\nexport type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;\nexport type Charset = {[key: string]: string};\n\nexport type CharData = [number, string, number, number];\nexport type LineData = CharData[];\n\nexport type LinkHoverEvent = {\n x: number,\n y: number,\n length: number\n};\n\nexport enum LinkHoverEventTypes {\n HOVER = 'linkhover',\n TOOLTIP = 'linktooltip',\n LEAVE = 'linkleave'\n};\n","/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n *\n * Terminal Emulation References:\n * http://vt100.net/\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * http://invisible-island.net/vttest/\n * http://www.inwap.com/pdp10/ansicode.txt\n * http://linux.die.net/man/4/console_codes\n * http://linux.die.net/man/7/urxvt\n */\n\nimport { BufferSet } from './BufferSet';\nimport { Buffer, MAX_BUFFER_SIZE } from './Buffer';\nimport { CompositionHelper } from './CompositionHelper';\nimport { EventEmitter } from './EventEmitter';\nimport { Viewport } from './Viewport';\nimport { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';\nimport { CircularList } from './utils/CircularList';\nimport { C0 } from './EscapeSequences';\nimport { InputHandler } from './InputHandler';\nimport { Parser } from './Parser';\nimport { Renderer } from './renderer/Renderer';\nimport { Linkifier } from './Linkifier';\nimport { SelectionManager } from './SelectionManager';\nimport { CharMeasure } from './utils/CharMeasure';\nimport * as Browser from './utils/Browser';\nimport { MouseHelper } from './utils/MouseHelper';\nimport { CHARSETS } from './Charsets';\nimport { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types';\nimport { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme, ILinkifier } from './Interfaces';\nimport { BellSound } from './utils/Sounds';\nimport { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';\nimport { IMouseZoneManager } from './input/Interfaces';\nimport { MouseZoneManager } from './input/MouseZoneManager';\nimport { initialize as initializeCharAtlas } from './renderer/CharAtlas';\nimport { IRenderer } from './renderer/Interfaces';\n\n// Declares required for loadAddon\ndeclare var exports: any;\ndeclare var module: any;\ndeclare var define: any;\ndeclare var require: any;\n\n// Let it work inside Node.js for automated testing purposes.\nconst document = (typeof window !== 'undefined') ? window.document : null;\n\n/**\n * The amount of write requests to queue before sending an XOFF signal to the\n * pty process. This number must be small in order for ^C and similar sequences\n * to be responsive.\n */\nconst WRITE_BUFFER_PAUSE_THRESHOLD = 5;\n\n/**\n * The number of writes to perform in a single batch before allowing the\n * renderer to catch up with a 0ms setTimeout.\n */\nconst WRITE_BATCH_SIZE = 300;\n\nconst DEFAULT_OPTIONS: ITerminalOptions = {\n convertEol: false,\n termName: 'xterm',\n geometry: [80, 24],\n cursorBlink: false,\n cursorStyle: 'block',\n bellSound: BellSound,\n bellStyle: 'none',\n enableBold: true,\n fontFamily: 'courier-new, courier, monospace',\n fontSize: 15,\n lineHeight: 1.0,\n letterSpacing: 0,\n scrollback: 1000,\n screenKeys: false,\n debug: false,\n cancelEvents: false,\n disableStdin: false,\n useFlowControl: false,\n tabStopWidth: 8,\n theme: null\n // programFeatures: false,\n // focusKeys: false,\n};\n\nexport class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal {\n public textarea: HTMLTextAreaElement;\n public element: HTMLElement;\n\n /**\n * The HTMLElement that the terminal is created in, set by Terminal.open.\n */\n private parent: HTMLElement;\n private context: Window;\n private document: Document;\n private body: HTMLBodyElement;\n private viewportScrollArea: HTMLElement;\n private viewportElement: HTMLElement;\n private helperContainer: HTMLElement;\n private compositionView: HTMLElement;\n private charSizeStyleElement: HTMLStyleElement;\n private bellAudioElement: HTMLAudioElement;\n private visualBellTimer: number;\n\n public browser: IBrowser = Browser;\n\n public options: ITerminalOptions;\n private colors: any;\n\n // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options\n public cursorState: number;\n public cursorHidden: boolean;\n public convertEol: boolean;\n\n private sendDataQueue: string;\n private customKeyEventHandler: CustomKeyEventHandler;\n\n // modes\n public applicationKeypad: boolean;\n public applicationCursor: boolean;\n public originMode: boolean;\n public insertMode: boolean;\n public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false\n public bracketedPasteMode: boolean;\n\n // charset\n // The current charset\n public charset: Charset;\n public gcharset: number;\n public glevel: number;\n public charsets: Charset[];\n\n // mouse properties\n private decLocator: boolean; // This is unstable and never set\n public x10Mouse: boolean;\n public vt200Mouse: boolean;\n private vt300Mouse: boolean; // This is unstable and never set\n public normalMouse: boolean;\n public mouseEvents: boolean;\n public sendFocus: boolean;\n public utfMouse: boolean;\n public sgrMouse: boolean;\n public urxvtMouse: boolean;\n\n // misc\n private refreshStart: number;\n private refreshEnd: number;\n public savedCols: number;\n\n // stream\n private readable: boolean;\n private writable: boolean;\n\n public defAttr: number;\n public curAttr: number;\n\n public params: (string | number)[];\n public currentParam: string | number;\n public prefix: string;\n public postfix: string;\n\n // user input states\n public writeBuffer: string[];\n private writeInProgress: boolean;\n\n /**\n * Whether _xterm.js_ sent XOFF in order to catch up with the pty process.\n * This is a distinct state from writeStopped so that if the user requested\n * XOFF via ^S that it will not automatically resume when the writeBuffer goes\n * below threshold.\n */\n private xoffSentToCatchUp: boolean;\n\n /** Whether writing has been stopped as a result of XOFF */\n private writeStopped: boolean;\n\n // leftover surrogate high from previous write invocation\n private surrogate_high: string;\n\n // Store if user went browsing history in scrollback\n private userScrolling: boolean;\n\n private inputHandler: InputHandler;\n private parser: Parser;\n public renderer: IRenderer;\n public selectionManager: SelectionManager;\n public linkifier: ILinkifier;\n public buffers: BufferSet;\n public buffer: Buffer;\n public viewport: IViewport;\n private compositionHelper: ICompositionHelper;\n public charMeasure: CharMeasure;\n private _mouseZoneManager: IMouseZoneManager;\n public mouseHelper: MouseHelper;\n\n public cols: number;\n public rows: number;\n public geometry: [/*cols*/number, /*rows*/number];\n\n /**\n * Creates a new `Terminal` object.\n *\n * @param {object} options An object containing a set of options, the available options are:\n * - `cursorBlink` (boolean): Whether the terminal cursor blinks\n * - `cols` (number): The number of columns of the terminal (horizontal size)\n * - `rows` (number): The number of rows of the terminal (vertical size)\n *\n * @public\n * @class Xterm Xterm\n * @alias module:xterm/src/xterm\n */\n constructor(\n options: ITerminalOptions = {}\n ) {\n super();\n this.options = options;\n this.setup();\n }\n\n private setup(): void {\n Object.keys(DEFAULT_OPTIONS).forEach((key) => {\n if (this.options[key] == null) {\n this.options[key] = DEFAULT_OPTIONS[key];\n }\n // TODO: We should move away from duplicate options on the Terminal object\n this[key] = this.options[key];\n });\n\n // this.context = options.context || window;\n // this.document = options.document || document;\n // TODO: WHy not document.body?\n this.parent = document ? document.body : null;\n\n this.cols = this.options.cols || this.options.geometry[0];\n this.rows = this.options.rows || this.options.geometry[1];\n this.geometry = [this.cols, this.rows];\n\n if (this.options.handler) {\n this.on('data', this.options.handler);\n }\n\n this.cursorState = 0;\n this.cursorHidden = false;\n this.sendDataQueue = '';\n this.customKeyEventHandler = null;\n\n // modes\n this.applicationKeypad = false;\n this.applicationCursor = false;\n this.originMode = false;\n this.insertMode = false;\n this.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n this.bracketedPasteMode = false;\n\n // charset\n this.charset = null;\n this.gcharset = null;\n this.glevel = 0;\n // TODO: Can this be just []?\n this.charsets = [null];\n\n this.readable = true;\n this.writable = true;\n\n this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);\n this.curAttr = (0 << 18) | (257 << 9) | (256 << 0);\n\n this.params = [];\n this.currentParam = 0;\n this.prefix = '';\n this.postfix = '';\n\n // user input states\n this.writeBuffer = [];\n this.writeInProgress = false;\n\n this.xoffSentToCatchUp = false;\n this.writeStopped = false;\n this.surrogate_high = '';\n this.userScrolling = false;\n\n this.inputHandler = new InputHandler(this);\n this.parser = new Parser(this.inputHandler, this);\n // Reuse renderer if the Terminal is being recreated via a reset call.\n this.renderer = this.renderer || null;\n this.selectionManager = this.selectionManager || null;\n this.linkifier = this.linkifier || new Linkifier(this);\n this._mouseZoneManager = this._mouseZoneManager || null;\n\n // Create the terminal's buffers and set the current buffer\n this.buffers = new BufferSet(this);\n this.buffer = this.buffers.active; // Convenience shortcut;\n this.buffers.on('activate', (buffer: Buffer) => {\n this.buffer = buffer;\n });\n\n // Ensure the selection manager has the correct buffer\n if (this.selectionManager) {\n this.selectionManager.setBuffer(this.buffer);\n }\n }\n\n /**\n * back_color_erase feature for xterm.\n */\n public eraseAttr(): number {\n // if (this.is('screen')) return this.defAttr;\n return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);\n }\n\n /**\n * Focus the terminal. Delegates focus handling to the terminal's DOM element.\n */\n public focus(): void {\n this.textarea.focus();\n }\n\n public get isFocused(): boolean {\n return document.activeElement === this.textarea;\n }\n\n /**\n * Retrieves an option's value from the terminal.\n * @param {string} key The option key.\n */\n public getOption(key: string): any {\n if (!(key in DEFAULT_OPTIONS)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n\n if (typeof this.options[key] !== 'undefined') {\n return this.options[key];\n }\n\n return this[key];\n }\n\n /**\n * Sets an option on the terminal.\n * @param {string} key The option key.\n * @param {any} value The option value.\n */\n public setOption(key: string, value: any): void {\n if (!(key in DEFAULT_OPTIONS)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n switch (key) {\n case 'bellStyle':\n if (!value) {\n value = 'none';\n }\n break;\n case 'cursorStyle':\n if (!value) {\n value = 'block';\n }\n break;\n case 'lineHeight':\n if (value < 1) {\n console.warn(`${key} cannot be less than 1, value: ${value}`);\n return;\n }\n case 'tabStopWidth':\n if (value < 1) {\n console.warn(`${key} cannot be less than 1, value: ${value}`);\n return;\n }\n break;\n case 'theme':\n // If open has been called we do not want to set options.theme as the\n // source of truth is owned by the renderer.\n if (this.renderer) {\n this._setTheme(value);\n return;\n }\n break;\n case 'scrollback':\n value = Math.min(value, MAX_BUFFER_SIZE);\n\n if (value < 0) {\n console.warn(`${key} cannot be less than 0, value: ${value}`);\n return;\n }\n if (this.options[key] !== value) {\n const newBufferLength = this.rows + value;\n if (this.buffer.lines.length > newBufferLength) {\n const amountToTrim = this.buffer.lines.length - newBufferLength;\n const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);\n this.buffer.lines.trimStart(amountToTrim);\n this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);\n this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);\n if (needsRefresh) {\n this.refresh(0, this.rows - 1);\n }\n }\n }\n break;\n }\n this[key] = value;\n this.options[key] = value;\n switch (key) {\n case 'fontFamily':\n case 'fontSize':\n // When the font changes the size of the cells may change which requires a renderer clear\n this.renderer.clear();\n this.charMeasure.measure(this.options);\n break;\n case 'enableBold':\n case 'letterSpacing':\n case 'lineHeight':\n // When the font changes the size of the cells may change which requires a renderer clear\n this.renderer.clear();\n this.renderer.onResize(this.cols, this.rows, false);\n this.refresh(0, this.rows - 1);\n // this.charMeasure.measure(this.options);\n case 'scrollback':\n this.buffers.resize(this.cols, this.rows);\n this.viewport.syncScrollArea();\n break;\n case 'tabStopWidth': this.buffers.setupTabStops(); break;\n case 'bellSound':\n case 'bellStyle': this.syncBellSound(); break;\n }\n // Inform renderer of changes\n if (this.renderer) {\n this.renderer.onOptionsChanged();\n }\n }\n\n /**\n * Binds the desired focus behavior on a given terminal object.\n */\n private _onTextAreaFocus(): void {\n if (this.sendFocus) {\n this.send(C0.ESC + '[I');\n }\n this.element.classList.add('focus');\n this.showCursor();\n this.emit('focus');\n };\n\n /**\n * Blur the terminal, calling the blur function on the terminal's underlying\n * textarea.\n */\n public blur(): void {\n return this.textarea.blur();\n }\n\n /**\n * Binds the desired blur behavior on a given terminal object.\n */\n private _onTextAreaBlur(): void {\n this.refresh(this.buffer.y, this.buffer.y);\n if (this.sendFocus) {\n this.send(C0.ESC + '[O');\n }\n this.element.classList.remove('focus');\n this.emit('blur');\n }\n\n /**\n * Initialize default behavior\n */\n private initGlobal(): void {\n this.bindKeys();\n\n // Bind clipboard functionality\n on(this.element, 'copy', (event: ClipboardEvent) => {\n // If mouse events are active it means the selection manager is disabled and\n // copy should be handled by the host program.\n if (!this.hasSelection()) {\n return;\n }\n copyHandler(event, this, this.selectionManager);\n });\n const pasteHandlerWrapper = event => pasteHandler(event, this);\n on(this.textarea, 'paste', pasteHandlerWrapper);\n on(this.element, 'paste', pasteHandlerWrapper);\n\n // Handle right click context menus\n if (Browser.isFirefox) {\n // Firefox doesn't appear to fire the contextmenu event on right click\n on(this.element, 'mousedown', (event: MouseEvent) => {\n if (event.button === 2) {\n rightClickHandler(event, this.textarea, this.selectionManager);\n }\n });\n } else {\n on(this.element, 'contextmenu', (event: MouseEvent) => {\n rightClickHandler(event, this.textarea, this.selectionManager);\n });\n }\n\n // Move the textarea under the cursor when middle clicking on Linux to ensure\n // middle click to paste selection works. This only appears to work in Chrome\n // at the time is writing.\n if (Browser.isLinux) {\n // Use auxclick event over mousedown the latter doesn't seem to work. Note\n // that the regular click event doesn't fire for the middle mouse button.\n on(this.element, 'auxclick', (event: MouseEvent) => {\n if (event.button === 1) {\n moveTextAreaUnderMouseCursor(event, this.textarea);\n }\n });\n }\n }\n\n /**\n * Apply key handling to the terminal\n */\n private bindKeys(): void {\n const self = this;\n on(this.element, 'keydown', function (ev: KeyboardEvent): void {\n if (document.activeElement !== this) {\n return;\n }\n self._keyDown(ev);\n }, true);\n\n on(this.element, 'keypress', function (ev: KeyboardEvent): void {\n if (document.activeElement !== this) {\n return;\n }\n self._keyPress(ev);\n }, true);\n\n on(this.element, 'keyup', (ev: KeyboardEvent) => {\n if (!wasMondifierKeyOnlyEvent(ev)) {\n this.focus();\n }\n }, true);\n\n on(this.textarea, 'keydown', (ev: KeyboardEvent) => {\n this._keyDown(ev);\n }, true);\n\n on(this.textarea, 'keypress', (ev: KeyboardEvent) => {\n this._keyPress(ev);\n // Truncate the textarea's value, since it is not needed\n this.textarea.value = '';\n }, true);\n\n on(this.textarea, 'compositionstart', () => this.compositionHelper.compositionstart());\n on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this.compositionHelper.compositionupdate(e));\n on(this.textarea, 'compositionend', () => this.compositionHelper.compositionend());\n this.on('refresh', () => this.compositionHelper.updateCompositionElements());\n this.on('refresh', (data) => this.queueLinkification(data.start, data.end));\n }\n\n /**\n * Opens the terminal within an element.\n *\n * @param {HTMLElement} parent The element to create the terminal within.\n */\n public open(parent: HTMLElement): void {\n let i = 0;\n let div;\n\n this.parent = parent || this.parent;\n\n if (!this.parent) {\n throw new Error('Terminal requires a parent element.');\n }\n\n // Grab global elements\n this.context = this.parent.ownerDocument.defaultView;\n this.document = this.parent.ownerDocument;\n this.body = this.document.body;\n\n initializeCharAtlas(this.document);\n\n // Create main element container\n this.element = this.document.createElement('div');\n this.element.classList.add('terminal');\n this.element.classList.add('xterm');\n\n this.element.setAttribute('tabindex', '0');\n\n this.viewportElement = document.createElement('div');\n this.viewportElement.classList.add('xterm-viewport');\n this.element.appendChild(this.viewportElement);\n this.viewportScrollArea = document.createElement('div');\n this.viewportScrollArea.classList.add('xterm-scroll-area');\n this.viewportElement.appendChild(this.viewportScrollArea);\n\n // preload audio\n this.syncBellSound();\n\n this._mouseZoneManager = new MouseZoneManager(this);\n this.on('scroll', () => this._mouseZoneManager.clearAll());\n this.linkifier.attachToDom(this._mouseZoneManager);\n\n // Create the container that will hold helpers like the textarea for\n // capturing DOM Events. Then produce the helpers.\n this.helperContainer = document.createElement('div');\n this.helperContainer.classList.add('xterm-helpers');\n // TODO: This should probably be inserted once it's filled to prevent an additional layout\n this.element.appendChild(this.helperContainer);\n this.textarea = document.createElement('textarea');\n this.textarea.classList.add('xterm-helper-textarea');\n this.textarea.setAttribute('autocorrect', 'off');\n this.textarea.setAttribute('autocapitalize', 'off');\n this.textarea.setAttribute('spellcheck', 'false');\n this.textarea.tabIndex = 0;\n this.textarea.addEventListener('focus', () => this._onTextAreaFocus());\n this.textarea.addEventListener('blur', () => this._onTextAreaBlur());\n this.helperContainer.appendChild(this.textarea);\n\n this.compositionView = document.createElement('div');\n this.compositionView.classList.add('composition-view');\n this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);\n this.helperContainer.appendChild(this.compositionView);\n\n this.charSizeStyleElement = document.createElement('style');\n this.helperContainer.appendChild(this.charSizeStyleElement);\n\n this.parent.appendChild(this.element);\n\n this.charMeasure = new CharMeasure(document, this.helperContainer);\n\n this.renderer = new Renderer(this, this.options.theme);\n this.options.theme = null;\n this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);\n this.viewport.onThemeChanged(this.renderer.colorManager.colors);\n\n this.on('cursormove', () => this.renderer.onCursorMove());\n this.on('resize', () => this.renderer.onResize(this.cols, this.rows, false));\n this.on('blur', () => this.renderer.onBlur());\n this.on('focus', () => this.renderer.onFocus());\n window.addEventListener('resize', () => this.renderer.onWindowResize(window.devicePixelRatio));\n this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows, true));\n this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea());\n\n this.selectionManager = new SelectionManager(this, this.buffer, this.charMeasure);\n this.element.addEventListener('mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e));\n this.selectionManager.on('refresh', data => this.renderer.onSelectionChanged(data.start, data.end));\n this.selectionManager.on('newselection', text => {\n // If there's a new selection, put it into the textarea, focus and select it\n // in order to register it as a selection on the OS. This event is fired\n // only on Linux to enable middle click to paste selection.\n this.textarea.value = text;\n this.textarea.focus();\n this.textarea.select();\n });\n this.on('scroll', () => {\n this.viewport.syncScrollArea();\n this.selectionManager.refresh();\n });\n this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());\n\n this.mouseHelper = new MouseHelper(this.renderer);\n\n // Measure the character size\n this.charMeasure.measure(this.options);\n\n // Setup loop that draws to screen\n this.refresh(0, this.rows - 1);\n\n // Initialize global actions that need to be taken on the document.\n this.initGlobal();\n\n // Listen for mouse events and translate\n // them into terminal mouse protocols.\n this.bindMouse();\n }\n\n /**\n * Sets the theme on the renderer. The renderer must have been initialized.\n * @param theme The theme to ste.\n */\n private _setTheme(theme: ITheme): void {\n const colors = this.renderer.setTheme(theme);\n if (this.viewport) {\n this.viewport.onThemeChanged(colors);\n }\n }\n\n /**\n * Attempts to load an add-on using CommonJS or RequireJS (whichever is available).\n * @param {string} addon The name of the addon to load\n * @static\n */\n public static loadAddon(addon: string, callback?: Function): boolean | any {\n // TODO: Improve return type and documentation\n if (typeof exports === 'object' && typeof module === 'object') {\n // CommonJS\n return require('./addons/' + addon + '/' + addon);\n } else if (typeof define === 'function') {\n // RequireJS\n return (require)(['./addons/' + addon + '/' + addon], callback);\n } else {\n console.error('Cannot load a module without a CommonJS or RequireJS environment.');\n return false;\n }\n }\n\n /**\n * XTerm mouse events\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking\n * To better understand these\n * the xterm code is very helpful:\n * Relevant files:\n * button.c, charproc.c, misc.c\n * Relevant functions in xterm/button.c:\n * BtnCode, EmitButtonCode, EditorButton, SendMousePosition\n */\n public bindMouse(): void {\n const el = this.element;\n const self = this;\n let pressed = 32;\n\n // mouseup, mousedown, wheel\n // left click: ^[[M 3<^[[M#3<\n // wheel up: ^[[M`3>\n function sendButton(ev: MouseEvent | WheelEvent): void {\n let button;\n let pos;\n\n // get the xterm-style button\n button = getButton(ev);\n\n // get mouse coordinates\n pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows);\n if (!pos) return;\n\n sendEvent(button, pos);\n\n switch ((ev).overrideType || ev.type) {\n case 'mousedown':\n pressed = button;\n break;\n case 'mouseup':\n // keep it at the left\n // button, just in case.\n pressed = 32;\n break;\n case 'wheel':\n // nothing. don't\n // interfere with\n // `pressed`.\n break;\n }\n }\n\n // motion example of a left click:\n // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<\n function sendMove(ev: MouseEvent): void {\n let button = pressed;\n let pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows);\n if (!pos) return;\n\n // buttons marked as motions\n // are incremented by 32\n button += 32;\n\n sendEvent(button, pos);\n }\n\n // encode button and\n // position to characters\n function encode(data: number[], ch: number): void {\n if (!self.utfMouse) {\n if (ch === 255) {\n data.push(0);\n return;\n }\n if (ch > 127) ch = 127;\n data.push(ch);\n } else {\n if (ch === 2047) {\n data.push(0);\n return;\n }\n if (ch < 127) {\n data.push(ch);\n } else {\n if (ch > 2047) ch = 2047;\n data.push(0xC0 | (ch >> 6));\n data.push(0x80 | (ch & 0x3F));\n }\n }\n }\n\n // send a mouse event:\n // regular/utf8: ^[[M Cb Cx Cy\n // urxvt: ^[[ Cb ; Cx ; Cy M\n // sgr: ^[[ Cb ; Cx ; Cy M/m\n // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \\r\n // locator: CSI P e ; P b ; P r ; P c ; P p & w\n function sendEvent(button: number, pos: {x: number, y: number}): void {\n // self.emit('mouse', {\n // x: pos.x - 32,\n // y: pos.x - 32,\n // button: button\n // });\n\n if (self.vt300Mouse) {\n // NOTE: Unstable.\n // http://www.vt100.net/docs/vt3xx-gp/chapter15.html\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n let data = C0.ESC + '[24';\n if (button === 0) data += '1';\n else if (button === 1) data += '3';\n else if (button === 2) data += '5';\n else if (button === 3) return;\n else data += '0';\n data += '~[' + pos.x + ',' + pos.y + ']\\r';\n self.send(data);\n return;\n }\n\n if (self.decLocator) {\n // NOTE: Unstable.\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n if (button === 0) button = 2;\n else if (button === 1) button = 4;\n else if (button === 2) button = 6;\n else if (button === 3) button = 3;\n self.send(C0.ESC + '['\n + button\n + ';'\n + (button === 3 ? 4 : 0)\n + ';'\n + pos.y\n + ';'\n + pos.x\n + ';'\n // Not sure what page is meant to be\n + (pos).page || 0\n + '&w');\n return;\n }\n\n if (self.urxvtMouse) {\n pos.x -= 32;\n pos.y -= 32;\n pos.x++;\n pos.y++;\n self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');\n return;\n }\n\n if (self.sgrMouse) {\n pos.x -= 32;\n pos.y -= 32;\n self.send(C0.ESC + '[<'\n + (((button & 3) === 3 ? button & ~3 : button) - 32)\n + ';'\n + pos.x\n + ';'\n + pos.y\n + ((button & 3) === 3 ? 'm' : 'M'));\n return;\n }\n\n let data: number[] = [];\n\n encode(data, button);\n encode(data, pos.x);\n encode(data, pos.y);\n\n self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data));\n }\n\n function getButton(ev: MouseEvent): number {\n let button;\n let shift;\n let meta;\n let ctrl;\n let mod;\n\n // two low bits:\n // 0 = left\n // 1 = middle\n // 2 = right\n // 3 = release\n // wheel up/down:\n // 1, and 2 - with 64 added\n switch ((ev).overrideType || ev.type) {\n case 'mousedown':\n button = ev.button != null\n ? +ev.button\n : ev.which != null\n ? ev.which - 1\n : null;\n\n if (Browser.isMSIE) {\n button = button === 1 ? 0 : button === 4 ? 1 : button;\n }\n break;\n case 'mouseup':\n button = 3;\n break;\n case 'DOMMouseScroll':\n button = ev.detail < 0\n ? 64\n : 65;\n break;\n case 'wheel':\n button = (ev).wheelDeltaY > 0\n ? 64\n : 65;\n break;\n }\n\n // next three bits are the modifiers:\n // 4 = shift, 8 = meta, 16 = control\n shift = ev.shiftKey ? 4 : 0;\n meta = ev.metaKey ? 8 : 0;\n ctrl = ev.ctrlKey ? 16 : 0;\n mod = shift | meta | ctrl;\n\n // no mods\n if (self.vt200Mouse) {\n // ctrl only\n mod &= ctrl;\n } else if (!self.normalMouse) {\n mod = 0;\n }\n\n // increment to SP\n button = (32 + (mod << 2)) + button;\n\n return button;\n }\n\n on(el, 'mousedown', (ev: MouseEvent) => {\n\n // Prevent the focus on the textarea from getting lost\n // and make sure we get focused on mousedown\n ev.preventDefault();\n this.focus();\n\n // Don't send the mouse button to the pty if mouse events are disabled or\n // if the selection manager is having selection forced (ie. a modifier is\n // held).\n if (!this.mouseEvents || this.selectionManager.shouldForceSelection(ev)) {\n return;\n }\n\n // send the button\n sendButton(ev);\n\n // fix for odd bug\n // if (this.vt200Mouse && !this.normalMouse) {\n if (this.vt200Mouse) {\n (ev).overrideType = 'mouseup';\n sendButton(ev);\n return this.cancel(ev);\n }\n\n // bind events\n if (this.normalMouse) on(this.document, 'mousemove', sendMove);\n\n // x10 compatibility mode can't send button releases\n if (!this.x10Mouse) {\n const handler = (ev: MouseEvent) => {\n sendButton(ev);\n // TODO: Seems dangerous calling this on document?\n if (this.normalMouse) off(this.document, 'mousemove', sendMove);\n off(this.document, 'mouseup', handler);\n return this.cancel(ev);\n };\n // TODO: Seems dangerous calling this on document?\n on(this.document, 'mouseup', handler);\n }\n\n return this.cancel(ev);\n });\n\n // if (this.normalMouse) {\n // on(this.document, 'mousemove', sendMove);\n // }\n\n on(el, 'wheel', (ev: WheelEvent) => {\n if (!this.mouseEvents) return;\n if (this.x10Mouse || this.vt300Mouse || this.decLocator) return;\n sendButton(ev);\n ev.preventDefault();\n });\n\n // allow wheel scrolling in\n // the shell for example\n on(el, 'wheel', (ev: WheelEvent) => {\n if (this.mouseEvents) return;\n this.viewport.onWheel(ev);\n return this.cancel(ev);\n });\n\n on(el, 'touchstart', (ev: TouchEvent) => {\n if (this.mouseEvents) return;\n this.viewport.onTouchStart(ev);\n return this.cancel(ev);\n });\n\n on(el, 'touchmove', (ev: TouchEvent) => {\n if (this.mouseEvents) return;\n this.viewport.onTouchMove(ev);\n return this.cancel(ev);\n });\n }\n\n /**\n * Destroys the terminal.\n */\n public destroy(): void {\n super.destroy();\n this.readable = false;\n this.writable = false;\n this.handler = () => {};\n this.write = () => {};\n if (this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n // this.emit('close');\n }\n\n /**\n * Tells the renderer to refresh terminal content between two rows (inclusive) at the next\n * opportunity.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\n public refresh(start: number, end: number): void {\n if (this.renderer) {\n this.renderer.queueRefresh(start, end);\n }\n }\n\n /**\n * Queues linkification for the specified rows.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\n private queueLinkification(start: number, end: number): void {\n if (this.linkifier) {\n this.linkifier.linkifyRows(start, end);\n }\n }\n\n /**\n * Display the cursor element\n */\n public showCursor(): void {\n if (!this.cursorState) {\n this.cursorState = 1;\n this.refresh(this.buffer.y, this.buffer.y);\n }\n }\n\n /**\n * Scroll the terminal down 1 row, creating a blank line.\n * @param isWrapped Whether the new line is wrapped from the previous line.\n */\n public scroll(isWrapped?: boolean): void {\n const newLine = this.blankLine(undefined, isWrapped);\n const topRow = this.buffer.ybase + this.buffer.scrollTop;\n let bottomRow = this.buffer.ybase + this.buffer.scrollBottom;\n\n if (this.buffer.scrollTop === 0) {\n // Determine whether the buffer is going to be trimmed after insertion.\n const willBufferBeTrimmed = this.buffer.lines.length === this.buffer.lines.maxLength;\n\n // Insert the line using the fastest method\n if (bottomRow === this.buffer.lines.length - 1) {\n this.buffer.lines.push(newLine);\n } else {\n this.buffer.lines.splice(bottomRow + 1, 0, newLine);\n }\n\n // Only adjust ybase and ydisp when the buffer is not trimmed\n if (!willBufferBeTrimmed) {\n this.buffer.ybase++;\n // Only scroll the ydisp with ybase if the user has not scrolled up\n if (!this.userScrolling) {\n this.buffer.ydisp++;\n }\n } else {\n // When the buffer is full and the user has scrolled up, keep the text\n // stable unless ydisp is right at the top\n if (this.userScrolling) {\n this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0);\n }\n }\n } else {\n // scrollTop is non-zero which means no line will be going to the\n // scrollback, instead we can just shift them in-place.\n const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/;\n this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);\n this.buffer.lines.set(bottomRow, newLine);\n }\n\n // Move the viewport to the bottom of the buffer unless the user is\n // scrolling.\n if (!this.userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n\n // Flag rows that need updating\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n\n /**\n * This event is emitted whenever the terminal is scrolled.\n * The one parameter passed is the new y display position.\n *\n * @event scroll\n */\n this.emit('scroll', this.buffer.ydisp);\n }\n\n /**\n * Scroll the display of the terminal\n * @param {number} disp The number of lines to scroll down (negative scroll up).\n * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollLines. This is used\n * to avoid unwanted events being handled by the viewport when the event was triggered from the\n * viewport originally.\n */\n public scrollLines(disp: number, suppressScrollEvent?: boolean): void {\n if (disp < 0) {\n if (this.buffer.ydisp === 0) {\n return;\n }\n this.userScrolling = true;\n } else if (disp + this.buffer.ydisp >= this.buffer.ybase) {\n this.userScrolling = false;\n }\n\n const oldYdisp = this.buffer.ydisp;\n this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);\n\n // No change occurred, don't trigger scroll/refresh\n if (oldYdisp === this.buffer.ydisp) {\n return;\n }\n\n if (!suppressScrollEvent) {\n this.emit('scroll', this.buffer.ydisp);\n }\n\n this.refresh(0, this.rows - 1);\n }\n\n /**\n * Scroll the display of the terminal by a number of pages.\n * @param {number} pageCount The number of pages to scroll (negative scrolls up).\n */\n public scrollPages(pageCount: number): void {\n this.scrollLines(pageCount * (this.rows - 1));\n }\n\n /**\n * Scrolls the display of the terminal to the top.\n */\n public scrollToTop(): void {\n this.scrollLines(-this.buffer.ydisp);\n }\n\n /**\n * Scrolls the display of the terminal to the bottom.\n */\n public scrollToBottom(): void {\n this.scrollLines(this.buffer.ybase - this.buffer.ydisp);\n }\n\n /**\n * Writes text to the terminal.\n * @param {string} data The text to write to the terminal.\n */\n public write(data: string): void {\n this.writeBuffer.push(data);\n\n // Send XOFF to pause the pty process if the write buffer becomes too large so\n // xterm.js can catch up before more data is sent. This is necessary in order\n // to keep signals such as ^C responsive.\n if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {\n // XOFF - stop pty pipe\n // XON will be triggered by emulator before processing data chunk\n this.send(C0.DC3);\n this.xoffSentToCatchUp = true;\n }\n\n if (!this.writeInProgress && this.writeBuffer.length > 0) {\n // Kick off a write which will write all data in sequence recursively\n this.writeInProgress = true;\n // Kick off an async innerWrite so more writes can come in while processing data\n setTimeout(() => {\n this.innerWrite();\n });\n }\n }\n\n private innerWrite(): void {\n const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);\n while (writeBatch.length > 0) {\n const data = writeBatch.shift();\n\n // If XOFF was sent in order to catch up with the pty process, resume it if\n // the writeBuffer is empty to allow more data to come in.\n if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {\n this.send(C0.DC1);\n this.xoffSentToCatchUp = false;\n }\n\n this.refreshStart = this.buffer.y;\n this.refreshEnd = this.buffer.y;\n\n // HACK: Set the parser state based on it's state at the time of return.\n // This works around the bug #662 which saw the parser state reset in the\n // middle of parsing escape sequence in two chunks. For some reason the\n // state of the parser resets to 0 after exiting parser.parse. This change\n // just sets the state back based on the correct return statement.\n const state = this.parser.parse(data);\n this.parser.setState(state);\n\n this.updateRange(this.buffer.y);\n this.refresh(this.refreshStart, this.refreshEnd);\n }\n if (this.writeBuffer.length > 0) {\n // Allow renderer to catch up before processing the next batch\n setTimeout(() => this.innerWrite(), 0);\n } else {\n this.writeInProgress = false;\n }\n }\n\n /**\n * Writes text to the terminal, followed by a break line character (\\n).\n * @param {string} data The text to write to the terminal.\n */\n public writeln(data: string): void {\n this.write(data + '\\r\\n');\n }\n\n /**\n * Attaches a custom key event handler which is run before keys are processed,\n * giving consumers of xterm.js ultimate control as to what keys should be\n * processed by the terminal and what keys should not.\n * @param customKeyEventHandler The custom KeyboardEvent handler to attach.\n * This is a function that takes a KeyboardEvent, allowing consumers to stop\n * propogation and/or prevent the default action. The function returns whether\n * the event should be processed by xterm.js.\n */\n public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {\n this.customKeyEventHandler = customKeyEventHandler;\n }\n\n /**\n * Attaches a http(s) link handler, forcing web links to behave differently to\n * regular tags. This will trigger a refresh as links potentially need to be\n * reconstructed. Calling this with null will remove the handler.\n * @param handler The handler callback function.\n */\n public setHypertextLinkHandler(handler: LinkMatcherHandler): void {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');\n }\n this.linkifier.setHypertextLinkHandler(handler);\n // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n }\n\n /**\n * Attaches a validation callback for hypertext links. This is useful to use\n * validation logic or to do something with the link's element and url.\n * @param callback The callback to use, this can\n * be cleared with null.\n */\n public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');\n }\n this.linkifier.setHypertextValidationCallback(callback);\n // // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n }\n\n /**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param handler The callback when the link is called.\n * @param options Options for the link matcher.\n * @return The ID of the new matcher, this can be used to deregister.\n */\n public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number {\n if (this.linkifier) {\n const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);\n this.refresh(0, this.rows - 1);\n return matcherId;\n }\n return 0;\n }\n\n /**\n * Deregisters a link matcher if it has been registered.\n * @param matcherId The link matcher's ID (returned after register)\n */\n public deregisterLinkMatcher(matcherId: number): void {\n if (this.linkifier) {\n if (this.linkifier.deregisterLinkMatcher(matcherId)) {\n this.refresh(0, this.rows - 1);\n }\n }\n }\n\n /**\n * Gets whether the terminal has an active selection.\n */\n public hasSelection(): boolean {\n return this.selectionManager ? this.selectionManager.hasSelection : false;\n }\n\n /**\n * Gets the terminal's current selection, this is useful for implementing copy\n * behavior outside of xterm.js.\n */\n public getSelection(): string {\n return this.selectionManager ? this.selectionManager.selectionText : '';\n }\n\n /**\n * Clears the current terminal selection.\n */\n public clearSelection(): void {\n if (this.selectionManager) {\n this.selectionManager.clearSelection();\n }\n }\n\n /**\n * Selects all text within the terminal.\n */\n public selectAll(): void {\n if (this.selectionManager) {\n this.selectionManager.selectAll();\n }\n }\n\n /**\n * Handle a keydown event\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keydown event to be handled.\n */\n protected _keyDown(ev: KeyboardEvent): boolean {\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n if (!this.compositionHelper.keydown(ev)) {\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n return false;\n }\n\n const result = this._evaluateKeyEscapeSequence(ev);\n\n if (result.key === C0.DC3) { // XOFF\n this.writeStopped = true;\n } else if (result.key === C0.DC1) { // XON\n this.writeStopped = false;\n }\n\n if (result.scrollLines) {\n this.scrollLines(result.scrollLines);\n return this.cancel(ev, true);\n }\n\n if (isThirdLevelShift(this.browser, ev)) {\n return true;\n }\n\n if (result.cancel) {\n // The event is canceled at the end already, is this necessary?\n this.cancel(ev, true);\n }\n\n if (!result.key) {\n return true;\n }\n\n this.emit('keydown', ev);\n this.emit('key', result.key, ev);\n this.showCursor();\n this.handler(result.key);\n\n return this.cancel(ev, true);\n }\n\n /**\n * Returns an object that determines how a KeyboardEvent should be handled. The key of the\n * returned value is the new key code to pass to the PTY.\n *\n * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * @param ev The keyboard event to be translated to key escape sequence.\n */\n protected _evaluateKeyEscapeSequence(ev: KeyboardEvent): {cancel: boolean, key: string, scrollLines: number} {\n const result: {cancel: boolean, key: string, scrollLines: number} = {\n // Whether to cancel event propogation (NOTE: this may not be needed since the event is\n // canceled at the end of keyDown\n cancel: false,\n // The new key even to emit\n key: undefined,\n // The number of characters to scroll, if this is defined it will cancel the event\n scrollLines: undefined\n };\n const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0);\n switch (ev.keyCode) {\n case 0:\n if (ev.key === 'UIKeyInputUpArrow') {\n if (this.applicationCursor) {\n result.key = C0.ESC + 'OA';\n } else {\n result.key = C0.ESC + '[A';\n }\n }\n else if (ev.key === 'UIKeyInputLeftArrow') {\n if (this.applicationCursor) {\n result.key = C0.ESC + 'OD';\n } else {\n result.key = C0.ESC + '[D';\n }\n }\n else if (ev.key === 'UIKeyInputRightArrow') {\n if (this.applicationCursor) {\n result.key = C0.ESC + 'OC';\n } else {\n result.key = C0.ESC + '[C';\n }\n }\n else if (ev.key === 'UIKeyInputDownArrow') {\n if (this.applicationCursor) {\n result.key = C0.ESC + 'OB';\n } else {\n result.key = C0.ESC + '[B';\n }\n }\n break;\n case 8:\n // backspace\n if (ev.shiftKey) {\n result.key = C0.BS; // ^H\n break;\n }\n result.key = C0.DEL; // ^?\n break;\n case 9:\n // tab\n if (ev.shiftKey) {\n result.key = C0.ESC + '[Z';\n break;\n }\n result.key = C0.HT;\n result.cancel = true;\n break;\n case 13:\n // return/enter\n result.key = C0.CR;\n result.cancel = true;\n break;\n case 27:\n // escape\n result.key = C0.ESC;\n result.cancel = true;\n break;\n case 37:\n // left-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';\n // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key === C0.ESC + '[1;3D') {\n result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OD';\n } else {\n result.key = C0.ESC + '[D';\n }\n break;\n case 39:\n // right-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';\n // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key === C0.ESC + '[1;3C') {\n result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OC';\n } else {\n result.key = C0.ESC + '[C';\n }\n break;\n case 38:\n // up-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';\n // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key === C0.ESC + '[1;3A') {\n result.key = C0.ESC + '[1;5A';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OA';\n } else {\n result.key = C0.ESC + '[A';\n }\n break;\n case 40:\n // down-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';\n // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key === C0.ESC + '[1;3B') {\n result.key = C0.ESC + '[1;5B';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OB';\n } else {\n result.key = C0.ESC + '[B';\n }\n break;\n case 45:\n // insert\n if (!ev.shiftKey && !ev.ctrlKey) {\n // or + are used to\n // copy-paste on some systems.\n result.key = C0.ESC + '[2~';\n }\n break;\n case 46:\n // delete\n if (modifiers) {\n result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[3~';\n }\n break;\n case 36:\n // home\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OH';\n else\n result.key = C0.ESC + '[H';\n break;\n case 35:\n // end\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OF';\n else\n result.key = C0.ESC + '[F';\n break;\n case 33:\n // page up\n if (ev.shiftKey) {\n result.scrollLines = -(this.rows - 1);\n } else {\n result.key = C0.ESC + '[5~';\n }\n break;\n case 34:\n // page down\n if (ev.shiftKey) {\n result.scrollLines = this.rows - 1;\n } else {\n result.key = C0.ESC + '[6~';\n }\n break;\n case 112:\n // F1-F12\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';\n } else {\n result.key = C0.ESC + 'OP';\n }\n break;\n case 113:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';\n } else {\n result.key = C0.ESC + 'OQ';\n }\n break;\n case 114:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';\n } else {\n result.key = C0.ESC + 'OR';\n }\n break;\n case 115:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';\n } else {\n result.key = C0.ESC + 'OS';\n }\n break;\n case 116:\n if (modifiers) {\n result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[15~';\n }\n break;\n case 117:\n if (modifiers) {\n result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[17~';\n }\n break;\n case 118:\n if (modifiers) {\n result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[18~';\n }\n break;\n case 119:\n if (modifiers) {\n result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[19~';\n }\n break;\n case 120:\n if (modifiers) {\n result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[20~';\n }\n break;\n case 121:\n if (modifiers) {\n result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[21~';\n }\n break;\n case 122:\n if (modifiers) {\n result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[23~';\n }\n break;\n case 123:\n if (modifiers) {\n result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[24~';\n }\n break;\n default:\n // a-z and space\n if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = String.fromCharCode(ev.keyCode - 64);\n } else if (ev.keyCode === 32) {\n // NUL\n result.key = String.fromCharCode(0);\n } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {\n // escape, file sep, group sep, record sep, unit sep\n result.key = String.fromCharCode(ev.keyCode - 51 + 27);\n } else if (ev.keyCode === 56) {\n // delete\n result.key = String.fromCharCode(127);\n } else if (ev.keyCode === 219) {\n // ^[ - Control Sequence Introducer (CSI)\n result.key = String.fromCharCode(27);\n } else if (ev.keyCode === 220) {\n // ^\\ - String Terminator (ST)\n result.key = String.fromCharCode(28);\n } else if (ev.keyCode === 221) {\n // ^] - Operating System Command (OSC)\n result.key = String.fromCharCode(29);\n }\n } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {\n // On Mac this is a third level shift. Use instead.\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);\n } else if (ev.keyCode === 192) {\n result.key = C0.ESC + '`';\n } else if (ev.keyCode >= 48 && ev.keyCode <= 57) {\n result.key = C0.ESC + (ev.keyCode - 48);\n }\n } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) {\n if (ev.keyCode === 65) { // cmd + a\n this.selectAll();\n }\n }\n break;\n }\n\n return result;\n }\n\n /**\n * Set the G level of the terminal\n * @param g\n */\n public setgLevel(g: number): void {\n this.glevel = g;\n this.charset = this.charsets[g];\n }\n\n /**\n * Set the charset for the given G level of the terminal\n * @param g\n * @param charset\n */\n public setgCharset(g: number, charset: Charset): void {\n this.charsets[g] = charset;\n if (this.glevel === g) {\n this.charset = charset;\n }\n }\n\n /**\n * Handle a keypress event.\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keypress event to be handled.\n */\n protected _keyPress(ev: KeyboardEvent): boolean {\n let key;\n\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.cancel(ev);\n\n if (ev.charCode) {\n key = ev.charCode;\n } else if (ev.which == null) {\n key = ev.keyCode;\n } else if (ev.which !== 0 && ev.charCode !== 0) {\n key = ev.which;\n } else {\n return false;\n }\n\n if (!key || (\n (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this.browser, ev)\n )) {\n return false;\n }\n\n key = String.fromCharCode(key);\n\n this.emit('keypress', key, ev);\n this.emit('key', key, ev);\n this.showCursor();\n this.handler(key);\n\n return true;\n }\n\n /**\n * Send data for handling to the terminal\n * @param {string} data\n */\n public send(data: string): void {\n if (!this.sendDataQueue) {\n setTimeout(() => {\n this.handler(this.sendDataQueue);\n this.sendDataQueue = '';\n }, 1);\n }\n\n this.sendDataQueue += data;\n }\n\n /**\n * Ring the bell.\n * Note: We could do sweet things with webaudio here\n */\n public bell(): void {\n this.emit('bell');\n if (this.soundBell()) this.bellAudioElement.play();\n\n if (this.visualBell()) {\n this.element.classList.add('visual-bell-active');\n clearTimeout(this.visualBellTimer);\n this.visualBellTimer = window.setTimeout(() => {\n this.element.classList.remove('visual-bell-active');\n }, 200);\n }\n }\n\n /**\n * Log the current state to the console.\n */\n public log(text: string, data?: any): void {\n if (!this.options.debug) return;\n if (!this.context.console || !this.context.console.log) return;\n this.context.console.log(text, data);\n }\n\n /**\n * Log the current state as error to the console.\n */\n public error(text: string, data?: any): void {\n if (!this.options.debug) return;\n if (!this.context.console || !this.context.console.error) return;\n this.context.console.error(text, data);\n }\n\n /**\n * Resizes the terminal.\n *\n * @param {number} x The number of columns to resize to.\n * @param {number} y The number of rows to resize to.\n */\n public resize(x: number, y: number): void {\n if (isNaN(x) || isNaN(y)) {\n return;\n }\n\n if (x === this.cols && y === this.rows) {\n // Check if we still need to measure the char size (fixes #785).\n if (!this.charMeasure.width || !this.charMeasure.height) {\n this.charMeasure.measure(this.options);\n }\n return;\n }\n\n if (x < 1) x = 1;\n if (y < 1) y = 1;\n\n this.buffers.resize(x, y);\n\n this.cols = x;\n this.rows = y;\n this.buffers.setupTabStops(this.cols);\n\n this.charMeasure.measure(this.options);\n\n this.refresh(0, this.rows - 1);\n\n this.geometry = [this.cols, this.rows];\n this.emit('resize', {cols: x, rows: y});\n }\n\n /**\n * Updates the range of rows to refresh\n * @param {number} y The number of rows to refresh next.\n */\n public updateRange(y: number): void {\n if (y < this.refreshStart) this.refreshStart = y;\n if (y > this.refreshEnd) this.refreshEnd = y;\n // if (y > this.refreshEnd) {\n // this.refreshEnd = y;\n // if (y > this.rows - 1) {\n // this.refreshEnd = this.rows - 1;\n // }\n // }\n }\n\n /**\n * Set the range of refreshing to the maximum value\n */\n public maxRange(): void {\n this.refreshStart = 0;\n this.refreshEnd = this.rows - 1;\n }\n\n /**\n * Erase in the identified line everything from \"x\" to the end of the line (right).\n * @param {number} x The column from which to start erasing to the end of the line.\n * @param {number} y The line in which to operate.\n */\n public eraseRight(x: number, y: number): void {\n const line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm\n for (; x < this.cols; x++) {\n line[x] = ch;\n }\n this.updateRange(y);\n }\n\n /**\n * Erase in the identified line everything from \"x\" to the start of the line (left).\n * @param {number} x The column from which to start erasing to the start of the line.\n * @param {number} y The line in which to operate.\n */\n public eraseLeft(x: number, y: number): void {\n const line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n const ch: CharData = [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */]; // xterm\n x++;\n while (x--) {\n line[x] = ch;\n }\n this.updateRange(y);\n }\n\n /**\n * Clear the entire buffer, making the prompt line the new first line.\n */\n public clear(): void {\n if (this.buffer.ybase === 0 && this.buffer.y === 0) {\n // Don't clear if it's already clear\n return;\n }\n this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));\n this.buffer.lines.length = 1;\n this.buffer.ydisp = 0;\n this.buffer.ybase = 0;\n this.buffer.y = 0;\n for (let i = 1; i < this.rows; i++) {\n this.buffer.lines.push(this.blankLine());\n }\n this.refresh(0, this.rows - 1);\n this.emit('scroll', this.buffer.ydisp);\n }\n\n /**\n * Erase all content in the given line\n * @param {number} y The line to erase all of its contents.\n */\n public eraseLine(y: number): void {\n this.eraseRight(0, y);\n }\n\n /**\n * Return the data array of a blank line\n * @param {boolean} cur First bunch of data for each \"blank\" character.\n * @param {boolean} isWrapped Whether the new line is wrapped from the previous line.\n * @param {boolean} cols The number of columns in the terminal, if this is not\n * set, the terminal's current column count would be used.\n */\n public blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData {\n const attr = cur ? this.eraseAttr() : this.defAttr;\n\n const ch: CharData = [attr, ' ', 1, 32 /* ' '.charCodeAt(0) */]; // width defaults to 1 halfwidth character\n const line: LineData = [];\n\n // TODO: It is not ideal that this is a property on an array, a buffer line\n // class should be added that will hold this data and other useful functions.\n if (isWrapped) {\n (line).isWrapped = isWrapped;\n }\n\n cols = cols || this.cols;\n for (let i = 0; i < cols; i++) {\n line[i] = ch;\n }\n\n return line;\n }\n\n /**\n * If cur return the back color xterm feature attribute. Else return defAttr.\n * @param cur\n */\n public ch(cur?: boolean): CharData {\n if (cur) {\n return [this.eraseAttr(), ' ', 1, 32 /* ' '.charCodeAt(0) */];\n }\n return [this.defAttr, ' ', 1, 32 /* ' '.charCodeAt(0) */];\n }\n\n /**\n * Evaluate if the current terminal is the given argument.\n * @param term The terminal name to evaluate\n */\n public is(term: string): boolean {\n return (this.options.termName + '').indexOf(term) === 0;\n }\n\n /**\n * Emit the 'data' event and populate the given data.\n * @param {string} data The data to populate in the event.\n */\n public handler(data: string): void {\n // Prevents all events to pty process if stdin is disabled\n if (this.options.disableStdin) {\n return;\n }\n\n // Clear the selection if the selection manager is available and has an active selection\n if (this.selectionManager && this.selectionManager.hasSelection) {\n this.selectionManager.clearSelection();\n }\n\n // Input is being sent to the terminal, the terminal should focus the prompt.\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n this.emit('data', data);\n }\n\n /**\n * Emit the 'title' event and populate the given title.\n * @param {string} title The title to populate in the event.\n */\n private handleTitle(title: string): void {\n /**\n * This event is emitted when the title of the terminal is changed\n * from inside the terminal. The parameter is the new title.\n *\n * @event title\n */\n this.emit('title', title);\n }\n\n /**\n * ESC\n */\n\n /**\n * ESC D Index (IND is 0x84).\n */\n public index(): void {\n this.buffer.y++;\n if (this.buffer.y > this.buffer.scrollBottom) {\n this.buffer.y--;\n this.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this.buffer.x >= this.cols) {\n this.buffer.x--;\n }\n }\n\n /**\n * ESC M Reverse Index (RI is 0x8d).\n *\n * Move the cursor up one row, inserting a new blank line if necessary.\n */\n public reverseIndex(): void {\n if (this.buffer.y === this.buffer.scrollTop) {\n // possibly move the code below to term.reverseScroll();\n // test: echo -ne '\\e[1;1H\\e[44m\\eM\\e[0m'\n // blankLine(true) is xterm/linux behavior\n const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop;\n this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1);\n this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true));\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n } else {\n this.buffer.y--;\n }\n }\n\n /**\n * ESC c Full Reset (RIS).\n */\n public reset(): void {\n this.options.rows = this.rows;\n this.options.cols = this.cols;\n const customKeyEventHandler = this.customKeyEventHandler;\n const inputHandler = this.inputHandler;\n const buffers = this.buffers;\n this.setup();\n this.customKeyEventHandler = customKeyEventHandler;\n this.inputHandler = inputHandler;\n this.buffers = buffers;\n this.refresh(0, this.rows - 1);\n this.viewport.syncScrollArea();\n }\n\n\n /**\n * ESC H Tab Set (HTS is 0x88).\n */\n private tabSet(): void {\n this.buffer.tabs[this.buffer.x] = true;\n }\n\n // TODO: Remove cancel function and cancelEvents option\n public cancel(ev: Event, force?: boolean): boolean {\n if (!this.options.cancelEvents && !force) {\n return;\n }\n ev.preventDefault();\n ev.stopPropagation();\n return false;\n }\n\n // TODO: Remove when true color is implemented\n public matchColor(r1: number, g1: number, b1: number): number {\n return matchColor_(r1, g1, b1);\n }\n\n private visualBell(): boolean {\n return this.options.bellStyle === 'visual' ||\n this.options.bellStyle === 'both';\n }\n\n private soundBell(): boolean {\n return this.options.bellStyle === 'sound' ||\n this.options.bellStyle === 'both';\n }\n\n private syncBellSound(): void {\n if (this.soundBell() && this.bellAudioElement) {\n this.bellAudioElement.setAttribute('src', this.options.bellSound);\n } else if (this.soundBell()) {\n this.bellAudioElement = document.createElement('audio');\n this.bellAudioElement.setAttribute('preload', 'auto');\n this.bellAudioElement.setAttribute('src', this.options.bellSound);\n this.helperContainer.appendChild(this.bellAudioElement);\n } else if (this.bellAudioElement) {\n this.helperContainer.removeChild(this.bellAudioElement);\n }\n }\n}\n\n/**\n * Helpers\n */\n\nfunction globalOn(el: any, type: string, handler: (event: Event) => any, capture?: boolean): void {\n if (!Array.isArray(el)) {\n el = [el];\n }\n el.forEach((element: HTMLElement) => {\n element.addEventListener(type, handler, capture || false);\n });\n}\n// TODO: Remove once everything is typed\nconst on = globalOn;\n\nfunction off(el: any, type: string, handler: (event: Event) => any, capture: boolean = false): void {\n el.removeEventListener(type, handler, capture);\n}\n\nfunction isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {\n const thirdLevelKey =\n (browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||\n (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);\n\n if (ev.type === 'keypress') {\n return thirdLevelKey;\n }\n\n // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)\n return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);\n}\n\nfunction wasMondifierKeyOnlyEvent(ev: KeyboardEvent): boolean {\n return ev.keyCode === 16 || // Shift\n ev.keyCode === 17 || // Ctrl\n ev.keyCode === 18; // Alt\n}\n\n/**\n * TODO:\n * The below color-related code can be removed when true color is implemented.\n * It's only purpose is to match true color requests with the closest matching\n * ANSI color code.\n */\n\n// Colors 0-15 + 16-255\n// Much thanks to TooTallNate for writing this.\nconst vcolors: number[][] = (function(): number[][] {\n const result = DEFAULT_ANSI_COLORS.map(c => {\n c = c.substring(1);\n return [\n parseInt(c.substring(0, 2), 16),\n parseInt(c.substring(2, 4), 16),\n parseInt(c.substring(4, 6), 16)\n ];\n });\n const r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];\n\n // 16-231\n for (let i = 0; i < 216; i++) {\n result.push([\n r[(i / 36) % 6 | 0],\n r[(i / 6) % 6 | 0],\n r[i % 6]\n ]);\n }\n\n // 232-255 (grey)\n let c: number;\n for (let i = 0; i < 24; i++) {\n c = 8 + i * 10;\n result.push([c, c, c]);\n }\n\n return result;\n})();\n\nconst matchColorCache: {[colorRGBHash: number]: number} = {};\n\n// http://stackoverflow.com/questions/1633828\nfunction matchColorDistance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number {\n return Math.pow(30 * (r1 - r2), 2)\n + Math.pow(59 * (g1 - g2), 2)\n + Math.pow(11 * (b1 - b2), 2);\n};\n\n\nfunction matchColor_(r1: number, g1: number, b1: number): number {\n const hash = (r1 << 16) | (g1 << 8) | b1;\n\n if (matchColorCache[hash] != null) {\n return matchColorCache[hash];\n }\n\n let ldiff = Infinity;\n let li = -1;\n let i = 0;\n let c: number[];\n let r2: number;\n let g2: number;\n let b2: number;\n let diff: number;\n\n for (; i < vcolors.length; i++) {\n c = vcolors[i];\n r2 = c[0];\n g2 = c[1];\n b2 = c[2];\n\n diff = matchColorDistance(r1, g1, b1, r2, g2, b2);\n\n if (diff === 0) {\n li = i;\n break;\n }\n\n if (diff < ldiff) {\n ldiff = diff;\n li = i;\n }\n }\n\n return matchColorCache[hash] = li;\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\n/**\n * Represents a selection within the buffer. This model only cares about column\n * and row coordinates, not wide characters.\n */\nexport class SelectionModel {\n /**\n * Whether select all is currently active.\n */\n public isSelectAllActive: boolean;\n\n /**\n * The [x, y] position the selection starts at.\n */\n public selectionStart: [number, number];\n\n /**\n * The minimal length of the selection from the start position. When double\n * clicking on a word, the word will be selected which makes the selection\n * start at the start of the word and makes this variable the length.\n */\n public selectionStartLength: number;\n\n /**\n * The [x, y] position the selection ends at.\n */\n public selectionEnd: [number, number];\n\n constructor(\n private _terminal: ITerminal\n ) {\n this.clearSelection();\n }\n\n /**\n * Clears the current selection.\n */\n public clearSelection(): void {\n this.selectionStart = null;\n this.selectionEnd = null;\n this.isSelectAllActive = false;\n this.selectionStartLength = 0;\n }\n\n /**\n * The final selection start, taking into consideration select all.\n */\n public get finalSelectionStart(): [number, number] {\n if (this.isSelectAllActive) {\n return [0, 0];\n }\n\n if (!this.selectionEnd || !this.selectionStart) {\n return this.selectionStart;\n }\n\n return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart;\n }\n\n /**\n * The final selection end, taking into consideration select all, double click\n * word selection and triple click line selection.\n */\n public get finalSelectionEnd(): [number, number] {\n if (this.isSelectAllActive) {\n return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1];\n }\n\n if (!this.selectionStart) {\n return null;\n }\n\n // Use the selection start if the end doesn't exist or they're reversed\n if (!this.selectionEnd || this.areSelectionValuesReversed()) {\n return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]];\n }\n\n // Ensure the the word/line is selected after a double/triple click\n if (this.selectionStartLength) {\n // Select the larger of the two when start and end are on the same line\n if (this.selectionEnd[1] === this.selectionStart[1]) {\n return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]];\n }\n }\n return this.selectionEnd;\n }\n\n /**\n * Returns whether the selection start and end are reversed.\n */\n public areSelectionValuesReversed(): boolean {\n const start = this.selectionStart;\n const end = this.selectionEnd;\n if (!start || !end) {\n return false;\n }\n return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n * @return Whether a refresh is necessary.\n */\n public onTrim(amount: number): boolean {\n // Adjust the selection position based on the trimmed amount.\n if (this.selectionStart) {\n this.selectionStart[1] -= amount;\n }\n if (this.selectionEnd) {\n this.selectionEnd[1] -= amount;\n }\n\n // The selection has moved off the buffer, clear it.\n if (this.selectionEnd && this.selectionEnd[1] < 0) {\n this.clearSelection();\n return true;\n }\n\n // If the selection start is trimmed, ensure the start column is 0.\n if (this.selectionStart && this.selectionStart[1] < 0) {\n this.selectionStart[1] = 0;\n }\n return false;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { MouseHelper } from './utils/MouseHelper';\nimport * as Browser from './utils/Browser';\nimport { CharMeasure } from './utils/CharMeasure';\nimport { CircularList } from './utils/CircularList';\nimport { EventEmitter } from './EventEmitter';\nimport { ITerminal, ICircularList, ISelectionManager, IBuffer } from './Interfaces';\nimport { SelectionModel } from './SelectionModel';\nimport { LineData, CharData } from './Types';\nimport { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';\n\n/**\n * The number of pixels the mouse needs to be above or below the viewport in\n * order to scroll at the maximum speed.\n */\nconst DRAG_SCROLL_MAX_THRESHOLD = 50;\n\n/**\n * The maximum scrolling speed\n */\nconst DRAG_SCROLL_MAX_SPEED = 15;\n\n/**\n * The number of milliseconds between drag scroll updates.\n */\nconst DRAG_SCROLL_INTERVAL = 50;\n\n/**\n * A string containing all characters that are considered word separated by the\n * double click to select work logic.\n */\nconst WORD_SEPARATORS = ' ()[]{}\\'\"';\n\nconst NON_BREAKING_SPACE_CHAR = String.fromCharCode(160);\nconst ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g');\n\n/**\n * Represents a position of a word on a line.\n */\ninterface IWordPosition {\n start: number;\n length: number;\n}\n\n/**\n * A selection mode, this drives how the selection behaves on mouse move.\n */\nenum SelectionMode {\n NORMAL,\n WORD,\n LINE\n}\n\n/**\n * A class that manages the selection of the terminal. With help from\n * SelectionModel, SelectionManager handles with all logic associated with\n * dealing with the selection, including handling mouse interaction, wide\n * characters and fetching the actual text within the selection. Rendering is\n * not handled by the SelectionManager but a 'refresh' event is fired when the\n * selection is ready to be redrawn.\n */\nexport class SelectionManager extends EventEmitter implements ISelectionManager {\n protected _model: SelectionModel;\n\n /**\n * The amount to scroll every drag scroll update (depends on how far the mouse\n * drag is above or below the terminal).\n */\n private _dragScrollAmount: number;\n\n /**\n * The current selection mode.\n */\n private _activeSelectionMode: SelectionMode;\n\n /**\n * A setInterval timer that is active while the mouse is down whose callback\n * scrolls the viewport when necessary.\n */\n private _dragScrollIntervalTimer: NodeJS.Timer;\n\n /**\n * The animation frame ID used for refreshing the selection.\n */\n private _refreshAnimationFrame: number;\n\n /**\n * Whether selection is enabled.\n */\n private _enabled = true;\n\n private _mouseMoveListener: EventListener;\n private _mouseUpListener: EventListener;\n\n constructor(\n private _terminal: ITerminal,\n private _buffer: IBuffer,\n private _charMeasure: CharMeasure\n ) {\n super();\n this._initListeners();\n this.enable();\n\n this._model = new SelectionModel(_terminal);\n this._activeSelectionMode = SelectionMode.NORMAL;\n }\n\n /**\n * Initializes listener variables.\n */\n private _initListeners(): void {\n this._mouseMoveListener = event => this._onMouseMove(event);\n this._mouseUpListener = event => this._onMouseUp(event);\n\n // Only adjust the selection on trim, shiftElements is rarely used (only in\n // reverseIndex) and delete in a splice is only ever used when the same\n // number of elements was just added. Given this is could actually be\n // beneficial to leave the selection as is for these cases.\n this._buffer.lines.on('trim', (amount: number) => this._onTrim(amount));\n }\n\n /**\n * Disables the selection manager. This is useful for when terminal mouse\n * are enabled.\n */\n public disable(): void {\n this.clearSelection();\n this._enabled = false;\n }\n\n /**\n * Enable the selection manager.\n */\n public enable(): void {\n this._enabled = true;\n }\n\n /**\n * Sets the active buffer, this should be called when the alt buffer is\n * switched in or out.\n * @param buffer The active buffer.\n */\n public setBuffer(buffer: IBuffer): void {\n this._buffer = buffer;\n this.clearSelection();\n }\n\n public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }\n public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }\n\n /**\n * Gets whether there is an active text selection.\n */\n public get hasSelection(): boolean {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return false;\n }\n return start[0] !== end[0] || start[1] !== end[1];\n }\n\n /**\n * Gets the text currently selected.\n */\n public get selectionText(): string {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return '';\n }\n\n // Get first row\n const startRowEndCol = start[1] === end[1] ? end[0] : null;\n let result: string[] = [];\n result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));\n\n // Get middle rows\n for (let i = start[1] + 1; i <= end[1] - 1; i++) {\n const bufferLine = this._buffer.lines.get(i);\n const lineText = this._buffer.translateBufferLineToString(i, true);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Get final row\n if (start[1] !== end[1]) {\n const bufferLine = this._buffer.lines.get(end[1]);\n const lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Format string by replacing non-breaking space chars with regular spaces\n // and joining the array into a multi-line string.\n const formattedResult = result.map(line => {\n return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');\n }).join(Browser.isMSWindows ? '\\r\\n' : '\\n');\n\n return formattedResult;\n }\n\n /**\n * Clears the current terminal selection.\n */\n public clearSelection(): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this.refresh();\n }\n\n /**\n * Queues a refresh, redrawing the selection on the next opportunity.\n * @param isNewSelection Whether the selection should be registered as a new\n * selection on Linux.\n */\n public refresh(isNewSelection?: boolean): void {\n // Queue the refresh for the renderer\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh());\n }\n\n // If the platform is Linux and the refresh call comes from a mouse event,\n // we need to update the selection for middle click to paste selection.\n if (Browser.isLinux && isNewSelection) {\n const selectionText = this.selectionText;\n if (selectionText.length) {\n this.emit('newselection', this.selectionText);\n }\n }\n }\n\n /**\n * Fires the refresh event, causing consumers to pick it up and redraw the\n * selection state.\n */\n private _refresh(): void {\n this._refreshAnimationFrame = null;\n this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd });\n }\n\n /**\n * Selects all text within the terminal.\n */\n public selectAll(): void {\n this._model.isSelectAllActive = true;\n this.refresh();\n this.emit('selection');\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n */\n private _onTrim(amount: number): void {\n const needsRefresh = this._model.onTrim(amount);\n if (needsRefresh) {\n this.refresh();\n }\n }\n\n /**\n * Gets the 0-based [x, y] buffer coordinates of the current mouse event.\n * @param event The mouse event.\n */\n private _getMouseBufferCoords(event: MouseEvent): [number, number] {\n const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.element, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true);\n if (!coords) {\n return null;\n }\n\n // Convert to 0-based\n coords[0]--;\n coords[1]--;\n\n // Convert viewport coords to buffer coords\n coords[1] += this._terminal.buffer.ydisp;\n return coords;\n }\n\n /**\n * Gets the amount the viewport should be scrolled based on how far out of the\n * terminal the mouse is.\n * @param event The mouse event.\n */\n private _getMouseEventScrollAmount(event: MouseEvent): number {\n let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.element)[1];\n const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight);\n if (offset >= 0 && offset <= terminalHeight) {\n return 0;\n }\n if (offset > terminalHeight) {\n offset -= terminalHeight;\n }\n\n offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD);\n offset /= DRAG_SCROLL_MAX_THRESHOLD;\n return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));\n }\n\n /**\n * Returns whether the selection manager should force selection, regardless of\n * whether the terminal is in mouse events mode.\n * @param event The mouse event.\n */\n public shouldForceSelection(event: MouseEvent): boolean {\n return Browser.isMac ? event.altKey : event.shiftKey;\n }\n\n /**\n * Handles te mousedown event, setting up for a new selection.\n * @param event The mousedown event.\n */\n public onMouseDown(event: MouseEvent): void {\n // If we have selection, we want the context menu on right click even if the\n // terminal is in mouse mode.\n if (event.button === 2 && this.hasSelection) {\n return;\n }\n\n // Only action the primary button\n if (event.button !== 0) {\n return;\n }\n\n // Allow selection when using a specific modifier key, even when disabled\n if (!this._enabled) {\n if (!this.shouldForceSelection(event)) {\n return;\n }\n\n // Don't send the mouse down event to the current process, we want to select\n event.stopPropagation();\n }\n\n // Tell the browser not to start a regular selection\n event.preventDefault();\n\n // Reset drag scroll state\n this._dragScrollAmount = 0;\n\n if (this._enabled && event.shiftKey) {\n this._onIncrementalClick(event);\n } else {\n if (event.detail === 1) {\n this._onSingleClick(event);\n } else if (event.detail === 2) {\n this._onDoubleClick(event);\n } else if (event.detail === 3) {\n this._onTripleClick(event);\n }\n }\n\n this._addMouseDownListeners();\n this.refresh(true);\n }\n\n /**\n * Adds listeners when mousedown is triggered.\n */\n private _addMouseDownListeners(): void {\n // Listen on the document so that dragging outside of viewport works\n this._terminal.element.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);\n this._terminal.element.ownerDocument.addEventListener('mouseup', this._mouseUpListener);\n this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);\n }\n\n /**\n * Removes the listeners that are registered when mousedown is triggered.\n */\n private _removeMouseDownListeners(): void {\n this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);\n this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);\n clearInterval(this._dragScrollIntervalTimer);\n this._dragScrollIntervalTimer = null;\n }\n\n /**\n * Performs an incremental click, setting the selection end position to the mouse\n * position.\n * @param event The mouse event.\n */\n private _onIncrementalClick(event: MouseEvent): void {\n if (this._model.selectionStart) {\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n }\n }\n\n /**\n * Performs a single click, resetting relevant state and setting the selection\n * start position.\n * @param event The mouse event.\n */\n private _onSingleClick(event: MouseEvent): void {\n this._model.selectionStartLength = 0;\n this._model.isSelectAllActive = false;\n this._activeSelectionMode = SelectionMode.NORMAL;\n\n // Initialize the new selection\n this._model.selectionStart = this._getMouseBufferCoords(event);\n if (!this._model.selectionStart) {\n return;\n }\n this._model.selectionEnd = null;\n\n // Ensure the line exists\n const line = this._buffer.lines.get(this._model.selectionStart[1]);\n if (!line) {\n return;\n }\n\n // Return early if the click event is not in the buffer (eg. in scroll bar)\n if (line.length >= this._model.selectionStart[0]) {\n return;\n }\n\n // If the mouse is over the second half of a wide character, adjust the\n // selection to cover the whole character\n const char = line[this._model.selectionStart[0]];\n if (char[CHAR_DATA_WIDTH_INDEX] === 0) {\n this._model.selectionStart[0]++;\n }\n }\n\n /**\n * Performs a double click, selecting the current work.\n * @param event The mouse event.\n */\n private _onDoubleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.WORD;\n this._selectWordAt(coords);\n }\n }\n\n /**\n * Performs a triple click, selecting the current line and activating line\n * select mode.\n * @param event The mouse event.\n */\n private _onTripleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.LINE;\n this._selectLineAt(coords[1]);\n }\n }\n\n /**\n * Handles the mousemove event when the mouse button is down, recording the\n * end of the selection and refreshing the selection.\n * @param event The mousemove event.\n */\n private _onMouseMove(event: MouseEvent): void {\n // If the mousemove listener is active it means that a selection is\n // currently being made, we should stop propogation to prevent mouse events\n // to be sent to the pty.\n event.stopImmediatePropagation();\n\n // Record the previous position so we know whether to redraw the selection\n // at the end.\n const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;\n\n // Set the initial selection end based on the mouse coordinates\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n if (!this._model.selectionEnd) {\n this.refresh(true);\n return;\n }\n\n // Select the entire line if line select mode is active.\n if (this._activeSelectionMode === SelectionMode.LINE) {\n if (this._model.selectionEnd[1] < this._model.selectionStart[1]) {\n this._model.selectionEnd[0] = 0;\n } else {\n this._model.selectionEnd[0] = this._terminal.cols;\n }\n } else if (this._activeSelectionMode === SelectionMode.WORD) {\n this._selectToWordAt(this._model.selectionEnd);\n }\n\n // Determine the amount of scrolling that will happen.\n this._dragScrollAmount = this._getMouseEventScrollAmount(event);\n\n // If the cursor was above or below the viewport, make sure it's at the\n // start or end of the viewport respectively.\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd[0] = this._terminal.cols;\n } else if (this._dragScrollAmount < 0) {\n this._model.selectionEnd[0] = 0;\n }\n\n // If the character is a wide character include the cell to the right in the\n // selection. Note that selections at the very end of the line will never\n // have a character.\n if (this._model.selectionEnd[1] < this._buffer.lines.length) {\n const char = this._buffer.lines.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]];\n if (char && char[CHAR_DATA_WIDTH_INDEX] === 0) {\n this._model.selectionEnd[0]++;\n }\n }\n\n // Only draw here if the selection changes.\n if (!previousSelectionEnd ||\n previousSelectionEnd[0] !== this._model.selectionEnd[0] ||\n previousSelectionEnd[1] !== this._model.selectionEnd[1]) {\n this.refresh(true);\n }\n }\n\n /**\n * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the\n * scrolling of the viewport.\n */\n private _dragScroll(): void {\n if (this._dragScrollAmount) {\n this._terminal.scrollLines(this._dragScrollAmount, false);\n // Re-evaluate selection\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows];\n } else {\n this._model.selectionEnd = [0, this._terminal.buffer.ydisp];\n }\n this.refresh();\n }\n }\n\n /**\n * Handles the mouseup event, removing the mousedown listeners.\n * @param event The mouseup event.\n */\n private _onMouseUp(event: MouseEvent): void {\n this._removeMouseDownListeners();\n\n if (this.hasSelection)\n this.emit('selection');\n }\n\n /**\n * Converts a viewport column to the character index on the buffer line, the\n * latter takes into account wide characters.\n * @param coords The coordinates to find the 2 index for.\n */\n private _convertViewportColToCharacterIndex(bufferLine: any, coords: [number, number]): number {\n let charIndex = coords[0];\n for (let i = 0; coords[0] >= i; i++) {\n const char = bufferLine[i];\n if (char[CHAR_DATA_WIDTH_INDEX] === 0) {\n // Wide characters aren't included in the line string so decrement the\n // index so the index is back on the wide character.\n charIndex--;\n } else if (char[CHAR_DATA_CHAR_INDEX].length > 1 && coords[0] !== i) {\n // Emojis take up multiple characters, so adjust accordingly. For these\n // we don't want ot include the character at the column as we're\n // returning the start index in the string, not the end index.\n charIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;\n }\n }\n return charIndex;\n }\n\n public setSelection(col: number, row: number, length: number): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this._model.selectionStart = [col, row];\n this._model.selectionStartLength = length;\n this.refresh();\n }\n\n /**\n * Gets positional information for the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _getWordAt(coords: [number, number]): IWordPosition {\n const bufferLine = this._buffer.lines.get(coords[1]);\n if (!bufferLine) {\n return null;\n }\n\n const line = this._buffer.translateBufferLineToString(coords[1], false);\n\n // Get actual index, taking into consideration wide characters\n let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);\n let endIndex = startIndex;\n\n // Record offset to be used later\n const charOffset = coords[0] - startIndex;\n let leftWideCharCount = 0;\n let rightWideCharCount = 0;\n let leftLongCharOffset = 0;\n let rightLongCharOffset = 0;\n\n if (line.charAt(startIndex) === ' ') {\n // Expand until non-whitespace is hit\n while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') {\n startIndex--;\n }\n while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') {\n endIndex++;\n }\n } else {\n // Expand until whitespace is hit. This algorithm works by scanning left\n // and right from the starting position, keeping both the index format\n // (line) and the column format (bufferLine) in sync. When a wide\n // character is hit, it is recorded and the column index is adjusted.\n let startCol = coords[0];\n let endCol = coords[0];\n\n // Consider the initial position, skip it and increment the wide char\n // variable\n if (bufferLine[startCol][CHAR_DATA_WIDTH_INDEX] === 0) {\n leftWideCharCount++;\n startCol--;\n }\n if (bufferLine[endCol][CHAR_DATA_WIDTH_INDEX] === 2) {\n rightWideCharCount++;\n endCol++;\n }\n\n // Adjust the end index for characters whose length are > 1 (emojis)\n if (bufferLine[endCol][CHAR_DATA_CHAR_INDEX].length > 1) {\n rightLongCharOffset += bufferLine[endCol][CHAR_DATA_CHAR_INDEX].length - 1;\n endIndex += bufferLine[endCol][CHAR_DATA_CHAR_INDEX].length - 1;\n }\n\n // Expand the string in both directions until a space is hit\n while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine[startCol - 1])) {\n const char = bufferLine[startCol - 1];\n if (char[CHAR_DATA_WIDTH_INDEX] === 0) {\n // If the next character is a wide char, record it and skip the column\n leftWideCharCount++;\n startCol--;\n } else if (char[CHAR_DATA_CHAR_INDEX].length > 1) {\n // If the next character's string is longer than 1 char (eg. emoji),\n // adjust the index\n leftLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1;\n startIndex -= char[CHAR_DATA_CHAR_INDEX].length - 1;\n }\n startIndex--;\n startCol--;\n }\n while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine[endCol + 1])) {\n const char = bufferLine[endCol + 1];\n if (char[CHAR_DATA_WIDTH_INDEX] === 2) {\n // If the next character is a wide char, record it and skip the column\n rightWideCharCount++;\n endCol++;\n } else if (char[CHAR_DATA_CHAR_INDEX].length > 1) {\n // If the next character's string is longer than 1 char (eg. emoji),\n // adjust the index\n rightLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1;\n endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;\n }\n endIndex++;\n endCol++;\n }\n }\n\n // Incremenet the end index so it is at the start of the next character\n endIndex++;\n\n // Calculate the start _column_, converting the the string indexes back to\n // column coordinates.\n const start =\n startIndex // The index of the selection's start char in the line string\n + charOffset // The difference between the initial char's column and index\n - leftWideCharCount // The number of wide chars left of the initial char\n + leftLongCharOffset; // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)\n\n // Calculate the length in _columns_, converting the the string indexes back\n // to column coordinates.\n const length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols\n endIndex // The index of the selection's end char in the line string\n - startIndex // The index of the selection's start char in the line string\n + leftWideCharCount // The number of wide chars left of the initial char\n + rightWideCharCount // The number of wide chars right of the initial char (inclusive)\n - leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)\n - rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)\n\n return { start, length };\n }\n\n /**\n * Selects the word at the coordinates specified.\n * @param coords The coordinates to get the word at.\n */\n protected _selectWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionStart = [wordPosition.start, coords[1]];\n this._model.selectionStartLength = wordPosition.length;\n }\n }\n\n /**\n * Sets the selection end to the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _selectToWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];\n }\n }\n\n /**\n * Gets whether the character is considered a word separator by the select\n * word logic.\n * @param char The character to check.\n */\n private _isCharWordSeparator(charData: CharData): boolean {\n // Zero width characters are never separators as they are always to the\n // right of wide characters\n if (charData[CHAR_DATA_WIDTH_INDEX] === 0) {\n return false;\n }\n return WORD_SEPARATORS.indexOf(charData[CHAR_DATA_CHAR_INDEX]) >= 0;\n }\n\n /**\n * Selects the line specified.\n * @param line The line index.\n */\n protected _selectLineAt(line: number): void {\n this._model.selectionStart = [0, line];\n this._model.selectionStartLength = this._terminal.cols;\n }\n}\n","/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n */\n\nimport { C0 } from './EscapeSequences';\nimport { IInputHandler } from './Interfaces';\nimport { CHARSETS, DEFAULT_CHARSET } from './Charsets';\n\nconst normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};\nnormalStateHandler[C0.BEL] = (parser, handler) => handler.bell();\nnormalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed();\nnormalStateHandler[C0.VT] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.FF] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn();\nnormalStateHandler[C0.BS] = (parser, handler) => handler.backspace();\nnormalStateHandler[C0.HT] = (parser, handler) => handler.tab();\nnormalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut();\nnormalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn();\nnormalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED);\n\n// TODO: Remove terminal when parser owns params and currentParam\nconst escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {};\nescapedStateHandler['['] = (parser, terminal) => {\n // ESC [ Control Sequence Introducer (CSI is 0x9b)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.CSI_PARAM);\n};\nescapedStateHandler[']'] = (parser, terminal) => {\n // ESC ] Operating System Command (OSC is 0x9d)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.OSC);\n};\nescapedStateHandler['P'] = (parser, terminal) => {\n // ESC P Device Control String (DCS is 0x90)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.DCS);\n};\nescapedStateHandler['_'] = (parser, terminal) => {\n // ESC _ Application Program Command ( APC is 0x9f).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['^'] = (parser, terminal) => {\n // ESC ^ Privacy Message ( PM is 0x9e).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['c'] = (parser, terminal) => {\n // ESC c Full Reset (RIS).\n terminal.reset();\n};\nescapedStateHandler['E'] = (parser, terminal) => {\n // ESC E Next Line ( NEL is 0x85).\n terminal.buffer.x = 0;\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['D'] = (parser, terminal) => {\n // ESC D Index ( IND is 0x84).\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['M'] = (parser, terminal) => {\n // ESC M Reverse Index ( RI is 0x8d).\n terminal.reverseIndex();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['%'] = (parser, terminal) => {\n // ESC % Select default/utf-8 character set.\n // @ = default, G = utf-8\n terminal.setgLevel(0);\n terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)\n parser.setState(ParserState.NORMAL);\n parser.skipNextChar();\n};\nescapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {};\ncsiParamStateHandler['?'] = (parser) => parser.setPrefix('?');\ncsiParamStateHandler['>'] = (parser) => parser.setPrefix('>');\ncsiParamStateHandler['!'] = (parser) => parser.setPrefix('!');\ncsiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10);\ncsiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1);\ncsiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2);\ncsiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3);\ncsiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4);\ncsiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5);\ncsiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6);\ncsiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7);\ncsiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8);\ncsiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9);\ncsiParamStateHandler['$'] = (parser) => parser.setPostfix('$');\ncsiParamStateHandler['\"'] = (parser) => parser.setPostfix('\"');\ncsiParamStateHandler[' '] = (parser) => parser.setPostfix(' ');\ncsiParamStateHandler['\\''] = (parser) => parser.setPostfix('\\'');\ncsiParamStateHandler[';'] = (parser) => parser.finalizeParam();\ncsiParamStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string, parser: Parser) => void} = {};\ncsiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params);\ncsiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params);\ncsiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params);\ncsiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params);\ncsiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params);\ncsiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params);\ncsiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params);\ncsiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params);\ncsiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params);\ncsiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params);\ncsiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params);\ncsiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params);\ncsiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params);\ncsiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params);\ncsiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params);\ncsiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params);\ncsiStateHandler['T'] = (handler, params, prefix) => {\n if (params.length < 2 && !prefix) {\n handler.scrollDown(params);\n }\n};\ncsiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);\ncsiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);\ncsiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);\ncsiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);\ncsiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);\ncsiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);\ncsiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);\ncsiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);\ncsiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);\ncsiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);\ncsiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);\ncsiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);\ncsiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params);\ncsiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params);\ncsiStateHandler['p'] = (handler, params, prefix) => {\n switch (prefix) {\n case '!': handler.softReset(params); break;\n }\n};\ncsiStateHandler['q'] = (handler, params, prefix, postfix) => {\n if (postfix === ' ') {\n handler.setCursorStyle(params);\n }\n};\ncsiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params);\ncsiStateHandler['s'] = (handler, params) => handler.saveCursor(params);\ncsiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);\ncsiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);\n\nexport enum ParserState {\n NORMAL = 0,\n ESCAPED = 1,\n CSI_PARAM = 2,\n CSI = 3,\n OSC = 4,\n CHARSET = 5,\n DCS = 6,\n IGNORE = 7\n}\n\n/**\n * The terminal's parser, all input into the terminal goes through the parser\n * which parses and defers the actual input handling the the IInputHandler\n * specified in the constructor.\n */\nexport class Parser {\n private _state: ParserState;\n private _position: number;\n\n // TODO: Remove terminal when handler can do everything\n constructor(\n private _inputHandler: IInputHandler,\n private _terminal: any\n ) {\n this._state = ParserState.NORMAL;\n }\n\n /**\n * Parse and handle data.\n *\n * @param data The data to parse.\n */\n public parse(data: string): ParserState {\n const l = data.length;\n let j;\n let cs;\n let ch;\n let code;\n let low;\n\n const cursorStartX = this._terminal.buffer.x;\n const cursorStartY = this._terminal.buffer.y;\n\n if (this._terminal.debug) {\n this._terminal.log('data: ' + data);\n }\n\n this._position = 0;\n // apply leftover surrogate high from last write\n if (this._terminal.surrogate_high) {\n data = this._terminal.surrogate_high + data;\n this._terminal.surrogate_high = '';\n }\n\n for (; this._position < l; this._position++) {\n ch = data[this._position];\n\n // FIXME: higher chars than 0xa0 are not allowed in escape sequences\n // --> maybe move to default\n code = data.charCodeAt(this._position);\n if (0xD800 <= code && code <= 0xDBFF) {\n // we got a surrogate high\n // get surrogate low (next 2 bytes)\n low = data.charCodeAt(this._position + 1);\n if (isNaN(low)) {\n // end of data stream, save surrogate high\n this._terminal.surrogate_high = ch;\n continue;\n }\n code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;\n ch += data.charAt(this._position + 1);\n }\n // surrogate low - already handled above\n if (0xDC00 <= code && code <= 0xDFFF)\n continue;\n\n switch (this._state) {\n case ParserState.NORMAL:\n if (ch in normalStateHandler) {\n normalStateHandler[ch](this, this._inputHandler);\n } else {\n this._inputHandler.addChar(ch, code);\n }\n break;\n case ParserState.ESCAPED:\n if (ch in escapedStateHandler) {\n escapedStateHandler[ch](this, this._terminal);\n // Skip switch as it was just handled\n break;\n }\n switch (ch) {\n\n // ESC (,),*,+,-,. Designate G0-G2 Character Set.\n case '(': // <-- this seems to get all the attention\n case ')':\n case '*':\n case '+':\n case '-':\n case '.':\n switch (ch) {\n case '(':\n this._terminal.gcharset = 0;\n break;\n case ')':\n this._terminal.gcharset = 1;\n break;\n case '*':\n this._terminal.gcharset = 2;\n break;\n case '+':\n this._terminal.gcharset = 3;\n break;\n case '-':\n this._terminal.gcharset = 1;\n break;\n case '.':\n this._terminal.gcharset = 2;\n break;\n }\n this._state = ParserState.CHARSET;\n break;\n\n // Designate G3 Character Set (VT300).\n // A = ISO Latin-1 Supplemental.\n // Not implemented.\n case '/':\n this._terminal.gcharset = 3;\n this._state = ParserState.CHARSET;\n this._position--;\n break;\n\n // ESC N\n // Single Shift Select of G2 Character Set\n // ( SS2 is 0x8e). This affects next character only.\n case 'N':\n break;\n // ESC O\n // Single Shift Select of G3 Character Set\n // ( SS3 is 0x8f). This affects next character only.\n case 'O':\n break;\n // ESC n\n // Invoke the G2 Character Set as GL (LS2).\n case 'n':\n this._terminal.setgLevel(2);\n break;\n // ESC o\n // Invoke the G3 Character Set as GL (LS3).\n case 'o':\n this._terminal.setgLevel(3);\n break;\n // ESC |\n // Invoke the G3 Character Set as GR (LS3R).\n case '|':\n this._terminal.setgLevel(3);\n break;\n // ESC }\n // Invoke the G2 Character Set as GR (LS2R).\n case '}':\n this._terminal.setgLevel(2);\n break;\n // ESC ~\n // Invoke the G1 Character Set as GR (LS1R).\n case '~':\n this._terminal.setgLevel(1);\n break;\n\n // ESC 7 Save Cursor (DECSC).\n case '7':\n this._inputHandler.saveCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC 8 Restore Cursor (DECRC).\n case '8':\n this._inputHandler.restoreCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC # 3 DEC line height/width\n case '#':\n this._state = ParserState.NORMAL;\n this._position++;\n break;\n\n // ESC H Tab Set (HTS is 0x88).\n case 'H':\n this._terminal.tabSet();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC = Application Keypad (DECKPAM).\n case '=':\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n if (this._terminal.viewport) {\n this._terminal.viewport.syncScrollArea();\n }\n this._state = ParserState.NORMAL;\n break;\n\n // ESC > Normal Keypad (DECKPNM).\n case '>':\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n if (this._terminal.viewport) {\n this._terminal.viewport.syncScrollArea();\n }\n this._state = ParserState.NORMAL;\n break;\n\n default:\n this._state = ParserState.NORMAL;\n this._terminal.error('Unknown ESC control: %s.', ch);\n break;\n }\n break;\n\n case ParserState.CHARSET:\n if (ch in CHARSETS) {\n cs = CHARSETS[ch];\n if (ch === '/') { // ISOLatin is actually /A\n this.skipNextChar();\n }\n } else {\n cs = DEFAULT_CHARSET;\n }\n this._terminal.setgCharset(this._terminal.gcharset, cs);\n this._terminal.gcharset = null;\n this._state = ParserState.NORMAL;\n break;\n\n case ParserState.OSC:\n // OSC Ps ; Pt ST\n // OSC Ps ; Pt BEL\n // Set Text Parameters.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n\n this._terminal.params.push(this._terminal.currentParam);\n\n switch (this._terminal.params[0]) {\n case 0:\n case 1:\n case 2:\n if (this._terminal.params[1]) {\n this._terminal.title = this._terminal.params[1];\n this._terminal.handleTitle(this._terminal.title);\n }\n break;\n case 3:\n // set X property\n break;\n case 4:\n case 5:\n // change dynamic colors\n break;\n case 10:\n case 11:\n case 12:\n case 13:\n case 14:\n case 15:\n case 16:\n case 17:\n case 18:\n case 19:\n // change dynamic ui colors\n break;\n case 46:\n // change log file\n break;\n case 50:\n // dynamic font\n break;\n case 51:\n // emacs shell\n break;\n case 52:\n // manipulate selection data\n break;\n case 104:\n case 105:\n case 110:\n case 111:\n case 112:\n case 113:\n case 114:\n case 115:\n case 116:\n case 117:\n case 118:\n // reset colors\n break;\n }\n\n this._terminal.params = [];\n this._terminal.currentParam = 0;\n this._state = ParserState.NORMAL;\n } else {\n if (!this._terminal.params.length) {\n if (ch >= '0' && ch <= '9') {\n this._terminal.currentParam =\n this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;\n } else if (ch === ';') {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = '';\n }\n } else {\n this._terminal.currentParam += ch;\n }\n }\n break;\n\n case ParserState.CSI_PARAM:\n if (ch in csiParamStateHandler) {\n csiParamStateHandler[ch](this);\n break;\n }\n this.finalizeParam();\n // Fall through the CSI as this character should be the CSI code.\n this._state = ParserState.CSI;\n\n case ParserState.CSI:\n if (ch in csiStateHandler) {\n if (this._terminal.debug) {\n this._terminal.log(`CSI ${this._terminal.prefix ? this._terminal.prefix : ''} ${this._terminal.params ? this._terminal.params.join(';') : ''} ${this._terminal.postfix ? this._terminal.postfix : ''} ${ch}`);\n }\n csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);\n } else {\n this._terminal.error('Unknown CSI code: %s.', ch);\n }\n\n this._state = ParserState.NORMAL;\n this._terminal.prefix = '';\n this._terminal.postfix = '';\n break;\n\n case ParserState.DCS:\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n let pt;\n let valid: boolean;\n\n switch (this._terminal.prefix) {\n // User-Defined Keys (DECUDK).\n case '':\n break;\n\n // Request Status String (DECRQSS).\n // test: echo -e '\\eP$q\"p\\e\\\\'\n case '$q':\n pt = this._terminal.currentParam;\n valid = false;\n\n switch (pt) {\n // DECSCA\n case '\"q':\n pt = '0\"q';\n break;\n\n // DECSCL\n case '\"p':\n pt = '61\"p';\n break;\n\n // DECSTBM\n case 'r':\n pt = ''\n + (this._terminal.buffer.scrollTop + 1)\n + ';'\n + (this._terminal.buffer.scrollBottom + 1)\n + 'r';\n break;\n\n // SGR\n case 'm':\n pt = '0m';\n break;\n\n default:\n this._terminal.error('Unknown DCS Pt: %s.', pt);\n pt = '';\n break;\n }\n\n this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\\\');\n break;\n\n // Set Termcap/Terminfo Data (xterm, experimental).\n case '+p':\n break;\n\n // Request Termcap/Terminfo String (xterm, experimental)\n // Regular xterm does not even respond to this sequence.\n // This can cause a small glitch in vim.\n // test: echo -ne '\\eP+q6b64\\e\\\\'\n case '+q':\n pt = this._terminal.currentParam;\n valid = false;\n\n this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\\\');\n break;\n\n default:\n this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);\n break;\n }\n\n this._terminal.currentParam = 0;\n this._terminal.prefix = '';\n this._state = ParserState.NORMAL;\n } else if (!this._terminal.currentParam) {\n if (!this._terminal.prefix && ch !== '$' && ch !== '+') {\n this._terminal.currentParam = ch;\n } else if (this._terminal.prefix.length === 2) {\n this._terminal.currentParam = ch;\n } else {\n this._terminal.prefix += ch;\n }\n } else {\n this._terminal.currentParam += ch;\n }\n break;\n\n case ParserState.IGNORE:\n // For PM and APC.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n this._state = ParserState.NORMAL;\n }\n break;\n }\n }\n\n // Fire the cursormove event if it's moved. This is done inside the parser\n // as a render cannot happen in the middle of a parsing round.\n if (this._terminal.buffer.x !== cursorStartX || this._terminal.buffer.y !== cursorStartY) {\n this._terminal.emit('cursormove');\n }\n\n return this._state;\n }\n\n /**\n * Set the parser's current parsing state.\n *\n * @param state The new state.\n */\n public setState(state: ParserState): void {\n this._state = state;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'\n * or '!'.\n *\n * @param prefix The prefix.\n */\n public setPrefix(prefix: string): void {\n this._terminal.prefix = prefix;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have postfixes of '$',\n * '\"', ' ', '\\''.\n *\n * @param postfix The postfix.\n */\n public setPostfix(postfix: string): void {\n this._terminal.postfix = postfix;\n }\n\n /**\n * Sets the parser's current parameter.\n *\n * @param param the parameter.\n */\n public setParam(param: number): void {\n this._terminal.currentParam = param;\n }\n\n /**\n * Gets the parser's current parameter.\n */\n public getParam(): number {\n return this._terminal.currentParam;\n }\n\n /**\n * Finalizes the parser's current parameter, adding it to the list of\n * parameters and setting the new current parameter to 0.\n */\n public finalizeParam(): void {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = 0;\n }\n\n /**\n * Tell the parser to skip the next character.\n */\n public skipNextChar(): void {\n this._position++;\n }\n\n /**\n * Tell the parser to repeat parsing the current character (for example if it\n * needs parsing using a different state.\n */\n // public repeatChar(): void {\n // this._position--;\n // }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ILinkMatcherOptions, ITerminal, IBufferAccessor, ILinkifier, IElementAccessor } from './Interfaces';\nimport { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback, LineData, LinkHoverEvent, LinkHoverEventTypes } from './Types';\nimport { IMouseZoneManager } from './input/Interfaces';\nimport { MouseZone } from './input/MouseZoneManager';\nimport { EventEmitter } from './EventEmitter';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~]*)*';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\n/**\n * The ID of the built in http(s) link matcher.\n */\nconst HYPERTEXT_LINK_MATCHER_ID = 0;\n\n/**\n * The Linkifier applies links to rows shortly after they have been refreshed.\n */\nexport class Linkifier extends EventEmitter implements ILinkifier {\n /**\n * The time to wait after a row is changed before it is linkified. This prevents\n * the costly operation of searching every row multiple times, potentially a\n * huge amount of times.\n */\n protected static TIME_BEFORE_LINKIFY = 200;\n\n protected _linkMatchers: LinkMatcher[] = [];\n\n private _mouseZoneManager: IMouseZoneManager;\n private _rowsTimeoutId: number;\n private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;\n private _rowsToLinkify: {start: number, end: number};\n\n constructor(\n protected _terminal: IBufferAccessor & IElementAccessor\n ) {\n super();\n this._rowsToLinkify = {\n start: null,\n end: null\n };\n this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });\n }\n\n /**\n * Attaches the linkifier to the DOM, enabling linkification.\n * @param mouseZoneManager The mouse zone manager to register link zones with.\n */\n public attachToDom(mouseZoneManager: IMouseZoneManager): void {\n this._mouseZoneManager = mouseZoneManager;\n }\n\n /**\n * Queue linkification on a set of rows.\n * @param start The row to linkify from (inclusive).\n * @param end The row to linkify to (inclusive).\n */\n public linkifyRows(start: number, end: number): void {\n // Don't attempt linkify if not yet attached to DOM\n if (!this._mouseZoneManager) {\n return;\n }\n\n // Increase range to linkify\n if (!this._rowsToLinkify.start) {\n this._rowsToLinkify.start = start;\n this._rowsToLinkify.end = end;\n } else {\n this._rowsToLinkify.start = this._rowsToLinkify.start < start ? this._rowsToLinkify.start : start;\n this._rowsToLinkify.end = this._rowsToLinkify.end > end ? this._rowsToLinkify.end : end;\n }\n\n // Clear out any existing links on this row range\n this._mouseZoneManager.clearAll(start, end);\n\n // Restart timer\n if (this._rowsTimeoutId) {\n clearTimeout(this._rowsTimeoutId);\n }\n this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier.TIME_BEFORE_LINKIFY);\n }\n\n /**\n * Linkifies the rows requested.\n */\n private _linkifyRows(): void {\n this._rowsTimeoutId = null;\n for (let i = this._rowsToLinkify.start; i <= this._rowsToLinkify.end; i++) {\n this._linkifyRow(i);\n }\n this._rowsToLinkify.start = null;\n this._rowsToLinkify.end = null;\n }\n\n /**\n * Attaches a handler for hypertext links, overriding default behavior for\n * tandard http(s) links.\n * @param handler The handler to use, this can be cleared with null.\n */\n public setHypertextLinkHandler(handler: LinkMatcherHandler): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;\n }\n\n /**\n * Attaches a validation callback for hypertext links.\n * @param callback The callback to use, this can be cleared with null.\n */\n public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;\n }\n\n /**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param regex The regular expression to search for. Specifically, this\n * searches the textContent of the rows. You will want to use \\s to match a\n * space ' ' character for example.\n * @param handler The callback when the link is called.\n * @param options Options for the link matcher.\n * @return The ID of the new matcher, this can be used to deregister.\n */\n public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number {\n if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {\n throw new Error('handler must be defined');\n }\n const matcher: LinkMatcher = {\n id: this._nextLinkMatcherId++,\n regex,\n handler,\n matchIndex: options.matchIndex,\n validationCallback: options.validationCallback,\n hoverTooltipCallback: options.tooltipCallback,\n hoverLeaveCallback: options.leaveCallback,\n priority: options.priority || 0\n };\n this._addLinkMatcherToList(matcher);\n return matcher.id;\n }\n\n /**\n * Inserts a link matcher to the list in the correct position based on the\n * priority of each link matcher. New link matchers of equal priority are\n * considered after older link matchers.\n * @param matcher The link matcher to be added.\n */\n private _addLinkMatcherToList(matcher: LinkMatcher): void {\n if (this._linkMatchers.length === 0) {\n this._linkMatchers.push(matcher);\n return;\n }\n\n for (let i = this._linkMatchers.length - 1; i >= 0; i--) {\n if (matcher.priority <= this._linkMatchers[i].priority) {\n this._linkMatchers.splice(i + 1, 0, matcher);\n return;\n }\n }\n\n this._linkMatchers.splice(0, 0, matcher);\n }\n\n /**\n * Deregisters a link matcher if it has been registered.\n * @param matcherId The link matcher's ID (returned after register)\n * @return Whether a link matcher was found and deregistered.\n */\n public deregisterLinkMatcher(matcherId: number): boolean {\n // ID 0 is the hypertext link matcher which cannot be deregistered\n for (let i = 1; i < this._linkMatchers.length; i++) {\n if (this._linkMatchers[i].id === matcherId) {\n this._linkMatchers.splice(i, 1);\n return true;\n }\n }\n return false;\n }\n\n /**\n * Linkifies a row.\n * @param rowIndex The index of the row to linkify.\n */\n private _linkifyRow(rowIndex: number): void {\n const absoluteRowIndex = this._terminal.buffer.ydisp + rowIndex;\n if (absoluteRowIndex >= this._terminal.buffer.lines.length) {\n return;\n }\n const text = this._terminal.buffer.translateBufferLineToString(absoluteRowIndex, false);\n for (let i = 0; i < this._linkMatchers.length; i++) {\n this._doLinkifyRow(rowIndex, text, this._linkMatchers[i]);\n }\n }\n\n /**\n * Linkifies a row given a specific handler.\n * @param rowIndex The row index to linkify.\n * @param text The text of the row (excludes text in the row that's already\n * linkified).\n * @param matcher The link matcher for this line.\n * @param offset The how much of the row has already been linkified.\n * @return The link element(s) that were added.\n */\n private _doLinkifyRow(rowIndex: number, text: string, matcher: LinkMatcher, offset: number = 0): void {\n // Iterate over nodes as we want to consider text nodes\n let result = [];\n const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;\n\n // Find the first match\n let match = text.match(matcher.regex);\n if (!match || match.length === 0) {\n return;\n }\n let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n\n // Get index, match.index is for the outer match which includes negated chars\n const index = text.indexOf(uri);\n\n // Ensure the link is valid before registering\n if (matcher.validationCallback) {\n matcher.validationCallback(uri, isValid => {\n // Discard link if the line has already changed\n if (this._rowsTimeoutId) {\n return;\n }\n if (isValid) {\n this._addLink(offset + index, rowIndex, uri, matcher);\n }\n });\n } else {\n this._addLink(offset + index, rowIndex, uri, matcher);\n }\n\n // Recursively check for links in the rest of the text\n const remainingStartIndex = index + uri.length;\n const remainingText = text.substr(remainingStartIndex);\n if (remainingText.length > 0) {\n this._doLinkifyRow(rowIndex, remainingText, matcher, offset + remainingStartIndex);\n }\n }\n\n /**\n * Registers a link to the mouse zone manager.\n * @param x The column the link starts.\n * @param y The row the link is on.\n * @param uri The URI of the link.\n * @param matcher The link matcher for the link.\n */\n private _addLink(x: number, y: number, uri: string, matcher: LinkMatcher): void {\n this._mouseZoneManager.add(new MouseZone(\n x + 1,\n x + 1 + uri.length,\n y + 1,\n e => {\n if (matcher.handler) {\n return matcher.handler(e, uri);\n }\n window.open(uri, '_blank');\n },\n e => {\n this.emit(LinkHoverEventTypes.HOVER, { x, y, length: uri.length});\n this._terminal.element.style.cursor = 'pointer';\n },\n e => {\n this.emit(LinkHoverEventTypes.TOOLTIP, { x, y, length: uri.length});\n if (matcher.hoverTooltipCallback) {\n matcher.hoverTooltipCallback(e, uri);\n }\n },\n () => {\n this.emit(LinkHoverEventTypes.LEAVE, { x, y, length: uri.length});\n this._terminal.element.style.cursor = '';\n if (matcher.hoverLeaveCallback) {\n matcher.hoverLeaveCallback();\n }\n }\n ));\n }\n}\n","/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n */\n\nimport { IInputHandler, ITerminal, IInputHandlingTerminal } from './Interfaces';\nimport { C0 } from './EscapeSequences';\nimport { DEFAULT_CHARSET } from './Charsets';\nimport { CharData } from './Types';\nimport { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';\nimport { FLAGS } from './renderer/Types';\nimport { wcwidth } from './CharWidth';\n\n/**\n * The terminal's standard implementation of IInputHandler, this handles all\n * input from the Parser.\n *\n * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand\n * each function's header comment.\n */\nexport class InputHandler implements IInputHandler {\n constructor(private _terminal: IInputHandlingTerminal) { }\n\n public addChar(char: string, code: number): void {\n if (char >= ' ') {\n // calculate print space\n // expensive call, therefore we save width in line buffer\n const ch_width = wcwidth(code);\n\n if (this._terminal.charset && this._terminal.charset[char]) {\n char = this._terminal.charset[char];\n }\n\n let row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert combining char in last cell\n // FIXME: needs handling after cursor jumps\n if (!ch_width && this._terminal.buffer.x) {\n // dont overflow left\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {\n if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) {\n // found empty cell after fullwidth, need to go 2 cells back\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char;\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][3] = char.charCodeAt(0);\n }\n } else {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char;\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][3] = char.charCodeAt(0);\n }\n this._terminal.updateRange(this._terminal.buffer.y);\n }\n return;\n }\n\n // goto next line if ch would overflow\n // TODO: needs a global min terminal width of 2\n if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) {\n // autowrap - DECAWM\n if (this._terminal.wraparoundMode) {\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll(true);\n } else {\n // The line already exists (eg. the initial viewport), mark it as a\n // wrapped line\n (this._terminal.buffer.lines.get(this._terminal.buffer.y)).isWrapped = true;\n }\n } else {\n if (ch_width === 2) // FIXME: check for xterm behavior\n return;\n }\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert mode: move characters to right\n if (this._terminal.insertMode) {\n // do this twice for a fullwidth char\n for (let moves = 0; moves < ch_width; ++moves) {\n // remove last cell, if it's width is 0\n // we have to adjust the second last cell as well\n const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();\n if (removed[CHAR_DATA_WIDTH_INDEX] === 0\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2]\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)];\n }\n\n // insert empty cell at cursor\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]);\n }\n }\n\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width, char.charCodeAt(0)];\n this._terminal.buffer.x++;\n this._terminal.updateRange(this._terminal.buffer.y);\n\n // fullwidth char - set next cell width to zero and advance cursor\n if (ch_width === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0, undefined];\n this._terminal.buffer.x++;\n }\n }\n }\n\n /**\n * BEL\n * Bell (Ctrl-G).\n */\n public bell(): void {\n this._terminal.bell();\n }\n\n /**\n * LF\n * Line Feed or New Line (NL). (LF is Ctrl-J).\n */\n public lineFeed(): void {\n if (this._terminal.convertEol) {\n this._terminal.buffer.x = 0;\n }\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n /**\n * This event is emitted whenever the terminal outputs a LF or NL.\n *\n * @event lineFeed\n */\n this._terminal.emit('lineFeed');\n }\n\n /**\n * CR\n * Carriage Return (Ctrl-M).\n */\n public carriageReturn(): void {\n this._terminal.buffer.x = 0;\n }\n\n /**\n * BS\n * Backspace (Ctrl-H).\n */\n public backspace(): void {\n if (this._terminal.buffer.x > 0) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * TAB\n * Horizontal Tab (HT) (Ctrl-I).\n */\n public tab(): void {\n this._terminal.buffer.x = this._terminal.buffer.nextStop();\n }\n\n /**\n * SO\n * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the\n * G1 character set.\n */\n public shiftOut(): void {\n this._terminal.setgLevel(1);\n }\n\n /**\n * SI\n * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0\n * character set (the default).\n */\n public shiftIn(): void {\n this._terminal.setgLevel(0);\n }\n\n /**\n * CSI Ps @\n * Insert Ps (Blank) Character(s) (default = 1) (ICH).\n */\n public insertChars(params: number[]): void {\n let param = params[0];\n if (param < 1) param = 1;\n\n const row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n let j = this._terminal.buffer.x;\n const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row).splice(j++, 0, ch);\n this._terminal.buffer.lines.get(row).pop();\n }\n }\n\n /**\n * CSI Ps A\n * Cursor Up Ps Times (default = 1) (CUU).\n */\n public cursorUp(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n }\n\n /**\n * CSI Ps B\n * Cursor Down Ps Times (default = 1) (CUD).\n */\n public cursorDown(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps C\n * Cursor Forward Ps Times (default = 1) (CUF).\n */\n public cursorForward(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps D\n * Cursor Backward Ps Times (default = 1) (CUB).\n */\n public cursorBackward(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n this._terminal.buffer.x -= param;\n if (this._terminal.buffer.x < 0) {\n this._terminal.buffer.x = 0;\n }\n }\n\n /**\n * CSI Ps E\n * Cursor Next Line Ps Times (default = 1) (CNL).\n * same as CSI Ps B ?\n */\n public cursorNextLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps F\n * Cursor Preceding Line Ps Times (default = 1) (CNL).\n * reuse CSI Ps A ?\n */\n public cursorPrecedingLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps G\n * Cursor Character Absolute [column] (default = [row,1]) (CHA).\n */\n public cursorCharAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n }\n\n /**\n * CSI Ps ; Ps H\n * Cursor Position [row;column] (default = [1,1]) (CUP).\n */\n public cursorPosition(params: number[]): void {\n let col: number;\n let row: number = params[0] - 1;\n\n if (params.length >= 2) {\n col = params[1] - 1;\n } else {\n col = 0;\n }\n\n if (row < 0) {\n row = 0;\n } else if (row >= this._terminal.rows) {\n row = this._terminal.rows - 1;\n }\n\n if (col < 0) {\n col = 0;\n } else if (col >= this._terminal.cols) {\n col = this._terminal.cols - 1;\n }\n\n this._terminal.buffer.x = col;\n this._terminal.buffer.y = row;\n }\n\n /**\n * CSI Ps I\n * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).\n */\n public cursorForwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.buffer.nextStop();\n }\n }\n\n /**\n * CSI Ps J Erase in Display (ED).\n * Ps = 0 -> Erase Below (default).\n * Ps = 1 -> Erase Above.\n * Ps = 2 -> Erase All.\n * Ps = 3 -> Erase Saved Lines (xterm).\n * CSI ? Ps J\n * Erase in Display (DECSED).\n * Ps = 0 -> Selective Erase Below (default).\n * Ps = 1 -> Selective Erase Above.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInDisplay(params: number[]): void {\n let j;\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y + 1;\n for (; j < this._terminal.rows; j++) {\n this._terminal.eraseLine(j);\n }\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y;\n while (j--) {\n this._terminal.eraseLine(j);\n }\n break;\n case 2:\n j = this._terminal.rows;\n while (j--) this._terminal.eraseLine(j);\n break;\n case 3:\n // Clear scrollback (everything not in viewport)\n const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;\n if (scrollBackSize > 0) {\n this._terminal.buffer.lines.trimStart(scrollBackSize);\n this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);\n this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);\n // Force a scroll event to refresh viewport\n this._terminal.emit('scroll', 0);\n }\n break;\n }\n }\n\n /**\n * CSI Ps K Erase in Line (EL).\n * Ps = 0 -> Erase to Right (default).\n * Ps = 1 -> Erase to Left.\n * Ps = 2 -> Erase All.\n * CSI ? Ps K\n * Erase in Line (DECSEL).\n * Ps = 0 -> Selective Erase to Right (default).\n * Ps = 1 -> Selective Erase to Left.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInLine(params: number[]): void {\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 2:\n this._terminal.eraseLine(this._terminal.buffer.y);\n break;\n }\n }\n\n /**\n * CSI Ps L\n * Insert Ps Line(s) (default = 1) (IL).\n */\n public insertLines(params: number[]): void {\n let param: number = params[0];\n if (param < 1) {\n param = 1;\n }\n let row: number = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n let scrollBottomRowsOffset = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n let scrollBottomAbsolute = this._terminal.rows - 1 + this._terminal.buffer.ybase - scrollBottomRowsOffset + 1;\n while (param--) {\n // test: echo -e '\\e[44m\\e[1L\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(scrollBottomAbsolute - 1, 1);\n this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true));\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps M\n * Delete Ps Line(s) (default = 1) (DL).\n */\n public deleteLines(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n const row: number = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n let j: number;\n j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;\n while (param--) {\n // test: echo -e '\\e[44m\\e[1M\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(row, 1);\n this._terminal.buffer.lines.splice(j, 0, this._terminal.blankLine(true));\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps P\n * Delete Ps Character(s) (default = 1) (DCH).\n */\n public deleteChars(params: number[]): void {\n let param: number = params[0];\n if (param < 1) {\n param = 1;\n }\n\n const row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm\n\n while (param--) {\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);\n this._terminal.buffer.lines.get(row).push(ch);\n }\n this._terminal.updateRange(this._terminal.buffer.y);\n }\n\n /**\n * CSI Ps S Scroll up Ps lines (default = 1) (SU).\n */\n public scrollUp(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps T Scroll down Ps lines (default = 1) (SD).\n */\n public scrollDown(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps X\n * Erase Ps Character(s) (default = 1) (ECH).\n */\n public eraseChars(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n const row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n let j = this._terminal.buffer.x;\n const ch: CharData = [this._terminal.eraseAttr(), ' ', 1, 32]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row)[j++] = ch;\n }\n }\n\n /**\n * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).\n */\n public cursorBackwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.buffer.prevStop();\n }\n }\n\n /**\n * CSI Pm ` Character Position Absolute\n * [column] (default = [row,1]) (HPA).\n */\n public charPosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Pm a Character Position Relative\n * [columns] (default = [row,col+1]) (HPR)\n * reuse CSI Ps C ?\n */\n public HPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps b Repeat the preceding graphic character Ps times (REP).\n */\n public repeatPrecedingCharacter(params: number[]): void {\n let param = params[0] || 1;\n const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y);\n const ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1, 32];\n\n while (param--) {\n line[this._terminal.buffer.x++] = ch;\n }\n }\n\n /**\n * CSI Ps c Send Device Attributes (Primary DA).\n * Ps = 0 or omitted -> request attributes from terminal. The\n * response depends on the decTerminalID resource setting.\n * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'')\n * -> CSI ? 1 ; 0 c (``VT101 with No Options'')\n * -> CSI ? 6 c (``VT102'')\n * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'')\n * The VT100-style response parameters do not mean anything by\n * themselves. VT220 parameters do, telling the host what fea-\n * tures the terminal supports:\n * Ps = 1 -> 132-columns.\n * Ps = 2 -> Printer.\n * Ps = 6 -> Selective erase.\n * Ps = 8 -> User-defined keys.\n * Ps = 9 -> National replacement character sets.\n * Ps = 1 5 -> Technical characters.\n * Ps = 2 2 -> ANSI color, e.g., VT525.\n * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode).\n * CSI > Ps c\n * Send Device Attributes (Secondary DA).\n * Ps = 0 or omitted -> request the terminal's identification\n * code. The response depends on the decTerminalID resource set-\n * ting. It should apply only to VT220 and up, but xterm extends\n * this to VT100.\n * -> CSI > Pp ; Pv ; Pc c\n * where Pp denotes the terminal type\n * Pp = 0 -> ``VT100''.\n * Pp = 1 -> ``VT220''.\n * and Pv is the firmware version (for xterm, this was originally\n * the XFree86 patch number, starting with 95). In a DEC termi-\n * nal, Pc indicates the ROM cartridge registration number and is\n * always zero.\n * More information:\n * xterm/charproc.c - line 2012, for more information.\n * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)\n */\n public sendDeviceAttributes(params: number[]): void {\n if (params[0] > 0) {\n return;\n }\n\n if (!this._terminal.prefix) {\n if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[?1;2c');\n } else if (this._terminal.is('linux')) {\n this._terminal.send(C0.ESC + '[?6c');\n }\n } else if (this._terminal.prefix === '>') {\n // xterm and urxvt\n // seem to spit this\n // out around ~370 times (?).\n if (this._terminal.is('xterm')) {\n this._terminal.send(C0.ESC + '[>0;276;0c');\n } else if (this._terminal.is('rxvt-unicode')) {\n this._terminal.send(C0.ESC + '[>85;95;0c');\n } else if (this._terminal.is('linux')) {\n // not supported by linux console.\n // linux console echoes parameters.\n this._terminal.send(params[0] + 'c');\n } else if (this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[>83;40003;0c');\n }\n }\n }\n\n /**\n * CSI Pm d Vertical Position Absolute (VPA)\n * [row] (default = [1,column])\n */\n public linePosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y = param - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n }\n\n /**\n * CSI Pm e Vertical Position Relative (VPR)\n * [rows] (default = [row+1,column])\n * reuse CSI Ps B ?\n */\n public VPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps ; Ps f\n * Horizontal and Vertical Position [row;column] (default =\n * [1,1]) (HVP).\n */\n public HVPosition(params: number[]): void {\n if (params[0] < 1) params[0] = 1;\n if (params[1] < 1) params[1] = 1;\n\n this._terminal.buffer.y = params[0] - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n\n this._terminal.buffer.x = params[1] - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps g Tab Clear (TBC).\n * Ps = 0 -> Clear Current Column (default).\n * Ps = 3 -> Clear All.\n * Potentially:\n * Ps = 2 -> Clear Stops on Line.\n * http://vt100.net/annarbor/aaa-ug/section6.html\n */\n public tabClear(params: number[]): void {\n let param = params[0];\n if (param <= 0) {\n delete this._terminal.buffer.tabs[this._terminal.buffer.x];\n } else if (param === 3) {\n this._terminal.buffer.tabs = {};\n }\n }\n\n /**\n * CSI Pm h Set Mode (SM).\n * Ps = 2 -> Keyboard Action Mode (AM).\n * Ps = 4 -> Insert Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Automatic Newline (LNM).\n * CSI ? Pm h\n * DEC Private Mode Set (DECSET).\n * Ps = 1 -> Application Cursor Keys (DECCKM).\n * Ps = 2 -> Designate USASCII for character sets G0-G3\n * (DECANM), and set VT100 mode.\n * Ps = 3 -> 132 Column Mode (DECCOLM).\n * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM).\n * Ps = 5 -> Reverse Video (DECSCNM).\n * Ps = 6 -> Origin Mode (DECOM).\n * Ps = 7 -> Wraparound Mode (DECAWM).\n * Ps = 8 -> Auto-repeat Keys (DECARM).\n * Ps = 9 -> Send Mouse X & Y on button press. See the sec-\n * tion Mouse Tracking.\n * Ps = 1 0 -> Show toolbar (rxvt).\n * Ps = 1 2 -> Start Blinking Cursor (att610).\n * Ps = 1 8 -> Print form feed (DECPFF).\n * Ps = 1 9 -> Set print extent to full screen (DECPEX).\n * Ps = 2 5 -> Show Cursor (DECTCEM).\n * Ps = 3 0 -> Show scrollbar (rxvt).\n * Ps = 3 5 -> Enable font-shifting functions (rxvt).\n * Ps = 3 8 -> Enter Tektronix Mode (DECTEK).\n * Ps = 4 0 -> Allow 80 -> 132 Mode.\n * Ps = 4 1 -> more(1) fix (see curses resource).\n * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN-\n * RCM).\n * Ps = 4 4 -> Turn On Margin Bell.\n * Ps = 4 5 -> Reverse-wraparound Mode.\n * Ps = 4 6 -> Start Logging. This is normally disabled by a\n * compile-time option.\n * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 6 6 -> Application keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends backspace (DECBKM).\n * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Enable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt).\n * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Interpret \"meta\" key, sets eighth bit.\n * (enables the eightBitInput resource).\n * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num-\n * Lock keys. (This enables the numLock resource).\n * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This\n * enables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete\n * key.\n * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This\n * enables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Keep selection even if not highlighted.\n * (This enables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Enable Urgency window manager hint when\n * Control-G is received. (This enables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Enable raising of the window when Control-G\n * is received. (enables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate\n * Screen Buffer, clearing it first. (This may be disabled by\n * the titeInhibit resource). This combines the effects of the 1\n * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based\n * applications rather than the 4 7 mode.\n * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Set Sun function-key mode.\n * Ps = 1 0 5 2 -> Set HP function-key mode.\n * Ps = 1 0 5 3 -> Set SCO function-key mode.\n * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Set VT220 keyboard emulation.\n * Ps = 2 0 0 4 -> Set bracketed paste mode.\n * Modes:\n * http: *vt100.net/docs/vt220-rm/chapter4.html\n */\n public setMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.setMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = true;\n break;\n case 20:\n // this._t.convertEol = true;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = true;\n break;\n case 2:\n this._terminal.setgCharset(0, DEFAULT_CHARSET);\n this._terminal.setgCharset(1, DEFAULT_CHARSET);\n this._terminal.setgCharset(2, DEFAULT_CHARSET);\n this._terminal.setgCharset(3, DEFAULT_CHARSET);\n // set VT100 mode here\n break;\n case 3: // 132 col mode\n this._terminal.savedCols = this._terminal.cols;\n this._terminal.resize(132, this._terminal.rows);\n break;\n case 6:\n this._terminal.originMode = true;\n break;\n case 7:\n this._terminal.wraparoundMode = true;\n break;\n case 12:\n // this.cursorBlink = true;\n break;\n case 66:\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n // no release, no motion, no wheel, no modifiers.\n case 1000: // vt200 mouse\n // no motion.\n // no modifiers, except control on the wheel.\n case 1002: // button event mouse\n case 1003: // any event mouse\n // any event - sends motion events,\n // even if there is no button held down.\n\n // TODO: Why are params[0] compares nested within a switch for params[0]?\n\n this._terminal.x10Mouse = params[0] === 9;\n this._terminal.vt200Mouse = params[0] === 1000;\n this._terminal.normalMouse = params[0] > 1000;\n this._terminal.mouseEvents = true;\n this._terminal.element.classList.add('enable-mouse-events');\n this._terminal.selectionManager.disable();\n this._terminal.log('Binding to mouse events.');\n break;\n case 1004: // send focusin/focusout events\n // focusin: ^[[I\n // focusout: ^[[O\n this._terminal.sendFocus = true;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = true;\n // for wide terminals\n // simply encodes large values as utf8 characters\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = true;\n // for wide terminals\n // does not add 32 to fields\n // press: ^[[ Keyboard Action Mode (AM).\n * Ps = 4 -> Replace Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Normal Linefeed (LNM).\n * CSI ? Pm l\n * DEC Private Mode Reset (DECRST).\n * Ps = 1 -> Normal Cursor Keys (DECCKM).\n * Ps = 2 -> Designate VT52 mode (DECANM).\n * Ps = 3 -> 80 Column Mode (DECCOLM).\n * Ps = 4 -> Jump (Fast) Scroll (DECSCLM).\n * Ps = 5 -> Normal Video (DECSCNM).\n * Ps = 6 -> Normal Cursor Mode (DECOM).\n * Ps = 7 -> No Wraparound Mode (DECAWM).\n * Ps = 8 -> No Auto-repeat Keys (DECARM).\n * Ps = 9 -> Don't send Mouse X & Y on button press.\n * Ps = 1 0 -> Hide toolbar (rxvt).\n * Ps = 1 2 -> Stop Blinking Cursor (att610).\n * Ps = 1 8 -> Don't print form feed (DECPFF).\n * Ps = 1 9 -> Limit print to scrolling region (DECPEX).\n * Ps = 2 5 -> Hide Cursor (DECTCEM).\n * Ps = 3 0 -> Don't show scrollbar (rxvt).\n * Ps = 3 5 -> Disable font-shifting functions (rxvt).\n * Ps = 4 0 -> Disallow 80 -> 132 Mode.\n * Ps = 4 1 -> No more(1) fix (see curses resource).\n * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC-\n * NRCM).\n * Ps = 4 4 -> Turn Off Margin Bell.\n * Ps = 4 5 -> No Reverse-wraparound Mode.\n * Ps = 4 6 -> Stop Logging. (This is normally disabled by a\n * compile-time option).\n * Ps = 4 7 -> Use Normal Screen Buffer.\n * Ps = 6 6 -> Numeric keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends delete (DECBKM).\n * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Disable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output\n * (rxvt).\n * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Don't interpret \"meta\" key. (This disables\n * the eightBitInput resource).\n * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num-\n * Lock keys. (This disables the numLock resource).\n * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key.\n * (This disables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad\n * Delete key.\n * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key.\n * (This disables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Do not keep selection when not highlighted.\n * (This disables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Disable Urgency window manager hint when\n * Control-G is received. (This disables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Disable raising of the window when Control-\n * G is received. (This disables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen\n * first if in the Alternate Screen. (This may be disabled by\n * the titeInhibit resource).\n * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor\n * as in DECRC. (This may be disabled by the titeInhibit\n * resource). This combines the effects of the 1 0 4 7 and 1 0\n * 4 8 modes. Use this with terminfo-based applications rather\n * than the 4 7 mode.\n * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Reset Sun function-key mode.\n * Ps = 1 0 5 2 -> Reset HP function-key mode.\n * Ps = 1 0 5 3 -> Reset SCO function-key mode.\n * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.\n * Ps = 2 0 0 4 -> Reset bracketed paste mode.\n */\n public resetMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.resetMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = false;\n break;\n case 20:\n // this._t.convertEol = false;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = false;\n break;\n case 3:\n if (this._terminal.cols === 132 && this._terminal.savedCols) {\n this._terminal.resize(this._terminal.savedCols, this._terminal.rows);\n }\n delete this._terminal.savedCols;\n break;\n case 6:\n this._terminal.originMode = false;\n break;\n case 7:\n this._terminal.wraparoundMode = false;\n break;\n case 12:\n // this.cursorBlink = false;\n break;\n case 66:\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n case 1000: // vt200 mouse\n case 1002: // button event mouse\n case 1003: // any event mouse\n this._terminal.x10Mouse = false;\n this._terminal.vt200Mouse = false;\n this._terminal.normalMouse = false;\n this._terminal.mouseEvents = false;\n this._terminal.element.classList.remove('enable-mouse-events');\n this._terminal.selectionManager.enable();\n break;\n case 1004: // send focusin/focusout events\n this._terminal.sendFocus = false;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = false;\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = false;\n break;\n case 1015: // urxvt ext mode mouse\n this._terminal.urxvtMouse = false;\n break;\n case 25: // hide cursor\n this._terminal.cursorHidden = true;\n break;\n case 1049: // alt screen buffer cursor\n // FALL-THROUGH\n case 47: // normal screen buffer\n case 1047: // normal screen buffer - clearing it first\n // Ensure the selection manager has the correct buffer\n this._terminal.buffers.activateNormalBuffer();\n // TODO: Not sure if we need to save/restore after switching the buffer\n // if (params[0] === 1049) {\n // this.restoreCursor(params);\n // }\n this._terminal.selectionManager.setBuffer(this._terminal.buffer);\n this._terminal.refresh(0, this._terminal.rows - 1);\n this._terminal.viewport.syncScrollArea();\n this._terminal.showCursor();\n break;\n case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)\n this._terminal.bracketedPasteMode = false;\n break;\n }\n }\n }\n\n /**\n * CSI Pm m Character Attributes (SGR).\n * Ps = 0 -> Normal (default).\n * Ps = 1 -> Bold.\n * Ps = 2 -> Faint, decreased intensity (ISO 6429).\n * Ps = 4 -> Underlined.\n * Ps = 5 -> Blink (appears as Bold).\n * Ps = 7 -> Inverse.\n * Ps = 8 -> Invisible, i.e., hidden (VT300).\n * Ps = 2 2 -> Normal (neither bold nor faint).\n * Ps = 2 4 -> Not underlined.\n * Ps = 2 5 -> Steady (not blinking).\n * Ps = 2 7 -> Positive (not inverse).\n * Ps = 2 8 -> Visible, i.e., not hidden (VT300).\n * Ps = 3 0 -> Set foreground color to Black.\n * Ps = 3 1 -> Set foreground color to Red.\n * Ps = 3 2 -> Set foreground color to Green.\n * Ps = 3 3 -> Set foreground color to Yellow.\n * Ps = 3 4 -> Set foreground color to Blue.\n * Ps = 3 5 -> Set foreground color to Magenta.\n * Ps = 3 6 -> Set foreground color to Cyan.\n * Ps = 3 7 -> Set foreground color to White.\n * Ps = 3 9 -> Set foreground color to default (original).\n * Ps = 4 0 -> Set background color to Black.\n * Ps = 4 1 -> Set background color to Red.\n * Ps = 4 2 -> Set background color to Green.\n * Ps = 4 3 -> Set background color to Yellow.\n * Ps = 4 4 -> Set background color to Blue.\n * Ps = 4 5 -> Set background color to Magenta.\n * Ps = 4 6 -> Set background color to Cyan.\n * Ps = 4 7 -> Set background color to White.\n * Ps = 4 9 -> Set background color to default (original).\n *\n * If 16-color support is compiled, the following apply. Assume\n * that xterm's resources are set so that the ISO color codes are\n * the first 8 of a set of 16. Then the aixterm colors are the\n * bright versions of the ISO colors:\n * Ps = 9 0 -> Set foreground color to Black.\n * Ps = 9 1 -> Set foreground color to Red.\n * Ps = 9 2 -> Set foreground color to Green.\n * Ps = 9 3 -> Set foreground color to Yellow.\n * Ps = 9 4 -> Set foreground color to Blue.\n * Ps = 9 5 -> Set foreground color to Magenta.\n * Ps = 9 6 -> Set foreground color to Cyan.\n * Ps = 9 7 -> Set foreground color to White.\n * Ps = 1 0 0 -> Set background color to Black.\n * Ps = 1 0 1 -> Set background color to Red.\n * Ps = 1 0 2 -> Set background color to Green.\n * Ps = 1 0 3 -> Set background color to Yellow.\n * Ps = 1 0 4 -> Set background color to Blue.\n * Ps = 1 0 5 -> Set background color to Magenta.\n * Ps = 1 0 6 -> Set background color to Cyan.\n * Ps = 1 0 7 -> Set background color to White.\n *\n * If xterm is compiled with the 16-color support disabled, it\n * supports the following, from rxvt:\n * Ps = 1 0 0 -> Set foreground and background color to\n * default.\n *\n * If 88- or 256-color support is compiled, the following apply.\n * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second\n * Ps.\n * Ps = 4 8 ; 5 ; Ps -> Set background color to the second\n * Ps.\n */\n public charAttributes(params: number[]): void {\n // Optimize a single SGR0.\n if (params.length === 1 && params[0] === 0) {\n this._terminal.curAttr = this._terminal.defAttr;\n return;\n }\n\n const l = params.length;\n let flags = this._terminal.curAttr >> 18;\n let fg = (this._terminal.curAttr >> 9) & 0x1ff;\n let bg = this._terminal.curAttr & 0x1ff;\n let p;\n\n for (let i = 0; i < l; i++) {\n p = params[i];\n if (p >= 30 && p <= 37) {\n // fg color 8\n fg = p - 30;\n } else if (p >= 40 && p <= 47) {\n // bg color 8\n bg = p - 40;\n } else if (p >= 90 && p <= 97) {\n // fg color 16\n p += 8;\n fg = p - 90;\n } else if (p >= 100 && p <= 107) {\n // bg color 16\n p += 8;\n bg = p - 100;\n } else if (p === 0) {\n // default\n flags = this._terminal.defAttr >> 18;\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n // flags = 0;\n // fg = 0x1ff;\n // bg = 0x1ff;\n } else if (p === 1) {\n // bold text\n flags |= FLAGS.BOLD;\n } else if (p === 4) {\n // underlined text\n flags |= FLAGS.UNDERLINE;\n } else if (p === 5) {\n // blink\n flags |= FLAGS.BLINK;\n } else if (p === 7) {\n // inverse and positive\n // test with: echo -e '\\e[31m\\e[42mhello\\e[7mworld\\e[27mhi\\e[m'\n flags |= FLAGS.INVERSE;\n } else if (p === 8) {\n // invisible\n flags |= FLAGS.INVISIBLE;\n } else if (p === 2) {\n // dimmed text\n flags |= FLAGS.DIM;\n } else if (p === 22) {\n // not bold nor faint\n flags &= ~FLAGS.BOLD;\n flags &= ~FLAGS.DIM;\n } else if (p === 24) {\n // not underlined\n flags &= ~FLAGS.UNDERLINE;\n } else if (p === 25) {\n // not blink\n flags &= ~FLAGS.BLINK;\n } else if (p === 27) {\n // not inverse\n flags &= ~FLAGS.INVERSE;\n } else if (p === 28) {\n // not invisible\n flags &= ~FLAGS.INVISIBLE;\n } else if (p === 39) {\n // reset fg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n } else if (p === 49) {\n // reset bg\n bg = this._terminal.defAttr & 0x1ff;\n } else if (p === 38) {\n // fg color 256\n if (params[i + 1] === 2) {\n i += 2;\n fg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (fg === -1) fg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n fg = p;\n }\n } else if (p === 48) {\n // bg color 256\n if (params[i + 1] === 2) {\n i += 2;\n bg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (bg === -1) bg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n bg = p;\n }\n } else if (p === 100) {\n // reset fg/bg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n } else {\n this._terminal.error('Unknown SGR attribute: %d.', p);\n }\n }\n\n this._terminal.curAttr = (flags << 18) | (fg << 9) | bg;\n }\n\n /**\n * CSI Ps n Device Status Report (DSR).\n * Ps = 5 -> Status Report. Result (``OK'') is\n * CSI 0 n\n * Ps = 6 -> Report Cursor Position (CPR) [row;column].\n * Result is\n * CSI r ; c R\n * CSI ? Ps n\n * Device Status Report (DSR, DEC-specific).\n * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI\n * ? r ; c R (assumes page is zero).\n * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).\n * or CSI ? 1 1 n (not ready).\n * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)\n * or CSI ? 2 1 n (locked).\n * Ps = 2 6 -> Report Keyboard status as\n * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).\n * The last two parameters apply to VT400 & up, and denote key-\n * board ready and LK01 respectively.\n * Ps = 5 3 -> Report Locator status as\n * CSI ? 5 3 n Locator available, if compiled-in, or\n * CSI ? 5 0 n No Locator, if not.\n */\n public deviceStatus(params: number[]): void {\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 5:\n // status report\n this._terminal.send(C0.ESC + '[0n');\n break;\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '['\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n }\n } else if (this._terminal.prefix === '?') {\n // modern xterm doesnt seem to\n // respond to any of these except ?6, 6, and 5\n switch (params[0]) {\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '[?'\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n case 15:\n // no printer\n // this.send(C0.ESC + '[?11n');\n break;\n case 25:\n // dont support user defined keys\n // this.send(C0.ESC + '[?21n');\n break;\n case 26:\n // north american keyboard\n // this.send(C0.ESC + '[?27;1;0;0n');\n break;\n case 53:\n // no dec locator/mouse\n // this.send(C0.ESC + '[?50n');\n break;\n }\n }\n }\n\n /**\n * CSI ! p Soft terminal reset (DECSTR).\n * http://vt100.net/docs/vt220-rm/table4-10.html\n */\n public softReset(params: number[]): void {\n this._terminal.cursorHidden = false;\n this._terminal.insertMode = false;\n this._terminal.originMode = false;\n this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n this._terminal.applicationKeypad = false; // ?\n this._terminal.viewport.syncScrollArea();\n this._terminal.applicationCursor = false;\n this._terminal.buffer.scrollTop = 0;\n this._terminal.buffer.scrollBottom = this._terminal.rows - 1;\n this._terminal.curAttr = this._terminal.defAttr;\n this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?\n this._terminal.charset = null;\n this._terminal.glevel = 0; // ??\n this._terminal.charsets = [null]; // ??\n }\n\n /**\n * CSI Ps SP q Set cursor style (DECSCUSR, VT520).\n * Ps = 0 -> blinking block.\n * Ps = 1 -> blinking block (default).\n * Ps = 2 -> steady block.\n * Ps = 3 -> blinking underline.\n * Ps = 4 -> steady underline.\n * Ps = 5 -> blinking bar (xterm).\n * Ps = 6 -> steady bar (xterm).\n */\n public setCursorStyle(params?: number[]): void {\n const param = params[0] < 1 ? 1 : params[0];\n switch (param) {\n case 1:\n case 2:\n this._terminal.setOption('cursorStyle', 'block');\n break;\n case 3:\n case 4:\n this._terminal.setOption('cursorStyle', 'underline');\n break;\n case 5:\n case 6:\n this._terminal.setOption('cursorStyle', 'bar');\n break;\n }\n const isBlinking = param % 2 === 1;\n this._terminal.setOption('cursorBlink', isBlinking);\n }\n\n /**\n * CSI Ps ; Ps r\n * Set Scrolling Region [top;bottom] (default = full size of win-\n * dow) (DECSTBM).\n * CSI ? Pm r\n */\n public setScrollRegion(params: number[]): void {\n if (this._terminal.prefix) return;\n this._terminal.buffer.scrollTop = (params[0] || 1) - 1;\n this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y = 0;\n }\n\n\n /**\n * CSI s\n * Save cursor (ANSI.SYS).\n */\n public saveCursor(params: number[]): void {\n this._terminal.buffer.savedX = this._terminal.buffer.x;\n this._terminal.buffer.savedY = this._terminal.buffer.y;\n }\n\n\n /**\n * CSI u\n * Restore cursor (ANSI.SYS).\n */\n public restoreCursor(params: number[]): void {\n this._terminal.buffer.x = this._terminal.buffer.savedX || 0;\n this._terminal.buffer.y = this._terminal.buffer.savedY || 0;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IEventEmitter, IListenerType } from './Interfaces';\n\nexport class EventEmitter implements IEventEmitter {\n private _events: {[type: string]: IListenerType[]};\n\n constructor() {\n // Restore the previous events if available, this will happen if the\n // constructor is called multiple times on the same object (terminal reset).\n this._events = this._events || {};\n }\n\n public on(type: string, listener: IListenerType): void {\n this._events[type] = this._events[type] || [];\n this._events[type].push(listener);\n }\n\n public off(type: string, listener: IListenerType): void {\n if (!this._events[type]) {\n return;\n }\n\n let obj = this._events[type];\n let i = obj.length;\n\n while (i--) {\n if (obj[i] === listener || obj[i].listener === listener) {\n obj.splice(i, 1);\n return;\n }\n }\n }\n\n public removeAllListeners(type: string): void {\n if (this._events[type]) {\n delete this._events[type];\n }\n }\n\n public once(type: string, listener: IListenerType): void {\n function on(): void {\n let args = Array.prototype.slice.call(arguments);\n this.off(type, on);\n listener.apply(this, args);\n }\n (on).listener = listener;\n this.on(type, on);\n }\n\n public emit(type: string, ...args: any[]): void {\n if (!this._events[type]) {\n return;\n }\n let obj = this._events[type];\n for (let i = 0; i < obj.length; i++) {\n obj[i].apply(this, args);\n }\n }\n\n public listeners(type: string): IListenerType[] {\n return this._events[type] || [];\n }\n\n protected destroy(): void {\n this._events = {};\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/**\n * C0 control codes\n * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes\n */\nexport namespace C0 {\n /** Null (Caret = ^@, C = \\0) */\n export const NUL = '\\x00';\n /** Start of Heading (Caret = ^A) */\n export const SOH = '\\x01';\n /** Start of Text (Caret = ^B) */\n export const STX = '\\x02';\n /** End of Text (Caret = ^C) */\n export const ETX = '\\x03';\n /** End of Transmission (Caret = ^D) */\n export const EOT = '\\x04';\n /** Enquiry (Caret = ^E) */\n export const ENQ = '\\x05';\n /** Acknowledge (Caret = ^F) */\n export const ACK = '\\x06';\n /** Bell (Caret = ^G, C = \\a) */\n export const BEL = '\\x07';\n /** Backspace (Caret = ^H, C = \\b) */\n export const BS = '\\x08';\n /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \\t) */\n export const HT = '\\x09';\n /** Line Feed (Caret = ^J, C = \\n) */\n export const LF = '\\x0a';\n /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \\v) */\n export const VT = '\\x0b';\n /** Form Feed (Caret = ^L, C = \\f) */\n export const FF = '\\x0c';\n /** Carriage Return (Caret = ^M, C = \\r) */\n export const CR = '\\x0d';\n /** Shift Out (Caret = ^N) */\n export const SO = '\\x0e';\n /** Shift In (Caret = ^O) */\n export const SI = '\\x0f';\n /** Data Link Escape (Caret = ^P) */\n export const DLE = '\\x10';\n /** Device Control One (XON) (Caret = ^Q) */\n export const DC1 = '\\x11';\n /** Device Control Two (Caret = ^R) */\n export const DC2 = '\\x12';\n /** Device Control Three (XOFF) (Caret = ^S) */\n export const DC3 = '\\x13';\n /** Device Control Four (Caret = ^T) */\n export const DC4 = '\\x14';\n /** Negative Acknowledge (Caret = ^U) */\n export const NAK = '\\x15';\n /** Synchronous Idle (Caret = ^V) */\n export const SYN = '\\x16';\n /** End of Transmission Block (Caret = ^W) */\n export const ETB = '\\x17';\n /** Cancel (Caret = ^X) */\n export const CAN = '\\x18';\n /** End of Medium (Caret = ^Y) */\n export const EM = '\\x19';\n /** Substitute (Caret = ^Z) */\n export const SUB = '\\x1a';\n /** Escape (Caret = ^[, C = \\e) */\n export const ESC = '\\x1b';\n /** File Separator (Caret = ^\\) */\n export const FS = '\\x1c';\n /** Group Separator (Caret = ^]) */\n export const GS = '\\x1d';\n /** Record Separator (Caret = ^^) */\n export const RS = '\\x1e';\n /** Unit Separator (Caret = ^_) */\n export const US = '\\x1f';\n /** Space */\n export const SP = '\\x20';\n /** Delete (Caret = ^?) */\n export const DEL = '\\x7f';\n};\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\ninterface IPosition {\n start: number;\n end: number;\n}\n\n/**\n * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend\n * events, displaying the in-progress composition to the UI and forwarding the final composition\n * to the handler.\n */\nexport class CompositionHelper {\n /**\n * Whether input composition is currently happening, eg. via a mobile keyboard, speech input or\n * IME. This variable determines whether the compositionText should be displayed on the UI.\n */\n private isComposing: boolean;\n\n /**\n * The position within the input textarea's value of the current composition.\n */\n private compositionPosition: IPosition;\n\n /**\n * Whether a composition is in the process of being sent, setting this to false will cancel any\n * in-progress composition.\n */\n private isSendingComposition: boolean;\n\n /**\n * Creates a new CompositionHelper.\n * @param textarea The textarea that xterm uses for input.\n * @param compositionView The element to display the in-progress composition in.\n * @param terminal The Terminal to forward the finished composition to.\n */\n constructor(\n private textarea: HTMLTextAreaElement,\n private compositionView: HTMLElement,\n private terminal: ITerminal\n ) {\n this.isComposing = false;\n this.isSendingComposition = false;\n this.compositionPosition = { start: null, end: null };\n }\n\n /**\n * Handles the compositionstart event, activating the composition view.\n */\n public compositionstart(): void {\n this.isComposing = true;\n this.compositionPosition.start = this.textarea.value.length;\n this.compositionView.textContent = '';\n this.compositionView.classList.add('active');\n }\n\n /**\n * Handles the compositionupdate event, updating the composition view.\n * @param {CompositionEvent} ev The event.\n */\n public compositionupdate(ev: CompositionEvent): void {\n this.compositionView.textContent = ev.data;\n this.updateCompositionElements();\n setTimeout(() => {\n this.compositionPosition.end = this.textarea.value.length;\n }, 0);\n }\n\n /**\n * Handles the compositionend event, hiding the composition view and sending the composition to\n * the handler.\n */\n public compositionend(): void {\n this.finalizeComposition(true);\n }\n\n /**\n * Handles the keydown event, routing any necessary events to the CompositionHelper functions.\n * @param ev The keydown event.\n * @return Whether the Terminal should continue processing the keydown event.\n */\n public keydown(ev: KeyboardEvent): boolean {\n if (this.isComposing || this.isSendingComposition) {\n if (ev.keyCode === 229) {\n // Continue composing if the keyCode is the \"composition character\"\n return false;\n } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {\n // Continue composing if the keyCode is a modifier key\n return false;\n } else {\n // Finish composition immediately. This is mainly here for the case where enter is\n // pressed and the handler needs to be triggered before the command is executed.\n this.finalizeComposition(false);\n }\n }\n\n if (ev.keyCode === 229) {\n // If the \"composition character\" is used but gets to this point it means a non-composition\n // character (eg. numbers and punctuation) was pressed when the IME was active.\n this.handleAnyTextareaChanges();\n return false;\n }\n\n return true;\n }\n\n /**\n * Finalizes the composition, resuming regular input actions. This is called when a composition\n * is ending.\n * @param waitForPropogation Whether to wait for events to propogate before sending\n * the input. This should be false if a non-composition keystroke is entered before the\n * compositionend event is triggered, such as enter, so that the composition is send before\n * the command is executed.\n */\n private finalizeComposition(waitForPropogation: boolean): void {\n this.compositionView.classList.remove('active');\n this.isComposing = false;\n this.clearTextareaPosition();\n\n if (!waitForPropogation) {\n // Cancel any delayed composition send requests and send the input immediately.\n this.isSendingComposition = false;\n const input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);\n this.terminal.handler(input);\n } else {\n // Make a deep copy of the composition position here as a new compositionstart event may\n // fire before the setTimeout executes.\n const currentCompositionPosition = {\n start: this.compositionPosition.start,\n end: this.compositionPosition.end,\n };\n\n // Since composition* events happen before the changes take place in the textarea on most\n // browsers, use a setTimeout with 0ms time to allow the native compositionend event to\n // complete. This ensures the correct character is retrieved, this solution was used\n // because:\n // - The compositionend event's data property is unreliable, at least on Chromium\n // - The last compositionupdate event's data property does not always accurately describe\n // the character, a counter example being Korean where an ending consonsant can move to\n // the following character if the following input is a vowel.\n this.isSendingComposition = true;\n setTimeout(() => {\n // Ensure that the input has not already been sent\n if (this.isSendingComposition) {\n this.isSendingComposition = false;\n let input;\n if (this.isComposing) {\n // Use the end position to get the string if a new composition has started.\n input = this.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);\n } else {\n // Don't use the end position here in order to pick up any characters after the\n // composition has finished, for example when typing a non-composition character\n // (eg. 2) after a composition character.\n input = this.textarea.value.substring(currentCompositionPosition.start);\n }\n this.terminal.handler(input);\n }\n }, 0);\n }\n }\n\n /**\n * Apply any changes made to the textarea after the current event chain is allowed to complete.\n * This should be called when not currently composing but a keydown event with the \"composition\n * character\" (229) is triggered, in order to allow non-composition text to be entered when an\n * IME is active.\n */\n private handleAnyTextareaChanges(): void {\n const oldValue = this.textarea.value;\n setTimeout(() => {\n // Ignore if a composition has started since the timeout\n if (!this.isComposing) {\n const newValue = this.textarea.value;\n const diff = newValue.replace(oldValue, '');\n if (diff.length > 0) {\n this.terminal.handler(diff);\n }\n }\n }, 0);\n }\n\n /**\n * Positions the composition view on top of the cursor and the textarea just below it (so the\n * IME helper dialog is positioned correctly).\n * @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is\n * necessary as the IME events across browsers are not consistently triggered.\n */\n public updateCompositionElements(dontRecurse?: boolean): void {\n if (!this.isComposing) {\n return;\n }\n\n if (this.terminal.buffer.isCursorInViewport) {\n const cellHeight = Math.ceil(this.terminal.charMeasure.height * this.terminal.options.lineHeight);\n const cursorTop = this.terminal.buffer.y * cellHeight;\n const cursorLeft = this.terminal.buffer.x * this.terminal.charMeasure.width;\n\n this.compositionView.style.left = cursorLeft + 'px';\n this.compositionView.style.top = cursorTop + 'px';\n this.compositionView.style.height = cellHeight + 'px';\n this.compositionView.style.lineHeight = cellHeight + 'px';\n // Sync the textarea to the exact position of the composition view so the IME knows where the\n // text is.\n const compositionViewBounds = this.compositionView.getBoundingClientRect();\n this.textarea.style.left = cursorLeft + 'px';\n this.textarea.style.top = cursorTop + 'px';\n this.textarea.style.width = compositionViewBounds.width + 'px';\n this.textarea.style.height = compositionViewBounds.height + 'px';\n this.textarea.style.lineHeight = compositionViewBounds.height + 'px';\n }\n\n if (!dontRecurse) {\n setTimeout(() => this.updateCompositionElements(true), 0);\n }\n };\n\n /**\n * Clears the textarea's position so that the cursor does not blink on IE.\n * @private\n */\n private clearTextareaPosition(): void {\n this.textarea.style.left = '';\n this.textarea.style.top = '';\n };\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Charset } from './Types';\n\n/**\n * The character sets supported by the terminal. These enable several languages\n * to be represented within the terminal with only 8-bit encoding. See ISO 2022\n * for a discussion on character sets. Only VT100 character sets are supported.\n */\nexport const CHARSETS: { [key: string]: Charset } = {};\n\n/**\n * The default character set, US.\n */\nexport const DEFAULT_CHARSET: Charset = CHARSETS['B'];\n\n/**\n * DEC Special Character and Line Drawing Set.\n * Reference: http://vt100.net/docs/vt102-ug/table5-13.html\n * A lot of curses apps use this if they see TERM=xterm.\n * testing: echo -e '\\e(0a\\e(B'\n * The xterm output sometimes seems to conflict with the\n * reference above. xterm seems in line with the reference\n * when running vttest however.\n * The table below now uses xterm's output from vttest.\n */\nCHARSETS['0'] = {\n '`': '\\u25c6', // '◆'\n 'a': '\\u2592', // '▒'\n 'b': '\\u0009', // '\\t'\n 'c': '\\u000c', // '\\f'\n 'd': '\\u000d', // '\\r'\n 'e': '\\u000a', // '\\n'\n 'f': '\\u00b0', // '°'\n 'g': '\\u00b1', // '±'\n 'h': '\\u2424', // '\\u2424' (NL)\n 'i': '\\u000b', // '\\v'\n 'j': '\\u2518', // '┘'\n 'k': '\\u2510', // '┐'\n 'l': '\\u250c', // '┌'\n 'm': '\\u2514', // '└'\n 'n': '\\u253c', // '┼'\n 'o': '\\u23ba', // '⎺'\n 'p': '\\u23bb', // '⎻'\n 'q': '\\u2500', // '─'\n 'r': '\\u23bc', // '⎼'\n 's': '\\u23bd', // '⎽'\n 't': '\\u251c', // '├'\n 'u': '\\u2524', // '┤'\n 'v': '\\u2534', // '┴'\n 'w': '\\u252c', // '┬'\n 'x': '\\u2502', // '│'\n 'y': '\\u2264', // '≤'\n 'z': '\\u2265', // '≥'\n '{': '\\u03c0', // 'π'\n '|': '\\u2260', // '≠'\n '}': '\\u00a3', // '£'\n '~': '\\u00b7' // '·'\n};\n\n/**\n * British character set\n * ESC (A\n * Reference: http://vt100.net/docs/vt220-rm/table2-5.html\n */\nCHARSETS['A'] = {\n '#': '£'\n};\n\n/**\n * United States character set\n * ESC (B\n */\nCHARSETS['B'] = null;\n\n/**\n * Dutch character set\n * ESC (4\n * Reference: http://vt100.net/docs/vt220-rm/table2-6.html\n */\nCHARSETS['4'] = {\n '#': '£',\n '@': '¾',\n '[': 'ij',\n '\\\\': '½',\n ']': '|',\n '{': '¨',\n '|': 'f',\n '}': '¼',\n '~': '´'\n};\n\n/**\n * Finnish character set\n * ESC (C or ESC (5\n * Reference: http://vt100.net/docs/vt220-rm/table2-7.html\n */\nCHARSETS['C'] =\nCHARSETS['5'] = {\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * French character set\n * ESC (R\n * Reference: http://vt100.net/docs/vt220-rm/table2-8.html\n */\nCHARSETS['R'] = {\n '#': '£',\n '@': 'à',\n '[': '°',\n '\\\\': 'ç',\n ']': '§',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': '¨'\n};\n\n/**\n * French Canadian character set\n * ESC (Q\n * Reference: http://vt100.net/docs/vt220-rm/table2-9.html\n */\nCHARSETS['Q'] = {\n '@': 'à',\n '[': 'â',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '`': 'ô',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': 'û'\n};\n\n/**\n * German character set\n * ESC (K\n * Reference: http://vt100.net/docs/vt220-rm/table2-10.html\n */\nCHARSETS['K'] = {\n '@': '§',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Ü',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'ß'\n};\n\n/**\n * Italian character set\n * ESC (Y\n * Reference: http://vt100.net/docs/vt220-rm/table2-11.html\n */\nCHARSETS['Y'] = {\n '#': '£',\n '@': '§',\n '[': '°',\n '\\\\': 'ç',\n ']': 'é',\n '`': 'ù',\n '{': 'à',\n '|': 'ò',\n '}': 'è',\n '~': 'ì'\n};\n\n/**\n * Norwegian/Danish character set\n * ESC (E or ESC (6\n * Reference: http://vt100.net/docs/vt220-rm/table2-12.html\n */\nCHARSETS['E'] =\nCHARSETS['6'] = {\n '@': 'Ä',\n '[': 'Æ',\n '\\\\': 'Ø',\n ']': 'Å',\n '^': 'Ü',\n '`': 'ä',\n '{': 'æ',\n '|': 'ø',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Spanish character set\n * ESC (Z\n * Reference: http://vt100.net/docs/vt220-rm/table2-13.html\n */\nCHARSETS['Z'] = {\n '#': '£',\n '@': '§',\n '[': '¡',\n '\\\\': 'Ñ',\n ']': '¿',\n '{': '°',\n '|': 'ñ',\n '}': 'ç'\n};\n\n/**\n * Swedish character set\n * ESC (H or ESC (7\n * Reference: http://vt100.net/docs/vt220-rm/table2-14.html\n */\nCHARSETS['H'] =\nCHARSETS['7'] = {\n '@': 'É',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Swiss character set\n * ESC (=\n * Reference: http://vt100.net/docs/vt220-rm/table2-15.html\n */\nCHARSETS['='] = {\n '#': 'ù',\n '@': 'à',\n '[': 'é',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '_': 'è',\n '`': 'ô',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'û'\n};\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number {\n // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c\n // combining characters\n const COMBINING_BMP = [\n [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],\n [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],\n [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],\n [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],\n [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],\n [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],\n [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],\n [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],\n [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],\n [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],\n [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],\n [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],\n [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],\n [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],\n [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],\n [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],\n [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],\n [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],\n [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],\n [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],\n [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],\n [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],\n [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],\n [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],\n [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],\n [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],\n [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],\n [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],\n [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],\n [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],\n [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],\n [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],\n [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],\n [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],\n [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],\n [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],\n [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],\n [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],\n [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],\n [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],\n [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],\n [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],\n [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],\n ];\n const COMBINING_HIGH = [\n [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],\n [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],\n [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],\n [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],\n [0xE0100, 0xE01EF]\n ];\n // binary search\n function bisearch(ucs: number, data: number[][]): boolean {\n let min = 0;\n let max = data.length - 1;\n let mid;\n if (ucs < data[0][0] || ucs > data[max][1])\n return false;\n while (max >= min) {\n mid = (min + max) >> 1;\n if (ucs > data[mid][1])\n min = mid + 1;\n else if (ucs < data[mid][0])\n max = mid - 1;\n else\n return true;\n }\n return false;\n }\n function wcwidthBMP(ucs: number): number {\n // test for 8-bit control characters\n if (ucs === 0)\n return opts.nul;\n if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))\n return opts.control;\n // binary search in table of non-spacing characters\n if (bisearch(ucs, COMBINING_BMP))\n return 0;\n // if we arrive here, ucs is not a combining or C0/C1 control character\n if (isWideBMP(ucs)) {\n return 2;\n }\n return 1;\n }\n function isWideBMP(ucs: number): boolean {\n return (\n ucs >= 0x1100 && (\n ucs <= 0x115f || // Hangul Jamo init. consonants\n ucs === 0x2329 ||\n ucs === 0x232a ||\n (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi\n (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables\n (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs\n (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms\n (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms\n (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms\n (ucs >= 0xffe0 && ucs <= 0xffe6)));\n }\n function wcwidthHigh(ucs: number): 0 | 1 | 2 {\n if (bisearch(ucs, COMBINING_HIGH))\n return 0;\n if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {\n return 2;\n }\n return 1;\n }\n const control = opts.control | 0;\n let table: number[] | Uint32Array = null;\n function init_table(): number[] | Uint32Array {\n // lookup table for BMP\n const CODEPOINTS = 65536; // BMP holds 65536 codepoints\n const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2\n const ITEMSIZE = 32; // using uint32_t\n const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;\n const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;\n table = (typeof Uint32Array === 'undefined')\n ? new Array(CONTAINERSIZE)\n : new Uint32Array(CONTAINERSIZE);\n for (let i = 0; i < CONTAINERSIZE; ++i) {\n let num = 0;\n let pos = CODEPOINTS_PER_ITEM;\n while (pos--)\n num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);\n table[i] = num;\n }\n return table;\n }\n // get width from lookup table\n // position in container : num / CODEPOINTS_PER_ITEM\n // ==> n = table[Math.floor(num / 16)]\n // ==> n = table[num >> 4]\n // 16 codepoints per number: FFEEDDCCBBAA99887766554433221100\n // position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH\n // ==> m = (n % 16) * 2\n // ==> m = (num & 15) << 1\n // right shift to position m\n // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766\n // we are only interested in 2 LSBs, cut off higher bits\n // ==> n = n & 3 e.g. 000000000000000000000000000000XX\n return function (num: number): number {\n num = num | 0; // get asm.js like optimization under V8\n if (num < 32)\n return control | 0;\n if (num < 127)\n return 1;\n let t = table || init_table();\n if (num < 65536)\n return t[num >> 4] >> ((num & 15) << 1) & 3;\n // do a full search for high codepoints\n return wcwidthHigh(num);\n };\n})({nul: 0, control: 0}); // configurable options\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, IBufferSet } from './Interfaces';\nimport { Buffer } from './Buffer';\nimport { EventEmitter } from './EventEmitter';\n\n/**\n * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and\n * provides also utilities for working with them.\n */\nexport class BufferSet extends EventEmitter implements IBufferSet {\n private _normal: Buffer;\n private _alt: Buffer;\n private _activeBuffer: Buffer;\n\n /**\n * Create a new BufferSet for the given terminal.\n * @param {Terminal} terminal - The terminal the BufferSet will belong to\n */\n constructor(private _terminal: ITerminal) {\n super();\n this._normal = new Buffer(this._terminal, true);\n this._normal.fillViewportRows();\n\n // The alt buffer should never have scrollback.\n // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer\n this._alt = new Buffer(this._terminal, false);\n this._activeBuffer = this._normal;\n\n this.setupTabStops();\n }\n\n /**\n * Returns the alt Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get alt(): Buffer {\n return this._alt;\n }\n\n /**\n * Returns the normal Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get active(): Buffer {\n return this._activeBuffer;\n }\n\n /**\n * Returns the currently active Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get normal(): Buffer {\n return this._normal;\n }\n\n /**\n * Sets the normal Buffer of the BufferSet as its currently active Buffer\n */\n public activateNormalBuffer(): void {\n // The alt buffer should always be cleared when we switch to the normal\n // buffer. This frees up memory since the alt buffer should always be new\n // when activated.\n this._alt.clear();\n\n this._activeBuffer = this._normal;\n this.emit('activate', this._normal);\n }\n\n /**\n * Sets the alt Buffer of the BufferSet as its currently active Buffer\n */\n public activateAltBuffer(): void {\n // Since the alt buffer is always cleared when the normal buffer is\n // activated, we want to fill it when switching to it.\n this._alt.fillViewportRows();\n this._activeBuffer = this._alt;\n this.emit('activate', this._alt);\n }\n\n /**\n * Resizes both normal and alt buffers, adjusting their data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n this._normal.resize(newCols, newRows);\n this._alt.resize(newCols, newRows);\n }\n\n /**\n * Setup the tab stops.\n * @param i The index to start setting up tab stops from.\n */\n public setupTabStops(i?: number): void {\n this._normal.setupTabStops(i);\n this._alt.setupTabStops(i);\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, IBuffer } from './Interfaces';\nimport { CircularList } from './utils/CircularList';\nimport { LineData, CharData } from './Types';\n\nexport const CHAR_DATA_ATTR_INDEX = 0;\nexport const CHAR_DATA_CHAR_INDEX = 1;\nexport const CHAR_DATA_WIDTH_INDEX = 2;\nexport const CHAR_DATA_CODE_INDEX = 3;\nexport const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1\n\n/**\n * This class represents a terminal buffer (an internal state of the terminal), where the\n * following information is stored (in high-level):\n * - text content of this particular buffer\n * - cursor position\n * - scroll position\n */\nexport class Buffer implements IBuffer {\n private _lines: CircularList;\n\n public ydisp: number;\n public ybase: number;\n public y: number;\n public x: number;\n public scrollBottom: number;\n public scrollTop: number;\n public tabs: any;\n public savedY: number;\n public savedX: number;\n\n /**\n * Create a new Buffer.\n * @param _terminal The terminal the Buffer will belong to.\n * @param _hasScrollback Whether the buffer should respect the scrollback of\n * the terminal.\n */\n constructor(\n private _terminal: ITerminal,\n private _hasScrollback: boolean\n ) {\n this.clear();\n }\n\n public get lines(): CircularList {\n return this._lines;\n }\n\n public get hasScrollback(): boolean {\n return this._hasScrollback && this.lines.maxLength > this._terminal.rows;\n }\n\n public get isCursorInViewport(): boolean {\n const absoluteY = this.ybase + this.y;\n const relativeY = absoluteY - this.ydisp;\n return (relativeY >= 0 && relativeY < this._terminal.rows);\n }\n\n /**\n * Gets the correct buffer length based on the rows provided, the terminal's\n * scrollback and whether this buffer is flagged to have scrollback or not.\n * @param rows The terminal rows to use in the calculation.\n */\n private _getCorrectBufferLength(rows: number): number {\n if (!this._hasScrollback) {\n return rows;\n }\n\n const correctBufferLength = rows + this._terminal.options.scrollback;\n\n return correctBufferLength > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : correctBufferLength;\n }\n\n /**\n * Fills the buffer's viewport with blank lines.\n */\n public fillViewportRows(): void {\n if (this._lines.length === 0) {\n let i = this._terminal.rows;\n while (i--) {\n this.lines.push(this._terminal.blankLine());\n }\n }\n }\n\n /**\n * Clears the buffer to it's initial state, discarding all previous data.\n */\n public clear(): void {\n this.ydisp = 0;\n this.ybase = 0;\n this.y = 0;\n this.x = 0;\n this._lines = new CircularList(this._getCorrectBufferLength(this._terminal.rows));\n this.scrollTop = 0;\n this.scrollBottom = this._terminal.rows - 1;\n this.setupTabStops();\n }\n\n /**\n * Resizes the buffer, adjusting its data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n // Increase max length if needed before adjustments to allow space to fill\n // as required.\n const newMaxLength = this._getCorrectBufferLength(newRows);\n if (newMaxLength > this._lines.maxLength) {\n this._lines.maxLength = newMaxLength;\n }\n\n // The following adjustments should only happen if the buffer has been\n // initialized/filled.\n if (this._lines.length > 0) {\n // Deal with columns increasing (we don't do anything when columns reduce)\n if (this._terminal.cols < newCols) {\n const ch: CharData = [this._terminal.defAttr, ' ', 1, 32]; // does xterm use the default attr?\n for (let i = 0; i < this._lines.length; i++) {\n // TODO: This should be removed, with tests setup for the case that was\n // causing the underlying bug, see https://github.com/sourcelair/xterm.js/issues/824\n if (this._lines.get(i) === undefined) {\n this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols));\n }\n while (this._lines.get(i).length < newCols) {\n this._lines.get(i).push(ch);\n }\n }\n }\n\n // Resize rows in both directions as needed\n let addToY = 0;\n if (this._terminal.rows < newRows) {\n for (let y = this._terminal.rows; y < newRows; y++) {\n if (this._lines.length < newRows + this.ybase) {\n if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {\n // There is room above the buffer and there are no empty elements below the line,\n // scroll up\n this.ybase--;\n addToY++;\n if (this.ydisp > 0) {\n // Viewport is at the top of the buffer, must increase downwards\n this.ydisp--;\n }\n } else {\n // Add a blank line if there is no buffer left at the top to scroll to, or if there\n // are blank lines after the cursor\n this._lines.push(this._terminal.blankLine(undefined, undefined, newCols));\n }\n }\n }\n } else { // (this._terminal.rows >= newRows)\n for (let y = this._terminal.rows; y > newRows; y--) {\n if (this._lines.length > newRows + this.ybase) {\n if (this._lines.length > this.ybase + this.y + 1) {\n // The line is a blank line below the cursor, remove it\n this._lines.pop();\n } else {\n // The line is the cursor, scroll down\n this.ybase++;\n this.ydisp++;\n }\n }\n }\n }\n\n // Reduce max length if needed after adjustments, this is done after as it\n // would otherwise cut data from the bottom of the buffer.\n if (newMaxLength < this._lines.maxLength) {\n // Trim from the top of the buffer and adjust ybase and ydisp.\n const amountToTrim = this._lines.length - newMaxLength;\n if (amountToTrim > 0) {\n this._lines.trimStart(amountToTrim);\n this.ybase = Math.max(this.ybase - amountToTrim, 0);\n this.ydisp = Math.max(this.ydisp - amountToTrim, 0);\n }\n this._lines.maxLength = newMaxLength;\n }\n\n // Make sure that the cursor stays on screen\n if (this.y >= newRows) {\n this.y = newRows - 1;\n }\n if (addToY) {\n this.y += addToY;\n }\n\n if (this.x >= newCols) {\n this.x = newCols - 1;\n }\n\n this.scrollTop = 0;\n }\n\n this.scrollBottom = newRows - 1;\n }\n\n /**\n * Translates a buffer line to a string, with optional start and end columns.\n * Wide characters will count as two columns in the resulting string. This\n * function is useful for getting the actual text underneath the raw selection\n * position.\n * @param line The line being translated.\n * @param trimRight Whether to trim whitespace to the right.\n * @param startCol The column to start at.\n * @param endCol The column to end at.\n */\n public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol: number = null): string {\n // Get full line\n let lineString = '';\n const line = this.lines.get(lineIndex);\n if (!line) {\n return '';\n }\n\n // Initialize column and index values. Column values represent the actual\n // cell column, indexes represent the index in the string. Indexes are\n // needed here because some chars are 0 characters long (eg. after wide\n // chars) and some chars are longer than 1 characters long (eg. emojis).\n let startIndex = startCol;\n endCol = endCol || line.length;\n let endIndex = endCol;\n\n for (let i = 0; i < line.length; i++) {\n const char = line[i];\n lineString += char[CHAR_DATA_CHAR_INDEX];\n // Adjust start and end cols for wide characters if they affect their\n // column indexes\n if (char[CHAR_DATA_WIDTH_INDEX] === 0) {\n if (startCol >= i) {\n startIndex--;\n }\n if (endCol >= i) {\n endIndex--;\n }\n } else {\n // Adjust the columns to take glyphs that are represented by multiple\n // code points into account.\n if (char[CHAR_DATA_CHAR_INDEX].length > 1) {\n if (startCol > i) {\n startIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;\n }\n if (endCol > i) {\n endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;\n }\n }\n }\n }\n\n // Calculate the final end col by trimming whitespace on the right of the\n // line if needed.\n if (trimRight) {\n const rightWhitespaceIndex = lineString.search(/\\s+$/);\n if (rightWhitespaceIndex !== -1) {\n endIndex = Math.min(endIndex, rightWhitespaceIndex);\n }\n // Return the empty string if only trimmed whitespace is selected\n if (endIndex <= startIndex) {\n return '';\n }\n }\n\n return lineString.substring(startIndex, endIndex);\n }\n\n /**\n * Setup the tab stops.\n * @param i The index to start setting up tab stops from.\n */\n public setupTabStops(i?: number): void {\n if (i != null) {\n if (!this.tabs[i]) {\n i = this.prevStop(i);\n }\n } else {\n this.tabs = {};\n i = 0;\n }\n\n for (; i < this._terminal.cols; i += this._terminal.options.tabStopWidth) {\n this.tabs[i] = true;\n }\n }\n\n /**\n * Move the cursor to the previous tab stop from the given position (default is current).\n * @param x The position to move the cursor to the previous tab stop.\n */\n public prevStop(x?: number): number {\n if (x == null) {\n x = this.x;\n }\n while (!this.tabs[--x] && x > 0);\n return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x;\n }\n\n /**\n * Move the cursor one tab stop forward from the given position (default is current).\n * @param x The position to move the cursor one tab stop forward.\n */\n public nextStop(x?: number): number {\n if (x == null) {\n x = this.x;\n }\n while (!this.tabs[++x] && x < this._terminal.cols);\n return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x;\n }\n}\n",null],"names":[],"mappings":"AkCAA;;;ADMA;AAGa;AACA;AACA;AACA;AACA;AASb;AAmBA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAOA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAGA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAYA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAjSa;;;;;;;;;;;;;;;;;ADhBb;AACA;AAMA;AAAA;AASA;AAAA;AAAA;AAEA;AACA;AAIA;AACA;AAEA;;AACA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAKA;AAIA;AAEA;AACA;AACA;AAKA;AAGA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AAxFa;;;;;;;ADRA;AAGb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;;;;;;ADpJa;AAKA;AAYb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;AD7OA;AAwBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAUA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AAUA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AAAA;AACA;AAAA;AApNa;;;;;;;ADRb;AAAA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AAAC;;;;;;;ADvED;AAGA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA/Da;;;;;;;ADAb;AACA;AAEA;AACA;AACA;AASA;AACA;AAAA;AAAA;AAEA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AAIA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAuCA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAAA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAwFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAoFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAmEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAyBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AAz5Ca;;;;;;;;;;;;;;;;;ADfb;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAKA;AAAA;AAeA;AAAA;AACA;AARA;AAIA;AAOA;AACA;AACA;AACA;AACA;;AACA;AAMA;AACA;AACA;AAOA;AAAA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AACA;AAYA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAWA;AAAA;AAAA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AASA;AAAA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AA3PA;AA4PA;AAlQA;AAAa;;;;;;;ADhCb;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAKA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AAIA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAMA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AAQA;AACA;AACA;AAQA;AACA;AACA;AAOA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AASA;AAAA;AAjfa;;;;;;;;;;;;;;;;;ADnKb;AACA;AAGA;AAEA;AAEA;AAMA;AAKA;AAKA;AAMA;AAEA;AACA;AAaA;AAAA;AACA;AACA;AACA;AACA;AAUA;AAAA;AAiCA;AAAA;AACA;AACA;AACA;AARA;AAWA;AACA;AAEA;AACA;;AACA;AAKA;AAAA;AACA;AACA;AAMA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AAOA;AACA;AACA;AACA;AAEA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AACA;AAEA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AAGA;AAGA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AAAA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAIA;AAIA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAKA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAIA;AAEA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAOA;AAGA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AAjqBa;;;;;;;ADtDb;AAuBA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AAxHa;;;;;;;;;;;;;;;;;ADYb;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AAEA;AACA;AAUA;AAOA;AAMA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAAA;AA8HA;AACA;AADA;AA3GA;AA+GA;AACA;;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAKA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAKA;AAEA;AACA;AAKA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAMA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAOA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAIA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAGA;AAGA;AAIA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAOA;AAEA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAYA;AAAA;AACA;AACA;AACA;AAKA;AACA;AACA;AAGA;AAGA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AAIA;AACA;AACA;AACA;AAAA;AAIA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AAOA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAGA;AAEA;AACA;AAAA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AAIA;AACA;AAKA;AACA;AACA;AAGA;AAIA;AACA;AACA;AACA;AACA;AAGA;AAAA;AAGA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAMA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AAGA;AACA;AAQA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AAMA;AAAA;AACA;AAKA;AAGA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAWA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AASA;AACA;AAGA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AAAA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAMA;AACA;AAAA;AACA;AAAA;AAOA;AAKA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AASA;AACA;AAEA;AACA;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAMA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAMA;AAOA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AA//Da;AAqgEb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAAC;AAGD;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;;;;;;;AD9rEA;AAAA;AACA;AACA;AACA;AACA;AAAC;;;;;;;ADrBD;AAcA;AAAA;AACA;AACA;AACA;AACA;AAjBA;AACA;AACA;AACA;AAgBA;AAGA;AACA;AAEA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAMA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AAnIa;;;;;;;ADOb;AACA;AACA;AACA;AACA;AACA;AALA;AAWA;AACA;AACA;AACA;AACA;AACA;AALA;AAWA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AATA;AAgBA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAzBA;AAgCA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AApBA;AA4BA;AACA;AAGA;AACA;AACA;AANA;;;;;;;AD9GA;AAUA;AAWA;AAAA;AACA;AAXA;AAEA;AAIA;AACA;AACA;AAKA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAIA;AACA;AAGA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AArKa;AAuKb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAXa;;;;;;;ADlLb;AAEA;AAEa;AACb;AAEA;AAYA;AAIA;AACA;AAdA;AACA;AACA;AACA;AACA;AACA;AAWA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAOA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAWA;AACA;AAKA;AAQA;AAAA;AACA;AAKA;AAQA;AACA;AAKA;AAQA;AACA;AACA;AAKA;AAKA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AAKA;AAAA;AACA;AACA;AAKA;AACA;AAYA;AACA;AACA;AACA;AACA;AAIA;AAgBA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AASA;AAAA;AACA;AACA;AAIA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AAIA;AACA;AAOA;AACA;AACA;AAKA;AACA;AACA;AAAA;AAhUsB;;;;;;;ADPtB;AAEa;AAgBb;AAQA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvCA;AAyCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AAJA;AAMA;AAIA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;;;;;;;ADrNA;AACA;AACA;AACA;AACA;AACa;AAEb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAKA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA1Da;;;;;;;;;;;;;;;;;AD5Db;AAGA;AAcA;AAEA;AAAA;AAMA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AApMa;AAsMb;AAcA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAEA;AACA;AACA;AAMA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;;;;;;;AD5VA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA3Ba;;;;;;;;;;;;;;;;;ADKb;AACA;AAEA;AAAA;AAGA;AAAA;AAFA;AAIA;AACA;;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAnCa;;;;;;;;;;;;;;;;;ADNb;AACA;AACA;AACA;AAGA;AACA;AAEA;AAAA;AAWA;AAAA;AAAA;AATA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAGA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAMA;AAKA;AAMA;AAIA;AAGA;AAIA;AAIA;AACA;AAOA;AACA;AAQA;AACA;AAGA;AAGA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AA5Ma;;;;;;;;;;;;;;;;;ADNb;AAEA;AAAA;AAGA;AAAA;AAEA;AACA;AACA;AACA;;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AAGA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AA5Ea;;;;;;;;;;;;;;;;;ADLb;AACA;AACA;AAEA;AAOA;AAEA;AAAA;AAMA;AAAA;AAFA;AAIA;;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAiBA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAIA;AAQA;AACA;AAMA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAKA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AAKA;AAGA;AAGA;AACA;AACA;AAOA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAnOa;;;;;;;ADZb;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAC;;;;;;;ADVD;AAEA;AACA;AACA;AAEa;AACA;AAKA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;ADhBb;AAQA;AAAA;AAOA;AAAA;AAEA;AACA;;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAtDa;;;;;;;;;;;;;;;;;ADRb;AAOA;AAAA;AAKA;AAAA;AACA;AAGA;AACA;AACA;;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAjBA;AAmBA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AATA;AAWA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAUA;AACA;AACA;AAUA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAWA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAAA;AAxMa;;;;;;;ADFb;AACA;AACA;AAFA;AAEC;;;;;;;ADJD;AACA;AAAA;AAAA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAeA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAKA;AACA;AAEA;AACA;AAYA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAAA;AAtFa;;;;;;;ADCA;;;;;;;ADFb;AAEA;;;"} \ No newline at end of file diff --git a/web/static/codemirror/addon/dialog/dialog.css b/web/static/codemirror/addon/dialog/dialog.css new file mode 100755 index 000000000..494690b40 --- /dev/null +++ b/web/static/codemirror/addon/dialog/dialog.css @@ -0,0 +1 @@ +.CodeMirror-dialog{position:absolute;left:0;right:0;background:inherit;z-index:15;padding:.4em .8em;overflow:hidden;color:inherit}.CodeMirror-dialog-top{border-bottom:1px solid #eee;top:0}.CodeMirror-dialog-bottom{border-top:1px solid #eee;bottom:0}.CodeMirror-dialog input{border:0;outline:0;background:transparent;width:20em;color:inherit;font-family:monospace}.CodeMirror-dialog button{font-size:70%}.Dialog-close{color:#111;float:right;font-family:Arial;font-size:16px;height:30px;line-height:30px;text-align:center;width:30px;cursor:pointer} \ No newline at end of file diff --git a/web/static/codemirror/addon/dialog/dialog.js b/web/static/codemirror/addon/dialog/dialog.js new file mode 100755 index 000000000..ef64aa000 --- /dev/null +++ b/web/static/codemirror/addon/dialog/dialog.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(b){function c(d,h,e){var g=d.getWrapperElement();var f;f=g.appendChild(document.createElement("div"));if(e){f.className="CodeMirror-dialog CodeMirror-dialog-bottom"}else{f.className="CodeMirror-dialog CodeMirror-dialog-top"}if(typeof h=="string"){f.innerHTML=h}else{f.appendChild(h)}return f}function a(d,e){if(d.state.currentNotificationClose){d.state.currentNotificationClose()}d.state.currentNotificationClose=e}b.defineExtension("openDialog",function(i,j,l){if(!l){l={}}a(this,null);var f=c(this,i,l.bottom);var e=false,g=this;function k(m){if(typeof m=="string"){h.value=m}else{if(e){return}e=true;f.parentNode.removeChild(f);g.focus();if(l.onClose){l.onClose(f)}}}var h=f.getElementsByTagName("input")[0],d;if(h){h.focus();if(l.value){h.value=l.value;if(l.selectValueOnOpen!==false){h.select()}}if(l.onInput){b.on(h,"input",function(m){l.onInput(m,h.value,k)})}if(l.onKeyUp){b.on(h,"keyup",function(m){l.onKeyUp(m,h.value,k)})}b.on(h,"keydown",function(m){if(l&&l.onKeyDown&&l.onKeyDown(m,h.value,k)){return}if(m.keyCode==27||(l.closeOnEnter!==false&&m.keyCode==13)){h.blur();b.e_stop(m);k()}if(m.keyCode==13){j(h.value,m)}});if(l.closeOnBlur!==false){b.on(h,"blur",k)}}else{if(d=f.getElementsByTagName("button")[0]){b.on(d,"click",function(){k();g.focus()});if(l.closeOnBlur!==false){b.on(d,"blur",k)}d.focus()}}return k});b.defineExtension("openConfirm",function(m,g,o){a(this,null);var h=c(this,m,o&&o.bottom);var j=h.getElementsByTagName("button");var f=false,k=this,d=1;function n(){if(f){return}f=true;h.parentNode.removeChild(h);k.focus()}j[0].focus();for(var e=0;e +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            + \ No newline at end of file diff --git a/web/static/codemirror/addon/display/autorefresh.js b/web/static/codemirror/addon/display/autorefresh.js new file mode 100755 index 000000000..5a7a32968 --- /dev/null +++ b/web/static/codemirror/addon/display/autorefresh.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(b){b.defineOption("autoRefresh",false,function(d,e){if(d.state.autoRefresh){a(d,d.state.autoRefresh);d.state.autoRefresh=null}if(e&&d.display.wrapper.offsetHeight==0){c(d,d.state.autoRefresh={delay:e.delay||250})}});function c(d,f){function e(){if(d.display.wrapper.offsetHeight){a(d,f);if(d.display.lastWrapHeight!=d.display.wrapper.clientHeight){d.refresh()}}else{f.timeout=setTimeout(e,f.delay)}}f.timeout=setTimeout(e,f.delay);f.hurry=function(){clearTimeout(f.timeout);f.timeout=setTimeout(e,50)};b.on(window,"mouseup",f.hurry);b.on(window,"keyup",f.hurry)}function a(d,e){clearTimeout(e.timeout);b.off(window,"mouseup",e.hurry);b.off(window,"keyup",e.hurry)}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/display/fullscreen.css b/web/static/codemirror/addon/display/fullscreen.css new file mode 100755 index 000000000..a414b0220 --- /dev/null +++ b/web/static/codemirror/addon/display/fullscreen.css @@ -0,0 +1 @@ +.CodeMirror-fullscreen{position:fixed;top:0;left:0;right:0;bottom:0;height:auto;z-index:9} \ No newline at end of file diff --git a/web/static/codemirror/addon/display/fullscreen.js b/web/static/codemirror/addon/display/fullscreen.js new file mode 100755 index 000000000..a2329a4dd --- /dev/null +++ b/web/static/codemirror/addon/display/fullscreen.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(a){a.defineOption("fullScreen",false,function(d,f,e){if(e==a.Init){e=false}if(!e==!f){return}if(f){b(d)}else{c(d)}});function b(d){var e=d.getWrapperElement();d.state.fullScreenRestore={scrollTop:window.pageYOffset,scrollLeft:window.pageXOffset,width:e.style.width,height:e.style.height};e.style.width="";e.style.height="auto";e.className+=" CodeMirror-fullscreen";document.documentElement.style.overflow="hidden";d.refresh()}function c(d){var e=d.getWrapperElement();e.className=e.className.replace(/\s*CodeMirror-fullscreen\b/,"");document.documentElement.style.overflow="";var f=d.state.fullScreenRestore;e.style.width=f.width;e.style.height=f.height;window.scrollTo(f.scrollLeft,f.scrollTop);d.refresh()}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/display/index.html b/web/static/codemirror/addon/display/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/codemirror/addon/display/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/codemirror/addon/display/panel.js b/web/static/codemirror/addon/display/panel.js new file mode 100755 index 000000000..d3b0e63f1 --- /dev/null +++ b/web/static/codemirror/addon/display/panel.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(a){a.defineExtension("addPanel",function(g,f){f=f||{};if(!this.state.panels){b(this)}var h=this.state.panels;var j=h.wrapper;var i=this.getWrapperElement();if(f.after instanceof d&&!f.after.cleared){j.insertBefore(g,f.before.node.nextSibling)}else{if(f.before instanceof d&&!f.before.cleared){j.insertBefore(g,f.before.node)}else{if(f.replace instanceof d&&!f.replace.cleared){j.insertBefore(g,f.replace.node);f.replace.clear()}else{if(f.position=="bottom"){j.appendChild(g)}else{if(f.position=="before-bottom"){j.insertBefore(g,i.nextSibling)}else{if(f.position=="after-top"){j.insertBefore(g,i)}else{j.insertBefore(g,j.firstChild)}}}}}}var e=(f&&f.height)||g.offsetHeight;this._setSize(null,h.heightLeft-=e);h.panels++;return new d(this,g,f,e)});function d(f,h,g,e){this.cm=f;this.node=h;this.options=g;this.height=e;this.cleared=false}d.prototype.clear=function(){if(this.cleared){return}this.cleared=true;var e=this.cm.state.panels;this.cm._setSize(null,e.heightLeft+=this.height);e.wrapper.removeChild(this.node);if(--e.panels==0){c(this.cm)}};d.prototype.changed=function(e){var f=e==null?this.node.offsetHeight:e;var g=this.cm.state.panels;this.cm._setSize(null,g.height+=(f-this.height));this.height=f};function b(f){var h=f.getWrapperElement();var g=window.getComputedStyle?window.getComputedStyle(h):h.currentStyle;var e=parseInt(g.height);var i=f.state.panels={setHeight:h.style.height,heightLeft:e,panels:0,wrapper:document.createElement("div")};h.parentNode.insertBefore(i.wrapper,h);var j=f.hasFocus();i.wrapper.appendChild(h);if(j){f.focus()}f._setSize=f.setSize;if(e!=null){f.setSize=function(m,k){if(k==null){return this._setSize(m,k)}i.setHeight=k;if(typeof k!="number"){var l=/^(\d+\.?\d*)px$/.exec(k);if(l){k=Number(l[1])}else{i.wrapper.style.height=k;k=i.wrapper.offsetHeight;i.wrapper.style.height=""}}f._setSize(m,i.heightLeft+=(k-e));e=k}}}function c(e){var g=e.state.panels;e.state.panels=null;var f=e.getWrapperElement();g.wrapper.parentNode.replaceChild(f,g.wrapper);f.style.height=g.setHeight;e.setSize=e._setSize;e.setSize()}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/display/placeholder.js b/web/static/codemirror/addon/display/placeholder.js new file mode 100755 index 000000000..acb9db7ec --- /dev/null +++ b/web/static/codemirror/addon/display/placeholder.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(b){b.defineOption("placeholder","",function(g,j,h){var i=h&&h!=b.Init;if(j&&!i){g.on("blur",e);g.on("change",a);g.on("swapDoc",a);a(g)}else{if(!j&&i){g.off("blur",e);g.off("change",a);g.off("swapDoc",a);c(g);var k=g.getWrapperElement();k.className=k.className.replace(" CodeMirror-empty","")}}if(j&&!g.hasFocus()){e(g)}});function c(g){if(g.state.placeholder){g.state.placeholder.parentNode.removeChild(g.state.placeholder);g.state.placeholder=null}}function d(g){c(g);var h=g.state.placeholder=document.createElement("pre");h.style.cssText="height: 0; overflow: visible";h.className="CodeMirror-placeholder";var i=g.getOption("placeholder");if(typeof i=="string"){i=document.createTextNode(i)}h.appendChild(i);g.display.lineSpace.insertBefore(h,g.display.lineSpace.firstChild)}function e(g){if(f(g)){d(g)}}function a(g){var i=g.getWrapperElement(),h=f(g);i.className=i.className.replace(" CodeMirror-empty","")+(h?" CodeMirror-empty":"");if(h){d(g)}else{c(g)}}function f(g){return(g.lineCount()===1)&&(g.getLine(0)==="")}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/display/rulers.js b/web/static/codemirror/addon/display/rulers.js new file mode 100755 index 000000000..cfbd155da --- /dev/null +++ b/web/static/codemirror/addon/display/rulers.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(b){b.defineOption("rulers",false,function(c,d){if(c.state.rulerDiv){c.state.rulerDiv.parentElement.removeChild(c.state.rulerDiv);c.state.rulerDiv=null;c.off("refresh",a)}if(d&&d.length){c.state.rulerDiv=c.display.lineSpace.parentElement.insertBefore(document.createElement("div"),c.display.lineSpace);c.state.rulerDiv.className="CodeMirror-rulers";a(c);c.on("refresh",a)}});function a(c){c.state.rulerDiv.textContent="";var k=c.getOption("rulers");var d=c.defaultCharWidth();var j=c.charCoords(b.Pos(c.firstLine(),0),"div").left;c.state.rulerDiv.style.minHeight=(c.display.scroller.offsetHeight+30)+"px";for(var h=0;h=0;v--){var y=t[v].head;s.replaceRange("",p(y.line,y.ch-1),p(y.line,y.ch+1),"+delete")}}function b(s){var u=o(s);var w=u&&a(u,"explode");if(!w||s.getOption("disableInput")){return f.Pass}var t=s.listSelections();for(var v=0;v0;return{anchor:new p(s.anchor.line,s.anchor.ch+(i?-1:1)),head:new p(s.head.line,s.head.ch+(i?1:-1))}}function q(F,s){var A=o(F);if(!A||F.getOption("disableInput")){return f.Pass}var u=a(A,"pairs");var D=u.indexOf(s);if(D==-1){return f.Pass}var I=a(A,"triples");var E=u.charAt(D+1)==s;var t=F.listSelections();var v=D%2==0;var B;for(var x=0;x=0&&F.getRange(H,p(H.line,H.ch+3))==s+s+s){C="skipThree"}else{C="skip"}}}else{if(E&&H.ch>1&&I.indexOf(s)>=0&&F.getRange(p(H.line,H.ch-2),H)==s+s&&(H.ch<=2||F.getRange(p(H.line,H.ch-3),p(H.line,H.ch-2))!=s)){C="addFour"}else{if(E){if(!f.isWordChar(y)&&n(F,H,s)){C="both"}else{return f.Pass}}else{if(v&&(F.getLine(H.line).length==H.ch||c(y,u)||/\s/.test(y))){C="both"}else{return f.Pass}}}}}if(!B){B=C}else{if(B!=C){return f.Pass}}}var w=D%2?u.charAt(D-1):s;var G=D%2?s:u.charAt(D+1);F.operation(function(){if(B=="skip"){F.execCommand("goCharRight")}else{if(B=="skipThree"){for(var K=0;K<3;K++){F.execCommand("goCharRight")}}else{if(B=="surround"){var J=F.getSelections();for(var K=0;K-1&&t%2==1}function h(i,t){var s=i.getRange(p(t.line,t.ch-1),p(t.line,t.ch+1));return s.length==2?s:null}function n(i,x,v){var s=i.getLine(x.line);var u=i.getTokenAt(x);if(/\bstring2?\b/.test(u.type)){return false}var w=new f.StringStream(s.slice(0,x.ch)+v+s.slice(x.ch),4);w.pos=w.start=u.start;for(;;){var t=i.getMode().token(w,u.state);if(w.pos>=x.ch+1){return/\bstring2?\b/.test(t)}w.start=w.pos}}function k(i,t){var s=i.getTokenAt(p(t.line,t.ch+1));return/\bstring/.test(s.type)&&s.start==t.ch}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/edit/closetag.js b/web/static/codemirror/addon/edit/closetag.js new file mode 100755 index 000000000..3fda5544b --- /dev/null +++ b/web/static/codemirror/addon/edit/closetag.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"),require("../fold/xml-fold"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","../fold/xml-fold"],a)}else{a(CodeMirror)}}})(function(b){b.defineOption("autoCloseTags",false,function(i,l,j){if(j!=b.Init&&j){i.removeKeyMap("autoCloseTags")}if(!l){return}var k={name:"autoCloseTags"};if(typeof l!="object"||l.whenClosing){k["'/'"]=function(m){return e(m)}}if(typeof l!="object"||l.whenOpening){k["'>'"]=function(m){return a(m)}}i.addKeyMap(k)});var d=["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"];var c=["applet","blockquote","body","button","div","dl","fieldset","form","frameset","h1","h2","h3","h4","h5","h6","head","html","iframe","layer","legend","object","ol","p","select","table","ul"];function a(x){if(x.getOption("disableInput")){return b.Pass}var k=x.listSelections(),r=[];for(var s=0;sw.ch){q=q.slice(0,q.length-y.end+w.ch)}var u=q.toLowerCase();if(!q||y.type=="string"&&(y.end!=w.ch||!/[\"\']/.test(y.string.charAt(y.string.length-1))||y.string.length==1)||y.type=="tag"&&j.type=="closeTag"||y.string.indexOf("/")==(y.string.length-1)||n&&g(n,u)>-1||f(x,q,w,j,true)){return b.Pass}var p=v&&g(v,u)>-1;r[s]={indent:p,text:">"+(p?"\n\n":"")+"",newPos:p?b.Pos(w.line+1,0):b.Pos(w.line,w.ch+1)}}for(var s=k.length-1;s>=0;s--){var o=r[s];x.replaceRange(o.text,k[s].head,k[s].anchor,"+insert");var m=x.listSelections().slice(0);m[s]={head:o.newPos,anchor:o.newPos};x.setSelections(m);if(o.indent){x.indentLine(o.newPos.line,null,true);x.indentLine(o.newPos.line+1,null,true)}}}function h(r,n){var k=r.listSelections(),m=[];var q=n?"/":""){l+=">"}m[o]=l}r.replaceSelections(m);k=r.listSelections();for(var o=0;o[> ]*|- \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/,c=/^(\s*)(>[> ]*|- \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/,a=/[*+-]\s/;b.commands.newlineAndIndentContinueMarkdownList=function(q){if(q.getOption("disableInput")){return b.Pass}var g=q.listSelections(),k=[];for(var m=0;m")>=0?n[2].replace("x"," "):(parseInt(n[3],10)+1)+n[4];k[m]="\n"+j+e+f}}q.replaceSelections(k)}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/edit/editAll.js b/web/static/codemirror/addon/edit/editAll.js new file mode 100755 index 000000000..0426f0e93 --- /dev/null +++ b/web/static/codemirror/addon/edit/editAll.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"),require("../fold/xml-fold"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","../fold/xml-fold"],a)}else{a(CodeMirror)}}})(function(c){c.defineOption("matchTags",false,function(e,g,f){if(f&&f!=c.Init){e.off("cursorActivity",b);e.off("viewportChange",d);a(e)}if(g){e.state.matchBothTags=typeof g=="object"&&g.bothTags;e.on("cursorActivity",b);e.on("viewportChange",d);b(e)}});function a(e){if(e.state.tagHit){e.state.tagHit.clear()}if(e.state.tagOther){e.state.tagOther.clear()}e.state.tagHit=e.state.tagOther=null}function b(e){e.state.failedTagMatch=false;e.operation(function(){a(e);if(e.somethingSelected()){return}var j=e.getCursor(),g=e.getViewport();g.from=Math.min(g.from,j.line);g.to=Math.max(j.line+1,g.to);var h=c.findMatchingTag(e,j,g);if(!h){return}if(e.state.matchBothTags){var i=h.at=="open"?h.open:h.close;if(i){e.state.tagHit=e.markText(i.from,i.to,{className:"CodeMirror-matchingtag"})}}var f=h.at=="close"?h.open:h.close;if(f){e.state.tagOther=e.markText(f.from,f.to,{className:"CodeMirror-matchingtag"})}else{e.state.failedTagMatch=true}})}function d(e){if(e.state.failedTagMatch){b(e)}}c.commands.toMatchingTag=function(f){var g=c.findMatchingTag(f,f.getCursor());if(g){var e=g.at=="close"?g.open:g.close;if(e){f.extendSelection(e.to,e.from)}}}});(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(e){var d=/MSIE \d/.test(navigator.userAgent)&&(document.documentMode==null||document.documentMode<8);var i=e.Pos;var a={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<"};function f(q,m,p,k){var s=q.getLineHandle(m.line),o=m.ch-1;var n=(o>=0&&a[s.text.charAt(o)])||a[s.text.charAt(++o)];if(!n){return null}var l=n.charAt(1)==">"?1:-1;if(p&&(l>0)!=(o==m.ch)){return null}var j=q.getTokenTypeAt(i(m.line,o+1));var r=g(q,i(m.line,o+(l>0?1:0)),l,j||null,k);if(r==null){return null}return{from:i(m.line,o),to:r&&r.pos,match:r&&r.ch==n.charAt(0),forward:l>0}}function g(w,r,n,k,m){var l=(m&&m.maxScanLineLength)||10000;var t=(m&&m.maxScanLines)||1000;var v=[];var x=m&&m.bracketRegex?m.bracketRegex:/[(){}[\]]/;var q=n>0?Math.min(r.line+t,w.lastLine()+1):Math.max(w.firstLine()-1,r.line-t);for(var o=r.line;o!=q;o+=n){var y=w.getLine(o);if(!y){continue}var u=n>0?0:y.length-1,p=n>0?y.length:-1;if(y.length>l){continue}if(o==r.line){u=r.ch-(n<0?1:0)}for(;u!=p;u+=n){var j=y.charAt(u);if(x.test(j)&&(k===undefined||w.getTokenTypeAt(i(o,u+1))==k)){var s=a[j];if((s.charAt(1)==">")==(n>0)){v.push(j)}else{if(!v.length){return{pos:i(o,u),ch:j}}else{v.pop()}}}}}return o-n==(n>0?w.lastLine():w.firstLine())?false:null}function b(s,n,m){var k=s.state.matchBrackets.maxHighlightLineLength||1000;var r=[],l=s.listSelections();for(var o=0;o +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            + \ No newline at end of file diff --git a/web/static/codemirror/addon/edit/matchbrackets.js b/web/static/codemirror/addon/edit/matchbrackets.js new file mode 100755 index 000000000..7e6a31eb7 --- /dev/null +++ b/web/static/codemirror/addon/edit/matchbrackets.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(e){var d=/MSIE \d/.test(navigator.userAgent)&&(document.documentMode==null||document.documentMode<8);var i=e.Pos;var a={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<"};function f(q,m,p,k){var s=q.getLineHandle(m.line),o=m.ch-1;var n=(o>=0&&a[s.text.charAt(o)])||a[s.text.charAt(++o)];if(!n){return null}var l=n.charAt(1)==">"?1:-1;if(p&&(l>0)!=(o==m.ch)){return null}var j=q.getTokenTypeAt(i(m.line,o+1));var r=g(q,i(m.line,o+(l>0?1:0)),l,j||null,k);if(r==null){return null}return{from:i(m.line,o),to:r&&r.pos,match:r&&r.ch==n.charAt(0),forward:l>0}}function g(w,r,n,k,m){var l=(m&&m.maxScanLineLength)||10000;var t=(m&&m.maxScanLines)||1000;var v=[];var x=m&&m.bracketRegex?m.bracketRegex:/[(){}[\]]/;var q=n>0?Math.min(r.line+t,w.lastLine()+1):Math.max(w.firstLine()-1,r.line-t);for(var o=r.line;o!=q;o+=n){var y=w.getLine(o);if(!y){continue}var u=n>0?0:y.length-1,p=n>0?y.length:-1;if(y.length>l){continue}if(o==r.line){u=r.ch-(n<0?1:0)}for(;u!=p;u+=n){var j=y.charAt(u);if(x.test(j)&&(k===undefined||w.getTokenTypeAt(i(o,u+1))==k)){var s=a[j];if((s.charAt(1)==">")==(n>0)){v.push(j)}else{if(!v.length){return{pos:i(o,u),ch:j}}else{v.pop()}}}}}return o-n==(n>0?w.lastLine():w.firstLine())?false:null}function b(s,n,m){var k=s.state.matchBrackets.maxHighlightLineLength||1000;var r=[],l=s.listSelections();for(var o=0;og.pos){g.pos=f;return null}g.pos=e;return"trailingspace"},name:"trailingspace"})}}})}); \ No newline at end of file diff --git a/web/static/codemirror/addon/hint/anyword-hint.js b/web/static/codemirror/addon/hint/anyword-hint.js new file mode 100755 index 000000000..2b27ad172 --- /dev/null +++ b/web/static/codemirror/addon/hint/anyword-hint.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(c){var b=/[\w$]+/,a=500;c.registerHelper("hint","anyword",function(o,u){var f=u&&u.word||b;var n=u&&u.range||a;var q=o.getCursor(),e=o.getLine(q.line);var l=q.ch,h=l;while(h&&f.test(e.charAt(h-1))){--h}var g=h!=l&&e.slice(h,l);var p=u&&u.list||[],d={};var s=new RegExp(f.source,"g");for(var k=-1;k<=1;k+=2){var t=q.line,j=Math.min(Math.max(t+k*n,o.firstLine()),o.lastLine())+k;for(;t!=j;t+=k){var r=o.getLine(t),i;while(i=s.exec(r)){if(t==q.line&&i[0]===g){continue}if((!g||i[0].lastIndexOf(g,0)==0)&&!Object.prototype.hasOwnProperty.call(d,i[0])){d[i[0]]=true;p.push(i[0])}}}}return{list:p,from:c.Pos(q.line,h),to:c.Pos(q.line,l)}})}); \ No newline at end of file diff --git a/web/static/codemirror/addon/hint/css-hint.js b/web/static/codemirror/addon/hint/css-hint.js new file mode 100755 index 000000000..1086c2a59 --- /dev/null +++ b/web/static/codemirror/addon/hint/css-hint.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"),require("../../mode/css/css"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","../../mode/css/css"],a)}else{a(CodeMirror)}}})(function(a){var b={link:1,visited:1,active:1,hover:1,focus:1,"first-letter":1,"first-line":1,"first-child":1,before:1,after:1,lang:1};a.registerHelper("hint","css",function(g){var h=g.getCursor(),e=g.getTokenAt(h);var m=a.innerMode(g.getMode(),e.state);if(m.mode.name!="css"){return}if(e.type=="keyword"&&"!important".indexOf(e.string)==0){return{list:["!important"],from:a.Pos(h.line,e.start),to:a.Pos(h.line,e.end)}}var d=e.start,f=h.ch,c=e.string.slice(0,f-d);if(/[^\w$_-]/.test(c)){c="";d=f=h.ch}var i=a.resolveMode("text/css");var l=[];function k(o){for(var n in o){if(!c||n.lastIndexOf(c,0)==0){l.push(n)}}}var j=m.state.state;if(j=="pseudo"||e.type=="variable-3"){k(b)}else{if(j=="block"||j=="maybeprop"){k(i.propertyKeywords)}else{if(j=="prop"||j=="parens"||j=="at"||j=="params"){k(i.valueKeywords);k(i.colorKeywords)}else{if(j=="media"||j=="media_parens"){k(i.mediaTypes);k(i.mediaFeatures)}}}}if(l.length){return{list:l,from:a.Pos(h.line,d),to:a.Pos(h.line,f)}}})}); \ No newline at end of file diff --git a/web/static/codemirror/addon/hint/html-hint.js b/web/static/codemirror/addon/hint/html-hint.js new file mode 100755 index 000000000..10e0ef6ea --- /dev/null +++ b/web/static/codemirror/addon/hint/html-hint.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"),require("./xml-hint"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","./xml-hint"],a)}else{a(CodeMirror)}}})(function(f){var g="ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa cy wo fy xh yi yo za zu".split(" ");var j=["_blank","_self","_top","_parent"];var a=["ascii","utf-8","utf-16","latin1","latin1"];var e=["get","post","put","delete"];var b=["application/x-www-form-urlencoded","multipart/form-data","text/plain"];var c=["all","screen","print","embossed","braille","handheld","print","projection","screen","tty","tv","speech","3d-glasses","resolution [>][<][=] [X]","device-aspect-ratio: X/Y","orientation:portrait","orientation:landscape","device-height: [X]","device-width: [X]"];var m={attrs:{}};var h={a:{attrs:{href:null,ping:null,type:null,media:c,target:j,hreflang:g}},abbr:m,acronym:m,address:m,applet:m,area:{attrs:{alt:null,coords:null,href:null,target:null,ping:null,media:c,hreflang:g,type:null,shape:["default","rect","circle","poly"]}},article:m,aside:m,audio:{attrs:{src:null,mediagroup:null,crossorigin:["anonymous","use-credentials"],preload:["none","metadata","auto"],autoplay:["","autoplay"],loop:["","loop"],controls:["","controls"]}},b:m,base:{attrs:{href:null,target:j}},basefont:m,bdi:m,bdo:m,big:m,blockquote:{attrs:{cite:null}},body:m,br:m,button:{attrs:{form:null,formaction:null,name:null,value:null,autofocus:["","autofocus"],disabled:["","autofocus"],formenctype:b,formmethod:e,formnovalidate:["","novalidate"],formtarget:j,type:["submit","reset","button"]}},canvas:{attrs:{width:null,height:null}},caption:m,center:m,cite:m,code:m,col:{attrs:{span:null}},colgroup:{attrs:{span:null}},command:{attrs:{type:["command","checkbox","radio"],label:null,icon:null,radiogroup:null,command:null,title:null,disabled:["","disabled"],checked:["","checked"]}},data:{attrs:{value:null}},datagrid:{attrs:{disabled:["","disabled"],multiple:["","multiple"]}},datalist:{attrs:{data:null}},dd:m,del:{attrs:{cite:null,datetime:null}},details:{attrs:{open:["","open"]}},dfn:m,dir:m,div:m,dl:m,dt:m,em:m,embed:{attrs:{src:null,type:null,width:null,height:null}},eventsource:{attrs:{src:null}},fieldset:{attrs:{disabled:["","disabled"],form:null,name:null}},figcaption:m,figure:m,font:m,footer:m,form:{attrs:{action:null,name:null,"accept-charset":a,autocomplete:["on","off"],enctype:b,method:e,novalidate:["","novalidate"],target:j}},frame:m,frameset:m,h1:m,h2:m,h3:m,h4:m,h5:m,h6:m,head:{attrs:{},children:["title","base","link","style","meta","script","noscript","command"]},header:m,hgroup:m,hr:m,html:{attrs:{manifest:null},children:["head","body"]},i:m,iframe:{attrs:{src:null,srcdoc:null,name:null,width:null,height:null,sandbox:["allow-top-navigation","allow-same-origin","allow-forms","allow-scripts"],seamless:["","seamless"]}},img:{attrs:{alt:null,src:null,ismap:null,usemap:null,width:null,height:null,crossorigin:["anonymous","use-credentials"]}},input:{attrs:{alt:null,dirname:null,form:null,formaction:null,height:null,list:null,max:null,maxlength:null,min:null,name:null,pattern:null,placeholder:null,size:null,src:null,step:null,value:null,width:null,accept:["audio/*","video/*","image/*"],autocomplete:["on","off"],autofocus:["","autofocus"],checked:["","checked"],disabled:["","disabled"],formenctype:b,formmethod:e,formnovalidate:["","novalidate"],formtarget:j,multiple:["","multiple"],readonly:["","readonly"],required:["","required"],type:["hidden","text","search","tel","url","email","password","datetime","date","month","week","time","datetime-local","number","range","color","checkbox","radio","file","submit","image","reset","button"]}},ins:{attrs:{cite:null,datetime:null}},kbd:m,keygen:{attrs:{challenge:null,form:null,name:null,autofocus:["","autofocus"],disabled:["","disabled"],keytype:["RSA"]}},label:{attrs:{"for":null,form:null}},legend:m,li:{attrs:{value:null}},link:{attrs:{href:null,type:null,hreflang:g,media:c,sizes:["all","16x16","16x16 32x32","16x16 32x32 64x64"]}},map:{attrs:{name:null}},mark:m,menu:{attrs:{label:null,type:["list","context","toolbar"]}},meta:{attrs:{content:null,charset:a,name:["viewport","application-name","author","description","generator","keywords"],"http-equiv":["content-language","content-type","default-style","refresh"]}},meter:{attrs:{value:null,min:null,low:null,high:null,max:null,optimum:null}},nav:m,noframes:m,noscript:m,object:{attrs:{data:null,type:null,name:null,usemap:null,form:null,width:null,height:null,typemustmatch:["","typemustmatch"]}},ol:{attrs:{reversed:["","reversed"],start:null,type:["1","a","A","i","I"]}},optgroup:{attrs:{disabled:["","disabled"],label:null}},option:{attrs:{disabled:["","disabled"],label:null,selected:["","selected"],value:null}},output:{attrs:{"for":null,form:null,name:null}},p:m,param:{attrs:{name:null,value:null}},pre:m,progress:{attrs:{value:null,max:null}},q:{attrs:{cite:null}},rp:m,rt:m,ruby:m,s:m,samp:m,script:{attrs:{type:["text/javascript"],src:null,async:["","async"],defer:["","defer"],charset:a}},section:m,select:{attrs:{form:null,name:null,size:null,autofocus:["","autofocus"],disabled:["","disabled"],multiple:["","multiple"]}},small:m,source:{attrs:{src:null,type:null,media:null}},span:m,strike:m,strong:m,style:{attrs:{type:["text/css"],media:c,scoped:null}},sub:m,summary:m,sup:m,table:m,tbody:m,td:{attrs:{colspan:null,rowspan:null,headers:null}},textarea:{attrs:{dirname:null,form:null,maxlength:null,name:null,placeholder:null,rows:null,cols:null,autofocus:["","autofocus"],disabled:["","disabled"],readonly:["","readonly"],required:["","required"],wrap:["soft","hard"]}},tfoot:m,th:{attrs:{colspan:null,rowspan:null,headers:null,scope:["row","col","rowgroup","colgroup"]}},thead:m,time:{attrs:{datetime:null}},title:m,tr:m,track:{attrs:{src:null,label:null,"default":null,kind:["subtitles","captions","descriptions","chapters","metadata"],srclang:g}},tt:m,u:m,ul:m,"var":m,video:{attrs:{src:null,poster:null,width:null,height:null,crossorigin:["anonymous","use-credentials"],preload:["auto","metadata","none"],autoplay:["","autoplay"],mediagroup:["movie"],muted:["","muted"],controls:["","controls"]}},wbr:m};var d={accesskey:["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"],"class":null,contenteditable:["true","false"],contextmenu:null,dir:["ltr","rtl","auto"],draggable:["true","false","auto"],dropzone:["copy","move","link","string:","file:"],hidden:["hidden"],id:null,inert:["inert"],itemid:null,itemprop:null,itemref:null,itemscope:["itemscope"],itemtype:null,lang:["en","es"],spellcheck:["true","false"],style:null,tabindex:["1","2","3","4","5","6","7","8","9"],title:null,translate:["yes","no"],onclick:null,rel:["stylesheet","alternate","author","bookmark","help","license","next","nofollow","noreferrer","prefetch","prev","search","tag"]};function i(o){for(var n in d){if(d.hasOwnProperty(n)){o.attrs[n]=d[n]}}}i(m);for(var l in h){if(h.hasOwnProperty(l)&&h[l]!=m){i(h[l])}}f.htmlSchema=h;function k(n,o){var q={schemaInfo:h};if(o){for(var p in o){q[p]=o[p]}}return f.hint.xml(n,q)}f.registerHelper("hint","html",k)}); \ No newline at end of file diff --git a/web/static/codemirror/addon/hint/index.html b/web/static/codemirror/addon/hint/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/codemirror/addon/hint/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/codemirror/addon/hint/javascript-hint.js b/web/static/codemirror/addon/hint/javascript-hint.js new file mode 100755 index 000000000..f9cfa09eb --- /dev/null +++ b/web/static/codemirror/addon/hint/javascript-hint.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(d){var l=d.Pos;function g(p,r){for(var q=0,s=p.length;qv.ch){s.end=v.ch;s.string=s.string.slice(0,v.ch-s.start)}}var p=s;while(p.type=="property"){p=w(u,l(v.line,p.start));if(p.string!="."){return}p=w(u,l(v.line,p.start));if(!r){var r=[]}r.push(p)}return{list:i(s,r,t,q),from:l(v.line,s.start),to:l(v.line,s.end)}}function a(q,p){return h(q,c,function(r,s){return r.getTokenAt(s)},p)}d.registerHelper("hint","javascript",a);function b(q,r){var p=q.getTokenAt(r);if(r.ch==p.start+1&&p.string.charAt(0)=="."){p.end=p.start;p.string=".";p.type="property"}else{if(/^\.[\w$_]*$/.test(p.string)){p.type="property";p.start++;p.string=p.string.replace(/\./,"")}}return p}function j(q,p){return h(q,n,b,p)}d.registerHelper("hint","coffeescript",j);var m=("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight toUpperCase toLowerCase split concat match replace search").split(" ");var o=("length concat join splice push pop shift unshift slice reverse sort indexOf lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");var f="prototype apply call bind".split(" ");var c=("break case catch continue debugger default delete do else false finally for function if in instanceof new null return switch throw true try typeof var void while with").split(" ");var n=("and break catch class continue delete do else extends false finally for if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");function k(q,s){if(!Object.getOwnPropertyNames||!Object.getPrototypeOf){for(var p in q){s(p)}}else{for(var r=q;r;r=Object.getPrototypeOf(r)){Object.getOwnPropertyNames(r).forEach(s)}}}function i(u,t,y,B){var A=[],r=u.string,s=B&&B.globalScope||window;function w(v){if(v.lastIndexOf(r,0)==0&&!e(A,v)){A.push(v)}}function q(v){if(typeof v=="string"){g(m,w)}else{if(v instanceof Array){g(o,w)}else{if(v instanceof Function){g(f,w)}}}k(v,w)}if(t&&t.length){var x=t.pop(),p;if(x.type&&x.type.indexOf("variable")===0){if(B&&B.additionalContext){p=B.additionalContext[x.string]}if(!B||B.useGlobalScope!==false){p=p||s[x.string]}}else{if(x.type=="string"){p=""}else{if(x.type=="atom"){p=1}else{if(x.type=="function"){if(s.jQuery!=null&&(x.string=="$"||x.string=="jQuery")&&(typeof s.jQuery=="function")){p=s.jQuery()}else{if(s._!=null&&(x.string=="_")&&(typeof s._=="function")){p=s._()}}}}}}while(p!=null&&t.length){p=p[t.pop().string]}if(p!=null){q(p)}}else{for(var z=u.state.localVars;z;z=z.next){w(z.name)}for(var z=u.state.globalVars;z;z=z.next){w(z.name)}if(!B||B.useGlobalScope!==false){q(s)}g(y,w)}return A}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/hint/show-hint.css b/web/static/codemirror/addon/hint/show-hint.css new file mode 100755 index 000000000..4027ed60e --- /dev/null +++ b/web/static/codemirror/addon/hint/show-hint.css @@ -0,0 +1 @@ +.CodeMirror-hints{position:absolute;z-index:10;overflow:hidden;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,.2);box-shadow:2px 3px 5px rgba(0,0,0,.2);border-radius:3px;border:1px solid silver;background:white;font-size:90%;font-family:monospace;max-height:20em;overflow-y:auto}.CodeMirror-hint{margin:0;padding:0 4px;border-radius:2px;white-space:pre;color:black;cursor:pointer}li.CodeMirror-hint-active{background:#08f;color:white} \ No newline at end of file diff --git a/web/static/codemirror/addon/hint/show-hint.js b/web/static/codemirror/addon/hint/show-hint.js new file mode 100755 index 000000000..24ca4b4e7 --- /dev/null +++ b/web/static/codemirror/addon/hint/show-hint.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(e){var i="CodeMirror-hint";var h="CodeMirror-hint-active";e.showHint=function(q,r,s){if(!r){return q.showHint(s)}if(s&&s.async){r.async=true}var t={hint:r};if(s){for(var u in s){t[u]=s[u]}}return q.showHint(t)};e.defineExtension("showHint",function(r){r=o(this,this.getCursor("start"),r);var t=this.listSelections();if(t.length>1){return}if(this.somethingSelected()){if(!r.hint.supportsSelection){return}for(var s=0;s0&&r.to.ch-r.from.ch!=q.to.ch-q.from.ch}function o(q,v,s){var t=q.options.hintOptions;var r={};for(var u in c){r[u]=c[u]}if(t){for(var u in t){if(t[u]!==undefined){r[u]=t[u]}}}if(s){for(var u in s){if(s[u]!==undefined){r[u]=s[u]}}}if(r.hint.resolve){r.hint=r.hint.resolve(q,v)}return r}function p(q){if(typeof q=="string"){return q}else{return q.text}}function d(u,x){var r={Up:function(){x.moveFocus(-1)},Down:function(){x.moveFocus(1)},PageUp:function(){x.moveFocus(-x.menuSize()+1,true)},PageDown:function(){x.moveFocus(x.menuSize()-1,true)},Home:function(){x.setFocus(0)},End:function(){x.setFocus(x.length-1)},Enter:x.pick,Tab:x.pick,Esc:x.close};var w=u.options.customKeys;var t=w?{}:r;function s(y,A){var z;if(typeof A!="string"){z=function(B){return A(B,x)}}else{if(r.hasOwnProperty(A)){z=r[A]}else{z=A}}t[y]=z}if(w){for(var v in w){if(w.hasOwnProperty(v)){s(v,w[v])}}}var q=u.options.extraKeys;if(q){for(var v in q){if(q.hasOwnProperty(v)){s(v,q[v])}}}return t}function m(r,q){while(q&&q!=r){if(q.nodeName.toUpperCase()==="LI"&&q.parentNode==r){return q}q=q.parentNode}}function n(E,P){this.completion=E;this.data=P;this.picked=false;var v=this,A=E.cm;var M=this.hints=document.createElement("ul");M.className="CodeMirror-hints";this.selectedHint=P.selectedHint||0;var z=P.list;for(var O=0;OM.clientHeight+1;var H=A.getScrollInfo();if(D>0){var J=C.bottom-C.top,q=y.top-(y.bottom-C.top);if(q-J>0){M.style.top=(I=y.top-J)+"px";G=false}else{if(J>N){M.style.height=(N-5)+"px";M.style.top=(I=y.bottom-C.top)+"px";var x=A.getCursor();if(P.from.ch!=x.ch){y=A.cursorCoords(x);M.style.left=(w=y.left)+"px";C=M.getBoundingClientRect()}}}}var F=C.right-B;if(F>0){if(C.right-C.left>B){M.style.width=(B-5)+"px";F-=(C.right-C.left)-B}M.style.left=(w=y.left-F)+"px"}if(r){for(var L=M.firstChild;L;L=L.nextSibling){L.style.paddingRight=A.display.nativeBarWidth+"px"}}A.addKeyMap(this.keyMap=d(E,{moveFocus:function(R,Q){v.changeActive(v.selectedHint+R,Q)},setFocus:function(Q){v.changeActive(Q)},menuSize:function(){return v.screenAmount()},length:z.length,close:function(){E.close()},pick:function(){v.pick()},data:P}));if(E.options.closeOnUnfocus){var K;A.on("blur",this.onBlur=function(){K=setTimeout(function(){E.close()},100)});A.on("focus",this.onFocus=function(){clearTimeout(K)})}A.on("scroll",this.onScroll=function(){var T=A.getScrollInfo(),S=A.getWrapperElement().getBoundingClientRect();var R=I+H.top-T.top;var Q=R-(window.pageYOffset||(document.documentElement||document.body).scrollTop);if(!G){Q+=M.offsetHeight}if(Q<=S.top||Q>=S.bottom){return E.close()}M.style.top=R+"px";M.style.left=(w+H.left-T.left)+"px"});e.on(M,"dblclick",function(R){var Q=m(M,R.target||R.srcElement);if(Q&&Q.hintId!=null){v.changeActive(Q.hintId);v.pick()}});e.on(M,"click",function(R){var Q=m(M,R.target||R.srcElement);if(Q&&Q.hintId!=null){v.changeActive(Q.hintId);if(E.options.completeOnSingleClick){v.pick()}}});e.on(M,"mousedown",function(){setTimeout(function(){A.focus()},20)});e.signal(P,"select",z[0],M.firstChild);return true}n.prototype={close:function(){if(this.completion.widget!=this){return}this.completion.widget=null;this.hints.parentNode.removeChild(this.hints);this.completion.cm.removeKeyMap(this.keyMap);var q=this.completion.cm;if(this.completion.options.closeOnUnfocus){q.off("blur",this.onBlur);q.off("focus",this.onFocus)}q.off("scroll",this.onScroll)},disable:function(){this.completion.cm.removeKeyMap(this.keyMap);var q=this;this.keyMap={Enter:function(){q.picked=true}};this.completion.cm.addKeyMap(this.keyMap)},pick:function(){this.completion.pick(this.data,this.selectedHint)},changeActive:function(q,s){if(q>=this.data.list.length){q=s?this.data.list.length-1:0}else{if(q<0){q=s?0:this.data.list.length-1}}if(this.selectedHint==q){return}var r=this.hints.childNodes[this.selectedHint];r.className=r.className.replace(" "+h,"");r=this.hints.childNodes[this.selectedHint=q];r.className+=" "+h;if(r.offsetTopthis.hints.scrollTop+this.hints.clientHeight){this.hints.scrollTop=r.offsetTop+r.offsetHeight-this.hints.clientHeight+3}}e.signal(this.data,"select",this.data.list[this.selectedHint],r)},screenAmount:function(){return Math.floor(this.hints.clientHeight/this.hints.firstChild.offsetHeight)||1}};function k(r,t){if(!r.somethingSelected()){return t}var q=[];for(var s=0;s0){z(B)}else{x(A+1)}})}x(0)};r.async=true;r.supportsSelection=true;return r}else{if(t=q.getHelper(q.getCursor(),"hintWords")){return function(v){return e.hint.fromList(v,{words:t})}}else{if(e.hint.anyword){return function(v,w){return e.hint.anyword(v,w)}}else{return function(){}}}}}e.registerHelper("hint","auto",{resolve:a});e.registerHelper("hint","fromList",function(w,z){var x=w.getCursor(),r=w.getTokenAt(x);var u=e.Pos(x.line,r.end);if(r.string&&/\w/.test(r.string[r.string.length-1])){var s=r.string,v=e.Pos(x.line,r.start)}else{var s="",v=u}var y=[];for(var t=0;t,]/,closeOnUnfocus:true,completeOnSingleClick:true,container:null,customKeys:null,extraKeys:null};e.defineOption("hintOptions",null)}); \ No newline at end of file diff --git a/web/static/codemirror/addon/hint/sql-hint.js b/web/static/codemirror/addon/hint/sql-hint.js new file mode 100755 index 000000000..cb9fd7e57 --- /dev/null +++ b/web/static/codemirror/addon/hint/sql-hint.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"),require("../../mode/sql/sql"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","../../mode/sql/sql"],a)}else{a(CodeMirror)}}})(function(p){var h;var s;var i;var j={QUERY_DIV:";",ALIAS_KEYWORD:"AS"};var n=p.Pos,o=p.cmpPos;function l(v){return Object.prototype.toString.call(v)=="[object Array]"}function d(v){var w=v.doc.modeOption;if(w==="sql"){w="text/x-sql"}return p.resolveMode(w).keywords}function a(v){return typeof v=="string"?v:v.text}function b(v,w){if(l(w)){w={columns:w}}if(!w.text){w.text=v}return w}function q(w){var v={};if(l(w)){for(var y=w.length-1;y>=0;y--){var z=w[y];v[a(z).toUpperCase()]=b(a(z),z)}}else{if(w){for(var x in w){v[x.toUpperCase()]=b(x,w[x])}}}return v}function c(v){return h[v.toUpperCase()]}function g(w){var v={};for(var x in w){if(w.hasOwnProperty(x)){v[x]=w[x]}}return v}function e(w,y){var v=w.length;var x=a(y).substr(0,v);return w.toUpperCase()===x.toUpperCase()}function f(v,y,z,x){if(l(z)){for(var w=0;w0)&&o(D,z[x])<=0){H={start:G,end:z[x]};break}G=z[x]}var E=I.getRange(H.start,H.end,false);for(var x=0;xB.ch){x.end=B.ch;x.string=x.string.slice(0,B.ch-x.start)}if(x.string.match(/^[.`\w@]\w*$/)){C=x.string;v=x.start;y=x.end}else{v=y=B.ch;C=""}if(C.charAt(0)=="."||C.charAt(0)=="`"){v=t(B,x,E,z)}else{f(E,C,h,function(F){return F});f(E,C,s,function(F){return F});if(!A){f(E,C,i,function(F){return F.toUpperCase()})}}return{list:E,from:n(B.line,v),to:n(B.line,y)}})}); \ No newline at end of file diff --git a/web/static/codemirror/addon/hint/xml-hint.js b/web/static/codemirror/addon/hint/xml-hint.js new file mode 100755 index 000000000..f2321c98c --- /dev/null +++ b/web/static/codemirror/addon/hint/xml-hint.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(b){var c=b.Pos;function a(o,h){var t=h&&h.schemaInfo;var E=(h&&h.quoteChar)||'"';if(!t){return}var g=o.getCursor(),m=o.getTokenAt(g);if(m.end>g.ch){m.end=g.ch;m.string=m.string.slice(0,g.ch-m.start)}var F=b.innerMode(o.getMode(),m.state);if(F.mode.name!="xml"){return}var p=[],z=false,A;var H=/\btag\b/.test(m.type)&&!/>$/.test(m.string);var e=H&&/^\w/.test(m.string),C;if(e){var s=o.getLine(g.line).slice(Math.max(0,m.start-2),m.start);var l=/<\/$/.test(s)?"close":/<$/.test(s)?"open":null;if(l){C=m.start-(l=="close"?2:1)}}else{if(H&&m.string=="<"){l="open"}else{if(H&&m.string=="")}}else{var u=t[F.state.tagName],x=u&&u.attrs;var j=t["!attrs"];if(!x&&!j){return}if(!x){x=j}else{if(j){var r={};for(var d in j){if(j.hasOwnProperty(d)){r[d]=j[d]}}for(var d in x){if(x.hasOwnProperty(d)){r[d]=x[d]}}x=r}}if(m.type=="string"||m.string=="="){var s=o.getRange(c(g.line,Math.max(0,g.ch-60)),c(g.line,m.type=="string"?m.start:m.end));var v=s.match(/([^\s\u00a0=<>\"\']+)=$/),q;if(!v||!x.hasOwnProperty(v[1])||!(q=x[v[1]])){return}if(typeof q=="function"){q=q.call(this,o)}if(m.type=="string"){A=m.string;var w=0;if(/['"]/.test(m.string.charAt(0))){E=m.string.charAt(0);A=m.string.slice(1);w++}var D=m.string.length;if(/['"]/.test(m.string.charAt(D-1))){E=m.string.charAt(D-1);A=m.string.substr(w,D-2)}z=true}for(var B=0;B +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            + \ No newline at end of file diff --git a/web/static/codemirror/addon/scroll/annotatescrollbar.js b/web/static/codemirror/addon/scroll/annotatescrollbar.js new file mode 100755 index 000000000..88f7f83b4 --- /dev/null +++ b/web/static/codemirror/addon/scroll/annotatescrollbar.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(a){a.defineExtension("annotateScrollbar",function(c){if(typeof c=="string"){c={className:c}}return new b(this,c)});a.defineOption("scrollButtonHeight",0);function b(c,e){this.cm=c;this.options=e;this.buttonHeight=e.scrollButtonHeight||c.getOption("scrollButtonHeight");this.annotations=[];this.doRedraw=this.doUpdate=null;this.div=c.getWrapperElement().appendChild(document.createElement("div"));this.div.style.cssText="position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";this.computeScale();function f(g){clearTimeout(d.doRedraw);d.doRedraw=setTimeout(function(){d.redraw()},g)}var d=this;c.on("refresh",this.resizeHandler=function(){clearTimeout(d.doUpdate);d.doUpdate=setTimeout(function(){if(d.computeScale()){f(20)}},100)});c.on("markerAdded",this.resizeHandler);c.on("markerCleared",this.resizeHandler);if(e.listenForChanges!==false){c.on("change",this.changeHandler=function(){f(250)})}}b.prototype.computeScale=function(){var c=this.cm;var d=(c.getWrapperElement().clientHeight-c.display.barHeight-this.buttonHeight*2)/c.getScrollerElement().scrollHeight;if(d!=this.hScale){this.hScale=d;return true}};b.prototype.update=function(c){this.annotations=c;this.redraw()};b.prototype.redraw=function(o){if(o!==false){this.computeScale()}var p=this.cm,t=this.hScale;var s=document.createDocumentFragment(),g=this.annotations;var k=p.getOption("lineWrapping");var j=k&&p.defaultTextHeight()*1.5;var e=null,n=null;function q(v,u){if(e!=v.line){e=v.line;n=p.getLineHandle(e)}if(k&&n.height>j){return p.charCoords(v,"local")[u?"top":"bottom"]}var i=p.heightAtLine(n,"local");return i+(u?0:n.height)}if(p.display.barWidth){for(var h=0,l;hc+0.9){break}d=g[++h];c=q(d.to,false)*t}if(c==m){continue}var r=Math.max(c-m,3);var f=s.appendChild(document.createElement("div"));f.style.cssText="position: absolute; right: 0px; width: "+Math.max(p.display.barWidth-1,2)+"px; top: "+(m+this.buttonHeight)+"px; height: "+r+"px";f.className=this.options.className;if(d.id){f.setAttribute("annotation-id",d.id)}}}this.div.textContent="";this.div.appendChild(s)};b.prototype.clear=function(){this.cm.off("refresh",this.resizeHandler);this.cm.off("markerAdded",this.resizeHandler);this.cm.off("markerCleared",this.resizeHandler);if(this.changeHandler){this.cm.off("change",this.changeHandler)}this.div.parentNode.removeChild(this.div)}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/scroll/index.html b/web/static/codemirror/addon/scroll/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/codemirror/addon/scroll/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/codemirror/addon/scroll/scrollpastend.js b/web/static/codemirror/addon/scroll/scrollpastend.js new file mode 100755 index 000000000..9e2877c83 --- /dev/null +++ b/web/static/codemirror/addon/scroll/scrollpastend.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(b){b.defineOption("scrollPastEnd",false,function(d,f,e){if(e&&e!=b.Init){d.off("change",a);d.off("refresh",c);d.display.lineSpace.parentNode.style.paddingBottom="";d.state.scrollPastEndPadding=null}if(f){d.on("change",a);d.on("refresh",c);c(d)}});function a(d,e){if(b.changeEnd(e).line==d.lastLine()){c(d)}}function c(d){var g="";if(d.lineCount()>1){var e=d.display.scroller.clientHeight-30,f=d.getLineHandle(d.lastLine()).height;g=(e-f)+"px"}if(d.state.scrollPastEndPadding!=g){d.state.scrollPastEndPadding=g;d.display.lineSpace.parentNode.style.paddingBottom=g;d.off("refresh",c);d.setSize();d.on("refresh",c)}}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/scroll/simplescrollbars.css b/web/static/codemirror/addon/scroll/simplescrollbars.css new file mode 100755 index 000000000..b4b5245bc --- /dev/null +++ b/web/static/codemirror/addon/scroll/simplescrollbars.css @@ -0,0 +1 @@ +.CodeMirror-simplescroll-horizontal div,.CodeMirror-simplescroll-vertical div{position:absolute;background:#ccc;-moz-box-sizing:border-box;box-sizing:border-box;border:1px solid #bbb;border-radius:2px}.CodeMirror-simplescroll-horizontal,.CodeMirror-simplescroll-vertical{position:absolute;z-index:6;background:#eee}.CodeMirror-simplescroll-horizontal{bottom:0;left:0;height:8px}.CodeMirror-simplescroll-horizontal div{bottom:0;height:100%}.CodeMirror-simplescroll-vertical{right:0;top:0;width:8px}.CodeMirror-simplescroll-vertical div{right:0;width:100%}.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler,.CodeMirror-overlayscroll .CodeMirror-gutter-filler{display:none}.CodeMirror-overlayscroll-horizontal div,.CodeMirror-overlayscroll-vertical div{position:absolute;background:#bcd;border-radius:3px}.CodeMirror-overlayscroll-horizontal,.CodeMirror-overlayscroll-vertical{position:absolute;z-index:6}.CodeMirror-overlayscroll-horizontal{bottom:0;left:0;height:6px}.CodeMirror-overlayscroll-horizontal div{bottom:0;height:100%}.CodeMirror-overlayscroll-vertical{right:0;top:0;width:6px}.CodeMirror-overlayscroll-vertical div{right:0;width:100%} \ No newline at end of file diff --git a/web/static/codemirror/addon/scroll/simplescrollbars.js b/web/static/codemirror/addon/scroll/simplescrollbars.js new file mode 100755 index 000000000..303b69a98 --- /dev/null +++ b/web/static/codemirror/addon/scroll/simplescrollbars.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(b){function a(f,h,e){this.orientation=h;this.scroll=e;this.screen=this.total=this.size=1;this.pos=0;this.node=document.createElement("div");this.node.className=f+"-"+h;this.inner=this.node.appendChild(document.createElement("div"));var g=this;b.on(this.inner,"mousedown",function(n){if(n.which!=1){return}b.e_preventDefault(n);var l=g.orientation=="horizontal"?"pageX":"pageY";var o=n[l],m=g.pos;function k(){b.off(document,"mousemove",j);b.off(document,"mouseup",k)}function j(p){if(p.which!=1){return k()}g.moveTo(m+(p[l]-o)*(g.total/g.size))}b.on(document,"mousemove",j);b.on(document,"mouseup",k)});b.on(this.node,"click",function(l){b.e_preventDefault(l);var j=g.inner.getBoundingClientRect(),k;if(g.orientation=="horizontal"){k=l.clientXj.right?1:0}else{k=l.clientYj.bottom?1:0}g.moveTo(g.pos+k*g.screen)});function i(l){var k=b.wheelEventPixels(l)[g.orientation=="horizontal"?"x":"y"];var j=g.pos;g.moveTo(g.pos+k);if(g.pos!=j){b.e_preventDefault(l)}}b.on(this.node,"mousewheel",i);b.on(this.node,"DOMMouseScroll",i)}a.prototype.setPos=function(f,e){if(f<0){f=0}if(f>this.total-this.screen){f=this.total-this.screen}if(!e&&f==this.pos){return false}this.pos=f;this.inner.style[this.orientation=="horizontal"?"left":"top"]=(f*(this.size/this.total))+"px";return true};a.prototype.moveTo=function(e){if(this.setPos(e)){this.scroll(e,this.orientation)}};var d=10;a.prototype.update=function(g,h,i){var f=this.screen!=h||this.total!=g||this.size!=i;if(f){this.screen=h;this.total=g;this.size=i}var e=this.screen*(this.size/this.total);if(eh.clientWidth+1;var e=h.scrollHeight>h.clientHeight+1;this.vert.node.style.display=e?"block":"none";this.horiz.node.style.display=i?"block":"none";if(e){this.vert.update(h.scrollHeight,h.clientHeight,h.viewHeight-(i?f:0));this.vert.node.style.bottom=i?f+"px":"0"}if(i){this.horiz.update(h.scrollWidth,h.clientWidth,h.viewWidth-(e?f:0)-h.barLeft);this.horiz.node.style.right=e?f+"px":"0";this.horiz.node.style.left=h.barLeft+"px"}return{right:e?f:0,bottom:i?f:0}};c.prototype.setScrollTop=function(e){this.vert.setPos(e)};c.prototype.setScrollLeft=function(e){this.horiz.setPos(e)};c.prototype.clear=function(){var e=this.horiz.node.parentNode;e.removeChild(this.horiz.node);e.removeChild(this.vert.node)};b.scrollbarModel.simple=function(f,e){return new c("CodeMirror-simplescroll",f,e)};b.scrollbarModel.overlay=function(f,e){return new c("CodeMirror-overlayscroll",f,e)}}); \ No newline at end of file diff --git a/web/static/codemirror/addon/search/index.html b/web/static/codemirror/addon/search/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/codemirror/addon/search/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/codemirror/addon/search/jump-to-line.js b/web/static/codemirror/addon/search/jump-to-line.js new file mode 100755 index 000000000..0d0a498dd --- /dev/null +++ b/web/static/codemirror/addon/search/jump-to-line.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"),require("../dialog/dialog"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","../dialog/dialog"],a)}else{a(CodeMirror)}}})(function(a){function b(e,i,g,j,h){if(e.openDialog){e.openDialog(i,h,{value:j,selectValueOnOpen:true})}else{h(prompt(g,j))}}var d='Jump to line: (Use line:column or scroll% syntax)';function c(e,g){var f=Number(g);if(/^[-+]/.test(g)){return e.getCursor().line+f}else{return f-1}}a.commands.jumpToLine=function(e){var f=e.getCursor();b(e,d,"Jump to line:",(f.line+1)+":"+f.ch,function(i){if(!i){return}var h;if(h=/^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(i)){e.setCursor(c(e,h[1]),Number(h[2]))}else{if(h=/^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(i)){var g=Math.round(e.lineCount()*Number(h[1])/100);if(/^[-+]/.test(h[1])){g=f.line+g+1}e.setCursor(g-1,f.ch)}else{if(h=/^\s*\:?\s*([\+\-]?\d+)\s*/.exec(i)){e.setCursor(c(e,h[1]),f.ch)}}}})};a.keyMap["default"]["Alt-G"]="jumpToLine"}); \ No newline at end of file diff --git a/web/static/codemirror/addon/search/match-highlighter.js b/web/static/codemirror/addon/search/match-highlighter.js new file mode 100755 index 000000000..5bc46f2ed --- /dev/null +++ b/web/static/codemirror/addon/search/match-highlighter.js @@ -0,0 +1 @@ +(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"),require("./matchesonscrollbar"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","./matchesonscrollbar"],a)}else{a(CodeMirror)}}})(function(d){var c={style:"matchhighlight",minChars:2,delay:100,wordsOnly:false,annotateScrollbar:false,showToken:false,trim:true};function j(n){this.options={};for(var m in c){this.options[m]=(n&&n.hasOwnProperty(m)?n:c)[m]}this.overlay=this.timeout=null;this.matchesonscroll=null;this.active=false}d.defineOption("highlightSelectionMatches",false,function(m,p,n){if(n&&n!=d.Init){l(m);clearTimeout(m.state.matchHighlighter.timeout);m.state.matchHighlighter=null;m.off("cursorActivity",i);m.off("focus",h)}if(p){var o=m.state.matchHighlighter=new j(p);if(m.hasFocus()){o.active=true;f(m)}else{m.on("focus",h)}m.on("cursorActivity",i)}});function i(m){var n=m.state.matchHighlighter;if(n.active||m.hasFocus()){a(m,n)}}function h(m){var n=m.state.matchHighlighter;if(!n.active){n.active=true;a(m,n)}}function a(m,n){clearTimeout(n.timeout);n.timeout=setTimeout(function(){f(m)},n.options.delay)}function b(n,r,p,o){var q=n.state.matchHighlighter;n.addOverlay(q.overlay=e(r,p,o));if(q.options.annotateScrollbar&&n.showMatchesOnScrollbar){var m=p?new RegExp("\\b"+r+"\\b"):r;q.matchesonscroll=n.showMatchesOnScrollbar(m,false,{className:"CodeMirror-selection-highlight-scrollbar"})}}function l(m){var n=m.state.matchHighlighter;if(n.overlay){m.removeOverlay(n.overlay);n.overlay=null;if(n.matchesonscroll){n.matchesonscroll.clear();n.matchesonscroll=null}}}function f(m){m.operation(function(){var n=m.state.matchHighlighter;l(m);if(!m.somethingSelected()&&n.options.showToken){var u=n.options.showToken===true?/[\w$]/:n.options.showToken;var t=m.getCursor(),v=m.getLine(t.line),o=t.ch,p=o;while(o&&u.test(v.charAt(o-1))){--o}while(p=n.options.minChars){b(m,q,false,n.options.style)}})}function g(m,r,q){var o=m.getRange(r,q);if(o.match(/^\w+$/)!==null){if(r.ch>0){var p={line:r.line,ch:r.ch-1};var n=m.getRange(p,r);if(n.match(/\W/)===null){return false}}if(q.ch=this.gap.to){break}if(f.to.line>=this.gap.from){this.matches.splice(g--,1)}}var h=this.cm.getSearchCursor(this.query,b.Pos(this.gap.from,0),this.caseFold);var e=this.options&&this.options.maxMatches||d;while(h.findNext()){var f={from:h.from(),to:h.to()};if(f.from.line>=this.gap.to){break}this.matches.splice(g++,0,f);if(this.matches.length>e){break}}this.gap=null};function a(e,g,f){if(e<=g){return e}return Math.max(g,e+f)}c.prototype.onChange=function(k){var l=k.from.line;var e=b.changeEnd(k).line;var f=e-k.to.line;if(this.gap){this.gap.from=Math.min(a(this.gap.from,l,f),k.from.line);this.gap.to=Math.max(a(this.gap.to,l,f),k.from.line)}else{this.gap={from:k.from.line,to:e+1}}if(f){for(var h=0;hv.cursorCoords(H,"window").top){(w=G).style.opacity=0.4}})};d(v,u,B,A,function(E,G){var D=n.keyName(E);var F=n.keyMap[v.getOption("keyMap")][D];if(!F){F=v.getOption("extraKeys")[D]}if(F=="findNext"||F=="findPrev"||F=="findPersistentNext"||F=="findPersistentPrev"){n.e_stop(E);t(v,s(v),G);v.execCommand(F)}else{if(F=="find"||F=="findPersistent"){n.e_stop(E);A(G,E)}}});if(z&&B){t(v,C,B);f(v,y)}}else{p(v,u,"Search for:",B,function(D){if(D&&!C.query){v.operation(function(){t(v,C,D);C.posFrom=C.posTo=v.getCursor();f(v,y)})}})}}function f(v,w,x){v.operation(function(){var y=s(v);var z=b(v,y.query,w?y.posFrom:y.posTo);if(!z.find(w)){z=b(v,y.query,w?n.Pos(v.lastLine()):n.Pos(v.firstLine(),0));if(!z.find(w)){return}}v.setSelection(z.from(),z.to());v.scrollIntoView({from:z.from(),to:z.to()},20);y.posFrom=z.from();y.posTo=z.to();if(x){x(z.from(),z.to())}})}function q(v){v.operation(function(){var w=s(v);w.lastQuery=w.query;if(!w.query){return}w.query=w.queryText=null;v.removeOverlay(w.overlay);if(w.annotate){w.annotate.clear();w.annotate=null}})}var a=' (Use /re/ syntax for regexp search)';var g='到: ';var o="替换? ";function c(v,w,x){v.operation(function(){for(var z=b(v,w);z.findNext();){if(typeof w!="string"){var y=v.getRange(z.from(),z.to()).match(w);z.replace(x.replace(/\$(\d)/g,function(A,B){return y[B]}))}else{z.replace(x)}}})}function r(v,x){if(v.getOption("readOnly")){return}var y=v.getSelection()||s(v).lastQuery;var w=x?"全部替换:":"替换:";p(v,w+a,w,y,function(z){if(!z){return}z=h(z);p(v,g,"更换:","",function(D){D=i(D);if(x){c(v,z,D)}else{q(v);var C=b(v,z,v.getCursor("from"));var B=function(){var F=C.from(),E;if(!(E=C.findNext())){C=b(v,z);if(!(E=C.findNext())||(F&&C.from().line==F.line&&C.from().ch==F.ch)){return}}v.setSelection(C.from(),C.to());v.scrollIntoView({from:C.from(),to:C.to()});l(v,o,"替换?",[function(){A(E)},B,function(){c(v,z,D)}])};var A=function(E){C.replace(typeof z=="string"?D:D.replace(/\$(\d)/g,function(F,G){return E[G]}));B()};B()}})})}n.commands.find=function(v){q(v);m(v)};n.commands.findPersistent=function(v){q(v);m(v,false,true)};n.commands.findPersistentNext=function(v){m(v,false,true,true)};n.commands.findPersistentPrev=function(v){m(v,true,true,true)};n.commands.findNext=m;n.commands.findPrev=function(v){m(v,true)};n.commands.clearSearch=q;n.commands.replace=r;n.commands.replaceAll=function(v){r(v,true)}});(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],a)}else{a(CodeMirror)}}})(function(a){var b=a.Pos;function c(k,i,l,g){this.atOccurrence=false;this.doc=k;if(g==null&&typeof i=="string"){g=false}l=l?k.clipPos(l):b(0,0);this.pos={from:l,to:l};if(typeof i!="string"){if(!i.global){i=new RegExp(i.source,i.ignoreCase?"ig":"g")}this.matches=function(p,t){if(p){i.lastIndex=0;var m=k.getLine(t.line).slice(0,t.ch),r=0,o,s;for(;;){i.lastIndex=r;var q=i.exec(m);if(!q){break}o=q;s=o.index;r=o.index+(o[0].length||1);if(r==m.length){break}}var n=(o&&o[0].length)||0;if(!n){if(s==0&&m.length==0){o=undefined}else{if(s!=k.getLine(t.line).length){n++}}}}else{i.lastIndex=t.ch;var m=k.getLine(t.line),o=i.exec(m);var n=(o&&o[0].length)||0;var s=o&&o.index;if(s+n!=m.length&&!n){n=1}}if(o&&n){return{from:b(t.line,s),to:b(t.line,s+n),match:o}}}}else{var e=i;if(g){i=i.toLowerCase()}var f=g?function(m){return m.toLowerCase()}:function(m){return m};var j=i.split("\n");if(j.length==1){if(!i.length){this.matches=function(){}}else{this.matches=function(o,q){if(o){var p=k.getLine(q.line).slice(0,q.ch),m=f(p);var n=m.lastIndexOf(i);if(n>-1){n=d(p,m,n);return{from:b(q.line,n),to:b(q.line,n+e.length)}}}else{var p=k.getLine(q.line).slice(q.ch),m=f(p);var n=m.indexOf(i);if(n>-1){n=d(p,m,n)+q.ch;return{from:b(q.line,n),to:b(q.line,n+e.length)}}}}}}else{var h=e.split("\n");this.matches=function(n,p){var t=j.length-1;if(n){if(p.line-(j.length-1)=1;--m,--o){if(j[m]!=f(k.getLine(o))){return}}var u=k.getLine(o),q=u.length-h[0].length;if(f(u.slice(q))!=j[0]){return}return{from:b(o,q),to:s}}else{if(p.line+(j.length-1)>k.lastLine()){return}var u=k.getLine(p.line),q=u.length-h[0].length;if(f(u.slice(q))!=j[0]){return}var r=b(p.line,q);for(var o=p.line+1,m=1;mh){--f}else{return f}}}}a.defineExtension("getSearchCursor",function(f,g,e){return new c(this.doc,f,g,e)});a.defineDocExtension("getSearchCursor",function(f,g,e){return new c(this,f,g,e)});a.defineExtension("selectMatches",function(g,f){var e=[];var h=this.getSearchCursor(g,this.getCursor("from"),f);while(h.findNext()){if(a.cmpPos(h.to(),this.getCursor("to"))>0){break}e.push({anchor:h.from(),head:h.to()})}if(e.length){this.setSelections(e,0)}})});(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"),require("../dialog/dialog"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","../dialog/dialog"],a)}else{a(CodeMirror)}}})(function(a){function b(e,i,g,j,h){if(e.openDialog){e.openDialog(i,h,{value:j,selectValueOnOpen:true})}else{h(prompt(g,j))}}var d='Jump to line: (Use line:column or scroll% syntax)';function c(e,g){var f=Number(g);if(/^[-+]/.test(g)){return e.getCursor().line+f}else{return f-1}}a.commands.jumpToLine=function(e){var f=e.getCursor();b(e,d,"Jump to line:",(f.line+1)+":"+f.ch,function(i){if(!i){return}var h;if(h=/^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(i)){e.setCursor(c(e,h[1]),Number(h[2]))}else{if(h=/^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(i)){var g=Math.round(e.lineCount()*Number(h[1])/100);if(/^[-+]/.test(h[1])){g=f.line+g+1}e.setCursor(g-1,f.ch)}else{if(h=/^\s*\:?\s*([\+\-]?\d+)\s*/.exec(i)){e.setCursor(c(e,h[1]),f.ch)}}}})};a.keyMap["default"]["Alt-G"]="jumpToLine"});(function(a){if(typeof exports=="object"&&typeof module=="object"){a(require("../../lib/codemirror"),require("./searchcursor"),require("../scroll/annotatescrollbar"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","./searchcursor","../scroll/annotatescrollbar"],a)}else{a(CodeMirror)}}})(function(b){b.defineExtension("showMatchesOnScrollbar",function(g,f,e){if(typeof e=="string"){e={className:e}}if(!e){e={}}return new c(this,g,f,e)});function c(e,j,i,h){this.cm=e;this.options=h;var f={listenForChanges:false};for(var k in h){f[k]=h[k]}if(!f.className){f.className="CodeMirror-search-match"}this.annotation=e.annotateScrollbar(f);this.query=j;this.caseFold=i;this.gap={from:e.firstLine(),to:e.lastLine()+1};this.matches=[];this.update=null;this.findMatches();this.annotation.update(this.matches);var g=this;e.on("change",this.changeHandler=function(l,m){g.onChange(m)})}var d=1000;c.prototype.findMatches=function(){if(!this.gap){return}for(var g=0;g=this.gap.to){break}if(f.to.line>=this.gap.from){this.matches.splice(g--,1)}}var h=this.cm.getSearchCursor(this.query,b.Pos(this.gap.from,0),this.caseFold);var e=this.options&&this.options.maxMatches||d;while(h.findNext()){var f={from:h.from(),to:h.to()};if(f.from.line>=this.gap.to){break}this.matches.splice(g++,0,f);if(this.matches.length>e){break}}this.gap=null};function a(e,g,f){if(e<=g){return e}return Math.max(g,e+f)}c.prototype.onChange=function(k){var l=k.from.line;var e=b.changeEnd(k).line;var f=e-k.to.line;if(this.gap){this.gap.from=Math.min(a(this.gap.from,l,f),k.from.line);this.gap.to=Math.max(a(this.gap.to,l,f),k.from.line)}else{this.gap={from:k.from.line,to:e+1}}if(f){for(var h=0;h=n.options.minChars){b(m,q,false,n.options.style)}})}function g(m,r,q){var o=m.getRange(r,q);if(o.match(/^\w+$/)!==null){if(r.ch>0){var p={line:r.line,ch:r.ch-1};var n=m.getRange(p,r);if(n.match(/\W/)===null){return false}}if(q.ch-1){n=d(p,m,n);return{from:b(q.line,n),to:b(q.line,n+e.length)}}}else{var p=k.getLine(q.line).slice(q.ch),m=f(p);var n=m.indexOf(i);if(n>-1){n=d(p,m,n)+q.ch;return{from:b(q.line,n),to:b(q.line,n+e.length)}}}}}}else{var h=e.split("\n");this.matches=function(n,p){var t=j.length-1;if(n){if(p.line-(j.length-1)=1;--m,--o){if(j[m]!=f(k.getLine(o))){return}}var u=k.getLine(o),q=u.length-h[0].length;if(f(u.slice(q))!=j[0]){return}return{from:b(o,q),to:s}}else{if(p.line+(j.length-1)>k.lastLine()){return}var u=k.getLine(p.line),q=u.length-h[0].length;if(f(u.slice(q))!=j[0]){return}var r=b(p.line,q);for(var o=p.line+1,m=1;mh){--f}else{return f}}}}a.defineExtension("getSearchCursor",function(f,g,e){return new c(this.doc,f,g,e)});a.defineDocExtension("getSearchCursor",function(f,g,e){return new c(this,f,g,e)});a.defineExtension("selectMatches",function(g,f){var e=[];var h=this.getSearchCursor(g,this.getCursor("from"),f);while(h.findNext()){if(a.cmpPos(h.to(),this.getCursor("to"))>0){break}e.push({anchor:h.from(),head:h.to()})}if(e.length){this.setSelections(e,0)}})}); \ No newline at end of file diff --git a/web/static/codemirror/index.html b/web/static/codemirror/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/codemirror/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/codemirror/lib/codemirror.css b/web/static/codemirror/lib/codemirror.css new file mode 100755 index 000000000..48e2ac5a8 --- /dev/null +++ b/web/static/codemirror/lib/codemirror.css @@ -0,0 +1,4 @@ +.CodeMirror{font-family:monospace;height:auto;color:black;border:#ccc 1px solid}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:white}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:black}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid black;border-right:0;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:-20px;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:bold}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error{color:#f00}.cm-invalidchar{color:#f00}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:white}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none!important;border:none!important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper{-webkit-user-select:none;-moz-user-select:none;user-select:none}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:none;font-variant-ligatures:none}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-code{outline:0}.CodeMirror-scroll,.CodeMirror-sizer,.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute;pointer-events:none}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0} +.CodeMirror-hints{position:absolute;z-index:10;overflow:hidden;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,.2);box-shadow:2px 3px 5px rgba(0,0,0,.2);border-radius:3px;border:1px solid silver;background:white;font-size:90%;font-family:monospace;max-height:20em;overflow-y:auto}.CodeMirror-hint{margin:0;padding:0 4px;border-radius:2px;white-space:pre;color:black;cursor:pointer}li.CodeMirror-hint-active{background:#08f;color:white} +.CodeMirror-dialog{position:absolute;left:0;right:0;background:inherit;z-index:15;padding:.4em .8em;overflow:hidden;color:inherit}.CodeMirror-dialog-top{border-bottom:1px solid #eee;top:0}.CodeMirror-dialog-bottom{border-top:1px solid #eee;bottom:0}.CodeMirror-dialog input{border:0;outline:0;background:transparent;width:20em;color:inherit;font-family:monospace}.CodeMirror-dialog button{font-size:70%}.Dialog-close{color:#111;float:right;font-family:Arial;font-size:16px;height:30px;line-height:30px;text-align:center;width:30px;cursor:pointer} +.CodeMirror-search-match{background:gold;border-top:1px solid orange;border-bottom:1px solid orange;-moz-box-sizing:border-box;box-sizing:border-box;opacity:.5} \ No newline at end of file diff --git a/web/static/codemirror/lib/codemirror.js b/web/static/codemirror/lib/codemirror.js new file mode 100755 index 000000000..b9cb291c8 --- /dev/null +++ b/web/static/codemirror/lib/codemirror.js @@ -0,0 +1 @@ +(function(c,d){typeof exports==="object"&&typeof module!=="undefined"?module.exports=d():typeof define==="function"&&define.amd?define(d):(c.CodeMirror=d())}(this,(function(){var iP=navigator.userAgent;var ir=navigator.platform;var kc=/gecko\/\d/i.test(iP);var kv=/MSIE \d/.test(iP);var nf=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(iP);var kj=kv||nf;var kH=kj&&(kv?document.documentMode||6:nf[1]);var mU=/WebKit\//.test(iP);var j6=mU&&/Qt\/\d+\.\d+/.test(iP);var ju=/Chrome\//.test(iP);var iL=/Opera\//.test(iP);var ob=/Apple Computer/.test(navigator.vendor);var jI=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(iP);var iq=/PhantomJS/.test(iP);var hE=/AppleWebKit/.test(iP)&&/Mobile\/\w+/.test(iP);var ie=hE||/Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(iP);var k9=hE||/Mac/.test(ir);var i6=/\bCrOS\b/.test(iP);var nW=/win/i.test(ir);var nE=iL&&iP.match(/Version\/(\d*\.\d*)/);if(nE){nE=Number(nE[1])}if(nE&&nE>=15){iL=false;mU=true}var mW=k9&&(j6||iL&&(nE==null||nE<12.11));var hT=kc||(kj&&kH>=9);function lO(a){return new RegExp("(^|\\s)"+a+"(?:$|\\s)\\s*")}var kS=function(d,c){var b=d.className;var e=lO(c).exec(b);if(e){var a=b.slice(e.index+e[0].length);d.className=b.slice(0,e.index)+(a?e[1]+a:"")}};function iN(b){for(var a=b.childNodes.length;a>0;--a){b.removeChild(b.firstChild)}return b}function mR(a,b){return iN(a).appendChild(b)}function gZ(f,b,c,d){var a=document.createElement(f);if(c){a.className=c}if(d){a.style.cssText=d}if(typeof b=="string"){a.appendChild(document.createTextNode(b))}else{if(b){for(var e=0;e=f){return h+(f-c)}h+=g-c;h+=b-(h%b);c=g+1}}function hA(){this.id=null}hA.prototype.set=function(a,b){clearTimeout(this.id);this.id=setTimeout(b,a)};function jb(a,c){for(var b=0;b=e){return a+Math.min(d,e-f)}f+=g-a;f+=b-(f%b);a=g+1;if(f>=e){return a}}}var gU=[""];function j7(a){while(gU.length<=a){gU.push(ji(gU)+" ")}return gU[a]}function ji(a){return a[a.length-1]}function mO(a,b){var d=[];for(var c=0;c"\x80"&&(a.toUpperCase()!=a.toLowerCase()||mu.test(a))}function mo(a,b){if(!b){return h0(a)}if(b.source.indexOf("\\w")>-1&&h0(a)){return true}return b.test(a)}function hS(a){for(var b in a){if(a.hasOwnProperty(b)&&a[b]){return false}}return true}var ky=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;function iB(a){return a.charCodeAt(0)>=768&&ky.test(a)}function iU(b,c,d){var a=this;this.input=d;a.scrollbarFiller=gZ("div",null,"CodeMirror-scrollbar-filler");a.scrollbarFiller.setAttribute("cm-not-content","true");a.gutterFiller=gZ("div",null,"CodeMirror-gutter-filler");a.gutterFiller.setAttribute("cm-not-content","true");a.lineDiv=gZ("div",null,"CodeMirror-code");a.selectionDiv=gZ("div",null,null,"position: relative; z-index: 1");a.cursorDiv=gZ("div",null,"CodeMirror-cursors");a.measure=gZ("div",null,"CodeMirror-measure");a.lineMeasure=gZ("div",null,"CodeMirror-measure");a.lineSpace=gZ("div",[a.measure,a.lineMeasure,a.selectionDiv,a.cursorDiv,a.lineDiv],null,"position: relative; outline: none");a.mover=gZ("div",[gZ("div",[a.lineSpace],"CodeMirror-lines")],null,"position: relative");a.sizer=gZ("div",[a.mover],"CodeMirror-sizer");a.sizerWidth=null;a.heightForcer=gZ("div",null,null,"position: absolute; height: "+ko+"px; width: 1px;");a.gutters=gZ("div",null,"CodeMirror-gutters");a.lineGutter=null;a.scroller=gZ("div",[a.sizer,a.heightForcer,a.gutters],"CodeMirror-scroll");a.scroller.setAttribute("tabIndex","-1");a.wrapper=gZ("div",[a.scrollbarFiller,a.gutterFiller,a.scroller],"CodeMirror");if(kj&&kH<8){a.gutters.style.zIndex=-1;a.scroller.style.paddingRight=0}if(!mU&&!(kc&&ie)){a.scroller.draggable=true}if(b){if(b.appendChild){b.appendChild(a.wrapper)}else{b(a.wrapper)}}a.viewFrom=a.viewTo=c.first;a.reportedViewFrom=a.reportedViewTo=c.first;a.view=[];a.renderedView=null;a.externalMeasured=null;a.viewOffset=0;a.lastWrapHeight=a.lastWrapWidth=0;a.updateLineNumbers=null;a.nativeBarWidth=a.barHeight=a.barWidth=0;a.scrollbarsClipped=false;a.lineNumWidth=a.lineNumInnerWidth=a.lineNumChars=null;a.alignWidgets=false;a.cachedCharWidth=a.cachedTextHeight=a.cachedPaddingH=null;a.maxLine=null;a.maxLineLength=0;a.maxLineChanged=false;a.wheelDX=a.wheelDY=a.wheelStartX=a.wheelStartY=null;a.shift=false;a.selForContextMenu=null;a.activeTouch=null;d.init(a)}function hm(c,a){a-=c.first;if(a<0||a>=c.size){throw new Error("There is no line "+(a+c.first)+" in the document.")}var f=c;while(!f.lines){for(var e=0;;++e){var b=f.children[e],d=b.chunkSize();if(a=b.first&&aa){return lA(a,hm(c,a).text.length)}return iu(b,hm(c,b.line).text.length)}function iu(b,c){var a=b.ch;if(a==null||a>c){return lA(b.line,c)}else{if(a<0){return lA(b.line,0)}else{return b}}}function lo(b,a){var d=[];for(var c=0;c=g:a.to>g);(b||(b=[])).push(new h7(d,a.from,f?null:a.to))}}}return b}function oc(h,f,c){var b;if(h){for(var e=0;e=f:a.to>f);if(g||a.from==f&&d.type=="bookmark"&&(!c||a.marker.insertLeft)){var j=a.from==null||(d.inclusiveLeft?a.from<=f:a.from0&&h){for(var l=0;l0){continue}var d=[k,1],n=kI(m.from,l.from),e=kI(m.to,l.to);if(n<0||!f.inclusiveLeft&&!n){d.push({from:m.from,to:l.from})}if(e>0||!f.inclusiveRight&&!e){d.push({from:l.to,to:m.to})}h.splice.apply(h,d);k+=d.length-1}}return h}function hV(c){var a=c.markedSpans;if(!a){return}for(var b=0;b=0&&g<=0||c<=0&&g>=0){continue}if(c<=0&&(k.marker.inclusiveRight&&h.inclusiveLeft?kI(b.to,f)>=0:kI(b.to,f)>0)||c>=0&&(k.marker.inclusiveRight&&h.inclusiveLeft?kI(b.from,e)<=0:kI(b.from,e)<0)){return true}}}}function jV(b){var a;while(a=kb(b)){b=a.find(-1,true).line}return b}function kW(b){var a,c;while(a=jy(b)){b=a.find(1,true).line;(c||(c=[])).push(b)}return c}function nJ(b,d){var a=hm(b,d),c=jV(a);if(a==c){return d}return m5(c)}function iJ(b,c){if(c>b.lastLine()){return c}var d=hm(b,c),a;if(!il(b,d)){return c}while(a=jy(d)){d=a.find(1,true).line}return m5(d)+1}function il(a,d){var e=gN&&d.markedSpans;if(e){for(var b=void 0,c=0;cb.maxLineLength){b.maxLineLength=e;b.maxLine=d}})}function iG(g,a,b,c){if(!g){return c(a,b,"ltr")}var d=false;for(var e=0;ea||a==b&&f.to==a){c(Math.max(f.from,a),Math.min(f.to,b),f.level==1?"rtl":"ltr");d=true}}if(!d){c(a,b,"ltr")}}function kT(a){return a.level%2?a.to:a.from}function hJ(a){return a.level%2?a.from:a.to}function md(b){var a=ld(b);return a?kT(a[0]):0}function lj(b){var a=ld(b);if(!a){return b.text.length}return hJ(ji(a))}function ne(d,c,b){var a=d[0].level;if(c==a){return true}if(b==a){return false}return ca){return d}if((b.from==a||b.to==a)){if(c==null){c=d}else{if(ne(e,b.level,e[c].level)){if(b.from!=b.to){hz=c}return d}else{if(b.from!=b.to){hz=d}return c}}}}return c}function hl(b,a,d,c){if(!c){return a+d}do{a+=d}while(a>0&&iB(b.text.charAt(a)));return a}function ka(f,a,g,e){var d=ld(f);if(!d){return nn(f,a,g,e)}var b=n6(d,a),h=d[b];var c=hl(f,a,h.level%2?-g:g,e);for(;;){if(c>h.from&&c0)==h.level%2?h.to:h.from}else{h=d[b+=g];if(!h){return null}if((g>0)==h.level%2){c=hl(f,h.to,-1,e)}else{c=hl(f,h.from,1,e)}}}}function nn(c,a,e,d){var b=a+e;if(d){while(b>0&&iB(c.text.charAt(b))){b+=e}}return b<0||b>c.text.length?null:b}var mc=(function(){var e="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";var g="rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm";function h(l){if(l<=247){return e.charAt(l)}else{if(1424<=l&&l<=1524){return"R"}else{if(1536<=l&&l<=1773){return g.charAt(l-1536)}else{if(1774<=l&&l<=2220){return"r"}else{if(8192<=l&&l<=8203){return"w"}else{if(l==8204){return"b"}else{return"L"}}}}}}}var b=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;var a=/[stwN]/,j=/[LRr]/,k=/[Lb1n]/,f=/[1n]/;var c="L";function d(l,m,n){this.level=l;this.from=m;this.to=n}return function(s){if(!b.test(s)){return false}var R=s.length,F=[];for(var l=0;l-1){d[e]=c.slice(0,f).concat(c.slice(f+1))}}}}}function n9(a,b){var e=h4(a,b);if(!e.length){return}var d=Array.prototype.slice.call(arguments,2);for(var c=0;c0}function nA(a){a.prototype.on=function(c,b){nY(this,c,b)};a.prototype.off=function(c,b){ih(this,c,b)}}function l7(a){if(a.preventDefault){a.preventDefault()}else{a.returnValue=false}}function iT(a){if(a.stopPropagation){a.stopPropagation()}else{a.cancelBubble=true}}function na(a){return a.defaultPrevented!=null?a.defaultPrevented:a.returnValue==false}function jK(a){l7(a);iT(a)}function mb(a){return a.target||a.srcElement}function hi(b){var a=b.which;if(a==null){if(b.button&1){a=1}else{if(b.button&2){a=3}else{if(b.button&4){a=2}}}}if(k9&&b.ctrlKey&&a==1){a=3}return a}var kr=function(){if(kj&&kH<9){return false}var a=gZ("div");return"draggable" in a||"dragDrop" in a}();var hk;function lP(a){if(hk==null){var b=gZ("span","\u200b");mR(a,gZ("span",[b,document.createTextNode("x")]));if(a.firstChild.offsetHeight!=0){hk=b.offsetWidth<=1&&b.offsetHeight>2&&!(kj&&kH<8)}}var c=hk?gZ("span","\u200b"):gZ("span","\u00a0",null,"display: inline-block; width: 1px; margin-right: -1px");c.setAttribute("cm-text","");return c}var hj;function m2(b){if(hj!=null){return hj}var a=mR(b,document.createTextNode("A\u062eA"));var c=ki(a,0,1).getBoundingClientRect();var d=ki(a,1,2).getBoundingClientRect();iN(b);if(!c||c.left==c.right){return false}return hj=(d.right-c.right<3)}var hd="\n\nb".split(/\n/).length!=3?function(b){var a=0,e=[],c=b.length;while(a<=c){var d=b.indexOf("\n",a);if(d==-1){d=b.length}var f=b.slice(a,b.charAt(d-1)=="\r"?d-1:d);var g=f.indexOf("\r");if(g!=-1){e.push(f.slice(0,g));a+=g+1}else{e.push(f);a=d+1}}return e}:function(a){return a.split(/\r\n?|\n/)};var lv=window.getSelection?function(b){try{return b.selectionStart!=b.selectionEnd}catch(a){return false}}:function(b){var c;try{c=b.ownerDocument.selection.createRange()}catch(a){}if(!c||c.parentElement()!=b){return false}return c.compareEndPoints("StartToEnd",c)!=0};var jz=(function(){var a=gZ("div");if("oncopy" in a){return true}a.setAttribute("oncopy","return;");return typeof a.oncopy=="function"})();var hu=null;function n2(d){if(hu!=null){return hu}var c=mR(d,gZ("span","x"));var b=c.getBoundingClientRect();var a=ki(c,0,1).getBoundingClientRect();return hu=Math.abs(b.left-a.left)>1}var le={};var nQ={};function jE(a,b){if(arguments.length>2){b.dependencies=Array.prototype.slice.call(arguments,2)}le[a]=b}function lT(b,a){nQ[b]=a}function hP(a){if(typeof a=="string"&&nQ.hasOwnProperty(a)){a=nQ[a]}else{if(a&&typeof a.name=="string"&&nQ.hasOwnProperty(a.name)){var b=nQ[a.name];if(typeof b=="string"){b={name:b}}a=ks(b,a);a.name=b.name}else{if(typeof a=="string"&&/^[\w\-]+\/[\w\-]+\+xml$/.test(a)){return hP("application/xml")}else{if(typeof a=="string"&&/^[\w\-]+\/[\w\-]+\+json$/.test(a)){return hP("application/json")}}}}if(typeof a=="string"){return{name:a}}else{return a||{name:"null"}}}function h8(f,g){g=hP(g);var c=le[g.name];if(!c){return h8(f,"text/plain")}var b=c(f,g);if(iV.hasOwnProperty(g.name)){var e=iV[g.name];for(var a in e){if(!e.hasOwnProperty(a)){continue}if(b.hasOwnProperty(a)){b["_"+a]=b[a]}b[a]=e[a]}}b.name=g.name;if(g.helperType){b.helperType=g.helperType}if(g.modeProps){for(var d in g.modeProps){b[d]=g.modeProps[d]}}return b}var iV={};function jc(b,c){var a=iV.hasOwnProperty(b)?iV[b]:(iV[b]={});n0(c,a)}function nK(c,b){if(b===true){return b}if(c.copyState){return c.copyState(b)}var d={};for(var a in b){var e=b[a];if(e instanceof Array){e=e.concat([])}d[a]=e}return d}function hX(b,a){var c;while(b.innerMode){c=b.innerMode(a);if(!c||c.mode==b){break}a=c.state;b=c.mode}return c||{mode:b,state:a}}function nR(b,c,a){return b.startState?b.startState(c,a):true}var hU=function(a,b){this.pos=this.start=0;this.string=a;this.tabSize=b||8;this.lastColumnPos=this.lastColumnValue=0;this.lineStart=0};hU.prototype={eol:function(){return this.pos>=this.string.length},sol:function(){return this.pos==this.lineStart},peek:function(){return this.string.charAt(this.pos)||undefined},next:function(){if(this.posb},eatSpace:function(){var b=this;var a=this.pos;while(/[\s\u00a0]/.test(this.string.charAt(this.pos))){++b.pos}return this.pos>a},skipToEnd:function(){this.pos=this.string.length},skipTo:function(a){var b=this.string.indexOf(a,this.pos);if(b>-1){this.pos=b;return true}},backUp:function(a){this.pos-=a},column:function(){if(this.lastColumnPos0){return null}if(e&&f!==false){this.pos+=e[0].length}return e}},current:function(){return this.string.slice(this.start,this.pos)},hideFirstChars:function(b,a){this.lineStart+=b;try{return a()}finally{this.lineStart-=b}}};function ib(f,g,b,c){var d=[f.state.modeGen],e={};j2(f,g.text,f.doc.mode,b,function(k,j){return d.push(k,j)},e,c);var h=function(j){var l=f.state.overlays[j],k=1,m=0;j2(f,g.text,l.mode,true,function(r,p){var n=k;while(mr){d.splice(k,1,r,d[k+1],q)}k+=2;m=Math.min(r,q)}if(!p){return}if(l.opaque){d.splice(n,k-n,r,"overlay "+p);k=n+2}else{for(;ne.options.maxHighlightLength?nK(e.doc.mode,c):c);d.stateAfter=c;d.styles=b.styles;if(b.classes){d.styleClasses=b.classes}else{if(d.styleClasses){d.styleClasses=null}}if(a===e.doc.frontier){e.doc.frontier++}}return d.styles}function kM(e,a,g){var c=e.doc,d=e.display;if(!c.mode.startState){return true}var b=mv(e,a,g),f=b>c.first&&hm(c,b-1).stateAfter;if(!f){f=nR(c.mode)}else{f=nK(c.mode,f)}c.iter(b,a,function(h){k0(e,h.text,f);var j=b==a-1||b%5==0||b>=d.viewFrom&&bb.start){return d}}throw new Error("Mode "+a.name+" failed to advance stream.")}function j4(c,f,j,k){var d=function(n){return({start:b.start,end:b.pos,string:b.current(),type:l||null,state:n?nK(e.mode,m):m})};var e=c.doc,h=e.mode,l;f=i3(e,f);var a=hm(e,f.line),m=kM(c,f.line,j);var b=new hU(a.text,c.options.tabSize),g;if(k){g=[]}while((k||b.pose.options.maxHighlightLength){o=false;if(m){k0(e,c,p,d.pos)}d.pos=c.length;g=null}else{g=i8(jj(l,d,p,a),n)}if(a){var b=a[0].name;if(b){g="m-"+(g?b+" "+g:b)}}if(!o||j!=g){while(hj;--a){if(a<=f.first){return f.first}var c=hm(f,a-1);if(c.stateAfter&&(!h||a<=f.frontier)){return a}var d=mV(c.text,null,e.options.tabSize);if(g==null||k>d){g=a-1;k=d}}return g}function gW(b,c,a){this.text=b;mM(this,c);this.height=a?a(this):1}nA(gW);gW.prototype.lineNo=function(){return m5(this)};function hY(e,a,d,c){e.text=a;if(e.stateAfter){e.stateAfter=null}if(e.styles){e.styles=null}if(e.order!=null){e.order=null}hV(e);mM(e,d);var b=c?c(e):1;if(b!=e.height){gV(e,b)}}function nv(a){a.parent=null;hV(a)}var lE={};var nM={};function hO(b,c){if(!b||/^\s*$/.test(b)){return null}var a=c.addModeClass?nM:lE;return a[b]||(a[b]=b.replace(/\S+/g,"cm-$&"))}function j0(c,d){var e=gZ("span",null,null,mU?"padding-right: .1px":null);var f={pre:gZ("pre",[e],"CodeMirror-line"),content:e,col:0,pos:0,cm:c,trailingSpace:false,splitSpaces:(kj||mU)&&c.getOption("lineWrapping")};d.measure={};for(var g=0;g<=(d.rest?d.rest.length:0);g++){var a=g?d.rest[g-1]:d.line,h=void 0;f.pos=0;f.addToken=ke;if(m2(c.display.measure)&&(h=ld(a))){f.addToken=lF(f.addToken,h)}f.map=[];var j=d!=c.display.externalMeasured&&m5(a);lK(a,f,jG(c,a,j));if(a.styleClasses){if(a.styleClasses.bgClass){f.bgClass=g9(a.styleClasses.bgClass,f.bgClass||"")}if(a.styleClasses.textClass){f.textClass=g9(a.styleClasses.textClass,f.textClass||"")}}if(f.map.length==0){f.map.push(0,0,f.content.appendChild(lP(c.display.measure)))}if(g==0){d.measure.map=f.map;d.measure.cache={}}else{(d.measure.maps||(d.measure.maps=[])).push(f.map);(d.measure.caches||(d.measure.caches=[])).push({})}}if(mU){var b=f.content.lastChild;if(/\bcm-tab\b/.test(b.className)||(b.querySelector&&b.querySelector(".cm-tab"))){f.content.className="cm-tab-wrap-hack"}}n9(c,"renderLine",c,d.line,f.pre);if(f.pre.className){f.textClass=g9(f.pre.className,f.textClass||"")}return f}function hn(b){var a=gZ("span","\u2022","cm-invalidchar");a.title="\\u"+b.charCodeAt(0).toString(16);a.setAttribute("aria-label",a.title);return a}function ke(e,k,u,c,g,r,l){if(!k){return}var a=e.splitSpaces?ma(k,e.trailingSpace):k;var q=e.cm.state.specialChars,p=false;var b;if(!q.test(k)){e.col+=k.length;b=document.createTextNode(a);e.map.push(e.pos,e.pos+k.length,b);if(kj&&kH<9){p=true}e.pos+=k.length}else{b=document.createDocumentFragment();var n=0;while(true){q.lastIndex=n;var d=q.exec(k);var s=d?d.index-n:k.length-n;if(s){var h=document.createTextNode(a.slice(n,n+s));if(kj&&kH<9){b.appendChild(gZ("span",[h]))}else{b.appendChild(h)}e.map.push(e.pos,e.pos+s,h);e.col+=s;e.pos+=s}if(!d){break}n+=s+1;var t=void 0;if(d[0]=="\t"){var f=e.cm.options.tabSize,j=f-e.col%f;t=b.appendChild(gZ("span",j7(j),"cm-tab"));t.setAttribute("role","presentation");t.setAttribute("cm-text","\t");e.col+=j}else{if(d[0]=="\r"||d[0]=="\n"){t=b.appendChild(gZ("span",d[0]=="\r"?"\u240d":"\u2424","cm-invalidchar"));t.setAttribute("cm-text",d[0]);e.col+=1}else{t=e.cm.options.specialCharPlaceholder(d[0]);t.setAttribute("cm-text",d[0]);if(kj&&kH<9){b.appendChild(gZ("span",[t]))}else{b.appendChild(t)}e.col+=1}}e.map.push(e.pos,e.pos+1,t);e.pos++}}e.trailingSpace=a.charCodeAt(k.length-1)==32;if(u||c||g||p||l){var o=u||"";if(c){o+=c}if(g){o+=g}var m=gZ("span",[b],o,l);if(r){m.title=r}return e.content.appendChild(m)}e.content.appendChild(b)}function ma(a,b){if(a.length>1&&!/ /.test(a)){return a}var e=b,f="";for(var d=0;dm&&l.from<=m){break}}if(l.to>=k){return b(f,d,n,j,c,e,g)}b(f,d.slice(0,l.to-m),n,j,null,e,g);j=null;d=d.slice(l.to-m);m=l.to}}}function ny(e,c,b,d){var a=!d&&b.widgetNode;if(a){e.map.push(e.pos,e.pos+c,a)}if(!d&&e.cm.display.input.needsContentAttribute){if(!a){a=e.content.appendChild(document.createElement("span"))}a.setAttribute("cm-marker",b.id)}if(a){e.cm.display.input.setUneditable(a);e.content.appendChild(a)}e.pos+=c;e.trailingSpace=false}function lK(d,A,e){var o=d.markedSpans,h=d.text,C=0;if(!o){for(var l=1;lq||B.collapsed&&y.to==q&&y.from==q)){if(y.to!=null&&y.to!=q&&k>y.to){k=y.to;m=""}if(B.className){v+=" "+B.className}if(B.css){c=(c?c+";":"")+B.css}if(B.startStyle&&y.from==q){D+=" "+B.startStyle}if(B.endStyle&&y.to==k){(f||(f=[])).push(B.endStyle,y.to)}if(B.title&&!g){g=B.title}if(B.collapsed&&(!t||lW(t.marker,B)<0)){t=y}}else{if(y.from>q&&k>y.from){k=y.from}}}}if(f){for(var n=0;n=w){break}var b=Math.min(w,k);while(true){if(a){var u=q+a.length;if(!t){var r=u>b?a.slice(0,b-q):a;A.addToken(A,r,s?s+v:v,D,q+r.length==k?m:"",g,c)}if(u>=b){a=a.slice(b-q);q=b;break}q=u;D=""}a=h.slice(C,C=e[x++]);s=hO(e[x++],A.cm.options)}}}function lk(b,a,c){this.line=a;this.rest=kW(a);this.size=this.rest?m5(ji(this.rest))-c+1:1;this.node=this.text=null;this.hidden=il(b,a)}function hQ(e,a,b){var c=[],f;for(var d=a;d2){e.push((b.bottom+j.top)/2-d.top)}}}e.push(d.bottom-d.top)}}function jT(c,e,b){if(c.line==e){return{map:c.measure.map,cache:c.measure.cache}}for(var d=0;db){return{map:c.measure.maps[a],cache:c.measure.caches[a],before:true}}}}function mP(c,d){d=jV(d);var a=m5(d);var e=c.display.externalMeasured=new lk(c.doc,d,a);e.lineN=a;var b=e.built=j0(c,e);e.text=b.pre;mR(c.display.lineMeasure,b.pre);return e}function ic(b,d,a,c){return mB(b,gP(b,d),a,c)}function hp(a,b){if(b>=a.display.viewFrom&&b=c.lineN&&bk){f=b-g;j=f-1;if(k>=b){c="right"}}}}if(j!=null){h=a[e+2];if(g==b&&d==(h.insertLeft?"left":"right")){c=d}if(d=="left"&&j==0){while(e&&a[e-2]==a[e-3]&&a[e-1].insertLeft){h=a[(e-=3)+2];c="left"}}if(d=="right"&&j==b-g){while(e=0;a--){if((b=d[a]).left!=b.right){break}}}return b}function kL(g,s,d,k){var f=n3(s.map,d,k);var u=f.node,l=f.start,m=f.end,p=f.collapse;var o;if(u.nodeType==3){for(var j=0;j<4;j++){while(l&&iB(s.line.text.charAt(f.coverStart+l))){--l}while(f.coverStart+m0){p=k="right"}var n;if(g.options.lineWrapping&&(n=u.getClientRects()).length>1){o=n[k=="right"?n.length-1:0]}else{o=u.getBoundingClientRect()}}if(kj&&kH<9&&!l&&(!o||!o.left&&!o.right)){var h=u.parentNode.getClientRects()[0];if(h){o={left:h.left,right:h.left+kJ(g.display),top:h.top,bottom:h.bottom}}else{o=jk}}var a=o.top-s.rect.top,c=o.bottom-s.rect.top;var q=(a+c)/2;var r=s.view.measure.heights;var t=0;for(;tp.from){return j(n-1)}return j(n,o)}var h=ld(g),c=e.ch;if(!h){return j(c)}var m=n6(h,c);var k=b(c,m);if(hz!=null){k.other=b(c,hz)}return k}function kw(c,a){var b=0;a=i3(c.doc,a);if(!c.options.lineWrapping){b=kJ(c.display)*a.ch}var e=hm(c.doc,a.line);var d=m7(e)+hs(c.display);return{left:b,right:b,top:d,bottom:d+e.height}}function g1(c,e,d,a){var b=lA(c,e);b.xRel=a;if(d){b.outside=true}return b}function hg(d,g,h){var e=d.doc;h+=d.display.viewOffset;if(h<0){return g1(e.first,0,true,-1)}var j=nj(e,h),c=e.first+e.size-1;if(j>c){return g1(e.first+e.size-1,hm(e,c).text.length,true,1)}if(g<0){g=0}var k=hm(e,j);for(;;){var b=mX(d,k,j,g,h);var f=jy(k);var a=f&&f.find(0,true);if(f&&(b.ch>a.from.ch||b.ch==a.from.ch&&b.xRel>0)){j=m5(k=a.to.line)}else{return b}}}function mX(e,r,m,c,d){var f=d-m7(r);var k=false,y=2*e.display.wrapper.clientWidth;var B=gP(e,r);function q(C){var D=lL(e,lA(m,C),"line",r,B);k=true;if(f>D.bottom){return D.left-y}else{if(fs){return g1(m,p,l,1)}for(;;){if(a?p==x||p==ka(r,x,1):p-x<=1){var b=c0&&b1){var w=mB(e,B,b,"right");if(f<=w.bottom&&f>=w.top&&Math.abs(c-w.right)1?1:0);return g}var h=Math.ceil(z/2),j=x+h;if(a){j=x;for(var v=0;vc){p=j;s=n;if(l=k){s+=1000}z=h}else{x=j;A=n;t=k;z-=h}}}var n7;function nF(a){if(a.cachedTextHeight!=null){return a.cachedTextHeight}if(n7==null){n7=gZ("pre");for(var b=0;b<49;++b){n7.appendChild(document.createTextNode("x"));n7.appendChild(gZ("br"))}n7.appendChild(document.createTextNode("x"))}mR(a.measure,n7);var c=n7.offsetHeight/50;if(c>3){a.cachedTextHeight=c}iN(a.measure);return c||1}function kJ(a){if(a.cachedCharWidth!=null){return a.cachedCharWidth}var c=gZ("span","xxxxxxxxxx");var b=gZ("pre",[c]);mR(a.measure,b);var d=c.getBoundingClientRect(),e=(d.right-d.left)/10;if(e>2){a.cachedCharWidth=e}return e||10}function ho(g){var b=g.display,d={},e={};var c=b.gutters.clientLeft;for(var a=b.gutters.firstChild,f=0;a;a=a.nextSibling,++f){d[g.options.gutters[f]]=a.offsetLeft+a.clientLeft+c;e[g.options.gutters[f]]=a.clientWidth}return{fixedPos:lw(b),gutterTotalWidth:b.gutters.offsetWidth,gutterLeft:d,gutterWidth:e,wrapperWidth:b.wrapper.clientWidth}}function lw(a){return a.scroller.getBoundingClientRect().left-a.sizer.getBoundingClientRect().left}function mp(b){var c=nF(b.display),d=b.options.lineWrapping;var a=d&&Math.max(5,b.display.scroller.clientWidth/kJ(b.display)-3);return function(f){if(il(b.doc,f)){return 0}var g=0;if(f.widgets){for(var e=0;e=d.display.viewTo){return null}a-=d.display.viewFrom;if(a<0){return null}var c=d.display.view;for(var b=0;b=c.display.viewTo||b.to().line3){e(D,F.top,null,F.bottom);D=n;if(F.bottomz.bottom||E.bottom==z.bottom&&E.right>z.right){z=E}if(D0){b.blinker=setInterval(function(){return b.cursorDiv.style.visibility=(c=!c)?"":"hidden"},a.options.cursorBlinkRate)}else{if(a.options.cursorBlinkRate<0){b.cursorDiv.style.visibility="hidden"}}}function kh(a){if(!a.state.focused){a.display.input.focus();mm(a)}}function nk(a){a.state.delayingBlurEvent=true;setTimeout(function(){if(a.state.delayingBlurEvent){a.state.delayingBlurEvent=false;nL(a)}},100)}function mm(a,b){if(a.state.delayingBlurEvent){a.state.delayingBlurEvent=false}if(a.options.readOnly=="nocursor"){return}if(!a.state.focused){n9(a,"focus",a,b);a.state.focused=true;h6(a.display.wrapper,"CodeMirror-focused");if(!a.curOp&&a.display.selForContextMenu!=a.doc.sel){a.display.input.reset();if(mU){setTimeout(function(){return a.display.input.reset(true)},20)}}a.display.input.receivedFocus()}ku(a)}function nL(a,b){if(a.state.delayingBlurEvent){return}if(a.state.focused){n9(a,"blur",a,b);a.state.focused=false;kS(a.display.wrapper,"CodeMirror-focused")}clearInterval(a.display.blinker);setTimeout(function(){if(!a.state.focused){a.display.shift=false}},150)}function ja(a){var c=a.display,b=c.view;if(!c.alignWidgets&&(!c.gutters.firstChild||!a.options.fixedGutter)){return}var e=lw(c)-c.scroller.scrollLeft+a.doc.scrollLeft;var j=c.gutters.offsetWidth,h=e+"px";for(var f=0;f0.001||d<-0.001){gV(b.line,a);kX(b.line);if(b.rest){for(var k=0;k=d){e=nj(c,m7(hm(c,b))-g.wrapper.clientHeight);d=b}}}return{from:e,to:Math.max(d,e+1)}}function l4(a,b){if(Math.abs(a.doc.scrollTop-b)<2){return}a.doc.scrollTop=b;if(!kc){lN(a,{top:b})}if(a.display.scroller.scrollTop!=b){a.display.scroller.scrollTop=b}a.display.scrollbars.setScrollTop(b);if(kc){lN(a)}ij(a,100)}function nq(a,b,c){if(c?b==a.doc.scrollLeft:Math.abs(a.doc.scrollLeft-b)<2){return}b=Math.min(b,a.display.scroller.scrollWidth-a.display.scroller.clientWidth);a.doc.scrollLeft=b;ja(a);if(a.display.scroller.scrollLeft!=b){a.display.scroller.scrollLeft=b}a.display.scrollbars.setScrollLeft(b)}var iF=0;var kG=null;if(kj){kG=-0.53}else{if(kc){kG=15}else{if(ju){kG=-0.7}else{if(ob){kG=-1/3}}}}function ls(b){var c=b.wheelDeltaX,a=b.wheelDeltaY;if(c==null&&b.detail&&b.axis==b.HORIZONTAL_AXIS){c=b.detail}if(a==null&&b.detail&&b.axis==b.VERTICAL_AXIS){a=b.detail}else{if(a==null){a=b.wheelDelta}}return{x:c,y:a}}function mF(a){var b=ls(a);b.x*=kG;b.y*=kG;return b}function k8(e,l){var c=ls(l),a=c.x,b=c.y;var j=e.display,f=j.scroller;var m=f.scrollWidth>f.clientWidth;var n=f.scrollHeight>f.clientHeight;if(!(a&&m||b&&n)){return}if(b&&k9&&mU){outer:for(var d=l.target,g=j.view;d!=f;d=d.parentNode){for(var o=0;od.clientWidth+1;var e=d.scrollHeight>d.clientHeight+1;var a=d.nativeBarWidth;if(e){this.vert.style.display="block";this.vert.style.bottom=b?a+"px":"0";var f=d.viewHeight-(b?a:0);this.vert.firstChild.style.height=Math.max(0,d.scrollHeight-d.clientHeight+f)+"px"}else{this.vert.style.display="";this.vert.firstChild.style.height="0"}if(b){this.horiz.style.display="block";this.horiz.style.right=e?a+"px":"0";this.horiz.style.left=d.barLeft+"px";var c=d.viewWidth-d.barLeft-(e?a:0);this.horiz.firstChild.style.width=(d.scrollWidth-d.clientWidth+c)+"px"}else{this.horiz.style.display="";this.horiz.firstChild.style.width="0"}if(!this.checkedZeroWidth&&d.clientHeight>0){if(a==0){this.zeroWidthHack()}this.checkedZeroWidth=true}return{right:e?a:0,bottom:b?a:0}},setScrollLeft:function(a){if(this.horiz.scrollLeft!=a){this.horiz.scrollLeft=a}if(this.disableHoriz){this.enableZeroWidthBar(this.horiz,this.disableHoriz)}},setScrollTop:function(a){if(this.vert.scrollTop!=a){this.vert.scrollTop=a}if(this.disableVert){this.enableZeroWidthBar(this.vert,this.disableVert)}},zeroWidthHack:function(){var a=k9&&!jI?"12px":"18px";this.horiz.style.height=this.vert.style.width=a;this.horiz.style.pointerEvents=this.vert.style.pointerEvents="none";this.disableHoriz=new hA;this.disableVert=new hA},enableZeroWidthBar:function(c,a){c.style.pointerEvents="auto";function b(){var d=c.getBoundingClientRect();var e=document.elementFromPoint(d.left+1,d.bottom-1);if(e!=c){c.style.pointerEvents="none"}else{a.set(1000,b)}}a.set(1000,b)},clear:function(){var a=this.horiz.parentNode;a.removeChild(this.horiz);a.removeChild(this.vert)}},i7.prototype);function hy(){}hy.prototype=n0({update:function(){return{bottom:0,right:0}},setScrollLeft:function(){},setScrollTop:function(){},clear:function(){}},hy.prototype);function hK(c,a){if(!a){a=kR(c)}var d=c.display.barWidth,e=c.display.barHeight;nN(c,a);for(var b=0;b<4&&d!=c.display.barWidth||e!=c.display.barHeight;b++){if(d!=c.display.barWidth&&c.options.lineWrapping){i(c)}nN(c,kR(c));d=c.display.barWidth;e=c.display.barHeight}}function nN(b,d){var a=b.display;var c=a.scrollbars.update(d);a.sizer.style.paddingRight=(a.barWidth=c.right)+"px";a.sizer.style.paddingBottom=(a.barHeight=c.bottom)+"px";a.heightForcer.style.borderBottom=c.bottom+"px solid transparent";if(c.right&&c.bottom){a.scrollbarFiller.style.display="block";a.scrollbarFiller.style.height=c.bottom+"px";a.scrollbarFiller.style.width=c.right+"px"}else{a.scrollbarFiller.style.display=""}if(c.bottom&&b.options.coverGutterNextToScrollbar&&b.options.fixedGutter){a.gutterFiller.style.display="block";a.gutterFiller.style.height=c.bottom+"px";a.gutterFiller.style.width=d.gutterWidth+"px"}else{a.gutterFiller.style.display=""}}var lX={"native":i7,"null":hy};function oa(a){if(a.display.scrollbars){a.display.scrollbars.clear();if(a.display.scrollbars.addClass){kS(a.display.wrapper,a.display.scrollbars.addClass)}}a.display.scrollbars=new lX[a.options.scrollbarStyle](function(b){a.display.wrapper.insertBefore(b,a.display.scrollbarFiller);nY(b,"mousedown",function(){if(a.state.focused){setTimeout(function(){return a.display.input.focus()},0)}});b.setAttribute("cm-not-content","true")},function(b,c){if(c=="horizontal"){nq(a,b)}else{l4(a,b)}},a);if(a.display.scrollbars.addClass){h6(a.display.wrapper,a.display.scrollbars.addClass)}}function iD(f,b){if(nT(f,"scrollCursorIntoView")){return}var a=f.display,e=a.sizer.getBoundingClientRect(),d=null;if(b.top+e.top<0){d=true}else{if(b.bottom+e.top>(window.innerHeight||document.documentElement.clientHeight)){d=false}}if(d!=null&&!iq){var c=gZ("div","\u200b",null,("position: absolute;\n top: "+(b.top-a.viewOffset-hs(f.display))+"px;\n height: "+(b.bottom-b.top+lh(f)+a.barHeight)+"px;\n left: "+(b.left)+"px; width: 2px;"));f.display.lineSpace.appendChild(c);c.scrollIntoView(d);f.display.lineSpace.removeChild(c)}}function mx(a,d,h,j){if(j==null){j=0}var b;for(var g=0;g<5;g++){var f=false;b=lL(a,d);var c=!h||h==d?b:lL(a,h);var k=mn(a,Math.min(b.left,c.left),Math.min(b.top,c.top)-j,Math.max(b.left,c.left),Math.max(b.bottom,c.bottom)+j);var e=a.doc.scrollTop,l=a.doc.scrollLeft;if(k.scrollTop!=null){l4(a,k.scrollTop);if(Math.abs(a.doc.scrollTop-e)>1){f=true}}if(k.scrollLeft!=null){nq(a,k.scrollLeft);if(Math.abs(a.doc.scrollLeft-l)>1){f=true}}if(!f){break}}return b}function mt(d,e,b,f,c){var a=mn(d,e,b,f,c);if(a.scrollTop!=null){l4(d,a.scrollTop)}if(a.scrollLeft!=null){nq(d,a.scrollLeft)}}function mn(d,o,e,q,f){var j=d.display,l=nF(d.display);if(e<0){e=0}var n=d.curOp&&d.curOp.scrollTop!=null?d.curOp.scrollTop:j.scroller.scrollTop;var b=m8(d),r={};if(f-e>b){f=e+b}var p=d.doc.height+ni(j);var h=ep-l;if(en+b){var g=Math.min(e,(k?p:f)-b);if(g!=n){r.scrollTop=g}}}var a=d.curOp&&d.curOp.scrollLeft!=null?d.curOp.scrollLeft:j.scroller.scrollLeft;var c=i4(d)-(d.options.fixedGutter?j.gutters.offsetWidth:0);var m=q-o>c;if(m){q=o+c}if(o<10){r.scrollLeft=0}else{if(oc+a-3){r.scrollLeft=q+(m?0:10)-c}}}return r}function lM(a,b,c){if(b!=null||c!=null){h2(a)}if(b!=null){a.curOp.scrollLeft=(a.curOp.scrollLeft==null?a.doc.scrollLeft:a.curOp.scrollLeft)+b}if(c!=null){a.curOp.scrollTop=(a.curOp.scrollTop==null?a.doc.scrollTop:a.curOp.scrollTop)+c}}function jl(b){h2(b);var d=b.getCursor(),a=d,c=d;if(!b.options.lineWrapping){a=d.ch?lA(d.line,d.ch-1):d;c=lA(d.line,d.ch+1)}b.curOp.scrollToPos={from:a,to:c,margin:b.options.cursorScrollMargin,isCursor:true}}function h2(c){var d=c.curOp.scrollToPos;if(d){c.curOp.scrollToPos=null;var a=kw(c,d.from),b=kw(c,d.to);var e=mn(c,Math.min(a.left,b.left),Math.min(a.top,b.top)-d.margin,Math.max(a.right,b.right),Math.max(a.bottom,b.bottom)+d.margin);c.scrollTo(e.scrollLeft,e.scrollTop)}}var iv=0;function lY(a){a.curOp={cm:a,viewChanged:false,startHeight:a.doc.height,forceUpdate:false,updateInput:null,typing:false,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:false,updateMaxLine:false,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:false,id:++iv};js(a.curOp)}function ng(a){var b=a.curOp;nX(b,function(c){for(var d=0;d=c.viewTo)||c.maxLineChanged&&a.options.lineWrapping;b.update=b.mustUpdate&&new n4(a,b.mustUpdate&&{top:b.scrollTop,ensure:b.scrollToPos},b.forceUpdate)}function m6(a){a.updatedDisplay=a.mustUpdate&&mC(a.cm,a.update)}function nP(b){var a=b.cm,c=a.display;if(b.updatedDisplay){i(a)}b.barMeasure=kR(a);if(c.maxLineChanged&&!a.options.lineWrapping){b.adjustWidthTo=ic(a,c.maxLine,c.maxLine.text.length).left+3;a.display.sizerWidth=b.adjustWidthTo;b.barMeasure.scrollWidth=Math.max(c.scroller.clientWidth,c.sizer.offsetLeft+b.adjustWidthTo+lh(a)+a.display.barWidth);b.maxScrollLeft=Math.max(0,c.sizer.offsetLeft+b.adjustWidthTo-i4(a))}if(b.updatedDisplay||b.selectionChanged){b.preparedSelection=c.input.prepareSelection(b.focus)}}function m9(b){var a=b.cm;if(b.adjustWidthTo!=null){a.display.sizer.style.minWidth=b.adjustWidthTo+"px";if(b.maxScrollLefte)){h.updateLineNumbers=e}c.curOp.viewChanged=true;if(e>=h.viewTo){if(gN&&nJ(c.doc,e)h.viewFrom){jw(c)}else{h.viewFrom+=b;h.viewTo+=b}}else{if(e<=h.viewFrom&&d>=h.viewTo){jw(c)}else{if(e<=h.viewFrom){var f=jq(c,d,d+b,1);if(f){h.view=h.view.slice(f.index);h.viewFrom=f.lineN;h.viewTo+=b}else{jw(c)}}else{if(d>=h.viewTo){var k=jq(c,e,e,-1);if(k){h.view=h.view.slice(0,k.index);h.viewTo=k.lineN}else{jw(c)}}else{var g=jq(c,e,e,-1);var j=jq(c,d,d+b,1);if(g&&j){h.view=h.view.slice(0,g.index).concat(hQ(c,g.lineN,j.lineN)).concat(h.view.slice(j.index));h.viewTo+=b}else{jw(c)}}}}}}var a=h.externalMeasured;if(a){if(d=c.lineN&&f=a.viewTo){return}var e=a.view[iR(g,f)];if(e.node==null){return}var d=e.changes||(e.changes=[]);if(jb(d,b)==-1){d.push(b)}}function jw(a){a.display.viewFrom=a.display.viewTo=a.doc.first;a.display.view=[];a.display.viewOffset=0}function jq(a,g,e,h){var d=iR(a,g),b,c=a.display.view;if(!gN||e==a.doc.first+a.doc.size){return{index:d,lineN:e}}var j=a.display.viewFrom;for(var f=0;f0){if(d==c.length-1){return null}b=(j+c[d].size)-g;d++}else{b=j-g}g+=b;e+=b}while(nJ(a.doc,e)!=e){if(d==(h<0?0:c.length-1)){return null}e+=h*c[d-(h<0?1:0)].size;d+=h}return{index:d,lineN:e}}function lm(c,a,b){var d=c.display,e=d.view;if(e.length==0||a>=d.viewTo||b<=d.viewFrom){d.view=hQ(c,a,b);d.viewFrom=a}else{if(d.viewFrom>a){d.view=hQ(c,a,d.viewFrom).concat(d.view)}else{if(d.viewFromb){d.view=d.view.slice(0,iR(c,b))}}}d.viewTo=b}function jv(e){var d=e.display.view,a=0;for(var b=0;b=c.display.viewTo){return}var d=+new Date+c.options.workTime;var b=nK(a.mode,kM(c,a.frontier));var e=[];a.iter(a.frontier,Math.min(a.first+a.size,c.display.viewTo+500),function(h){if(a.frontier>=c.display.viewFrom){var n=h.styles,j=h.text.length>c.options.maxHighlightLength;var l=ib(c,h,j?nK(a.mode,b):b,true);h.styles=l.styles;var f=h.styleClasses,m=l.classes;if(m){h.styleClasses=m}else{if(f){h.styleClasses=null}}var k=!n||n.length!=h.styles.length||f!=m&&(!f||!m||f.bgClass!=m.bgClass||f.textClass!=m.textClass);for(var g=0;!k&&gd){ij(c,c.options.workDelay);return true}});if(e.length){lI(c,function(){for(var f=0;f=g.viewFrom&&h.visible.to<=g.viewTo&&(g.updateLineNumbers==null||g.updateLineNumbers>=g.viewTo)&&g.renderedView==g.view&&jv(b)==0){return false}if(iE(b)){jw(b);h.dims=ho(b)}var j=c.first+c.size;var e=Math.max(h.visible.from-b.options.viewportMargin,c.first);var d=Math.min(j,h.visible.to+b.options.viewportMargin);if(g.viewFromd&&g.viewTo-d<20){d=Math.min(j,g.viewTo)}if(gN){e=nJ(b.doc,e);d=iJ(b.doc,d)}var k=e!=g.viewFrom||d!=g.viewTo||g.lastWrapHeight!=h.wrapperHeight||g.lastWrapWidth!=h.wrapperWidth;lm(b,e,d);g.viewOffset=m7(hm(b.doc,g.viewFrom));b.display.mover.style.top=g.viewOffset+"px";var a=jv(b);if(!k&&a==0&&!h.force&&g.renderedView==g.view&&(g.updateLineNumbers==null||g.updateLineNumbers>=g.viewTo)){return false}var f=j3();if(a>4){g.lineDiv.style.display="none"}km(b,g.updateLineNumbers,h.dims);if(a>4){g.lineDiv.style.display=""}g.renderedView=g.view;if(f&&j3()!=f&&f.offsetHeight){f.focus()}iN(g.cursorDiv);iN(g.selectionDiv);g.gutters.style.height=g.sizer.style.minHeight=0;if(k){g.lastWrapHeight=h.wrapperHeight;g.lastWrapWidth=h.wrapperWidth;ij(b,400)}g.updateLineNumbers=null;return true}function kt(e,a){var c=a.viewport;for(var b=true;;b=false){if(!b||!e.options.lineWrapping||a.oldDisplayWidth==i4(e)){if(c&&c.top!=null){c={top:Math.min(e.doc.height+ni(e.display)-m8(e),c.top)}}a.visible=lc(e.display,e.doc,c);if(a.visible.from>=e.display.viewFrom&&a.visible.to<=e.display.viewTo){break}}if(!mC(e,a)){break}i(e);var d=kR(e);nu(e);hK(e,d);kV(e,d)}a.signal(e,"update",e);if(e.display.viewFrom!=e.display.reportedViewFrom||e.display.viewTo!=e.display.reportedViewTo){a.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo);e.display.reportedViewFrom=e.display.viewFrom;e.display.reportedViewTo=e.display.viewTo}}function lN(d,b){var a=new n4(d,b);if(mC(d,a)){i(d);kt(d,a);var c=kR(d);nu(d);hK(d,c);kV(d,c);a.finish()}}function km(c,m,d){var g=c.display,a=c.options.lineNumbers;var o=g.lineDiv,b=o.firstChild;function h(p){var q=p.nextSibling;if(mU&&k9&&c.display.currentWheelTarget==p){p.style.display="none"}else{p.parentNode.removeChild(p)}return q}var f=g.view,j=g.viewFrom;for(var l=0;l-1){e=false}nC(c,k,j,d)}if(e){iN(k.lineNumber);k.lineNumber.appendChild(document.createTextNode(jF(c.options,j)))}b=k.node.nextSibling}}j+=k.size}while(b){b=h(b)}}function jO(a){var b=a.display.gutters.offsetWidth;a.display.sizer.style.marginLeft=b+"px"}function kV(a,b){a.display.sizer.style.minHeight=b.docHeight+"px";a.display.heightForcer.style.top=b.docHeight+"px";a.display.gutters.style.height=(b.docHeight+a.display.barHeight+lh(a))+"px"}function io(f){var e=f.display.gutters,a=f.options.gutters;iN(e);var d=0;for(;d-1&&!a.lineNumbers){a.gutters=a.gutters.slice(0);a.gutters.splice(b,1)}}}function g0(a,b){this.ranges=a;this.primIndex=b}g0.prototype={primary:function(){return this.ranges[this.primIndex]},equals:function(e){var a=this;if(e==this){return true}if(e.primIndex!=this.primIndex||e.ranges.length!=this.ranges.length){return false}for(var c=0;c=0&&kI(a,d.to())<=0){return c}}return -1}};function lz(a,b){this.anchor=a;this.head=b}lz.prototype={from:function(){return m3(this.anchor,this.head)},to:function(){return nD(this.anchor,this.head)},empty:function(){return this.head.line==this.anchor.line&&this.head.ch==this.anchor.ch}};function mA(j,b){var g=j[b];j.sort(function(l,k){return kI(l.from(),k.from())});b=jb(j,g);for(var e=1;e=0){var d=m3(h.from(),a.from()),c=nD(h.to(),a.to());var f=h.empty()?a.from()==a.head:h.from()==h.head;if(e<=b){--b}j.splice(--e,2,new lz(f?c:d,f?d:c))}}return new g0(j,b)}function j1(a,b){return new g0([new lz(a,b||a)],0)}function m1(a){if(!a.text){return a.to}return lA(a.from.line+a.text.length-1,ji(a.text).length+(a.text.length==1?a.from.ch:0))}function nS(b,c){if(kI(b,c.from)<0){return b}if(kI(b,c.to)<=0){return m1(c)}var a=b.line+c.text.length-(c.to.line-c.from.line)-1,d=b.ch;if(b.line==c.to.line){d+=m1(c).ch-c.to.ch}return lA(a,d)}function iK(b,a){var d=[];for(var c=0;c1){r.remove(a.line+1,c-1)}r.insert(a.line+1,l)}}}}nw(r,"change",r,f)}function iA(b,c,d){function a(j,e,g){if(j.linked){for(var f=0;f1&&!b.done[b.done.length-2].ranges){b.done.pop();return ji(b.done)}}}}function iX(d,f,a,g){var h=d.history;h.undone.length=0;var j=+new Date,c;var b;if((h.lastOp==g||h.lastOrigin==f.origin&&f.origin&&((f.origin.charAt(0)=="+"&&d.cm&&h.lastModTime>j-d.cm.options.historyEventDelay)||f.origin.charAt(0)=="*"))&&(c=kl(h,h.lastOp==g))){b=ji(c.changes);if(kI(f.from,f.to)==0&&kI(f.from,b.to)==0){b.to=m1(f)}else{c.changes.push(k6(d,f))}}else{var e=ji(h.done);if(!e||!e.ranges){lx(d.sel,h.done)}c={changes:[k6(d,f)],generation:h.generation};h.done.push(c);while(h.done.length>h.undoDepth){h.done.shift();if(!h.done[0].ranges){h.done.shift()}}}h.done.push(a);h.generation=++h.maxGeneration;h.lastModTime=h.lastSelTime=j;h.lastOp=h.lastSelOp=g;h.lastOrigin=h.lastSelOrigin=f.origin;if(!b){n9(d,"historyAdded")}}function nx(a,c,d,b){var e=c.charAt(0);return e=="*"||e=="+"&&d.ranges.length==b.ranges.length&&d.somethingSelected()==b.somethingSelected()&&new Date-a.history.lastSelTime<=(a.cm?a.cm.options.historyEventDelay:500)}function hN(a,d,c,e){var b=a.history,f=e&&e.origin;if(c==b.lastSelOp||(f&&b.lastSelOrigin==f&&(b.lastModTime==b.lastSelTime&&b.lastOrigin==f||nx(a,f,ji(b.done),d)))){b.done[b.done.length-1]=d}else{lx(d,b.done)}b.lastSelTime=+new Date;b.lastSelOrigin=f;b.lastSelOp=c;if(e&&e.clearRedo!==false){h3(b.undone)}}function lx(c,a){var b=ji(a);if(!(b&&b.ranges&&b.equals(c))){a.push(c)}}function nU(f,a,b,d){var c=a["spans_"+f.id],e=0;f.iter(Math.max(f.first,b),Math.min(f.first+f.size,d),function(g){if(g.markedSpans){(c||(c=a["spans_"+f.id]={}))[e]=g.markedSpans}++e})}function l3(a){if(!a){return null}var c;for(var b=0;b-1){ji(c)[m]=e[m];delete e[m]}}}}}}return l}function im(a,f,b,d){if(a.cm&&a.cm.display.shift||a.extend){var c=f.anchor;if(d){var e=kI(b,c)<0;if(e!=(kI(d,c)<0)){c=b;b=d}else{if(e!=(kI(b,d)<0)){b=d}}}return new lz(c,b)}else{return new lz(d||b,b)}}function g5(b,c,a,d){mL(b,new g0([im(b,b.sel.primary(),c,a)],0),d)}function mN(a,b,d){var e=[];for(var c=0;c=d.ch:m.to>d.ch))){if(f){n9(l,"beforeCursorEnter");if(l.explicitlyCleared){if(!a.markedSpans){break}else{--h;continue}}}if(!l.atomic){continue}if(j){var e=l.find(k<0?1:-1),c=void 0;if(k<0?l.inclusiveRight:l.inclusiveLeft){e=hF(b,e,-k,e&&e.line==d.line?a:null)}if(e&&e.line==d.line&&(c=kI(e,j))&&(k<0?c<0:c>0)){return iS(b,e,d,k,f)}}var g=l.find(k<0?-1:1);if(k<0?l.inclusiveLeft:l.inclusiveRight){g=hF(b,g,k,g.line==d.line?a:null)}return g?iS(b,g,d,k,f):null}}}return d}function mJ(b,a,e,g,d){var f=g||1;var c=iS(b,a,e,f,d)||(!d&&iS(b,a,e,f,true))||iS(b,a,e,-f,d)||(!d&&iS(b,a,e,-f,true));if(!c){b.cantEdit=true;return lA(b.first,0)}return c}function hF(c,b,d,a){if(d<0&&b.ch==0){if(b.line>c.first){return i3(c,lA(b.line-1))}else{return null}}else{if(d>0&&b.ch==(a||hm(c,b.line)).text.length){if(b.line=0;--e){mf(b,{from:d[e].from,to:d[e].to,text:e?[""]:a.text})}}else{mf(b,a)}}function mf(c,b){if(b.text.length==1&&b.text[0]==""&&kI(b.from,b.to)==0){return}var d=iK(c,b);iX(c,b,d,c.cm?c.cm.curOp.id:NaN);ik(c,b,d,h5(c,b));var a=[];iA(c,function(e,f){if(!f&&jb(a,e.history)==-1){kF(e.history,b);a.push(e.history)}ik(e,b,null,h5(e,b))})}function k5(c,d,a){if(c.cm&&c.cm.state.suppressEdits&&!a){return}var e=c.history,n,l=c.sel;var o=d=="undo"?e.done:e.undone,b=d=="undo"?e.undone:e.done;var h=0;for(;h=0;--k){var j=g(k);if(j){return j.v}}}function iC(d,b){if(b==0){return}d.first+=b;d.sel=new g0(mO(d.sel.ranges,function(e){return new lz(lA(e.anchor.line+b,e.anchor.ch),lA(e.head.line+b,e.head.ch))}),d.sel.primIndex);if(d.cm){np(d.cm,d.first,d.first-b,b);for(var c=d.cm.display,a=c.viewFrom;ab.lastLine()){return}if(a.from.linee){a={from:a.from,to:lA(e,hm(b,e).text.length),text:[a.text[0]],origin:a.origin}}a.removed=gY(b,a.from,a.to);if(!d){d=iK(b,a)}if(b.cm){n5(b.cm,a,f)}else{id(b,a,f)}jN(b,d,ln)}function n5(b,g,j){var c=b.doc,h=b.display,f=g.from,d=g.to;var e=false,k=f.line;if(!b.options.lineWrapping){k=m5(jV(hm(c,f.line)));c.iter(k,d.line+1,function(o){if(o==h.maxLine){e=true;return true}})}if(c.sel.contains(g.from,g.to)>-1){lD(b)}id(c,g,j,mp(b));if(!b.options.lineWrapping){c.iter(k,f.line+g.text.length,function(o){var p=hZ(o);if(p>h.maxLineLength){h.maxLine=o;h.maxLineLength=p;h.maxLineChanged=true;e=false}});if(e){b.curOp.updateMaxLine=true}}c.frontier=Math.min(c.frontier,f.line);ij(b,400);var a=g.text.length-(d.line-f.line)-1;if(g.full){np(b)}else{if(f.line==d.line&&g.text.length==1&&!lR(b.doc,g)){lS(b,f.line,"text")}else{np(b,f.line,d.line+1,a)}}var m=iI(b,"changes"),l=iI(b,"change");if(l||m){var n={from:f,to:d,text:g.text,removed:g.removed,origin:g.origin};if(l){nw(b,"change",b,n)}if(m){(b.curOp.changeObjs||(b.curOp.changeObjs=[])).push(n)}}b.display.selForContextMenu=null}function gT(d,e,a,b,c){if(!b){b=a}if(kI(b,a)<0){var f=b;b=a;a=f}if(typeof e=="string"){e=d.splitLines(e)}mi(d,{from:a,to:b,text:e,origin:c})}function ml(b,c,d,a){if(d1||!(this.children[0] instanceof hH))){var a=[];this.collapse(a);this.children=[new hH(a)];this.children[0].parent=this}},collapse:function(c){var a=this;for(var b=0;b50){var h=k.lines.length%25+25;for(var c=h;c10);c.parent.maybeSpill()},iterN:function(g,h,a){var b=this;for(var f=0;fc.display.maxLineLength){c.display.maxLine=m;c.display.maxLineLength=f;c.display.maxLineChanged=true}}}if(j!=null&&c&&this.collapsed){np(c,j,e+1)}this.lines.length=0;this.explicitlyCleared=true;if(this.atomic&&this.doc.cantEdit){this.doc.cantEdit=false;if(c){jt(c.doc)}}if(c){nw(c,"markerCleared",c,this)}if(k){ng(c)}if(this.parent){this.parent.clear()}};lV.prototype.find=function(d,f){var h=this;if(d==null&&this.type=="bookmark"){d=1}var a,b;for(var e=0;e0||b==0&&h.clearWhenEmpty!==false){return h}if(h.replacedWith){h.collapsed=true;h.widgetNode=gZ("span",[h.replacedWith],"CodeMirror-widget");if(!a.handleMouseEvents){h.widgetNode.setAttribute("cm-ignore-events","true")}if(a.insertLeft){h.widgetNode.insertLeft=true}}if(h.collapsed){if(jS(c,e.line,e,d,h)||e.line!=d.line&&jS(c,d.line,e,d,h)){throw new Error("Inserting collapsed marker partially overlapping an existing one")}lf()}if(h.addToHistory){iX(c,{from:e,to:d,origin:"markText"},c.sel,NaN)}var k=e.line,f=c.cm,l;c.iter(k,d.line+1,function(m){if(f&&h.collapsed&&!f.options.lineWrapping&&jV(m)==f.display.maxLine){l=true}if(h.collapsed&&k!=e.line){gV(m,0)}kP(m,new h7(h,k==e.line?e.ch:null,k==d.line?d.ch:null));++k});if(h.collapsed){c.iter(e.line,d.line+1,function(m){if(il(c,m)){gV(m,0)}})}if(h.clearOnEnter){nY(h,"beforeCursorEnter",function(){return h.clear()})}if(h.readOnly){ia();if(c.history.done.length||c.history.undone.length){c.clearHistory()}}if(h.collapsed){h.id=++gQ;h.atomic=true}if(f){if(l){f.curOp.updateMaxLine=true}if(h.collapsed){np(f,e.line,d.line+1)}else{if(h.className||h.title||h.startStyle||h.endStyle||h.css){for(var j=e.line;j<=d.line;j++){lS(f,j,"text")}}}if(h.atomic){jt(f.doc)}nw(f,"markerAdded",f,h)}return h}function jZ(b,c){var a=this;this.markers=b;this.primary=c;for(var d=0;d=0;h--){mi(e,c[h])}if(k){ht(this,k)}else{if(this.cm){jl(this.cm)}}}),undo:mg(function(){k5(this,"undo")}),redo:mg(function(){k5(this,"redo")}),undoSelection:mg(function(){k5(this,"undo",true)}),redoSelection:mg(function(){k5(this,"redo",true)}),setExtending:function(a){this.extend=a},getExtending:function(){return this.extend},historySize:function(){var b=this.history,e=0,c=0;for(var d=0;d=a.ch)){b.push(c.marker.parent||c.marker)}}}return b},findMarks:function(a,c,e){a=i3(this,a);c=i3(this,c);var d=[],b=a.line;this.iter(a.line,c.line+1,function(h){var f=h.markedSpans;if(f){for(var g=0;g=j.to||j.from==null&&b!=a.line||j.from!=null&&b==c.line&&j.from>=c.ch)&&(!e||e(j.marker))){d.push(j.marker.parent||j.marker)}}}++b});return d},getAllMarks:function(){var a=[];this.iter(function(c){var d=c.markedSpans;if(d){for(var b=0;bb){d=b;return true}b-=e;++a});return i3(this,lA(a,d))},indexFromPos:function(b){b=i3(this,b);var a=b.ch;if(b.linea){a=b.from}if(b.to!=null&&b.to-1){b.state.draggingText(d);setTimeout(function(){return b.display.input.focus()},20);return}try{var m=d.dataTransfer.getData("Text");if(m){var g;if(b.state.draggingText&&!b.state.draggingText.copy){g=b.listSelections()}jN(b.doc,j1(c,c));if(g){for(var j=0;j=0;h--){gT(g.doc,"",c[h].from,c[h].to,"+delete")}jl(g)})}var jg={selectAll:nh,singleSelection:function(a){return a.setSelection(a.getCursor("anchor"),a.getCursor("head"),ln)},killLine:function(a){return hL(a,function(b){if(b.empty()){var c=hm(a.doc,b.head.line).text.length;if(b.head.ch==c&&b.head.line0){b=new lA(b.line,b.ch+1);a.replaceRange(g.charAt(b.ch-1)+g.charAt(b.ch-2),lA(b.line,b.ch-2),b,"+transpose")}else{if(b.line>a.doc.first){var c=hm(a.doc,b.line-1).text;if(c){b=new lA(b.line,1);a.replaceRange(g.charAt(0)+a.doc.lineSeparator()+c.charAt(c.length-1),lA(b.line-1,c.length-1),b,"+transpose")}}}}f.push(new lz(b,b))}a.setSelections(f)})},newlineAndIndent:function(a){return lI(a,function(){var d=a.listSelections();for(var c=d.length-1;c>=0;c--){a.replaceRange(a.doc.lineSeparator(),d[c].anchor,d[c].head,"+input")}d=a.listSelections();for(var b=0;bg-400&&kI(jr.pos,a)==0){d="triple"}else{if(i1&&i1.time>g-400&&kI(i1.pos,a)==0){d="double";jr={time:g,pos:a}}else{d="single";i1={time:g,pos:a}}}var c=h.doc.sel,f=k9?b.metaKey:b.ctrlKey,e;if(h.options.dragDrop&&kr&&!h.isReadOnly()&&d=="single"&&(e=c.contains(a))>-1&&(kI((e=c.ranges[e]).from(),a)<0||a.xRel>0)&&(kI(e.to(),a)>0||a.xRel<0)){gR(h,b,a,f)}else{kB(h,b,a,d,f)}}function gR(f,b,a,g){var c=f.display,e=+new Date;var d=mQ(f,function(h){if(mU){c.scroller.draggable=false}f.state.draggingText=false;ih(document,"mouseup",d);ih(c.scroller,"drop",d);if(Math.abs(b.clientX-h.clientX)+Math.abs(b.clientY-h.clientY)<10){l7(h);if(!g&&+new Date-200-1){o=q[s]}else{o=new lz(n,n)}}else{o=r.sel.primary();s=r.sel.primIndex}if(i6?t.shiftKey&&t.metaKey:t.altKey){p="rect";if(!k){o=new lz(n,n)}n=kg(m,t,true,true);s=-1}else{if(p=="double"){var v=m.findWordAt(n);if(m.display.shift||r.extend){o=im(r,o,v.anchor,v.head)}else{o=v}}else{if(p=="triple"){var g=new lz(lA(n.line,0),i3(r,lA(n.line+1,0)));if(m.display.shift||r.extend){o=im(r,o,g.anchor,g.head)}else{o=g}}else{o=im(r,o,n)}}}if(!k){s=0;mL(r,new g0([o],0),l9);l=r.sel}else{if(s==-1){s=q.length;mL(r,mA(q.concat([o]),s),{scroll:false,origin:"*mouse"})}else{if(q.length>1&&q[s].empty()&&p=="single"&&!t.shiftKey){mL(r,mA(q.slice(0,s).concat(q.slice(s+1)),0),{scroll:false,origin:"*mouse"});l=r.sel}else{k2(r,s,o,l9)}}}var b=n;function c(B){if(kI(b,B)==0){return}b=B;if(p=="rect"){var J=[],E=m.options.tabSize;var K=mV(hm(r,n.line).text,n.ch,E);var x=mV(hm(r,B.line).text,B.ch,E);var I=Math.min(K,x),z=Math.max(K,x);for(var w=Math.min(n.line,B.line),G=Math.min(m.lastLine(),Math.max(n.line,B.line));w<=G;w++){var y=hm(r,w).text,H=jM(y,I,E);if(I==z){J.push(new lz(lA(w,H),lA(w,H)))}else{if(y.length>H){J.push(new lz(lA(w,H),lA(w,jM(y,z,E))))}}}if(!J.length){J.push(new lz(n,n))}mL(r,mA(l.ranges.slice(0,s).concat(J),s),{origin:"*mouse",scroll:false});m.scrollIntoView(B)}else{var D=o;var F=D.anchor,C=B;if(p!="single"){var A;if(p=="double"){A=m.findWordAt(B)}else{A=new lz(lA(B.line,0),i3(r,lA(B.line+1,0)))}if(kI(A.anchor,F)>0){C=A.head;F=m3(D.from(),A.anchor)}else{C=A.anchor;F=nD(D.to(),A.head)}}var L=l.ranges.slice(0);L[s]=new lz(i3(r,F),C);mL(r,mA(L,s),l9)}}var e=a.wrapper.getBoundingClientRect();var j=0;function u(x){var z=++j;var A=kg(m,x,true,p=="rect");if(!A){return}if(kI(A,b)!=0){m.curOp.focus=j3();c(A);var w=lc(a,r);if(A.line>=w.to||A.linee.bottom?20:0;if(y){setTimeout(mQ(m,function(){if(j!=z){return}a.scroller.scrollTop+=y;u(x)}),50)}}}function f(w){m.state.selectingText=false;j=Infinity;l7(w);a.input.focus();ih(document,"mousemove",d);ih(document,"mouseup",h);r.history.lastSelOrigin=null}var d=mQ(m,function(w){if(!hi(w)){f(w)}else{u(w)}});var h=mQ(m,f);m.state.selectingText=h;nY(document,"mousemove",d);nY(document,"mouseup",h)}function hC(b,f,d,c){var l,m;try{l=f.clientX;m=f.clientY}catch(f){return false}if(l>=Math.floor(b.display.gutters.getBoundingClientRect().right)){return false}if(c){l7(f)}var e=b.display;var g=e.lineDiv.getBoundingClientRect();if(m>g.bottom||!iI(b,d)){return na(f)}m-=g.top-e.viewOffset;for(var j=0;j=l){var a=nj(b.doc,m);var k=b.options.gutters[j];n9(b,d,b,a,k,f);return na(f)}}}function kE(a,b){return hC(a,b,"gutterClick",true)}function mI(a,b){if(my(a.display,b)||jh(a,b)){return}if(nT(a,b,"contextmenu")){return}a.display.input.onContextMenu(b)}function jh(a,b){if(!iI(a,"gutterContextMenu")){return false}return hC(a,b,"gutterContextMenu",false)}function lC(a){a.display.wrapper.className=a.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+a.options.theme.replace(/(^|\s)\s*/g," cm-s-");nm(a)}var kQ={toString:function(){return"CodeMirror.Init"}};var hB={};var mk={};function i0(a){var b=a.optionHandlers;function c(g,d,e,f){a.defaults[g]=d;if(e){b[g]=f?function(h,j,k){if(k!=kQ){e(h,j,k)}}:e}}a.defineOption=c;a.Init=kQ;c("value","",function(e,d){return e.setValue(d)},true);c("mode",null,function(e,d){e.doc.modeOption=d;ly(e)},true);c("indentUnit",2,ly,true);c("indentWithTabs",false);c("smartIndent",true);c("tabSize",4,function(d){h1(d);nm(d);np(d)},true);c("lineSeparator",null,function(h,e){h.doc.lineSep=e;if(!e){return}var f=[],d=h.doc.first;h.doc.iter(function(l){for(var j=0;;){var k=l.text.indexOf(e,j);if(k==-1){break}j=k+e.length;f.push(lA(d,k))}d++});for(var g=f.length-1;g>=0;g--){gT(h.doc,e,f[g],lA(f[g].line,f[g].ch+e.length))}});c("specialChars",/[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g,function(f,d,e){f.state.specialChars=new RegExp(d.source+(d.test("\t")?"":"|\t"),"g");if(e!=kQ){f.refresh()}});c("specialCharPlaceholder",hn,function(d){return d.refresh()},true);c("electricChars",true);c("inputStyle",ie?"contenteditable":"textarea",function(){throw new Error("inputStyle can not (yet) be changed in a running editor")},true);c("spellcheck",false,function(e,d){return e.getInputField().spellcheck=d},true);c("rtlMoveVisually",!nW);c("wholeLineUpdateBefore",true);c("theme","default",function(d){lC(d);k1(d)},true);c("keyMap","default",function(h,d,g){var f=g6(d);var e=g!=kQ&&g6(g);if(e&&e.detach){e.detach(h,f)}if(f.attach){f.attach(h,e||null)}});c("extraKeys",null);c("lineWrapping",false,i2,true);c("gutters",[],function(d){kK(d.options);k1(d)},true);c("fixedGutter",true,function(e,d){e.display.gutters.style.left=d?lw(e.display)+"px":"0";e.refresh()},true);c("coverGutterNextToScrollbar",false,function(d){return hK(d)},true);c("scrollbarStyle","native",function(d){oa(d);hK(d);d.display.scrollbars.setScrollTop(d.doc.scrollTop);d.display.scrollbars.setScrollLeft(d.doc.scrollLeft)},true);c("lineNumbers",false,function(d){kK(d.options);k1(d)},true);c("firstLineNumber",1,k1,true);c("lineNumberFormatter",function(d){return d},k1,true);c("showCursorWhenSelecting",false,nu,true);c("resetSelectionOnContextMenu",true);c("lineWiseCopyCut",true);c("readOnly",false,function(e,d){if(d=="nocursor"){nL(e);e.display.input.blur();e.display.disabled=true}else{e.display.disabled=false}e.display.input.readOnlyChanged(d)});c("disableInput",false,function(e,d){if(!d){e.display.input.reset()}},true);c("dragDrop",true,g2);c("allowDropFileTypes",null);c("cursorBlinkRate",530);c("cursorScrollMargin",0);c("cursorHeight",1,nu,true);c("singleCursorHeightPerLine",true,nu,true);c("workTime",100);c("workDelay",100);c("flattenSpans",true,h1,true);c("addModeClass",false,h1,true);c("pollInterval",100);c("undoDepth",200,function(e,d){return e.doc.history.undoDepth=d});c("historyEventDelay",1250);c("viewportMargin",10,function(d){return d.refresh()},true);c("maxHighlightLength",10000,h1,true);c("moveInputWithCursor",true,function(e,d){if(!d){e.display.input.resetPosition()}});c("tabindex",null,function(e,d){return e.display.input.getField().tabIndex=d||""});c("autofocus",null)}function k1(a){io(a);np(a);setTimeout(function(){return ja(a)},20)}function g2(f,b,e){var a=e&&e!=kQ;if(!b!=!a){var d=f.display.dragFunctions;var c=b?nY:ih;c(f.display.scroller,"dragstart",d.start);c(f.display.scroller,"dragenter",d.enter);c(f.display.scroller,"dragover",d.over);c(f.display.scroller,"dragleave",d.leave);c(f.display.scroller,"drop",d.drop)}}function i2(a){if(a.options.lineWrapping){h6(a.display.wrapper,"CodeMirror-wrap");a.display.sizer.style.minWidth="";a.display.sizerWidth=null}else{kS(a.display.wrapper,"CodeMirror-wrap");kY(a)}lr(a);np(a);nm(a);setTimeout(function(){return hK(a)},100)}function hx(g,e){var h=this;if(!(this instanceof hx)){return new hx(g,e)}this.options=e=e?n0(e):{};n0(hB,e,false);kK(e);var a=e.value;if(typeof a=="string"){a=new mT(a,e.mode,null,e.lineSeparator)}this.doc=a;var f=new hx.inputStyles[e.inputStyle](this);var b=this.display=new iU(g,a,f);b.wrapper.CodeMirror=this;io(this);lC(this);if(e.lineWrapping){this.display.wrapper.className+=" CodeMirror-wrap"}if(e.autofocus&&!ie){b.input.focus()}oa(this);this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:false,delayingBlurEvent:false,focused:false,suppressEdits:false,pasteIncoming:false,cutIncoming:false,selectingText:false,draggingText:false,highlight:new hA(),keySeq:null,specialChars:null};if(kj&&kH<11){setTimeout(function(){return h.display.input.reset(true)},20)}hf(this);l8();lY(this);this.curOp.forceUpdate=true;ip(this,a);if((e.autofocus&&!ie)||this.hasFocus()){setTimeout(mD(mm,this),20)}else{nL(this)}for(var c in mk){if(mk.hasOwnProperty(c)){mk[c](h,e[c],kQ)}}iE(this);if(e.finishInit){e.finishInit(this)}for(var d=0;d20*20}nY(c.scroller,"touchstart",function(j){if(!nT(h,j)&&!e(j)){c.input.ensurePolled();clearTimeout(a);var k=+new Date;c.activeTouch={start:k,moved:false,prev:k-f.end<=300?f:null};if(j.touches.length==1){c.activeTouch.left=j.touches[0].pageX;c.activeTouch.top=j.touches[0].pageY}}});nY(c.scroller,"touchmove",function(){if(c.activeTouch){c.activeTouch.moved=true}});nY(c.scroller,"touchend",function(l){var j=c.activeTouch;if(j&&!my(c,l)&&j.left!=null&&!j.moved&&new Date-j.start<300){var k=h.coordsChar(c.activeTouch,"page"),m;if(!j.prev||g(j,j.prev)){m=new lz(k,k)}else{if(!j.prev.prev||g(j,j.prev.prev)){m=h.findWordAt(k)}else{m=new lz(lA(k.line,0),i3(h.doc,lA(k.line+1,0)))}}h.setSelection(m.anchor,m.head);h.focus();l7(l)}b()});nY(c.scroller,"touchcancel",b);nY(c.scroller,"scroll",function(){if(c.scroller.clientHeight){l4(h,c.scroller.scrollTop);nq(h,c.scroller.scrollLeft,true);n9(h,"scroll",h)}});nY(c.scroller,"mousewheel",function(j){return k8(h,j)});nY(c.scroller,"DOMMouseScroll",function(j){return k8(h,j)});nY(c.wrapper,"scroll",function(){return c.wrapper.scrollTop=c.wrapper.scrollLeft=0});c.dragFunctions={enter:function(j){if(!nT(h,j)){jK(j)}},over:function(j){if(!nT(h,j)){hD(h,j);jK(j)}},start:function(j){return l0(h,j)},drop:mQ(h,l5),leave:function(j){if(!nT(h,j)){jx(h)}}};var d=c.input.getField();nY(d,"keyup",function(j){return me.call(h,j)});nY(d,"keydown",mQ(h,kp));nY(d,"keypress",mQ(h,mw));nY(d,"focus",function(j){return mm(h,j)});nY(d,"blur",function(j){return nL(h,j)})}var a9=[];hx.defineInitHook=function(a){return a9.push(a)};function nz(r,l,a,n){var b=r.doc,o;if(a==null){a="add"}if(a=="smart"){if(!b.mode.indent){a="prev"}else{o=kM(r,l)}}var g=r.options.tabSize;var q=hm(b,l),h=mV(q.text,null,g);if(q.stateAfter){q.stateAfter=null}var p=q.text.match(/^\s*/)[0],d;if(!n&&!/\S/.test(q.text)){d=0;a="not"}else{if(a=="smart"){d=b.mode.indent(o,q.text.slice(p.length),q.text);if(d==kU||d>150){if(!n){return}a="prev"}}}if(a=="prev"){if(l>b.first){d=mV(hm(b,l-1).text,null,g)}else{d=0}}else{if(a=="add"){d=h+r.options.indentUnit}else{if(a=="subtract"){d=h-r.options.indentUnit}else{if(typeof a=="number"){d=h+a}}}}d=Math.max(0,d);var c="",e=0;if(r.options.indentWithTabs){for(var k=Math.floor(d/g);k;--k){e+=g;c+="\t"}}if(e1){if(lZ&&lZ.text.join("\n")==k){if(n.ranges.length%lZ.text.length==0){p=[];for(var j=0;j=0;o--){var q=n.ranges[o];var d=q.from(),e=q.to();if(q.empty()){if(m&&m>0){d=lA(d.line,d.ch-m)}else{if(a.state.overwrite&&!l){e=lA(e.line,Math.min(hm(c,e.line).text.length,e.ch+ji(h).length))}else{if(lZ&&lZ.lineWise&&lZ.text.join("\n")==k){d=e=lA(d.line,0)}}}}g=a.curOp.updateInput;var f={from:d,to:e,text:p?p[o%p.length]:h,origin:b||(l?"paste":a.state.cutIncoming?"cut":"+input")};mi(a.doc,f);nw(a,"inputRead",a,f)}if(k&&!l){g7(a,k)}jl(a);a.curOp.updateInput=g;a.curOp.typing=true;a.state.pasteIncoming=a.state.cutIncoming=false}function mz(b,a){var c=b.clipboardData&&b.clipboardData.getData("Text");if(c){b.preventDefault();if(!a.isReadOnly()&&!a.options.disableInput){lI(a,function(){return g4(a,c,0,null,"paste")})}return true}}function g7(g,d){if(!g.options.electricChars||!g.options.smartIndent){return}var c=g.doc.sel;for(var e=c.ranges.length-1;e>=0;e--){var a=c.ranges[e];if(a.head.ch>100||(e&&c.ranges[e-1].head.line==a.head.line)){continue}var b=g.getModeAt(a.head);var h=false;if(b.electricChars){for(var f=0;f-1){h=nz(g,a.head.line,"smart");break}}}else{if(b.electricInput){if(b.electricInput.test(hm(g.doc,a.head.line).text.slice(0,a.head.ch))){h=nz(g,a.head.line,"smart")}}}if(h){nw(g,"electricInput",g,a.head.line)}}}function jf(f){var a=[],d=[];for(var c=0;c0){k2(h.doc,j,new lz(g,m[j].to()),ln)}}else{if(d.head.line>l){nz(h,d.head.line,e,true);l=d.head.line;if(j==h.doc.sel.primIndex){jl(h)}}}}}),getTokenAt:function(d,e){return j4(this,d,e)},getLineTokens:function(d,e){return j4(this,lA(d),e,true)},getTokenTypeAt:function(j){j=i3(this.doc,j);var e=jG(this,hm(this.doc,j.line));var l=0,k=(e.length-1)/2,f=j.ch;var g;if(f==0){g=e[2]}else{for(;;){var h=(l+k)>>1;if((h?e[h*2-1]:0)>=f){k=h}else{if(e[h*2+1]e){g=e;h=true}}f=hm(this.doc,g)}else{f=g}return j8(this,f,{top:0,left:0},j||"page",d).top+(h?this.doc.height-m7(f):0)},defaultTextHeight:function(){return nF(this.display)},defaultCharWidth:function(){return kJ(this.display)},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(h,l,f,k,d){var j=this.display;h=lL(this,i3(this.doc,h));var g=h.bottom,m=h.left;l.style.position="absolute";l.setAttribute("cm-ignore-events","true");this.display.input.setUneditable(l);j.sizer.appendChild(l);if(k=="over"){g=h.top}else{if(k=="above"||k=="near"){var n=Math.max(j.wrapper.clientHeight,this.doc.height),e=Math.max(j.sizer.clientWidth,j.lineSpace.clientWidth);if((k=="above"||h.bottom+l.offsetHeight>n)&&h.top>l.offsetHeight){g=h.top-l.offsetHeight}else{if(h.bottom+l.offsetHeight<=n){g=h.bottom}}if(m+l.offsetWidth>e){m=e-l.offsetWidth}}}l.style.top=g+"px";l.style.left=l.style.right="";if(d=="right"){m=j.sizer.clientWidth-l.offsetWidth;l.style.right="0px"}else{if(d=="left"){m=0}else{if(d=="middle"){m=(j.sizer.clientWidth-l.offsetWidth)/2}}l.style.left=m+"px"}if(f){mt(this,m,g,m+l.offsetWidth,g+l.offsetHeight)}},triggerOnKeyDown:jC(kp),triggerOnKeyPress:jC(mw),triggerOnKeyUp:me,execCommand:function(d){if(jg.hasOwnProperty(d)){return jg[d].call(null,this)}},triggerElectric:jC(function(d){g7(this,d)}),findPosH:function(j,e,d,g){var k=this;var h=1;if(e<0){h=-1;e=-e}var l=i3(this.doc,j);for(var f=0;f0&&h(e.charAt(k-1))){--k}while(f0.5){lr(this)}n9(this,"refresh",this)}),swapDoc:jC(function(d){var e=this.doc;e.cm=null;ip(this,d);nm(this);this.display.input.reset();this.scrollTo(d.scrollLeft,d.scrollTop);this.curOp.forceScroll=true;nw(this,"swapDoc",this,e);return e}),getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}};nA(a);a.registerHelper=function(e,f,d){if(!b.hasOwnProperty(e)){b[e]=a[e]={_global:[]}}b[e][f]=d};a.registerGlobalHelper=function(e,f,g,d){a.registerHelper(e,f,d);b[e]._global.push({pred:g,val:d})}};function lg(r,m,d,e,k){var g=m.line,f=m.ch,s=d;var p=hm(r,g);function a(){var t=g+d;if(t=r.first+r.size){return false}g=t;return p=hm(r,t)}function b(t){var u=(k?ka:nn)(p,f,d,true);if(u==null){if(!t&&a()){if(k){f=(d<0?lj:md)(p)}else{f=d<0?p.text.length:0}}else{return false}}else{f=u}return true}if(e=="char"){b()}else{if(e=="column"){b(true)}else{if(e=="word"||e=="group"){var c=null,j=e=="group";var q=r.cm&&r.cm.getHelper(m,"wordChars");for(var l=true;;l=false){if(d<0&&!b(!l)){break}var o=p.text.charAt(f)||"\n";var n=mo(o,q)?"w":j&&o=="\n"?"n":!j||/\s/.test(o)?null:"p";if(j&&!l&&!n){n="s"}if(c&&c!=n){if(d<0){d=1;b()}break}if(n){c=n}if(d>0&&!b(!l)){break}}}}}var h=mJ(r,lA(g,f),m,s,true);if(!kI(m,h)){h.hitSide=true}return h}function lB(b,g,k,c){var d=b.doc,e=g.left,f;if(c=="page"){var h=Math.min(b.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);var a=Math.max(h-0.5*nF(b.display),3);f=(k>0?g.bottom:g.top)+k*a}else{if(c=="line"){f=k>0?g.bottom+3:g.top-3}}var j;for(;;){j=hg(b,e,f);if(!j.outside){break}if(k<0?f<=0:f>=d.height){j.hitSide=true;break}f+=k*5}return j}function k7(a){this.cm=a;this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null;this.polling=new hA();this.composing=null;this.gracePeriod=false;this.readDOMTimeout=null}k7.prototype=n0({init:function(d){var a=this;var e=this,c=e.cm;var b=e.div=d.lineDiv;hh(b,c.options.spellcheck);nY(b,"paste",function(g){if(nT(c,g)||mz(g,c)){return}if(kH<=11){setTimeout(mQ(c,function(){if(!e.pollContent()){np(c)}}),20)}});nY(b,"compositionstart",function(g){a.composing={data:g.data}});nY(b,"compositionupdate",function(g){if(!a.composing){a.composing={data:g.data}}});nY(b,"compositionend",function(g){if(a.composing){if(g.data!=a.composing.data){a.readFromDOMSoon()}a.composing=null}});nY(b,"touchstart",function(){return e.forceCompositionEnd()});nY(b,"input",function(){if(!a.composing){a.readFromDOMSoon()}});function f(k){if(nT(c,k)){return}if(c.somethingSelected()){jJ({lineWise:false,text:c.getSelections()});if(k.type=="cut"){c.replaceSelection("",null,"cut")}}else{if(!c.options.lineWiseCopyCut){return}else{var g=jf(c);jJ({lineWise:true,text:g.text});if(k.type=="cut"){c.operation(function(){c.setSelections(g.ranges,0,ln);c.replaceSelection("",null,"cut")})}}}if(k.clipboardData){k.clipboardData.clearData();var l=lZ.text.join("\n");k.clipboardData.setData("Text",l);if(k.clipboardData.getData("Text")==l){k.preventDefault();return}}var m=nG(),j=m.firstChild;c.display.lineSpace.insertBefore(m,c.display.lineSpace.firstChild);j.value=lZ.text.join("\n");var h=document.activeElement;kf(j);setTimeout(function(){c.display.lineSpace.removeChild(m);h.focus();if(h==b){e.showPrimarySelection()}},50)}nY(b,"copy",f);nY(b,"cut",f)},prepareSelection:function(){var a=i9(this.cm,false);a.focus=this.cm.state.focused;return a},showSelection:function(b,a){if(!b||!this.cm.display.view.length){return}if(b.focus||a){this.showPrimarySelection()}this.showMultipleSelections(b)},showPrimarySelection:function(){var k=window.getSelection(),g=this.cm.doc.sel.primary();var j=mG(this.cm,k.anchorNode,k.anchorOffset);var e=mG(this.cm,k.focusNode,k.focusOffset);if(j&&!j.bad&&e&&!e.bad&&kI(m3(j,e),g.from())==0&&kI(nD(j,e),g.to())==0){return}var l=mq(this.cm,g.from());var f=mq(this.cm,g.to());if(!l&&!f){return}var b=this.cm.display.view;var h=k.rangeCount&&k.getRangeAt(0);if(!l){l={node:b[0].measure.map[2],offset:0}}else{if(!f){var m=b[b.length-1].measure;var a=m.maps?m.maps[m.maps.length-1]:m.map;f={node:a[a.length-1],offset:a[a.length-2]-a[a.length-3]}}}var d;try{d=ki(l.node,l.offset,f.offset,f.node)}catch(c){}if(d){if(!kc&&this.cm.state.focused){k.collapse(l.node,l.offset);if(!d.collapsed){k.removeAllRanges();k.addRange(d)}}else{k.removeAllRanges();k.addRange(d)}if(h&&k.anchorNode==null){k.addRange(h)}else{if(kc){this.startGracePeriod()}}}this.rememberSelection()},startGracePeriod:function(){var a=this;clearTimeout(this.gracePeriod);this.gracePeriod=setTimeout(function(){a.gracePeriod=false;if(a.selectionChanged()){a.cm.operation(function(){return a.cm.curOp.selectionChanged=true})}},20)},showMultipleSelections:function(a){mR(this.cm.display.cursorDiv,a.cursors);mR(this.cm.display.selectionDiv,a.selection)},rememberSelection:function(){var a=window.getSelection();this.lastAnchorNode=a.anchorNode;this.lastAnchorOffset=a.anchorOffset;this.lastFocusNode=a.focusNode;this.lastFocusOffset=a.focusOffset},selectionInEditor:function(){var b=window.getSelection();if(!b.rangeCount){return false}var a=b.getRangeAt(0).commonAncestorContainer;return hR(this.div,a)},focus:function(){if(this.cm.options.readOnly!="nocursor"){if(!this.selectionInEditor()){this.showSelection(this.prepareSelection(),true)}this.div.focus()}},blur:function(){this.div.blur()},getField:function(){return this.div},supportsTouch:function(){return true},receivedFocus:function(){var a=this;if(this.selectionInEditor()){this.pollSelection()}else{lI(this.cm,function(){return a.cm.curOp.selectionChanged=true})}function b(){if(a.cm.state.focused){a.pollSelection();a.polling.set(a.cm.options.pollInterval,b)}}this.polling.set(this.cm.options.pollInterval,b)},selectionChanged:function(){var a=window.getSelection();return a.anchorNode!=this.lastAnchorNode||a.anchorOffset!=this.lastAnchorOffset||a.focusNode!=this.lastFocusNode||a.focusOffset!=this.lastFocusOffset},pollSelection:function(){if(!this.composing&&this.readDOMTimeout==null&&!this.gracePeriod&&this.selectionChanged()){var b=window.getSelection(),a=this.cm;this.rememberSelection();var d=mG(a,b.anchorNode,b.anchorOffset);var c=mG(a,b.focusNode,b.focusOffset);if(d&&c){lI(a,function(){mL(a.doc,j1(d,c),ln);if(d.bad||c.bad){a.curOp.selectionChanged=true}})}}},pollContent:function(){if(this.readDOMTimeout!=null){clearTimeout(this.readDOMTimeout);this.readDOMTimeout=null}var f=this.cm,t=f.display,v=f.doc.sel.primary();var u=v.from(),m=v.to();if(u.ch==0&&u.line>f.firstLine()){u=lA(u.line-1,hm(f.doc,u.line-1).length)}if(m.ch==hm(f.doc,m.line).text.length&&m.linet.viewTo-1){return false}var j,l,g;if(u.line==t.viewFrom||(j=iR(f,u.line))==0){l=m5(t.view[0].line);g=t.view[0].node}else{l=m5(t.view[j].line);g=t.view[j-1].node.nextSibling}var w=iR(f,m.line);var d,a;if(w==t.view.length-1){d=t.viewTo-1;a=t.lineDiv.lastChild}else{d=m5(t.view[w+1].line)-1;a=t.view[w+1].node.previousSibling}if(!g){return false}var s=f.doc.splitLines(g3(f,g,a,l,d));var b=gY(f.doc,lA(l,0),lA(d,hm(f.doc,d).text.length));while(s.length>1&&b.length>1){if(ji(s)==ji(b)){s.pop();b.pop();d--}else{if(s[0]==b[0]){s.shift();b.shift();l++}else{break}}}var x=0,o=0;var e=s[0],p=b[0],q=Math.min(e.length,p.length);while(x1||s[0]||kI(k,c)){gT(f.doc,s,k,c,"+input");return true}},ensurePolled:function(){this.forceCompositionEnd()},reset:function(){this.forceCompositionEnd()},forceCompositionEnd:function(){if(!this.composing){return}this.composing=null;if(!this.pollContent()){np(this.cm)}this.div.blur();this.div.focus()},readFromDOMSoon:function(){var a=this;if(this.readDOMTimeout!=null){return}this.readDOMTimeout=setTimeout(function(){a.readDOMTimeout=null;if(a.composing){return}if(a.cm.isReadOnly()||!a.pollContent()){lI(a.cm,function(){return np(a.cm)})}},80)},setUneditable:function(a){a.contentEditable="false"},onKeyPress:function(a){a.preventDefault();if(!this.cm.isReadOnly()){mQ(this.cm,g4)(this.cm,String.fromCharCode(a.charCode==null?a.keyCode:a.charCode),0)}},readOnlyChanged:function(a){this.div.contentEditable=String(a!="nocursor")},onContextMenu:g8,resetPosition:g8,needsContentAttribute:true},k7.prototype);function mq(d,f){var e=hp(d,f.line);if(!e||e.hidden){return null}var b=hm(d.doc,f.line);var j=jT(e,b,f.line);var h=ld(b),g="left";if(h){var a=n6(h,f.ch);g=a%2?"right":"left"}var c=n3(j.map,f.ch,g);c.offset=c.collapse=="right"?c.end:c.start;return c}function jH(b,a){if(a){b.bad=true}return b}function g3(c,e,d,h,k){var a="",j=false,b=c.doc.lineSeparator();function g(l){return function(m){return m.id==l}}function f(o){if(o.nodeType==1){var r=o.getAttribute("cm-text");if(r!=null){if(r==""){a+=o.textContent.replace(/\u200b/g,"")}else{a+=r}return}var p=o.getAttribute("cm-marker"),l;if(p){var n=c.findMarks(lA(h,0),lA(k+1,0),g(+p));if(n.length&&(l=n[0].find())){a+=gY(c.doc,l.from,l.to).join(b)}return}if(o.getAttribute("contenteditable")=="false"){return}for(var q=0;q=9&&a.hasSelection){a.hasSelection=null}g.poll()});nY(d,"paste",function(h){if(nT(e,h)||mz(h,e)){return}e.state.pasteIncoming=true;g.fastPoll()});function c(j){if(nT(e,j)){return}if(e.somethingSelected()){jJ({lineWise:false,text:e.getSelections()});if(g.inaccurateSelection){g.prevInput="";g.inaccurateSelection=false;d.value=lZ.text.join("\n");kf(d)}}else{if(!e.options.lineWiseCopyCut){return}else{var h=jf(e);jJ({lineWise:true,text:h.text});if(j.type=="cut"){e.setSelections(h.ranges,null,ln)}else{g.prevInput="";d.value=h.text.join("\n");kf(d)}}}if(j.type=="cut"){e.state.cutIncoming=true}}nY(d,"cut",c);nY(d,"copy",c);nY(f.scroller,"paste",function(h){if(my(f,h)||nT(e,h)){return}e.state.pasteIncoming=true;g.focus()});nY(f.lineSpace,"selectstart",function(h){if(!my(f,h)){l7(h)}});nY(d,"compositionstart",function(){var h=e.getCursor("from");if(g.composing){g.composing.range.clear()}g.composing={start:h,range:e.markText(h,e.getCursor("to"),{className:"CodeMirror-composing"})}});nY(d,"compositionend",function(){if(g.composing){g.poll();g.composing.range.clear();g.composing=null}})},prepareSelection:function(){var g=this.cm,b=g.display,c=g.doc;var e=i9(g);if(g.options.moveInputWithCursor){var a=lL(g,c.sel.primary().head,"div");var f=b.wrapper.getBoundingClientRect(),d=b.lineDiv.getBoundingClientRect();e.teTop=Math.max(0,Math.min(b.wrapper.clientHeight-10,a.top+d.top-f.top));e.teLeft=Math.max(0,Math.min(b.wrapper.clientWidth-10,a.left+d.left-f.left))}return e},showSelection:function(b){var a=this.cm,c=a.display;mR(c.cursorDiv,b.cursors);mR(c.selectionDiv,b.selection);if(b.teTop!=null){this.wrapper.style.top=b.teTop+"px";this.wrapper.style.left=b.teLeft+"px"}},reset:function(e){if(this.contextMenuPending){return}var g,f,d=this.cm,b=d.doc;if(d.somethingSelected()){this.prevInput="";var a=b.sel.primary();g=jz&&(a.to().line-a.from().line>100||(f=d.getSelection()).length>1000);var c=g?"-":f||d.getSelection();this.textarea.value=c;if(d.state.focused){kf(this.textarea)}if(kj&&kH>=9){this.hasSelection=c}}else{if(!e){this.prevInput=this.textarea.value="";if(kj&&kH>=9){this.hasSelection=null}}}this.inaccurateSelection=g},getField:function(){return this.textarea},supportsTouch:function(){return false},focus:function(){if(this.cm.options.readOnly!="nocursor"&&(!ie||j3()!=this.textarea)){try{this.textarea.focus()}catch(a){}}},blur:function(){this.textarea.blur()},resetPosition:function(){this.wrapper.style.top=this.wrapper.style.left=0},receivedFocus:function(){this.slowPoll()},slowPoll:function(){var a=this;if(this.pollingFast){return}this.polling.set(this.cm.options.pollInterval,function(){a.poll();if(a.cm.state.focused){a.slowPoll()}})},fastPoll:function(){var c=false,a=this;a.pollingFast=true;function b(){var d=a.poll();if(!d&&!c){c=true;a.polling.set(60,b)}else{a.pollingFast=false;a.slowPoll()}}a.polling.set(20,b)},poll:function(){var a=this;var f=this.cm,g=this.textarea,e=this.prevInput;if(this.contextMenuPending||!f.state.focused||(lv(g)&&!e&&!this.composing)||f.isReadOnly()||f.options.disableInput||f.state.keySeq){return false}var c=g.value;if(c==e&&!f.somethingSelected()){return false}if(kj&&kH>=9&&this.hasSelection===c||k9&&/[\uf700-\uf7ff]/.test(c)){f.display.input.reset();return false}if(f.doc.sel==f.display.selForContextMenu){var d=c.charCodeAt(0);if(d==8203&&!e){e="\u200b"}if(d==8666){this.reset();return this.cm.execCommand("undo")}}var b=0,h=Math.min(e.length,c.length);while(b1000||c.indexOf("\n")>-1){g.value=a.prevInput=""}else{a.prevInput=c}if(a.composing){a.composing.range.clear();a.composing.range=f.markText(a.composing.start,f.getCursor("to"),{className:"CodeMirror-composing"})}});return true},ensurePolled:function(){if(this.pollingFast&&this.poll()){this.pollingFast=false}},onKeyPress:function(){if(kj&&kH>=9){this.hasSelection=null}this.fastPoll()},onContextMenu:function(l){var e=this,d=e.cm,j=d.display,p=e.textarea;var f=kg(d,l),g=j.scroller.scrollTop;if(!f||iL){return}var m=d.options.resetSelectionOnContextMenu;if(m&&d.doc.sel.contains(f)==-1){mQ(d,mL)(d.doc,j1(f),ln)}var k=p.style.cssText,a=e.wrapper.style.cssText;e.wrapper.style.cssText="position: absolute";var b=e.wrapper.getBoundingClientRect();p.style.cssText="position: absolute; width: 30px; height: 30px;\n top: "+(l.clientY-b.top-5)+"px; left: "+(l.clientX-b.left-5)+"px;\n z-index: 1000; background: "+(kj?"rgba(255, 255, 255, .05)":"transparent")+";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";var c;if(mU){c=window.scrollY}j.input.focus();if(mU){window.scrollTo(null,c)}j.input.reset();if(!d.somethingSelected()){p.value=e.prevInput=" "}e.contextMenuPending=true;j.selForContextMenu=d.doc.sel;clearTimeout(j.detectingSelectAll);function n(){if(p.selectionStart!=null){var r=d.somethingSelected();var q="\u200b"+(r?p.value:"");p.value="\u21da";p.value=q;e.prevInput=r?"":"\u200b";p.selectionStart=1;p.selectionEnd=q.length;j.selForContextMenu=d.doc.sel}}function h(){e.contextMenuPending=false;e.wrapper.style.cssText=a;p.style.cssText=k;if(kj&&kH<9){j.scrollbars.setScrollTop(j.scroller.scrollTop=g)}if(p.selectionStart!=null){if(!kj||(kj&&kH<9)){n()}var r=0,q=function(){if(j.selForContextMenu==d.doc.sel&&p.selectionStart==0&&p.selectionEnd>0&&e.prevInput=="\u200b"){mQ(d,nh)(d)}else{if(r++<10){j.detectingSelectAll=setTimeout(q,500)}else{j.input.reset()}}};j.detectingSelectAll=setTimeout(q,200)}}if(kj&&kH>=9){n()}if(hT){jK(l);var o=function(){ih(window,"mouseup",o);setTimeout(h,20)};nY(window,"mouseup",o)}else{setTimeout(h,50)}},readOnlyChanged:function(a){if(!a){this.reset()}},setUneditable:g8,needsContentAttribute:false},lt.prototype);function je(c,b){b=b?n0(b):{};b.value=c.value;if(!b.tabindex&&c.tabIndex){b.tabindex=c.tabIndex}if(!b.placeholder&&c.placeholder){b.placeholder=c.placeholder}if(b.autofocus==null){var a=j3();b.autofocus=a==c||c.getAttribute("autofocus")!=null&&a==document.body}function f(){c.value=d.getValue()}var e;if(c.form){nY(c.form,"submit",f);if(!b.leaveSubmitMethodAlone){var j=c.form;e=j.submit;try{var g=j.submit=function(){f();j.submit=e;j.submit();j.submit=g}}catch(h){}}}b.finishInit=function(k){k.save=f;k.getTextArea=function(){return c};k.toTextArea=function(){k.toTextArea=isNaN;f();c.parentNode.removeChild(k.getWrapperElement());c.style.display="";if(c.form){ih(c.form,"submit",f);if(typeof c.form.submit=="function"){c.form.submit=e}}}};c.style.display="none";var d=hx(function(k){return c.parentNode.insertBefore(k,c.nextSibling)},b);return d}function iZ(a){a.off=ih;a.on=nY;a.wheelEventPixels=mF;a.Doc=mT;a.splitLines=hd;a.countColumn=mV;a.findColumn=jM;a.isWordChar=h0;a.Pass=kU;a.signal=n9;a.Line=gW;a.changeEnd=m1;a.scrollbarModel=lX;a.Pos=lA;a.cmpPos=kI;a.modes=le;a.mimeModes=nQ;a.resolveMode=hP;a.getMode=h8;a.modeExtensions=iV;a.extendMode=jc;a.copyState=nK;a.startState=nR;a.innerMode=hX;a.commands=jg;a.keyMap=hq;a.keyName=iw;a.isModifierKey=jm;a.lookupKey=kN;a.normalizeKeyMap=l2;a.StringStream=hU;a.SharedTextMarker=jZ;a.TextMarker=lV;a.LineWidget=kO;a.e_preventDefault=l7;a.e_stopPropagation=iT;a.e_stop=jK;a.addClass=h6;a.contains=hR;a.rmClass=kS;a.keyNames=iQ}i0(hx);jX(hx);var k4="iter insert remove copy getEditor constructor".split(" ");for(var nd in mT.prototype){if(mT.prototype.hasOwnProperty(nd)&&jb(k4,nd)<0){hx.prototype[nd]=(function(a){return function(){return a.apply(this.doc,arguments)}})(mT.prototype[nd])}}nA(mT);hx.inputStyles={textarea:lt,contenteditable:k7};hx.defineMode=function(a){if(!hx.defaults.mode&&a!="null"){hx.defaults.mode=a}jE.apply(this,arguments)};hx.defineMIME=lT;hx.defineMode("null",function(){return({token:function(a){return a.skipToEnd()}})});hx.defineMIME("text/plain","null");hx.defineExtension=function(a,b){hx.prototype[a]=b};hx.defineDocExtension=function(a,b){mT.prototype[a]=b};hx.fromTextArea=je;iZ(hx);hx.version="5.21.0";return hx}))); \ No newline at end of file diff --git a/web/static/codemirror/lib/index.html b/web/static/codemirror/lib/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/codemirror/lib/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/codemirror/mode/index.html b/web/static/codemirror/mode/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/codemirror/mode/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/codemirror/mode/modeAll.js b/web/static/codemirror/mode/modeAll.js new file mode 100755 index 000000000..33a90a45b --- /dev/null +++ b/web/static/codemirror/mode/modeAll.js @@ -0,0 +1 @@ +(function(b){if(typeof exports=="object"&&typeof module=="object"){b(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],b)}else{b(CodeMirror)}}})(function(b){b.defineMode("sql",function(C,z){var B=z.client||{},w=z.atoms||{"false":true,"true":true,"null":true},s=z.builtin||{},y=z.keywords||{},x=z.operatorChars||/^[*+\-%<>!=&|~^]/,r=z.support||{},a=z.hooks||{},D=z.dateSQL||{date:true,time:true,timestamp:true};function A(c,e){var f=c.next();if(a[f]){var g=a[f](c,e);if(g!==false){return g}}if(r.hexNumber&&((f=="0"&&c.match(/^[xX][0-9a-fA-F]+/))||(f=="x"||f=="X")&&c.match(/^'[0-9a-fA-F]+'/))){return"number"}else{if(r.binaryNumber&&(((f=="b"||f=="B")&&c.match(/^'[01]+'/))||(f=="0"&&c.match(/^b[01]+/)))){return"number"}else{if(f.charCodeAt(0)>47&&f.charCodeAt(0)<58){c.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/);r.decimallessFloat&&c.eat(".");return"number"}else{if(f=="?"&&(c.eatSpace()||c.eol()||c.eat(";"))){return"variable-3"}else{if(f=="'"||(f=='"'&&r.doubleQuote)){e.tokenize=q(f);return e.tokenize(c,e)}else{if((((r.nCharCast&&(f=="n"||f=="N"))||(r.charsetCast&&f=="_"&&c.match(/[a-z][a-z0-9]*/i)))&&(c.peek()=="'"||c.peek()=='"'))){return"keyword"}else{if(/^[\(\),\;\[\]]/.test(f)){return null}else{if(r.commentSlashSlash&&f=="/"&&c.eat("/")){c.skipToEnd();return"comment"}else{if((r.commentHash&&f=="#")||(f=="-"&&c.eat("-")&&(!r.commentSpaceRequired||c.eat(" ")))){c.skipToEnd();return"comment"}else{if(f=="/"&&c.eat("*")){e.tokenize=u;return e.tokenize(c,e)}else{if(f=="."){if(r.zerolessFloat&&c.match(/^(?:\d+(?:e[+-]?\d+)?)/i)){return"number"}if(r.ODBCdotTable&&c.match(/^[a-zA-Z_]+/)){return"variable-2"}}else{if(x.test(f)){c.eatWhile(x);return null}else{if(f=="{"&&(c.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/)||c.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/))){return"number"}else{c.eatWhile(/^[_\w\d]/);var d=c.current().toLowerCase();if(D.hasOwnProperty(d)&&(c.match(/^( )+'[^']*'/)||c.match(/^( )+"[^"]*"/))){return"number"}if(w.hasOwnProperty(d)){return"atom"}if(s.hasOwnProperty(d)){return"builtin"}if(y.hasOwnProperty(d)){return"keyword"}if(B.hasOwnProperty(d)){return"string-2"}return null}}}}}}}}}}}}}}function q(c){return function(d,f){var e=false,g;while((g=d.next())!=null){if(g==c&&!e){f.tokenize=A;break}e=!e&&g=="\\"}return"string"}}function u(c,d){while(true){if(c.skipTo("*")){c.next();if(c.eat("/")){d.tokenize=A;break}}else{c.skipToEnd();break}}return"comment"}function v(c,d,e){d.context={prev:d.context,indent:c.indentation(),col:c.column(),type:e}}function t(c){c.indent=c.context.indent;c.context=c.context.prev}return{startState:function(){return{tokenize:A,context:null}},token:function(c,d){if(c.sol()){if(d.context&&d.context.align==null){d.context.align=false}}if(c.eatSpace()){return null}var e=d.tokenize(c,d);if(e=="comment"){return e}if(d.context&&d.context.align==null){d.context.align=true}var f=c.current();if(f=="("){v(c,d,")")}else{if(f=="["){v(c,d,"]")}else{if(d.context&&d.context.type==f){t(d)}}}return e},indent:function(c,e){var f=c.context;if(!f){return b.Pass}var d=e.charAt(0)==f.type;if(f.align){return f.col+(d?0:1)}else{return f.indent+(d?0:C.indentUnit)}},blockCommentStart:"/*",blockCommentEnd:"*/",lineComment:r.commentSlashSlash?"//":r.commentHash?"#":null}});(function(){function j(c){var d;while((d=c.next())!=null){if(d=="`"&&!c.eat("`")){return"variable-2"}}c.backUp(c.current().length-1);return c.eatWhile(/\w/)?"variable-2":null}function a(c){if(c.eat("@")){c.match(/^session\./);c.match(/^local\./);c.match(/^global\./)}if(c.eat("'")){c.match(/^.*'/);return"variable-2"}else{if(c.eat('"')){c.match(/^.*"/);return"variable-2"}else{if(c.eat("`")){c.match(/^.*`/);return"variable-2"}else{if(c.match(/^[0-9a-zA-Z$\.\_]+/)){return"variable-2"}}}}return null}function i(c){if(c.eat("N")){return"atom"}return c.match(/^[a-zA-Z.#!?]/)?"variable-2":null}var h="alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit ";function g(c){var e={},d=c.split(" ");for(var f=0;f!=]/,dateSQL:g("date time timestamp"),support:g("ODBCdotTable doubleQuote binaryNumber hexNumber")});b.defineMIME("text/x-mssql",{name:"sql",client:g("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),keywords:g(h+"begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare exec"),builtin:g("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "),atoms:g("false true null unknown"),operatorChars:/^[*+\-%<>!=]/,dateSQL:g("date datetimeoffset datetime2 smalldatetime datetime time"),hooks:{"@":a}});b.defineMIME("text/x-mysql",{name:"sql",client:g("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),keywords:g(h+"accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general get global grant grants group group_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"),builtin:g("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"),atoms:g("false true null unknown"),operatorChars:/^[*+\-%<>!=&|^]/,dateSQL:g("date time timestamp"),support:g("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"),hooks:{"@":a,"`":j,"\\":i}});b.defineMIME("text/x-mariadb",{name:"sql",client:g("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),keywords:g(h+"accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated get global grant grants group groupby_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show shutdown signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"),builtin:g("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"),atoms:g("false true null unknown"),operatorChars:/^[*+\-%<>!=&|^]/,dateSQL:g("date time timestamp"),support:g("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"),hooks:{"@":a,"`":j,"\\":i}});b.defineMIME("text/x-cassandra",{name:"sql",client:{},keywords:g("add all allow alter and any apply as asc authorize batch begin by clustering columnfamily compact consistency count create custom delete desc distinct drop each_quorum exists filtering from grant if in index insert into key keyspace keyspaces level limit local_one local_quorum modify nan norecursive nosuperuser not of on one order password permission permissions primary quorum rename revoke schema select set storage superuser table three to token truncate ttl two type unlogged update use user users using values where with writetime"),builtin:g("ascii bigint blob boolean counter decimal double float frozen inet int list map static text timestamp timeuuid tuple uuid varchar varint"),atoms:g("false true infinity NaN"),operatorChars:/^[<>=]/,dateSQL:{},support:g("commentSlashSlash decimallessFloat"),hooks:{}});b.defineMIME("text/x-plsql",{name:"sql",client:g("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"),keywords:g("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elseif elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning returns reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"),builtin:g("abs acos add_months ascii asin atan atan2 average bfile bfilename bigserial bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least length lengthb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim serial sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid unlogged upper user userenv varchar varchar2 variance varying vsize xml"),operatorChars:/^[*+\-%<>!=~]/,dateSQL:g("date time timestamp"),support:g("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber")});b.defineMIME("text/x-hive",{name:"sql",keywords:g("select alter $elem$ $key$ $value$ add after all analyze and archive as asc before between binary both bucket buckets by cascade case cast change cluster clustered clusterstatus collection column columns comment compute concatenate continue create cross cursor data database databases dbproperties deferred delete delimited desc describe directory disable distinct distribute drop else enable end escaped exclusive exists explain export extended external false fetch fields fileformat first format formatted from full function functions grant group having hold_ddltime idxproperties if import in index indexes inpath inputdriver inputformat insert intersect into is items join keys lateral left like limit lines load local location lock locks mapjoin materialized minus msck no_drop nocompress not of offline on option or order out outer outputdriver outputformat overwrite partition partitioned partitions percent plus preserve procedure purge range rcfile read readonly reads rebuild recordreader recordwriter recover reduce regexp rename repair replace restrict revoke right rlike row schema schemas semi sequencefile serde serdeproperties set shared show show_database sort sorted ssl statistics stored streamtable table tables tablesample tblproperties temporary terminated textfile then tmp to touch transform trigger true unarchive undo union uniquejoin unlock update use using utc utc_tmestamp view when where while with"),builtin:g("bool boolean long timestamp tinyint smallint bigint int float double date datetime unsigned string array struct map uniontype"),atoms:g("false true null unknown"),operatorChars:/^[*+\-%<>!=]/,dateSQL:g("date timestamp"),support:g("ODBCdotTable doubleQuote binaryNumber hexNumber")});b.defineMIME("text/x-pgsql",{name:"sql",client:g("source"),keywords:g(h+"a abort abs absent absolute access according action ada add admin after aggregate all allocate also always analyse analyze any are array array_agg array_max_cardinality asensitive assertion assignment asymmetric at atomic attribute attributes authorization avg backward base64 before begin begin_frame begin_partition bernoulli binary bit_length blob blocked bom both breadth c cache call called cardinality cascade cascaded case cast catalog catalog_name ceil ceiling chain characteristics characters character_length character_set_catalog character_set_name character_set_schema char_length check checkpoint class class_origin clob close cluster coalesce cobol collate collation collation_catalog collation_name collation_schema collect column columns column_name command_function command_function_code comment comments commit committed concurrently condition condition_number configuration conflict connect connection connection_name constraint constraints constraint_catalog constraint_name constraint_schema constructor contains content continue control conversion convert copy corr corresponding cost covar_pop covar_samp cross csv cube cume_dist current current_catalog current_date current_default_transform_group current_path current_role current_row current_schema current_time current_timestamp current_transform_group_for_type current_user cursor cursor_name cycle data database datalink datetime_interval_code datetime_interval_precision day db deallocate dec declare default defaults deferrable deferred defined definer degree delimiter delimiters dense_rank depth deref derived describe descriptor deterministic diagnostics dictionary disable discard disconnect dispatch dlnewcopy dlpreviouscopy dlurlcomplete dlurlcompleteonly dlurlcompletewrite dlurlpath dlurlpathonly dlurlpathwrite dlurlscheme dlurlserver dlvalue do document domain dynamic dynamic_function dynamic_function_code each element else empty enable encoding encrypted end end-exec end_frame end_partition enforced enum equals escape event every except exception exclude excluding exclusive exec execute exists exp explain expression extension external extract false family fetch file filter final first first_value flag float floor following for force foreign fortran forward found frame_row free freeze fs full function functions fusion g general generated get global go goto grant granted greatest grouping groups handler header hex hierarchy hold hour id identity if ignore ilike immediate immediately immutable implementation implicit import including increment indent index indexes indicator inherit inherits initially inline inner inout input insensitive instance instantiable instead integrity intersect intersection invoker isnull isolation k key key_member key_type label lag language large last last_value lateral lead leading leakproof least left length level library like_regex link listen ln load local localtime localtimestamp location locator lock locked logged lower m map mapping match matched materialized max maxvalue max_cardinality member merge message_length message_octet_length message_text method min minute minvalue mod mode modifies module month more move multiset mumps name names namespace national natural nchar nclob nesting new next nfc nfd nfkc nfkd nil no none normalize normalized nothing notify notnull nowait nth_value ntile null nullable nullif nulls number object occurrences_regex octets octet_length of off offset oids old only open operator option options ordering ordinality others out outer output over overlaps overlay overriding owned owner p pad parameter parameter_mode parameter_name parameter_ordinal_position parameter_specific_catalog parameter_specific_name parameter_specific_schema parser partial partition pascal passing passthrough password percent percentile_cont percentile_disc percent_rank period permission placing plans pli policy portion position position_regex power precedes preceding prepare prepared preserve primary prior privileges procedural procedure program public quote range rank read reads reassign recheck recovery recursive ref references referencing refresh regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy regr_syy reindex relative release rename repeatable replace replica requiring reset respect restart restore restrict result return returned_cardinality returned_length returned_octet_length returned_sqlstate returning returns revoke right role rollback rollup routine routine_catalog routine_name routine_schema row rows row_count row_number rule savepoint scale schema schema_name scope scope_catalog scope_name scope_schema scroll search second section security selective self sensitive sequence sequences serializable server server_name session session_user setof sets share show similar simple size skip snapshot some source space specific specifictype specific_name sql sqlcode sqlerror sqlexception sqlstate sqlwarning sqrt stable standalone start state statement static statistics stddev_pop stddev_samp stdin stdout storage strict strip structure style subclass_origin submultiset substring substring_regex succeeds sum symmetric sysid system system_time system_user t tables tablesample tablespace table_name temp template temporary then ties timezone_hour timezone_minute to token top_level_count trailing transaction transactions_committed transactions_rolled_back transaction_active transform transforms translate translate_regex translation treat trigger trigger_catalog trigger_name trigger_schema trim trim_array true truncate trusted type types uescape unbounded uncommitted under unencrypted unique unknown unlink unlisten unlogged unnamed unnest until untyped upper uri usage user user_defined_type_catalog user_defined_type_code user_defined_type_name user_defined_type_schema using vacuum valid validate validator value value_of varbinary variadic var_pop var_samp verbose version versioning view views volatile when whenever whitespace width_bucket window within work wrapper write xmlagg xmlattributes xmlbinary xmlcast xmlcomment xmlconcat xmldeclaration xmldocument xmlelement xmlexists xmlforest xmliterate xmlnamespaces xmlparse xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltext xmlvalidate year yes loop repeat"),builtin:g("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float8 inet integer int int4 interval json jsonb line lseg macaddr money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"),atoms:g("false true null unknown"),operatorChars:/^[*+\-%<>!=&|^\/#@?~]/,dateSQL:g("date time timestamp"),support:g("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast")});b.defineMIME("text/x-gql",{name:"sql",keywords:g("ancestor and asc by contains desc descendant distinct from group has in is limit offset on order select superset where"),atoms:g("false true"),builtin:g("blob datetime first key __key__ string integer double boolean null"),operatorChars:/^[*+\-%<>!=]/})}())});(function(b){if(typeof exports=="object"&&typeof module=="object"){b(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],b)}else{b(CodeMirror)}}})(function(e){var d={autoSelfClosers:{area:true,base:true,br:true,col:true,command:true,embed:true,frame:true,hr:true,img:true,input:true,keygen:true,link:true,meta:true,param:true,source:true,track:true,wbr:true,menuitem:true},implicitlyClosed:{dd:true,li:true,optgroup:true,option:true,p:true,rp:true,rt:true,tbody:true,td:true,tfoot:true,th:true,tr:true},contextGrabbers:{dd:{dd:true,dt:true},dt:{dd:true,dt:true},li:{li:true},option:{option:true,optgroup:true},optgroup:{optgroup:true},p:{address:true,article:true,aside:true,blockquote:true,dir:true,div:true,dl:true,fieldset:true,footer:true,form:true,h1:true,h2:true,h3:true,h4:true,h5:true,h6:true,header:true,hgroup:true,hr:true,menu:true,nav:true,ol:true,p:true,pre:true,section:true,table:true,ul:true},rp:{rp:true,rt:true},rt:{rp:true,rt:true},tbody:{tbody:true,tfoot:true},td:{td:true,th:true},tfoot:{tbody:true},th:{td:true,th:true},thead:{tbody:true,tfoot:true},tr:{tr:true}},doNotIndent:{pre:true},allowUnquoted:true,allowMissing:true,caseFold:true};var f={autoSelfClosers:{},implicitlyClosed:{},contextGrabbers:{},doNotIndent:{},allowUnquoted:false,allowMissing:false,caseFold:false};e.defineMode("xml",function(O,b){var H=O.indentUnit;var a={};var P=b.htmlMode?d:f;for(var V in P){a[V]=P[V]}for(var V in b){a[V]=b[V]}var U,T;function L(g,h){function j(l){h.tokenize=l;return l(g,h)}var i=g.next();if(i=="<"){if(g.eat("!")){if(g.eat("[")){if(g.match("CDATA[")){return j(c("atom","]]>"))}else{return null}}else{if(g.match("--")){return j(c("comment","-->"))}else{if(g.match("DOCTYPE",true,true)){g.eatWhile(/[\w\._\-]/);return j(K(1))}else{return null}}}}else{if(g.eat("?")){g.eatWhile(/[\w\._\-]/);h.tokenize=c("meta","?>");return"meta"}else{U=g.eat("/")?"closeTag":"openTag";h.tokenize=M;return"tag bracket"}}}else{if(i=="&"){var k;if(g.eat("#")){if(g.eat("x")){k=g.eatWhile(/[a-fA-F\d]/)&&g.eat(";")}else{k=g.eatWhile(/[\d]/)&&g.eat(";")}}else{k=g.eatWhile(/[\w\.\-:]/)&&g.eat(";")}return k?"atom":"error"}else{g.eatWhile(/[^&<]/);return null}}}L.isInText=true;function M(g,h){var i=g.next();if(i==">"||(i=="/"&&g.eat(">"))){h.tokenize=L;U=i==">"?"endTag":"selfcloseTag";return"tag bracket"}else{if(i=="="){U="equals";return null}else{if(i=="<"){h.tokenize=L;h.state=N;h.tagName=h.tagStart=null;var j=h.tokenize(g,h);return j?j+" tag error":"tag error"}else{if(/[\'\"]/.test(i)){h.tokenize=Q(i);h.stringStartCol=g.column();return h.tokenize(g,h)}else{g.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);return"word"}}}}}function Q(h){var g=function(i,j){while(!i.eol()){if(i.next()==h){j.tokenize=M;break}}return"string"};g.isInAttribute=true;return g}function c(g,h){return function(i,j){while(!i.eol()){if(i.match(h)){j.tokenize=L;break}i.next()}return g}}function K(g){return function(h,i){var j;while((j=h.next())!=null){if(j=="<"){i.tokenize=K(g+1);return i.tokenize(h,i)}else{if(j==">"){if(g==1){i.tokenize=L;break}else{i.tokenize=K(g-1);return i.tokenize(h,i)}}}}return"meta"}}function F(h,i,g){this.prev=h.context;this.tagName=i;this.indent=h.indented;this.startOfLine=g;if(a.doNotIndent.hasOwnProperty(i)||(h.context&&h.context.noIndent)){this.noIndent=true}}function C(g){if(g.context){g.context=g.context.prev}}function G(g,h){var i;while(true){if(!g.context){return}i=g.context.tagName;if(!a.contextGrabbers.hasOwnProperty(i)||!a.contextGrabbers[i].hasOwnProperty(h)){return}C(g)}}function N(i,g,h){if(i=="openTag"){h.tagStart=g.column();return X}else{if(i=="closeTag"){return D}else{return N}}}function X(i,g,h){if(i=="word"){h.tagName=g.current();T="tag";return W}else{T="error";return X}}function D(i,g,h){if(i=="word"){var j=g.current();if(h.context&&h.context.tagName!=j&&a.implicitlyClosed.hasOwnProperty(h.context.tagName)){C(h)}if((h.context&&h.context.tagName==j)||a.matchClosing===false){T="tag";return E}else{T="tag error";return J}}else{T="error";return J}}function E(h,i,g){if(h!="endTag"){T="error";return E}C(g);return N}function J(i,g,h){T="error";return E(i,g,h)}function W(h,j,g){if(h=="word"){T="attribute";return R}else{if(h=="endTag"||h=="selfcloseTag"){var i=g.tagName,k=g.tagStart;g.tagName=g.tagStart=null;if(h=="selfcloseTag"||a.autoSelfClosers.hasOwnProperty(i)){G(g,i)}else{G(g,i);g.context=new F(g,i,k==g.indented)}return N}}T="error";return W}function R(i,g,h){if(i=="equals"){return I}if(!a.allowMissing){T="error"}return W(i,g,h)}function I(i,g,h){if(i=="string"){return S}if(i=="word"&&a.allowUnquoted){T="string";return W}T="error";return W(i,g,h)}function S(i,g,h){if(i=="string"){return S}return W(i,g,h)}return{startState:function(h){var g={tokenize:L,state:N,indented:h||0,tagName:null,tagStart:null,context:null};if(h!=null){g.baseIndent=h}return g},token:function(g,h){if(!h.tagName&&g.sol()){h.indented=g.indentation()}if(g.eatSpace()){return null}U=null;var i=h.tokenize(g,h);if((i||U)&&i!="comment"){T=null;h.state=h.state(U||i,g,h);if(T){i=T=="error"?i+" error":T}}return i},indent:function(g,k,h){var i=g.context;if(g.tokenize.isInAttribute){if(g.tagStart==g.indented){return g.stringStartCol+1}else{return g.indented+H}}if(i&&i.noIndent){return e.Pass}if(g.tokenize!=M&&g.tokenize!=L){return h?h.match(/^(\s*)/)[0].length:0}if(g.tagName){if(a.multilineTagIndentPastTag!==false){return g.tagStart+g.tagName.length+2}else{return g.tagStart+H*(a.multilineTagIndentFactor||1)}}if(a.alignCDATA&&/$/,blockCommentStart:"",configuration:a.htmlMode?"html":"xml",helperType:a.htmlMode?"html":"xml",skipAttribute:function(g){if(g.state==I){g.state=W}}}});e.defineMIME("text/xml","xml");e.defineMIME("application/xml","xml");if(!e.mimeModes.hasOwnProperty("text/html")){e.defineMIME("text/html",{name:"xml",htmlMode:true})}});(function(b){if(typeof exports=="object"&&typeof module=="object"){b(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],b)}else{b(CodeMirror)}}})(function(A){function B(f,b,c,d,a,e){this.indented=f;this.column=b;this.type=c;this.info=d;this.align=a;this.prev=e}function L(d,b,e,a){var c=d.indented;if(d.context&&d.context.type=="statement"&&e!="statement"){c=d.context.indented}return d.context=new B(c,b,e,a,null,d.context)}function y(a){var b=a.context.type;if(b==")"||b=="]"||b=="}"){a.indented=a.context.indented}return a.context=a.context.prev}function R(a,b,c){if(b.prevToken=="variable"||b.prevToken=="variable-3"){return true}if(/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(a.string.slice(0,c))){return true}if(b.typeAtEndOfLine&&a.column()==a.indentation()){return true}}function C(a){for(;;){if(!a||a.type=="top"){return true}if(a.type=="}"&&a.prev.info!="namespace"){return false}a=a.prev}}A.defineMode("clike",function(d,W){var h=d.indentUnit,k=W.statementIndentUnit||h,g=W.dontAlignCalls,t=W.keywords||{},n=W.types||{},c=W.builtin||{},f=W.blockKeywords||{},i=W.defKeywords||{},o=W.atoms||{},q=W.hooks||{},m=W.multiLineStrings,p=W.indentStatements!==false,u=W.indentSwitch!==false,Z=W.namespaceSeparator,X=W.isPunctuationChar||/[\[\]{}\(\),;\:\.]/,l=W.numberStart||/[\d\.]/,s=W.number||/^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i,r=W.isOperatorChar||/[+\-*&%=<>!?|\/]/;var e,Y;function a(U,V){var aa=U.next();if(q[aa]){var S=q[aa](U,V);if(S!==false){return S}}if(aa=='"'||aa=="'"){V.tokenize=v(aa);return V.tokenize(U,V)}if(X.test(aa)){e=aa;return null}if(l.test(aa)){U.backUp(1);if(U.match(s)){return"number"}U.next()}if(aa=="/"){if(U.eat("*")){V.tokenize=j;return j(U,V)}if(U.eat("/")){U.skipToEnd();return"comment"}}if(r.test(aa)){while(!U.match(/^\/[\/*]/,false)&&U.eat(r)){}return"operator"}U.eatWhile(/[\w\$_\xa1-\uffff]/);if(Z){while(U.match(Z)){U.eatWhile(/[\w\$_\xa1-\uffff]/)}}var T=U.current();if(K(t,T)){if(K(f,T)){e="newstatement"}if(K(i,T)){Y=true}return"keyword"}if(K(n,T)){return"variable-3"}if(K(c,T)){if(K(f,T)){e="newstatement"}return"builtin"}if(K(o,T)){return"atom"}return"variable"}function v(S){return function(T,V){var U=false,aa,ab=false;while((aa=T.next())!=null){if(aa==S&&!U){ab=true;break}U=!U&&aa=="\\"}if(ab||!(U||m)){V.tokenize=null}return"string"}}function j(T,U){var S=false,V;while(V=T.next()){if(V=="/"&&S){U.tokenize=null;break}S=(V=="*")}return"comment"}function b(T,S){if(W.typeFirstDefinitions&&T.eol()&&C(S.context)){S.typeAtEndOfLine=R(T,S,T.pos)}}return{startState:function(S){return{tokenize:null,context:new B((S||0)-h,0,"top",null,false),indented:0,startOfLine:true,prevToken:null}},token:function(U,T){var aa=T.context;if(U.sol()){if(aa.align==null){aa.align=false}T.indented=U.indentation();T.startOfLine=true}if(U.eatSpace()){b(U,T);return null}e=Y=null;var V=(T.tokenize||a)(U,T);if(V=="comment"||V=="meta"){return V}if(aa.align==null){aa.align=true}if(e==";"||e==":"||(e==","&&U.match(/^\s*(?:\/\/.*)?$/,false))){while(T.context.type=="statement"){y(T)}}else{if(e=="{"){L(T,U.column(),"}")}else{if(e=="["){L(T,U.column(),"]")}else{if(e=="("){L(T,U.column(),")")}else{if(e=="}"){while(aa.type=="statement"){aa=y(T)}if(aa.type=="}"){aa=y(T)}while(aa.type=="statement"){aa=y(T)}}else{if(e==aa.type){y(T)}else{if(p&&(((aa.type=="}"||aa.type=="top")&&e!=";")||(aa.type=="statement"&&e=="newstatement"))){L(T,U.column(),"statement",U.current())}}}}}}}if(V=="variable"&&((T.prevToken=="def"||(W.typeFirstDefinitions&&R(U,T,U.start)&&C(T.context)&&U.match(/^\s*\(/,false))))){V="def"}if(q.token){var S=q.token(U,T,V);if(S!==undefined){V=S}}if(V=="def"&&W.styleDefs===false){V="variable"}T.startOfLine=false;T.prevToken=Y?"def":V||e;b(U,T);return V},indent:function(T,ad){if(T.tokenize!=a&&T.tokenize!=null||T.typeAtEndOfLine){return A.Pass}var S=T.context,ab=ad&&ad.charAt(0);if(S.type=="statement"&&ab=="}"){S=S.prev}if(W.dontIndentStatements){while(S.type=="statement"&&W.dontIndentStatements.test(S.info)){S=S.prev}}if(q.indent){var U=q.indent(T,S,ad);if(typeof U=="number"){return U}}var ac=ab==S.type;var V=S.prev&&S.prev.info=="switch";if(W.allmanIndentation&&/[{(]/.test(ab)){while(S.type!="top"&&S.type!="}"){S=S.prev}return S.indented}if(S.type=="statement"){return S.indented+(ab=="{"?0:k)}if(S.align&&(!g||S.type!=")")){return S.column+(ac?0:1)}if(S.type==")"&&!ac){return S.indented+k}return S.indented+(ac?0:h)+(!ac&&V&&!/^(?:case|default)\b/.test(ad)?h:0)},electricInput:u?/^\s*(?:case .*?:|default:|\{\}?|\})$/:/^\s*[{}]$/,blockCommentStart:"/*",blockCommentEnd:"*/",lineComment:"//",fold:"brace"}});function D(c){var a={},d=c.split(" ");for(var b=0;b <- <: <% >: # @ assert assume require print println printf readLine readBoolean readByte readShort readChar readInt readLong readFloat readDouble :: #:: "),types:D("AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable Compiler Double Exception Float Integer Long Math Number Object Package Pair Process Runtime Runnable SecurityManager Short StackTraceElement StrictMath String StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"),multiLineStrings:true,blockKeywords:D("catch class do else finally for forSome if match switch try while"),defKeywords:D("class def object package trait type val var"),atoms:D("true false null"),indentStatements:false,indentSwitch:false,hooks:{"@":function(a){a.eatWhile(/[\w\$_]/);return"meta"},'"':function(a,b){if(!a.match('""')){return false}b.tokenize=P;return b.tokenize(a,b)},"'":function(a){a.eatWhile(/[\w\$_\xa1-\uffff]/);return"atom"},"=":function(c,a){var b=a.context;if(b.type=="}"&&b.align&&c.eat(">")){a.context=new B(b.indented,b.column,b.type,b.info,null,b.prev);return"operator"}else{return false}}},modeProps:{closeBrackets:{triples:'"'}}});function Q(a){return function(f,d){var c=false,e,b=false;while(!f.eol()){if(!a&&!c&&f.match('"')){b=true;break}if(a&&f.match('"""')){b=true;break}e=f.next();if(!c&&e=="$"&&f.match("{")){f.skipTo("}")}c=!c&&e=="\\"&&!a}if(b||!a){d.tokenize=null}return"string"}}H("text/x-kotlin",{name:"clike",keywords:D("package as typealias class interface this super val var fun for is in This throw return break continue object if else while do try when !in !is as? file import where by get set abstract enum open inner override private public internal protected catch finally out final vararg reified dynamic companion constructor init sealed field property receiver param sparam lateinit data inline noinline tailrec external annotation crossinline const operator infix"),types:D("Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable Compiler Double Exception Float Integer Long Math Number Object Package Pair Process Runtime Runnable SecurityManager Short StackTraceElement StrictMath String StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"),intendSwitch:false,indentStatements:false,multiLineStrings:true,blockKeywords:D("catch class do else finally for if where try while enum"),defKeywords:D("class val var object package interface fun"),atoms:D("true false null this"),hooks:{'"':function(a,b){b.tokenize=Q(a.match('""'));return b.tokenize(a,b)}},modeProps:{closeBrackets:{triples:'"'}}});H(["x-shader/x-vertex","x-shader/x-fragment"],{name:"clike",keywords:D("sampler1D sampler2D sampler3D samplerCube sampler1DShadow sampler2DShadow const attribute uniform varying break continue discard return for while do if else struct in out inout"),types:D("float int bool void vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 mat2 mat3 mat4"),blockKeywords:D("for while do if else struct"),builtin:D("radians degrees sin cos tan asin acos atan pow exp log exp2 sqrt inversesqrt abs sign floor ceil fract mod min max clamp mix step smoothstep length distance dot cross normalize ftransform faceforward reflect refract matrixCompMult lessThan lessThanEqual greaterThan greaterThanEqual equal notEqual any all not texture1D texture1DProj texture1DLod texture1DProjLod texture2D texture2DProj texture2DLod texture2DProjLod texture3D texture3DProj texture3DLod texture3DProjLod textureCube textureCubeLod shadow1D shadow2D shadow1DProj shadow2DProj shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod dFdx dFdy fwidth noise1 noise2 noise3 noise4"),atoms:D("true false gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 gl_FogCoord gl_PointCoord gl_Position gl_PointSize gl_ClipVertex gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor gl_TexCoord gl_FogFragCoord gl_FragCoord gl_FrontFacing gl_FragData gl_FragDepth gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse gl_TexureMatrixTranspose gl_ModelViewMatrixInverseTranspose gl_ProjectionMatrixInverseTranspose gl_ModelViewProjectionMatrixInverseTranspose gl_TextureMatrixInverseTranspose gl_NormalScale gl_DepthRange gl_ClipPlane gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel gl_FrontLightModelProduct gl_BackLightModelProduct gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ gl_FogParameters gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits gl_MaxDrawBuffers"),indentSwitch:false,hooks:{"#":F},modeProps:{fold:["brace","include"]}});H("text/x-nesc",{name:"clike",keywords:D(G+"as atomic async call command component components configuration event generic implementation includes interface module new norace nx_struct nx_union post provides signal task uses abstract extends"),types:D(z),blockKeywords:D("case do else for if switch while struct"),atoms:D("null true false"),hooks:{"#":F},modeProps:{fold:["brace","include"]}});H("text/x-objectivec",{name:"clike",keywords:D(G+"inline restrict _Bool _Complex _Imaginary BOOL Class bycopy byref id IMP in inout nil oneway out Protocol SEL self super atomic nonatomic retain copy readwrite readonly"),types:D(z),atoms:D("YES NO NULL NILL ON OFF true false"),hooks:{"@":function(a){a.eatWhile(/[\w\$]/);return"keyword"},"#":F,indent:function(c,b,a){if(b.type=="statement"&&/^@\w/.test(a)){return b.indented}}},modeProps:{fold:"brace"}});H("text/x-squirrel",{name:"clike",keywords:D("base break clone continue const default delete enum extends function in class foreach local resume return this throw typeof yield constructor instanceof static"),types:D(z),blockKeywords:D("case catch class else for foreach if switch try while"),defKeywords:D("function local class"),typeFirstDefinitions:true,atoms:D("true false null"),hooks:{"#":F},modeProps:{fold:["brace","include"]}});var N=null;function I(a){return function(f,d){var c=false,e,b=false;while(!f.eol()){if(!c&&f.match('"')&&(a=="single"||f.match('""'))){b=true;break}if(!c&&f.match("``")){N=I(a);b=true;break}e=f.next();c=a=="single"&&!c&&e=="\\"}if(b){d.tokenize=null}return"string"}}H("text/x-ceylon",{name:"clike",keywords:D("abstracts alias assembly assert assign break case catch class continue dynamic else exists extends finally for function given if import in interface is let module new nonempty object of out outer package return satisfies super switch then this throw try value void while"),types:function(b){var a=b.charAt(0);return(a===a.toUpperCase()&&a!==a.toLowerCase())},blockKeywords:D("case catch class dynamic else finally for function if interface module new object switch try while"),defKeywords:D("class dynamic function interface module object package value"),builtin:D("abstract actual aliased annotation by default deprecated doc final formal late license native optional sealed see serializable shared suppressWarnings tagged throws variable"),isPunctuationChar:/[\[\]{}\(\),;\:\.`]/,isOperatorChar:/[+\-*&%=<>!?|^~:\/]/,numberStart:/[\d#$]/,number:/^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i,multiLineStrings:true,typeFirstDefinitions:true,atoms:D("true false null larger smaller equal empty finished"),indentSwitch:false,styleDefs:false,hooks:{"@":function(a){a.eatWhile(/[\w\$_]/);return"meta"},'"':function(a,b){b.tokenize=I(a.match('""')?"triple":"single");return b.tokenize(a,b)},"`":function(a,b){if(!N||!a.match("`")){return false}b.tokenize=N;N=null;return b.tokenize(a,b)},"'":function(a){a.eatWhile(/[\w\$_\xa1-\uffff]/);return"atom"},token:function(b,c,a){if((a=="variable"||a=="variable-3")&&c.prevToken=="."){return"variable-2"}}},modeProps:{fold:["brace","import"],closeBrackets:{triples:'"'}}})});(function(b){if(typeof exports=="object"&&typeof module=="object"){b(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],b)}else{b(CodeMirror)}}})(function(E){E.defineMode("css",function(b,r){var ac=r.inline;if(!r.propertyKeywords){r=E.resolveMode("text/css")}var j=b.indentUnit,af=r.tokenHooks,s=r.documentTypes||{},c=r.mediaTypes||{},o=r.mediaFeatures||{},k=r.mediaValueKeywords||{},t=r.propertyKeywords||{},ae=r.nonStandardPropertyKeywords||{},x=r.fontProperties||{},d=r.counterDescriptors||{},l=r.colorKeywords||{},h=r.valueKeywords||{},n=r.allowNested,f=r.supportsAtComponent===true;var ad,m;function a(X,W){ad=W;return X}function ag(W,X){var Y=W.next();if(af[Y]){var Z=af[Y](W,X);if(Z!==false){return Z}}if(Y=="@"){W.eatWhile(/[\w\\\-]/);return a("def",W.current())}else{if(Y=="="||(Y=="~"||Y=="|")&&W.eat("=")){return a(null,"compare")}else{if(Y=='"'||Y=="'"){X.tokenize=q(Y);return X.tokenize(W,X)}else{if(Y=="#"){W.eatWhile(/[\w\\\-]/);return a("atom","hash")}else{if(Y=="!"){W.match(/^\s*\w*/);return a("keyword","important")}else{if(/\d/.test(Y)||Y=="."&&W.eat(/\d/)){W.eatWhile(/[\w.%]/);return a("number","unit")}else{if(Y==="-"){if(/[\d.]/.test(W.peek())){W.eatWhile(/[\w.%]/);return a("number","unit")}else{if(W.match(/^-[\w\\\-]+/)){W.eatWhile(/[\w\\\-]/);if(W.match(/^\s*:/,false)){return a("variable-2","variable-definition")}return a("variable-2","variable")}else{if(W.match(/^\w+-/)){return a("meta","meta")}}}}else{if(/[,+>*\/]/.test(Y)){return a(null,"select-op")}else{if(Y=="."&&W.match(/^-?[_a-z][_a-z0-9-]*/i)){return a("qualifier","qualifier")}else{if(/[:;{}\[\]\(\)]/.test(Y)){return a(null,Y)}else{if((Y=="u"&&W.match(/rl(-prefix)?\(/))||(Y=="d"&&W.match("omain("))||(Y=="r"&&W.match("egexp("))){W.backUp(1);X.tokenize=ah;return a("property","word")}else{if(/[\w\\\-]/.test(Y)){W.eatWhile(/[\w\\\-]/);return a("property","word")}else{return a(null,null)}}}}}}}}}}}}}function q(W){return function(X,Z){var Y=false,aa;while((aa=X.next())!=null){if(aa==W&&!Y){if(W==")"){X.backUp(1)}break}Y=!Y&&aa=="\\"}if(aa==W||!Y&&W!=")"){Z.tokenize=null}return a("string","string")}}function ah(W,X){W.next();if(!W.match(/\s*[\"\')]/,false)){X.tokenize=q(")")}else{X.tokenize=null}return a(null,"(")}function i(X,Y,W){this.type=X;this.indent=Y;this.prev=W}function v(X,W,Y,Z){X.context=new i(Y,W.indentation()+(Z===false?0:j),X.context);return Y}function g(W){if(W.context.prev){W.context=W.context.prev}return W.context.type}function p(Y,W,X){return w[X.context.type](Y,W,X)}function e(Z,X,Y,W){for(var aa=W||1;aa>0;aa--){Y.context=Y.context.prev}return p(Z,X,Y)}function u(W){var X=W.current().toLowerCase();if(h.hasOwnProperty(X)){m="atom"}else{if(l.hasOwnProperty(X)){m="keyword"}else{m="variable"}}}var w={};w.top=function(Y,W,X){if(Y=="{"){return v(X,W,"block")}else{if(Y=="}"&&X.context.prev){return g(X)}else{if(f&&/@component/.test(Y)){return v(X,W,"atComponentBlock")}else{if(/^@(-moz-)?document$/.test(Y)){return v(X,W,"documentTypes")}else{if(/^@(media|supports|(-moz-)?document|import)$/.test(Y)){return v(X,W,"atBlock")}else{if(/^@(font-face|counter-style)/.test(Y)){X.stateArg=Y;return"restricted_atBlock_before"}else{if(/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(Y)){return"keyframes"}else{if(Y&&Y.charAt(0)=="@"){return v(X,W,"at")}else{if(Y=="hash"){m="builtin"}else{if(Y=="word"){m="tag"}else{if(Y=="variable-definition"){return"maybeprop"}else{if(Y=="interpolation"){return v(X,W,"interpolation")}else{if(Y==":"){return"pseudo"}else{if(n&&Y=="("){return v(X,W,"parens")}}}}}}}}}}}}}}return X.context.type};w.block=function(Z,W,Y){if(Z=="word"){var X=W.current().toLowerCase();if(t.hasOwnProperty(X)){m="property";return"maybeprop"}else{if(ae.hasOwnProperty(X)){m="string-2";return"maybeprop"}else{if(n){m=W.match(/^\s*:(?:\s|$)/,false)?"property":"tag";return"block"}else{m+=" error";return"maybeprop"}}}}else{if(Z=="meta"){return"block"}else{if(!n&&(Z=="hash"||Z=="qualifier")){m="error";return"block"}else{return w.top(Z,W,Y)}}}};w.maybeprop=function(Y,W,X){if(Y==":"){return v(X,W,"prop")}return p(Y,W,X)};w.prop=function(Y,W,X){if(Y==";"){return g(X)}if(Y=="{"&&n){return v(X,W,"propBlock")}if(Y=="}"||Y=="{"){return e(Y,W,X)}if(Y=="("){return v(X,W,"parens")}if(Y=="hash"&&!/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(W.current())){m+=" error"}else{if(Y=="word"){u(W)}else{if(Y=="interpolation"){return v(X,W,"interpolation")}}}return"prop"};w.propBlock=function(X,Y,W){if(X=="}"){return g(W)}if(X=="word"){m="property";return"maybeprop"}return W.context.type};w.parens=function(Y,W,X){if(Y=="{"||Y=="}"){return e(Y,W,X)}if(Y==")"){return g(X)}if(Y=="("){return v(X,W,"parens")}if(Y=="interpolation"){return v(X,W,"interpolation")}if(Y=="word"){u(W)}return"parens"};w.pseudo=function(Y,W,X){if(Y=="word"){m="variable-3";return X.context.type}return p(Y,W,X)};w.documentTypes=function(Y,W,X){if(Y=="word"&&s.hasOwnProperty(W.current())){m="tag";return X.context.type}else{return w.atBlock(Y,W,X)}};w.atBlock=function(Z,W,Y){if(Z=="("){return v(Y,W,"atBlock_parens")}if(Z=="}"||Z==";"){return e(Z,W,Y)}if(Z=="{"){return g(Y)&&v(Y,W,n?"block":"top")}if(Z=="interpolation"){return v(Y,W,"interpolation")}if(Z=="word"){var X=W.current().toLowerCase();if(X=="only"||X=="not"||X=="and"||X=="or"){m="keyword"}else{if(c.hasOwnProperty(X)){m="attribute"}else{if(o.hasOwnProperty(X)){m="property"}else{if(k.hasOwnProperty(X)){m="keyword"}else{if(t.hasOwnProperty(X)){m="property"}else{if(ae.hasOwnProperty(X)){m="string-2"}else{if(h.hasOwnProperty(X)){m="atom"}else{if(l.hasOwnProperty(X)){m="keyword"}else{m="error"}}}}}}}}}return Y.context.type};w.atComponentBlock=function(Y,W,X){if(Y=="}"){return e(Y,W,X)}if(Y=="{"){return g(X)&&v(X,W,n?"block":"top",false)}if(Y=="word"){m="error"}return X.context.type};w.atBlock_parens=function(Y,W,X){if(Y==")"){return g(X)}if(Y=="{"||Y=="}"){return e(Y,W,X,2)}return w.atBlock(Y,W,X)};w.restricted_atBlock_before=function(Y,W,X){if(Y=="{"){return v(X,W,"restricted_atBlock")}if(Y=="word"&&X.stateArg=="@counter-style"){m="variable";return"restricted_atBlock_before"}return p(Y,W,X)};w.restricted_atBlock=function(Y,W,X){if(Y=="}"){X.stateArg=null;return g(X)}if(Y=="word"){if((X.stateArg=="@font-face"&&!x.hasOwnProperty(W.current().toLowerCase()))||(X.stateArg=="@counter-style"&&!d.hasOwnProperty(W.current().toLowerCase()))){m="error"}else{m="property"}return"maybeprop"}return"restricted_atBlock"};w.keyframes=function(Y,W,X){if(Y=="word"){m="variable";return"keyframes"}if(Y=="{"){return v(X,W,"top")}return p(Y,W,X)};w.at=function(Y,W,X){if(Y==";"){return g(X)}if(Y=="{"||Y=="}"){return e(Y,W,X)}if(Y=="word"){m="tag"}else{if(Y=="hash"){m="builtin"}}return"at"};w.interpolation=function(Y,W,X){if(Y=="}"){return g(X)}if(Y=="{"||Y==";"){return e(Y,W,X)}if(Y=="word"){m="variable"}else{if(Y!="variable"&&Y!="("&&Y!=")"){m="error"}}return"interpolation"};return{startState:function(W){return{tokenize:null,state:ac?"block":"top",stateArg:null,context:new i(ac?"block":"top",W||0,null)}},token:function(W,X){if(!X.tokenize&&W.eatSpace()){return null}var Y=(X.tokenize||ag)(W,X);if(Y&&typeof Y=="object"){ad=Y[1];Y=Y[0]}m=Y;X.state=w[X.state](ad,W,X);return m},indent:function(W,Y){var Z=W.context,X=Y&&Y.charAt(0);var aa=Z.indent;if(Z.type=="prop"&&(X=="}"||X==")")){Z=Z.prev}if(Z.prev){if(X=="}"&&(Z.type=="block"||Z.type=="top"||Z.type=="interpolation"||Z.type=="restricted_atBlock")){Z=Z.prev;aa=Z.indent}else{if(X==")"&&(Z.type=="parens"||Z.type=="atBlock_parens")||X=="{"&&(Z.type=="at"||Z.type=="atBlock")){aa=Math.max(0,Z.indent-j);Z=Z.prev}}}return aa},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",fold:"brace"}});function P(a){var b={};for(var c=0;c-1){e.backUp(a.length-b)}else{if(a.match(/<\/?$/)){e.backUp(a.length);if(!e.match(d,false)){e.match(a)}}}return c}var n={};function p(b){var a=n[b];if(a){return a}return n[b]=new RegExp("\\s+"+b+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")}function r(a,c){var b=a.match(p(c));return b?/^\s*(.*?)\s*$/.exec(b[2])[1]:""}function j(a,b){return new RegExp((b?"^":"")+"","i")}function k(e,f){for(var d in e){var c=f[d]||(f[d]=[]);var a=e[d];for(var b=a.length-1;b>=0;b--){c.unshift(a[b])}}}function q(b,a){for(var c=0;c=0;a--){d.script.unshift(["type",h[a].matches,h[a].mode])}}function b(C,i){var J=e.token(C,i.htmlState),B=/\btag\b/.test(J),H;if(B&&!/[<>\s\/]/.test(C.current())&&(H=i.htmlState.tagName&&i.htmlState.tagName.toLowerCase())&&d.hasOwnProperty(H)){i.inTag=H+" "}else{if(i.inTag&&B&&/>$/.test(C.current())){var I=/^([\S]+) (.*)/.exec(i.inTag);i.inTag=null;var D=C.current()==">"&&q(d[I[1]],I[2]);var E=o.getMode(c,D);var F=j(I[1],true),G=j(I[1],false);i.token=function(s,t){if(s.match(F,false)){t.token=b;t.localState=t.localMode=null;return null}return m(s,G,t.localMode.token(s,t.localState))};i.localMode=E;i.localState=o.startState(E,e.indent(i.htmlState,""))}else{if(i.inTag){i.inTag+=C.current();if(C.eol()){i.inTag+=" "}}}}return J}return{startState:function(){var i=o.startState(e);return{token:b,inTag:null,localMode:null,localState:null,htmlState:i}},copyState:function(i){var t;if(i.localState){t=o.copyState(i.localMode,i.localState)}return{token:i.token,inTag:i.inTag,localMode:i.localMode,localState:t,htmlState:o.copyState(e,i.htmlState)}},token:function(i,t){return t.token(i,t)},indent:function(i,t){if(!i.localMode||/^\s*<\//.test(t)){return e.indent(i.htmlState,t)}else{if(i.localMode.indent){return i.localMode.indent(i.localState,t)}else{return o.Pass}}},innerMode:function(i){return{state:i.localState||i.htmlState,mode:i.localMode||e}}}},"xml","javascript","css");o.defineMIME("text/html","htmlmixed")});(function(b){if(typeof exports=="object"&&typeof module=="object"){b(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],b)}else{b(CodeMirror)}}})(function(d){function c(a,b,f){return/^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(b.lastType)||(b.lastType=="quasi"&&/\{\s*$/.test(a.string.slice(0,a.pos-(f||0))))}d.defineMode("javascript",function(bU,bK){var a0=bU.indentUnit;var bE=bK.statementIndent;var b8=bK.jsonld;var bG=bK.json||b8;var a6=bK.typescript;var cd=bK.wordCharacters||/[\w$\xa1-\uffff]/;var bt=function(){function f(o){return{type:o,style:"keyword"}}var k=f("keyword a"),m=f("keyword b"),n=f("keyword c");var l=f("operator"),h={type:"atom",style:"atom"};var j={"if":f("if"),"while":k,"with":k,"else":m,"do":m,"try":m,"finally":m,"return":n,"break":n,"continue":n,"new":f("new"),"delete":n,"throw":n,"debugger":n,"var":f("var"),"const":f("var"),let:f("var"),"function":f("function"),"catch":f("catch"),"for":f("for"),"switch":f("switch"),"case":f("case"),"default":f("default"),"in":l,"typeof":l,"instanceof":l,"true":h,"false":h,"null":h,"undefined":h,"NaN":h,"Infinity":h,"this":f("this"),"class":f("class"),"super":f("atom"),yield:n,"export":f("export"),"import":f("import"),"extends":n,await:n,async:f("async")};if(a6){var e={type:"variable",style:"variable-3"};var i={"interface":f("class"),"implements":n,namespace:n,module:f("module"),"enum":f("module"),type:f("type"),"public":f("modifier"),"private":f("modifier"),"protected":f("modifier"),"abstract":f("modifier"),as:l,string:e,number:e,"boolean":e,any:e};for(var g in i){j[g]=i[g]}}return j}();var bh=/[+\-*&%=<>!?|~^]/;var bv=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;function bu(e){var g=false,h,f=false;while((h=e.next())!=null){if(!g){if(h=="/"&&!f){return}if(h=="["){f=true}else{if(f&&h=="]"){f=false}}}g=!g&&h=="\\"}}var bd,bs;function bl(e,f,g){bd=e;bs=g;return f}function bb(i,f){var h=i.next();if(h=='"'||h=="'"){f.tokenize=be(h);return f.tokenize(i,f)}else{if(h=="."&&i.match(/^\d+(?:[eE][+\-]?\d+)?/)){return bl("number","number")}else{if(h=="."&&i.match("..")){return bl("spread","meta")}else{if(/[\[\]{}\(\),;\:\.]/.test(h)){return bl(h)}else{if(h=="="&&i.eat(">")){return bl("=>","operator")}else{if(h=="0"&&i.eat(/x/i)){i.eatWhile(/[\da-f]/i);return bl("number","number")}else{if(h=="0"&&i.eat(/o/i)){i.eatWhile(/[0-7]/i);return bl("number","number")}else{if(h=="0"&&i.eat(/b/i)){i.eatWhile(/[01]/i);return bl("number","number")}else{if(/\d/.test(h)){i.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);return bl("number","number")}else{if(h=="/"){if(i.eat("*")){f.tokenize=b9;return b9(i,f)}else{if(i.eat("/")){i.skipToEnd();return bl("comment","comment")}else{if(c(i,f,1)){bu(i);i.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);return bl("regexp","string-2")}else{i.eatWhile(bh);return bl("operator","operator",i.current())}}}}else{if(h=="`"){f.tokenize=b7;return b7(i,f)}else{if(h=="#"){i.skipToEnd();return bl("error","error")}else{if(bh.test(h)){i.eatWhile(bh);return bl("operator","operator",i.current())}else{if(cd.test(h)){i.eatWhile(cd);var e=i.current(),g=bt.propertyIsEnumerable(e)&&bt[e];return(g&&f.lastType!=".")?bl(g.type,g.style,e):bl("variable","variable",e)}}}}}}}}}}}}}}}function be(e){return function(i,g){var f=false,h;if(b8&&i.peek()=="@"&&i.match(bv)){g.tokenize=bb;return bl("jsonld-keyword","meta")}while((h=i.next())!=null){if(h==e&&!f){break}f=!f&&h=="\\"}if(!f){g.tokenize=bb}return bl("string","string")}}function b9(e,f){var h=false,g;while(g=e.next()){if(g=="/"&&h){f.tokenize=bb;break}h=(g=="*")}return bl("comment","comment")}function b7(e,g){var f=false,h;while((h=e.next())!=null){if(!f&&(h=="`"||h=="$"&&e.eat("{"))){g.tokenize=bb;break}f=!f&&h=="\\"}return bl("quasi","string-2",e.current())}var aZ="([{}])";function cb(e,k){if(k.fatArrowAt){k.fatArrowAt=null}var f=e.string.indexOf("=>",e.start);if(f<0){return}if(a6){var i=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(e.string.slice(e.start,f));if(i){f=i.index}}var h=0,j=false;for(var g=f-1;g>=0;--g){var m=e.string.charAt(g);var l=aZ.indexOf(m);if(l>=0&&l<3){if(!h){++g;break}if(--h==0){if(m=="("){j=true}break}}else{if(l>=3&&l<6){++h}else{if(cd.test(m)){j=true}else{if(/["'\/]/.test(m)){return}else{if(j&&!h){++g;break}}}}}}if(j&&!h){k.fatArrowAt=g}}var ba={atom:true,number:true,variable:true,string:true,regexp:true,"this":true,"jsonld-keyword":true};function bn(i,g,h,j,f,e){this.indented=i;this.column=g;this.type=h;this.prev=f;this.info=e;if(j!=null){this.align=j}}function aS(e,f){for(var g=e.localVars;g;g=g.next){if(g.name==f){return true}}for(var h=e.context;h;h=h.prev){for(var g=h.vars;g;g=g.next){if(g.name==f){return true}}}}function a7(k,g,h,e,j){var i=k.cc;bx.state=k;bx.stream=j;bx.marked=null,bx.cc=i;bx.style=g;if(!k.lexical.hasOwnProperty("align")){k.lexical.align=true}while(true){var f=i.length?i.pop():bG?bD:b2;if(f(h,e)){while(i.length&&i[i.length-1].lex){i.pop()()}if(bx.marked){return bx.marked}if(h=="variable"&&aS(k,e)){return"variable-2"}return g}}}var bx={state:null,column:null,marked:null,cc:null};function bT(){for(var e=arguments.length-1;e>=0;e--){bx.cc.push(arguments[e])}}function bO(){bT.apply(null,arguments);return true}function cc(f){function g(i){for(var h=i;h;h=h.next){if(h.name==f){return true}}return false}var e=bx.state;bx.marked="def";if(e.context){if(g(e.localVars)){return}e.localVars={name:f,next:e.localVars}}else{if(g(e.globalVars)){return}if(bK.globalVars){e.globalVars={name:f,next:e.globalVars}}}}var aU={name:"this",next:{name:"arguments"}};function b(){bx.state.context={prev:bx.state.context,vars:bx.state.localVars};bx.state.localVars=aU}function a(){bx.state.localVars=bx.state.context.vars;bx.state.context=bx.state.context.prev}function b4(f,e){var g=function(){var i=bx.state,h=i.indented;if(i.lexical.type=="stat"){h=i.lexical.indented}else{for(var j=i.lexical;j&&j.type==")"&&j.align;j=j.prev){h=j.indented}}i.lexical=new bn(h,bx.stream.column(),f,null,i.lexical,e)};g.lex=true;return g}function a5(){var e=bx.state;if(e.lexical.prev){if(e.lexical.type==")"){e.indented=e.lexical.indented}e.lexical=e.lexical.prev}}a5.lex=true;function aT(f){function e(g){if(g==f){return bO()}else{if(f==";"){return bT()}else{return bO(e)}}}return e}function b2(f,e){if(f=="var"){return bO(b4("vardef",e.length),a9,aT(";"),a5)}if(f=="keyword a"){return bO(b4("form"),bB,b2,a5)}if(f=="keyword b"){return bO(b4("form"),b2,a5)}if(f=="{"){return bO(b4("}"),bH,a5)}if(f==";"){return bO()}if(f=="if"){if(bx.state.lexical.info=="else"&&bx.state.cc[bx.state.cc.length-1]==a5){bx.state.cc.pop()()}return bO(b4("form"),bB,b2,a5,a8)}if(f=="function"){return bO(bk)}if(f=="for"){return bO(b4("form"),aQ,b2,a5)}if(f=="variable"){return bO(b4("stat"),b1)}if(f=="switch"){return bO(b4("form"),bB,b4("}","switch"),aT("{"),bH,a5,a5)}if(f=="case"){return bO(bD,aT(":"))}if(f=="default"){return bO(aT(":"))}if(f=="catch"){return bO(b4("form"),b,aT("("),bP,aT(")"),b2,a5,a)}if(f=="class"){return bO(b4("form"),bY,a5)}if(f=="export"){return bO(b4("stat"),b3,a5)}if(f=="import"){return bO(b4("stat"),bN,a5)}if(f=="module"){return bO(b4("form"),a4,b4("}"),aT("{"),bH,a5,a5)}if(f=="type"){return bO(a3,aT("operator"),a3,aT(";"))}if(f=="async"){return bO(b2)}return bT(b4("stat"),bD,aT(";"),a5)}function bD(e){return bV(e,false)}function b5(e){return bV(e,true)}function bB(e){if(e!="("){return bT()}return bO(b4(")"),bD,aT(")"),a5)}function bV(g,e){if(bx.state.fatArrowAt==bx.stream.start){var h=e?bj:bX;if(g=="("){return bO(b,b4(")"),ce(a4,")"),a5,aT("=>"),h,a)}else{if(g=="variable"){return bT(b,a4,aT("=>"),h,a)}}}var f=e?a2:bS;if(ba.hasOwnProperty(g)){return bO(f)}if(g=="function"){return bO(bk,f)}if(g=="class"){return bO(b4("form"),bp,a5)}if(g=="keyword c"||g=="async"){return bO(e?bJ:bL)}if(g=="("){return bO(b4(")"),bL,aT(")"),a5,f)}if(g=="operator"||g=="spread"){return bO(e?b5:bD)}if(g=="["){return bO(b4("]"),aY,a5,f)}if(g=="{"){return ca(aR,"}",null,f)}if(g=="quasi"){return bT(bg,f)}if(g=="new"){return bO(bq(e))}return bO()}function bL(e){if(e.match(/[;\}\)\],]/)){return bT()}return bT(bD)}function bJ(e){if(e.match(/[;\}\)\],]/)){return bT()}return bT(b5)}function bS(f,e){if(f==","){return bO(bD)}return a2(f,e,false)}function a2(h,f,i){var g=i==false?bS:a2;var e=i==false?bD:b5;if(h=="=>"){return bO(b,i?bj:bX,a)}if(h=="operator"){if(/\+\+|--/.test(f)){return bO(g)}if(f=="?"){return bO(bD,aT(":"),e)}return bO(e)}if(h=="quasi"){return bT(bg,g)}if(h==";"){return}if(h=="("){return ca(b5,")","call",g)}if(h=="."){return bO(bI,g)}if(h=="["){return bO(b4("]"),bL,aT("]"),a5,g)}}function bg(f,e){if(f!="quasi"){return bT()}if(e.slice(e.length-2)!="${"){return bO(bg)}return bO(bD,aV)}function aV(e){if(e=="}"){bx.marked="string-2";bx.state.tokenize=b7;return bO(bg)}}function bX(e){cb(bx.stream,bx.state);return bT(e=="{"?b2:bD)}function bj(e){cb(bx.stream,bx.state);return bT(e=="{"?b2:b5)}function bq(e){return function(f){if(f=="."){return bO(e?aX:bZ)}else{return bT(e?b5:bD)}}}function bZ(f,e){if(e=="target"){bx.marked="keyword";return bO(bS)}}function aX(f,e){if(e=="target"){bx.marked="keyword";return bO(a2)}}function b1(e){if(e==":"){return bO(a5,b2)}return bT(bS,aT(";"),a5)}function bI(e){if(e=="variable"){bx.marked="property";return bO()}}function aR(f,e){if(f=="async"){bx.marked="property";return bO(aR)}else{if(f=="variable"||bx.style=="keyword"){bx.marked="property";if(e=="get"||e=="set"){return bO(bo)}return bO(bm)}else{if(f=="number"||f=="string"){bx.marked=b8?"property":(bx.style+" property");return bO(bm)}else{if(f=="jsonld-keyword"){return bO(bm)}else{if(f=="modifier"){return bO(aR)}else{if(f=="["){return bO(bD,aT("]"),bm)}else{if(f=="spread"){return bO(bD)}else{if(f==":"){return bT(bm)}}}}}}}}}function bo(e){if(e!="variable"){return bT(bm)}bx.marked="property";return bO(bk)}function bm(e){if(e==":"){return bO(b5)}if(e=="("){return bT(bk)}}function ce(e,g){function f(j,i){if(j==","){var h=bx.state.lexical;if(h.info=="call"){h.pos=(h.pos||0)+1}return bO(function(l,k){if(l==g||k==g){return bT()}return bT(e)},f)}if(j==g||i==g){return bO()}return bO(aT(g))}return function(h,i){if(h==g||i==g){return bO()}return bT(e,f)}}function ca(e,h,f){for(var g=3;g"){return bO(a3)}}function bF(e){if(e=="variable"||bx.style=="keyword"){bx.marked="property";return bO(bF)}else{if(e==":"){return bO(a3)}}}function bR(e){if(e=="variable"){return bO(bR)}else{if(e==":"){return bO(a3)}}}function br(f,e){if(e=="<"){return bO(ce(a3,">"),br)}if(f=="["){return bO(aT("]"),br)}}function a9(){return bT(a4,bc,bQ,bW)}function a4(f,e){if(f=="modifier"){return bO(a4)}if(f=="variable"){cc(e);return bO()}if(f=="spread"){return bO(a4)}if(f=="["){return ca(a4,"]")}if(f=="{"){return ca(b6,"}")}}function b6(f,e){if(f=="variable"&&!bx.stream.match(/^\s*:/,false)){cc(e);return bO(bQ)}if(f=="variable"){bx.marked="property"}if(f=="spread"){return bO(a4)}if(f=="}"){return bT()}return bO(aT(":"),a4,bQ)}function bQ(f,e){if(e=="="){return bO(b5)}}function bW(e){if(e==","){return bO(a9)}}function a8(f,e){if(f=="keyword b"&&e=="else"){return bO(b4("form","else"),b2,a5)}}function aQ(e){if(e=="("){return bO(b4(")"),bz,aT(")"),a5)}}function bz(e){if(e=="var"){return bO(a9,aT(";"),bA)}if(e==";"){return bO(bA)}if(e=="variable"){return bO(aP)}return bT(bD,aT(";"),bA)}function aP(f,e){if(e=="in"||e=="of"){bx.marked="keyword";return bO(bD)}return bO(bS,bA)}function bA(f,e){if(f==";"){return bO(bC)}if(e=="in"||e=="of"){bx.marked="keyword";return bO(bD)}return bT(bD,aT(";"),bC)}function bC(e){if(e!=")"){bO(bD)}}function bk(f,e){if(e=="*"){bx.marked="keyword";return bO(bk)}if(f=="variable"){cc(e);return bO(bk)}if(f=="("){return bO(b,b4(")"),ce(bP,")"),a5,bc,b2,a)}}function bP(e){if(e=="spread"){return bO(bP)}return bT(a4,bc,bQ)}function bp(f,e){if(f=="variable"){return bY(f,e)}return bi(f,e)}function bY(f,e){if(f=="variable"){cc(e);return bO(bi)}}function bi(f,e){if(e=="extends"||e=="implements"){return bO(a6?a3:bD,bi)}if(f=="{"){return bO(b4("}"),aW,a5)}}function aW(f,e){if(f=="variable"||bx.style=="keyword"){if((e=="static"||e=="get"||e=="set"||(a6&&(e=="public"||e=="private"||e=="protected"||e=="readonly"||e=="abstract")))&&bx.stream.match(/^\s+[\w$\xa1-\uffff]/,false)){bx.marked="keyword";return bO(aW)}bx.marked="property";return bO(a6?b0:bk,aW)}if(e=="*"){bx.marked="keyword";return bO(aW)}if(f==";"){return bO(aW)}if(f=="}"){return bO()}}function b0(f,e){if(e=="?"){return bO(b0)}if(f==":"){return bO(a3,bQ)}return bT(bk)}function b3(f,e){if(e=="*"){bx.marked="keyword";return bO(bM,aT(";"))}if(e=="default"){bx.marked="keyword";return bO(bD,aT(";"))}return bT(b2)}function bN(e){if(e=="string"){return bO()}return bT(bw,bM)}function bw(f,e){if(f=="{"){return ca(bw,"}")}if(f=="variable"){cc(e)}if(e=="*"){bx.marked="keyword"}return bO(a1)}function a1(f,e){if(e=="as"){bx.marked="keyword";return bO(bw)}}function bM(f,e){if(e=="from"){bx.marked="keyword";return bO(bD)}}function aY(e){if(e=="]"){return bO()}return bT(ce(b5,"]"))}function by(e,f){return e.lastType=="operator"||e.lastType==","||bh.test(f.charAt(0))||/[,.]/.test(f.charAt(0))}return{startState:function(e){var f={tokenize:bb,lastType:"sof",cc:[],lexical:new bn((e||0)-a0,0,"block",false),localVars:bK.localVars,context:bK.localVars&&{vars:bK.localVars},indented:e||0};if(bK.globalVars&&typeof bK.globalVars=="object"){f.globalVars=bK.globalVars}return f},token:function(e,f){if(e.sol()){if(!f.lexical.hasOwnProperty("align")){f.lexical.align=false}f.indented=e.indentation();cb(e,f)}if(f.tokenize!=b9&&e.eatSpace()){return null}var g=f.tokenize(e,f);if(bd=="comment"){return g}f.lastType=bd=="operator"&&(bs=="++"||bs=="--")?"incdec":bd;return a7(f,g,bd,bs,e)},indent:function(m,j){if(m.tokenize==b9){return d.Pass}if(m.tokenize!=bb){return 0}var f=j&&j.charAt(0),e=m.lexical,g;if(!/^\s*else\b/.test(j)){for(var k=m.cc.length-1;k>=0;--k){var i=m.cc[k];if(i==a5){e=e.prev}else{if(i!=a8){break}}}}while((e.type=="stat"||e.type=="form")&&(f=="}"||((g=m.cc[m.cc.length-1])&&(g==bS||g==a2)&&!/^[,\.=+\-*:?[\(]/.test(j)))){e=e.prev}if(bE&&e.type==")"&&e.prev.type=="stat"){e=e.prev}var h=e.type,l=f==h;if(h=="vardef"){return e.indented+(m.lastType=="operator"||m.lastType==","?e.info+1:0)}else{if(h=="form"&&f=="{"){return e.indented}else{if(h=="form"){return e.indented+a0}else{if(h=="stat"){return e.indented+(by(m,j)?bE||a0:0)}else{if(e.info=="switch"&&!l&&bK.doubleIndentSwitch!=false){return e.indented+(/^(?:case|default)\b/.test(j)?a0:2*a0)}else{if(e.align){return e.column+(l?0:1)}else{return e.indented+(l?0:a0)}}}}}}},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:bG?null:"/*",blockCommentEnd:bG?null:"*/",lineComment:bG?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:bG?"json":"javascript",jsonldMode:b8,jsonMode:bG,expressionAllowed:c,skipExpression:function(f){var e=f.cc[f.cc.length-1];if(e==bD||e==b5){f.cc.pop()}}}});d.registerHelper("wordChars","javascript",/[\w$]/);d.defineMIME("text/javascript","javascript");d.defineMIME("text/ecmascript","javascript");d.defineMIME("application/javascript","javascript");d.defineMIME("application/x-javascript","javascript");d.defineMIME("application/ecmascript","javascript");d.defineMIME("application/json",{name:"javascript",json:true});d.defineMIME("application/x-json",{name:"javascript",json:true});d.defineMIME("application/ld+json",{name:"javascript",jsonld:true});d.defineMIME("text/typescript",{name:"javascript",typescript:true});d.defineMIME("application/typescript",{name:"javascript",typescript:true})});(function(b){if(typeof exports=="object"&&typeof module=="object"){b(require("../../lib/codemirror"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror"],b)}else{b(CodeMirror)}}})(function(b){b.defineMode("nginx",function(w){function p(c){var e={},d=c.split(" ");for(var f=0;f*\/]/.test(f)){return r(null,"select-op")}else{if(/[;{}:\[\]]/.test(f)){return r(null,f)}else{c.eatWhile(/[\w\\\-]/);return r("variable","variable")}}}}}}}}}}}}function a(c,d){var f=false,e;while((e=c.next())!=null){if(f&&e=="/"){d.tokenize=v;break}f=(e=="*")}return r("comment","comment")}function x(c,d){var e=0,f;while((f=c.next())!=null){if(e>=2&&f==">"){d.tokenize=v;break}e=(f=="-")?e+1:0}return r("comment","comment")}function n(c){return function(d,f){var e=false,g;while((g=d.next())!=null){if(g==c&&!e){break}e=!e&&g=="\\"}if(!e){f.tokenize=v}return r("string","string")}}return{startState:function(c){return{tokenize:v,baseIndent:c||0,stack:[]}},token:function(c,d){if(c.eatSpace()){return null}o=null;var e=d.tokenize(c,d);var f=d.stack[d.stack.length-1];if(o=="hash"&&f=="rule"){e="atom"}else{if(e=="variable"){if(f=="rule"){e="number"}else{if(!f||f=="@media{"){e="tag"}}}}if(f=="rule"&&/^[\{\};]$/.test(o)){d.stack.pop()}if(o=="{"){if(f=="@media"){d.stack[d.stack.length-1]="@media{"}else{d.stack.push("{")}}else{if(o=="}"){d.stack.pop()}else{if(o=="@media"){d.stack.push("@media")}else{if(f=="{"&&o!="comment"){d.stack.push("rule")}}}}return e},indent:function(d,e){var c=d.stack.length;if(/^\}/.test(e)){c-=d.stack[d.stack.length-1]=="rule"?2:1}return d.baseIndent+c*s},electricChars:"}"}});b.defineMIME("text/x-nginx-conf","nginx")});(function(b){if(typeof exports=="object"&&typeof module=="object"){b(require("../../lib/codemirror"),require("../htmlmixed/htmlmixed"),require("../clike/clike"))}else{if(typeof define=="function"&&define.amd){define(["../../lib/codemirror","../htmlmixed/htmlmixed","../clike/clike"],b)}else{b(CodeMirror)}}})(function(o){function m(a){var c={},b=a.split(" ");for(var d=0;d\w/,false)){b.tokenize=n([[["->",null]],[[/[\w]+/,"variable"]]],c,d)}return"variable-2"}var a=false;while(!e.eol()&&(a||d===false||(!e.match("{$",false)&&!e.match(/^(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/,false)))){if(!a&&e.match(c)){b.tokenize=null;b.tokStack.pop();b.tokStack.pop();break}a=e.next()=="\\"&&!a}return"string"}var k="abstract and array as break case catch class clone const continue declare default do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final for foreach function global goto if implements interface instanceof namespace new or private protected public static switch throw trait try use var while xor die echo empty exit eval include include_once isset list require require_once return print unset __halt_compiler self static parent yield insteadof finally";var j="true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__";var r="func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents file_put_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists array_intersect_key array_combine array_column pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count";o.registerHelper("hintWords","php",[k,j,r].join(" ").split(" "));o.registerHelper("wordChars","php",/[\w$]/);var l={name:"clike",helperType:"php",keywords:m(k),blockKeywords:m("catch do else elseif for foreach if switch try while finally"),defKeywords:m("class function interface namespace trait"),atoms:m(j),builtin:m(r),multiLineStrings:true,hooks:{"$":function(a){a.eatWhile(/[\w\$_]/);return"variable-2"},"<":function(e,b){var c;if(c=e.match(/<<\s*/)){var d=e.eat(/['"]/);e.eatWhile(/[\w\.]/);var a=e.current().slice(c[0].length+(d?2:1));if(d){e.eat(d)}if(a){(b.tokStack||(b.tokStack=[])).push(a,0);b.tokenize=q(a,d!="'");return"string"}}return false},"#":function(a){while(!a.eol()&&!a.match("?>",false)){a.next()}return"comment"},"/":function(a){if(a.eat("/")){while(!a.eol()&&!a.match("?>",false)){a.next()}return"comment"}return false},'"':function(b,a){(a.tokStack||(a.tokStack=[])).push('"',0);a.tokenize=q('"');return"string"},"{":function(b,a){if(a.tokStack&&a.tokStack.length){a.tokStack[a.tokStack.length-1]++}return false},"}":function(b,a){if(a.tokStack&&a.tokStack.length>0&&!--a.tokStack[a.tokStack.length-1]){a.tokenize=q(a.tokStack[a.tokStack.length-2])}return false}}};o.defineMode("php",function(b,a){var e=o.getMode(b,"text/html");var d=o.getMode(b,l);function c(f,h){var i=h.curMode==d;if(f.sol()&&h.pending&&h.pending!='"'&&h.pending!="'"){h.pending=null}if(!i){if(f.match(/^<\?\w*/)){h.curMode=d;if(!h.php){h.php=o.startState(d,e.indent(h.html,""))}h.curState=h.php;return"meta"}if(h.pending=='"'||h.pending=="'"){while(!f.eol()&&f.next()!=h.pending){}var v="string"}else{if(h.pending&&f.pos/.test(g)){h.pending=x[0]}else{h.pending={end:f.pos,style:v}}f.backUp(g.length-w)}return v}else{if(i&&h.php.tokenize==null&&f.match("?>")){h.curMode=e;h.curState=h.html;if(!h.php.context.prev){h.php=null}return"meta"}else{return d.token(f,h.curState)}}}return{startState:function(){var g=o.startState(e);var f=a.startOpen?o.startState(d):null;return{html:g,php:f,curMode:a.startOpen?d:e,curState:a.startOpen?f:g,pending:null}},copyState:function(h){var u=h.html,i=o.copyState(e,u),f=h.php,v=f&&o.copyState(d,f),g;if(h.curMode==e){g=i}else{g=v}return{html:i,php:v,curMode:h.curMode,curState:g,pending:h.pending}},token:c,indent:function(f,g){if((f.curMode!=d&&/^\s*<\//.test(g))||(f.curMode==d&&/^\?>/.test(g))){return e.indent(f.html,g)}return f.curMode.indent(f.curState,g)},blockCommentStart:"/*",blockCommentEnd:"*/",lineComment:"//",innerMode:function(f){return{state:f.curState,mode:f.curMode}}}},"htmlmixed","clike");o.defineMIME("application/x-httpd-php","php");o.defineMIME("application/x-httpd-php-open",{name:"php",startOpen:true});o.defineMIME("text/x-php",l)}); \ No newline at end of file diff --git a/web/static/css/ensite.css b/web/static/css/ensite.css new file mode 100755 index 000000000..531a660fe --- /dev/null +++ b/web/static/css/ensite.css @@ -0,0 +1,3703 @@ +body { + line-height: 1.4; + color: #333; + font-family: "微软雅黑", Arial, Helvetica, sans-serif; + font-size: 12px +} + +input, +textarea, +select { + font-size: 100%; + font-family: inherit +} + +body, +h1, +h2, +h3, +h4, +h5, +h6, +p, +ul, +ol, +form { + margin: 0 +} + +h4, +h5, +h6 { + font-size: 1em +} + +ul, +ol { + padding-left: 0; + list-style-type: none +} + +fieldset, +img { + border: 0 +} + +a { + color: #333; + border: 0; + text-decoration: none +} + +a:hover { + text-decoration: none +} + +a:link { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-touch-callout: none +} + +:-moz-placeholder { + color: #999 +} + +::-moz-placeholder { + color: #999 +} + +input:-ms-input-placeholder { + color: #999 +} + +input::-webkit-input-placeholder { + color: #999 +} + +body, +html { + height: 100% +} + +.f12 { + font-size: 12px +} + +.f14 { + font-size: 14px +} + +.f15 { + font-size: 15px +} + +.f16 { + font-size: 16px +} + +.f18 { + font-size: 18px +} + +.f20 { + font-size: 20px +} + +.cw { + color: white +} + +.c0 { + color: #000 +} + +.c3 { + color: #333 +} + +.c4 { + color: #444 +} + +.c5 { + color: #555 +} + +.c6 { + color: #666 +} + +.c7 { + color: #777 +} + +.c8 { + color: #888 +} + +.c9 { + color: #999 +} + +.cbt { + color: #20a53a +} + +.bgw { + background-color: white +} + +.bge6 { + background-color: #e6e9ee +} + +.plr10 { + padding: 0 10px +} + +.plr15 { + padding: 0 15px +} + +.plr20 { + padding: 0 20px +} + +.ptb10 { + padding: 10px 0 +} + +.ptb15 { + padding: 15px 0 +} + +.ptb20 { + padding: 20px 0 +} + +.pd0 { + padding: 0 +} + +.pd15 { + padding: 15px +} + +.pd20 { + padding: 20px +} + +.pr8 { + padding-right: 8px +} + +.pl7 { + padding-left: 7px +} + +.pb15 { + padding-bottom: 15px +} + +.pb55 { + padding-bottom: 55px +} + +.pb70 { + padding-bottom: 70px +} + +.mt10 { + margin-top: 10px +} + +.mtb10 { + margin: 10px 0 +} + +.mtb15 { + margin: 15px 0 +} + +.mtb20 { + margin: 20px 0 +} + +.mlr15 { + margin: 0 15px +} + +.mlr20 { + margin: 0 20px +} + +.mb15 { + margin-bottom: 15px +} + +.mr50 { + margin-right: 50px +} + +.ml5 { + margin-left: 5px +} + +.mr5 { + margin-right: 5px +} + +.mr20 { + margin-right: 20px +} + +.mg10 { + margin: 10px +} + +.va0 { + vertical-align: 0 +} + +.ico-font-ask { + border: 1px solid #999; + border-radius: 8px; + display: inline-block; + font-family: arial; + font-size: 11px; + font-style: normal; + height: 16px; + line-height: 16px; + margin-left: 5px; + text-align: center; + width: 16px; + cursor: help +} + +.btlink { + color: #20a53a +} + +.btlink:hover { + cursor: pointer +} + +.btn-btlink { + border-color: #20a53a; + color: #20a53a; + vertical-align: 1px +} + +.btn-btlink:hover { + border-color: orange; + color: orange +} + +.b-shadown { + transition: border-color .15s ease-in-out 0s, box-shadow .15s ease-in-out 0s +} + +.b-shadown:hover { + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +.important-title { + background-color: #fbfbfb; + border: 1px solid #eee; + border-radius: 3px; + line-height: 28px; + margin-bottom: 15px; + padding: 5px 10px +} + +.bt-input-text { + border: 1px solid #ccc; + height: 30px; + line-height: 30px; + padding-left: 5px; + border-radius: 3px; + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s +} + +.bt-input-text:focus, +.bt-input-text:active { + border: #66afe9 1px solid; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +.bt-submit { + background-color: #20a53a; + border-radius: 3px; + width: 140px; + height: 34px; + line-height: 34px; + text-align: center; + color: #fff; + cursor: pointer +} + +.cursor { + cursor: pointer +} + +.help-info-text { + margin-top: 15px +} + +.help-info-text>li { + list-style: inside disc; + line-height: 24px +} + +.relative { + position: relative +} + +.ico-copy { + background: url(../img/ico-copy.png) no-repeat; + height: 14px; + width: 12px; + display: inline-block; + vertical-align: -2px +} + +.zclip embed { + vertical-align: top +} + +.webDelete .options { + padding: 20px 0 +} + +.webDelete .options label { + width: 30%; + float: left; + font-weight: normal +} + +.webDelete .options label input { + float: left; + margin: 0 10px 0 0; + margin-top: 1px +} + +.webDelete .options label span { + float: left; + margin: 0; + line-height: 16px +} + +.webDelete .vcode { + background-color: #f0f0f0; + clear: both; + font-size: 14px; + height: 40px; + line-height: 40px; + margin: 10px 0; + padding-left: 12px; + text-align: left; + color: #444 +} + +.webDelete .vcode .text { + margin-right: 10px; + margin-left: 10px +} + +.webDelete .vcode #vcodeResult { + display: inline; + height: 26px; + line-height: 26px; + margin-left: 10px; + width: 50px; + color: #444 +} + +.btswitch { + display: none +} + +.btswitch+.btswitch-btn { + outline: 0; + display: block; + width: 3em; + height: 1.8em; + position: relative; + cursor: pointer +} + +.btswitch+.btswitch-btn:after, +.btswitch+.btswitch-btn:before { + position: relative; + display: block; + content: ""; + width: 50%; + height: 100% +} + +.btswitch+.btswitch-btn:after { + left: 0 +} + +.btswitch+.btswitch-btn:before { + display: none +} + +.btswitch:checked+.btswitch-btn:after { + left: 50% +} + +.btswitch-ios+.btswitch-btn { + background: #cdcdcd; + border-radius: .9em; + padding: 2px; + -webkit-transition: all .4s ease; + transition: all .4s ease; + border: 1px solid #e8eae9 +} + +.btswitch-ios+.btswitch-btn:after { + border-radius: .9em; + background: #fbfbfb; + -webkit-transition: left .3s cubic-bezier(.175, .885, .32, 1.275), padding .3s ease, margin .3s ease; + transition: left .3s cubic-bezier(.175, .885, .32, 1.275), padding .3s ease, margin .3s ease; + -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1), 0 4px 0 rgba(0, 0, 0, .08); + box-shadow: 0 0 0 1px rgba(0, 0, 0, .1), 0 4px 0 rgba(0, 0, 0, .08) +} + +.btswitch-ios+.btswitch-btn:active { + -webkit-box-shadow: inset 0 0 0 2em #e8eae9; + box-shadow: inset 0 0 0 2em #e8eae9 +} + +.btswitch-ios+.btswitch-btn:active:after { + padding-right: .8em +} + +.btswitch-ios:checked+.btswitch-btn { + background: #20a53a +} + +.btswitch-ios:checked+.btswitch-btn:active { + -webkit-box-shadow: none; + box-shadow: none +} + +.btswitch-ios:checked+.btswitch-btn:active:after { + margin-left: -.8em +} + +.bt-warp { + position: relative; + min-height: 100% +} + +.bt-warp>.container-fluid { + padding: 0 +} + +.main-content { + margin-left: 180px +} + +.sidebar-scroll { + background-color: #3c444d; + width: 180px; + z-index: 100; + height: 100%; + position: fixed; + overflow: hidden +} + +.sidebar-auto { + overflow: auto; + height: 100%; + margin-right: -18px +} + +.mypcip { + display: block; + padding: 0 10px; + position: relative; + transition-duration: 500ms; + transition-property: background; + transition-timing-function: ease; + width: 100%; + cursor: pointer; + margin: 1px 0 +} + +.mypcip:hover { + background: #20a53a; + opacity: 1 +} + +.mypcip span { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAQCAYAAAAS7Y8mAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAGPSURBVDiNldQ9ixRBEAbgZ3Zn1fVOuQU/wK9AMDDS1MvuRxj5CwTBP2CiYmYmGBsYGRsaa2qkkSiCgRd4y3ri3XqzbdDV7Lgs7uwLRc90V1dXv29VVymluxiiRoVkNaoYG7wN+9chpTTBBn7jKA5YhYR+JPQEDxYd6rAPeIwDnOgQuMEIz3F8mUMdC9/wqkPAxb1P0Yv/6ziLX/hTy9c6jXPYXSPwBQywF+Mz7GCCSRGMbqK1Ufwbmb4beI9TuNZFqFWoZCH7Mp0XcaX33y3dUDKfmRdD1bM+BYuoWtaEKeIdyqSvg5+RZSWX6Ri3ZUqmRbxjcqnsx3cyF7Utbpmf4YxcapXcWFNcjiSbOk7Yxhu5BocRaGBeoyXYNII32MTJsJ7cvR/D92aN1xFsFA6HYZ/iiiWjTVyVGyrhBz7jXSQxxq3wPahxJ7JuX30sd9IoshviC77KzdSmag+XYm4QNq0tF+0e7sc1SwPs4xFeLPHvt2ibsfwl24rFl/KLV/jdCho2ZC3a+I6HOB9rR38B10ZjDE49T6kAAAAASUVORK5CYII=") no-repeat 0 center; + display: inline-block; + line-height: 46px; + padding-left: 30px +} + +.btpc-plus { + line-height: 40px; + color: #aaa; + font-family: arial; + font-size: 26px; + cursor: pointer; + padding-left: 80px; + transition-duration: 500ms; + transition-property: background; + transition-timing-function: ease +} + +.btpc-plus:hover { + background-color: #20a53a; + color: #fff +} + +.mypcip .btedit { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFVSURBVDiNndO/ahRRFMfxz+zOav6goKC1S54gBBUECy0lIKTeyrXwFcROyBuENCHdNhaCoCgiWoggGPAJxFgIsbEJJBmTrCfFnBFZNjHrDy4z5849935/Z84tIsJ/6gneFhFR4AHuYHjM4jN4iZWM13ALSyWWcR+rOERrzAYFNvP9Ka7jKrZExKeIWI8IpxivIuJrRFxo5lqJXfzDbwfvcQU3cAnTUOIsfif6zeYDfuEDzuM5LuIatrGBu/hcInCAy1mH2STawyLmknI+56bykFZDsJsUP7CAdhIMUeELXo9YGmbOn4o3Nah6vd5Ot9vd6ff7lfr3dXKMVZnIVVp4MRgMZtLW6LpneJhxpB1lJnfwE49yblRtfPsrLhoLpbqA7fT15jjUERXNQeO6biK11DXYnyCnymdIjI+4jXuJdlJXHqq7cArf620izkXEu5hMj5u7cAQCwbENi6a1WQAAAABJRU5ErkJggg==) no-repeat center center; + width: 16px; + height: 16px; + display: none; + position: absolute; + left: 156px; + top: 14px +} + +.mypcip:hover .btedit { + display: block +} + +.task { + position: absolute; + right: 6px; + top: 14px; + height: 20px; + width: 20px; + line-height: 20px; + background-color: #fc6d26; + z-index: 99; + text-align: center; + border-radius: 6px; + cursor: pointer; + font-family: arial; + font-size: 14px; + font-weight: bold +} + +.softnum { + position: absolute; + left: 154px; + top: 12px; + height: 20px; + width: 20px; + line-height: 20px; + background-color: #fc6d26; + z-index: 99; + text-align: center; + border-radius: 6px; + cursor: pointer; + font-family: arial; + font-size: 14px; + font-weight: bold; + display: none; + color: #fff +} + +.cmdlist li { + border-bottom: 1px solid #dbdbea; + line-height: 48px +} + +.cmdlist li .titlename { + padding-left: 12px; + position: relative +} + +.cmdlist li .titlename:before { + background-color: #20a53a; + border-radius: 3px; + content: ""; + height: 5px; + left: 0; + position: absolute; + top: 6px; + width: 5px +} + +.cmdlist li .cmd { + height: 200px; + background-color: #424251; + overflow: auto; + line-height: 22px; + color: #fff; + padding-left: 10px; + font-family: arial +} + +#remind td { + vertical-align: middle +} + +#remind .titlename { + position: relative +} + +#remind .titlename:before { + background-color: #20a53a; + border-radius: 3px; + content: ""; + height: 5px; + left: -10px; + position: absolute; + top: 6px; + width: 5px +} + +.btn-default[disabled], +.btn-default:active[disabled] { + background-color: #f7f7f7; + color: #bbb; + opacity: 1 +} + +.table-page { + height: 32px +} + +.table-page a { + border: 1px solid #ccc; + float: left; + height: 30px; + margin-left: -1px; + outline: 0 none; + position: relative; + width: 34px; + z-index: 1; + color: #666; + vertical-align: middle; + text-align: center; + line-height: 30px +} + +.table-page a:hover { + border: 1px solid #20a53a; + color: #20a53a; + z-index: 3 +} + +.table-page a.disable { + background-color: #f3f3f3; + cursor: not-allowed; + color: #bbb +} + +.table-page a.disable:hover { + border: 1px solid #ccc +} + +.table-page-select { + float: left; + position: relative +} + +.table-page .table-page-num { + width: 60px +} + +.page-select-ul { + background-color: #fff; + border: 1px solid #ccc; + bottom: 29px; + left: -1px; + line-height: 22px; + max-height: 150px; + min-width: 100%; + overflow: auto; + position: absolute; + top: auto; + width: 60px; + display: none +} + +.page-selected .page-select-ul { + display: block +} + +.page-select-ul li { + padding: 0 12px +} + +.page-select-ul li:hover { + background-color: #f0f0f0 +} + +.sidebar-auto .menu { + background-color: #353d44 +} + +.menu li { + margin-bottom: 1px; + position: relative +} + +.menu li a { + font-size: 15px; + color: #d6d7d9; + display: block; + line-height: 44px; + padding-left: 52px; + background-repeat: no-repeat; + background-size: 16px auto; + background-position: 25px 14px; + border-left: #404040 2px solid +} + +.menu li.current a, +.menu li a:hover { + background-color: #2c3138; + color: #fff; + border-left: #20a53a 2px solid +} + +.menu .menu_home { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKwwAACsMBNCkkqwAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFZSURBVDiNpdO9a1VBEAXw3zOioIUQEdFGkQdWVtr6D1ikERtRppLU6awUsUkh2CgoBNwhoPhsLESxERR7wcI2XSBNMAHRNFmLbB6Xa64RXFjYOXvmzMfOjmqt/mcd2I+QmUuZOcgbDWWQmTN4iFms4wGu4jC+RMT7wQwy8xBe4FtE3MBXPMdqE5sfLCEzj+Il3kXEE4iIJTzFFbzB9z0FMvMYJphExLPuXUQs4y1eYfmPHmTmbHN+FBGv92zMDm+ulXAzItZHtVaZeRH3sBgRn4ecOyKXcRt3dkuYwyJOZeaJfZyP42TjX1Nrne5SyqSUMu5ho559tpQy2bUP9gL8wHYnzbtYzczTuB8RHzs8/H0Sz+ETbuEDxg3f7pL6AhW/2vknNiNiCxvY6uDT8e2XMINLmbmC89js3I0z8wLOdP36Aht43KIcwULD1+w82/WGT+dk8DP96/oNlqecb6uu8YEAAAAASUVORK5CYII=") +} + +.menu .current .menu_home, +.menu .menu_home:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKTQAACk0BtZPkxgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFVSURBVDiNpdO9atVBFATw3/UjBEGidkJSRFFIiKJBsAmaBxDFQptUIoJPYCc2Wmpjo4haRUFMnsEmnXUIKQXFRlEvaiIxY/HfheXCNUUWht2dM+fsYXe2l8Ruxp4d4jN4gDNlvw/70dMQw8ZRXMdFbOAvpjGJ93iHrWEdHMdDfMMcPuMpjiC4hYPDOpjCPazgceGeYS8m8BYj2AZJWpxOspTk9gBfcTPJSpJrlRtMXk5yY0hyxUKSF0lOtQWuJHmV5NIOyRWXk7xOcrVe4iSW8BXj/3mZ+jpfiv5ErThS5udJzjcnHUgyXubKnU3yUrcerR38wSH0sVm4C3iDO+Xm5wu/hX44jI3WB4OePoaPeIIPOm9U3XYVtQV6JfC77PtYxyrW8KPwP9vDWiNFZ5BzOtvO4Fejm8ZJzGqM1BbYLHiE77pPc7fEPuE+FjCGxaLV2+13/gdXJgTGYi2BZQAAAABJRU5ErkJggg==") +} + +.menu .menu_web { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAHuSURBVDiNpZO/S9ZRGMU/5xZl0NJQDf2ybAgrGgylGloiCR0jDAO3+4WQlqA/oiHkpUGvgYtFRENL5g+CCEIiqSjMoFKsaJFqScMXfU9DV3vVluiZDs997odzuM+Vbf6n1v+tmVJqA9okHba9CXgF9McY+1fPqtpBSmkH0Al8B45Iema7DDRlyHagK8b4ZQ2gp6dnn6QB2+2SdgFvJW2xPQeUbdcCM5L6gNYY4/QyIKW0AbgGPAb2AueALuAQMA9MApeAu7YnJZ0ALscYyyE7aZf0JsZ4R1I/MAKMAwFYl+0/AW4URXFb0gTQTh4AOGP7Xta7gbEY43NJw8BA1hPAHoA821wdYUxSCZi13QhslPTI9kmgLGkUOG37m6QXwGbbF2OMTUvPuGD7JzCbM2N7FigD87bnJC3r7HyhOsK0pNEY46CkoRxhRNJDYDjGOGz7fdYPsqNP1YBB2y0Ai4uLH4GjKaUG281AS0qpAThQqVSmsrsWYAj+bOIt4Gpvb+/ZEMJ+4BTwLp8ZaASOhRA6UkofgHrgyupFqpN0H7gA7ATGJW21/SNvY10IYcZ2n+3WoiimVgAAuru7a0MIncBXoF7SU9sLwHHgJbDN9vWiKD4v3VkBACiVSqqpqekAzgMHc/s1vz/TTVbVGsC/1i9dw/hm1FHr2QAAAABJRU5ErkJggg==") +} + +.menu .current .menu_web, +.menu .menu_web:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAGCSURBVDiNpdLPaw9wGAfw13fExWVq1IzYJEkc5kezgws5zM2S+irOWi7KH+EgB0e1m9ZycMGWm0jLRYqUWNNyIDkw2lq9Hb7P5rsfDvKpT717Pp/n/byf53k3kvif0/GX+AXcx3t8wgQurvexsUrBDozgGw7jBRZwHK+wHbeKdA1BLx6iiZ14i078LJLd+IJRnMVMO8Em3MQT7MH5qnQQ8/iAq7hXeBDXsLCxqjfxBuPVRide4xA2lPxnuIPP2Fo5o5JIMp6ku/BAkuHCp5MMFr6S5Gjh7iRjSSwp6MUpzOEYNmMRJ6v/rmqnG7uwpVpdJljEryKYr9hcJc+3DXIJd1TOMsEMnmMW36vSYzQq4Sn21Vqn0INz/DHSBIYKf8QR9ONMxfuxH9P1ZwiT7Qru4gaGsbfm8a7eUnMZwCUtdx7AdVYaqQ8PtCzbo7XGLvyo/vusNNL0agJabhvB16oyVcM6gZfYhts1q9LX2mv7bSS5nGQyyWzdR0ma6/xdo+Cfz28JnsxkWP6vVAAAAABJRU5ErkJggg==") +} + +.menu .menu_ftp { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAH6SURBVDiNpdM/qJZlGAbw3/1+n31o/0BJrMFaGmoRRBsM14M4iINLixBxHs6pxSxBmhpCqEk5CL2Pp5ZAdBD/UIJNhdBQzTqJYKkoB8HEY+r53rvB99hnuXlNNzfXfT3X9dzPE5npWTB8WrPWuhvvYwvGOI+vSykn/8uNSQfz8/MvdV33DRaxBlMInMZ9LI7H449mZ2cfLM80y0Xbtiu7rjsZEecz8ws8xBEc7l3swfXBYHB8bm6u+Z9A0zSfYmtmXoyIg/gKV3AJLQ7hV+wcjUYzT0Sota7C2Yg4kJm7sQM/YQOWcAFb+ygn8HHXdVMzMzMPlx1swML09PSPEXEIpzCNc/gBH+D7zPyylHIGt5umeWsywou4B5n5BhZKKTdwFddKKTdxJyLe7PmLWD0pcB2v9PVlvN627WZsxKZa6ya8hj96zsuZuUD/DobD4YWlpaUVtdbP8A62RcRNrO+38SG2I2qtv2AUERcfXyLUWnfhu8zcGBEFv+PVPtp9vI2j+A3vlVKOPSHQi+zHrcz8NiLebZrmr3yE1ePx+OfBYLAfK0opnz9eY9u2WzJzHRJ/Yy3uRsTePnfgSmYexAu4gZX9/NUh9kXElH8xxgjPTfTWR8RmPMBg+XCcGOKTiHh+gtxFxKqu685gXd/7MyJ29g6bCe7teNbv/A8ZE8Q3GMBOFwAAAABJRU5ErkJggg==") +} + +.menu .current .menu_ftp, +.menu .menu_ftp:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAGVSURBVDiNpdLdaw9wFAbwzw+jzVspIxdIuXHhQqbGHVmSRLlx4w9YKbkhpVz4A6SkKOWlldA0uXA1Um645motL7NImsnYNI+LnZ9+W63IqW+dzvc8zznPOaeRxP/YgnnixzCISUzgEQ7/DcEK3MEefEMDbfiKI7iKxfMRtKMfT3EePwtwCdM4gVHcnoVL0nxnk0wl2ZvkYZLuJKeSHE+yO0lfkgOZsd4mrgnuSPI4SU+SW0nGkwwkeZ1kKMmDJGNJric5mGQwSVsrQXeSu+V3JbmRZE2SK0kuJuks4i2Vcz/J1iR/tCzH9/I34hM+YATv8bEGublyJrCqdYijWF3+MDagC9uwvd46vK2clVXEogq8rHWdwQ7sq6rraxu92F9rfYYleNVKMI3LuFlVh/ECQyVtEmPow3McLYzGnFM+jc+4hl0YR0rvk/pvw7kmoJFkJ9ZW4g90mrnCk6W7gTe4gGU13PbCjzSS9KOnpYvp0jjrZEvGFBY2i+NeI8kmLG1J/IUODFRn8A6HqsPW8/8ydwb/bL8B1eb4OuOuSusAAAAASUVORK5CYII=") +} + +.menu .menu_data { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAHISURBVDiNpZPPi81RGMY/z+3mpmasGBOZEgthQ5SFTDM2svMjOyt9X1eUEhshRbGx1e1cO5M/YVIzSWbjx4RidTeKbl3XgoyYW5rHwvlO35TZzFtn8fac5znPc855ZZvVVL3apJR2AGeAw5JGbI9m6Iukvu0ZIEVEp+TINimlOnANGAc6wCvbHUm/AAHrgZ3AHmA7MC3pVlEUS6WDWaALHIuIb/9xOw3QarVGa7XafduzwGQtg+PA+xXIy9VsNnvAHDBRvYMBcDyldEDSvO1nkj7lCNhea3uLpEO29wOjmbMsUAfOAYu2C+CC7XW2hzL+A1iw3QWu5/0vqgIAD4A7EXFxpQgppRPA1bIvBX4Dt4GJlNIV4DPQlTTIERrApryeA3eBh1WBBkBEnM+n7AO22R7O+ALwISJeZvxkySn/wQD4CDyV9Nj2XET0/7G+ATgIHAEmgbGIaJQO1gCngF22A7iRUloCfgLOpwnoA4+AFvC6GmEGOA1ciogpgHa7vRkYygLfi6LoVdzcI79CGUHAZf5+jh4wb/udpK/5EoclbQX2AruBt8DNiFhUdRpTSmPAWeCopI22R7KDfh6mJ8BURLwpOVrtOP8BlJPKP95zNKgAAAAASUVORK5CYII=") +} + +.menu .current .menu_data, +.menu .menu_data:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKdQAACnUBSiXd/QAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAF3SURBVDiNpdI5a5VhEAXg58qFRDBi4RIiBkSJoiIoihZi0EpsFWy0FAtbSwvB0uUn2LiUliIWMWgT90i0sRFcYxqFiCYQcizufHK9hUQcGIZh3jnvOYdpJfE/sayn34pLeI4PWKj8jJe4jJHuhVYxaOM8RvEGT6r+RAursQ27sBl3cBGLkkgynuRmklXV/y0Hk9xOMpbkt4RRvMK3JciexkMc6pYwhyl8wlM8wPuSAMuxAQexF4PYif4GYAH7C+g0hrASKwrgO2bxEdfKs0doN7oWkkwmObEED44leVY7f0g4Wbr24Uv9Nl8M+orVECZwH9fR3+56AGer7sEmDFQ/i7d4XP3xZqdhMI93GMfdcnmmx/01OIAjOIxh9DUAwW5sx6lyeRE/atanc1AzuIXXOtfaaoy5l+Rqj1nrk2xJMlLH0z27kmSi28QWzpWJ0zq3MIWvRX8AG4vlDkziAuYagCaGcQZHsQ5rS8JM5Rhu4EWz0Avwz/ELJiL9PSckq44AAAAASUVORK5CYII=") +} + +.menu .menu_set { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAH3SURBVDiNpZI/SJZhFMV/90kicsv+EdEQRUNQYC1S0R+ag2hxMKPgvc/r0Ba2FFEpJARhiF/P9QsaGippimgoG4QgI6egICcJEkoIEyI/9L0NvV98llLQmS6c5x7OPc8Rd+d/0LQcUa1W1xZFcR9ocvfOGOPkXwXMbDvQoqov3b0NuA00i8gBYNLM9gBfVXWiviP1E8xsB3AZmANmgHVAt4jMu/sl4DuwFfgI9KvqO4DQYKAFmFXVU8Bj4JqqfsiybAroBZ6r6jFglbtv+MNB6aIHeKKqL4aGhra4eycQRORelmXvU0pHgPYYoy4SSCltFJGDwEngCjANXHT3WyGEeXfvAq4Dze7eKyJ3i6IYyfN8KgCIyAjQDFxQ1VfAaaA/xjiWZdm4iPQBHao6LiLdwJoQwsiiX3D3z0VR1NNd4e5zDfnUACnnWeATsNB4wjYR2Q8cdfcbIYSau58D+kRkwd273X1QRGaAAWC4KIrRPM8nfg/xqrs/jTGOmtlOoKN08zDGOGZm+0SkPcuys7+W3J3Sxd6U0s1ybk0ptda5SqWyOqW0u+QepJTa6lxjD2rASjO7A5wBBszsEEAI4TzQZWaDwBfg23I92AWsV9VnZnYC2OTur0Vks6oOm9lhYFpV3ywp0IiySI/42bzjMca3S71bVuBf8QODpRL9eTmkdgAAAABJRU5ErkJggg==") +} + +.menu .current .menu_set, +.menu .menu_set:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAF0SURBVDiNpdJNiM5RGAXw38ukkJVvMYqZRsqYZmyYDTZWbCxsKLG3pCSaWNmNJU2NmhU7pQjFRhYsJCTWykfJRz5eH8fmefn39lLy1FPn3nPv7Zzz3FYS/1Oz/sItwg3cwuo/HWp1KRjEQtzBTizAfHzCDMbwDk97PTCECXzBWyzGYXzDCXzGGjzHJB6DJJ3ekuRc4R1JNja4VUl2FZ5OsrXDNTO4jRcYx1W8wTEcx1xcwna0cbM7xGXYgxF8LakTuIbLZWVdWevHXixvWniY5ECSkVqfbGBJBpOcKjyc5FCSR90WXjXSnV1hdqqNVuH3eInvTQUDSfYnmUkylmRDkvNJ1icZSjJV+wNJriQ5WKr01avPqtfW3O/hNPaVmrO1N14qp35pa/jclORM4dHqDjevMdYLSTZ3uD6/q405mMYHjOJojewIluJHjfdj51L3Vx7GElzHbqzAXazERWzDazzoZaG7+5PcT/Kkwux5rlvBP9dPgIpDWf6ENxgAAAAASUVORK5CYII=") +} + +.menu .menu_folder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAE3SURBVDiNpZO9SgNBFIW/OySiaBCtbCwE8ReUgGDpE9gExNJmd6O+hM8gmGJn3yKxt7WyEARBUUSbIPhTGFAwxyarSwoZ2AO3mjkf98y9Y5IoI1fKDVQA0jRtOOd2B8BRST5Jkk4IwCSRZVkbOAYeACdpD3iX9DrUpQO+gHaz2XwuRnCSLuM4vpU0AnSAvplNmdlkocbNbN3MTrz3de/9WKVA/07TdN85twbcA5/A8AsLuAO6wI6k2UrhoGZmy3EcH4Zk995vAAd5hA9gBXgJMQ+0ZWanOaAH1IGrEGer1TJgxszOckAFWATOQwDVanUO6EVR9JYD5oGJJEmeAtvfBG7gb4xLwEWgGWA1v59PoQs8Zlm2ANg/RgETwLRz7voXIOnIzBrAdgCgBvgoivowWOUyKv0bfwCvBmEVd9ynHgAAAABJRU5ErkJggg==") +} + +.menu .current .menu_folder, +.menu .menu_folder:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAADuSURBVDiNpdO9SkQxEIbh56ynUHQbQbCxEMR/BEGw9B4s1m6t9W5svQgL3d7Wyk4QLES0EcGfQkGbsTgJuywIwTMQEsh8b+ZLJlVEaBOdVmrUad7HQQJO4hQXJYAqWTjHCR4S5BAfeBursoOflP8CIkJEDCJiLq03ozymcgUD9NHDFu7xjfEbjmR7Oo2FemSjizUcl3jHDo6yv0+s47VQDHsYZMAXtnFTKK4wj8sMqLGCq0LAYjr0PQOWMIOnQsAu7hi+8SquC8WwkfPzKzzjEcsaf39FpEpnccuwE3uadp4oAHQ1rX42Cvh3tP6Nv5Cebn/RRiyLAAAAAElFTkSuQmCC") +} + +.menu .menu_day { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKwwAACsMBNCkkqwAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFrSURBVDiNpZMxSFxBEIa/ed49tQsBQdEqgrVgithFW1sr62PhKttXpjy0vWb2WjkQrk6rjWUgjY3ICorYGCFV9C7vxuJ24fE4Y8SBZf/9d3b2n/1ZMTPeE1l10ev1Frz3Wy8lq+pXVf1Y5cTM6Ha7jTzPd4BtYBUYAHO184/ALnAJnJRl+b3dbo8aAM1m8xB4AO6BT2b2C5ivFxCRv8AtsJ5l2SZQYGao6kBVFyNeNjOmDVVdifOSqh6Z2aQF7/0xcGNmF4BMkV9tw0RkDfjgnGs14kYJXInIOTALjGOhukUZ8AQ0gc+JIJI/nHNnIYRWCME5505CCC7i04hbzrkz4Gc8Q1IwEwedTmcvXfcSruZnNZKiKPpFUfT/hZnYX1YVyGu31hRk6X2SgnEi/lPBb6JTSUFuZsM3KPiTWk4FbkXki/d+CORMbK3aKEzsTXhDRK6rBb4B+2bWAkbVN5kSDRG5E5EDiJ/pPfEM08DH4VH64rEAAAAASUVORK5CYII=") +} + +.menu .current .menu_day, +.menu .menu_day:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAEPSURBVDiNpdO9SkNBEIbh58RoFKwEwT8QFOwEQQvt1NbW+7Cx8B7svAZLa1tbG0sbEQQh2KigjcZoxsI9YT2EGMnCMt8us+98s8sWEWGYUausp7HTJ38bU/lGPYt72MUyFjFeOfyOfdziAudol4BjPOMRS3jCRA/AJ5pYwxaORISIOIuImaTnU+w1F1KcjYjTiOi28IVD3KDoYT93EVhJ+hfgDtdooJNA1SeqoYVRbOSAFq5wmR0q+mhYzQEjaeYJ/XQ3v1bZlCrFH7rw03bXwSBVc10rYaWDzgBVc/0ivVTpYAwf/3DwJrVcAprYTJCx1F/15huZXsc9FOk3TuIAc2hXqlVHHQ84wWsx7Hf+BgvadUGnT3fcAAAAAElFTkSuQmCC") +} + +.menu .menu_control { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAF5SURBVDiNpdM9ixNRFMbx3yRZ3/ANbCxEcBubpNJSLIQFaxs7D4JfQSz9CpYWItyPYCOIFtqvhQxopSyCb1gpEuNukrHImewQY7UHLsOce85z/vPMvVXTNA4SAyilfMI2Zpn/n2rV6RtGxOYgE9sITLK5Qa8jVGGezwpH8VgWwRTjiJigj6vYwrVcW5nrZ824pW0Jeh0xSVJhr4M86ez32/qBf6OH04nZCmzk1HbI0qN1AhO8zuYfOJ5i8xWKZq1AREzxpZRyAlfwJiI+r5RVrUCLNOvgtnER93FpDeV0laBnYcwMSimHcQpP7Z+V2xZelA7FkuA8opRyK9/P4gIeYq+Ucj2/fwM3Lc7Mua7AVzzH91LKHZxBExE/s/EudiwOz0u8wLeuwDgidvAMf3APr3LvHR7gfUT8joiPw+HwQwqrmqZRSnmSWL9wEpt4a/Hr5qPRaLeu60OdgcfwKCJutCZeTnNmuXZxpDWqruvG/j1o0vDhkuAg8RdE7nuSY6nc+gAAAABJRU5ErkJggg==") +} + +.menu .current .menu_control, +.menu .menu_control:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKwwAACsMBNCkkqwAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFaSURBVDiNpdM7a5RREAbg59uLJOgSkRQhfYSIhWWsQgj+gfgLTJEmBJLa0tZW0MbawtbaUkQEQ8Q/YCWIIAqb7G1S7JzNYdlUOfAxlzPznndevmkiwk1OJ+03nGKMBpNr6lsIdLGBrQJwimP05wCatFEBTHAHL2sGY/zHEMt4jBWMKqZ/8RkXVc8MoJX+MF8bJJth3nczV5i0s2cGUJ82eslgkLlbOU57vngRwAA/sqmftp35AhiFzSKAIX6mv4+3C2pa887YlTjlPMAJtjNeMtVC1k5qgA5Wa2Q8xQvsZnyI51jD3cK+jLCOA/xLyn1TET/m3U4+0M+xRpmfAfzGBzzBM/zB17Rf8B57+ISHpsI+AhEhIt6kbSLiKCLOImIzc72IOIyIbsblex0RMwZLuGf6t73Dd/zK/AVepYDLKV4v7zS5jfUyjXCO25Wok/TLnnRwH1vNTdf5EpkVg1v2kuagAAAAAElFTkSuQmCC") +} + +.menu .menu_soft { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAF7SURBVDiNrdPPi41hFAfwz72uoVsUWZOFJbKThYkVe1mpx13YjcmU/NwrZCKRLNRZsrSZErvJ1h+glFgiYxC6Pyze8zSvOwsLzuZ5T9/n/T7n+z3ndCaTiX+JHkTELlzHHozQxQrOl1JeRcReLGIbxtiAN7jcS6JrCT5OcIx9mMcAZ/EBz5N8hCO4UQl240kp5XYtLSJmsZDpdjwspTxr4V9xppv5EP0peVuzEnlumcL7GFaCDn5OXfj+l/wHulXCBIci4rXG2BFmMZP4RhyLiBmNR0McxqgSLGBOY1iNIW7l96LGyNMtfBXznf81B/vzhR0pB37hbillOSIO4hw2JdbBJ9yrEu7gIx7504OLWMYFvMcLax6cwv1K0MPLUsrTWlr2eS7TEZZKKUstfCcGtY3jVnk1+i05E+vnZDPGlaBnfZ+/aMZWnqtT+Df0qoR3ON7qc92FlcQ/YxARB6ztwlG8rQRXNdt4Mn/u5AuXEn+AmziRcroaU6/8BgTXdRpxDzi5AAAAAElFTkSuQmCC") +} + +.menu .current .menu_soft, +.menu .menu_soft:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFsSURBVDiNrdO/jw1RFAfwz4xnVzYIydZeY0tEKETChkIoNGrNtkKyhQL/gUg0YgudQiciGpVEIRsdPdlkQ0usH4vNvHcUc27e9bbkm9zMmfnOPfP9nvneJiL8CwZ5PYS72I8xdmANN/EeQ9zGAkZosYHrTSp4gD14m+QIZ/ATl/AI83iRzcc4jC0RISIeR8S5rMu6EhFvsl6NiOUpfjEinrZpYZwKasyhy7rL+xp7MW6rB5tTL/xKO9Dg9xS/WQ9xJ85jJj12OJ2zgMBJvMs9IyxipgzxFK5lo4JvWMFrHMNV7Kv4DivN/8rBkVQwn3JhC/fwCiewjNnkGnzG/aLgJT7h4ZTHBVzEE3w0yUGHyxgWBQOs4lml7rvet2z4PFfBASzVOZj1N+YqO2F7DnapcjCwPQdfTXLQ6v9KjR8YFAsfcMEkByXrG8l/wRKOmpyVs1gvQzyoP23D3NzkF27oc3Acd7A77bT6od76A2AskgeNVoIQAAAAAElFTkSuQmCC") +} + +.menu .menu_firewall { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAG0SURBVDiNhdNPiI5RFAbw352ZsGBDMhkpYyE12cxOyYIFayW7SY2zkCxIlkrZKIrJjPe+X1FksrQQo/xp2IhZiPXUxIoaGxa+mGsx78dn+vDUvXVu53l6znO6qZSig6mpqbX9/f37cRB7sDOl1C6lzOIZHkXEK11IpRR1Xe8upRzFKNqYxzt8xGpsxy6swwCeYjIi5gcaoTt4i8BcRBQ90Gq11i8tLY3gCjZibABKKUu4FxGvexE7GB8fX8RsVVVPUkqDGjtQGqv/RM75CJ6nlH6gT+dCwvf/kE9hGlvwrZfAqrqu+3LOQz3Ix3EJpyPipeUwdQt8RbuUshkLjdUOeQzXcD4iLnfptvmdwXtsjYgPOefrmM45f0Y/buJiRJzrIm+wvOJfDl5gBCLiBFp4iPuYiIizK6YaSim96Ra4jeGc86ZG5BhyQz65Io9hDJZSZkApRSlFVVUTVVXd7dR/O1VVPaiq6mqn7jiQUjqD0Zxzd1B/IOc8iW1N7zKv+zM1I8zgE8YjYqF534EbTegHImKxp0DTnHABh3ALa3AY0ys20Vugg7qu95ZSHuML9kXEXK++ny1tzgEddf2OAAAAAElFTkSuQmCC") +} + +.menu .current .menu_firewall, +.menu .menu_firewall:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKdQAACnUBSiXd/QAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFrSURBVDiNhdMvaJZRGAXw36viFrRoENxw4IJYLDMJalkxC8OkrC5qNAwEi+CC4lAsikGMY0FQmGIQ/AMKyjDIUGYyqMXgmB7Dd9/P63j37cAN73OfczjPc+7bJFFhFyZxCsdxGGt4hqd4hFc1oSkCxzCNiUJYwXt8xRDGcQS7sQNPMI8VSST5lGQxydEkTal1nT1JTiR5k+RuEtuKkz9YwGv8N9MGfCvjLBUnfYEUq1vhDEbwu+W2Ag3WtyCfx32M4leXwM7yPdJBnsFVXMALvWWqBX7qbX8/PherLc7hBi5hrqqvURaBVRzAF9wsVr9jO+7gCmYr8l69iLXxXEyyUMV1O/9wrSPOpSRnk/QFxpK8S7Kvarq1CflgkuW2t764nuTBgEfUnoe1cH0xnORjkrkB5PkkH5IMdQkott4meVzGauuHkjxP8rI85z6n/ZlqNLiM07iHYUyVZGY3Ng+a9WSS9SQ/kkxs1vcXeVqZSyUF+yoAAAAASUVORK5CYII=") +} + +.menu .menu_exit { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAEYSURBVDiNpZOtTkNBEEbPFCQIUHgEBoJAYFDgERgSCBjSvU9Q0/AkX5+ApB6PI6lAYPhJUCgEgkApkHyI/nBpb28DPWozu3MyuzsTtpmG2f5CUs32BlABouBsAK8RUU8pPY4IImIfOLd9k4/n+IyII2AZGBUAM8BZlmXX48qVtAksFl6hx/y45B5N4C4fqExI+EVK6QJYkLT9L0GPJ+BU0hYUP9YAScfACtDh52c6wAPQlHRQKoiIN+B5WGC7DXzYfi8VVKvV5nCs0WgsRcSh7b0sy1qlgiJszwG1lFIL/viIknaAdkrpsh8bruBlgmOXbq8UduIXcCLpvkDc31+j20wDBgdty/Y6sMroMJnuda+A2/xGTDvO32OQXrvPg7l3AAAAAElFTkSuQmCC") +} + +.menu .current .menu_exit, +.menu .menu_exit:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKdQAACnUBSiXd/QAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAADvSURBVDiNpdO7LkRRFAbg7wwlBZVeoSEKEtHSayUSGs+g8RwqbyDR6ycaiYRC45KoVAqFuAySpdhzODn2mWMyf7KSvdflz7oWEWEUjFfee1hCB0XGt8AL9vGQI9jECa5r+hKf2MZsE8EYjnA1IOMVTDeVAJMDguEYt1VFpyWgji6msNaUwX/wiAN84LSNYAdz6PmdTA/3UjlbbQSveMoQvPUzeBcRpVxExGrl3yQzEdGNiOWIGLqJMCEt3TnDT2G9n/5Zqaj34LmFYEPalewmfmEXdxni0r4gdf8HVcdDLGLe32MKqdxL3FQNxajn/A0ZS19hUhhlTwAAAABJRU5ErkJggg==") +} + +html .menu .menu_home:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAPPz8+np6d3d3c/Pz8vLy8XFxb+/v729vbu7u7e3t7W1tbGxsa+vr62traurq6mpqaenp6WlpaOjo6GhoZ2dnZubmwrPOpmZmQzLPJeXl5WVlRLFPhLDPhy5RI+Pjx63RImJiYeHhyypSiirSIODgzKfToGBgTabUDibUDiZUH5+fj6TUnx8fECRVEKPVHp6ekSNVkCPVESLVnZ2dnR0dEyDWnBwcFR4XlZ2Xlh2XlpyYGZmZgAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJHgA7ACwAAAEAEAAOAAAGhsCdcLgzJUjE5HCmECxMFYZHuWMRHDuJINIg2JIqAmQYOS6SpEEkORFciOkJlVIwCTUEDfVOyOweFyAzVDMgFw1DBSFDOkMkBUQ2B0g1HyIdNUUGX0OTOy0jNyMrRQecOzYIKjsyKDspMDsvpkQGFDklJzsoJTgVBkkIAAEYLjsuGAEACEJBACH5BAkeADsALAAAAQAQAA4AAAZxwJ1wuDMlSMTkcKYQLEwVhke5YxEcO4kg0iDYkioCZBg5LpKkQSQ5EVyI6QmVUjAJNQQN9U7I7B4XIDNUMyAXDUMFIVQkBUQ2B0hKJgZfQ5FUJgeWOzYIKlQvm0QGFEM6QxUGSQgAARYcIhsWAQAIQkEAIfkECR4AOwAsAAABABAADgAABnfAnXC4MyVIxORwphAsTBWGR7ljERw7iSDSINiSKgJkGDkukqRBJDkRXIjpCZVSMAk1BA31TsjsHhcgM1QzIBcNQwUhVCQFRDYHSEomBl9DkVQmB5Y7NggqQzpDL5tEBhQ5NR8iHTU4FQZJCAABGC47LhgBAAhCQQAh+QQJHgA7ACwAAAEAEAAOAAAGfcCdcLgzJUjE5HCmECxMFYZHuWMRHDuJINIg2JIqAmQYOS6SpEEkORFciOkJlVIwCTUEDfVOyOweFyAzVDMgFw1DBSFUJAVENgdISiYGX0ORQzpDJgeWOzYIKjs1HyIdNTsvnUQGFDklJzsoJTgVBkkIAAEYLjsuGAEACEJBACH5BAUeADsALAAAAQAQAA4AAAaCwJ1wuDMlSMTkcKYQLEwVhke5YxEcO4kg0iDYkioCZBg5LpKkQSQ5EVyI6QmVUjAJNQQN9U7I7B4XIDNUMyAXDUMFIVQkBUQ2B0hCOkMmBl9DkTs1HyIdNUUHmDs2CCo7Mig7KTA7L6JEBhQ5JSc7KCU4FQZJCAABGC47LhgBAAhCQQAh+QQFHgA7ACwGAAkABQADAAAGDsDdTiesfUSdWmt0G62CACH5BAkyADsALAAAAQAQAA4AAAYVwJ1wSCwaj8ikcslsOp/QqHRKdQYBADs=") +} + +html .menu .menu_web:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAPHx8e/v7+vr6+np6efn5+Xl5eHh4d/f393d3dXV1dHR0c3NzcvLy8nJycfHx7+/v729vbu7u7m5ube3t6+vr62traurq6mpqaenp6WlpaGhoZ+fn52dnZubm5mZmZeXlxTDPpOTkyqrSiqpSoeHhy6nSiynSoWFhS6lTDCjTDKhTjSfTjadTjadUDSdTjabUDyXUjqXUjyVUj6TUkCRVHp6ekSNVkaLVkSLVnh4eEqHWHZ2dkiHWEqFWE6BWlB+XFJ8XFJ6XFR6XFZ4XlZ2XlpyYFxwYmZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFDgBHACwAAAEADwAOAAAHbYBHgoIdCgIACRyDizsTFQ0XGA4WEjuLNQMnH4shJwU1gw8di4sdD4IaGKSkGBpHC5argzsMRwYbsoMbB0cHHrmCHghHDDnARzm1GhnHGa5HEL+yHxCDmJqcJASg1hEUDRgZkxLGqxoJAQGJi4EAIfkEBQ4ARwAsBAADAAgAAgAABxGAR0dDPkBDgi0gNCgqNCAsgQAh+QQFDgBHACwEAAUACAACAAAHEYBFKUAogyNGPjZHPT5HMD+BACH5BAUOAEcALAMABwAKAAIAAAcWgDcoJjclKDciKTc6Kig9KCo6Jis6gQAh+QQFDgBHACwEAAkACAABAAAHCoA+Nkc6PkcwP4EAIfkEBQ4ARwAsBAAKAAgAAQAABwqARShAJSlAI0aBACH5BAUOAEcALAQACwAIAAIAAAcRgCwgMyUpMiAvR0dDPkJDioEAIfkECTIARwAsAAABAA8ADgAABxaAR4KDhIWGh4iJiouMjY6PkJGSk5SBADs=") +} + +html .menu .menu_ftp:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAP////39/fv7+/f39+3t7evr6+Pj49/f39nZ2dfX19XV1dPT09HR0c/Pz83NzcvLy8nJycfHx8XFxcPDw8HBwb+/v729vbu7u7W1tbOzs7Gxsa+vr62trampqaenp6OjowDZNp+fnwLXNp2dnQTVOATTOJubmwrPOpmZmQrNOgzLPJeXl5WVlRi/QBa/QJGRkY+PjyC1RI2NjSSxRomJiYeHh4WFhYODg4GBgX5+fnx8fECRVESNVkSLVkaLVkiHWHR0dE6BWnJyclB+XFJ8XFR6XlR4Xlp0YFh0YGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJMgBJACwAAAEAEAAOAAAHvYBJgoIhCAICCiuDi4IOEQsBAQ4QE4yCCR06DRUWD0AYDYwaATALNBweMA8sAB6LCC8QAwwEBQwDESwIgzQOSTURQBMVQBA5SQw3grFJKBdJGRtJFCxJDzKCOApJNg01DA42DDZJCziDCRsMAhQGBxIBDBsJiygCORYjqB8XNgAmjDZ8SCLjxg0YSTRkGBSkh48fO3i0UKGiRY8dP3z4GBKDhMcSIkCIBCGihEcSM5AUWVlkyAmRKYiwLHIkEAAh+QQFDgBJACwAAAEADwALAAAHj4BJgoIhCAICCiuDi0kOEQsBAQ4QE4xJCR06DRUWD0AYDYsaATALNBweMA8sAB6DCC8QAwwEBQwDESwIgjQOSTURQBMVQBA5SQw3SbFJKBdJGRtJFCxJDzJJOApJNg01DA42DDZJCziCCRsMAhQGBxIBDBsJgygCORYjqB8XNgAmizZ8SCLjxg0YSTRkEBQIACH5BAUOAEkALAAADAACAAMAAAcIgEE9MSRIRYEAIfkEBQ4ASQAsAgAMAAEAAwAABwWAPiRFgQAh+QQJDgBJACwAAAEADwAOAAAHIIBJgoOEhYaHiImKi4yNjo+QkY8/OzyMJCUijEVFQ4mBACH5BAkOAEkALAAAAQAPAA4AAAepgEmCgiEIAgIKK4OLSQ4RCwEBDhATjEkJHToNFRYPQBgNixoBMAs0HB4wDywAHoMILxADDAQFDAMRLAiCNA5JNRFAExVAEDlJDDdJsUkoF0kZG0kULEkPMkk4Ckk2DTUMDjYMNkkLOIIJGwwCFAYHEgEMGwmDKAI5FiOoHxc2ACaLNnxIIuPGDRhJNGQQFKSHjx87eFgaFIOExRIiQGgEwQhJkY9FJg4KBAAh+QQFDgBJACwAAAEADwAOAAAHsYBJgoIhCAICCiuDi0kOEQsBAQ4QE4xJCR06DRUWD0AYDYsaATALNBweMA8sAB6DCC8QAwwEBQwDESwIgjQOSTURQBMVQBA5SQw3SbFJKBdJGRtJFCxJDzJJOApJNg01DA42DDZJCziCCRsMAhQGBxIBDBsJgygCORYjqB8XNgAmizZ8SCLjxg0YSTRkEBSkh48fO3gs6rHjh6AYJDKWEAGiIwgRJUgIQlKkpKUkJpMEAgAh+QQFDgBJACwNAAwAAwADAAAHC4A+PkMkJDNFRUeBACH5BAUOAEkALAQADAAGAAMAAAcOgElJLSoqLYKISUVDiYEAIfkECTIASQAsAAABABAADgAABx2ASYKDhIWGh4iJiouMjY6PkJGSk5SEJyAgKUSFgQA7") +} + +html .menu .menu_data:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAP////39/fv7+/Hx8e/v7+np6ePj4+Hh4d/f393d3dfX19XV1dPT08XFxcPDw8HBwb+/v7u7u7m5ube3t62traenp6OjowDZNgLXNp2dnZubmwrPOpmZmQrNOpeXlxLFPhTDQBTDPhbBQBTBQBa/QBq9Qhi9Qh63RBy5QiC1RCC1RiKzRiSxRouLi4mJiYeHhyirSCynSoWFhS6lTIODgzKhTjSfTjadTjKfToGBgTibUDiZUHx8fD6TUnh4eHZ2dkiHWHR0dEyDWnJyclB+XFZ4XlZ2Xlh0YFxwYGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFMgBJACwAAAEADwAOAAAHr4BJgkUzIB0XFxsfM0WCgjUlMD1CRERAOiwiNoIlLEiOoElIKSVJFzehoTsXSRggJzZCR0hIR0I2JyIYSQEyOTErKCQkKCoxNDIBvAYaqYIcB8oCHBAHDA4QEA4MBg8cAkkAHIIyGhYWGi/PAEkCBQ4cQaBBHg4F4AA0GQsGBgkJB6xloMFOQQRHP3jwkCcoAgJBFBg0qNAiRw4XGSIskADKxwQDAwIEIGAgAg1BgQAAIfkECQ8ASQAsAAABAA8ADgAABxaASYKDhIWGh4iJiouMjY6PkJGSk5SBACH5BAkPAEkALAAAAAAPAA8AAAe0gEmCRTMgHRcXGx8zRYKCNSUwPUJEREA6LCI2giUsSI6gSUgpJUkXN6GhOxdJGCAnNkJHSEhHQjYnIhigMSsoJCQoKjGgATI5qYI0MgFJAQYayUkcB80CHBAHDA4QEA4MBg8cAkkAHIIyGhYWGi+CHABJAgUOHEGgQR4OBeQANBkLDBhIkOCAtgw04imI4OgHDx73BEVAIIgCgwYVWuTI4SJDhAUSQPmYYGBAgAAEDESgISgQACH5BAkPAEkALAAAAQAPAA4AAAevgEmCRTMgHRcXGx8zRYKCNSUwPUJEREA6LCI2giUsSI6gSUgpJUkXN6GhOxdJGCAnNkJHSEhHQjYnIhhJATI5MSsoJCQoKjE0MgG8BhqpghwHygIcEAcMDhAQDgwGDxwCSQAcgjIaFhYaL88ASQIFDhxBoEEeDgXgADQZCwYGCQkHrGWgwU5BBEc/ePCQJygCAkEUGDSo0CJHDhcZIiyQAMrHBAMDAgQgYCACDUGBAAAh+QQJDwBJACwAAAAADwAPAAAHtIBJgkUzIB0XFxsfM0WCgjUlMD1CRERAOiwiNoIlLEiOoElIKSVJFzehoTsXSRggJzZCR0hIR0I2JyIYoDErKCQkKCoxoAEyOamCNDIBSQEGGslJHAfNAhwQBwwOEBAODAYPHAJJAByCMhoWFhovghwASQIFDhxBoEEeDgXkADQZCwwYSJDggLYMNOIpiODoBw8e9wRFQCCIAoMGFVrkyOEiQ4QFEkD5mGBgQIAABAxEoCEoEAA7") +} + +html .menu .menu_control:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAPcAAOTk5N/f38bUxsnQycTTxMfOx8XLxcPKw8bKxsfHx8TIxMbGxsXGxcXFxcfFx8PGw8TExMXExcbCxsfCx8PDw8PEw8PCw8PBw8HBwcW/xcHAwb7AvsK/whzVHBzTHCDOICLLIjC2MDO0MzOzMzKzMjWxNTOyMzWvNTavNjirOE2hTTqpOjunOzylPEmhSUqgSjqlOoaGhj+iPz6iPoSEhD6hPj6fPj+fP4ODg0CfQDqhOoKCgkGcQUObQ4CAgEWXRUaWRkWWRUqQSkiSSEiRSEyNTFuHW3p6eluGW3l5eU6KTliFWE6JTk2JTVaFVlWFVWB/YFGGUXV1dVGEUVWAVVSAVHRzdHNzc3FxcVV9VXZvdlp4Wm9vb2hyaGF0YVt2W1x1XFx0XGFyYWNwY15yXl1zXWtra15xXmNtY2FuYWpqamBvYG1obWJtYmlpaWJsYmhpaGNqY2hoaGJrYmRqZGRpZGdnZ2VpZWVoZWRoZGZmZmVnZWdlZ2VlZWdkZ2djZ2hjaGxdbG9ab2ZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCACDACwAAAEADwAOAAAIhgAHAUgAYcGCBggbGISQIMCgBlwG9dFjp6IdPX0GYaEwCMKgQWqO4NixA8cRNR89LvjoJglJkkncpBy0EqSPGDRoxPCBsiPNj3KwSLlyRQoWOTNrflzK1GeDplAHcWQQtWmFQQOqMi0wCIHWpQoGWfj6cSzEQXC63IG6UWCCBhA2KDS4sGFAACH5BAUIAIMALAIACQACAAMAAAgJAMUMcsHjSZSAACH5BAUIAIMALAMACgADAAQAAAgQAGGUGbSCxaBBJ4IM8gIlIAAh+QQFCACDACwEAAoAAwAEAAAIEAAHhTkzKAeVFiCoDDIyKCAAIfkEBQgAgwAsBQAIAAMABAAACBAABzFJM6jElj0pwAwaMiggACH5BAUIAIMALAYABwADAAUAAAgSAAfpGFSlQ5FBQEIMGpRiYZiAACH5BAUIAIMALAgACQADAAQAAAgQAAcN2jJIiIwpHogMujEoIAAh+QQFCACDACwJAAcABQAFAAAIGQAHCRw4kI2gHiK+iBlEwoQNFYOaBMqyJCAAIfkECTIAgwAsAAABAA8ADgAACBkABwkcSLCgwYMIEypcyLChw4cQI0qcSDEgADs=") +} + +html .menu .menu_firewall:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAP////39/fv7+/f39+3t7evr6+np6efn5+Xl5ePj4+Hh4d/f393d3dnZ2dfX19XV1dHR0cvLy8nJycfHx8PDw8HBwb+/v729vbu7u7m5ube3t7W1tbOzs7Gxsa+vr6enp6WlpaOjo5+fnwDZNpeXl42NjYuLi4mJiSitSCqpSiypSoeHhyirSC6lTIWFhTKhTjiZUDyXUnx8fECRVEKPVHp6enh4eEqHWEiHWHR0dFB+XHBwcFpyYGZmZgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFAwA9ACwAAAAADwAQAAAGdMCesEeiHAACR2c17J0oiUXEE/pgHAsFptYjQFzN4c6UmPQKoXAYIzl/1M1M5AyCDzdzwtveu8wRdXwXbQ8afD0SGD0dEIcNIj01CDl2NQeUPRZzcA8WTQWKYRUGYTkJDlw9MgwLO3AaBh0bBhyHJQEDYE1BACH5BAUEAD0ALAMABgACAAMAAAYHwB6PN+qlggAh+QQFBAA9ACwFAAcAAQADAAAGBUDaCBUEACH5BAUIAD0ALAYACAACAAMAAAYHwBtuNGKpggAh+QQFCAA9ACwIAAcAAQADAAAGBcDZSBUEACH5BAUIAD0ALAkABgABAAMAAAYFwNioFQQAIfkEBQgAPQAsCgAFAAEAAwAABgVA2OgVBAAh+QQFBgA9ACwLAAQAAgADAAAGB0Aab6SD9YIAIfkECTIAPQAsAAAAAA8AEAAABhXAnnBILBqPyKRyyWw6n9CodEqtJoMAOw==") +} + +html .menu .menu_day:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAANPT09HR0c3NzcvLy8nJycfHx8XFxcPDw8HBwb+/v729vbu7u7m5ubW1tbOzs7Gxsa2trampqaenp6WlpaOjo6GhoQDZNpubm5WVlY+Pj42NjYuLi4mJiYeHh4WFhYODg35+fnZ2dnR0dHJycnBwcGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgAlACwAAAEADwAOAAAGesCSUKQRGkua0RGjAFQm0EkFoMAIFY9HAHPpXjABiINREohKoaMwHSKUBgvKJEKvTygLw1vCyWj+gBkcEnoGHCUWFmpCHAglBYeLRhsHjxuSRhqOBJeYSI4FnZgeegMdniUgBSULER8dHrCxG7QRDUIPCQq7vLsJDyVBACH5BAUKACUALAcABwACAAEAAAYEQIslCAAh+QQFCgAlACwKAAcAAgABAAAGBECLJQgAIfkEBQoAJQAsBAAJAAIAAQAABgRAiyUIACH5BAUKACUALAcACQACAAEAAAYEQIslCAAh+QQFCgAlACwKAAkAAgABAAAGBECLJQgAIfkEBQoAJQAsBAALAAIAAQAABgRAiyUIACH5BAUKACUALAcACwACAAEAAAYEQIslCAAh+QQFCgAlACwKAAsAAgABAAAGBECLJQgAIfkECTIAJQAsAAABAA8ADgAABhTAknBILBqPyKRyyWw6n9CodEoNAgA7") +} + +html .menu .menu_soft:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAOPj4+Hh4d/f393d3dvb29nZ2dfX19XV1dPT09HR0c/Pz83NzcvLy8nJycfHx8XFxcPDw7+/v729vbu7u6+vr62traurq6mpqaenp6WlpZmZmZeXl5WVlRTDQBTDPpOTkxLDPhbBQBi/QBTBQBq9Qha/QJGRkRi9Qhi9QI+Pjxq7Qhy5RB63RBy5QiC1RiC1RI2NjSKzRiSxRouLiyKxRiavSCitSCSvSImJiSatSCypSiirSCynSoWFhYODg4GBgTadUDadTjabUDSdTjibUDiZUDyXUn5+fjqXUjyVUnx8fHh4eESLVkaLVkaJWEaJVkqHWEiHWEqFWE6DWkyDWk6BWlB+WlB8XFJ8XFJ6XFR6XlR4Xlh2Xlp0YGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFHgBeACwAAAEAEAAPAAAHtoBeXTEdIR0qWF4/BgECAQtKXjIrQkBAMjVeEAwVFBUHCl4iRV6lUy5eCyalXhgAXidArE6oCxqsFQGiRKxStR+sGLonLk5MTTwsXgoSHBscCwJeWDUu1jFUXjMLCt0OOKzh4uI+DwndDDNeOAzdCQ89XgUKzhwTCcsR9dFeAxasKUItAFZKmBcCFFhxEHirVK5+F1jBwKeKVYZXDA50qtDAgZcHnDwdwKdEQaMABcD1KHAywZFAACH5BAUeAF4ALAAAAQAQAAcAAAdhgF5LDAECAQY+XloqHSEdMVxeDQcVFBUND147MkBAQisuXgMXXqUwCV4uUKVeRh1eBBSsHApeMUysRK8EFqwptTGrpa6wChwbHBMIqTxNTE4xIV49DwrWCzBeUzEu3TVWgQAh+QQFHgBeACwJAAEABwAPAAAHWIA/BgECAQtKEAwVFBUHCgsmXpIYAAsakl4Vhh+YGAEKEhwbHAsCMwsKqQ44mK1WMS6xNlguOk1MTjEhMVCYRh0xTJhEHS69kkkdNjJAQEIrLlckHSHFWoEAIfkECR4AXgAsAAABABAADwAAB3CAXoKDhIWGh4iJiouMjY6PiVg2LpQxVV44DAoKCQ89XiQuTkxNPCxeChEcGxwLAl4iRINSLl4LH4MYAV4nQINOtQsagxW7IkWDU8EmgxkAXjErQkBAMjVeDwwVFBUHCV5cLh0hHSRWXj0FAQIBCUeBADs=") +} + +html .menu .menu_set:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAMfHx8XFxbm5ube3t7W1tbOzs7GxsQbROGqpeg7JPBDHPpWVlRLFPmylemCnchTDQBbBQFqnbBTBQFSpaBi/QEqrYpGRkRq9QlinbBa/QI+RkRi9QBi9Qh65RI+Pjxq7QkqpYhy5RIuPjRy5Qh63RCC1RIePiSKzRiSxRn6Rg4uLiyKxRiavSCitSCSvSImJiSqrSiatSIeHhyypSiirSCqpSiynSoWFhS6lTHyJfjCjTHyHfjKhTDKhToODgzSfTmaNcIGBgTadTjKfTjabUDSdTjqZUDiZUH5+fjyXUjqXUDqXUj6VUjyVUj6TVHx8fD6TUkCRVEKPVHp6ekKNVnJ8dECPVESNVkaLVkSLVnh4eEiJWEaJVkaJWEiHWEqHWHZ2dkyFWkqFWE6DWkyDWk6BWlB+XFB+WlB8XFJ8XFR6XlR6XFJ6XGR0aFR4XlZ4Xlp0YFh2XlZ2XlxyYFh0YFpyYFxwYlxwYF5uYmZmZgAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJDwB5ACwAAAEADwAOAAAHpoB5goN4Hxdwg4l5b2Z5W1JMUnlpb4lvJywzKHBzMDYkMJWCZS15Fj6CWgt5LGSJOi95WgYFSHkeNINzVCQyUwEyNwFINyFSc3kKTml5BKh5TwR5akYKyVuCA7Z5UwN5cFIMeXFRKDdBAEhIATdPIU6ieTwqeUEDAjJ5LzOJZvw3NwahOlFmkBoYmSi46gGjITODYfJgUVLmSh4yahQNgsMgQcZEgQAAIfkECQ8AeQAsAAABAA8ADgAAB7CAeYJ5c15pgjo2eIODeEIXLExJQh1mjIM/aXhGUXlRW4xzZVcsGj6CWggwMEeLJxdMGHlaBgVIeSJfJGR5NUp5MlMBMjcBSDdwLId5QlJ5BKd5TwR5PE6DUUx5A7d5UwPMRIJpNnk3QQBISAE3T3MwOHlbJWMmeUEDAjJ5E1IbM3nsCNHRocKNQTtKYBmzKM8iL+LGJJmzJd6lPGQeMHESpUWJIxcdtgCYx0yYhnkCAQAh+QQJDwB5ACwAAAAADwAPAAAHqoB5goN5c4SHhEIoUYiDb3lvNk4wW2UoSYQlEEYhZXlqLFg2KIQtWGkNPoJVC2SMg2FOL3laBgVIeQ4zhFI2MlMBMjcBSDdOJFh5RD+CBKp5TwSCNUlwM06CA7h5UwN5Ui1feU41RjdBAEhIATdAEHCEFBF5QQMCMnkpRPCDRmo3OQS1AZEGgo5BaQ4YoQIjTZ4eLZpQ8DRISY85ZWjAqCEITyNBTpKMORQIACH5BAkPAHkALAAAAQAPAA4AAAemgHmCg3gfF3CDiXlvZnlbUkxSeWlviW8nLDMocHMwNiQwlYJlLXkWPoJaC3ksZIk6L3laBgVIeR40g3NUJDJTATI3AUg3IVJzeQpOaXkEqHlPBHlqRgrJW4IDtnlTA3lwUgx5cVEoN0EASEgBN08hTqJ5PCp5QQMCMnkvM4lm/Dc3BqE6UWaQGhiZKLjqAaMhM4Nh8mBRUuZKHjJqFA2CwyBBxkSBAAAh+QQJDwB5ACwAAAEADwAOAAAHsIB5gnlzXmmCOjZ4g4N4QhcsTElCHWaMgz9peEZReVFbjHNlVywaPoJaCDAwR4snF0wYeVoGBUh5Il8kZHk1SnkyUwEyNwFIN3Ash3lCUnkEp3lPBHk8ToNRTHkDt3lTA8xEgmk2eTdBAEhIATdPczA4eVslYyZ5QQMCMnkTUhszeewI0dGhwo1BO0pgGbMozyIv4sYkmbMl3qU8ZB4wcRKlRYkjFx22AJjHTJiGeQIBACH5BAkPAHkALAAAAAAPAA8AAAeqgHmCg3lzhIeEQihRiINveW82TjBbZShJhCUQRiFleWosWDYohC1YaQ0+glULZIyDYU4veVoGBUh5DjOEUjYyUwEyNwFIN04kWHlEP4IEqnlPBII1SXAzToIDuHlTA3lSLV95TjVGN0EASEgBN0AQcIQUEXlBAwIyeSlE8INGajc5BLUBkQaCjkFpDhihAiNNnh4tmlDwNEhJjzllaMCoIQhPI0FOkow5FAgAIfkECQ8AeQAsAAABAA8ADgAAB6aAeYKDeB8XcIOJeW9meVtSTFJ5aW+JbycsMyhwczA2JDCVgmUteRY+gloLeSxkiToveVoGBUh5HjSDc1QkMlMBMjcBSDchUnN5Ck5peQSoeU8EeWpGCslbggO2eVMDeXBSDHlxUSg3QQBISAE3TyFOonk8KnlBAwIyeS8ziWb8NzcGoTpRZpAaGJkouOoBoyEzg2HyYFFS5koeMmoUDYLDIEHGRIEAACH5BAkPAHkALAAAAQAPAA4AAAewgHmCeXNeaYI6NniDg3hCFyxMSUIdZoyDP2l4RlF5UVuMc2VXLBo+gloIMDBHiycXTBh5WgYFSHkiXyRkeTVKeTJTATI3AUg3cCyHeUJSeQSneU8EeTxOg1FMeQO3eVMDzESCaTZ5N0EASEgBN09zMDh5WyVjJnlBAwIyeRNSGzN57AjR0aHCjUE7SmAZsyjPIi/ixiSZsyXepTxkHjBxEqVFiSMXHbYAmMdMmIZ5AgEAOw==") +} + +html .menu .menu_folder:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAPX19efn593d3dXV1dPT09HR0c/Pz83NzcvLy8nJycfHx8XFxcHBwb29vbm5ube3t7W1tbOzs7Gxsa+vr62traurq6mpqaenp6WlpaOjo6GhoQDZNp+fn52dnZubm5mZmZeXl5WVlZOTk4+Pj4WFhX5+fnx8fHp6enh4eHZ2dnJycnBwcGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFDgAsACwAAAIAEAAMAAAGYEAWCHEohFjIpJJVGJlKD0oGQ8VoVEkCdslFajEMiuVCJlskjwTz1OgmSQsWQhRxIysg1mLysbMcKywKCih2J3UsAgZ+Hh1IABJ+ECVIARwlmJmZJAxJHkQHoaKhA3ksQQAh+QQJDgAsACwAAAAAEAAOAAAGGUCWULgpGo/DpHLJbDqf0Kh0Sq1ar9isMAgAIfkECQ4ALAAsAAABABAADQAABmZAllC4KRqPQhDiUAgNn8/CyFR6UDKYLEajGhK60LDwi2FQLJd02iJ5JFiFU0M8JC1YCFGELqyAWAsTH3wsDissCgoofCd7LAIGhB4dQgAShBAlQgEcJZ6fnyQMQx5LB6eopwN/LEEAIfkECQ4ALAAsAAACABAADAAABmNAFghxKGyOyCSLVRiZSg9KBkPFaFRLFgGb7Xq1KgyDYrmYzRbJI8E8Nb5d0oKFEEXg2QqItZh88EsOKywKCiiAJ3csAgaALB4dSwASjhAlSwEcJZucnCQMWR5EB6SlpAN7LEEAIfkECQ4ALAAsAAACABAADAAABlxAFghxKIRYyKSSVRiZSpuodIokqJbYpBXDoFguYLBF8kgwT41skrRgIUQRNbICYi0mHznLsWIpFChyJ3EsAgZ6Hh1IABJ6ECVIARwllJWVJAxJHkQHnZ6dA3UsQQAh+QQFDgAsACwAAAIAEAAMAAAGZUAWCHEohFjIpJJVGJlKD0oGQ8VoVEkClrXper9ILYZBsVzOZ4vkkWCeGsslacFCiCJxZQXEWkw+eUkOKywKCiiBLCd4LAIGiSweHUgAEpAQJUgBHCWdnp4kDEkeRAemp6YDfCxBACH5BAkOACwALAAAAgAQAAwAAAYUQJZwSCwaj8ikcslsOp/QqHQqDAIAIfkECQ4ALAAsAAACABAADAAABlxAFghxKIRYyKSSVRiZSpuodIokqJbYpBXDoFguYLBF8kgwT41skrRgIUQRNbICYi0mHznLsWIpFChyJ3EsAgZ6Hh1IABJ6ECVIARwllJWVJAxJHkQHnZ6dA3UsQQAh+QQJDgAsACwAAAIAEAAMAAAGY0AWCHEobI7IJItVGJlKD0oGQ8VoVEsWAZvterUqDINiuZjNFskjwTw1vl3SgoUQReDZCoi1mHzwSw4rLAoKKIAndywCBoAsHh1LABKOECVLARwlm5ycJAxZHkQHpKWkA3ssQQAh+QQJDgAsACwAAAEAEAANAAAGZkCWULgpGo9CEOJQCA2fz8LIVHpQMpgsRqMaErrQsPCLYVAsl3TaInkkWIVTQzwkLVgIUYQurIBYCxMffCwOKywKCih8J3ssAgaEHh1CABKEECVCARwlnp+fJAxDHksHp6inA38sQQAh+QQJDgAsACwAAAAAEAAOAAAGZkCWULgpGo/DpHIJQhwKoaWyMDKVHpQMZovRqIaEr3QZxjAolotabZE8EqzCqTEWkhYshChSZ1VALAsTH30OKywKCih1J3wsAgZ9Hh1CABJ9ECVCARwlnp+fJAxDHk4Hp6inA4AsQQA7") +} + +html .menu .menu_exit:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAOPj4+Hh4dnZ2dXV1c3NzcvLy8fHx8PDw8HBwb+/v729vbu7u7W1ta2trampqaenp6Ojo6GhoZ2dnQzNOgjPOpeXlxq9Qhi9QI+Pj4uLi4mJiSatSCqpSjCjTDSfToGBgTadTjKfTjibUDqZUDqXUDyVUkCRVESNVnZ2dkyFWnR0dE6BWlB+XFB8XFR4Xlh2XlxwYF5uYmZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCAAyACwCAAEACwAOAAAGQMBGgUAsEgyoAEMSaTYlgwzgI6taZYkKQHO1ZlDb7jUsrpLLZ3G6ux5zxRgwVayoBBQPh17/EGQgCQiCgwcLKkEAIfkEBQgAMgAsBwAHAAEAAgAABgRA0ykIACH5BAUIADIALAgABwABAAIAAAYEwI4oCAAh+QQFCAAyACwJAAcAAgACAAAGBkCPhzQKAgAh+QQFCAAyACwLAAcAAQACAAAGBEDPKAgAIfkEBQgAMgAsDAAHAAIAAgAABgZA0KbECQIAIfkEBQgAMgAsDAAFAAIAAgAABgZAVypmCQIAIfkEBQgAMgAsDgAGAAIAAwAABgfAlYySmqyCACH5BAUIADIALAwACQADAAEAAAYFQNiFFQQAIfkEBQgAMgAsDAAKAAIAAQAABgTAFysIACH5BAkyADIALAIAAQAOAA4AAAYUQJlwSCwaj8ikcslsOp/QqHRqDAIAOw==") +} + +.pos-box { + height: 42px; + width: 100% +} + +.position { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKwwAACsMBNCkkqwAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFZSURBVDiNpdO9a1VBEAXw3zOioIUQEdFGkQdWVtr6D1ikERtRppLU6awUsUkh2CgoBNwhoPhsLESxERR7wcI2XSBNMAHRNFmLbB6Xa64RXFjYOXvmzMfOjmqt/mcd2I+QmUuZOcgbDWWQmTN4iFms4wGu4jC+RMT7wQwy8xBe4FtE3MBXPMdqE5sfLCEzj+Il3kXEE4iIJTzFFbzB9z0FMvMYJphExLPuXUQs4y1eYfmPHmTmbHN+FBGv92zMDm+ulXAzItZHtVaZeRH3sBgRn4ecOyKXcRt3dkuYwyJOZeaJfZyP42TjX1Nrne5SyqSUMu5ho559tpQy2bUP9gL8wHYnzbtYzczTuB8RHzs8/H0Sz+ETbuEDxg3f7pL6AhW/2vknNiNiCxvY6uDT8e2XMINLmbmC89js3I0z8wLOdP36Aht43KIcwULD1+w82/WGT+dk8DP96/oNlqecb6uu8YEAAAAASUVORK5CYII="); + background-position: 10px 10px; + background-repeat: no-repeat; + background-size: 22px auto; + line-height: 42px; + padding-left: 30px +} + +.search { + width: 306px +} + +.ser-text { + border: #20a53a 1px solid; + height: 30px; + width: 262px; + padding: 0 8px; + margin-top: 6px +} + +.ser-sub { + width: 38px; + height: 30px; + border: 0; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAxAAAAMQBz4pYTAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAE2SURBVDiNndJBS1VRFMXx9UzNQUQD0caFk6YhEUFCOPETiJQfIygQbNQgCCeOGjYLGjRJkPoKoSGIRKADs1nQoEIEfw3cry6v+3oPN2z2ufuu8z/7nruC9ORNvMRXHOIFbrTogjQfOniKX87iR2P9HSv/A1zA+xK/wRwmMYV5vKt3r/sBnpTgcb9R8aw0D3sB1/GzTum3uZsfSnupCVgr8vwQgKXSLnR7I0nuJJFkN4PjY9WZbmMkyXGtx4YAjFY9aQI2knSS3B4CMFd1508HV3CE3QHfP17G2sdo7298UJfzChMtmy9jszTLbT4InpfgCx6Vme5hFd/8jW1cbQME93Hg39jDYmOKT5hGOmi7rNkk15KcJvmcZKv6E0k2k9xNsp1keZBx2vIi3tYkG+cBxJmV13HrN6Szg7ZCfX3NAAAAAElFTkSuQmCC") no-repeat center #20a53a; + cursor: pointer; + margin-right: 2px; + margin-top: 6px +} + +.title { + height: 50px; + line-height: 50px; + border-bottom: 1px solid #ddd +} + +.title h3 { + display: inline-block; + line-height: 50px; + margin-right: 12px +} + +.divtable .table { + border: 1px solid #ddd; + color: #666; + font-size: 12px; + margin-bottom: 0 +} + +.divtable .table thead th { + background-color: #f5f6fa; + border-bottom: 1px solid #e1e6eb; + color: #999; + font-weight: normal; + padding: 8px +} + +.table .btlinkbed { + color: #666; + padding: 1px 5px; + border: 1px solid transparent; + white-space: nowrap; + overflow: hidden; + float: left; + display: block +} + +.table .btlinkbed:hover { + border: #ddd 1px solid +} + +.dataTables_paginate { + display: block; + height: 30px; + margin-bottom: 10px; + margin-top: 15px +} + +.page { + line-height: 16px; + text-align: right; + margin-top: 5px; + margin-right: 1px; + height: 30px +} + +.page div { + float: right +} + +.page span, +.page a { + display: inline-block; + height: 28px; + padding: 0 11px; + border-right: #ddd solid 1px; + border-top: #ddd solid 1px; + border-bottom: #ddd solid 1px; + border-left: #ddd solid 1px; + line-height: 28px; + font-family: "Arial"; + font-size: 13px; + color: #20a53a; + float: left; + margin-left: -1px +} + +.page a.prev { + border-left: #ddd solid 1px +} + +.page a:hover { + background: #e8e8e8; + color: #222; + text-shadow: 0 1px 0 #fff; + cursor: pointer +} + +.page spanold { + font-size: 12px; + padding: 4px 10px !important; + border-style: solid; + border-width: 1px; + border-color: #ddd #ddd #ccc; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + display: inline-block; + line-height: 16px; + background: #f5f5f5; + color: #333; + text-shadow: 0 1px 0 #fff +} + +.page .Pcurrent { + color: #999; + background: #f5f5f5; + cursor: default +} + +.page .Pcount { + color: #777 +} + +.system-info-con { + width: 100% +} + +.sys-i-c-box { + box-sizing: border-box; + max-width: 350px +} + +.sys-i-c-box .siteinfo-box { + height: 110px; + position: relative; + width: 100% +} + +.sys-i-c-box .siteinfo-box h3 { + background: #20a53a; + color: #fff; + font-style: normal; + height: 110px; + left: 0; + line-height: 110px; + position: absolute; + text-align: center; + top: 0; + width: 56px +} + +.siteinfo-box-right { + border-bottom: 1px solid #ddd; + border-right: 1px solid #ddd; + border-top: 1px solid #ddd; + height: 110px; + margin-left: 56px; + overflow: hidden +} + +.siteinfo-box-right h4 { + font-size: 32px; + height: 68px; + line-height: 60px; + overflow: hidden; + padding-top: 8px; + text-align: center +} + +.siteinfo-box-right .sbr-btn { + color: #ccc; + display: block; + font-size: 14px; + height: 27px; + line-height: 22px; + padding-top: 5px; + text-align: center +} + +.siteinfo-box-right .sbr-btn a { + color: #20a53a; + font-size: 16px; + padding: 0 10px +} + +.circle-box h3, +.circle-box h4 { + line-height: 30px +} + +.circle { + width: 100px; + height: 100px; + position: relative; + border-radius: 50%; + background: #20a53a; + margin: 0 auto +} + +.pie_left, +.pie_right { + width: 100px; + height: 100px; + position: absolute; + top: 0; + left: 0 +} + +.left, +.right { + width: 100px; + height: 100px; + background: #ccc; + border-radius: 50%; + position: absolute; + top: 0; + left: 0 +} + +.pie_right, +.right { + clip: rect(0, auto, auto, 50px); + transition: transform 1s ease-in 0s; + -webkit-transition: -webkit-transform 1s ease-in 0s; + -moz-transition: -moz-transform 1s ease-in 0s +} + +.pie_left, +.left { + clip: rect(0, 50px, auto, 0); + transition: transform .4s ease-in 1s; + -webkit-transition: -webkit-transform .4s ease-in 1s; + -moz-transition: -moz-transform .4s ease-in 1s +} + +.mask { + width: 88px; + height: 88px; + border-radius: 50%; + left: 6px; + top: 6px; + background: #FFF; + position: absolute; + line-height: 88px; + font-size: 18px; + color: #20a53a +} + +@-webkit-keyframes shineGreen { + from { + -webkit-box-shadow: 0 0 10px #999 + } + + 50% { + -webkit-box-shadow: 0 0 15px #20a53a + } + + to { + -webkit-box-shadow: 0 0 10px #999 + } +} + +.shine_green { + -webkit-animation-name: shineGreen; + -webkit-animation-duration: 3s; + -webkit-animation-iteration-count: infinite +} + +.mem-release { + cursor: pointer +} + +.mem-re-min { + background: url(../img/ico/rocket_min.png) no-repeat center center; + height: 35px; + left: 40px; + position: absolute; + top: 8px; + width: 20px; + z-index: 9; + opacity: .9 +} + +.mem-re-con { + background: url(../img/ico/ico-rocket.gif) no-repeat center center; + width: 100%; + height: 100%; + position: absolute; + z-index: 9; + display: none; + top: 15px +} + +.mem-release:hover .mem-re-con { + display: block +} + +.soft-man .col-lg-3 { + border-bottom: 1px solid #ececfb; + border-left: 1px solid #ececfb; + border-right: 1px solid #ececfb; + margin-right: -1px; + margin-bottom: -1px; + height: 148px; + cursor: pointer; + padding: 0 +} + +.soft-man .col-lg-3:hover { + box-shadow: 0 0 38px rgba(0, 0, 0, .08) inset; + -webkit-transition: all .25s ease; + transition: all .25s ease +} + +.soft-man .dashed-border { + border: 1px dashed #20a53a; + z-index: 1 +} + +.soft-man .no-bg:hover { + box-shadow: 0 0 38px rgba(0, 0, 0, .01) inset; + -webkit-transition: all .15s ease; + transition: all .15s ease +} + +.soft-man .image { + height: 40px; + margin: 30px 0 20px; + text-align: center +} + +.soft-man .sname { + text-align: center; + color: #555 +} + +.soft-man .col-sm-3>div { + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 0 +} + +.soft-man .spanmove { + background: url(../images/move.png) no-repeat; + width: 30px; + height: 30px; + display: none; + position: absolute; + top: 5px; + right: 5px; + z-index: 3 +} + +.soft-man .col-lg-3:hover .spanmove { + display: block +} + +.soft-man .text { + background-color: rgba(0, 0, 0, 0.3); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + display: none +} + +.soft-man .text a { + background-color: #fff; + border-radius: 4px; + display: block; + height: 30px; + line-height: 30px; + margin: 59px auto; + text-align: center; + width: 100px +} + +.soft-man-con p.status { + line-height: 40px; + margin-bottom: 20px; + font-size: 14px +} + +.soft-man-con .sfm-opt .btn { + margin-right: 15px +} + +.soft-man .sname .glyphicon-True { + color: #20a53a; + margin-left: 10px +} + +.soft-man .sname .glyphicon-False { + color: red; + margin-left: 10px +} + +.soft-man { + position: relative +} + +.soft-man .rowbg { + position: absolute; + top: 0; + width: 100%; + z-index: 0 +} + +.soft-man .rowbg .col-lg-3 { + cursor: default +} + +.soft-man .rowbg .col-lg-3:hover { + box-shadow: 0 0 38px rgba(0, 0, 0, .01) inset; + -webkit-transition: all .15s ease; + transition: all .15s ease +} + +.bw-info ul li { + height: 56px; + line-height: 56px +} + +.bw-info ul li.bi-line { + border-bottom: #ddd solid 1px +} + +.bw-info ul li span { + display: block; + margin-right: 15px +} + +.btn-zhm { + border-radius: 0 3px 0 0; + display: none; + position: absolute; + right: 42px; + top: 0 +} + +.line { + padding: 5px 0 +} + +.line .span_tit { + display: inline-block; + text-align: right; + width: 140px +} + +.line .tname { + display: block; + float: left; + height: 30px; + line-height: 30px; + overflow: hidden; + padding-right: 20px; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + width: 110px +} + +.line .info-r { + margin-bottom: 5px; + margin-left: 110px; + position: relative +} + +.placeholder { + cursor: text; + left: 20px; + line-height: 24px; + position: absolute; + top: 27px +} + +.bt-form-submit-btn { + background: #f6f8f8; + border-top: 1px solid #edf1f2; + bottom: 0; + left: 0; + padding: 8px 20px 10px; + position: absolute; + text-align: right; + width: 100% +} + +.bt-form-submit-btn .btn:first-child { + margin-right: 4px +} + +.btn-danger { + background-color: #cbcbcb; + border-color: #cbcbcb; + color: #fff +} + +.bt-w-main { + height: 525px +} + +.bt-form { + height: 100% +} + +.bt-w-menu { + float: left; + background-color: #f0f0f1; + height: 100%; + width: 110px +} + +.bt-w-menu p { + cursor: pointer; + height: 40px; + line-height: 40px; + padding-left: 20px; + position: relative; + text-overflow: ellipsis; + overflow: hidden +} + +.bt-w-menu p a { + display: block +} + +.bt-w-menu p .spanmove { + display: none +} + +.bt-w-con { + margin-left: 110px; + position: relative +} + +.label-input-group input { + margin-top: 0; + margin-right: 5px; + vertical-align: -2px +} + +.label-input-group label { + margin-bottom: 0; + font-weight: normal +} + +.bingfa .line { + margin-bottom: 10px +} + +.bingfa .line .span_tit { + text-align: right; + width: 120px; + display: inline-block +} + +.bingfa .bt-input-text { + width: 100px +} + +.ssl-con-key { + width: 47% +} + +.ssl-con-key textarea { + height: 240px; + line-height: 18px; + width: 100%; + margin-top: 5px +} + +.ssh-item { + float: left; + margin-left: 10px; + padding-top: 5px +} + +.ss-text em { + color: #555; + float: left; + font-style: normal; + line-height: 32px; + padding-right: 2px +} + +.view1, +.view2 { + margin-bottom: 15px +} + +.searcTime { + position: relative:z-index:999; + margin-top: 11px +} + +.searcTime .tit { + float: left; + padding: 5px 10px; + display: none +} + +.searcTime .gt { + padding: 5px 10px; + border: #ddd 1px solid; + margin-right: -1px; + cursor: pointer; + float: left; + line-height: 16px +} + +.searcTime .gt.on, +.searcTime .gt:hover, +.searcTime .ss .st:hover, +.searcTime .ss .st.on { + background-color: #20a53a; + color: #fff +} + +.searcTime .ss { + display: inline-block; + position: relative; + float: left; + line-height: 16px +} + +.searcTime .ss .st { + padding: 5px 10px; + border: #ddd 1px solid; + margin-right: -1px; + cursor: pointer; + float: left +} + +.searcTime .ss .time { + background-color: #fff; + border: 1px solid #ddd; + display: none; + padding: 10px; + position: absolute; + right: -1px; + top: 27px; + width: 217px; + z-index: 99 +} + +.searcTime .ss .time span { + margin-bottom: 10px; + display: block +} + +.searcTime .ss .time input { + border: 1px solid #ddd; + height: 22px; + padding: 0 5px; + width: 132px +} + +.searcTime .ss .time input:focus { + border: #66afe9 1px solid; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +.searcTime .ss .sbtn { + background-color: #20a53a; + border: 1px solid #20a53a; + color: #fff; + line-height: 24px; + text-align: center; + cursor: pointer +} + +.plan { + padding: 10px 20px; + margin-bottom: 10px +} + +.plan .typename { + line-height: 34px; + width: 120px +} + +.plan .textname { + height: 34px; + line-height: 34px; + float: left +} + +.plan .planname input { + height: 34px; + width: 260px; + padding: 0 12px +} + +.plan .dropdown button b { + font-weight: normal +} + +.plan .dropdown-menu { + min-width: 100px +} + +.dropdown-menu>li>a { + padding: 3px 12px +} + +.plan-submit { + margin-left: 141px +} + +.plan_hms { + position: relative; + height: 34px +} + +.plan_hms span { + float: left; + height: 32px; + line-height: 32px; + position: relative +} + +.plan_hms .name { + border-left: 1px solid #ccc; + width: 44px; + text-align: center; + background-color: #f6f6f6 +} + +.planSign { + margin-left: 10px; + height: 35px; + line-height: 35px +} + +.planSign i { + margin-right: 8px; + font-size: 12px; + font-style: initial; + color: red +} + +.plan_hms span input { + float: left; + width: 48px; + height: 32px; + line-height: 32px; + border: 0; + text-align: center; + font-size: 12px +} + +.plan_hms span:first-child input { + border-radius: 4px 0 0 4px +} + +.plan_hms span:last-child { + border-radius: 0 4px 4px 0 +} + +.plan .dropdown button { + width: 94px +} + +.txtsjs { + width: 260px; + height: 80px +} + +.sl-s-info { + width: 124px; + border: #ccc 1px solid +} + +.rec-install { + padding: 16px; + width: 100%; + float: left +} + +.rec-install .rec-box { + width: 296px; + float: left +} + +.rec-install h3 { + font-size: 20px; + margin-bottom: 5px +} + +.rec-box-con { + border: #ddd 1px solid; + border-radius: 3px; + padding: 10px +} + +.rec-install .rec-box:hover .rec-box-con { + background-color: #f9f9f9; + border: #20a53a 1px solid +} + +.rec-box-con .ico img { + width: 22px; + margin-right: 10px +} + +.rec-box-con ul li { + line-height: 26px; + margin-bottom: 5px +} + +.fangshi { + line-height: 19px; + margin-top: 10px; + display: block; + margin-bottom: 15px +} + +.fangshi label { + font-weight: normal; + margin-right: 44px; + float: right +} + +.fangshi label input { + vertical-align: -2px; + margin-left: 5px +} + +.zun-form-new .fangshi { + padding: 0 30px 0; + line-height: 30px +} + +.zun-form-new .fangshi label { + height: 20px; + line-height: 20px; + margin: 4px 20px 0 0 +} + +.zun-form-new .version { + padding: 0 30px +} + +.zun-form-new .version select { + height: 28px; + margin-left: 30px; + width: 120px +} + +.sec-install-btn { + font-family: "宋体"; + width: 90px; + height: 30px; + line-height: 30px; + text-align: center; + background-color: #20a53a; + border-radius: 3px; + color: #fff; + cursor: pointer; + margin-top: 10px +} + +.rec-box-con .onekey { + font-family: "宋体"; + width: 90px; + height: 30px; + line-height: 30px; + text-align: center; + background-color: #20a53a; + border-radius: 3px; + color: #fff; + margin: 5px auto; + cursor: pointer +} + +.rec-box-con .onekey:hover, +.sec-install-btn:hover { + background: #10952a; + background: linear-gradient(#10952a, #088d22) +} + +.phppz { + margin: 14px 0 +} + +.phppz span { + padding: 10px +} + +.phppz textarea { + width: 500px; + margin: 0; + vertical-align: bottom; + padding: 0 5px; + height: 38px; + margin-right: 20px +} + +.soft-man-con .user_pw { + display: none; + margin-top: 30px +} + +.soft-man-con .user_pw_tit { + margin-top: 30px; + padding-top: 30px; + border-top: #ccc 1px dashed +} + +.soft-man-con .user_pw p { + margin-bottom: 15px +} + +.soft-man-con .user_pw_tit .tit { + float: left; + line-height: 22px +} + +.soft-man-con .user_pw span, +.soft-man-con .user_pw_tit span { + width: 50px; + display: inline-block +} + +.soft-man-con .user_pw span { + margin-right: 10px +} + +.btswitch-p { + margin-left: 10px +} + +.soft-man-con .user_pw input { + width: 200px +} + +.soft-man-con .conf_p { + margin-bottom: 10px +} + +.soft-man-con .conf_p input { + width: 100px +} + +.soft-man-con .funarea { + width: 100%; + height: 80px; + line-height: 22px; + margin-bottom: 10px +} + +.soft-man-con .ver .btn { + vertical-align: 0; + margin-left: 10px +} + +.webEdit-box .soft-man-con .phpmyadmindk { + width: 100px; + display: inline-block; + margin: 0; + padding-right: 0 +} + +.softlist td img { + width: 24px; + height: 20px; + margin-right: 5px +} + +.softlist .glyphicon-folder-open { + cursor: pointer; + color: #efd566 +} + +.softlist .btswitch+.btswitch-btn { + width: 2.4em; + height: 1.4em; + margin-bottom: 0 +} + +.wafConf_checkbox label { + font-weight: normal; + margin-right: 22px +} + +.wafConf button { + vertical-align: 0 +} + +.wafConf_cc { + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + margin: 10px 0; + padding: 15px 0 +} + +.wafConf_cc span { + margin-right: 5px +} + +.wafConf fieldset { + border: 1px solid #ccc; + border-radius: 3px; + float: left; + padding-bottom: 0; + width: 240px +} + +.wafConf fieldset:nth-of-type(2) { + margin: 0 10px +} + +.wafConf legend { + border: 0 none; + font-size: 14px; + margin: 0 6px; + padding: 3px; + width: auto +} + +.wafConf fieldset input { + margin-left: 4px +} + +.wafConf fieldset .table { + margin-top: -1px; + margin-bottom: 0 +} + +.wafConf fieldset .table tr td:nth-of-type(2) { + width: 42px +} + +.wafConf fieldset .table-overflow { + height: 210px; + overflow: auto; + margin-top: 10px; + border-top: #ddd 1px solid +} + +.wafConf-btn { + border-bottom: #ddd 1px solid; + margin-bottom: 12px; + padding-bottom: 15px; + height: 45px +} + +.wafConf-btn span { + float: left; + margin-right: 8px; + line-height: 33px +} + +.wafConf-btn .btn { + margin-right: 10px +} + +.wafConf-btn .ssh-item { + margin-right: 50px +} + +.gzEdit { + padding: 13px 20px +} + +.gzEdit button { + margin: 8px; + width: 140px +} + +.setting-con p { + line-height: 30px +} + +.setting-con p .set-tit { + display: inline-block; + height: 22px; + margin-right: 10px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: -2px; + width: 90px +} + +.setting-con p .disable { + background-color: #ededed +} + +.setting-con p .inputtxt { + width: 260px +} + +.setting-con p .modify { + margin-left: -40px; + vertical-align: 0; + position: relative; + z-index: 10 +} + +.setting-con p .set-info { + margin-left: 20px +} + +.set-submit { + margin: 20px 0 10px 100px +} + +.changepath { + height: 500px +} + +.changepath .path-top { + height: 50px; + line-height: 50px; + padding-left: 10px; + border-bottom: #aaa 1px solid +} + +.changepath .path-top .btn { + margin-right: 10px +} + +.changepath .path-top .btn span { + -moz-transform: scaleX(-1); + -webkit-transform: scaleX(-1); + -o-transform: scaleX(-1); + transform: scaleX(-1); +} + +.changepath .path-top .place { + display: inline-block +} + +.changepath .path-top .place span { + color: #444; + font-size: 12px; + font-weight: bold +} + +.changepath .path-con-left { + width: 130px; + height: 450px; + float: left; + border-right: #aaa 1px solid; + padding-top: 5px +} + +.changepath .path-con-left dl dt { + background: url("../img/ico-computer.png") no-repeat left center; + height: 30px; + line-height: 30px; + padding-left: 23px; + font-size: 14px; + font-weight: normal; + margin-left: 10px +} + +.changepath .path-con-left dl dd { + line-height: 30px; + padding-left: 12px; + cursor: pointer +} + +.changepath .path-con-left dl dd span { + color: #666 +} + +.changepath .path-con-right { + float: left; + height: 450px; + overflow: auto; + width: 520px +} + +.changepath .path-con-right .default li { + width: 25%; + float: left; + text-align: center; + margin-top: 20px; + cursor: pointer +} + +.changepath .path-con-right .default li span { + font-size: 40px; + color: #666 +} + +.path-con-right .file-list { + display: none +} + +.path-con-right .list-list td { + padding-left: 10px; + cursor: pointer +} + +.list-list span.glyphicon { + color: #666; + margin-right: 10px; + font-size: 18px +} + +.list-list span.glyphicon-folder-open { + color: #edca5c +} + +.list-list span.glyphicon-cloud-download { + font-size: 12px +} + +.newFolderName { + border: #ccc 1px solid; + height: 30px; + padding: 0 5px; + width: 90% +} + +.path-con-right .list-list td .delfile-btn { + display: none; + float: right; + font-size: 14px; + text-align: center; + width: 18px +} + +.path-con-right .list-list td .delfile-btn:hover { + color: red +} + +.path-con-right .list-list tr:hover .delfile-btn { + display: block +} + +.path-con-right .list-list td .btn-xs { + margin-top: 4px +} + +.getfile-btn { + background: #f6f8f8 none repeat scroll 0 0; + border-top: 1px solid #edf1f2; + padding: 8px 20px 10px; + text-align: right; + width: 100% +} + +.success-msg { + width: 100%; + padding: 30px 50px; + font-family: "Microsoft Yahei"; + float: left; + position: relative +} + +.success-msg .pic { + position: absolute; + top: 50%; + margin-top: -100px +} + +.success-msg .pic img { + width: 158px +} + +.success-msg .suc-con { + float: right; + width: 260px +} + +.success-msg .suc-con h3 { + font-size: 16px; + margin-bottom: 15px +} + +.success-msg .suc-con p { + line-height: 23px +} + +.success-msg .suc-con .p1 { + border-bottom: 1px dashed #aaa; + color: #000; + height: 30px; + margin-bottom: 8px +} + +.success-msg .bottom-btn { + margin-top: 60px; + width: 100%; + float: left +} + +.success-msg .bottom-btn a { + width: 200px; + height: 40px; + line-height: 40px; + text-align: center; + display: inline-block; + border-radius: 5px; + margin-left: 30px; + color: #fff; + font-size: 16px; + cursor: pointer +} + +.success-msg .bottom-btn .close-btn { + background-color: #cbcbcb +} + +.success-msg .bottom-btn .blue-btn { + background-color: #5cb85c +} + +.tasklist { + padding: 15px +} + +.tab-nav { + border-bottom: #cacad9 1px solid +} + +.tab-nav span { + background-color: #ddd; + background: -moz-linear-gradient(top, #f6f6f6, #ddd); + background: -webkit-gradient(linear, 0% 0, 0% 100%, from(#f6f6f6), to(#ddd)); + background: -ms-linear-gradient(top, #f6f6f6, #ddd); + height: 32px; + line-height: 32px; + padding: 0 12px; + border: #cacad9 1px solid; + color: #444; + display: inline-block; + margin: 0 -1px -1px 0; + cursor: pointer +} + +.tab-nav .on { + background: #fff; + border-bottom: #fff 1px solid; + color: #444 +} + +.tab-con { + height: 380px; + margin-right: -15px; + overflow: auto; + padding: 10px +} + +.tab-con ul.cmdlist { + list-style-type: decimal +} + +.tab-con ul.cmdlist li { + position: relative; + list-style-type: decimal; + list-style-position: inside; + line-height: 40px; + border-bottom: #dbdbea 1px solid; + margin-top: 6px +} + +.tab-con ul.cmdlist li .com-progress, +.tab-con ul.cmdlist li .state, +.opencmd { + float: right; + margin-left: 20px; + color: #535362 +} + +.tab-con ul.cmdlist li .line-progress { + position: absolute; + bottom: -1px; + left: 0; + height: 1px; + background-color: #20a53a +} + +.tab-con ul.cmdlist li .cmd { + border: 0 none; + border-radius: 0; + display: block; + width: 570px; + height: 200px; + line-height: 22px; + padding: 0 10px; + background-color: #333; + color: #eee; + overflow: auto +} + +.yuandian { + width: 10px; + height: 10px; + border-radius: 5px; + background-color: #20a53a; + z-index: 999999 +} + +.DrawRecordL i, +.DrawRecordR-T i { + background: url(../img/Detailsbg.png) no-repeat +} + +.DrawRecordCon { + font-size: 14px; + height: 422px; + overflow: auto +} + +.DrawRecord { + background: url(../img/DrawRecordord.png) repeat-y -204px center; + margin: 0 auto; + overflow: hidden; + line-height: 22px +} + +.DrawRecordlist { + padding-top: 6px +} + +.DrawRecord .DrawRecordL { + float: left; + color: #9a9a9a; + margin-top: 9px; + padding-right: 22px; + position: relative; + text-align: right; + width: 110px +} + +.DrawRecord .DrawRecordL i { + background-position: 0 0; + display: inline-block; + height: 5px; + overflow: hidden; + position: absolute; + right: 0; + top: 9px; + width: 16px +} + +.DrawRecord .DrawRecordR { + margin: 1px 1px 30px 112px; + color: #666; + padding-right: 5px +} + +.DrawRecord .DrawRecordR h3 { + font-size: 14px; + font-weight: normal; + margin-bottom: 5px +} + +.footer { + bottom: 0; + position: absolute; + text-align: center; + width: 100%; + line-height: 50px +} + +.bingfa { + padding: 10px 0 60px 0 +} + +.bingfa p { + margin-bottom: 10px +} + +.bingfa p .span_tit { + display: inline-block; + text-align: right; + width: 120px +} + +.bingfa p input { + border: 1px solid #ccc; + height: 30px; + line-height: 30px; + padding-left: 5px; + border-radius: 3px; + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s +} + +.bingfa p input:focus, +.bingfa p input:active { + border: #66afe9 1px solid; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +.bingfa .submit-btn { + background: #f6f8f8 none repeat scroll 0 0; + border-top: 1px solid #edf1f2; + bottom: 0; + left: 0; + padding: 8px 20px 10px; + position: absolute; + text-align: right; + width: 100% +} + +.bingfa .submit-btn .btn-danger { + margin-right: 5px +} + +.transfer .backupdata { + height: 150px +} + +.selectdata .slabel { + font-weight: normal; + margin: 10px 41px 10px 0 +} + +.selectdata .slabel input { + margin-right: 5px; + margin-top: 0; + vertical-align: -2px +} + +.backupbtn { + margin: 20px 0; + width: 100% +} + +.neice_con { + padding: 7px 20px 20px +} + +.neice_con .tit { + font-weight: bold; + line-height: 30px; + color: #777 +} + +.neice_con .nc_con { + background-color: #fdfdfd; + border: 1px solid #ddd; + line-height: 28px; + padding: 10px; + border-radius: 3px +} + +.nc_opt { + padding-top: 6px; + text-align: right; + height: 24px +} + +.neice_con .nc_opt input { + margin-right: 6px; + vertical-align: -2px +} + +.neice_con .nc_opt label { + float: right; + width: 150px +} + +.nc_con_user p { + margin: 15px 0 +} + +.nc_con p span { + width: 80px; + display: inline-block +} + +.nc_con p input { + background-color: #fff; + border: #ccc 1px solid; + height: 30px; + line-height: 30px; + padding: 0 5px; + width: 280px +} + +.nc_con p input.disabled { + background-color: #eee +} + +.nc-tips { + padding: 20px +} + +.nc-tips p { + line-height: 30px +} + +.nc-tips p span { + font-size: 14px; + font-weight: bold +} + +.nc-tips p a { + color: #20a53a +} + +#BarTools .glyphicon { + color: #666 +} + +#BarTools .glyphicon:hover { + color: #fff +} + +.list-list .ico { + background-position: center center; + background-repeat: no-repeat; + display: inline-block; + height: 30px; + margin-right: 10px; + width: 33px; + z-index: 1; + float: left +} + +.fileList .ico { + background-position: center center; + background-repeat: no-repeat; + display: block; + height: 80px; + margin: 12px auto 4px; + width: 84px; + z-index: 1 +} + +.list-list .column-name .text { + color: #595c5f; + display: inline-block; + max-width: 85%; + overflow: hidden; + text-overflow: ellipsis; + line-height: 30px +} + +.list-list .column-name .cursor { + display: inline-block; + cursor: pointer; + width: 200px\9 +} + +.table>tbody>tr>td { + vertical-align: middle +} + +.showpicdiv { + max-height: 500px; + overflow: hidden; + width: 500px +} + +#BarTools { + font-size: 16px; + float: left; + margin-right: 20px +} + +#BarTools>button { + top: 0 +} + +.comlist { + float: left; + line-height: 28px; + margin-right: 10px +} + +.comlist>span { + margin-right: 20px; + font-size: 14px; + float: left; + padding: 0 10px; + border: #fff 1px solid; + border-radius: 3px; + cursor: pointer +} + +.comlist>span:hover { + border: #ccc 1px solid; + border-radius: 3px; + background-color: #f3f3f3 +} + +.PathPlaceText { + position: absolute; + right: 0; + top: 0; + background-color: #fff +} + +.PathPlace { + color: #666; + float: left; + font-size: 16px; + line-height: 30px; + margin-right: 5px; + width: 170px\9; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.PathPlaceInfo { + font-size: 14px; + line-height: 30px; + float: left; + margin-right: 20px; + color: #999 +} + +.onPath { + float: left; + line-height: 30px; + font-size: 14px; + color: #666 +} + +.editmenu span { + display: none; + text-align: right; + width: 300px +} + +.table tr.hover, +.table tr.on, +.ui-selecting, +.ui-selected { + background-color: #f5f5f5 +} + +.table tr.on .editmenu span, +.table tr.ui-selected .editmenu span, +.table tr.ui-selecting .editmenu span, +.table tr:hover .editmenu span { + display: inline +} + +.ui-selectable-helper { + background: #6bb0c9; + border: 1px dotted #072246; + opacity: .25; + overflow: hidden; + position: absolute; + z-index: 99999 +} + +.list-list .ico-folder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAMpJREFUeNpi/P//P8NAAiaGAQYD7gAWdIFPpxIoMS8ZiOcQqfY5SD0LFT1jAsRTuaU0GZjZuAgq/vPto+S3V3fmUssBokC8llNEjp2ZFWjk/1+Eg56TE0RJsgCD3BPImAvikGs7IzMLAxuvKAMrNz9ZaWAul5iKJAsXPwUBAMzK//9AaDIcIAkODiKCbbQcGHXAqANGHTDqgFEHjDpg1AGjDqCVA57/+f6Z7hZD7XwBahOmfHv1ANQqlqCzG54CcRrjaOd0oB0AEGAAscwsxMSUtNsAAAAASUVORK5CYII=") +} + +.list-list .ico-folder-unempty { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQRJREFUeNpi/P//P8NAAiaGAQYD7gAWcjR9OpWASyoZiOcQacxzkHoWKnrGBIincktpMjCzcRFU/OfbR8lvr+7MZSQnEbIzosbc65NxokDqDKeInBwrNz/xIfnwMgPjx5PxnkD2XCCWJNfrjMwsDGy8ogzs/CKkRSXQAaAomMslpiLJwsVPQegDQ/H/HwhNRiKUZOHkZPjx4xvD799/6ZLyWVmZGTjYWVBzAchybsUKujjg6/0OuAMGvBwAJcL/A14Q8SkYD4jlnx6chaaB/79GK6NRB4w6YNQBow4YdcDIdsDfP98/0d1iqJ1/QdVx47dXDxsGIDRADdBGxtHO6UA7ACDAAIASR69Q0kB9AAAAAElFTkSuQmCC") +} + +.list-list .ico-access, +.list-list .ico-mdb, +.list-list .ico-accdb, +.list-list .ico-db, +.list-list .ico-sql { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAwZJREFUeNrMV0trE1EU/jKJkzTTR4oJCX1lqq6q+GwriiLarS4VfCwqFEFxKShYUKE+Kv4GcVkquBE3glq0Vqt9iGg3qfZhSVttU0tNJk2aGc+9dGSSJmlaJqkfHG7OvZOc757v3HMnFk3TsJGwGZ2hoaEjNNwnazQxxh6yT8aJurq69AQIHT5CSUmJKZEDgQAbHpOdTCWhQ0jxfZIkQVVVU4zB7/c/XyaxOxcCpkMUxUtEojsTCaEQhUYkmmVZHkhHwpbPwKWlpXodMJxaHgfJLOsiEB4eRqi7B+HvI1CjUUQnp/i8o7ICgihC2rYVmw8fgrNW5vMejwderzddYa49A7NvujHzsgvlBw+gnIJYHXbYfT6+Fg0GocbiiAx/w/jDR/CdOA5XYz1f04sxpz6QDTMvXqHy7Gk4t9SuWHNUVPDRKft5NqafPvtHYE2NKBvUpSVYbDaEet5B2CRCS5BvoRoWBKjxGKx2OxKLi7C73dDo2XV1wmxQxsZRVFPNLRsSigJlYsJ8Aq76fZjvH4CtrIzLYBGSTzDbdYSKMzo1BVdDg/kEfvf1Q758EbFfM5j/2M/TzqRgEiSUCEnggJNOgaO6CsGOTvhbzptLgOnPdil63NwyShAOr8iOSRLsJQkGYS2WYKX7wkFHUKCjqOu+OP0TSwsLiM+G1iRBzlTn3n+Aa38DP2oMf6ihzHa95sa0Z5CoNtgzc7295mdAl4Dt3klWcAk8TUcRaLvLOyHLgoVaLxs16nTK+A/empXRMd4n3E3HzCdQde4MIiOjCL3twWQfOwVx7rPdFvlrIFAjYneAfKGF+6YT4K2WAugXjVkoyPvAf00gSYJQ602EChH1SWfmGrDa8vqShETKTZkx2s57t/H52vUkXwebN/pG6N/R143PGn8vp1Ogk0hHJlc/dS1vRcgCZdp1pmyZSoAFMu40VxJJEgjFxQlNUay72u+sSOFqNbDa7tln9keYxUi6Y4z/jvvaH7TGvny9pYbDeekPgiSp4o7tN+qvXmlLS2Aj8FeAAQC23WzgeNtzYAAAAABJRU5ErkJggg==") +} + +.list-list .ico-c { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFuKz0K4QDTjbbc0N4qrvP7+p0yABWY5sKTqf7mmAHMuHVy5ATg9k5wJ+VoZ/n/79Z2JkZCQqyElNhKCOMB8Lwz+cDjAVYGw69f5P3ac/xIWMy4rLJPkW6MG/QDuaUeqXge6eAwQYABvvATpIEEu4AAAAAElFTkSuQmCC") +} + +.list-list .ico-cpp { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFuxZaFeIBpzttuYGSWIo1fef36RnQ5ChyIYh85EtxidG1TRAyFCy0wA5ABbk6CFErENZyLUUPbjJDSEUB/CzMvz79O8/EyMjI4aBhBIXMQDUEeZjYUBppTIi947LdlypP/X+f92nP7QpH4Ae/GsqwNjc5aHTiNUBAwEAAgwALWAgpic4qfUAAAAASUVORK5CYII=") +} + +.list-list .ico-cs { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoFJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFvxZaFeIBpzttuYGhjiyGNbq+89v8rMhyBJ0S0F8ZHF0NTRNAzCLYI4gJgRolgiRHUE3B8AsRPY9qY5gRO6cOq64/PfTPxYmRkZGmiRCkF28jH/+HYjUZcaaCE0FGJtOvf9T9+kP7pBxWXGZJHFkwM/K8BdoRzPOEBgIABBgAMrJHxYdaBS2AAAAAElFTkSuQmCC") +} + +.list-list .ico-fla, +.list-list .ico-flv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYFJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDkV3BAwwofEluLm5Gf79+0cVDALy8vK7oI4wIMYBVAdsbGxZQEccweUIJnokNKAjEhQUFM5hcwQLLS3m4+ODpQMQCIPS54GYkS4OEBUVZRAXF8eWMMkLgZf1fQx/37yD85lFhBjEG4sYPizfCOYLRPpj6IElRqLKAWIAyBIuG1OqhRITwwCDAXcAyVEAim9YnLNrKDMI5ybS1wGjaWDA0wAu8O3IaTCGAVDaAKURQoARuUECbA/8V1ZWpqmP7969C2oPMI6mgVEHDM5syNI+k+EhkXn0DyUWLp6I2w5cliq1laDw71X1gMVANDa12MSxOZykggiXoXRLAyCfoYcELt8TUkfTECDWcqo3StHjHl9awB4F3Fx//5NhKbE+/g+1A2dldGPCrBqm6/caGb59Z6JJNuTi/PdPU6leoyCtBasDBgIABBgAWP2ttPWI30cAAAAASUVORK5CYII=") +} + +.list-list .ico-css { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAALdSURBVFiF7ZfLTxNRFMZ/M/RBnwik0FYpoJIYhJioLFSIupCoWzVqotGNCeEPcGPiI+50Q9xAYli5MaEbFyTiI0EWhhghGkohlgVREVzQhpfals64qB067ZRpoaALv6Rp73dPzvnuud/cOxVkWeZvwpA+CAaDZ4BewFPEGoeBkXSisbFRWwDQ6/V6PTabrSiVQ6EQQB9woaGhYUQrRswYeywWC5IkFeUD4PP5BoC+UCh0KB8BRYfZbO6ora19A/i1RGy5AACTyXS9rq5uWEtEpgeKCqfTmfIBwKU/3+8BYVsEuFwuqqurVVyaoK0XAChmzIVNe0CS4drrGeLSxg60ggRMRKJMLcRU3Mf5X+yvMGMUBaYWYkxEosUXEEvIdAfC9IxHKDeXqOYGPi9zqsYOQLm5hJ7xCN2BMLFEfh3RFRAIR+kcmqXKaqCr1U1l6ZqAuCQTDEc5UFkKQGVpCV2tbqqsBjqHZgmE9buha8Jbw9/pOeHFY80OfTv3kyNuC6KwxgnAud1OjrqtdAx+49lZ37r5dTtQYzfSP72kabKXX5Zp99mz+Lgk0z+9RI3dqJdeX8CjNg8Ok0jn0KzKYMtxibkfq+xxmlTxE5HkljlMIo/a9C9V3S0QBbi4t4xjbisPP8xzo7GcpgozgzMrHPdaVbGBcJTHwQh3W1zstOmvHgp4DHfZjXS1uql3JBO/+rqiuD+FekcyJt/iUOBJKAA2Y1JzV6s7az41Vwi25Tb8L+CfFqAy4ZWRBIx8UsaiIX835wNpNQ7A6NpLcfZToFX0xfl9yu92/+SGedFgVETkFKBVPDN5u3+yYD4XhPR/RgefjMmZHVgvQWqlmfO5eEhuw+jVZuX62pQJ2/2Tyqrz4bVQsIBU0sx91uNzQbUFJ5+OJRYlgygIgiqoWCaUZRmHsCoNXm5W3mpUAm4+D9x5F5FvL65uzflQZiTRskO4/+B00z1NAQqZ0YFiI73mb2NmSwowsJSgAAAAAElFTkSuQmCC") +} + +.list-list .ico-js { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAALFSURBVFiF7ZfLTxNRFIe/GTotffJKoa1SQCUx1cSFslBLdKFNdKtGFya6MSH8CybG6NJNwwYS496EblywwEeCLAwx1mgshVgWREVwQRteamk746K2dNopM5WCLvwlTXvPPTn3u+f+5lFBURT+pkylg3g8fgF4BHjruMYJIFoaCAQC2gDAI5/P57Xb7XVZOZFIAIwCV3p7e6NaOWLZ2Gu1WpFluS4fAL/fPw6MJhKJ40YA6i6LxTLQ1dX1EohoQew6AIDZbL7Z3d09pQVR7oG6yuVyFXwAcO339xtA2BMAt9tNR0eHKlYCtPsAQNGM1bRjD8gK3HixQEb+sxtaTQAzqTRzK5uq2PvlnxxptSCJAnMrm8yk0vUH2MwpDMeSjEynaLE0qObGP61zvtMBQIulgZHpFMOxJJs5Yx3RBYgl0wxOLtJuMxEOemhr3ALIyArxZJpjbY0AtDU2EA56aLeZGJxcJJbU74auCW9PfWPkrA+vrTL11dIPTnqsiMJWTAAuHXBxymNjYOIrTy76t62v24FOh8TY/JqmyZ59Xifkd1TEM7LC2PwanQ5Jr7w+wFC/F6dZZHByUWWw9YzM0vcsB11mVf5MKn9kTrPIUL/+Q1X3CEQBrh5q4rTHxoN3y9wKtHC01cLEwgZnfDZVbiyZ5mE8xd0+N/vs+ruHGi7D/Q6JcNBDjzNf+PmXjaL7C+px5nOMLg413gkFwC7lmcNBT8V8Ya4W7cnT8D/APw2gMuH1aA6iH4tj0WTczUYkZzMAvN16Ka68Cowu+vTyYUKR2eLvggoxLYkmqQhRFaBWlYJojfVUFw8Y7YCWdtyBWo+hXDV1oHSR8lgoMlvz7qGsA00S8qqsiIIgVMuvgAhFZg3vXlEUXCZUb6kqgL5m4d7rVPbOala7M+cef9AsXC1eriaJXF+zcL80Jhj5e75dR7aTkdq/AFCYGAYKWHWoAAAAAElFTkSuQmCC") +} + +.list-list .ico-htm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFtQstCtEg8FtzQ04GxsAycPkYGqR1SOL/fvzm7IGCbJjkA2maRoAWYbL97hCglgHMhHra2r4li65ABYKxDoYxQH8rMAmHg07qyCz+VgYUFqpjMgWlu24Un/q/f+6T39oUz4APfjXVICxuctDpxGrAwYCAAQYAPufFoHFXwuVAAAAAElFTkSuQmCC") +} + +.list-list .ico-html { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnpJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFkgW2hWiweC25gacjQ2A5GFyMLXI6mHyyHL//vwmLxsiOwbZQJqmAZBluHyPKyRIdSATIYOp6Vua5gJs8U2yA/hZgU08GnRWYVEJMpuPhQGllcqIbGHZjiv1p97/r/v0hzblA9CDf00FGJu7PHQasTpgIABAgAEAooAb7fMLYAoAAAAASUVORK5CYII=") +} + +.list-list .ico-java { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAvRJREFUeNrUV0toE1EUPfPJTJNp82lNY1PaBGwtpn6q9bNQQRFFpSAKgrgQu3PpXlBEBMGNWwU3LtwoxYULhSoKgkJbawVDta3WT5XWEPpL2iSTifeNjqY6TeNkavDA5c375N1z7z3v8cLlcjmUE2J+JxqNHqDmOlmdjT42k/XlD0QiEXMCzHkwGKxTFMUWz0NDQ6y5RXa0ubm5z2wN/1u/zul0QtM0W4yhsbHxPiNBZNqLIWAZI+PzpuOyLJ8KhUKP6fO2GQlbCLyPpfAhll50XpKkk+Fw+JkZCbFU56qWw72BKRxq9/0x53a7DR0wHPvR9pJxthCYSmbR1RPHnlY32GEei6cRrJZ+7u73+xEIBMyEWXoJBt4ncfXBOLavrkLv2wSVIIX6POcGzIRZMoGZ+Szu9MbRuasW/aMJ+BQB25oqLQViiYDIcwj7ZfirRF0D6az129QSAafEY+uqSnyZzKBjow89IwlEx+b+HQGGNfVOVDg4uJ0C2kIuVMr88hNgwnv+LoHYjKr3fYoIjlS3b71Hzwq7jNJqDk9ez+Bu/yS0IipT9DFkG8dnVYxPZ/DyYxJM0FnyUOHg4RA5NNRIlAkFEn3vaKnCtYcTiH5KYm2Dyx4CbOPddN5Hv6b0aAMex4J5Rkbgvx9ClURpCNW2DBiYoAw8fTMLmerPiLhkQXfICO7f4NXHWIlO7FwBUeDsIZBMaxj8PId6n6Srn1kheFyCnhHbCDyKTuMFXThNKyvgdYkUNdWdNmcOHMKvtCdSml4iNn94S/Xfv4gWw8E2L/au8+jpj02rmJrLIp5QkSFhZsixTOmXSYw1dDF1bPLqp8PSk6wQWKSsBMzsBI8yo+wEFpTgUjddXd3Dy+70RqRABiRZXmCXO1tt7f/fGrh4vAlnbg7rrYH8vjFfaH3JGSi0mZX14lLRFtM3oix2/aIZUCT2btSW/JFVsL1d5CN/jMv/d3yl69W5wQmcTaaXR5wUYLalFhdOH2k9b0qgHPgmwABqjFw0GGyiFAAAAABJRU5ErkJggg==") +} + +.list-list .ico-log { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkLQSevnqLU05aTBivXlhiJKocINcSSgATwwADiqOA0pAbjYLRKBiNgtEoGHXA4MoFE+fsooulM/twt4pBzScMDY3VmQz1rdMxxGAAWQ6XOAz8+vWL9GxIyEEwPi7xAUsDhCynqQNAvkeODro7gBjfY6QBLk62f79+/2MCAqw+ghkMi290y9DFsTVQQXYgizEi946nzllbf+f+y7pv33/RJGS4ONn/qiiKNWenBDdidcBAAIAAAwBHPa+btF94QQAAAABJRU5ErkJggg==") +} + +.list-list .ico-mht { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFtQstCtEA0y7rbmBVQzEhsnBxNEBst5/f37TtkGC7BhkiylKAzCf4jIYJIbL93RJhCAHEeNbihyAHt80yYb8rMAmHg07qyCz+VgYUFqpjMgWlu24Un/q/f+6T39oUz4APfjXVICxuctDpxGrAwYCAAQYAIH+FoEeflonAAAAAElFTkSuQmCC") +} + +.list-list .ico-php { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA1ZJREFUeNrsVktPU0EUPn3dPq73QmmbFlRaAw1Q5KUiYCIkmmjUGEMUdGWMSze6cmViTFwYVv4Aw8KNRhe6kA0SAonRGGNBHpWkBAXFIFAeLS307ZyB1ku90EJvdeOXTO7c6XS+b8755tyRxeNx+JdQCl9cLlcLeXSQdlRCjjrSBoUDDodDXADBUwsBx3GSMLvdbnw8J63NbrcPis2Rp7xbWJaFWCwmSUNYrdZuFEHE1GYiQHIwDHODiHizlQj53zAaEXHNZrM5xUQoc0nM83zCB4j2jecAabJdCVgLx8D5xQ9j06swOR+CGDnCkWgcWLUCrCY1VO3XQlWxDhTy9fVNJhOYzWYxY+48Akj8eniZkOjgdE0+mHgVMMp1In8wBnPeMJ3TM+KFC0f0YLdo6G8JM2ZUB7ZCn8tLCW6esYBG9ds27pk1sns5FOkZYEkEbKShmGfvPOBbjcKhA2zatdOacGI2SMIdhLZGwyZyBI5j+IVAQVebjfB+fIWK3lElFEM/2f3Zunzax0WnF0IgI5E/V6eHmaUwdA8tQa2N1A5S0Ycm/RAhET9RycOp6jzo/+yDSw0F2UVgZjkM5jwV7X+dC0JLBQ+lZg18J0ICJNwXGwzwaTJAhVXs1dL8D5H3YqMapkiEsk6B8GPl8UVAv0cJPxZDNNScVgFoeJVCRsONXvjmCdHxTJFWgJFTUUKaL0L08sMC5OmUEIrEwWpkqOlYzfoyvaNeGJ4KQH0JS71TSARl7YGTB3l45VyC9sYCKCvS0BQkaoKJV9LdN9k56B1ZhvOH9clj2eVchCvHDNlHoITku5Sc6SdvPVBWqE2O44nAhkUH09G8IQzT9Kh3Fo6XczQlWUcAga42kNx39s3SAlNtZYHTyJMEaEDfWhS6Bvy0QrbW66G8SLvzC8l2qLHqwLFPS3P8cWIF5slOkRhNiLnGyohV8nKTIVmKJRWAwHxjdcukwkl2CnKN/wI2eeBBD6l6PeM5J33s2MaEjFr9xx86rpcm+7c7x5NjqX2xeakIBYO7SwEumEqSzTxJ7oSZkkgiIEEmDK0wBdvNk0RApgtmOk/UAywD0XSXyGyAayOHcEwmvHA8fDF6Z+wn3AuEclMfdAzEys1w91Zr5X1RAf8CvwQYAB0rcvQMbg3OAAAAAElFTkSuQmCC") +} + +.list-list .ico-url { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAr5JREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUJLi/n4+GDpAATCoPR5IGakmgP+/P/LsP/FJYZzb+8wMDEyMchyizD4yVgwcLGwM4iKijKIi4tjS5jkh8DLHx8Ytj45xXDj4xOG59/fMfCwcDBYi2kxBMpZMajxSYPVgMRBDgABWGIkqhwgBO5+fs7QcWUVw4+/vxn42bgZtATkGAwElRjWPDzCsPf5RYZcTT8GIyFlBklOIfIKInwAZOnE6xsZ/gB9FKfszOAqaYgoWIAOmXlrG8P0m1sYuoyTGQTZeIh2ANG54Oirqwzvf31hiFC0R7EcnNe5xRhKtUMY/gGL9d3PzpNfFOMN/i8vGJR5JYHBfZhhyb19eNQ9J8kBRIeAOIcAgzA7Lzgq8IGfBOTJDgE9QUVwYmszTABnNWoBokPg+OvrYHrD42NY5S+9v88Qe6SH4dy7u7RxAMgCHQF5hlNvboFzw+sfH8Hiv/79AWbBC2AxDX5ZcDakSRR8+v2dockglmHu7Z0Mx4ChcebtbXBh8+3PT7A8yHHZGr6UNUjwATEOfgYWRmaGTHVvBlMRNYbTwJB4+u0tg7iAANByBQZHCT3KW0T4QISCHcPBl5eBiVGBwUBIicFEWJX6TTJ8ABS/IExtQJf2wKB2AEoUTHixg4HhBe0tnYXUKiYqBNqdMrDyQTQyxqWeau0BbKBy3wwUi5H5Qy8NkAOQg5tU3w+KKEBxADcT+9+v/34yY7OEVJ/C1KOrBdmBzGdE7h1PPbGm5vaPF43f/v2iSdrgYmL7p8ohUZ9tEdKC1QEDAQACDADzFSd59WqSKgAAAABJRU5ErkJggg==") +} + +.list-list .ico-xml { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57TwhAATCysK3hOhi5UPorHJIcujmwXCJEeB25obDLtCNMBsEA3i0z0NwByBbjk1HDc0cgHMdzDfEhM6ZDmAnxXYxEPrrCIbjssR+ByOrB5kNh8LA0orlRHZwrIdV+pPvf9f9+kPbaIG6MG/pgKMzV0eOo1YHTAQACDAAOGRHaFJlGXVAAAAAElFTkSuQmCC") +} + +.list-list .ico-ai { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAg9JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7DQ0mI+Pj5YOgCBCCh9BogZ6eIAAQEBsCMYGRnh+OHDh7SLgvdXNjK82N8K57OysjIwMzMTXw4QC96dX87w7elRBin3HgYmVg64uKCOP5D0R7WAhYXh79+/OM0iOQT+/f4BtpyJjZfh65MzROkBhQITExM4Cih2AMhSNkFVBl5Vb4bvz1DLljen5oCjARuApQGKHQCylFPKmIFTQofh1/vbDL8/vSBaL8UOAFkGspRbxoSBhVOAgUPMgOHbi8sUJVySHACyDGQpLOGxi2owfH2wj3q1IcH4B1r279dnhidbclCj5cU1YJRo0dYBIEtAAD3rgbLk9+cXyXYA0VEAsoRDVA/FchDglNQHZ0tQ9iQHMCK3iIANkv/Kyso0rZju3r0LapAw0rU2pFouGHUAzVvFLEfSGR4eoYOFWodxhwA7C3FYLWweXj4uPDzSgHzQPIaH65LA9IAlQmpYTlaTDOZ7XHzKQoCV798/GvZVwWaz8v7DWRfc2Fxbz/TmbB3D78+0SZxsfH//CRs1a/g2N2J1wEAAgAADAIk82p3e8s5qAAAAAElFTkSuQmCC") +} + +.list-list .ico-bmp { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmtJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7anvAKhs1Sx6biiIO46OLIetDVvPt708yygE0S5D5xOjDp4eoNAAyAIaRDUL2KSkOo1sIYIsmqldGsFDAZgkxDkVxABcT2z9gxcPEzMiEMyGSE8zIlRrIDpTGDXLveOqJNfW3f7yo+/bvF03KB24m9r8qHOLN2RYhjVgdMBAAIMAA9C4L2PHEWagAAAAASUVORK5CYII=") +} + +.list-list .ico-cdr { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmRJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7Cr/WPhHObj44H0OMGHFk8O3vT+ITIchAZEOQ+aSKUyUNEDKM5pURMQBblFEtBNDjGFcowTAx6lFCgIuJ7R+w4mFiZmSCG4bLR6T6FFapgexAadwg946nnlhTf/vHi7pv/37RpHzgZmL/q8Ih3pxtEdKI1QEDAQACDAD7sQauaYXLQgAAAABJRU5ErkJggg==") +} + +.list-list .ico-gif { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM77raBVTYiax6bChcDsZHlkOXRwbe/P8krCWEW4eLjs5QmaQCbZSBHoYcETeoCmCUDFgLkWERUCHAxsf0DVjxMzIxMWC0lNXixVWogO1AaN8i946kn1tTf/vGi7tu/XzQpH7iZ2P+qcIg3Z1uENGJ1wEAAgAADACWJ9+r79LDyAAAAAElFTkSuQmCC") +} + +.list-list .ico-ico { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAaVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLBSZ/Pc3w+/HVxhYZHUYGJlZMaT5+Phg6QAEIqD0GSBmpIoDfl7Zx/Dt4EIGLvt4BnZ9dwx5UVFRBnFxcWwJkzohwKpoxMD+9gmYxgVgiZGocoBUwMQnysDllEy9gogU8H5SFJzN5ZjEwK7rQp4nyHUAl1MKGFMKyHYAu44TGA+YA6gFRh0w4A5goTQLgsC3/fPAGAQE85bR3gGc1pEDGwIcxr6jiXD4OIARuVn+sECTLm10+QnX8TRIWNixa6rewvCw1QfOhjsaKoZPHAX8+UlZLkB2CDIflzjN0wAxlgzvXIAc7xSXhP85Bf4x/vrKxMDIhDfIsSU2XOIo4P8/oB38/3BmwzuzCutZ7h+vY/z+kSZR859L4O8fBYtmlbT+RqwOGAgAEGAAjiC9hXL1ZWMAAAAASUVORK5CYII=") +} + +.list-list .ico-jpeg { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnpJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7hoZa+0SG5oPzwTQMgPgwOWSATRwmBgLf/v6krCREtgDGRrYAXQ4bn+ppAGQBekhgczRV6gKYRciG4rMAm3qiQwCbr0AG4bOQFN9jhAAXE9s/YMXDxMzIRFT8YXMozIHYHA+q1EB2oDRukHvHU0+sqb/940Xdt3+/aFI+cDOx/1XhEG/OtghpxOqAgQAAAQYA3ogNywRusk8AAAAASUVORK5CYII=") +} + +.list-list .ico-jpg, +.list-list .ico-JPG { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAm1JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7/vaBVTZD07GpYBqeTYF8ZHls4jDw7e9P6pWEMAuQHYXuGGyOoFsaIGQ5xSEAC25sFuGTIysEkOMW2Ye4LCDG9xghwMXE9g9Y8TAxMzLhTYD4ghybQ5ErNZAdKI0b5N7x1BNr6m//eFH37d8vmqQNbib2vyoc4s3ZFiGNWB0wEAAgwADnlgGzkKaAXwAAAABJRU5ErkJggg==") +} + +.list-list .ico-png { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7anvAKhuRHY9NRRFD5qPLIcsjg29/f5JRDmCxCGvjBU2ekHqK0gDIYGTf4nM4xQURepBTUw8Tsb7FZhC+UCDWsSghwMXE9g9Y8TAxMzLRJFpAlRrIDpTGDXLveOqJNfW3f7yo+/bvF03KB24m9r8qHOLN2RYhjVgdMBAAIMAADIwGkBp21VIAAAAASUVORK5CYII=") +} + +.list-list .ico-psd { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkRJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7DQ0mI+Pj5YOgCBCCh9BogZ6eIAAQEBsCMYGRnh+OHDh/SLAlZWVgZmZmbiywFCYMKxewyvvv1CZHAJPoYgHSn8FrCwMPz9+5c6DgCBcC1JBn0pfoaXn38wLL/8jOHA3dcMDsqiePWAQgFU4mIrdclOA+K8HAyWMgIMl15+Ajvg5KN3DBtvvYLLt7looKgHxT9VQgAbAIUGyPIMYzkGOUEunOqwOYLsRPjo/TeGPfffMuiJ8zEIcLKCxd5//01ZZUQMWHntORiDgL+aGIO5nBA8bex/8JZhy+1XDC6KwnBxqjsAlgjRAUgMhC8++wh2oCQwjeCLDpqVAwpCXAzcrMwM3//8pU0I4EoPM84+gvPdgFGgLspLlF5G5LwJbJD8V1ZWpmnFdPfuXVCDhJGutSFd08CoAygqiGLOArPO2Vs0t/ScFp5syMTCiqFhmZ8anB216RaGOEwMWR26Whj49+c3eeUAsiUgNoxGFsPmQGyOoEoagDkCl0+pnghBlqH7CNkRNHcAyDJc8U+JI1DSAD8rw79P//4z4Wq9EBMFuBIsCICKfT4Whn8464KyHVfqT73/X/fpD23KB6AH/5oKMDZ3eeg0YnXAQACAAAMAI0n+E64by+4AAAAASUVORK5CYII=") +} + +.list-list .ico-webp { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAo1JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7Q5V9PEPbwYUYGmHitfaJKOLNB+eD5ZABTD+yWpC6b39/UqckBBmGDpAdjewJmFqQYyr3zSCcBkAaYT5CppEtABmGHhIgNTCMLQSpWheQEgIwh2LTg9MBsFBApqnpWKwO4GJi+weseJiYGQnnTvTEhRxd6KGBXKmB7EBp3CD3jqeeWFN/+8eLum//ftGkfOBmYv+rwiHenG0R0ojVAQMBAAIMAErgG/MeljhhAAAAAElFTkSuQmCC") +} + +.list-list .ico-ape { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwxElUOEAM+dpYTpY6/vJP0gohYQMhwYh1Jt1ww6oBB7QCiE+Gn/lqG/79+DZwDiLH887Q2hn+fP9ImChjZ2FBorIUOiZaTFAJ8hc2EfcPLD3YEiKa6A4gBvFlVo9lw6DmAEblZ/rwwhi5tdMn+JbgbJGyMjFg1Cdf0MbxtKULhwwBMHFkMWRwZ/ELrh1CUC5AthrGxWUpxGoBZgO5DXGqJUUdyCGAzFCaG7GtSQ4CFWN9j45NqGUEH/OPk/vfvxzcmSvImtsQJNx9UqQHtwJkNH8zur2e7f6OO8ftXmpQP/7i4//5W0GhWSC1sxOqAgQAAAQYADkDCgkQGXJUAAAAASUVORK5CYII=") +} + +.list-list .ico-avi { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAtpJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08IsLGz48SdqZpE8dHFkfHQTQPNcUoMtYvugWkYQObD5GmaCJEtp0kiJOR7bHxYKJDje4wQ4GYDtSH/0Sy+QWZzAe1AFmNE7h1PWHe1/sYrhrpvv2iTOIEe/KsuxtBcEKTdiNUBAwEAAgwAcCQxq0cx9U0AAAAASUVORK5CYII=") +} + +.list-list .ico-flv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAuNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08aYGNnx9DQHKeEwq9ddA8sBqKR1eDjw8Cvnz/JS4TYDKNrGgD5CD0ksIUKPt9TlA0HPASIdSQpvsdwADcbqA35j4Fa0YIOQGZzAe1AFmNE7h1PWHe1/sYrhrpvv2hTPgA9+FddjKG5IEi7EasDBgIABBgAhSU3wy5Zmw0AAAAASUVORK5CYII=") +} + +.list-list .ico-mkv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAq1JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYkayHfD333+GE4/fMVx88ZHhxecfYDEJXg4GfQl+BgtZIQZmJrjZDKKiogzi4uLYEiZ5IfDpx2+GBeceMvBzsDK4q4ozyPJzgsUff/zOcOTBG4azT98zJBjJM/AB5WEAlhiJKgcI+RxkuQ7Qp05KomCxGafugekMMyUGZSFuhn33XjPMB6rJtlBmYEEKCXyA6ER48sk7sM9hlsMAckEKkhMAqjkFVEtWSYgPXHj+ERzsyADkc3RgoyDCsPP2SwYrOWHqOuDpp+8MMnycKMGOjZ1opMDwDKiWWEBWOYAc7OhsRmDUMzEyUj8KpIG+fwL0GXKwY2PfffcVnC2pHgKgfA7KasgAFOywoIcBkBp1ER7qO8AcWMh8AJYDoKyGKzpAcg8/fAOnF2IBI3KDBNge+K+srEywIBLgZGWwlhfBKIhADowzlGNYevExgwgXO4OzsiiDKDc7ihl3794FtQcYyXIAclF8/tkHhldff4LFxICWGEoJwIviP0A1oLLg5OP3DIXWKngdQHJdALLAWl4YjHGmbKAaUDlATFlAl+p41AFEF0QxZ/8C26+3aG7pOS08IcDEwoqCt4ZogzEuMWQ5bGLofJpFweYANQbfDbdokwZABoMswGURuhgh9UMzF8B8hc03yD4mRj1OB/CzMvwjt7OKzRGY7Yj/DHwsDP9wVkZlO67Un3r/v+7TH9pEDdCDf00FGJu7PHQasTpgIABAgAEAZzJTrgcAEqEAAAAASUVORK5CYII=") +} + +.list-list .ico-mov { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAupJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08aYGNnh7Ob45TAdO2iezjFYHx0MXQ9MP6vnz8pS4TIAJ9FVEsDIANhviTWAlL0DI1cAPMRKcFLrB6UNMDNxvDv579/TEBAkiXoiRAXADVQuYB2IIsxIveOJ6y7Wn/jFUPdt1+0iRqgB/+qizE0FwRpN2J1wEAAgAADAIjpSLqXWcBIAAAAAElFTkSuQmCC") +} + +.list-list .ico-mp3 { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwxElUOEAM+dpYTpY6/vJP0gohYQMhwYh1Jt1ww6oBB7QCiE+Gn/lqG/79+DZwDiLH887Q2hn+fP9ImChjZ2FBorIUOiZaTFAJ8hc2EfcPLD3YEiKa6A4gBvFlVo9lw6DmAEblZ/rwwhi5tdMn+JbgbJGyMjCh84Zo+MP22pQirGIyNTw0y+IXWD6FKFIAsQXYMiEYXoygNYDOckHpi1VIlBEAWYbOMaiGAbBg2H8GCGz3+SS4J/3Fy//v34xsTExXSBLZECGqe/gfagTMbPpjdX892/0Yd4/evNCkf/nFx//2toNGskFrYiNUBAwEAAgwArNbTCDlVe6IAAAAASUVORK5CYII=") +} + +.list-list .ico-mp4 { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAvBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08aYGNnx9DQHKcEpmsX3cMqBmOjq4GpQxb79fMnbaIAZAm6Y5DZFKcBZMPRfTUoEiHIUcgOI9aRTKQGMy6DYVGALe3giwoUB3CzgdqQ/6iaJpATJshsLqAdyOoYkXvHE9Zdrb/xiqHu2y/alA9AD/5VF2NoLgjSbsTqgIEAAAEGAKRaSLN4Zv7KAAAAAElFTkSuQmCC") +} + +.list-list .ico-mpeg { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAwJJREFUeNrMV01ME0EUftS2221LscBSt0pbjBWsBjGAQRMT48FEL/5F44kYD8ajnjypMVw8cvFkSJSrxMSDMRoSUeNPDARJtKBVo0Sx0CZFC6Wldeu8laWz2y0sMoAvedmZN1/nfe/NezNQls/nYS3FTE/C4fAh8ukiKjL00UJ0gDaEQiF9Aujc6/WKDoeDiedIJIKfO0RPBoPBAT2MSTMXeZ4HSZKYKIrP53uIJAiZZiMEmAvHcef9fv8TMuzRI2FajUKzWq1nAoHAKz0S5qVuNp2R4Pn7JIyMpSGezMk2wWWGbRt52BN0goMrxORyuZQ6QDk99+0nWvZPBN5+m4F7/QnY6bfDsVY3eCossn38ZxaGRlPQ+SAKR1rcsGMT/5eYIIDH49ErzKVnAJ3fH5yEs/sFENdbVGu1VVZZm+sc0P0sLtsUEkoxlhKT0bRj5O37qoucq1qIrCEGsVNpyVBghgi8jExBo89e5LyrLwaJ6VwRCcS++JBkR2D4+ww0kXPXyqfxDNx4NAGRaFplRywWKTMCsV858Lr1U5+aleDW0zj0hQsRiwQbS+bYEVhUyHsmUY8a1t06gzsb6gLs87FEVq50rdgsJjjVVgkNXtu8Dduy0mlmRwAvGexzLYGAwMGJ3W6o0jhDbINoY3cEbVucMPQ1BT8msyr7uQNCkfMowSB279ZydgScNpN8w+EloyVBC67dJhjE4m+YHcH8zUbqrOtxDJoCdthFWk1wWea6JAuDJOo3X1JwtLVwFTMlIJOo5aGuhpMfo57XCfkxwmqvLjdDvcjDxcMbVI8RcwIo6OBgY4WsLGRV/h74rwmojuB6L6my3o8r7rQ7tEANWDkOOto3y+PL3Z/n7bRNGdMY2lbKjrbZTGZ5RajnQBnThLVryvzSzWFjNUBHqt2olCBOmwk90kwyoHdMC5FU8IYzQGdBb2O0GcnKYuRUGXBYQcpIkmm5WaEJao/ETnzQ8zL6v+POu++ujkzAldTsytwPJMDf9TXQceH49mu6BNZC/ggwAJR8VIseM6jsAAAAAElFTkSuQmCC") +} + +.list-list .ico-mpg { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAvlJREFUeNrMV81PE0EUf61tt9uWYoGlbpW2GCtYDWIAgyYmxoOJXvyKxhMxHoxH/QPUGC4euXhsor1KTDwYoyERNX7EQJBEK1o1ahQLbVK0UPrl1nkr2+5ut7CVAfwlLzP75nXfb2Z+b2ZrKBaLsJYwyR8ikcgh0oSI8RRzdBMblTuCwaA2AUzu8Xh4u91OJXM0GsXmFrGTgUBgVCvGqHrmWZYFQRCoGMLr9d5HEoRMlx4C1MEwzHmfz/eIdAe1SBhXQ2gWi+WM3+9/oUXCVOvL5rICPH2XgonJDCRSBdHHOU2wbSMLewIOsDPlOTmdTkkHiNML7Qgxwz8ReP1tHu6MJGGnzwbHelzgrjeL/qmfeRj/moaBezE40u2CHZvYv8Q4Dtxut5Ywa18BTH53bAbO7ueAX29WjLU0WkTrarVD+ElC9EkkJDFWg1HvsuPM+/Y1VSRXlBAZwxiMnc0Iuiami8Dz6Cx0eG0VyUPDcUjOFSpIYOyz9yl6BN5+n4dOsu9qfJzKwvUH0xCNZRR+jEWRUiMQ/1UAj0t76dM5AW48TsBwpDxjnsTGUwV6BJYEuc8E2aWGulun8826qgDrfDKZF5WuhtVshFO9DdDusZZ8WJYNDhM9AnjIYJ2rCfg5Bk7sdkGjKhnGtvNWelvQu8UB41/S8GMmr/CfO8BVJI+RGIzdu7WOHgGH1SiecHjIqEnIgWM3SQzG4m+obUHpZCM6Cz2MQ6ffBrtIqXFO80KV5GGMzPrV5zQc7SkfxVQJiCRaWGhtZsTLaPBlUryMUO1NdSZo41m4eHiD4jKiTgCBCQ521ItGA6vyPfBfE1BswbUhorKhDyueNBxcRAMWhin1+/s2i+2l8CdNn9SvFqP2I3LZ7PJEqIaUAJNKpNRk1CRq0oB8pku9bDGC1MpQDa1t0jNWUxVoLa18rFoCPaulWAG7BYSsIBgJlqUJuQjlwA9UG8kh9xnk/44Hbr+5MjENl9O5lTkfyAR/tzVD/4Xj269qElgL/BFgADHlS7OoZ6a9AAAAAElFTkSuQmCC") +} + +.list-list .ico-rm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAuNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08aYGNnx9DQHKeEwq9ddA9FHMbHJYYMfv38SV4iRLcElwV0TwMgh8B8TY7DiA4B5Giglu+pFgWwUCDHYShRwM0GakP+o1nqB5nNBbQDWYwRuXc8Yd3V+huvGOq+/aJN+QD04F91MYbmgiDtRqwOGAgAEGAAnMw5uSNz54cAAAAASUVORK5CYII=") +} + +.list-list .ico-rmvb { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAvNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08a6EzVROHXLroHppvjlFD46GIgNrocTBwZlM++TjgRYjOIEoCsH92DTKQaBPMRusPwyYH4MExUCCAHG6W+JxQCJEcBtvgmRo6oXMDNxvCPFlkTOUQbF17/NyNDG85nRO4dT1h3tf7GK4a6b79oUz4APfhXXYyhuSBIuxGrAwYCAAQYAPSMU/fnJ99KAAAAAElFTkSuQmCC") +} + +.list-list .ico-swf { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfVJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUKp4d/3H2D4tm8fw/9fPxlEOjtR5Pj4+GDpAATCoPR5IGakigM+LVrM8PfdWwbBokIGJkFBDHlRUVEGcXFxbAmT8hD4+/Ytw69LlxhEuoC+ZsIdk7DEiAuQnQZ+Xb3KwGFpgddykgsiUsCfR48ZPq9eDY4GEOAJDmbgi4ulnwP+fvrIINLezsCmpUlRCJAdfv+//2Bg5OSgOIuS7QBQ6mfi4hoYB/z/9Yvh78tXDMzAbEZ/BwCz1ad58xg4rawozgFkOeB1UTFQFzMDX0I89dsDxADRCf1UrSfoUhmNOmDUAfgAI3Kz/KaTM13a6Or79uJukACbTxgaFGfPAtP3U9PgfBgbXR1IHKYeBpDV/gKWoiSVA8iW4bIYGyBWHRMxBqGHALIYMo1sKYiPHhJkJ0JswUqMHmJCgYmYKMDmCBgfmaa8LuDj+/vv2zdmJqRaDlsUkAvADVSgHTiz4Y2GxhrG8+cbGT5/pk35wMv777+hYb1GQ30LVgcMBAAIMACmqfNs4ifjnwAAAABJRU5ErkJggg==") +} + +.list-list .ico-wav { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwxElUOEAM+dpYTpY6/vJP0gohYQMhwYh1Jt1ww6oBB7QCiE+Gn/lqG/79+DZwDiLH887Q2hn+fP9ImChjZ2FBorIUOiZaTFAJ8hc2EfcPLD3YEiKa6A4gBvFlVo9lw6DmAEblZ/rwwhi5tdMn+JYw4Q4CNkRGOJWv7Ufi4xEnhD6008LaliEG4pg/MRqZB4jAA48PkselDVk+TEEC2nOJEiC2ekH2JzffE8kHgF9A+nInwHyc3gSYkhQ1UUKUGtANnCDyY3V/Pdv9GHeP3rzRJnP+4uP/+VtBoVkgtbMTqgIEAAAEGAH8AyCqN8wbhAAAAAElFTkSuQmCC") +} + +.list-list .ico-webm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAwxJREFUeNrMV81PE0EUf137tbQUCyx1q7TFWEE0iAEMmpgYDyZ68SsaLxKjifGof4Aaw8UjHky8NFGuEhMPxmhIRI0fMRAkwYpWDRothTYpWmgLLVvnrSzdr2JLBuoveZndt2/m/ebNezOzhlwuB+WEUf4SDAYPkSZAhKfoo53IkFzR3NysTwCdu91u3mazUfEcCoWwuUfkpN/vH9KzYVTvPMuyIAgCFUF4PJ7HSIKQaSuGAHVYLJaLXq/3GXns0yPBrEWimc3msz6f740eCWOpg83OCfDyYwLGwmmIJbKijnMYYdtGFvb47WCz5OfkcDikPECcXmwHiRhWRGD0RwoeDMZhp7cCjnU4wVVlEvWTvzIw8j0JPY8icKTdCTs2sX+JcRy4XC69xCw9Auj84fA0nNvPAb/epPhWX2MWpa3BBr0vYqJOIiElYyEwxYYdZ961r1bjXFFC5BvaoO1MWihqYkUReB2agRZPhcZ5YCAK8dmshgTavvqUoEfgw88UtJJ1V+PL5BzcejIFoUhaoUdbTFJqBKK/s+B26oc+OS/AnecxGAjmZ8wT22giS4/AP0HOM0F2qGHerSty5KKqAOs8HM+Ima6G1cTAqc5qaHJbl3RYltV2Iz0CuMlgnasJ+DgLnNjthBqVM7Rt4q30lqBzix1GviVhYjqj0F84wGmcR4gN2u7dWkmPgN3KiDscbjJqEnLgt7vEBm2xD7UlWNrZSJ4Fnkah1VcBu0ipcQ7TYpVkYJjM+t14Eo525LdiqgREEvUsNNRZxMOo721cPIww22srjdDIs3D58AbFYUSdAAIdHGypEoUG1uQ+8F8TUCzBjX6SZf2fV91pb/5SDAb5f8FESshd6f2q6dDdtRlQj60chXRSH/m7XMezjGHFSahHUO1Ez6bwOUIiIMmZm6O5cHIhd/52SNNKz5Lo6dT6Qq3cZ9kjwOgNJl/zksJZgPBy4ygiYDODMCcIy5amPOnUCVcoQvl7gvaeqKiCnvvvr41NwdXk/OrsD2SCC4110H3p+PbrugTKgT8CDACPHLU9lTMolQAAAABJRU5ErkJggg==") +} + +.list-list .ico-wma { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAeFJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgSmX93C8ODzC6IsVOCVYMjU9oHzf//7w3DgySUGVxkj4soBbABkeadFClEOKD8xB85+9f0Dw9LbexlefHuP1wE0SYRnXt9imAEMOWsJbYJqaeKA1XcPMaRpeTOYiWkMjANAQIJLkCh1dCkHBsQBz76+HTgHhCrbMcy+vo3h3Js7pFXHVKt/RdUYZLhFGZbd3ke5A0CFC3L+xq9WHCUR5ur6Mxx4dokyByCXbKQCViYWvIXQ8M4Fow4gq1U84cUOBoYXtLd0FlKrGCMEuJjZwbjFKQ3ORsYwcRCNrgaXGDJ/6KSBtoMLGars48FsZBokjksNshyyGEzNgCRCfJYTLAlhrkem8anB5ntcfKwhwMXE9u/v/380Cw2Q2SA7kMUYkXvHU0+sqb/940Xdt3+/aBI13Ezsf1U4xJuzLUIasTpgIABAgAEAkufcCgWFyTcAAAAASUVORK5CYII=") +} + +.list-list .ico-wmv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAt9JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08IsLGzg3FnqiacjYxh4iAaXQ2yGDY5ED100kDtonsMzXFKYDYyDRLHpYZYuaGTC2C+QKbxqSFFDqsDuNlAbch/NPMtyGwuoB3IYozIveMJ667W33jFUPftF22iBujBv+piDM0FQdqNWB0wEAAgwAD570Ssrd8AngAAAABJRU5ErkJggg==") +} + +.list-list .ico-doc, +.list-list .ico-docm, +.list-list .ico-dotx, +.list-list .ico-dotm, +.list-list .ico-dot, +.list-list .ico-rtf { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAArdJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7DQ0mI+Pj5YOgCBCCh9BogZ6eIAAQEBsCMYGRnh+OHDh/SLAlZWVgZmZmbiywFcYPnW+ww/f/1lSAhUQRFfsP4OmIaJP3j6hWHGiptgtoQIJ0NBvBYDCwsLw9+/f3GaTVQI6KkLMtx99Jnh959/cDEQ+97jz2AMExcWYGcIcpVn4OFiYdDXEIKrBYUCExMTOArIcoC6Ah/QAEawI2AAxJYU5QJjmDgvNyuDGlDt1+9/gA4QRDEDlgbIcgALCxODhhI/w417H+FiILamMj8YI4tfuf2eQVaSm0GInx3DHLIdAAK6aoIM1+5+YIBVXTfuf2TQVhFg0FIWQBG/fOsDgwFS8JNUGeGNBkU+hh8//zI8e/mNAeQRFmZGBlEhDkhqB4YQSBwUBU9efGWI9VeivgNYYdFwHxLcmkCfwwA4GoDinOzMDEqyvMBEyEq0A0gqB0C5ARTfIKyF4gABsNgVUPBrCpFUVpDkAHUFfoZXb38wvPvwk0FeGlFlK0jzMLx9/5Ph8cuvDDqqAuQ3SAiXbEzgbMYGpJmQUjQzMIuqAdPInz//GdjZmGnnABCI9sWewCK8FMkqrulSHY86gOg0MGHJIyD5iOaWzmrD3SoGNZ9Q+JU5Dij89ikHsMoRIw4Cv379Ij0XoBsO4sNoYsUHLA3gs3xk5AL0NIQ3F3BxMP/79ecfE6j5hMsQWJDC4ptYcRAA9ZZAdqA0UpB7x1MXHKm//eh73bcff2kSMtyczH9VZDmbsxNsGrE6YCAAQIABAHOPGcK/91HrAAAAAElFTkSuQmCC") +} + +.list-list .ico-docx { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAphJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUJLi/n4+GDpAATCoPR5IGakiwMEBAQYQAmakZERjEHg8ePH9IsCVlZWBhYWhB9hjiA5ClZsu8/w4+dfhoRAFRTxBevvgGmY+IOnXxhmrLgJyU4inAwF8VpgB4AsBuUKbA4gKgR0VQUZ7j76zPD7zz+4GIh97/FnMIaJCwuwMwS5yjPwcrMy6GkIwtUyMzODMdkOUFPkA9MgR8AAiC0lxgXGMHGQxWoKfAyfv/5mMNAQQrWIiQmMyXIAKwsTg7aqAMONex/hYiC2hhI/GCOLX779nkFOiptBiJ8dwxyyQwAWDTfuIzngPpIDkMSv3v6A4Xt8gGgHgKLh67c/DC/efAdjZiZGcEIDYRAAiX349Ivh8YuvDLpqguTVhqREA7IlWsoQcQ52ZgYlGV5wWqC6A0BAW0WA4ei5V2C2h600XFxPXZBhx+GnYLaprghJZQVJBREovp+9+sbw+t0PcEKDARAbJAaSAzmS7AYJMdEAcgQTMP6ZkFI0iK0KzH6g8gAUDTRzAAhE+ShhFY/wUiSruKZLdTzqAKLTwIQlj4DkI5pbOqsNd6sY1HxiqMxxQBFrn3IAzkaWIyQOEkNX09i3i7hcgK4RxMdmID5xZDl0NTRNA8gW4bN8+OYC5PSAHAXYACNy57Swefefn78ZmavznGiWCHum7/3bX+vKgtUBUxcerbn98Fvjtx9/aRIyXBzM/1Tlueqz461bsDpgIABAgAEAbXE38hMP+G4AAAAASUVORK5CYII=") +} + +.list-list .ico-pdf, +.list-list .ico-fdf { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7CQa+jf9x8ZGFlZGJh4cKcXPj4+WDoAgQgofQaIGSl2wJupCxk4tNUYuG1MGVhEhbGqERAQADuCkZERjh8+fMhAeQgAExgoBPgD3IF+YcSpjJWVleHPnz/wBEm9RMjExMAiLsLw/fwVhj8v3+AvaFhYGJiZmYFamMAhQLVEyOfryvBu/mpgGuAiqJYmDuDQVGFgFhJg+H72MlHqYWmAag748+oNOBF+OXya4ffjZ0Q7gmoO+LL/OAOPszWDcGokw7t5qxh+P3lOeWVEdCb4+g2cC76fvsjw6/5jhv/AlP5m2iKGvx8+M3CZGwALib8MrDKSDLxudtR3wO/nrxjeL14HLoQYOdgZ+IBZkVVCFJwdf964y/Dz3kMwn0VCjPoh8PXIaYaPG3cx8Hk7MfA4WGKW+xrKYExSjiZW4ffzVxl+3rzLwGWix8Bjb0G1eoJoB/x5847h/6/fDHxeTnhLP5olQl5XWwYGEKYyoEt1POoAotMAS/tMhof0sHDxRNyJEFuqVGorgbPvVfVgiBEjDs9N5OYCZAtgbGTDcYkPSBoAOQY9JCguB2AGEuM7UkKA5CigaS5g4Ob89//rdyZGEkOFWAeCu8FcnCgtVEbk3vGN/ln1TDfu1TF8+06b8oGb6+8/dcVmjcK0RqwOGAgAEGAASrzyfwJnztEAAAAASUVORK5CYII=") +} + +.list-list .ico-ppt, +.list-list .ico-pptm, +.list-list .ico-pot, +.list-list .ico-potm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdtJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUJLi/n4+GDpAATCoPR5IGYkywHvJ0VhCjKzMLCpmDNwmAYwMAtJo0iJiooyiIuLY0uY5IUAb3Atw+e1zQxczmkMLKLyYLH/v74x/Lp1nOHzimoG3pB6BmYxRRQ9sMRIVDlACDBDLWUWlESxiEVGm4GJT4zh+/FVDDz+5SRFE9USIbuOE8OfZzdJ1kfdXMDINHAO+HXzGDyKyK4LiAV/3z9nYGRhgyTCP78Yfj84z/Dj/DYGHt9S+jjg295ZiFBn42JgkdVm4PEpZmCV06WPA0DZjUVKnSpRR5eieNQBVEsDoAQnmLdsNAqGlwMYkZvlDws06dJGl59wHU+DhIUdU0PVZoQj23yxiiHz0dWigD8/ycsFyBbD2NjEkNnDqxyABTGy77CJ0cwB2CyhxGKsUfCfS+Avw/9/tAtvoNlgO3Blwzuzi2pY7h1rZPz+kSZp4z8n/78/Slb1Kql9LVgdMBAAIMAARLzKcEnwqqcAAAAASUVORK5CYII=") +} + +.list-list .ico-pptx { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7DQ0mI+Pj5YOgCBCCh9BogZyXLA+0lRGGKMrOwMrIrGDBymAQzMwjIocqKiogzi4uLYEiZ5IcATWMXwZX0bA5dzGgOLqDxY7P+fXwy/bh1j+Ly6noE3qJqBWUwJRQ8sMRJVDhACrLI6YJpZUBJokSLCECl1BiZeEYbvx1cz8PiXkxRNVEuE7LouDH+e3iBZHxVzATBd/f83cA74deMQA7O4MmV1AbHg39f3DP8+voIkwp9fGX7dOcXw88J2Bm6fIvo44Ov2SQgOMwsDCzDlc/sUM7DK6dLHAbwh9eCUTw1Al6J41AFUTQOCectGo2B4OYARuVn+sECTLm10+QnX8TRIWNghiqo2IxzW5otVDJmPLg7TA9MH5//5SXwuQLYYxsYmhm4hsiPQ5eiWBoixnGAIwIIYPTjRxWhWEGGzhFiLyYqC/5wC/8hp1eCyHCOxAs3+z8n/D2c2vDOrsJ7l/vE6xu8faZI2/nMJ/P2jYNGsktbfiNUBAwEAAgwA8LXlQoWLfqIAAAAASUVORK5CYII=") +} + +.list-list .ico-txt { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVJJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkLQSevnqLU05aTBivXlhiJKocINcSSgATwwADiqOA0pAbjYLRKBiNgtEoGHXA4MoFE+fsooulM/twt4pBzScMDY3VmRhi9a3TweIgGp86mFoY+PXrF4ocI3LHJL1own9sDkC2ANkwZDFsjkFXC3PAzL4CRqqlAWyWj6xcgBwFFDuAi5PtH6FGJK40QYwjQGaD7MCZCKfOWVt/5/7Lum/ff9Ekarg42f+qKIo1Z6cEN2J1wEAAgAADALEDt/dBJDrPAAAAAElFTkSuQmCC") +} + +.list-list .ico-xls, +.list-list .ico-csv, +.list-list .ico-xlsm, +.list-list .ico-xlsb { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAiVJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDkV3BAwwofEluLm5Gf79+0cVDALy8vK7oI4wIMYBVAdsbGxZQEccweUIJnokNKAjEhQUFM5hcwQLLS3m4+ODpQMQCIPS54GYkS4OEBAQYAAlaEZGRjAGgcePH9MvClhZWRlYWBB+hDmCLAeceHmdIWZPO5gmpObYi6uIOAY6AISRQ4GsNGAhrgmmp1zegMJHtnzSpfUMGdo+DFYS2ihyzMzMYMuxlbokpQFcjoBZnqLpyWAnpYdVLxMTE+UOwOYIEABZHqfuyuAkY4hXL0VRgMsR/4C+ilJ1YvCQMyUroVKUC/5Bg1SEk586tSEpOQIU7AGK1gxyvGI4EyZNHACz3EfegiFMxR4uTq4jWMixXISDn0FPRInoLEoVB8As//PvL8OLb+8Y9j05z6AjpECxIxiR8yawRfRfWVmZpjXj3bt3QS0iRrpWxzTLhsPCASiJcPKr3QwMIExjMB2pVYwRAuyMLCi42DYKKx9dHCYGw+hyMExyFEw5soohxwbSmgLRID42AJODYZgeqqQBmIG4LEd3KIxPs7qAmo4gKgRgvscXrDA5WBSQlQu4mNj+/v7/l5kJ0WpGCXr0qEC3lFC8/2P4D7YDZ1E8/eS6mts/XzZ+//eLJuUDJxPbP1V28fpM86AWrA4YCAAQYADJhRMxzVK59wAAAABJRU5ErkJggg==") +} + +.list-list .ico-xlsx { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjlJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUJLi/n4+GDpAATCoPR5IGakiwMEBAQYQAmakZERjEHg8ePH9IsCVlZWBhYWhB9hjiDLASdeXmeI2dMOpgmpOfbiKiKOgQ4AYeRQICsNWIhrgukplzeg8JEtn3RpPUOGtg+DlYQ2ihwzMzPYcmylLklpAJcjYJanaHoy2EnpYdXLxMREuQOwOQIEQJbHqbsyOMkY4tVLURTgcsQ/oK+iVJ0YPORMyUqoFOWCf9AgFeHkp05tSEqOAAV7gKI1gxyvGM6ESRMHwCz3kbdgCFOxh4uT6wgWciwX4eBn0BNRIjqLUsUBMMv//PvL8OLbO4Z9T84z6AgpUOwIRuS8CWwR/VdWVqZpzXj37l1Qi4iRrtUxzbLhsHAASiKc/Go3AwMI0xhMR2oVY4QAOyMLGBfbRsHZyHx0cZgYDOPTC8JER8GUI6sYcmwgrSgQDeJjAzA5GIbxidFLMA0gG0iMQ2F8YvVSrU2IyxEU5wL04MSlBmYpssXE6MUoCYsPTf/z+/9fZiZgqxk9+LAZCJPHZgk2vf8Y/jOwMjL/7bXLZMHqgOkn19Xc/vmy8fu/XzQpHziZ2P6psovXZ5oHtWB1wEAAgAADABRqMy9QT3WBAAAAAElFTkSuQmCC") +} + +.list-list .ico-7z { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAX9JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7bo012zDEHrZ4YYiDxHDX7z/JLweQDQZZCuOjiw9YGkB2FE0dgM0iciynWgiQazlGGvjPKfCP8ddXJgZGJpISIro4Tsf8/we0g/8fTgf8UbRoYrl/vI7x+0ecLnjY4EyUGFb7uQT+/lGwaMZZDgwEAAgwANHIuc9Vl0AAAAAAAElFTkSuQmCC") +} + +.list-list .ico-cab { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYxJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7quKabQgHtnhhyCGLIavFph5Sv/8kPhFiswCroTgsJUY9SQURNsPxhQIhyykuCdGDnJwQYCLHQpjBMIzNIWSVhP85Bf4x/vrKxMDIBPcNqUGKV/3/f0A7+P/hzIZ3ZhXWs9w/Xsf4/SNNyof/XAJ//yhYNKuk9TdidcBAAIAAAwBVN9K79PuTHAAAAABJRU5ErkJggg==") +} + +.list-list .ico-iso { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7bo012xgetnjB2WBHQ/nIYujiqPX7T8rLAXSHgNjIYuhqqJ4GYBbi9SmtEyGyI+juAGTfU+oIlDTwn1PgH+Ovr0wMjEwkRwG6Y7BGzf9/QDv4/+HMhndmFdaz3D9ex/j9I03Kh/9cAn//KFg0q6T1N2J1wEAAgAADALuKyAid7COMAAAAAElFTkSuQmCC") +} + +.list-list .ico-rar { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAX5JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7quKabSj8hy1eGPLIYoTUM/z5SXo5gG4BhqEUqKcoDcAMR/c11UtCZAuwRQEp6slyALYgxRb3MD7ZUfCfU+Afw/9/tEv+QLP/c/L/w5kN78wqrGe5f7yO8ftHmpQP/7kE/v5RsGhWSetvxOqAgQAAAQYAQvfLEuMZrKwAAAAASUVORK5CYII=") +} + +.list-list .ico-zip { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYFJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7bo0121D4D1u8wGIwGlkcd/3+k/xyANlgdMcgy8McRbM0QIoFVC8J8VkOCxVSHMdCTZ+TEyooDvjPKfCP8ddXJgZGJqISIskW/v8HtIP/H85seGdWYT3L/eN1jN8/0qR8+M8l8PePgkWzSlp/I1YHDAQACDAAtKS/DHmsv9AAAAAASUVORK5CYII=") +} + +.list-list .ico-gz { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAHtSURBVFiF7Ze/T8JAFMe/1QYCRKIDIe1AYcAfLJLowL/grCbGydEJTXBw0qiLA4NxNGFlkX/A0UlJJGEBQzoAC3EzxhgTgz0HPXI9jrahxTr4TZrm3r3r+/Tdu2tPIoTAT8lso9lsrgEoAVA8jLEKoMYaMpmMGABASVVVJRKJeBJZ13UAuAawmU6nayKfKa6thEIhGIbhyQUAiUTiBsC1rusrTgA8VzAY3NU07RZARQQxcQAACAQCO8lk8l4EwdeAp4pGo7QOAGDr5/4AQPoVgFgshng8brIxQM4AXisn6PdaYwHI6gJmNo6tfewe0u+1MJcvC/ueL7dH9tF+O7kqwrl82VGQiQHYZWDiAP8ZoBC+AriV7wCudkK38w/8lQxI0ve3odFoDDuoC2MXmqzMC+003gDASnZ7uVv5PgW+A5imIHK1jicA2sUjuvtLEwk49LvLngs6e4uEVaewTHjxtk5h2XTZ+u4tmg4iQ0XYPchCK9bRPcgO2lRasW7y48WOo758m5ejjWjUYKdiwXjZFiEdPOoBboJbArBvrRXrlllg0y3ytRovEUIGO1P7MPcpfbxNQRpvdfI1MPTmxAAJhI3UeXWamkw10E/lTuX23ZH0/iImkIOWADQDAITLmIRnP/vJ3BlQZYw+H8+/AFShLMorUUd8AAAAAElFTkSuQmCC") +} + +.list-list .ico-bt { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbxJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDkV3BAwwofEluLm5Gf79+0cVDALy8vK7oI4wIMYBVAdsbGxZQEccweUIJnokNKAjEhQUFM5hcwQLLS3m4+ODpQMQCIPS54GYkS4OEBUVZRAXF8eWMOkTAiAAS4xElQO4QPmJORhivKycDO6yJgymYupY5WGg0yKFcgfgMqjm1AKwA5DlQI4hZCnVcsHvf38ojiK6ZEOqRAG+eKaLA7DFKzUcNeBRQJEDWJlY6BMFzIxMWIPbSkKLYgcwIreIgA2S/8rKyjQN8rt374IaJIzDIw2MOoDqBdGEFzsYGF7Q3tJZeFrFDFzM7DS1/Nvfn7iz4cPPL/8zQWOl99wqhmKjMLgcMh+dDQLIatHFkdUD28sM8rzi2LNh6+mlKBaAaBjGZTm6xTCAy0EwO8hOhDCHobNhlsD4+BxHlANgBsAwNoPRLYGpRfc10UVx4YGpf/4w/GMGlf20AH///wOmeqa//Q7ZLFhzgQqHeMPtHy8agSmVJi7gYmL7B7IDZwgMBAAIMABhJOYNvD7xMgAAAABJRU5ErkJggg==") +} + +.list-list .ico-file { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAOVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gJMDAMMRh0w6oBRB4w6YNQBow4YdcCoA0YdMOgc8Pzbt280swxq9gt8reKU58+fgzqnEjRyw1MgTkMWYBzo7jlAgAEAzk5sMbucHicAAAAASUVORK5CYII=") +} + +.list-list .ico-apk { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhlJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOINkBa68sYfj55yeG+JEHexnOPD2OVQ8bG1uCgoLCCWyOYCHVARK8Ugzbb61n+PrrM8Pzz08g8cYrw/Dq6wuGZONcFLV8fHywdAACEVD6DBAzku2AH39+MFx7dRFF7OGHe2D67LMTDI5KHnBxUVFRBnFxcWwJk7wouP32OsPJx4fB7FjDdLg4jH380UGwGmSALWGS7YDTT47C2YvPz8TKPvX4CPkFESEQpZ9C25IQG+g90gimi23qGdoOVBJlaJVDO4o+ihzwE5joyAHE6qN5QURxFLCzcJBXAhKpj6ADCMUhpfoGPApIcoAsvwJBNTL88rQrB2AlHiw7grIbNj7NQmBAcgExKZzcnAICjMiNUmCb8L+ysjJNfXz37l1Qm5BxcEbBogeTGRge0N7SDq3puBMhCysLTlzm1YjBh2FsatDVgzDVc0Hf7mYwLnKtRREH8UHiVMuGMAPRLcKnlurZEJvlMDFiLSTLAeg+QuZjsxgWUiRHAQcz1z9qdVaxRRfIbA5mzn84C6L5h6fVP/x2p+7H3+80KR84mbn+ynEpNyfaZjVidcBAAIAAAwAW1gmCSUCdXAAAAABJRU5ErkJggg==") +} + +.fileList .ico-bookfolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmJJREFUeNrs3E9r1EAYx/HJ/3S3mwTb7a4u7kkPguLBky9A8OrVk9A3ISiK4mvwpNCLHj37GjwJ4smjFNZuQTbrYXe72/hM2lrBS6LuhITvDx7SlsIkn5lkMp22VpZlivx9LAABBBBAAAmAADYEMP3woK7Xsiv1ylBb+1J7Us/dhgyEW1Iv25euKcdvrb2xbHU0mE9GjxbpgdMEwK7Uu43toe94cjnZYv23ra1UEO8oAdy1a47nSL0JkotDrx2bffbZVt55dQd84UfdO0HcrewE6gx4z20lD4O4p59KlZ2EK7OuL8cnUnr6HdRFT/CU39k6u5WqOw+pZ360Iz3ZV5bj1WgAHsvAW1bfkfr9Sc8olp0ZmcGaFv0M7FZ9G9QdkAAIIIAAEgABBBBAAiCAAAJICsfIptJyPlU/vn2SY2rmooJIbfZuyLHTjBFoEu+kw9K8zUaNQB09KsJooLLjlZqOPv7xfZbtqk7/5q/PD7+8z4/bV+8WbmuW7hvtMEPPwJNNH4237phog0kEQAABJAA2A9DbuKAcr81KpHys/JXD3+zJW06mZpOv6mj2HcBCdJalWltX8iXX6RdUmAyVO4u4hYsJ2ud4v/dmmADIJEIABBBAAAmAAAII4H9c9+ro9e66c96Gmd/7NrIW1tuL+U7Zwee8yuZsc6lUm2HcnBGY79GG5v6WTbfV6V1v1ghMLt/mGUgABBBAAAmAAAIIIAEQQAABJBpwnK2WSJTMqdlYA76eT8YKxHJ42kyyp38e+HQxPfSl7svHfXgKZST1Vuox/8HyHwMggAACCCABsKr8FGAAiCO50cIM93UAAAAASUVORK5CYII=") +} + +.fileList .ico-folder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAY1JREFUeNrs3E9Kw0AUx/FM/kwwxSagqdVC9649gwdw60rIJQRFUTyDK4VudOlBvIdQjJvWVWPr9I3o3gaaNOH7g0eyCUM+mcybbKKMMQ4pHxcCAAEEEEACIIDNjF/mounr2SbeSyb1UNFYb1IjqVu/JRPhSOq+c3DoeDpa+2Bm8TWYTcYXxfTdawNgKvWytTvUXiC3Y4q1D6hk4QvjniOAWdPXQE/qKUz2h0EnrnRg5aqfh9d0wDvdTY/DOKULl8iJHyXnYbxnV6X6urB0VC3HKynbWgeN2T5EiaO3d/5epVq3MTe625Mn2XeUFzRoAn7LxJtvxD4wsx1FuaaSDtbGL5G07teATzkACYAAAgggARBAAAEkAAIIIIAEQAABBJAACCCAABIAAQQQQAIggAACSAAEEEAACYAAAgggARBAAAEEkAAIIIAAEgABBBBAAmD1gLlZzJFYMb9muQV8nE1yB8TV8KyZZGT/G3NdfH5oqVM578Pzr4ylnqUuFT/ipokACCCABEAAm5mlAAMAvOdJo4EAKcIAAAAASUVORK5CYII=") +} + +.fileList .ico-folder-empty { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABBJJREFUeNrs3MFPE3kUB/D3m+lMp62kpQgEiXDw5CaYuGsUXTxtOOllr3oxcvI/0JiYaEzWv0AvHrzgcU941kQPxuxmEzl42gNshAXRpSC0nbbz871fmdpGBdoK7LTfl/zSMm0ymU/f7/febyZBaa0J0XpYIAAgAAEIQAQAARjNiIVvVv+8GvVrmeLxcJ/O9ZbHIx53Yh2SCD/xuJ/M/kCWk9zzk+nAH/Y3lm6WNpfsTgDs5/F7vGfUteRyAn/PT6h4uMkBYsCpqK+BNo9pNzU04njpfT2xUsr8eFEHvOskBiYlG1CFm49fY/He625qUFalg6vCXH1dfr3F4wqP4ci0D/FechJ94VQ60DbmtpMcNL+kstzo5J8OeJT/F33glKwh5nfchwrWiTuR/oOeBtjKARABQABGM2LShAYrT8ny50jpva/CJTVEvnU00mjKdiiWyG71o+V/yA4WvvnlfKHaayW89u87qMxJ8vrGyYt41ulKiYq5f0kFmmJOsMz9X3GrKW3cEq3nFanBS9X3S4+pJ9H6lkn1niIr+yOfIvq9puKFL54eIH9tmWIq2OCLKtU+rHCDb1uf8dInfjPHc685G1emKeG2iHf4545a+5SlttbAuu2QoPn2CKniPLnDVbzc6xvms3j/BP03/5jKFaJDnqbd9t6diPdFEZEQmLI3RgPnZ6iUmyUnPUbrb+6R5aTJTjEqvw5dnDPH8gv3KRkHXkMbE7N5FGbp498PDJ5knpMZo+ToJYMoqMWVF7TJWbibaayypzseL7yZ0FAwDh27ZrJMc9HYXKxOWW2nKTs+XRUPclTgJTNfVJSI669iGry+s93TSNcXDMk8ybqgZ4JcRzfgfXh52aBtlDKUOTdDBeeEAe5WPAMYrn2CJ5knU9ROjpjpKlCCJ1NY8FLOau2Yqdib86ZidyueARQAgZA1zjtygeKHJ8zfghbiCag3dKEBLwQNq3E34pnrLj8/qvPc2wpOz/HrVNngFoZbFoELoQRPMlRgg1Kuhheufyp7hvHGuw5vbW62ugYKRMZbpcKbauWVKlyfZYXFJ8DbqQpLG+O5mnu8J7QmVZix6tc84O0AaHYbjuTlDKX4qOxYv7bmAW8bwBoiVfu87DngNQ0YhkznAk9naWvq8QROABE7AAqY7Hn7klRrVYDXBKCg1d8wAN4ONxO2/RLwms/Az3hnzS4D0UIGAq8NQOC1AQi8NgCB1wYg8NqowvL8Qh4CIVrIQOC1Aejbx4DXDmDJHoXCXm/lEAAEIAABiAAgAAEIQAQAAQhAACIACEAAAhABwO8CGOhKGRJNxpZZYGmlXhVz7wiIzeEZM6X+kOfCk8WP75/56ysnMaV3HQHj/cWvvyj8I24UEQACEIAIAAIwmvFJgAEAn5Nwqgtf3PMAAAAASUVORK5CYII=") +} + +.fileList .ico-folder-unempty { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlFJREFUeNrs3M1qE1EYxvH3zNcx09ovaUot6FIUNyJ4A+IFeAfRXINQFIS60IV7Vy7cuPUaXEtEkG68ANEYjaZNE+YjM56ZhBIxlIykMVP+D7zMJnCY35wz7zmBRKVpKuTfY0EAIIAAAkgABLCccYp8+Nf7+4t8L3VTL+c01mdTr0w9cc7IRLhp6oW/cU0s1z/1wdIk3AmPmo+iXtM+C4Cbpt7o85c9K7udJDz1AZUpz6+KAayX/R1om3rtLW1fcs+tznVgpVT+8FSRs7BWi+XdbNSeuZXqrl7ezhbW3Mfvtj6WtwsbvLuOXt/1lrb+C97xTPzZuOeZ62NTNVM7pdk+6HVxKxfEnkPTOGkGZk1kz/W38iepLK88UzBNTMULsQ+sZx0lfyWOOlgQxhJFA3bJY3FdW7TnTATcHHWU42R4K1ceoDaWg0/PJwJylJvlUa7XDyUIYjnqBTJot9EZy2Gnl69MrR3xK95kwAxv7fpDWcPrr6zc2ht+H7D/9A9AlvAslnDU/SJxvy16EEm/UUPlpNOYqX7LFaeyMQRUSWr2f6ksX7wqynYRmmYLaiZa0PkqmZ1jDsOiV6s5oqQhOtMc38yLLzMLD74N34HKUqgURlQ0kVkEQAABBBBAAiCAAAJIAAQQQAAJgAACCCABEEAAASQAAggggARAAAEEkAAIIIAAEgABBBBAAiCAAAJIAARwIQCTdBAjUTAjs8RKlXoXdFoCYjG83EypRvaL9TtB98fb8PD7DZb01EkM3gdzva34I26aCIAAAkgABLCc+S3AAB9MnrCxlmzHAAAAAElFTkSuQmCC") +} + +.fileList .ico-fromchromefolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABCVJREFUeNrs3F1oW2UcBvDn5KQnbdYmTfPRLMnafdCLUcfGIhM3O2WgOPFiu/FCZBY6BooyvJAJiqNTL4ao6IV4oVJERbwYKoKiBTdlaLeVIV3Z/KhzW9O0OUuWpGnaJWnP3vd01LUGbba2W9LngT9Jc5JzyC/vef9vT0MVwzDA3HwsJCAgAQlIQIaABCzPWG/8IX2ivZzfS4eo95foWBFRXaIOWStkIIRFvbsisB6qZl/0gxmT+eDV1PCLuXRMrQRAr6gjNZ4mTa0Sb8fILfoBFTHx2Zw+CMCOcp8DVVGf2OpXNlWtcC7pgRWLYn545Q74qubwPmhzeu+MJlIs7ZEDi3LgruDhW93Fbqu9/gWbs1HOSrcNUEn1PKmJ25ellajgUkAuAB4EHrQ6N6zV9tuGl77QZ47ATs3hE5+kH4pa9d+Ne4HiWB1egL1MiYFXuCPWgR2yoygWY0k62PQ6IIdKiWwi3usdheGvcgQkIAEZAhKQgMsnC3I5q8VVgz13+dDqseO3xDi+P38FRy+lMGVwBP5v1jir8UpbM+Ra/KMzMRNw36aVaN/QyBE4n4QcGk5GR/HWqcjMiOu/nMWBe0L4OTKKs/EsAYtlVZ0N69016NOz+OlSetY2CZoYz+O17c0YyebxzqmhioUsGVCeqk9vDmBH0z9XgLsvJPHe6eisOa/z+EXUV1vxyFoXOu9rwv7uvxAdy1UcYMlz4KPrGrA1UIc3Tkbw2Jfn8HbvELaHnNi70T/reZFMzjyVXz8REaN0DE+0eityBJYMuFMAHvk9juODaRTEkDt2MSWQBvHQahfuDTqKvkZ25I2+WgLKuMVpeT41Meux3uEMvvgjjqc2+eGx//uibCY3BU1VCCgTnyiY674bU6up+OysDl00jufuDmDu5cWwvxYDyQkCynwzkMCuFjfuF02kUYy2h9e48OHOFrMrvynmO7kufFY0mWrr9K7ldlnfisU1u7DI1wKwoaYK+8OBmcc+7o/NnNaHewbx/JYQtoUc5hypqRZ8fk4350oCisilSlffCL76Mw6vgJTdNpObnNn+a2wMz3QPYIs4bSXe6ZGM+RwupOckMV4wq1iSYp787u8kr8YwBCQgAQnIELAcAO1qAds8UTRoE8sK8JauSFsUA2FXDA94I+JWh2aZXlD3pxvwQyyEX+J+ZCetBJybVkcCbd4hbHVHUWvNF90ua9/afvQkGvGjHkDvFd/yBlxlz6DNM4QdvsF5n6ZyRMrXyErmbTgaC+KYHqwoQPkNVcPRvIHd4CYiv6HKLsxlDAEJSECGgAQkIAEZAhKQgARkCEhAAhKQISABCUhAZl6AujFZoESJuW6mS8APrqZ0ELE0PGkm0iX/sH4wN3pZE/W4uO8nz7wyLOpTUS8p/EfcbCIEJCABGQISsDxzTYABAF5pTWwmgxlgAAAAAElFTkSuQmCC") +} + +.fileList .ico-documentfolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlZJREFUeNrsnE1rE1EUhu98ZCZOTSbYpB9GKgguBHelP8BFodtuuxLyJwqKovQ3uFLoRpeu/Q2CgktxJdgPTChNQtskTTI9N1hw4cKkk5nMzfPCyc3qXu4z57znTi7EiqJIocllgwCAAAQgABEAAZhNufqj9fmpCXupSbxNaK0DiX2J164hibAu8Wbh7iPleMHUF4sGl9Vu8/hZr/XbMQFgReLjrfKa5+RkO1Fv6gtaYnx+uKQEYC3rHuhIvPdLq2u5hTDRhS3bGj28rAPc84qVTT+s0IUn0LYblHb9cFm7UnpdWDqwJ+MLCd2Kq5k5PgQl5RUWr0sp1WPMK6+4JE9yRVlOLkMJOJTE68/EObCmO4plR4l0MBPfRCpplwGvcgBEAAQgAAGIAAhAAAIQARCAAJwbJXqpdHlxos4a31W/04zn6bt5VVxdV26+MB8Z2D76Fhs8rWG/o1pHX2XO9nxk4HDQHY1hdUPlgsUbz9f48UkgXqhO+5fK2/eVm8CdcKoArxUHvL/VOf05GvOFe4mXszFNRENMo5yN6sK6nDVE40s4LpUfbv3TE8lAzoHjddJJsg2AMw6GEgYgHhirB86qBeCBlDAA8UA8EA+khAEIQACirADsndXjn/O8YX4X1teQo5u0wy9TXcPYDLy9/FjZjj+9zcjceg1jM9ALyurOgyd4IAIgAAEIQARAAAIQgABEAAQgAOcbYD0a9CExpv4wq2uA77rNugLiePA0M9G+/j3wZa/d8CR25PsKeP5LxxIfJJ5b/BE3TQSAAAQgAiAAs6krAQYAXiGbDBfBqf8AAAAASUVORK5CYII=") +} + +.fileList .ico-fromphonefolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjNJREFUeNrs3E1O20AYxvGxndgQq04ESfhIxaalgjVnQOq2264q5RJIIKoiztBVkdi0S9acoVfox6qoEQkgjNriNLF5J1Cpm6glJHEm/j/SqySbsfLzeGZsTWIlSaLI4LEhABBAAAEkAAJoZnJ/fwg/vjL5u9Sl3o3pWCdSh1J7uSnpCBtSb/3ldeW4hZEfLOn+rkWXje12eOpMA2BF6mi2vOI6efk6SXvkB7Rk4POKVSWAddPHQEfqvVdaWsn7xbEe2LKt3skzHXDfDSqbXrHCLDxAXuQKpS2vuKBHpfRmYZl5XXndldJTcM2Y5UOhpNxH838upVSXMW/coCpnclFZTt6gDhhLx+tMxDqwrmcUy07GMoNN451IJe3LgFs5AAmAAAIIIAEQQNOSzvPAJFY/z7+o6/BExZ3rh/WA3IyaCWqqMPfk9kFdFgB/nH1Wvy6+DueOWE6APhl6i4pffpaNSzgKvxnR5sQCxt22EW1O7hh4l/Lq86G00/p0zCzMMgZAAiCAAAJIAAQQQAAJgAACCCABEEAAASQAAggggARAAAGcwqS6tSPNLRlGA9qO23cz0L/2y/RD121mBtALHvfdHzhor/SCWnYA/fmnSv9YMAq/S0+MHtibPcFbkjZXMzQGWrbyy2u9YhZmGUMABBBAAAmAAJoK2Ey6HSTumTuzpgY8iC6bCsT74WkzyaG+lXvdvmq5Ui/l/SI8/5WG1AepHYs/4mYSARBAAAmAAJqZGwEGAN+Yh/QHkxJLAAAAAElFTkSuQmCC") +} + +.fileList .ico-mix { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhFJREFUeNrsnL9KA0EQh/fukjtNNBc08S9oJSJo5TMItrY2CnkJQVEUn8FKwUZLa19BO7ESKwtRTBASEU1Mcs4GtVFRA5u44fvBcOF2YG+/m53Z4SBOFEUKNS8XBAAEIAABiAAIQDsV+8mhdLpsy1pyYrstmutabF9sK9YhgTArtpMcmVKenzA+WVR7GS0Xb1crpTuvEwBmxY66M2O+F5flRBXjEzqS+IJwQAnAnO050BM7CNLDY/Fk2NKJHddpvDzbAW77qexcEGapwk1oIZZIrwThoM5K7avCUmV9ua6L6XI7as3xIZFWfm//+1Zq6zFm008NyJscUo4XtygA6xJ41X9xDszpiuK4UUsqWCd2Itl2bwNaOQAiAAIQgABEAAQgAAGIAAhAAAIQARCANqgl34Vfnu7VY+FCVZ+Lnx+gK1TJzKSKd/cZ9bU6Ah9uzr5cpJa+r8dN+1odgfVauXHNTMx/GitcHn+Mm/QlB1JEAIgAaGkRcWNdql59biT278ZN+1odgT2D08r1gq8fQO7rcdO+puQUT5ai1PgMe7EJla7OyYG0crRytHK0cgiAAAQgxxhaOVo5WjlaOYoIAiAAAQhABEAAAhCACIAABCAAEQANA8xHtSok/qg3ZnkNcK9czCsg/g2eZiba199ENioPBV9sUX4PgedXuhU7FFtz+CNuiggAAQhABEAA2qlXAQYAvkAprthin2kAAAAASUVORK5CYII=") +} + +.fileList .ico-musicfolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAexJREFUeNrs3L9OwlAUx/HbFopCBKKAKImJg4OzPoCLiaurkwkvYaLRaHwGJ01cdHR28An0EYwjCREYwMTI33qukcVJVHq59ftLTiAMtP309B5yB5wgCBT5eRwAAQQQQAAJgAD+A8DW/e4kX0tZ6jykY1WkLqVOYhFphDWps9TiqvL85NgPFvS7pXazut9pPXtRAMxL3UznlnwvLpcTdMb/2LpKJTIFJYBl13I8T+oqkV1Yiqcy4a59rvNx82wHPPXT+c1EJm/sBGwG3I4ls3uJzLxelYydREwmqy+vh1J6xJZs0RM85c/MDR8lc+chdeynC3Ini8rx4hY14EAar2f+RurfT3qiOG4QygSLWvQamDf9GNgOSAAEEEAACYAAAgggARBAq2JsS7/xdKeCwfh2U9zYlJpd3ohuB44TT2fQe4t2Bw6TW9n68++sP96yBjJEACQAAggggARAAAEEkAAIIICRTCjbWc3Kg+q+NujAnyaqeKF1YJS72/o10HR3M0RsAPSTuZE+Zw38knRpnQ6c1JjubuunsOnuZogACCCAABIAAQQQQAIggAACSAAE0DLAWtDvITFiPs1qGvCi3awpEEfD02aSS70jfdR5qftSO/K+CM+3UpW6ljrgHyx/GQABBBBAAAmApvIuwAAZ2pRkuq79SQAAAABJRU5ErkJggg==") +} + +.fileList .ico-picturefolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+tJREFUeNrsnE1PE2EQx2d3u7vttts3WlqKKYrBiO8Rj179AF49mfAlTDQajZ/BkyZc9OgX8O7BA0ExctGIECmlon2DvnedeaAQA9JW0227nX+YLIGQ7v52npn/PLtBsiwLWP8uiQEyQAbIABkgiwEywBEAmH93d5CvZR7juU2f9R1jAeOJyyGJMIfxzJuYBUUzev5hVqM2Wclt3q/mtxQnAIxivPZEkpqi4uVY1d4vWxlAD4wDApyXhxyegvFSD04kVW/A3tonS+LmDTvAp5o/eksPRPt2AsMM8LbLCN7TAzGqSn07CRd2Vg2PDzGoxU4OCz2EB5o51lpK/TsPjMeafxzvZBwkRR2iBGxi4tX7fyPJP1FHkWTLlg7mNFENjPZ7GQw7QBYDZIAMkAGyGCADZIAMkMUAGSADZICsjtXzh0r1SgGK6WU85u25IN0PvtglcXREBtoJb++G5fEzPzorA0n+xBxo3t4+/KkUUlDYfG/rDbOhBlogya6ewyPp5oTzamBbvFYD6qUsNOolUN0hUDQvN5FO1ajuQD61KI4tuQNJ8I1fYICdiOpVs1be65qaCaVfX6GcWwOXOwBu/3A8Ye2bD6RCT+EJnUZYpwQ0M34FZEXHZrDBRrqD4icO1GAOJMn4peDvmgywbe1w+0F2uXHZru7bDgt2f36GRm0XVCPCNbC9JGwWF7EOLkF27e3BT1VPCIzQGQbYicgbBpM3oVpMoY0pCxvTqZejzt3Ev6GMdemmyGaKkfOBiurBRjLdsSmnJV/KfhPwjs7B5ugB7MYzFtIfoF7OYf0MgieYRNPtE1lH42KjWoBKPsUA/zbjig0CSUKrc/XIMm/tvBjhGdj+8sb5AKs7Gax76T+Gfsom3YyDRh1Yko/srtCI509cP7HOCQvk5Ays7W5DMbOCy614eNGKSm+9C1BkoBXVACNyDnRfXCzN/MYiKLoPAokbA/n+om0Ay9k1AY8ageaLYQ2bQssSPqxxtRKOcevYIFahkFqCImYTZRs1CzN2eWBf/rQHIE4dxcwnHNM08EZnj7Uq1I29mHme0BTsbK1ApbgpvKI3ch6N9dho+0DasiIYtKlKM++JoxHOwubENRCGhEY6abAf29h2dkZ4ui28Y7pCt7fKuQCpOfS+zq4fjImOWMLUGFoqpJdF2HJRNk4kPc1A8mVG+KzwePbt8gTAh13bERlIXdcYmxHhVPGbCQyQATJABshigAyQATJAFgNkgAyQAbIYoN0AM1ajziS61D6zDAF8UcllgCF2B4+YoRZoQ/VRtfBDw7iD38cZT0eiZ66vMB7wf7D8TzFABsgAGSADZDHAfum3AAMAO2tj3VmRR4EAAAAASUVORK5CYII=") +} + +.fileList .ico-videofolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABSdJREFUeNrsnNtuG2UQgGft9a7Ph8R27KQyLVSgCCICBSTENQ/ALVdIeQkkEAjEM3BFpd7AJQ9RiQsqJGgJIIFapS0hjp3E8dler73MjO3QSNheO+na3sxIv7KKvPNrP88/x00Uy7JAZHbxCAIBKAAFoAAUEYACcDlFneWmyr2PFvFZdnB949Be+7ju4PpSdYkh3ML1dWh9E7xa8LlvZnU7G+1y/hOjUvC6AWAK1/eBZE7z+vBxLOO5b6ig49NjaUCAO8vuA724vtXj2ZwvFHN0Y8Wj8Je37AC/0qKp9/VYSqLwDPKBGox/rMfWyCvNLwpjRNXw52e4KLRuLE36EIyDFlkdHqW5pjFfaNE0fpMZULy+JTLAHhqeuRB54A5FFMVjORLB3FiJpOZ9DKSUE4AiAlAACkABKCIABaAAFIAiAlAACsArI4szVLJ6YDSOwagXwGyVwTRq/DsWxQOqFgbVHwMtlAYtuNqf7AhAZGR1oVnag+bpHo0LR8I12xVerfJTbvwG4tchkLgOiuK9ugA7zRLUDh9At9Ps+xPVD3okgxaWYkiqHiV6CK4Kva4BRq2A6xCv29A4/othRjKvgy+QuHoAW5V9hLfLgAhccPUm+KPX/ueTygAkINgkQHqTwdURYM9sQfnvexBeew3v3bg6ANvVA4T3K1/r4QwC2EKXZvcoKuCP5fC+LFTyv0AH/SbpUtAn6pGs+6MwBYjqAF4g/gJEsttTwHsGIx7x2MZbZ1ZLOs3WqdsBWlDN3+egQJYXSm1eUJ+C1vsq+Cgqo85q/gE4PSN2FCD5rm6nwT4vnNm6JK0KRDPbbJGkm/ZwJ0C0kMbxQ74MJV8Zm36cPvkBaoXfOMWxe5yDiRf5unHy6L/80U0AjcYRpx/DVGWsn8S0hSyp/PRHjrR2hPwpgaTPG/WiCwHWj/pRN7LOx85WwMHEuYTWSJF2shl6ODL3v6xj9wEcRkgtlJzu5GN1Ut7/CY/mw4mf9QVXzu3lKoDk4Dnx1CIzRW+qPCoHP6N7G+0XqV5+di8XAbTwwc0zhz+zG8AyrlbYHRNMtEG8Mh1LZxwCeHnv3li2I6ziriPsGVrHqI6LDaGmQXhM8m11jQtb+eIC9PXfnjeN6kz3BzDPi117h9OgkYGKeogoXl/IfQCHLadpczSypuj6LUy+X554LDuNk3N7uQqgFkqddWLsOngCkci9d3bvpEqnXTs4t5cTojpngSvg1ULQNeoMsZ9Qj4AdXgNVj0Bw5SXbwaB5+pj9K+1Be7mymRBI3OCf9aM/x9a50ewbCO+mbXjUrW6UHp3bw5UAqWus+uNcr1YP7l9ak4JaZGR9pNvpzrTD/UAFrWubUxqavtWLf1wc3uEu18qkk3Q7lf/NCWB/cBRdf5PbWeS3KljnjivPJtXI7eo/rIt0jktxXAOQIxceNc7pvDq3uUp7d6FVfmIvOqPV0Rj05PFd6DRPWAfpIp1zeRaYk9CQPJ57l2cZPBgq/M4NVy2c5uG5R9UxIQ5S7QZds9nv82EtTHlkb1BxUCs/srY1F8ubO8DhcY5tvM1W2MDIPBycT2rL05gziIk1jznnLAvxageB0HJJHrAbmAx3WmXo4fXQ0ihAeHwB8NGrHeEsWmYAFkUW6g+uCQzVvAFYHpG3swSgABSAAlBEAApAASgARQTgfAAWra4pJKaUAbMiAbzdLhdBIE4Hj5ih3KFmwudG9UjD9SFeZwSPLcnj+g7Xp4r8I24JIgJQAApAEQEoAJdT/hVgAK5f795U9dlNAAAAAElFTkSuQmCC") +} + +.fileList .ico-sefolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/9JREFUeNrs3G9IG2ccB/Dv5ZKLuUSN1miMq3/atVU6aauu615so6ulY+2gK0ihrFhwZRR801LGcLJRcXtRxmAwBhtzyFYHZUPYi/XNBlspdK3rnznHWitpFfvHNkprUqOJJrfnuZYiYiA6tRq/X/jhn3uSI588d7/nzqBiGAaY2cdCAgISkIAEZAhIwKUZa7IDgx0HFvtrqRP19QLt65aoVlFN1hSZCJWivnD6yqBq+rzvzIiNF0SGB96PBu+pqQDoEdXuyCnUVJt4OUZ03neoiBOfPTMXArBuqZ8DVVFtdnd+oc2ZuaA7ViyK+eYtdcBmLcOz3Z7pWbxNpP5c0bzs+GPL1v/7FG9adfd79sw8eVZ6aoDK8PlaTXz9QJRsswWJBjbEf1tMeBB40NJXwJqmPzW8YF+XOQOPaRm54p30QlFtiUdfnzvAjOLKOXiWuJh4E4tiHVgnO4piMRakgz1aB0SRKpFNxPO4ozC8lCMgAQnIEJCABFw+mfPbWT69FNW+d5Cl+TAWC6F7+Cx+H/iGgNNFVWzYln8QG7J34BnnevN3ofEhE6zN/y5ixjgcajqy7D7cj9wm4OR4HWtwcO1XyHOsMn8eivTjzmgP7BYduwsbsDlnD1p6DuHu6HVE42OcgZOjW92oLzsBt+bFucAPaO9rRnjigblNzridK4/g5bxa1Je2ofnvakRiI2wik7Mt/20TTx6qJ/xHn+DJjIrz3o+9x/DrnS/NQ/dVMZZdeEo2Zu80oU7d/CzhmJ/7PzVhN2S9xi48NTlpK3F6oBXHqzq5DpzNg87c/Q7lWduTGpvKDWTWM9Af/NNsEg0XqxAcD3AGzjQVOW/AoqioKWlKOKamuAmfb+lLeqYuK8ASVwX6Hv6FTdmv463Vn8CuOp9ss1nSsKeoEa94a9F1/xezeAhPiVzrfXSlGvtWH8cWT425aO4f6ULciKPQVW5eoXQMtuPkjUZeC0+X2+FuqBYNLdcO4cXcvXgpbz+KXBvNbTdCl8wmIwF5MyFBvvUfRuWKXbg8dAoXBn/CH/dO8m7MTBIY6xXrwF4wvB9IQAISkIBzEodY1lQ418KtutiFk9dXsF4vwWZXKZ7Ti2FTHj1dz9gtnA/9i86wH6PxKAGnZk1aAapc67DJ+Sx0cek23XZZe42t6Bzx48JIN/4J9y5vQK8tG88LtBfSy5I+TOWMlNCygrEwOh5eFXUlpQDlJ1SNjKJydoNZRH5ClV2YyxgCEpCADAEJSEACMgQkIAEJyBCQgAQkIENAAhKQgExSgAEjNkGJGeaxWUACtkSGAyDizPCkmUir/MP6h9HQoCZqn/jeS56kMiDqe1GNCv8RN5sIAQlIQIaABFya+U+AAQBLjB3QxtGdmwAAAABJRU5ErkJggg==") +} + +.fileList .ico-access, +.fileList .ico-mdb, +.fileList .ico-accdb, +.fileList .ico-sql, +.fileList .ico-db { + background-image: url("../img/ico/ico-access.png") +} + +.fileList .ico-c { + background-image: url("../img/ico/ico-c.png") +} + +.fileList .ico-cpp { + background-image: url("../img/ico/ico-cpp.png") +} + +.fileList .ico-cs { + background-image: url("../img/ico/ico-cs.png") +} + +.fileList .ico-js { + background-image: url("../img/ico/ico-js.png") +} + +.fileList .ico-fla, +.fileList .ico-flv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAz5JREFUeNrsnL1vEmEcxx8OOE1LEUyKthATgqYJ6eKEM8QRZ1+WmrropJs6mLjof6CJL9HFl7nMJY7SmLg0JIaBRBKrkBhDW2Io3Hm/B69eT6D3Qjm4+36TJwcctL1Pvr+3p+F8siwzyLoEIABARxUYdrJcLovK4YGyVpQVd8tFxxOJp8rhltH3nwiHrQFU9DASidyNRqMsEAi4Al6lUmGNev3mfCzGzEC0CnCV4AmCwCRJck3YnV5YYN+3tkYC8bAcOE/w3KZjosghkhOVp09QRByE6OkqPAqInm9j7EJEH2gTIgDahAiANiECoE2IAGgTom/YdpYyC8upVMpVcBqNBms2m6Y+k06nfVZHOddJme1ZKBTqucf3j8ugx7VaDSGsVTAY3N8Y0Uaf1Y1lzzlQhUjqdrv/uW6QEwGwD0QC1Ol0+NEsOM8D5Bf/N5S1W3UT78AfK9dNvf/U61d9P69/3Q5ECmVa+jBGCBuU3+/fd6IZeI4CHJWDRgmRoGmrsRGImES0MATBtAMBsA9EM//GAMB+862urUERsQgRDhwDRAC0mzOBAACdHQed+sWHjXST1mjDgW5z4LQ4DA4EQAAEQAgAARAAARCaqEb6KEfBcTbpcOC0OdCuOyZtBIQDARAAARAAIQAEQAAEQAgAARAAARACQACcIg39tuaXbA53JlO0VFy3/m1NURQ9Da/dbiOEj1Jj3dIP5/MscuVy33P1R4+ZmEzy81+vXhv6c44vL7PY/XusU6+zb7fveAegqkGACKARzVzIMGl3lwViMQ7z9+YmqrDhP3h2loWyWdZcK3AHEky0MSZE8Eg7xSJrlUpsJpPhUD0VwmfevjnwnGD8fPHScPi2PpZ4CNOR8ip3ZKGAHHhoS6XkSFo768Vei1Gt8kVQPQXQcvjmeuF78sYqX3q4BBMAhxQPynfktF/v3h94PfH8GXehEwCnpoioxYLynlZqLqQ86EQxmUgH6ouMCkrNeXq1NkrcgU4Uk+GbCflLXXFvz9PjXjsYlJYKa35LISyfO7tBd7Pw4u3i6Zr5tadSn6yH8OLixY4kf/BVq+fZ9ra3nDg3J8nJ5GeWiOcshzDkwlEOAF2mPwIMAMxjFWM7SdA6AAAAAElFTkSuQmCC") +} + +.fileList .ico-htm { + background-image: url("../img/ico/ico-htm.png") +} + +.fileList .ico-html { + background-image: url("../img/ico/ico-html.png") +} + +.fileList .ico-java { + background-image: url("../img/ico/ico-java.png") +} + +.fileList .ico-log { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA9lJREFUeNrsnE9IFFEcx9/srlMbq+2fXHZdD8pG0WIHKQ92iP6QYhRFtzyVHiI7FJ2KIAiMTkoeMoSwTnoqhKLQQ0kHgzI8FAaZGIiu7JKKRVuLO9P83jrjpOu4M7Pu6rzfF5aZffNmYT78/nzfWxhOFEWCMi4bIkCABZVD6+Lo6CgvHW5LnwvSJ2SVhw6Vlz+UDi3Zzt9ZUmIMoKQ7brf7hsfjIQ6HwxLwxsbGSDwWu1zq9xM9EI0CbAZ4NpuNCIJgmbQLBINkJhrNCcT1amApwLOatvE8hQiRKH3txCZSQIhMd+FcQGTexpiFiD7QJEQEaBIiAjQJEQGahIgATULktLazpLWwGA6HLQUnHo+ThYUFXfdEIhHO6FLOcpLW9sTlcqWjh1vmstb55OQkprBaRUVFysaIOvuMbiwzF4EyRFAqlVoVdWtFYt4BTsV+5AVGyO8zDBEALS4u0qNecBsO0OiD5VNyKqu36vSCZN7GAES73b4qjVdGJdZADckAIRIz1UIEmCVEgKbuxpjCOgW773ojEAFmgKjnbwxmbYzm+nYp+rIx10zbmGwgYgpvMEQEaLZmIgIEiAC39FJwq9uYQrsCtDGYwggQASJAFAJEG4M2Bm0MCgEiQASIAFFoY9DGYApjDUQhQASIABEgCgEiQASIAFEIEAFuWmluJnQ8GkBCkrraI8YAgnieZxpeMpnEFC5YCudKh2urSP2xg+TW3SdrzvF6iknd0QNk/74KZezbxDQZePORTEX/35wNBX107u7KsnXnWgLgegIgFxvryOzcT9LZ/ZxCAKBnGmpJS9Np0t3TT8YnonRuuDJImhrrKbDWth6S+JOk98tz2zqf0t9hqgtDNCUSSQneCyWCAMLjngEK6mzDIWUunMMYXAN4ILgn/f0vjXamIhCiB1Kx//VwxusfRr6S8+eOkKql1IbI7JdSdaUAZmtbrzVroJY87uJ0FM1krl3TS+Net0sZG5cikKkmspGC2qluJpD6UAeZATg3ny74oYBPaRRqlQXS/+7Nzv9aHgsuz4Xap9THk7UkXFHG1lIOGgA0hZrqvRmv11TvoVH1+ct3mrrQKDLNdW7n8w5v03Rh8G9OJy/ZkFO0qcjNQk7PvldDSqPofTZIvSJcA2hqGwT35Fuaby66dP2+mIulnGykM6nv5RDttGaN9Nt3n+i8emk8lzUQlnJd7dc4QwCv3nyQEkSO6eWejROFjntX7IZSuDzofQ8vp2HxdfHwzPDsoYBn2HAX9nldJwRRHJyJzVf/TiSZisQdTl4I+H0ju3zFxw3XQNQW6cIIkGH9E2AAawxpPqroZK8AAAAASUVORK5CYII=") +} + +.fileList .ico-mht { + background-image: url("../img/ico/ico-mht.png") +} + +.fileList .ico-php { + background-image: url("../img/ico/ico-php.png") +} + +.fileList .ico-url { + background-image: url("../img/ico/ico-url.png") +} + +.fileList .ico-xml { + background-image: url("../img/ico/ico-xml.png") +} + +.fileList .ico-ai { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABLNJREFUeNrsml2LE1cYx59MZpKNSTYb2tUsG0trqsX0YrVUL1pvpNSXFgr1poIgpUVEP4FXgvQ72ItCL7wuxStxlQVfSxFBS+lCXSPupl2XTeu62d1uncyL5zmzEyfZ3UlmEpJJzvOHw8mZOSdkfjxv52RCpmkCyb8kQkAAuyrZ7ebk5GSEdedZ+5q10X556NFs9nvWnW12fmpw0B9ApgtDQ0Pn0uk0yLLcF/CmpqagND9/ZnjrVvAC0S/AbxGeJElgGEbfuF1mZATmnj1rC8RGMXAY4fWbopEIh4iWyIYXKYl0EaLQWbgdEIUvY1qFSHVgixAJYIsQCWCLEAlgixAJYIsQQ27HWWwvbOZyub6CUyqVoFwue1qTz+dDfrdyfSe2t4dEImFZT+g1l80+F4tFcmGnFEWpHow4vc/vwbJwFmhDROm6vs7qNrNEArgBRASkaRrvvYITHiB/+DVXdh7V9bwFlsdPgbH0F0jJLAwe/qEjENGVsdW7cTMAA5VEtH/+4PC4VbAex51QOBzmzSu8wAFUn45bDzS0o2bcSYjOeNhTFmhWVqBSvMk/x/ZYp+w4xuudEp6+96wFqjM3wNRVCKfeBvnN93mPY7zeSSFEL39jSEFzX2Vkf02vTl9vav3yrXO8tUP1ZU3gAeqLT0FfmLKyYmZ/Ta8//5Pfb/jQksJbu9QsRDlI1heKJEF+Y7f1w1iPY1Nd4vdjY6ddvyN+4Lu2/66eSCI8zk1PWG6b+RBNyTYpa8zdeILPC6K6DrDy911uZZbb7qt1j7Ux3sd5bnrx01HehANYrfUcFlfdrzosspM1Yc8ANJZnQZv/rSbm1cQgR0zEeTifADqtz1Gi2Fl3XZZzXG+2pBEDoGkwt3wNxK776uW8zuebwXrJqWtlTGXuPhir/1bHS9fPNHZ5Nh/XbQZbKAv0mxSClky6YoHG/wtQmf3VShTRFAx+dglC4YhrrVi+chLMl4t8Ha6XBtLiWqAzlkVzn7vC45DZ/Wjuiw1jp6AAx9eDaSAn6CC5cccBaqXfq/Vc5J0j3IWb2peyeTi/vn4UDmC1lmM7jOjOLz2t5fPtncnMhHgAnafOSvYASPGMtx/L5uM6XgZ1+LQ6EADtU2fUwHtf+foOe103Tqu7D3At+MvbPqj+ceRVuA7XByWZCPd2llcVCgXXt7Po/cBePo0hgCQCSAAJIAEkgCQCSAAJIAEkEUACSACFk+v/wvKd0zB9hwBB/rY/gKioLDbAlxq5sHgxcPjgBcgevwzx3KFab9g2tuF1AuiMKYkMB2VUlmHLWx+TBXoVWhfCW3x4iYNEoATQC8B3D8HK42uwWrReKo9t/4gANqsYc1lJSXB4hrrC+2T+GAFsVluYtWnLc6A+L/Dxf8VfONBYgGNhYKo8jHWx7RYozLT1YFdn7hJAV/ddi3WzP5/g7msrNXYSkruPgRS5SC7cKPvasc8pO5nEc4cJoFvyQBfGmFcvjIfqQoEDlpSBwAF0fbno0Y9H9AisSCEQU0hGhbix65urYV8WaKZ23dMN64tEhMefPbXzvu8kYsZHP9XAuKGXn+yFypJYBw9K0jDTOx6Y8ewnvl2Y1INbOQIomF4JMACqJM9nuTUMmgAAAABJRU5ErkJggg==") +} + +.fileList .ico-bmp { + background-image: url("../img/ico/ico-bmp.png") +} + +.fileList .ico-cdr { + background-image: url("../img/ico/ico-cdr.png") +} + +.fileList .ico-gif { + background-image: url("../img/ico/ico-gif.png") +} + +.fileList .ico-ico { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA7lJREFUeNrsnEFIFGEUx9/szu66uOvumCtuSlREkB2ChII6dEgisFtCEQVBetBD4KU8lBAFURejg0HSqYgKjwke8tBBrEPSaYMMCrRW1NpCxTZmdvrep7vsrLObO+vubPO9P4zfOPNWmB//9733fYsj6boOJOtyEQICaKvkQjdjsZiXDQPsuMiOZqc8dHNLywM29G42PlRXZw0g041wONyvKArIsuwIeNPT07AwP98TaWyEYiBaBXgJ4blcLkilUo5Ju6ZoFObi8S2B+K85MILwnCaf18shohPZr0NURGyEKHQV3gqIwrcxpUKkPrBEiASwRIgEsESIBLBEiASwRIiyaHDq2LoWl3Ob1dfZ2Z5Qa2svAVwXW9tDIBDg55IkZa7nO5+ZmaEUzpbH48lsjGRvJlvdWBbOgWmIKE3TNrgunxMJoAlEBKSqKh+LBSc8QP7w66mcvVVXLMiqnwNXJ19A4v45PpYLotvt3pDGua78bwEm348ZxnIIAZpBdMRKxHfghGEsN8Rs5zmiiPiPnOVHJZT+6gJbGsc4sNJCiMV8jUEATbTZAlLVKYyVN1fK5acVhUgOrADEqnVgttvM3Fg1cyb5jAASQAJIAEkEkAASQAJIIoAEkAASQFIxqrrdmEI7L3bvEZIDRXCg3Y4iBxJAAkgASQSQABJAAkgigASQABJAEgEkgATQwSq4H1j78DTMCQ6oFn/c+2AN4FqET2yCapJS2LYULod8uw5C5MJdSIwOwsq70cz1UHsXBLP+H+RP/COsxl7D0sRzw+fN4lamRg1/y9EAzdTYPQSumoAB6rbO6xA63s3PESLeb2Dgc+OUU32gdPTx67mwhajCwaNnwBvdCz9Gbhlc9H3kJiQ/T7H7a26rbevgcYtPrhriEi8H4df4MIctK1HxAPpbj/FUxVTM1cLjKzA/3MvdhSAxTk3EN8SlgSJk4QCiq9Sf8fxFcB2YqyZoChmV+r3M4r6BHBbQgbQSKVHoKnRhvvmxZeAVuPxB5rKlvHGY4rKyvaCTHQsQ5zVsbczg1Oxu46mJabw08YzPl2aFIj332dHK2A4QHxoh1XdeMxQB7BUR7K/xR5k4dGvD+TuGOGxjsAJjJTYrMI7vA7EAYKUNtXfzfg6PdGpjv4cONcZ1mcbZ1UhLhV4486X/kCapSeZSCcSUDrrHn9p5+43bUgpr0f2TkNLwtT4CsmPPzJ5da9o3aTmF9fodJ1WAMffCp8PS8qJQ75jRAw2qFtnzFhlYTmESNdIEsNr1V4ABAKATVWjNArx4AAAAAElFTkSuQmCC") +} + +.fileList .ico-jpeg { + background-image: url("../img/ico/ico-jpeg.png") +} + +.fileList .ico-jpg, +.fileList .ico-JPG { + background-image: url("../img/ico/ico-jpg.png") +} + +.fileList .ico-png { + background-image: url("../img/ico/ico-png.png") +} + +.fileList .ico-psd { + background-image: url("../img/ico/ico-psd.png") +} + +.fileList .ico-webp { + background-image: url("../img/ico/ico-webp.png") +} + +.fileList .ico-ape { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA7JJREFUeNrsnE9M01Acx9+27g9jIkOZTMAYiTEZF4melJtwNJ6MHgwx/jlovHhSL0ZOejNGo4l6Mh7wZMSjHDyoB4NycolZNJpFZhiJSmRs0K6+X6FzsNKtLdtK+/0mTbf2tUs/+/5+v/fe8uaRZZlB5uUFAgBsqgS9k8lkMsB31/l2mm/dTnno7p6eB3x3sdb2W9vazAHkGm1vb78ajUaZIAiOgJdKpVh2ZuZCZyzGjEA0C/AswfN6vaxYLDom7LricfYzk9kQiNVyYCfBc5qCgYACkZzI395HEWkiRFdX4Y2A6PpujFWI6AdahAiAFiECoEWIAGgRIgBahOjRm87iY2G5r6/PUXCy2Sybm5szdE0ikfCYHco5TnxszyKRyLJ7PP+5rPc6nU4jhMvl9/tLEyPl0Wd2Ytl1DlQhkiRJqnDdek4EQA2IBEgURWVvFFzTAM4/e8zEb6m6f46wey9rPXFOv81KKJdP1RkF2fAc2Ah4Rj6HIPp8voowXutK24VwZOQS88V7N/y+UibN/j65Z+gaFSA5USsX2hJgPeBZuS9BJGjl1diWIWxn0ey7UQcCoAZEIz9jAKDW+LbGAuLqfmAtEOHABkAEQKs5EwgAEAABEAAhAARAAHSl6jaUmx97yMTvX+BAs3IDvLo6sBGyg8s3dQ60g8tRRADQoQCFPftMnUMRWVHr8TNwoO2/fRu4fFN3Y+zgchQRAARAAARACAABEAABEAJAAARAAIQMSXe1ZubyKfwzGVf89lPzqzUDBpY9OVGLVRYhIoQtqmkTqltGLimzxrmXY6zw4W3puJ8fo1VMayXnc2zh1bjSdr02pOKvWfbnzqizAXqj2xV4BCXQP7AKoCparrX09fMq4OGjJ1cdW9vGNVU4eOBwyVEEkoBWU27iRcmhru/GBA8eYoXJd2zx09RyoerfjxxYqyhkPaEwW0x+VFxIEEODwyz/ZkL3uvDQMWVPIeuLblNea+XBBe7Uavfa1AD9iQEl0UvTy3/msJScUqDSpjpSCw7BpoJD16oA7ZADGwqQch2BIkVv3K0AWw7QDnBsB1DNdb9vXVEcpaqFh2docIg7LIyxcLXqSy4rh6f09nk+VIsLiohO8aAQXpoYrzhH+ZA2ApybfV7zPbWKSKM70rqTCdPXzkvBQt7Vw71CMFTcefORz1QIi1273kscsBunZOiZ6dnFrt5J0yEsdcSG87L8Wpj5MeBZmHeVE+WW1qIY656SOnYcMR3CkE2HcgAIlfRPgAEAIIZMFb61acMAAAAASUVORK5CYII=") +} + +.fileList .ico-avi { + background-image: url("../img/ico/ico-avi.png") +} + +.fileList .ico-flv { + background-image: url("../img/ico/ico-flv.png") +} + +.fileList .ico-mkv { + background-image: url("../img/ico/ico-mkv.png") +} + +.fileList .ico-mov { + background-image: url("../img/ico/ico-mov.png") +} + +.fileList .ico-mp3 { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/xJREFUeNrsnLtv01AUxm8SO0mbUOKKhj4RaoUqpQsFJuhGOyImHhNCBQYQf0BZECwgJgYQSBQxIIaiTsBIBwZgQIVOREIRCBTRogaBVEjzsmPucevgJm4aX+fhXp9Pct3G17b883fOufe6jkdVVYJilxcRIMCWSqi2MR6P++nqKl3O0qWPl4vu6++/T1eXam2/s6ODDSDV9UgkMiVJEhEEgQt4iUSCpJaXL3ZFo8QKRFaA5wCe1+slxWKRm7Dr7ukhP5aW6gJxqxzYBfB4U8Dv1yCCE+mf97CItBCiq6twPSC6vhtjFyL2A21CRIA2ISJAmxARoE2ICNAmRE+16Sw6FlaHhoa4gpNKpcjKyoqlfWKxmId1KMed6NiehMPhNfd4/nPZ7PdkMokhbJQoiqWJEWP0sU4su86BOkSQoigVrtvMiQjQBCIAkmVZW1sF1zKA6acPifw10fDzCHv3kdCp89XbrIeycarOKsim58BmwLNyHoDo8/kqwrjclY4L4fCZy8TXM1D34ypLSfL38V1L++gAwYlmudCRABsBz85xASJAM1ZjR4awkwWz71YdiABNIFp5jIEAzca3NRYQV/cDa4GIDmwCRARoN2ciAgSIABEgAkQhQASIAF2phg3l0jMPiPztMzqQVW6A11AHNkNOcPm2zoFOcDkWEQTIKUBhcJhpGxaRdYVOTKIDHX/3HeDybd2NcYLLsYggQASIABEgCgEiQASIAFEIEAEiQASIsqSqszHS9A3yy+WAJPhx+wkbQJDfwmtPPCq/xUuIGMKNDOF6Kjg2TtrGjxP5yyfyx+RNItgGbXLv35DVFzNEHBzW3mYql5pdJZmXz7V2xv10ZeaekezrOf4Alk5IwXilXaT4++eGzwOHDpu2h9e2ChS6rh0Uavux09pnABngAXAAGjh4RNumZjIlwNxVYXCQf2T/xjw7Mko8wfYKqGZapQ4DATy4GcpisgRLX/t6B/h1YG7+reYUY5iJsVEttK0qPfuoIk2ACh8X+O0H5uMftBAW15+agfPAgfkaL7qd5jwNkgE43BDp2p1Sji0w3Ixt40AIOVhECg0uFHIfhDUABJDlKi8k0BZynjHcIXRhgZsSOjmpPa0rdyc3AHUXBscmtGrqjx3Q4AGYWopINUE7SBEQyulZjodycJEQugAREj5rzopM3dLC1qxQcT0W1kMWnAKhyJqz9GPo+RRyIaSEfBOLSMv+M6EQX8t5dvprkAvL86TeEW+Wqn710+KVC0ogl3X1cC8XCBZ7b077mEJY7t7zTqGA3fhl8XDNcO1y98A8cwgrndGJrKq+Epa/j3oyaVc5UW0LFeVo34LSufsocwijHFqFESCqpH8CDACOqX4es1WjewAAAABJRU5ErkJggg==") +} + +.fileList .ico-mp4 { + background-image: url("../img/ico/ico-mp4.png") +} + +.fileList .ico-mpeg { + background-image: url("../img/ico/ico-mpeg.png") +} + +.fileList .ico-mpg { + background-image: url("../img/ico/ico-mpg.png") +} + +.fileList .ico-rm { + background-image: url("../img/ico/ico-rm.png") +} + +.fileList .ico-rmvb { + background-image: url("../img/ico/ico-rmvb.png") +} + +.fileList .ico-swf { + background-image: url("../img/ico/ico-swf.png") +} + +.fileList .ico-wav { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABE9JREFUeNrsnDts01AUhm8S59E2lKbQ0PQl1AgVpQsFJuhGOzMhFlQhXhKoCwuPBcEEGwMIJGBCDLDCyMICAyqEpRFV1AqIaFADKqpIX/EDn5vc5CZO09hpXNc+R4qcHD+i+/n/z7Fv5LgURSEYxsONCBDgtoZQa2UikfCpi1vq66z66rXLoHv7+h6riyv1br+7vd0YQDXudHR03AiFQkQQBFvASyaTJLOwcLkrHCZ6IBoFeB7gud1uIsuybWzXHYmQX+n0lkDcrAZ2ATy7hd/noxBBierHR9hEthGio7vwVkB0/GVMoxDxOrBBiAiwQYgIsEGICLBBiAiwQYiuWtNZ6r2wEo1GbQUnk8mQpaUlXfvEYjGX0Vs524V6b0+CwWBePa4Sl43ep1IptDAfXq+3ODHCu8/oxLLjFMggQkiSpFHdRkpEgFUgAiBRFOlSL7htA5h99YyI35JN/x5h/wHSdvpC7W0KVuan6vSCNL0GmgFPz/cARI/Ho7FxpSotZ+HgxCTxRPq3/LhSOkX+PX+oax8GEJRYrRZaEmAz4DVyXIAI0PhubEkLWzlg9l2vAhFgFYh6fsZAgNXub+tsII6+DqwHIirQBIgIsNGaiQgQIAJEgAgQAwEiQAToyGjarVz25RMifp9FBRoNJ8BrqgLNCCuofEfXQCuoHJsIArQpQGFwyNA6bCKFaDt1DhVo+bNvAZXv6MsYK6gcmwgCRIAIEAFiIEAEiAARIAYCRIAIEAFi6IqaT2umr57BfyZTI3L/hfGnNX06HnuyY6xv8hAiWtjqNdAd2ktCtx+QlrGTZfn2S9do3tNTejDGFWituu2uiUma9x85Xsx5B4doLjA6pnXN8AhdB8sdD1Be/E2k+RQdMA+KgePz3mj+vZT+UXYCYHpeWV0uA5Kbm6HH9cUOa77TGxuh269Px+3RhWGw7s49GlCimvdEBop5eA8Dz83OFHOgOsitvH1NQQLQYn1KfKYngs/ByYHjr019sM9lDCiKVx2AAvXk5r5SVcE6pkaAB8CKAI8eozCYmnzDh0oAp79ocgAPjgdwbQOQKYrZFZYwQFAmP2gAzNuXwYVtmSUDo+Oa8sDbGOzL8rYBCIOHAYHyGChWw1heKKiTQa0GI5eI0/35WsjbuGjfT+9N68Km/awJYMCOMHgGlOW9tElky/IAhIGCjlrZJJilwd7QtcHG8uKfvGIL1rYVwHwdHKOXHXyTgDzkvBXNg9W1v/eul9VEgAXbL79ppXl4ifQkHCTySv4EgGptdy/M4ICy+DrH8tBh+Tx0X1AZD49ZljWXUjOJ0/3BvmY1D9MB8vbkLVZpZ9Y8ADTUPI2SC3UTALPuzewMn826fKlrMmH+5kXJv7bq6Nu9NX9A7rn71GNIgWL3wEdJBezEKRkYM4xd7O6fMtxEpM7w+KqivBMWfo64VrKOUqLS0iaL4d641LnvhGELY1ioiSBAjKrxX4ABABrs14sDnYP6AAAAAElFTkSuQmCC") +} + +.fileList .ico-webm { + background-image: url("../img/ico/ico-webm.png") +} + +.fileList .ico-wma { + background-image: url("../img/ico/ico-wma.png") +} + +.fileList .ico-wmv { + background-image: url("../img/ico/ico-wmv.png") +} + +.fileList .ico-doc, +.fileList .ico-docm, +.fileList .ico-dotx, +.fileList .ico-dotm, +.fileList .ico-dot, +.fileList .ico-rtf { + background-image: url("../img/ico/ico-doc.png") +} + +.fileList .ico-docx { + background-image: url("../img/ico/ico-docx.png") +} + +.fileList .ico-pdf, +.fileList .ico-fdf { + background-image: url("../img/ico/ico-pdf.png") +} + +.fileList .ico-ppt, +.fileList .ico-pptm, +.fileList .ico-pot, +.fileList .ico-potm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABWZJREFUeNrsnF1oHFUUgM/O7272J7s1iVkT2mrAmBRREQUpiFCtCj74pFAoCOJDlIogSJ8K+qL45A8qqPhSEH0VLAit+lKF0tIXiaQBUaLd2m3zs5tkd+fnjufObtIkZmdmZzLb2Z1zYJifPXdm7rfn3HPO3ZlNWJYFJP4lQQAJYHQBzs7OKrg6hctLuIz1S6fHxsc/w9WrXvUHc7m2n0kubd/O5/MnC4UCSJLUF/Dm5+ehfP36zPDICHQC0S/Alzk8QRCAMdY3bjdaLMK1UmlPIAounw9zeP0mqqLYELkl4u6nYQLsW9kriLEFuFcQYw1wLyDGHmBQiAQwIEQCGBAiAQwIkQAGhOhWC1sTExO+b2Tth09AmzvvSVcsFEF98FlQp5/AnfDKxnK5DJVKpaM209PTCb+lXNfEXCrB+k9fQf3id5A+OgPS2FQo18HaHjKZTNN6Ere4tNteWFiA22aBG7L00TF7XXj967Y6xtU5G6B5c8G2wMxzb4J84AFP529UFyEhSCDKKoiK6qqv6zoYhtEW3E6AThYYmTFQumsScsfeBfmeh9EcDdv92frydshaHWEtQW25DGs3rm4eZwhDkhUw9ebnlmk4XkuWZXt2yQ3e1u3eCCIJAdLPnABx5G6w6qug/fZj8zh6SW35Bhi1VTROCdRU+v9fgKpCMpMDCS2wjhCN+roniBugNpat+z2ZxiQkBVKPvWBva1d+bfJjDC1MhmQaAaGbCg5BRk6mYCBXQLM0QUfgjlaPANtZYm9a4BZ3tl2zUm52RBRBVlPevwSuP5BB8CaY6PZuEEXU3+nGXq0wmnmgxVqe638Sl/edQ9dra/YQ4JhCIcDdIPZsJcKqN5s3ly5sEPV1HhHdXpRkMPSGu24L4s6xsCcB6n9carnyfUH4tSAqwHTNmzUJQscWGLlfinj0rV8+0yypph/f5tKOVc9i2SG4i96npxAiz429/twbKYCWocHqmQ9tiPL++0EaP+QJYKowsreZQMv6vECMDECjNA/r5z4Hc/EfELJDMHB0pjUgMtcgEEo6FUUX3ijp3FKY9NOvgTCQb4KzzN3zPTW5rRpxQAHpoWJoECNjgeK+MUg+8jwok4dvuS3mce2sT0ll7MU5G2JQW62Eet9dBeg0mbBtZgaTX17P8vo20AyPqdupTJgSyTSGD96m1gh8HlPTQFSS8QMoYadNE8swQ/cPD9vyc0hxBMjrMCWdBb1R95VD8za8LT8HeIym/QWQWyHWsQKWYXxGxergwSauy9vwtlIHExB9B9COtOkcr8VgvbKEFlVzLwHR6rgub2O37cYXDREXZSBrz6rUq8s4rmFkVhQst8TNOUGG0ZphumNgwODBJ5UfRuvrXrd64qnJBMJK5YfAQAsztBqmOHVgrQDDXZXP/0nJNLpssvtDTZTyP/dxMXlbIPXsGNgLQgAJ4G1Ot8I8eaO6HJmOqtl87wEM66bJhQkgCQEkgBSFKQpTFCYXJoAEkAASQBICSAAJIAEkIYAEsAfF8V25v96Yov+FQjnwwe8B3taU1HjTMxrkwmFKVx/tGD91dtfj1V++gZWzX7rqrF36HkZPnHa8xrWPj4OxVOpPgFxWzn0B1fPfbu5nD78Ig0desTvNAbnp/P3Ok5vHh4+/b6/Lp9+KbxTmoFi9Ckrx3kA6lMZQHuhPuHsKySxopSuBdGIRRLjwsYwvuwWITnRiC3BngPCrQy5MYyAJAQx7MuHPk4+aCaMh8Dce4ykWWJLKDr53QfRlgWbx0AX7jUmwYgmP952NTl30HYWtffufMi32s/Dv3EOJ2kqs3N1KDTJ25+RldsfBI75dmCTgGEhCAEOX/wQYAOhdFmkKB8pMAAAAAElFTkSuQmCC") +} + +.fileList .ico-pptx { + background-image: url("../img/ico/ico-pptx.png") +} + +.fileList .ico-txt { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2JJREFUeNrsnN9LU2EYx5/98KgxbXM6tuZFMSgaeiHmhV4IGSVFkX+C1FV20W0hBF1IVwVeaAQR/QNBERR5UeGFg1K8MAyySBCbbP1cP6zhzul9js6t1NP5oW6e8/3C4WznbBfvZ8/3fb7vOzguRVEIMi83EABgSeXVujk9PS2J02Vx9IojapdBRxsbb4hTn97P766tNQdQ6Irf778YCATI6/XaAt7MzAylU6lzDaEQGYFoFuBZhud2u0mWZdvYLhyJ0EIyuSkQ/zcHNjA8u6lSklSIXIni7TCaSAkhOroLbwZEx8cYqxCRAy1CBECLEAHQIkQAtAgRAC1CdGltZ4m1sBKLxWwFJ51OUyaTMfSdeDzuMruUs53E2p58Pt9y9bgKXDZ6PTc3BwsXq6KiYnVjpNh9ZjeWHVeBeYisXC63puo2qsRtBzif+rgtMKKhoGmIDGhpaUk9GwW35QDNDmw7lbdy8VadUZCOjzEM0ePxrLHxv1WJOVBDeYBcievNhQCoEyJDK+7GsLBB8e670QoEwHUgGvkbw7ExRnN9u1J9esK1o2OMHoiw8BZDBECrcyYQACAA7uil4E6PMaVOBYgxsDAAAiAAQgCIGIMYgxgDASAAAiAAQogxiDGwMOZACAABEAABEAJAAARAAIQAEADLVpqbCYO3RkBI6Ob1uDmALEmSHA0vm83CwiWz8GaqrWU/9Zzo2PD+18wP+v5jkeoCNXRt6C4t/ir88n1nTlJ1VaV6T0v3Ho7Ri8nX9gTIAyse3EB/Lz1+Mk6jiZer16KRoIB1irq7WgWMhHqts71JXK+n4dsPaD5Z2OXm691dh6h/4A66cF4MiKG2tRyg2L6ICpQhjSam/oLnSAvrFVdk08G91HO8Q9j4twD3QUCdQA40ovuPEup8x9bl1wjSBhXwF5rFnnB5/zlVdhaurpJEt26nqVez6ntuKG9nk/Tp8zdUoB6dXok6I08n1IN17HArLKw3KzaLBsJNgyuOj9GxKfUaxxYA1NByZGlVrVucF7krv3n3njo7mtVoU27SfPTThUtDOVlxOXq553Yp8uDV8x5TFdgYqXvOD6dx4uPiecw89mg4MG66CwfrfEdlRXm2kPrS8nMx66hK3FUtyeFQcLI+WHPEtIWhHRqkAdBB+iPAAD3HE9ONhnJoAAAAAElFTkSuQmCC") +} + +.fileList .ico-xls, +.fileList .ico-csv, +.fileList .ico-xlsm, +.fileList .ico-xlsb { + background-image: url("../img/ico/ico-xls.png") +} + +.fileList .ico-xlsx { + background-image: url("../img/ico/ico-xlsx.png") +} + +.fileList .ico-7z { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABAFJREFUeNrsnF1IU2EYx5+znW3aNvcR6qZS0shwXmUQVFAXKgTrKoSiiwjMiwRDb0ToA/q46E5JsCDpoiAKuuhCIajsAyqQspua5IgKq6mLmV+zyc45nfcs15pnunO2M7ed5w8vZ+Ocs5fz4/9/n/d956Q4jgOUfFEIEAHmLkCv16vnD+f5doJvlYXy0JVVVdf4Q1uq11tKSpKeo9e594LVau222WxA03RBwPP5fBCYnj5VWlYGUiDKBdhC4Gk0GmBZtmBi53A6YdLvzwhEzTrnSwm8QpNBrxcgEifyb/uVBFiwyhRE1QLMFERVA8wERNUDTBciAkwTIgJMEyICTBMiAkwT4nprYc7lchUUnEAgAHNzc5LucbvdlNylXMGJX9uDyWSKuof6xyXZ64mJCYxwvHQ6XWxjJD59crf1VOfAFYhEDMOscl0yJyJAEYgEUCQSEY5SwakeoPDwf6Mcv1WXcw6MTH2CxaEeYBeCWQekMdnB6OkEuty1JkQSZdISY5wKQMWLSGh4YEPgCc7i+w09ur7udVqtVmhS4WXFgczP6DTAeLAd9DV7Ur5v5uqx2Gvb6TuS+10efw2LD/uACX5P6XoCkECLr8Y54UDgouOLFHiZkJz+yO67VAfiUk4EopSvMXK2CsfHNt04S9WK+1KZXNPoubUhYoQVhpizDsx2bGWPmegzBLixy0GswuhABIgRVrgKowMxwqi8XMolVtxcjHVOA8yHcRAjrIaJNDoQq3AGxGX5r/yz1J/yAKloF+H3w1nlF+uPUvYRFR8DaYcLIn4fhJ7eFFrWB3mHK78daGxqA7qqbmMqJN8v6T+vHaixloP58BksIigEiAARIAJEIUAEiAARIAoBIkAEiABRRGv+WvNrRy3+XyheW3vH0vi1Jm1QN71IGCOspDb8a03boU4w1ntEz3272AiO9ltA2ypEz4c/j0Lgdpe6Ac4M9ggtFokiE5S19sPS2Avh/WTf8VX3mPcdAUtDKyy8HcIqLOZI9vcCzD4eED1f7D4gwJt/dReWvM8RYCIc0n4N9orHxebkAXfAsn88KWBVA7Q0tMDi6JAASEz25nPALs1D8P4lnEiLuY8Ui9AH8VhaGk+C3lnDw7sMkRk/AkzUJvd+HswPobImyrjLA+a9R2H2yY2k7lQ9QMO2+ljljRdxnc3TKRSM+Zf3cC0sJgJJU2ReFU0ypbE3nxWcGT/VwYl0gnQV26OrpuD/AHU82JVJdEXXg9WrLB6s2DwxZzYTvnTvZqhImHcpBeoUBxxtYKuvjGhlRZhx1o0AywgfpEZ45NlZR+0b2RHm7FuaGI59ppn6uJNamlXVxgNXbGHZ8h3v2M3VDbIjjEpzDEQhQMX1R4ABANtbXMt+ZZZBAAAAAElFTkSuQmCC") +} + +.fileList .ico-gz { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAP3SURBVHic7ZzBa9NQHMe/L8k6pd3WTra1rDihoGy7zJueZSB48+hJ8LSBMmEHQRC8C4qCepAd/Q8EQdi8CSroQSpaPJQhc61MHFtl0iQeutSsbV6T/vqS1/Z9Lm2T9/JLPvv98l6ypsy2bSg6R4t6B3odJZCIwVuZz+djAO4AuApgOowdCoPpbPYJgGW/7cdGRz3XcQUCuJtMJm+lUikYRrumvUGhUEC5VFqamJwEAkj0op2Va6lUCpqmwbIsaixpSGcy+LG11RWJ7c6BE5rWf6fJ4VgM6UwG5VJpCcBjyrb6z45PuiVxYAUC3ZE40AIBusSBFwjQJCqBh3QqUQl00YlEJbCBoBKVwBYEkch4t7Py+bydy+W6vX+RUi6Xsbu7G6jP3Nwc81rXHxe4AUgmk0gkEgAAxv578Xq/ubnJ3d7AlfDQ0FD9xoi7+jq9sTxwGQjUJAKAaZr1ZU7WeWWiFwMpEKhJZIyhWq2CMRZYnMPACgRQL2X3rTrpMrC6/Q37L+7D2tsRHaoJLTGO+KWbMKa8ZxKGYcA0TZim2VTGfgQKH0Qq688ikQcA1t4OKq+etm2n6zp0XQcQTB4QQgaaP2vTgPjF64idPu+736+HV+rvUzeeB4779+sb7L98BHPnu6/2uq6DMXZkNJYiA2HXzi9B5HWDTuJpmhY4AwduHtgOTdMQ5N8Y0o7C7rKllnNQnOzzM7mWVmDUqBLuAlLMAzsl7LLtFJWBRJRAItKWcJSjcBBUBhJRAolIW8LuspUZlYFElEAi0pYw0DziyljWUguUUVgjqoSJSJuBsk2YvVAZSCQ8gXbI3/IPKZ54gawW4uDTuvBQburxmNhDFH4ONNI5VLcKqGysobKxJjpcy/giEZ6B8cVlGNl50WFaYmTnEV8kP4zEjyF06wC05BRGLt8WHSYy1ChMRAkkogQSUQKJKIFElEAiSiARJZCIEkhECSSiBBJRAokogUS4T2sWV2bVL5MBmHnwmfC0pjHc1Z3pOaoH3NXCS3jm3kfRISJFnQOJCL0j7WSf81pcXTjy2cFZ7qwrri409Wm1XWd9Y59GWm2jWwgV6D44h8bPrZa1asNr647HayuC0EvYzwEFOWi/okUR2Vc7+mVwiUQgrwxFbF8koZdwmAcXBpFPY6jZ19g/7D9QKCXsnpI0Tjd40w93f0cKr3/jq7uNKNpfC/fApZzQrKsecK+FuSVsHxuxALnuJ4Q7etuHDrzhlrCZmX9rFN+dg6YD8P9bKiJpVfJiss8GLBNWevY9rxVXoD1+ctG0rdfa9pez7M/vyAcch+LKrPAY9vExy5o688E6ceoCrx33HOiXIL/0IxvU45cmq3oVJZDIP8OiU3PHSqspAAAAAElFTkSuQmCC") +} + +.fileList .ico-cab { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABLVJREFUeNrsnF1oU2cYx59zepp+JTSp1CZaNl3AYXo1hQ0V9EIFobsYQ5gMBMFVWGFjvRHBWZjbhXjTMaEKftwIoqCXBWGrzsI2lM3dzAwt4kbR1GZr1takq5wP3+dtczxJTr5OkpP0nOcPh3ydnJfzy///PO970lTQNA1I1iUSAgJYV0mFXoxGox52M8y2Q2xb75STXt/be/a/4fcGzV5LHrmR81wkErEGkOkrv99/LBAIgCRJjoA3OTkJ8ZmZT7tP3oV8EKvmQKbDCE8URVBV1TGxC4ZCMB2LVQVisRrYjfCcphaPh0NEJ/pP3h2lJlJHiK7uwtWA6PppTKUQaR6YBZFNcUYJoI0QCWCFEAlghRAJYIUQhUKXs9haWAuHw46CE4/HYX5+vqz3sLWwYHUp5zixtT14vd5l9wivueS7PzU1RRE2qrm5Wb8wYkyf1QvLrnNgGiJKUZQc1+VzIgE0gYiAZFnmt+WCcz1AfvIrUTZeqms4B8rPH0NybATUF7O2AxK9XdDRPwRST7ggRIwybtkxLgVgzZtI6taFusDjzmLjpr4/V3S/pqYmvpULzxYHKv8sTwM69n0Gnk3bSn5f4ruP9fuBz6+UPe7LR79A8uYZUGaflrQ/AkRoxm7cEA4Ebbm+lAOvGrIyHl59L9eBtJQzgVjO1xgN24WNsa00zuUq7b5SJtcSea4wRIpwjSE2rAPtjq3lmkk+I4D1XQ5SFyYHEkCKcI27MDmQIkxalUu57I7biLFuaICroQ5ShN0wkSYHUheugjSb/8rfpvFqD1BYHmLpj1u28tPHE2p7ijWvgVIwDHJsElK3L/HN9iIfDK9uB3bsHQSpt68+HZKNi+OvageK/h7wfXicmgiJABJAAkgASQSQABJAAkgigASQABJAEqrgrzX//mIz/Wcypje//bOCX2tKLe6mJy9RhGsp27/W7NzzCfi2H9Afv4w9gsXoHVj46VrOvt0HT0PLxi2QGBuB5G9j+vP4HL5mprnx86bHcgTAtQOjILZ6M4Cs2X8COncP8PvGE5cCIQ5K/X8B2iO7MgCmFb98FJae3NcfB94f4seSE9P8Q3FUF/bt+Ag8oU0we/2bDBj/Xv+aQ/DtOJCxf8fWfg5vbvwCB4lAiyn14M4K/KDzpjFtzEXoCoysmZNmzg9yd+oAt/RD8v4YLK5AaYvsLDpGe98uDn0xOuG8CKP7Fgxxy2l2iVgGbLHVx+BNMCAvOHh0aHZtM6uDWAONx3JcEylF7cxtcuKZ7tYUcxRCTbs4Xw3E2Af6h3LqqSMAIgx0Yb76iMV/+sxB3YGo3uEfcsAWag5YWzH6rW9tdR5APHGEhBCz6yCeMDoOo4cwUc9Of8Djmz39EVtHii+vDLXUMU0E3YGQuvZ/yaOWPdebG7+oNw+EbYTHP4AHE3pM8wlfww8Im4/jaiACwU7buWeA16l0rUI34rwQoWF0pcA6HWZ2CcANAcuJi3mbyMLPV03njHW5mPDXsXcVQV5iLhXAndJAk1rUDafuNVmKsBLquweqwg/kRnh47mpw86+WI6x1vbFX0dQfxecP3xEW51x14UFr61TVnrd/V9ds2G05wqQG6sIEkGSqVwIMAG021uWJKtY8AAAAAElFTkSuQmCC") +} + +.fileList .ico-iso { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABH9JREFUeNrsnEtoE1EUhs9MJmlLUpu0tk20+CCotK5UEMSFCxWEuhARFFERbBcWFN1oQRTUjbpRFFRocVMRBRduCoJvQQTxsdGIFlGpNm1TW9qa1pZ5eM9NZ4w1jZlJ85xz4DLp5N7czpf/v+fMTVNB0zSgsB4iISCAOQ0p2ZOhUMjFDidY28va/GK56Pl1dVfYoSXV/hVz5lgDyOKk1+tt9fl8IElSUcDr6uqCSH///uqaGjAD0SrAfQhPFEVQVbVobOcPBKA3HJ4ViP9bA6sRXrFFicvFIaIS2Y+XKYnkEKKts/BsQLR9GZMuRKoD04RIANOESADThEgA04RIANOEKCTbzmL3wlowGCwqOJFIBEZGRkyNaWhoEKzeyhVdsHt78Hg8MfUIf7jM9Li7u5ssHB9Op9PYGIl3n9WNZdspUIeIoSjKP6qbSYkEMAFEBCTLMj+aBWd7gPzip6wcv1WXdwqU+z5BtPM8qD8Hsw5I9FSCu/EwSLXBpBDRytim2zgVgBlPImMP23MCjyuLzTt27+p/+zkcDt7MwsuKApWBWBng3nQAXEvXpDxu6OJO47Hv4A3T805+fA7Ru5dAGfyeUn8EiNDis3FeKBC02PpiBt5shJX5cPfdrALpVi4BRDMfY+RtFo63bbp2Nhu6+lIpriXSXHKIZOEMQ8xbBWbbtpbXTNIZAczt7SBlYVIgASQLZzgLkwLJwhQFeSs3PePmo63zGmAhrINkYTsU0qRAysKzEFqW/8o/S/NlHqAQm2Li7cOs8jPmEzJ7iRlfAyV/EORwF4w9usZb1hd5f7CwFeje2AJS3fLcZEg2L85f0AoUvbVQvvUYJREKAkgACSABpCCABJAAEkAKAkgACSABpMBI+m3Nr4fq6T+TsVh44X0a39aUSuxNT54gC2cycvKxZvXuc+Dw+qH30p7YL+ELQMX6JihrWGf0GQ89geEH7SAPhY1zJYtXQsWGJnAFlhrnoq87Yfh+G6i/ftoziYilHpi76yw4GMSec1vg26kNEOk4As7AEn5eD/eqRg5eYUCn96tpvszfBFsCdDI1Sb55TEXthoomPr/mysLzqDaEjArFcz9un/6r3wCDKJaVQ/naHfYEqE3BQGsiKD1Gn93iKpsMf+TqE0vLIfqq85/xCBPBulc2/jW+qNfA+EBAww/amMKaYd6RO8a6Jg/1cIj8XWYK0/smTJRT6yQqdqY+RQtQVxs2VJqLWzrAFYVt4PpRysLJImZPDweIFo1CzKYIEpMDPq+OjxrnEilMTyCoWltmYbTv9PULQSEQfe1Tf41ymInGo1LR9rkoZXIOcDz0lMOZy0qU+PrOt/kwX9PG3j3hYLAmRFBV244bsLEuxHGo0NFnN+1pYUwAAx1HWRmynVs2XoFY52GpwhMLU6E8GObZWk82+VBIJ91M+NK6WhHkCaZSAewZGmhSibrozAuHJQsrgeUvQFX4C9kRHl676q9/adnCWuWCjYqmPhb7PqwQxodttfGglVWoau2yN2rVovWWLUxRAFmYANo8fgswAF4ssuGz3PXWAAAAAElFTkSuQmCC") +} + +.fileList .ico-rar { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABGNJREFUeNrsnEtoE1EUhs+k07Q1qclUahMtvoJK05UtCCrUhQpCXblRBEFQFxaUdiMFQVA34qaioC6KG0EU3BYE6xNUEI0bqWjxRZWo0UpfaSOZGe+5adIkjdPMpJkkM+fAZSYzk3uZL/85/52bpoKqqkBhPByEgACWNEStk0NDQ062Oc3aIdZWWuWmVzY3X2Wbrnyv9yxdagwgizNer7dXkiQQRdES8IaHhyHy8+exxuXLQQ9EowAPIzyHwwGKolgm7Xx+P3wPhxcF4kI1sBHhWS1qnE4OEZXIXl4hEykhRFu78GJAtP00plCINA8sECIBLBAiASwQIgEsECIBLBCioLWcxZ6F1UAgYCk4kUgExsfHdb0nGAwKRh/lLBfs2R7cbndCPcIcl//tj4yMUAqnR3V1dWphJD37jC4s206BSYgYsizPU93/lEgAc0BEQPF4nG/1grM9QH7zs6mcvlRXdgqM//gAUwN9oEyOmg7I4W4AV2cPiE0BTYiYytiy0zgfgEU3keiD/pLA48pi40bvXVvwuqqqKt70wjNFgfKvxDTAtfs4ODdsyft9fy4dSO1LJ27qHvfv++cwdfcyyKPf8roeASK0dDcuCwWCmqgveuAtRhgZD1ff9SqQHuVyQNTzNUbZunB62haaznojqb58JtciaU4bIqVwkSGWrQLNTlvDNZN0RgBL+zhILkwKJICUwkV2YVIgpTBFRT7KZTtuOaZ1WQOshDpIKWyHiTQpkFx4EUI1+a/8TRqv+ACFxBCxNw9M5ZcaTyjuLRa9Boq+AMTDwxB9eJ0304u8L1DZCnTt6gKxubU0DsnGxfErWoEObxPU7z1FJkJBAAkgASSAFASQABJAAkhBAAkgASSAFBiav9b80t1C/5mMxeqLbwv4taZYY2968RilcDHDtK81a9a2QePBCznPTTy7BWOD/ZmfbK0bfCdusG09fL98EOJ/wqlz9dv2gWfH0Xn9KDMTMHa/H6ZeDVgPYDIiN05C7FMo9drV3glSZw8HlH7jda3bOTyEUhfsgImnt+f19fXszozX0p4e3lfsYygDuKVdGKEhJKd/Q8ZxV1snTA89Zu0J38+3L672dW32m8ZgyiYDYWKLMngzTE2itIKXACN9WR4g1jNM1cmM9O3gqkwo8DHfd7cvrELPziP8WkvXwFxGgiaSURd5+j6ZS83QANRv3c9A9zFAk6njzacH5/X1+865jGssbyJJR535EOLH0VRQkQgxu/bhuXQzyTYR/HCkPd3MRF6ZBrEsTITXvRXr+XZJcDv8Db/ncNIbHqtj57QCywDCx5ppmxqISsG6xdNB8nOzwLqXHXgMjUXLTOTZqYtgJxPhEKcnuLpcs0aRywSSx7TMJPlB1AbMm8ZoLiZ87t0sC/EYgyyAPUMFVaxR1px/UWVIgbK/9QUoMu/IjvDw3hVfy0vDLqw2rNolq8ojx493m4TpMVstPKh1HkVp2vhaWbZmh+EUpqigRzkCaNP4J8AArrWcCArY0yoAAAAASUVORK5CYII=") +} + +.fileList .ico-zip { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+hJREFUeNrsnF1IU2EYx5+zHTdkU7eZuuUocWA4r/QiqC68UCHQqxCKiAiqiwRD74RKyKu6UhIqULoJouhWIcjsAyqQsJtY5IgKq5krze8m56P3PWtruA/Px77P84fDmTvvu5fz2/95n+d958aIoggo9TIgAgSYU7GpLvp8PhM5DZLjDDlqi+Wma93uW+TUI7d9RXm5OoBEV20224DdbgeWZYsCnt/vh+Di4oWq6mpQAlEtwLMUnsFgAEEQiibsnC4XLAQCaYG42xxYReEVm8wmkwSROpH8eROTSA4h6joLpwOi7ssYrRCxDtQIEQFqhIgANUJEgBohIkCNEJlU21lkLSx6PJ6ighMMBmF1dVVRH6/Xy6hdyhWdyNoerFZr2D3Mfy7JHs/Pz2MIx6qkpCS6MRIbfWo3lnXnwAhEKp7n41yXzIkIMAFECojjOOmsFJzuAUo3/y+UY7fq8s6B3I+PsDE5DML6UtYBGawOsHT2A1vjSQmRhjI9doaxHIAZTyKb0+M5gSc5i4y7+fj2ru2MRqN0KIWXFQfyP8NlgOVoL5gaDsnut3zjZPSx/eI9xeNuz72GjUejwC99k9WeAqTQYrNxXjgQxPD8ogReOqRmPLr7rtSBuJRLAFHJxxh5m4Vjw1ZrOCtVxH1yimsWPZcaIoZwhiHmrQOzHbaq50z0GQLM7XIQszA6EAFiCGc4C6MDMYRRBbmU25lx8zGs8xpgIcyDGMJ6KKTRgZiF0yAxy//ln6XxMg+QCQ8RejedVX7R8ZjM3mLG50DW6QEu4IfNp3ekI+uTvNNT2A60dPQA627KTYYk49LxC9qBBlsNlB27hEkEhQARIAJEgCgEiAARIAJEIUAEiAARIIoq5bc1v/Q14i+TEe0fea/h25qsWd/0uBCGcCaVk481WbsLnL13k15feTIGay8fgHtwCjZmJ2F5YhjKjhyHirbzcW23A3Pwe2JEOusGILccgK9D7XHPV3ZfAXN9C2z5XiTtG9uPvhF7Tl0HR/dlWBg9re8sXNF+Dkq9rcRtIxJguW8EdShr3yvB1C1ACq7s8AlYe3WfuO851oFK50N7Vx+EPs3CytS44r6Wlk7ixO+yXVsUc2CsHGTeE7bW4NfDIVntaWJJlER0lYUjsnf1g8nVAItjPSD8WZfVJ1Hy0WUI07KEhh8tWXJVghQsQOo6WtPRhEHrPSykFcpc3xzNvu7B1rjrkeK54DcTPg8c5BkuRFzKgD4lgsiahbprM0ZVIcy7mmZA4KUX0iM8eu+Cs/GN6hAWHfs6eFF4ZvjxoZnZWtHVxoNYWiEINQfeCpV1bapDGFVAa2EEqFP9FWAAVhNMgyXjjfoAAAAASUVORK5CYII=") +} + +.fileList .ico-bt { + background-image: url("../img/ico/ico-bt.png") +} + +.fileList .ico-file { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAehJREFUeNrs3D9PwkAcxvFrKXUhCAMEAhsbL8nJxElfgYPR+CZ0MnHyJbEwMkDoQGAG6h2CNlIo16t/cvd9kktL2g73ye9a7gj14jgWJH88AAH8v4CDwSCUm3vZLmXr2NLpTrf7LDc3p55/Xq0ePBZkXPtYq9Vu6/W6CILACrzhcCii6fS60WwKHcS8gFcKz/d9sV6vrRl2rXZbTMbjQhD9jOMNhWdbzsJwg6gqUX58+klAa1MUorOARSE6DVgEovOApogAGiICaIgIoCEigIaIWXPhuNfrWYUTRZFYLBZa1/T7fS/vVM66yLm9qFQqH9Xjfbkc2h+NRgzhZMrl8ufCSHL05V3Wc64Cd4gqq9Vqr+oOVSKAKYgKaLlcbra6cM4Dbjq/HcrJpTpdSOe/xijEUqm0N4y/VyUVeCQ7QFWJafdCAE9EVGjJpzFDWDNq9V23AgFMQdT5GQPAtPntiQ8Q7oEZiFTgLyACaHrPhABAAAEEkAAIIIAAEgABBBBAAiCAAAJIAAQQQAAJgAACCCABEEAAASQAAggggARAAAEEkAAIIIAAEgABBNBywCj5bhXXsu17ZAL4MpvNhIuIqs+q7zKvx87L+sP1w3w+D2W7kPstxwwnsr3JdnfsJF4FbxgAAfzbvAswAK/3kejP2oZLAAAAAElFTkSuQmCC") +} + +.fileList .ico-apk { + background-image: url("../img/ico/ico-apk.png") +} + +.fileList .ico-css { + background-image: url("../img/ico/ico-css.png") +} + +.ico-cmd { + background: url("../img/soft_ico/ico-cmd.png") no-repeat center center; + height: 18px; + width: 16px; + display: block +} + +.btn:hover .ico-cmd { + background: url("../img/soft_ico/ico-cmd-hover.png") no-repeat center center; + height: 18px; + width: 16px; + display: block +} + +.fileUploadDiv { + padding: 10px +} + +#file_input { + display: none +} + +#up_box { + background-color: #fafbfe; + border: #e4e5e9 1px solid; + margin: 0; + padding: 10px; + clear: both; + height: 360px; + overflow: auto +} + +#up_box li { + list-style-type: none; + height: 30px; + line-height: 30px; + font-size: 12px; + width: 100%; + margin: 0 0 5px; + padding: 0 +} + +#up_box li .filename { + display: inline-block; + height: 30px; + margin-right: 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 240px +} + +#up_box li .filesize { + color: #999; + display: inline-block; + height: 30px; + overflow: hidden +} + +.fileUploadDiv button { + float: left; + border-radius: 3px; + border-style: solid; + border-width: 1px; + width: 75px; + height: 30px; + color: #fff; + margin-bottom: 10px; + cursor: pointer +} + +#up { + background-color: #5cb85c; + border-color: #4cae4c; + color: #fff; + text-align: center; + margin-left: 10px; + position: absolute; + bottom: 0; + right: 10px +} + +#filesClose { + position: absolute; + bottom: 0; + right: 95px; + background-color: #cbcbcb; + border-color: #cbcbcb; + color: #fff +} + +#opt { + background-color: #4592f0; + border-color: #367fa9 +} + +#up_box li em { + font-style: normal; + color: #06F; + float: right; + margin-right: 10px +} + +.cancel { + float: right; + cursor: pointer; + color: #c00 +} + +#textBody { + min-height: 450px; + _height: 450px +} + +#filesClose:hover { + background-color: #c9302c; + border-color: #ac2925; + color: #fff +} + +#up:hover { + background-color: #449d44; + border-color: #398439 +} + +#totalProgress progress { + width: 180px; + height: 10px; + border: 1px solid #ccc; + background-color: #e6e6e6; + color: #0064b4; + opacity: .9 +} + +#totalProgress p { + color: #666 +} + +.setchmod fieldset { + margin-left: 15px; + margin-bottom: 15px; + border: 1px solid #ccc; + float: left; + width: 114px; + padding-bottom: 10px; + border-radius: 3px +} + +.setchmod legend { + padding: 3px; + border: 0; + width: auto; + margin: 0 6px; + font-size: 14px +} + +.setchmod fieldset p { + margin-left: 10px +} + +.setchmod fieldset p input { + position: relative; + top: 2px; + margin-right: 5px +} + +.setchmodnum { + clear: both; + margin: 0 15px -10px +} + +.setchmodnum #access, +#chown { + width: 60px; + margin-right: 5px; + height: 28px +} + +.setchmodnum span { + margin-left: 10px +} + +#chown { + margin-left: 5px +} + +.shellcode { + margin-bottom: 5px; + height: 490px; + overflow: auto +} + +.shellcode pre { + background-color: #111; + color: white; + padding: 5px 10px; + border: 0 none; + border-radius: 0; + min-height: 490px; + margin: 0 +} + +#mExec { + width: 89%; + margin-top: 0; + float: left +} + +.editmenu { + text-align: right +} + +#filesBody { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none +} + +.folderBox input { + display: none +} + +.fileList>div:hover, +.fileList div.ui-selecting { + background: #f6f8fd; + border-color: #d3dfec +} + +.fileList div.active, +.fileList div.focus, +.fileList div.ui-selected { + background: #eff3f9 none repeat scroll 0 0; + border-color: #bcccde +} + +.fileList .file { + background: rgba(0, 0, 0, 0) none repeat scroll 0 0; + border: 1px solid #fff; + border-radius: 2px; + display: inline-block; + height: 136px; + margin: 0 5px 10px 0; + text-align: center; + width: 112px +} + +.fileList div.file { + transition: all .2s ease 0s +} + +.fileList .file .titleBox { + color: #595c5f; + display: block; + height: 23px; + line-height: 23px; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + width: 100px; + margin-left: 5px +} + +#DirPathPlace { + display: none; + padding: 0 +} + +#DirPathPlace>input { + border: 1px solid #ccc; + height: 28px; + line-height: 28px; + padding: 0 5px; + width: 280px +} + +#DirPathPlace>input:active, +#DirPathPlace>input:focus { + border: #66afe9 1px solid; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +#PathPlaceBtn { + background-color: #f3f3f3; + border: 1px solid #ccc; + height: 28px; + line-height: 26px; + padding: 0; + width: 290px; + cursor: text; + overflow: hidden +} + +#PathPlaceBtn ul { + display: inline-block; + height: 26px; + width: auto; + position: relative +} + +#PathPlaceBtn li { + background: url("../img/ico/ico-ltr.png") no-repeat right center; + padding-left: 10px; + padding-right: 18px; + float: left; + height: 26px; + line-height: 26px +} + +#PathPlaceBtn li a { + display: inline-block; + cursor: pointer; + height: 26px; + line-height: 26px +} + +.backBtn { + color: #666; + border-radius: 0; + height: 28px; + line-height: 8px; + margin-right: -1px; + margin-top: -1px +} + +#tipTools { + background: #fff url(../img/ico_line.png) repeat-x 0 55px; + height: 110px; + border-bottom: #ccc 1px solid; + position: absolute; + top: 0; + left: 0 +} + +.re-head { + border-bottom: 1px solid #ccc; + float: left; + padding: 15px 20px; + width: 100% +} + +.re-con { + float: left; + height: 500px; + width: 100% +} + +.re-con-menu { + background-color: #f0f0f1; + float: left; + height: 100%; + width: 120px +} + +.re-con-menu p { + cursor: pointer; + line-height: 40px; + padding-left: 30px; + position: relative +} + +.re-con-menu p.on { + background-color: #fff +} + +.re-con-con { + height: 500px; + margin-left: 120px; + overflow: auto +} + +@media only screen and (max-width:990px) { + .pr8 { + padding-right: 0 + } + + .pl7 { + padding-left: 0; + margin-top: 15px + } +} + +.bt-ico-ask { + border: 1px solid #fb7d00; + border-radius: 8px; + color: #fb7d00; + cursor: help; + display: inline-block; + font-family: arial; + font-size: 11px; + font-style: normal; + height: 16px; + line-height: 16px; + margin-left: 5px; + text-align: center; + width: 16px +} + +.bt-ico-ask:hover { + background-color: #fb7d00; + color: #fff +} + +#RecycleBody .tname { + line-height: 30px +} + +.menu-sub { + border-bottom: 1px solid #ccc; + height: 40px; + line-height: 30px; + margin-bottom: 15px +} + +.menu-sub span { + display: inline-block; + font-size: 14px; + height: 40px; + padding: 0 25px; + cursor: pointer +} + +.menu-sub .on { + border-bottom: 2px solid #20a53a; + color: #20a53a; + font-weight: bold +} + +.bt-progress { + background-color: #e2e2e2; + border-radius: 8px; + height: 16px; + line-height: 16px; + position: relative; + margin: 5px 0 +} + +.bt-progress-bar { + background-color: #5ab76c; + border-radius: 8px; + height: 16px; + max-width: 100%; + position: absolute; + text-align: right; + transition: all .3s ease 0s; + width: 0 +} + +.bt-progress-text { + font-size: 12px; + color: #fff; + padding: 0 10px; + position: static +} \ No newline at end of file diff --git a/web/static/css/index.html b/web/static/css/index.html new file mode 100755 index 000000000..e69de29bb diff --git a/web/static/css/install.css b/web/static/css/install.css new file mode 100755 index 000000000..fa853aabc --- /dev/null +++ b/web/static/css/install.css @@ -0,0 +1,181 @@ +html, +body, +div, +span, +p, +a, +table, +input, +ul, +li, +* { + margin: 0; + padding: 0; + font-size: 12px +} + +body { + background-color: #444; + height: 100%; + min-height: 768px +} + +.main { + height: 900px; + width: 100%; + background-size: cover; + position: relative; + z-index: 0 +} + +.warp { + background-color: #f6f6f6; + width: 700px; + height: 370px; + position: absolute; + top: 50%; + left: 50%; + margin-top: -340px; + margin-left: -350px; + box-shadow: 0 0 10px #666; + border-radius: 3px +} + +.warp .logo { + background: url(../images/logo.png) no-repeat; + height: 37px; + left: 50%; + margin-left: -52px; + position: absolute; + top: -67px; + width: 103px +} + +.warp .title { + height: 70px; + line-height: 70px; + text-align: center; + font-size: 26px; + border-bottom: #ababab 1px solid; + margin: 0 20px; + color: #444 +} + +.warp .form { + padding: 20px +} + +fieldset { + background-color: #fff; + padding: 10px; + margin-bottom: 20px; + border: #ccc 1px solid +} + +fieldset legend { + padding: 0 5px; + font-size: 14px; + color: #000 +} + +fieldset p { + line-height: 40px; + color: #555 +} + +fieldset p .tit { + display: inline-block; + margin-right: 10px; + text-align: right; + width: 100px +} + +fieldset input { + margin: 0 7px; + height: 28px; + border: #ccc 1px solid; + width: 200px; + padding: 0 5px; + border-radius: 2px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s +} + +fieldset input:focus, +fieldset input:active { + border: #66afe9 1px solid; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +.text { + background-color: #f3f3f3; + border: 1px solid #ccc; + border-radius: 2px; + display: inline-block; + height: 26px; + line-height: 28px; + margin: 0 7px; + padding: 0 5px; + width: 188px +} + +.submit-btn { + background-color: #20a53a; + border: 1px solid #20a53a; + color: #fff; + cursor: pointer; + font-size: 16px; + height: 42px; + line-height: 35px; + text-align: center; + width: 330px; + margin: 12px 164px; + border-radius: 3px +} + +.submit-btn:hover { + background-color: #10952a; + border: 1px solid #10952a +} + +.success { + background-color: #f6f6f6; + border-radius: 5px; + box-shadow: 0 0 140px #556789; + height: 140px; + left: 50%; + margin-left: -150px; + margin-top: -70px; + position: absolute; + text-align: center; + top: 50%; + width: 300px +} + +.success p { + font-size: 20px; + line-height: 90px +} + +.success a { + color: #3385d6; + font-size: 16px; + text-decoration: none +} + +.copyright { + color: #9d9d9d; + text-align: center; + margin-top: -50px; + position: relative; + z-index: 3 +} + +.copyright a { + color: #9d9d9d; + text-decoration: none +} \ No newline at end of file diff --git a/web/static/css/login.css b/web/static/css/login.css new file mode 100644 index 000000000..423e9ae9b --- /dev/null +++ b/web/static/css/login.css @@ -0,0 +1,143 @@ +body.mw-login { + background-color: var(--mw-bg, #f6f6fa); + background-image: var(--mw-app-background-overlay, linear-gradient(180deg, rgba(246, 246, 250, 0.78) 0%, rgba(246, 246, 250, 0.94) 100%)), var(--mw-app-background-image, none); + background-size: auto, cover; + background-position: center, center; + background-repeat: no-repeat, no-repeat; + background-attachment: fixed, fixed; + font-family: "Inter", "PingFang SC", "Microsoft YaHei", sans-serif; + min-height: 100vh; + margin: 0; + color: var(--mw-text, #1f1f1f); +} + +input { + -webkit-appearance: none; +} + +.mw-login-layout { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 32px; +} + +.mw-login-card { + width: min(420px, 100%); + background: color-mix(in srgb, var(--mw-surface, #ffffff) 88%, transparent); + border-radius: 24px; + padding: 32px; + box-shadow: 0 20px 48px rgba(15, 23, 42, 0.18); + border: 1px solid color-mix(in srgb, var(--mw-border, #e5e7eb) 70%, transparent); + display: flex; + flex-direction: column; + gap: 24px; + backdrop-filter: blur(14px); +} + +.mw-login-brand { + display: flex; + flex-direction: column; + gap: 8px; + text-align: center; +} + +.mw-login-brand .material-icons { + font-size: 36px; + color: var(--mw-primary, #6750a4); +} + +.mw-login-brand h1 { + font-size: 22px; + margin: 0; +} + +.mw-login-brand p { + margin: 0; + color: var(--mw-muted, #64748b); + font-size: 13px; +} + +.mw-login-form { + display: flex; + flex-direction: column; + gap: 16px; +} + +.mw-field { + display: flex; + flex-direction: column; + gap: 6px; + font-size: 13px; + color: var(--mw-muted, #64748b); +} + +.mw-input { + width: 100%; + border-radius: 14px; + border: 1px solid var(--mw-border, #e2e8f0); + padding: 12px 14px; + font-size: 14px; + background: var(--mw-surface, #ffffff); + color: var(--mw-text, #1f1f1f); + transition: border 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; +} + +.mw-input:focus { + border-color: var(--mw-primary, #6750a4); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--mw-primary, #6750a4) 18%, transparent); + outline: none; +} + +.mw-error { + min-height: 18px; + color: #ef4444; + font-size: 12px; +} + +.mw-login-submit { + width: 100%; + --mdui-color-primary: var(--mw-primary, #6750a4); +} + +.mw-login-link { + align-self: flex-end; + color: var(--mw-primary, #6750a4); +} + +.mw-captcha-row { + display: flex; + align-items: center; + gap: 12px; +} + +.mw-captcha-row img { + border-radius: 8px; + border: 1px solid var(--mw-border, #e2e8f0); +} + +.Validform_checktip { + color: var(--mw-muted, #94a3b8); + font-size: 12px; +} + +body.mw-login .mw-login-submit::part(button) { + border-radius: 14px; +} + +body.mw-login mdui-button.mw-login-submit::part(button), +body.mw-login mdui-button.mw-login-link::part(button) { + --mdui-color-primary: var(--mw-primary, #6750a4); +} + +@media (max-width: 640px) { + .mw-login-card { + padding: 24px; + } + + .mw-captcha-row { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/web/static/css/material-icons.css b/web/static/css/material-icons.css new file mode 100644 index 000000000..6aaf86c8d --- /dev/null +++ b/web/static/css/material-icons.css @@ -0,0 +1,23 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url('../fonts/material-icons.ttf') format('truetype'); + font-display: block; +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/web/static/css/mdui2-theme.css b/web/static/css/mdui2-theme.css new file mode 100644 index 000000000..d016ef7ba --- /dev/null +++ b/web/static/css/mdui2-theme.css @@ -0,0 +1,4407 @@ +:root { + --mw-primary-rgb: var(--mdui-color-primary, var(--mw-theme-primary-rgb, 103, 80, 164)); + --mw-primary: rgb(var(--mw-primary-rgb)); + --mw-on-primary-rgb: var(--mdui-color-on-primary, 255, 255, 255); + --mw-on-primary: rgb(var(--mw-on-primary-rgb)); + --mw-primary-soft: color-mix(in srgb, var(--mw-primary) 14%, transparent); + --mw-bg-rgb: var(--mdui-color-surface-container-low, 246, 246, 250); + --mw-bg: rgb(var(--mw-bg-rgb)); + --mw-surface-rgb: var(--mdui-color-surface, 255, 255, 255); + --mw-surface: rgb(var(--mw-surface-rgb)); + --mw-surface-container-rgb: var(--mdui-color-surface-container, 242, 243, 247); + --mw-surface-container: rgb(var(--mw-surface-container-rgb)); + --mw-border-rgb: var(--mdui-color-outline, 209, 213, 219); + --mw-border: rgb(var(--mw-border-rgb)); + --mw-text-rgb: var(--mdui-color-on-surface, 31, 31, 31); + --mw-text: rgb(var(--mw-text-rgb)); + --mw-muted-rgb: var(--mdui-color-on-surface-variant, 95, 99, 104); + --mw-muted: rgb(var(--mw-muted-rgb)); + --mw-shadow: 0 10px 24px rgba(18, 18, 23, 0.08); + --mw-radius: 16px; + --mw-app-background-overlay: linear-gradient(180deg, color-mix(in srgb, var(--mw-bg) 68%, transparent) 0%, color-mix(in srgb, var(--mw-bg) 94%, transparent) 100%); + --mw-app-background-image: none; + --mw-app-background-size: cover; + --mw-app-background-position: center; + --mw-app-background-repeat: no-repeat; + --mw-app-background-attachment: fixed; + --mw-glass-surface-ratio: 84%; + --mw-glass-border-ratio: 78%; + --mw-glass-blur: 18px; +} + +body, +html { + height: 100%; + font-family: "Inter", "PingFang SC", "Microsoft YaHei", sans-serif; + background-color: var(--mw-bg); + background-image: var(--mw-app-background-overlay), var(--mw-app-background-image); + background-size: auto, var(--mw-app-background-size); + background-position: center, var(--mw-app-background-position); + background-repeat: no-repeat, var(--mw-app-background-repeat); + background-attachment: fixed, var(--mw-app-background-attachment); + color: var(--mw-text); + font-size: 14px; +} + +a { + color: var(--mw-primary); +} + +.mw-theme .bt-warp, +.mw-theme #container { + background: transparent; +} + +.mw-shell { + display: flex; + flex-direction: column; + min-height: 100vh; + background: transparent; +} + +.mw-icon-button { + border: none; + background: transparent; + border-radius: 999px; + padding: 8px; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--mw-muted); +} + +.mw-icon-button:hover { + background: var(--mw-surface-container); + color: var(--mw-primary); +} + +.mw-main { + flex: 1; + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.mw-topbar { + position: sticky; + top: 0; + z-index: 900; + background: transparent; + padding: 16px 28px 0; +} + +.mw-topbar-glass { + background: color-mix(in srgb, var(--mw-surface) 76%, transparent); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 1px solid color-mix(in srgb, var(--mw-border) 40%, transparent); +} + +.mw-topbar-card { + width: 100%; + display: flex; + align-items: center; + flex-wrap: nowrap; + padding: 16px 28px 0; + gap: 16px; + background: transparent; + border: none; + border-radius: 0; + box-shadow: none; + backdrop-filter: none; + overflow: hidden; +} + +.mw-topbar-left, +.mw-topbar-right { + flex: 0 0 auto; + white-space: nowrap; +} + +.mw-topbar-center { + flex: 1 1 0; + min-width: 0; + display: flex; + justify-content: center; +} + +.mw-topbar-brand { + display: flex; + flex-direction: column; + gap: 4px; + flex-shrink: 0; + align-items: flex-start; +} + +.mw-topbar-title { + font-size: 18px; + font-weight: 600; +} + +.mw-topbar-sub { + font-size: 12px; + color: var(--mw-muted); +} + +.mw-topbar-nav { + display: flex; + align-items: center; + flex-wrap: nowrap; + gap: 12px; + padding: 0; + justify-content: center; + min-width: 0; + overflow-x: auto; + scrollbar-width: thin; +} + +.mw-topbar-nav > * { + margin: 0; + flex: 0 0 auto; +} + +.mw-topbar-actions { + display: flex; + align-items: center; + gap: 8px; + margin-left: auto; + flex-shrink: 0; + flex-wrap: nowrap; + white-space: nowrap; + align-self: center; +} + +.mw-topbar-item { + display: inline-flex; + align-items: center; + justify-content: center; + width: 38px; + height: 38px; + border-radius: 12px; + border: none; + background: transparent; + color: var(--mw-muted); + cursor: pointer; + transition: background 0.2s ease, color 0.2s ease; +} + +.mw-topbar-item .material-icons { + font-size: 20px; +} + +.mw-topbar-item:hover { + background: var(--mw-surface-container); + color: var(--mw-primary); +} + +.mw-topbar-item.is-active { + background: var(--mw-primary-soft); + color: var(--mw-primary); +} + +.mw-content { + padding: 28px 32px 40px; + flex: 1; + min-width: 0; + display: flow-root; + width: 100%; + max-width: 1400px; + margin: 0 auto; +} + +.mw-footer { + width: 100%; + max-width: 1400px; + margin: 0 auto 24px; + padding: 0 32px 24px; +} + +.mw-footer-card { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 14px 20px; + background: var(--mw-surface); + border: 1px solid var(--mw-border); + border-radius: var(--mw-radius); + box-shadow: var(--mw-shadow); +} + +.mw-footer-content { + font-size: 12px; + color: var(--mw-muted); + text-align: center; +} + +.bgw { + background: var(--mw-surface); + border-radius: var(--mw-radius); + border: 1px solid var(--mw-border); + box-shadow: var(--mw-shadow); +} + +.title { + padding: 16px 20px; + font-weight: 600; + border-bottom: 1px solid var(--mw-border); +} + +.btlink { + color: var(--mw-primary); +} + +.btn, +.btn-primary, +.btn-success, +.btn-default, +.btn-danger, +.btn-info, +.btn-warning, +input[type="submit"], +button { + border-radius: 12px; + border: 1px solid var(--mw-border); + box-shadow: none; + background: var(--mw-primary); + color: var(--mw-on-primary); + transition: all 0.2s ease; +} + +.btn:hover, +button:hover, +input[type="submit"]:hover { + opacity: 0.9; +} + +.btn-default { + background: var(--mw-surface); + color: var(--mw-text); +} + +.bt-input-text, +input[type="text"], +input[type="password"], +select, +textarea { + border-radius: 12px; + border: 1px solid var(--mw-border); + padding: 8px 12px; + background: var(--mw-surface); + color: var(--mw-text); + box-shadow: none; +} + +.bt-input-text:focus, +input[type="text"]:focus, +input[type="password"]:focus, +select:focus, +textarea:focus { + border-color: var(--mw-primary); + box-shadow: 0 0 0 3px var(--mw-primary-soft); + outline: none; +} + +.table { + border-radius: 12px; + overflow: hidden; + border: 1px solid var(--mw-border); +} + +.table > thead > tr > th, +.table > tbody > tr > td { + border-color: var(--mw-border); +} + +.alert { + border-radius: 12px; + border: 1px solid transparent; +} + +.main-content { + padding: 0; + margin-left: 0; + width: 100%; + min-width: 0; +} + +.index-pos-box { + border-radius: 16px; +} + +.pos-box { + border-radius: 16px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); + box-shadow: var(--mw-shadow); +} + +.position { + padding: 16px 20px; + color: var(--mw-muted); + font-weight: 500; +} + +.position a { + color: var(--mw-muted); +} + +.position a:hover { + color: var(--mw-primary); +} + +.search { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; +} + +.ser-text { + border-radius: 999px; + border: 1px solid var(--mw-border); + padding: 8px 14px; + min-width: 200px; + box-shadow: none; +} + +.ser-sub { + border: 1px solid var(--mw-border); + background: var(--mw-surface); + border-radius: 999px; + width: 36px; + height: 36px; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.ser-sub::before { + content: "search"; + font-family: "Material Icons"; + font-size: 18px; + color: var(--mw-muted); +} + +.softbox, +.softlist, +.divtable { + border-radius: 16px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); + box-shadow: var(--mw-shadow); +} + +.softlist .divtable { + border: none; + box-shadow: none; +} + +.menu-sub { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 12px 16px; + border-bottom: 1px solid var(--mw-border); +} + +.menu-sub span { + padding: 6px 12px; + border-radius: 999px; + border: 1px solid transparent; + color: var(--mw-muted); + background: var(--mw-surface-container); + font-weight: 500; +} + +.menu-sub .on { + color: var(--mw-primary); + background: var(--mw-primary-soft); + border-color: rgba(37, 99, 235, 0.2); +} + +.table { + margin-bottom: 0; +} + +.table thead th { + background: var(--mw-surface-container); + color: var(--mw-muted); + font-weight: 600; +} + +.table tbody tr:hover { + background: color-mix(in srgb, var(--mw-primary) 7%, var(--mw-surface) 93%); +} + +.page, +.pagination { + display: flex; + gap: 6px; + padding: 12px 16px; + align-items: center; +} + +.page a, +.page span, +.pagination li a { + border-radius: 10px; + border: 1px solid var(--mw-border); + padding: 4px 10px; + background: var(--mw-surface); + color: var(--mw-muted); +} + +.page .Pcurrent, +.pagination li.active a { + background: var(--mw-primary); + color: #fff; + border-color: var(--mw-primary); +} + +.page a:hover, +.pagination li a:hover { + color: var(--mw-primary); + border-color: var(--mw-primary); +} + +.softlist td img { + border-radius: 8px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); + padding: 4px; +} + +.btswitch + .btswitch-btn { + border-radius: 999px; + border: 1px solid var(--mw-border); + background: var(--mw-surface-container); +} + +.btswitch + .btswitch-btn:before { + border-radius: 999px; +} + +.btswitch:checked + .btswitch-btn { + background: var(--mw-primary); + border-color: var(--mw-primary); +} + +.tabs-nav .tabs-item.active { + color: var(--mw-primary); + border-bottom: 2px solid var(--mw-primary); +} + + + +@media (max-width: 1024px) { + .mw-topbar { + padding: 14px 20px 0; + } + + .mw-content { + padding: 20px; + } + + .mw-footer { + padding: 0 20px 20px; + } +} + +.mw-card { + background: var(--mw-surface); + border: 1px solid var(--mw-border); + border-radius: var(--mw-radius); + box-shadow: var(--mw-shadow); + display: block; + width: 100%; + box-sizing: border-box; +} + +mdui-card.mw-card { + display: block; + width: 100%; + max-width: 100%; +} + +.mw-card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--mw-border); + gap: 12px; +} + +.mw-card-title { + font-size: 16px; + font-weight: 600; +} + +.mw-card-content { + padding: 20px; + display: flow-root; +} + +.mw-dashboard { + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; + min-width: 0; +} + +.mw-dashboard-body { + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; + min-width: 0; +} + +.mw-page-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + gap: 16px; +} + +.mw-page-left { + display: flex; + flex-direction: column; + gap: 6px; +} + +.mw-page-title { + font-size: 18px; + font-weight: 700; +} + +.mw-page-sub { + color: var(--mw-muted); + font-size: 13px; +} + +.mw-page-actions { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + color: var(--mw-muted); +} + +.mw-version { + font-weight: 600; + color: var(--mw-text); +} + +.mw-link-button { + --mdui-color-primary: var(--mw-primary); +} + +.mw-stat-grid { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 16px; +} + +.mw-stat-grid > li { + float: none !important; + margin: 0 !important; + padding: 12px; + border-radius: 12px; + background: var(--mw-surface-container); + border: 1px solid var(--mw-border); +} + +.mw-stat-progress { + margin: 8px 0 10px; + width: 100%; +} + +.mw-stat-progress mdui-linear-progress { + display: block; + height: 8px; + border-radius: 999px; + --mw-progress-solid: #000; +} + +.mw-stat-progress mdui-linear-progress::part(track) { + background: var(--mw-surface-container); +} + +.mw-stat-progress mdui-linear-progress::part(indicator) { + background: var(--mw-progress-solid); +} + +.mw-overview-grid { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px; +} + +.mw-overview-item { + padding: 16px 12px; + border-radius: 12px; + background: var(--mw-surface-container); + border: 1px solid var(--mw-border); +} + +.mw-overview-grid .sys-li-box { + margin: 0; + max-width: none; + width: 100%; + height: auto; + background: transparent; + border: none; +} + +.mw-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 20px; + width: 100%; + min-width: 0; +} + +.mw-grid-item { + min-height: 100%; +} + +.mw-soft-container { + padding: 0; + height: 442px; + overflow: hidden; +} + +.mw-chart { + width: 100%; + height: 330px; + background: color-mix(in srgb, var(--mw-surface-container) 92%, var(--mw-primary) 8%); + border: 1px solid var(--mw-border); + border-radius: 16px; + padding: 8px; + box-sizing: border-box; +} + +.mw-dashboard .mw-chart { + background: var(--mw-surface-container); +} + +.mw-observe-page .mw-page-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.mw-observe-hero { + background: linear-gradient(135deg, color-mix(in srgb, var(--mw-primary) 18%, transparent) 0%, var(--mw-surface) 70%); +} + +.mw-observe-hero-actions { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.mw-observe-shell { + display: grid; + grid-template-columns: minmax(260px, 380px) 1fr; + gap: 20px; +} + +.mw-observe-aside { + display: flex; + flex-direction: column; + gap: 16px; +} + +.mw-observe-main { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 0; +} + +.mw-observe-panel { + padding: 18px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.mw-observe-panel-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} + +.mw-observe-panel-title { + font-size: 16px; + font-weight: 600; +} + +.mw-observe-panel-subtitle { + color: var(--mw-muted); + font-size: 12px; + margin-top: 4px; +} + +.mw-observe-quick-range { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + color: var(--mw-muted); + font-size: 12px; +} + +.mw-observe-quick-range-actions { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.mw-observe-chip { + border-radius: 999px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); + padding: 5px 12px; + font-size: 12px; + color: var(--mw-text); + cursor: pointer; + transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; +} + +.mw-observe-chip.is-active, +.mw-observe-chip:hover { + background: var(--mw-primary); + color: #fff; + border-color: transparent; +} + +.mw-observe-kpi-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.mw-observe-kpi { + display: grid; + grid-template-columns: auto 1fr; + gap: 12px; + padding: 12px; + border-radius: 14px; + border: 1px solid var(--mw-border); + background: var(--mw-surface-container); + position: relative; +} + +.mw-observe-kpi-title { + font-size: 12px; + color: var(--mw-muted); +} + +.mw-observe-kpi-value { + font-size: 20px; + font-weight: 600; + margin-top: 4px; +} + +.mw-observe-kpi-desc { + font-size: 12px; + color: var(--mw-muted); +} + +.mw-observe-kpi-icon { + width: 36px; + height: 36px; + border-radius: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--mw-primary) 16%, transparent); + color: var(--mw-primary); + font-size: 20px; +} + +.mw-observe-kpi-meter { + grid-column: 1 / -1; + height: 6px; + background: color-mix(in srgb, var(--mw-border) 60%, transparent); + border-radius: 999px; + overflow: hidden; + margin-top: 6px; +} + +.mw-observe-kpi-meter span { + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, var(--mw-primary) 0%, color-mix(in srgb, var(--mw-primary) 55%, #fff) 100%); +} + +.mw-observe-settings-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 12px; +} + +.mw-observe-settings-item { + display: flex; + flex-direction: column; + gap: 8px; + padding: 14px; + border-radius: 12px; + border: 1px solid var(--mw-border); + background: var(--mw-surface-container); +} + +.mw-observe-settings-item.is-action { + justify-content: center; +} + +.mw-observe-settings-label { + font-size: 12px; + color: var(--mw-muted); +} + +.mw-observe-settings-field { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} + +.mw-observe-switch { + min-height: 34px; +} + +.mw-observe-event-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 12px; +} + +.mw-observe-event-list li { + display: flex; + gap: 10px; + align-items: flex-start; +} + +.mw-observe-event-dot { + width: 8px; + height: 8px; + border-radius: 999px; + background: var(--mw-primary); + margin-top: 6px; +} + +.mw-observe-event-title { + font-size: 13px; + font-weight: 600; +} + +.mw-observe-event-time { + font-size: 12px; + color: var(--mw-muted); + margin-top: 2px; +} + +.mw-observe-main-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 18px 20px 0; +} + +.mw-observe-main-title { + font-size: 18px; + font-weight: 600; +} + +.mw-observe-main-title { + font-size: 18px; + font-weight: 600; +} + +.mw-observe-main-subtitle { + font-size: 12px; + color: var(--mw-muted); + margin-top: 4px; +} + +.mw-observe-charts { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 20px; +} + +.mw-observe-chart-card { + min-width: 0; +} + +.mw-observe-span { + grid-column: 1 / -1; +} + +.mw-observe-chart-header { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 18px 20px 0; +} + +.mw-observe-chart-title { + font-size: 15px; + font-weight: 600; +} + +.mw-observe-chart-subtitle { + font-size: 12px; + color: var(--mw-muted); + margin-top: 4px; +} + +.mw-observe-chart-body { + padding: 16px 20px 20px; +} + +.mw-observe-chart { + height: 360px; + position: relative; + overflow: hidden; + padding: 0; +} + +.mw-observe-chart::before { + content: none; +} + + +.mw-monitor-summary { + display: flex; + flex-direction: column; + gap: 12px; +} + +.mw-monitor-summary-header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; +} + +.mw-monitor-tabs { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.mw-monitor-summary .tabs-down { + margin-left: auto; +} + +.mw-monitor-summary .tabs-down select { + min-width: 120px; +} + +.tabs-nav { + display: flex; + align-items: center; + gap: 16px; +} + +.tabs-nav .tab-list { + display: flex; + gap: 16px; +} + +.tabs-nav .tabs-item { + font-weight: 600; + color: var(--mw-muted); + padding-bottom: 10px; + position: relative; +} + +.tabs-nav .tabs-item.active { + color: var(--mw-primary); +} + +.tabs-nav .tabs-item.active::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 3px; + border-radius: 999px; + background: var(--mw-primary); +} + +.tabs-down { + display: flex; + gap: 8px; +} + +.tabs-content .tabs-item { + display: none; +} + +.tabs-content .tabs-item.tabs-active { + display: block; +} + +.mw-monitor-summary .mw-chart { + min-height: 280px; +} + +#netImg, +#ioStat { + min-height: 280px; +} + +.bw-info { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-bottom: 12px; + height: auto; +} + +.bw-info > div { + flex: 1 1 120px; + float: none; + padding-top: 0; +} + +.mw-theme-settings .mw-theme-controls { + display: inline-flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; +} + +.mw-theme-picker { + width: 44px; + height: 36px; + border-radius: 12px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); + padding: 0; +} + +.mw-theme-presets { + margin-top: 12px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.mw-theme-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px; +} + +.mw-theme-row .set-tit { + flex: 0 0 90px; +} + +.mw-theme-row .mw-theme-controls { + flex: 1 1 auto; +} + +.mw-theme-row .set-info { + flex: 1 1 100%; + margin-left: 100px; + line-height: 1.6; +} + +.mw-theme-mode-switch { + display: inline-flex; + align-items: stretch; + border: 1px solid var(--mw-border); + border-radius: 999px; + overflow: hidden; + background: var(--mw-surface); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); +} + +.mw-theme-mode-btn { + appearance: none; + border: none; + background: transparent; + color: var(--mw-muted); + padding: 8px 14px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s ease, color 0.2s ease; +} + +.mw-theme-mode-btn + .mw-theme-mode-btn { + border-left: 1px solid var(--mw-border); +} + +.mw-theme-mode-btn:hover { + background: var(--mw-surface-container); +} + +.mw-theme-mode-btn.is-active { + background: var(--mw-primary-soft); + color: var(--mw-primary); +} + +.mw-theme-background-controls { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +.mw-theme-bg-input { + min-width: 260px; + flex: 1 1 280px; +} + +.mw-theme-state { + flex: 1 1 100%; + margin-left: 100px; + color: var(--mw-muted); + font-size: 12px; +} + +.mw-file-card .mw-card-content { + padding: 20px; + background: var(--mw-surface); + border-radius: 14px; +} + +.mw-file-toolbar { + display: flex; + flex-direction: column; + gap: 16px; + background: var(--mw-surface); + border: 1px solid var(--mw-border); + border-radius: 16px; + padding: 18px; + margin-bottom: 16px; +} + +.mw-file-toolbar-row { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: 16px; +} + +.mw-file-toolbar-primary { + padding: 6px 0; +} + +.mw-file-toolbar-secondary { + padding-top: 8px; + border-top: 1px dashed color-mix(in srgb, var(--mw-border) 70%, transparent 30%); +} + +.mw-file-path { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + flex: 1; + min-width: 260px; +} + +.mw-file-path-field { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 6px 10px; + border-radius: 12px; + background: var(--mw-surface); + border: 1px solid var(--mw-border); +} + +.mw-file-path-label { + font-size: 12px; + color: var(--mw-muted); + padding: 2px 8px; + border-radius: 999px; + background: var(--mw-surface-container); + border: 1px solid var(--mw-border); + white-space: nowrap; +} + +.mw-file-icon-btn { + border: 1px solid var(--mw-border); + background: var(--mw-surface); + border-radius: 10px; + padding: 6px 8px; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--mw-text); + transition: background 0.2s ease, border-color 0.2s ease; +} + +.mw-file-icon-btn:hover { + background: var(--mw-surface-container); +} + +.mw-file-icon-btn.active { + background: var(--mw-primary); + border-color: transparent; + color: #fff; +} + +.mw-file-path-input input { + min-width: 320px; + border-radius: 10px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); + padding: 6px 10px; + height: 36px; + font-weight: 500; +} + +.mw-file-search { + margin-left: auto; + display: flex; + align-items: center; +} + +.mw-file-search-form { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.mw-file-search-field { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + border-radius: 12px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); +} + +.mw-file-search-input { + border-radius: 10px; + border: none; + padding: 6px 10px; + background: transparent; + min-width: 180px; + height: 36px; +} + +.mw-file-search-btn { + border: none; + background: transparent; + border-radius: 10px; + width: 34px; + height: 34px; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--mw-muted); +} + +.mw-file-search .file_search { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 999px; + border: 1px solid var(--mw-border); + background: var(--mw-surface-container); + font-size: 12px; + color: var(--mw-muted); +} + +.mw-file-actions { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + flex: 1; +} + +.mw-file-breadcrumbs { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-left: 8px; +} + +.mw-file-breadcrumbs span { + padding: 4px 10px; + border-radius: 999px; + border: 1px solid var(--mw-border); + background: var(--mw-surface-container); + cursor: pointer; + font-weight: 500; +} + +.mw-file-breadcrumbs #recycle_bin { + position: static !important; + border-color: var(--mw-border) !important; + margin-left: auto; +} + +.mw-file-btn::part(button) { + border-radius: 10px; +} + +.mw-file-tool-items .btn, +.mw-file-batch .btn, +.mw-file-actions .btn { + border-radius: 10px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); + color: var(--mw-text); +} + +.mw-file-actions .btn-group { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.mw-file-tool-items .btn.btn-Warning, +.mw-file-batch .btn.btn-Warning { + background: var(--mw-primary); + color: #fff; + border-color: transparent; +} + +:root.mdui-theme-dark { + --mw-shadow: 0 18px 40px rgba(0, 0, 0, 0.28); + --mw-primary-soft: color-mix(in srgb, var(--mw-primary) 22%, transparent); +} + +.mw-file-tool-items .dropdown-menu { + border-radius: 12px; + border: 1px solid var(--mw-border); + box-shadow: 0 12px 32px rgba(15, 23, 42, 0.12); + padding: 8px 0; +} + +.mw-file-tool-items .dropdown-menu a, +.mw-file-context-menu a { + padding: 8px 16px; + color: var(--mw-text); +} + +.mw-file-view-toggle { + display: flex; + gap: 6px; +} + +.mw-file-table { + border-radius: 12px; + border: 1px solid var(--mw-border); + overflow: hidden; + background: var(--mw-surface); +} + +.mw-file-table .table { + margin-bottom: 0; +} + +.mw-file-table .table thead th { + background: var(--mw-surface-container); + border-bottom: 1px solid var(--mw-border); + padding: 12px 16px; + font-weight: 600; + color: var(--mw-text); +} + +.mw-file-table .table tbody td { + padding: 12px 16px; + border-top: 1px solid var(--mw-border); +} + +.mw-file-table .table-hover > tbody > tr:hover { + background: var(--mw-surface-container); +} + +.mw-file-pagination { + margin-top: 12px; +} + +.mw-file-card .fileList .file { + border-radius: 12px; + border: 1px solid transparent; + background: transparent; +} + +.mw-file-card .fileList>div:hover, +.mw-file-card .fileList div.ui-selecting { + background: var(--mw-surface-container); + border-color: var(--mw-border); +} + +.mw-file-card .fileList div.active, +.mw-file-card .fileList div.focus, +.mw-file-card .fileList div.ui-selected { + background: color-mix(in srgb, var(--mw-primary) 12%, var(--mw-surface-container) 88%); + border-color: var(--mw-primary); +} + +.mw-file-context-menu { + border-radius: 12px; + border: 1px solid var(--mw-border); + box-shadow: 0 12px 32px rgba(15, 23, 42, 0.12); + padding: 8px 0; +} + +.mw-file-context-menu li a { + display: block; + padding: 8px 16px; +} + +.mw-file-context-menu li a:hover { + background: var(--mw-surface-container); +} + +.layui-layer { + border-radius: 16px !important; + box-shadow: 0 20px 60px rgba(15, 23, 42, 0.18) !important; + border: 1px solid color-mix(in srgb, var(--mw-border) 40%, transparent) !important; + background: color-mix(in srgb, var(--mw-surface) 88%, transparent) !important; + color: var(--mw-text) !important; + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} + +.layui-layer-title { + background: transparent !important; + border-bottom: 0 !important; + font-weight: 600 !important; + padding: 0 16px !important; +} + +.layui-layer-btn { + background: transparent !important; + border-top: 0 !important; + padding: 12px 16px !important; +} + +.layui-layer-btn a { + border-radius: 999px !important; + background: transparent !important; + border: 1px solid color-mix(in srgb, var(--mw-border) 40%, transparent) !important; + font-weight: 600 !important; + color: var(--mw-text) !important; + padding: 0 16px !important; + height: 34px !important; + line-height: 34px !important; +} + +.layui-layer-btn .layui-layer-btn1 { + background: transparent !important; + color: var(--mw-text) !important; + border: 1px solid color-mix(in srgb, var(--mw-border) 40%, transparent) !important; +} + +.mdui-dialog { + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +.mw-theme-label { + font-size: 13px; + color: var(--mw-muted); +} + +.mw-theme-preset-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +@media (max-width: 1024px) { + .mw-page-header { + flex-direction: column; + align-items: flex-start; + } + + .mw-soft-container { + height: auto; + } + + .mw-chart { + height: 260px; + } + + .mw-monitor-chart { + height: 260px; + } + + .mw-monitor-grid { + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + } + + .mw-monitor-span { + grid-column: auto; + } + + .mw-file-path-input input { + min-width: 200px; + } + + .mw-theme-row .set-info, + .mw-theme-state { + margin-left: 0; + } +} + +@media (max-width: 640px) { + .mw-theme-row .set-tit { + flex-basis: 100%; + } + + .mw-theme-row .mw-theme-controls { + width: 100%; + } + + .mw-theme-bg-input { + min-width: 100%; + } +} + +/* -------------------------------------------------------------------------- */ +/* Modern shell refresh */ +/* -------------------------------------------------------------------------- */ + +:root { + --mw-theme-primary-rgb: 15, 118, 110; + --mw-theme-primary: rgb(var(--mw-theme-primary-rgb)); + --mw-on-primary-rgb: 255, 255, 255; + --mw-on-primary: rgb(var(--mw-on-primary-rgb)); + --mw-primary-rgb: var(--mw-theme-primary-rgb); + --mw-primary: rgb(var(--mw-primary-rgb)); + --mw-primary-soft: color-mix(in srgb, var(--mw-primary) 16%, transparent); + --mw-bg-rgb: 246, 248, 252; + --mw-bg: rgb(var(--mw-bg-rgb)); + --mw-surface-rgb: 255, 255, 255; + --mw-surface: rgb(var(--mw-surface-rgb)); + --mw-surface-container-rgb: 236, 241, 247; + --mw-surface-container: rgb(var(--mw-surface-container-rgb)); + --mw-border-rgb: 220, 228, 237; + --mw-border: rgb(var(--mw-border-rgb)); + --mw-text-rgb: 20, 24, 31; + --mw-text: rgb(var(--mw-text-rgb)); + --mw-muted-rgb: 94, 105, 122; + --mw-muted: rgb(var(--mw-muted-rgb)); + --mw-shadow: 0 14px 30px rgba(15, 23, 42, 0.08); + --mw-radius: 18px; + --mw-app-background-overlay: radial-gradient(circle at 12% 12%, rgba(15, 118, 110, 0.10) 0%, transparent 28%), radial-gradient(circle at 86% 8%, rgba(59, 130, 246, 0.08) 0%, transparent 30%), linear-gradient(180deg, rgba(248, 250, 252, 0.96) 0%, rgba(238, 243, 248, 0.94) 100%); + --mw-app-background-image: none; + --mw-app-background-size: cover; + --mw-app-background-position: center; + --mw-app-background-repeat: no-repeat; + --mw-app-background-attachment: fixed; + --mw-glass-surface-ratio: 72%; + --mw-glass-border-ratio: 64%; + --mw-glass-blur: 22px; + --mdui-color-primary: var(--mw-primary); + --mdui-color-on-primary: var(--mw-on-primary); + --mdui-color-surface: var(--mw-surface); + --mdui-color-surface-container: var(--mw-surface-container); + --mdui-color-surface-container-low: var(--mw-bg); + --mdui-color-outline: var(--mw-border); + --mdui-color-on-surface: var(--mw-text); + --mdui-color-on-surface-variant: var(--mw-muted); +} + +:root.mdui-theme-dark { + --mw-bg-rgb: 6, 10, 18; + --mw-surface-rgb: 10, 15, 24; + --mw-surface-container-rgb: 15, 22, 34; + --mw-border-rgb: 33, 45, 63; + --mw-text-rgb: 228, 232, 241; + --mw-muted-rgb: 149, 162, 183; + --mw-shadow: 0 20px 44px rgba(0, 0, 0, 0.34); + --mw-primary-soft: color-mix(in srgb, var(--mw-primary) 22%, transparent); + --mw-app-background-overlay: radial-gradient(circle at 14% 16%, color-mix(in srgb, var(--mw-primary) 20%, transparent) 0%, transparent 30%), radial-gradient(circle at 84% 12%, rgba(45, 212, 191, 0.12) 0%, transparent 28%), radial-gradient(circle at 50% 100%, rgba(59, 130, 246, 0.08) 0%, transparent 40%), linear-gradient(180deg, rgba(6, 10, 18, 0.96) 0%, rgba(10, 14, 23, 0.92) 55%, rgba(6, 9, 15, 0.98) 100%); +} + +:root.mdui-theme-light { + --mw-glass-surface-ratio: 84%; + --mw-glass-border-ratio: 78%; + --mw-glass-blur: 18px; + --mw-app-background-overlay: radial-gradient(circle at 12% 12%, rgba(15, 118, 110, 0.08) 0%, transparent 28%), radial-gradient(circle at 86% 8%, rgba(59, 130, 246, 0.06) 0%, transparent 30%), linear-gradient(180deg, rgba(248, 250, 252, 0.82) 0%, rgba(238, 243, 248, 0.78) 100%); +} + +@media (prefers-color-scheme: dark) { + :root.mdui-theme-auto { + --mw-bg-rgb: 6, 10, 18; + --mw-surface-rgb: 10, 15, 24; + --mw-surface-container-rgb: 15, 22, 34; + --mw-border-rgb: 33, 45, 63; + --mw-text-rgb: 228, 232, 241; + --mw-muted-rgb: 149, 162, 183; + --mw-shadow: 0 20px 44px rgba(0, 0, 0, 0.34); + --mw-primary-soft: color-mix(in srgb, var(--mw-primary) 22%, transparent); + --mw-app-background-overlay: radial-gradient(circle at 14% 16%, color-mix(in srgb, var(--mw-primary) 18%, transparent) 0%, transparent 30%), radial-gradient(circle at 84% 12%, rgba(45, 212, 191, 0.10) 0%, transparent 28%), radial-gradient(circle at 50% 100%, rgba(59, 130, 246, 0.06) 0%, transparent 40%), linear-gradient(180deg, rgba(6, 10, 18, 0.90) 0%, rgba(10, 14, 23, 0.84) 55%, rgba(6, 9, 15, 0.94) 100%); + } +} + +@media (prefers-color-scheme: light) { +:root.mdui-theme-auto { + --mw-bg-rgb: 246, 248, 252; + --mw-surface-rgb: 255, 255, 255; + --mw-surface-container-rgb: 236, 241, 247; + --mw-border-rgb: 220, 228, 237; + --mw-text-rgb: 20, 24, 31; + --mw-muted-rgb: 94, 105, 122; + --mw-shadow: 0 14px 30px rgba(15, 23, 42, 0.08); + --mw-primary-soft: color-mix(in srgb, var(--mw-primary) 16%, transparent); + --mw-app-background-overlay: radial-gradient(circle at 12% 12%, rgba(15, 118, 110, 0.08) 0%, transparent 28%), radial-gradient(circle at 86% 8%, rgba(59, 130, 246, 0.06) 0%, transparent 30%), linear-gradient(180deg, rgba(248, 250, 252, 0.82) 0%, rgba(238, 243, 248, 0.78) 100%); + } +} + +html, +body { + font-family: "Inter", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", system-ui, sans-serif; + color: var(--mw-text); + background-color: var(--mw-bg); + background-image: var(--mw-app-background-overlay), var(--mw-app-background-image); + background-size: auto, var(--mw-app-background-size); + background-position: center, var(--mw-app-background-position); + background-repeat: no-repeat, var(--mw-app-background-repeat); + background-attachment: fixed, var(--mw-app-background-attachment); + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; +} + +::selection { + background: color-mix(in srgb, var(--mw-primary) 30%, transparent); + color: var(--mw-on-primary); +} + +a { + text-decoration: none; +} + +a:hover { + color: var(--mw-primary); +} + +.c0, +.c3, +.c4 { + color: var(--mw-text) !important; +} + +.c5, +.c6 { + color: color-mix(in srgb, var(--mw-text) 76%, var(--mw-muted) 24%) !important; +} + +.c7 { + color: var(--mw-muted) !important; +} + +.c8 { + color: color-mix(in srgb, var(--mw-muted) 86%, var(--mw-text) 14%) !important; +} + +.c9 { + color: color-mix(in srgb, var(--mw-muted) 92%, var(--mw-text) 8%) !important; +} + +.cbt { + color: var(--mw-primary) !important; +} + +.bgw, +.pos-box, +.softbox, +.setbox, +.white-black-ip, +.divtable, +.tab-view-box, +.mw-card, +.mw-file-toolbar, +.title.tabs-nav, +#logAudit .logAuditTab, +.mw-theme-settings { + background: color-mix(in srgb, var(--mw-surface) 92%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 86%, transparent); + border-radius: var(--mw-radius); + box-shadow: var(--mw-shadow); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); +} + +.bgw, +.pos-box, +.softbox, +.setbox, +.white-black-ip, +.divtable, +.tab-view-box, +.mw-card, +.mw-file-toolbar, +.title.tabs-nav, +#logAudit .logAuditTab, +.mw-theme-settings, +.mw-file-table, +.mw-page-header, +.mw-observe-panel, +.mw-observe-chart-card, +.mw-login-card { + transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease, background 0.25s ease; +} + +.bgw:hover, +.pos-box:hover, +.softbox:hover, +.setbox:hover, +.white-black-ip:hover, +.divtable:hover, +.tab-view-box:hover, +.mw-card:hover, +.mw-file-toolbar:hover, +.mw-theme-settings:hover { + transform: translateY(-1px); + box-shadow: 0 20px 42px rgba(15, 23, 42, 0.12); +} + +@keyframes mw-panel-enter { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.mw-content > *, +.mw-dashboard > *, +.mw-dashboard-body > *, +.mw-observe-shell > *, +.main-content > .container-fluid > * { + animation: mw-panel-enter 0.36s ease both; +} + +.mw-shell { + background: transparent; +} + +.mw-main { + min-height: 100vh; +} + +.mw-topbar { + padding: 18px 28px 0; +} + +.mw-topbar-glass { + border-bottom: none; +} + +.mw-topbar-card { + max-width: 1560px; + margin: 0 auto; + padding: 14px 18px; + gap: 18px; + background: color-mix(in srgb, var(--mw-surface) 84%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 72%, transparent); + border-radius: 24px; + box-shadow: 0 18px 50px rgba(15, 23, 42, 0.12); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); +} + +.mw-topbar-brand { + gap: 2px; +} + +.mw-topbar-title { + font-size: 18px; + letter-spacing: 0.01em; +} + +.mw-topbar-sub { + font-size: 12px; + letter-spacing: 0.02em; + text-transform: uppercase; +} + +.mw-topbar-nav, +.mw-topbar-actions { + padding: 6px; + border-radius: 999px; + background: color-mix(in srgb, var(--mw-surface-container) 80%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 78%, transparent); + gap: 6px; +} + +.mw-topbar-item, +.mw-icon-button { + width: 42px; + height: 42px; + border-radius: 999px; + color: var(--mw-muted); +} + +.mw-topbar-item:hover, +.mw-icon-button:hover { + background: var(--mw-surface); + color: var(--mw-primary); +} + +.mw-topbar-item.is-active { + background: color-mix(in srgb, var(--mw-primary) 15%, var(--mw-surface) 85%); + color: var(--mw-primary); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--mw-primary) 32%, transparent); +} + +.mw-content { + max-width: 1560px; + padding: 28px 32px 42px; +} + +.mw-footer { + max-width: 1560px; +} + +.mw-footer-card { + background: color-mix(in srgb, var(--mw-surface) 94%, transparent); +} + +.mw-card { + background: color-mix(in srgb, var(--mw-surface) 92%, transparent); + border-color: color-mix(in srgb, var(--mw-border) 88%, transparent); +} + +.mw-card-header, +.mw-page-header, +.title, +.mw-observe-panel-header, +.mw-observe-chart-header, +.mw-monitor-summary-header, +.mw-file-toolbar, +#logAudit .tootls_group { + border-color: color-mix(in srgb, var(--mw-border) 88%, transparent); +} + +.mw-page-header { + position: relative; + overflow: hidden; + background: linear-gradient(135deg, color-mix(in srgb, var(--mw-primary) 10%, var(--mw-surface) 90%) 0%, color-mix(in srgb, var(--mw-surface) 96%, transparent) 55%, color-mix(in srgb, var(--mw-primary) 6%, var(--mw-surface) 94%) 100%); +} + +.mw-page-header::after { + content: ""; + position: absolute; + inset: auto -120px -140px auto; + width: 280px; + height: 280px; + border-radius: 50%; + background: radial-gradient(circle, color-mix(in srgb, var(--mw-primary) 24%, transparent) 0%, transparent 72%); + pointer-events: none; +} + +.mw-page-title { + font-size: 22px; + letter-spacing: 0.01em; +} + +.mw-page-sub, +.mw-version, +.mw-footer-content, +.mw-theme-state, +.mw-topbar-sub, +.mw-observe-panel-subtitle, +.mw-observe-main-subtitle, +.mw-observe-chart-subtitle { + color: var(--mw-muted); +} + +.mw-page-actions .mdui-button, +.mw-page-actions .btn, +.mw-theme-settings .mdui-button, +.mw-theme-settings .btn, +.mw-file-toolbar .btn, +.mw-file-toolbar mdui-button, +.mw-card .btn, +.mw-card mdui-button { + box-shadow: none; +} + +.main-content { + width: 100%; + min-width: 0; +} + +.pos-box { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; + padding: 16px 18px; +} + +.position { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0; + color: var(--mw-muted); +} + +.position a { + color: var(--mw-muted); +} + +.position a:hover { + color: var(--mw-primary); +} + +.search { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + border-radius: 999px; + background: color-mix(in srgb, var(--mw-surface-container) 84%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 86%, transparent); +} + +.ser-text { + min-width: 220px; + border-radius: 999px; + background: var(--mw-surface); +} + +.ser-sub { + width: 38px; + height: 38px; + border-radius: 999px; + background: var(--mw-primary); + border-color: transparent; + color: var(--mw-on-primary); +} + +.ser-sub::before { + color: inherit; +} + +.title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 20px; +} + +.title h3 { + margin: 0; + font-size: 16px; + font-weight: 700; + letter-spacing: 0.01em; +} + +.title.tabs-nav { + padding: 8px; +} + +.tab-list, +.tabs-nav .tab-list { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px; + border-radius: 999px; + background: color-mix(in srgb, var(--mw-surface-container) 84%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 88%, transparent); +} + +.tab-list .tabs-item, +.tabs-nav .tabs-item { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 96px; + height: 38px; + padding: 0 16px; + border-radius: 999px; + color: var(--mw-muted); + background: transparent; + position: relative; + transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; +} + +.tab-list .tabs-item:hover, +.tabs-nav .tabs-item:hover { + background: var(--mw-surface); + color: var(--mw-primary); +} + +.tab-list .tabs-item.active, +.tabs-nav .tabs-item.active { + color: var(--mw-primary); + background: color-mix(in srgb, var(--mw-primary) 14%, var(--mw-surface) 86%); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--mw-primary) 28%, transparent); +} + +.tab-list .tabs-item.active:after, +.tabs-nav .tabs-item.active:after { + display: none; +} + +.tab-view-box { + padding: 18px; +} + +.tootls_group { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + min-height: auto; + line-height: 1.4; +} + +.tootls_group .search { + width: auto; + background: transparent; + border: none; + padding: 0; +} + +.tootls_group .search .search_input { + width: 240px; + height: 38px; + line-height: 38px; + padding: 0 14px; + border-radius: 999px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); + color: var(--mw-text); +} + +.tootls_group .search .search_input:focus { + border-color: var(--mw-primary); + box-shadow: 0 0 0 3px var(--mw-primary-soft); +} + +.tootls_group .search .glyphicon-search { + top: 50%; + transform: translateY(-50%); + color: var(--mw-muted); +} + +.setting-con { + display: flex; + flex-direction: column; + gap: 0; +} + +.setting-con p { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px; + margin: 0; + padding: 12px 16px; + border-bottom: 1px solid color-mix(in srgb, var(--mw-border) 84%, transparent); + line-height: 1.5; +} + +.setting-con p:last-child { + border-bottom: none; +} + +.setting-con p .set-tit { + flex: 0 0 140px; + width: auto; + height: auto; + margin-right: 0; + color: var(--mw-muted); + font-weight: 600; +} + +.setting-con p .inputtxt { + flex: 1 1 260px; + min-width: 240px; + width: auto; + background: var(--mw-surface); +} + +.setting-con p .modify { + margin-left: 0; +} + +.setting-con p .set-info { + flex: 1 1 100%; + margin-left: 140px; + color: var(--mw-muted); + line-height: 1.6; +} + +.setbox .title, +.softbox .title, +.white-black-ip .title, +.bgw .title { + background: linear-gradient(90deg, color-mix(in srgb, var(--mw-primary) 8%, transparent) 0%, transparent 100%); +} + +.plan { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; + padding: 14px 18px; + margin-bottom: 12px; + border-bottom: 1px solid color-mix(in srgb, var(--mw-border) 84%, transparent); +} + +.plan .typename { + width: auto; + flex: 0 0 140px; + line-height: 1.5; + color: var(--mw-muted); + font-weight: 600; +} + +.plan .textname { + height: auto; + line-height: 1.4; +} + +.plan .planname input { + width: min(320px, 100%); + min-height: 38px; + border-radius: 12px; +} + +.plan .dropdown button { + min-width: 116px; + height: 38px; + border-radius: 12px; + background: var(--mw-surface); + border-color: var(--mw-border); + color: var(--mw-text); +} + +.plan .dropdown button b { + font-weight: 600; +} + +.plan .dropdown-menu { + min-width: 140px; + border-radius: 14px; + border: 1px solid var(--mw-border); + box-shadow: var(--mw-shadow); +} + +.plan-submit { + margin-left: 140px; +} + +.plan_hms { + display: inline-flex; + align-items: stretch; + height: 38px; + overflow: hidden; + border-radius: 12px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); +} + +.plan_hms span { + float: none; + display: inline-flex; + align-items: center; + height: 100%; + line-height: 1; +} + +.plan_hms span input { + width: 58px; + height: 100%; + line-height: 1; + border: none; + background: transparent; + color: var(--mw-text); +} + +.plan_hms .name { + width: 54px; + border-left: 1px solid var(--mw-border); + background: var(--mw-surface-container); + color: var(--mw-muted); +} + +.planSign { + margin-left: 140px; + color: var(--mw-muted); +} + +.planSign i { + color: var(--mw-primary); +} + +.safe, +.safe-port, +.white-black-ip, +.mw-theme-settings, +.mw-file-toolbar, +.mw-observe-panel, +#logAudit .logAuditTab { + overflow: hidden; +} + +.safe, +.safe-port, +.white-black-ip { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 16px; +} + +.safe-port { + padding: 18px; +} + +.ss-text { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 180px; +} + +.ss-text em { + font-style: normal; + font-weight: 600; + color: var(--mw-text); +} + +.ssh-item { + display: flex; + align-items: center; + gap: 10px; +} + +.weblog { + display: inline-flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; + padding-left: 12px; + color: var(--mw-muted); + border-left: 1px solid color-mix(in srgb, var(--mw-border) 84%, transparent); +} + +.firewall-port-box { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 12px; +} + +.firewall-port-box .bt-input-text, +.firewall-port-box select { + min-height: 38px; + border-radius: 12px; +} + +.info-title-tips, +.important-title { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 14px; + margin: 0 0 12px; + border-radius: 14px; + background: color-mix(in srgb, var(--mw-primary) 8%, var(--mw-surface) 92%); + border: 1px solid color-mix(in srgb, var(--mw-primary) 16%, var(--mw-border) 84%); +} + +.important-title { + color: color-mix(in srgb, #ef4444 68%, var(--mw-text) 32%); +} + +.info-title-tips p, +.important-title > p { + margin: 0; +} + +.info-title-tips .glyphicon-alert, +.important-title .glyphicon-alert { + color: var(--mw-primary) !important; +} + +.mw-theme-presets { + margin-top: 12px; +} + +.mw-theme-preset-list { + gap: 10px; +} + +.mw-theme-preset, +.mw-theme-mode-btn, +.mw-file-icon-btn, +.mw-observe-chip { + border-radius: 999px; +} + +.btn, +button, +input[type="submit"] { + border-radius: 12px; +} + +.btn-default { + background: var(--mw-surface); + color: var(--mw-text); + border-color: var(--mw-border); +} + +.btn-default:hover { + background: var(--mw-surface-container); + color: var(--mw-text); +} + +.btn-success { + background: var(--mw-primary); + border-color: var(--mw-primary); + color: var(--mw-on-primary); +} + +.btn-success:hover { + background: color-mix(in srgb, var(--mw-primary) 86%, #000 14%); + color: var(--mw-on-primary); +} + +.btn-info { + background: color-mix(in srgb, var(--mw-primary) 14%, var(--mw-surface) 86%); + border-color: color-mix(in srgb, var(--mw-primary) 28%, var(--mw-border) 72%); + color: var(--mw-primary); +} + +.btn-warning { + background: #f59e0b; + border-color: #f59e0b; + color: #fff; +} + +.btn-danger { + background: #ef4444; + border-color: #ef4444; + color: #fff; +} + +.alert { + border-radius: 14px; +} + +.alert-warning { + background: color-mix(in srgb, var(--mw-primary) 8%, var(--mw-surface) 92%); + border-color: color-mix(in srgb, var(--mw-primary) 16%, var(--mw-border) 84%); + color: var(--mw-text); +} + +#messageError { + background: color-mix(in srgb, var(--mw-primary) 8%, var(--mw-surface) 92%) !important; + border-color: color-mix(in srgb, var(--mw-primary) 18%, var(--mw-border) 82%) !important; + color: var(--mw-text) !important; +} + +.page a, +.page span, +.pagination li a { + background: var(--mw-surface); + border-color: var(--mw-border); + color: var(--mw-muted); +} + +.page .Pcurrent, +.pagination li.active a { + background: var(--mw-primary); + border-color: var(--mw-primary); + color: var(--mw-on-primary); +} + +.table { + border-color: var(--mw-border); +} + +.table > thead > tr > th, +.table > tbody > tr > td { + border-color: color-mix(in srgb, var(--mw-border) 84%, transparent); +} + +.table thead th { + background: color-mix(in srgb, var(--mw-surface-container) 84%, transparent); + color: var(--mw-muted); + font-weight: 700; + letter-spacing: 0.02em; +} + +.table tbody tr:hover { + background: color-mix(in srgb, var(--mw-primary) 6%, var(--mw-surface) 94%); +} + +.divtable { + overflow: hidden; +} + +.divtable .table { + border: none; + border-radius: 0; + box-shadow: none; +} + +#logAudit .logAuditTabContent { + display: grid; + grid-template-columns: 280px minmax(0, 1fr); + gap: 16px; +} + +#logAudit .logAuditTab { + overflow: hidden; +} + +#logAudit .logAuditTab .logAuditItem { + padding: 0 14px; + border-bottom: 1px solid color-mix(in srgb, var(--mw-border) 84%, transparent); + color: var(--mw-text); +} + +#logAudit .logAuditTab .logAuditItem.active, +#logAudit .logAuditTab .logAuditItem:hover { + background: color-mix(in srgb, var(--mw-primary) 8%, var(--mw-surface) 92%); +} + +#logAudit .logAuditContent { + padding-left: 0; + margin-left: 0; + border-left: none; +} + +#operationLog thead th:nth-child(1), +#operationLog tbody tr td:nth-child(1) { + width: 100px; +} + +#operationLog thead th:nth-child(2), +#operationLog tbody tr td:nth-child(2) { + width: 130px; +} + +#operationLog thead th:nth-child(4), +#operationLog tbody tr td:nth-child(4) { + width: 150px; +} + +#logAuditTable thead th:nth-child(4), +#logAuditTable tbody tr td:nth-child(4) { + width: 300px; +} + +#logAuditPre pre { + min-height: 188px; + margin: 0; + padding: 16px; + background: color-mix(in srgb, var(--mw-surface-container) 72%, #000 28%); + color: var(--mw-text); + overflow-x: hidden; + overflow-wrap: break-word; + white-space: pre-wrap; + border-radius: 14px; + border: 1px solid color-mix(in srgb, var(--mw-border) 80%, transparent); +} + +.mw-file-card .mw-card-content { + background: transparent; +} + +.mw-file-toolbar { + padding: 18px; +} + +.mw-file-toolbar-row { + gap: 14px; +} + +.mw-file-path-field, +.mw-file-search-field, +.mw-file-breadcrumbs span { + border-radius: 999px; +} + +.mw-file-path-field, +.mw-file-search-field, +.mw-file-breadcrumbs span, +.mw-file-icon-btn, +.mw-file-tool-items .btn, +.mw-file-batch .btn, +.mw-file-actions .btn { + border-color: var(--mw-border); +} + +.mw-file-path-label { + color: var(--mw-muted); +} + +.mw-file-path-input input, +.mw-file-search-input { + color: var(--mw-text); +} + +.mw-file-table, +.mw-file-context-menu { + border: 1px solid color-mix(in srgb, var(--mw-border) 88%, transparent); + border-radius: var(--mw-radius); +} + +.mw-file-table .table thead th { + background: color-mix(in srgb, var(--mw-surface-container) 84%, transparent); +} + +.mw-file-card .fileList div.active, +.mw-file-card .fileList div.focus, +.mw-file-card .fileList div.ui-selected { + background: color-mix(in srgb, var(--mw-primary) 12%, var(--mw-surface-container) 88%); + border-color: var(--mw-primary); +} + +.mw-observe-hero { + background: linear-gradient(135deg, color-mix(in srgb, var(--mw-primary) 14%, var(--mw-surface) 86%) 0%, color-mix(in srgb, var(--mw-surface) 96%, transparent) 58%, color-mix(in srgb, var(--mw-primary) 6%, var(--mw-surface) 94%) 100%); +} + +.mw-observe-chip.is-active, +.mw-observe-chip:hover { + background: var(--mw-primary); + color: var(--mw-on-primary); + border-color: transparent; +} + +.mw-observe-kpi { + background: color-mix(in srgb, var(--mw-surface-container) 88%, transparent); + border-color: color-mix(in srgb, var(--mw-border) 84%, transparent); +} + +.mw-observe-kpi-meter { + background: color-mix(in srgb, var(--mw-border) 52%, transparent); +} + +.mw-observe-kpi-meter span { + background: linear-gradient(90deg, var(--mw-primary) 0%, color-mix(in srgb, var(--mw-primary) 56%, #fff 44%) 100%); +} + +.mw-observe-settings-item, +.mw-observe-event-list li { + background: color-mix(in srgb, var(--mw-surface-container) 88%, transparent); + border-color: color-mix(in srgb, var(--mw-border) 84%, transparent); +} + +.mw-observe-event-dot { + background: var(--mw-primary); +} + +.mw-login-card { + background: color-mix(in srgb, var(--mw-surface) 88%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 76%, transparent); + box-shadow: 0 24px 64px rgba(15, 23, 42, 0.18); +} + +.mw-standalone-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 32px 20px; + margin: 0; + width: 100%; +} + +.mw-standalone-shell { + width: min(920px, 100%); +} + +.mw-standalone-card { + padding: 28px; + display: flex; + flex-direction: column; + gap: 22px; +} + +.mw-standalone-hero { + display: flex; + gap: 16px; + align-items: flex-start; +} + +.mw-standalone-icon { + width: 60px; + height: 60px; + border-radius: 20px; + display: grid; + place-items: center; + flex: 0 0 auto; + background: linear-gradient(135deg, var(--mw-primary) 0%, color-mix(in srgb, var(--mw-primary) 70%, #ffffff 30%) 100%); + color: var(--mw-on-primary); + box-shadow: 0 16px 32px color-mix(in srgb, var(--mw-primary) 28%, transparent); +} + +.mw-standalone-title { + margin: 0; + font-size: 26px; + font-weight: 800; + letter-spacing: 0.01em; +} + +.mw-standalone-copy { + margin: 0; + color: var(--mw-muted); + line-height: 1.75; +} + +.mw-standalone-meta { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.mw-standalone-pill { + padding: 8px 12px; + border-radius: 999px; + background: color-mix(in srgb, var(--mw-surface-container) 88%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 82%, transparent); + color: var(--mw-muted); + font-size: 13px; + line-height: 1; +} + +.mw-standalone-steps { + display: grid; + gap: 12px; +} + +.mw-standalone-step { + padding: 14px 16px; + border-radius: 14px; + background: color-mix(in srgb, var(--mw-surface-container) 92%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 84%, transparent); + color: var(--mw-text); + line-height: 1.65; +} + +.mw-standalone-step strong { + display: block; + margin-bottom: 6px; + font-size: 14px; +} + +.mw-standalone-code { + margin: 0; + padding: 16px 18px; + border-radius: 14px; + background: color-mix(in srgb, var(--mw-surface-container) 94%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 84%, transparent); + color: var(--mw-text); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; + font-size: 13px; + line-height: 1.7; + white-space: pre-wrap; + overflow-x: auto; +} + +.mw-standalone-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.mw-standalone-action { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 44px; + padding: 0 18px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--mw-border) 82%, transparent); + text-decoration: none; + color: var(--mw-text); + background: color-mix(in srgb, var(--mw-surface-container) 90%, transparent); + transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease, color 0.2s ease; +} + +.mw-standalone-action:hover { + transform: translateY(-1px); + box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); +} + +.mw-standalone-action.primary { + background: var(--mw-primary); + color: var(--mw-on-primary); + border-color: transparent; +} + +.mw-standalone-note { + color: var(--mw-muted); + font-size: 13px; + line-height: 1.65; +} + +@media (max-width: 1280px) { + .mw-content, + .mw-footer { + max-width: 100%; + } + + .mw-topbar-card { + flex-wrap: wrap; + } + + .mw-topbar-center { + order: 3; + width: 100%; + justify-content: flex-start; + } + + .mw-topbar-right { + margin-left: auto; + } + + .setting-con p .set-info, + .plan .planSign { + margin-left: 0; + } +} + +@media (max-width: 1024px) { + .mw-topbar { + padding: 14px 20px 0; + } + + .mw-content { + padding: 22px 20px 36px; + } + + .mw-footer { + padding: 0 20px 20px; + } + + .mw-page-header, + .mw-card-header, + .mw-card-content, + .mw-file-toolbar, + .setting-con p, + .plan, + .safe-port, + .firewall-port-box { + padding-left: 16px; + padding-right: 16px; + } + + .mw-observe-shell, + #logAudit .logAuditTabContent { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + .mw-topbar-card { + border-radius: 20px; + } + + .mw-topbar-left, + .mw-topbar-right, + .mw-topbar-center { + width: 100%; + } + + .mw-topbar-nav { + width: 100%; + justify-content: flex-start; + overflow-x: auto; + } + + .mw-topbar-actions { + margin-left: 0; + } + + .pos-box, + .setting-con p, + .plan, + .firewall-port-box, + .safe-port, + .weblog { + align-items: flex-start; + } + + .setting-con p .set-tit, + .plan .typename { + flex-basis: 100%; + } + + .setting-con p .set-info, + .plan .planSign, + .plan-submit { + margin-left: 0; + } + + .search, + .ser-text, + .mw-file-path-input input, + .mw-file-search-input, + .plan .planname input { + width: 100%; + min-width: 0; + } + + .mw-file-toolbar-row { + flex-wrap: wrap; + } + + .mw-file-search { + margin-left: 0; + width: 100%; + } +} + +/* stronger glass treatment for dashboard surfaces */ +.bgw, +.pos-box, +.softbox, +.setbox, +.white-black-ip, +.divtable, +.tab-view-box, +.mw-card, +.mw-file-toolbar, +.title.tabs-nav, +#logAudit .logAuditTab, +.mw-theme-settings, +.mw-file-table, +.mw-page-header, +.mw-observe-panel, +.mw-observe-chart-card, +.mw-login-card, +.mw-standalone-card { + background: color-mix(in srgb, var(--mw-surface) var(--mw-glass-surface-ratio), transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) var(--mw-glass-border-ratio), transparent); + backdrop-filter: blur(var(--mw-glass-blur)); + -webkit-backdrop-filter: blur(var(--mw-glass-blur)); +} + +.bgw:hover, +.pos-box:hover, +.softbox:hover, +.setbox:hover, +.white-black-ip:hover, +.divtable:hover, +.tab-view-box:hover, +.mw-card:hover, +.mw-file-toolbar:hover, +.mw-theme-settings:hover, +.mw-page-header:hover, +.mw-observe-panel:hover, +.mw-observe-chart-card:hover { + box-shadow: 0 22px 48px rgba(15, 23, 42, 0.14); +} + +.mw-file-toolbar { + position: sticky; + top: 18px; + z-index: 120; +} + +.mw-file-card .mw-card-content { + background: transparent; +} + +.mw-file-table { + overflow: hidden; +} + +.mw-file-table .table thead th, +.mw-file-table .table tbody td { + background: transparent; +} + +.mw-theme-settings, +.mw-file-toolbar, +.mw-observe-panel, +.mw-observe-chart-card { + box-shadow: 0 18px 42px rgba(15, 23, 42, 0.10); +} + +.mw-page-header { + background: linear-gradient(135deg, color-mix(in srgb, var(--mw-primary) 10%, transparent) 0%, color-mix(in srgb, var(--mw-surface) 86%, transparent) 62%, color-mix(in srgb, var(--mw-primary) 5%, transparent) 100%); +} + +.mw-soft-upload-line { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + margin-top: 14px; + color: var(--mw-muted); +} + +.mw-soft-table, +.mw-site-table { + width: 100%; +} + +:root { + --mw-glass-surface-ratio: 72%; + --mw-glass-border-ratio: 70%; + --mw-glass-blur: 22px; + --mw-app-background-overlay: linear-gradient(180deg, color-mix(in srgb, var(--mw-bg) 56%, transparent) 0%, color-mix(in srgb, var(--mw-bg) 90%, transparent) 100%); +} + +:root.mdui-theme-dark { + --mw-glass-surface-ratio: 84%; + --mw-glass-border-ratio: 76%; + --mw-glass-blur: 20px; +} + +/* shared action/button polish */ +mdui-button, +mdui-button[variant], +.mw-page-actions mdui-button, +.mw-theme-settings mdui-button, +.mw-file-toolbar mdui-button, +.mw-card mdui-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 40px; + padding: 0 16px; + border-radius: 999px; + border: 1px solid var(--mw-border); + background: color-mix(in srgb, var(--mw-surface-container) 80%, transparent); + color: var(--mw-text); + box-sizing: border-box; + white-space: nowrap; + text-decoration: none; + cursor: pointer; + box-shadow: none; + transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease, color 0.2s ease; +} + +mdui-button:hover, +mdui-button[variant]:hover, +.mw-page-actions mdui-button:hover, +.mw-theme-settings mdui-button:hover, +.mw-file-toolbar mdui-button:hover, +.mw-card mdui-button:hover { + transform: translateY(-1px); + background: color-mix(in srgb, var(--mw-primary) 10%, var(--mw-surface) 90%); + border-color: color-mix(in srgb, var(--mw-primary) 24%, var(--mw-border) 76%); + color: var(--mw-primary); +} + +mdui-button[variant="filled"], +.mw-page-actions mdui-button[variant="filled"], +.mw-theme-settings mdui-button[variant="filled"], +.mw-file-toolbar mdui-button[variant="filled"], +.mw-card mdui-button[variant="filled"] { + background: var(--mw-primary); + color: var(--mw-on-primary); + border-color: transparent; +} + +mdui-button[variant="filled"]:hover, +.mw-page-actions mdui-button[variant="filled"]:hover, +.mw-theme-settings mdui-button[variant="filled"]:hover, +.mw-file-toolbar mdui-button[variant="filled"]:hover, +.mw-card mdui-button[variant="filled"]:hover { + background: color-mix(in srgb, var(--mw-primary) 86%, #000 14%); + color: var(--mw-on-primary); +} + +mdui-button[variant="outlined"], +.mw-page-actions mdui-button[variant="outlined"], +.mw-theme-settings mdui-button[variant="outlined"], +.mw-file-toolbar mdui-button[variant="outlined"], +.mw-card mdui-button[variant="outlined"] { + background: color-mix(in srgb, var(--mw-surface) 92%, transparent); +} + +mdui-button[variant="text"], +.mw-page-actions mdui-button[variant="text"], +.mw-theme-settings mdui-button[variant="text"], +.mw-file-toolbar mdui-button[variant="text"], +.mw-card mdui-button[variant="text"] { + background: color-mix(in srgb, var(--mw-surface-container) 58%, transparent); +} + +mdui-button::part(button) { + border-radius: inherit; + font: inherit; +} + +/* shared empty states */ +.mw-empty-state, +.mw-soft-empty-state { + width: 100%; + min-height: 220px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + padding: 28px 20px; + border-radius: 18px; + border: 1px dashed color-mix(in srgb, var(--mw-border) 88%, transparent); + background: color-mix(in srgb, var(--mw-surface-container) 70%, transparent); + color: var(--mw-muted); + text-align: center; + box-sizing: border-box; +} + +.mw-empty-state .material-icons, +.mw-soft-empty-state .material-icons { + font-size: 32px; + color: var(--mw-primary); +} + +.mw-empty-state strong, +.mw-soft-empty-state strong { + font-size: 15px; + color: var(--mw-text); +} + +.mw-empty-state p, +.mw-soft-empty-state p { + margin: 0; + color: var(--mw-muted); +} + +/* dashboard header + search bars */ +.mw-page-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 10px; + flex-wrap: wrap; + min-width: 0; +} + +.mw-page-actions mdui-button, +.mw-page-actions .btn { + flex: 0 0 auto; +} + +.pos-box { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + flex-wrap: wrap; + height: auto !important; + min-height: 72px; + overflow: visible; +} + +.position { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0; + min-width: 0; + flex: 1 1 280px; + color: var(--mw-muted); +} + +.search { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + width: auto !important; + min-width: 280px; + max-width: 100%; + flex: 1 1 340px; + border-radius: 999px; + background: color-mix(in srgb, var(--mw-surface-container) 78%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-border) 88%, transparent); + box-sizing: border-box; + overflow: visible; +} + +.search form { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: nowrap; + width: 100%; + min-width: 0; +} + +.search .ser-text { + width: auto !important; + min-width: 220px; + flex: 1 1 220px; + margin-top: 0 !important; + height: 42px; + line-height: 42px; +} + +.search .ser-sub { + width: 42px; + height: 42px; + margin-top: 0 !important; + flex: 0 0 42px; +} + +.tootls_group { + height: auto !important; + line-height: normal !important; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; +} + +.tootls_group .search { + position: static !important; + top: auto !important; + right: auto !important; + width: auto !important; + height: auto !important; + line-height: normal !important; + background: transparent; + border: none; + padding: 0; + min-width: 0; +} + +.tootls_group .search .search_input { + width: min(100%, 320px); + min-width: 0; + height: 42px; + line-height: 42px; + border-radius: 999px; + border: 1px solid var(--mw-border); + background: var(--mw-surface); + padding: 0 14px; + box-sizing: border-box; +} + +.tootls_group .search .glyphicon-search { + position: static; + float: none; + height: auto; + line-height: 1; + padding: 0; +} + +.tootls_group .search .glyphicon-remove-sign { + position: static; + top: auto; + right: auto; +} + +/* system cards */ +.mw-stat-grid { + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); +} + +.mw-stat-grid > li { + width: auto !important; + min-width: 0; + min-height: 230px; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + gap: 10px; + padding: 18px; + box-sizing: border-box; +} + +.mw-stat-grid > li h3 { + margin: 0; +} + +.mw-stat-value { + font-size: 28px; + font-weight: 700; + line-height: 1.1; +} + +.mw-stat-status { + margin-top: auto; + color: var(--mw-muted); +} + +/* software homepage */ +.mw-soft-container { + height: auto; + overflow: visible; +} + +#indexsoft { + margin-left: 0; + margin-right: 0; +} + +.soft-man { + position: relative; +} + +.soft-man .col-lg-3 { + border: 0 !important; + margin: 0 !important; + padding: 8px !important; + height: auto !important; + cursor: pointer; +} + +.soft-man .col-lg-3 > div { + position: relative !important; + left: auto !important; + top: auto !important; + width: 100% !important; + height: 100% !important; + min-height: 156px; + padding: 18px 14px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + border-radius: 16px; + border: 1px solid var(--mw-border); + background: color-mix(in srgb, var(--mw-surface) 88%, transparent); + box-shadow: var(--mw-shadow); + box-sizing: border-box; +} + +.soft-man .image { + margin: 0; + height: auto; + text-align: center; +} + +.soft-man .image img { + max-width: 48px; + max-height: 48px; + object-fit: contain; +} + +.soft-man .sname { + color: var(--mw-text); + text-align: center; + line-height: 1.35; + word-break: break-word; + font-weight: 600; +} + +.soft-man .dashed-border { + border: 1px dashed color-mix(in srgb, var(--mw-primary) 48%, var(--mw-border) 52%); + background: transparent; + min-height: 156px; +} + +.soft-man .no-bg { + display: none !important; +} + +.soft-man .col-sm-3 > .mw-soft-home-card, +.soft-man .col-md-3 > .mw-soft-home-card, +.soft-man .col-lg-3 > .mw-soft-home-card { + position: relative !important; + inset: auto !important; + width: 100% !important; + min-width: 0 !important; + min-height: 172px !important; + padding: 18px !important; + display: flex !important; + flex-direction: column !important; + align-items: stretch !important; + justify-content: space-between !important; + gap: 14px !important; + border-radius: 18px !important; + border: 1px solid var(--mw-border) !important; + background: color-mix(in srgb, var(--mw-surface) 90%, transparent) !important; + box-shadow: var(--mw-shadow) !important; + box-sizing: border-box !important; + cursor: pointer !important; + overflow: hidden !important; + transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; +} + +.soft-man .mw-soft-home-item { + position: relative !important; + min-width: 0 !important; +} + +.soft-man .col-sm-3 > .mw-soft-home-card:hover, +.soft-man .col-md-3 > .mw-soft-home-card:hover, +.soft-man .col-lg-3 > .mw-soft-home-card:hover { + transform: translateY(-1px); + border-color: color-mix(in srgb, var(--mw-primary) 18%, var(--mw-border) 82%) !important; +} + +.soft-man .mw-soft-home-top { + display: flex !important; + align-items: flex-start !important; + gap: 14px !important; + width: 100% !important; + min-width: 0 !important; +} + +.soft-man .mw-soft-home-icon { + flex: 0 0 56px !important; + width: 56px !important; + height: 56px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 16px !important; + border: 1px solid color-mix(in srgb, var(--mw-border) 84%, transparent) !important; + background: color-mix(in srgb, var(--mw-primary) 8%, var(--mw-surface) 92%) !important; + box-sizing: border-box !important; +} + +.soft-man .mw-soft-home-icon img { + width: 32px !important; + height: 32px !important; + max-width: none !important; + max-height: none !important; + object-fit: contain !important; +} + +.soft-man .mw-soft-home-copy { + min-width: 0 !important; + flex: 1 1 auto !important; + display: flex !important; + flex-direction: column !important; + gap: 6px !important; +} + +.soft-man .mw-soft-home-title { + color: var(--mw-text) !important; + font-size: 15px !important; + font-weight: 700 !important; + line-height: 1.35 !important; + overflow-wrap: anywhere !important; +} + +.soft-man .mw-soft-home-meta { + color: var(--mw-muted) !important; + font-size: 13px !important; + line-height: 1.4 !important; +} + +.soft-man .mw-soft-home-desc { + color: var(--mw-muted) !important; + font-size: 12px !important; + line-height: 1.45 !important; + display: -webkit-box !important; + -webkit-line-clamp: 2 !important; + -webkit-box-orient: vertical !important; + overflow: hidden !important; + min-height: 34px !important; +} + +.soft-man .mw-soft-home-footer { + display: flex !important; + align-items: center !important; + justify-content: space-between !important; + gap: 10px !important; + width: 100% !important; + margin-top: auto !important; + min-width: 0 !important; +} + +.soft-man .mw-soft-home-state { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + padding: 6px 12px !important; + border-radius: 999px !important; + font-size: 12px !important; + font-weight: 600 !important; + white-space: nowrap !important; + letter-spacing: 0.02em !important; +} + +.soft-man .mw-soft-home-state--ok { + color: #2c8e68 !important; + background: color-mix(in srgb, #2c8e68 12%, transparent) !important; + border: 1px solid color-mix(in srgb, #2c8e68 22%, transparent) !important; +} + +.soft-man .mw-soft-home-state--off { + color: #b45309 !important; + background: color-mix(in srgb, #b45309 10%, transparent) !important; + border: 1px solid color-mix(in srgb, #b45309 20%, transparent) !important; +} + +.soft-man .mw-soft-home-hint { + color: var(--mw-muted) !important; + font-size: 12px !important; + line-height: 1.4 !important; + text-align: right !important; + flex: 1 1 auto !important; +} + +.soft-man .spanmove { + position: absolute !important; + top: 12px !important; + right: 12px !important; + width: 28px !important; + height: 28px !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + z-index: 4 !important; + cursor: move !important; + border-radius: 999px !important; + border: 1px solid color-mix(in srgb, var(--mw-border) 62%, transparent) !important; + background: color-mix(in srgb, var(--mw-surface) 70%, transparent) !important; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + opacity: 0.72 !important; +} + +.soft-man .spanmove::before { + content: '⋮⋮'; + color: var(--mw-muted); + font-size: 16px; + line-height: 1; + letter-spacing: -2px; + transform: translateY(-1px); +} + +.soft-man .col-sm-3:hover .spanmove, +.soft-man .col-md-3:hover .spanmove, +.soft-man .col-lg-3:hover .spanmove { + opacity: 1 !important; +} + +.mw-soft-empty-row td { + padding: 28px 16px !important; + background: transparent !important; +} + +.mw-soft-empty-row .mw-empty-state { + min-height: 180px; +} + +.mw-soft-table tbody td { + vertical-align: middle; +} + +/* file manager */ +.mw-file-toolbar { + overflow: visible; +} + +.mw-file-toolbar-row { + align-items: flex-start; + flex-wrap: wrap; +} + +.mw-file-path { + min-width: 0; + flex: 1 1 420px; +} + +.mw-file-path-field { + min-width: 0; + max-width: 100%; + flex: 1 1 auto; +} + +.mw-file-path-input input { + min-width: 220px; + width: min(100%, 520px); + max-width: 100%; +} + +.mw-file-search { + margin-left: 0; + min-width: 0; + flex: 1 1 360px; + justify-content: flex-end; +} + +.mw-file-search-form { + width: 100%; + justify-content: flex-end; +} + +.mw-file-search-field { + width: 100%; + max-width: 520px; + flex: 1 1 420px; + min-width: 0; +} + +.mw-file-search-input { + width: 100%; + min-width: 0; + flex: 1 1 auto; +} + +.mw-file-search .file_search { + position: static !important; + top: auto !important; + right: auto !important; + width: auto !important; + height: auto !important; + line-height: normal !important; + background: transparent !important; + border: 0 !important; + padding: 0 !important; + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--mw-muted); + flex: 0 0 auto; +} + +.mw-file-search .file_search label { + position: static !important; + top: auto !important; + right: auto !important; +} + +.mw-file-actions { + min-width: 0; + flex: 1 1 100%; + align-items: center; +} + +.mw-file-breadcrumbs { + min-width: 0; + flex: 1 1 auto; +} + +.comlist { + float: none !important; + display: flex !important; + align-items: center !important; + gap: 8px !important; + line-height: 34px !important; + margin-right: 0 !important; + min-width: 0 !important; + overflow: auto !important; +} + +.comlist > span { + float: none !important; + display: inline-flex !important; + align-items: center !important; + white-space: nowrap !important; +} + +.mw-file-tool-items, +.mw-file-batch { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +.mw-file-tool-items .btn, +.mw-file-batch .btn, +.mw-file-actions .btn { + border-radius: 999px; +} + +.mw-file-tool-items .btn-group { + display: inline-flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.mw-file-search-btn { + background: var(--mw-primary); + color: var(--mw-on-primary); +} + +.mw-file-search-btn .material-icons { + color: inherit; +} + +:root { + --mw-shell-panel-bg: color-mix(in srgb, var(--mw-surface) 90%, var(--mw-bg) 10%); + --mw-shell-panel-border: color-mix(in srgb, var(--mw-border) 82%, transparent); +} + +:root.mdui-theme-dark { + --mw-shell-panel-bg: color-mix(in srgb, var(--mw-surface) 94%, var(--mw-bg) 6%); + --mw-shell-panel-border: color-mix(in srgb, var(--mw-border) 88%, transparent); +} + +.mw-topbar-card, +.mw-page-header, +.mw-card, +.mw-theme-settings, +.mw-file-toolbar { + background: var(--mw-shell-panel-bg); + border-color: var(--mw-shell-panel-border); +} + +.mw-page-header { + background-image: none; +} + +.mw-topbar-card { + backdrop-filter: blur(var(--mw-glass-blur)); + -webkit-backdrop-filter: blur(var(--mw-glass-blur)); +} + +.mw-theme-opacity-row .mw-theme-controls { + flex: 1 1 auto; + min-width: 0; +} + +.mw-theme-opacity-controls { + display: flex; + align-items: center; + gap: 12px; + width: 100%; +} + +.mw-theme-range { + flex: 1 1 280px; + min-width: 220px; + accent-color: var(--mw-primary); + height: 6px; +} + +.mw-theme-range-value { + min-width: 68px; + padding: 6px 10px; + border-radius: 999px; + border: 1px solid var(--mw-border); + background: color-mix(in srgb, var(--mw-surface-container) 88%, transparent); + color: var(--mw-primary); + font-weight: 600; + text-align: center; +} + +.mw-file-toolbar { + gap: 14px; + overflow: visible; +} + +.mw-file-toolbar-row { + width: 100%; + gap: 16px; +} + +.mw-file-toolbar-primary { + justify-content: space-between; + align-items: flex-start; +} + +.mw-file-toolbar-secondary { + justify-content: space-between; + align-items: center; + flex-wrap: wrap; +} + +.mw-file-path { + flex: 1 1 0; + min-width: 0; +} + +.mw-file-path-field { + flex: 0 0 220px; + min-width: 0; + max-width: 260px; + background: color-mix(in srgb, var(--mw-surface-container) 88%, transparent); +} + +.mw-file-path-label { + background: color-mix(in srgb, var(--mw-primary) 12%, var(--mw-surface-container) 88%); + color: var(--mw-text); +} + +.mw-file-path-input input { + min-width: 0; + width: 100%; +} + +.mw-file-path-actions { + display: block; + flex: 1 1 360px; + min-width: 0; +} + +.mw-file-path-actions > div { + width: 100%; + height: 36px; + padding: 0 10px; + overflow: hidden; + position: relative; + border-radius: 999px; + border: 1px solid var(--mw-border); + background: color-mix(in srgb, var(--mw-surface-container) 84%, transparent); + display: flex; + align-items: center; +} + +.mw-file-path-actions ul { + display: inline-flex; + align-items: center; + gap: 8px; + list-style: none; + margin: 0; + padding: 0; + position: relative; + white-space: nowrap; +} + +.mw-file-path-actions li { + flex: 0 0 auto; +} + +.mw-file-path-actions a { + display: inline-flex; + align-items: center; + padding: 4px 10px; + border-radius: 999px; + color: var(--mw-primary); + background: color-mix(in srgb, var(--mw-primary) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--mw-primary) 18%, transparent); +} + +.mw-file-search { + flex: 0 1 420px; + min-width: 280px; + margin-left: auto; +} + +.mw-file-search-form { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 10px; + width: 100%; +} + +.mw-file-search-field { + width: 100%; + max-width: none; +} + +.mw-file-search-input { + width: 100%; + min-width: 0; +} + +.mw-file-search-btn { + border: 1px solid var(--mw-border); + background: var(--mw-primary); + color: var(--mw-on-primary); + width: 40px; + height: 40px; + min-width: 40px; + padding: 0; + flex: 0 0 40px; + box-sizing: border-box; +} + +.mw-file-search-btn::before { + content: none !important; +} + +.mw-file-search-btn .material-icons { + font-size: 18px; + color: inherit; +} + +.mw-file-search-btn:hover { + background: color-mix(in srgb, var(--mw-primary) 88%, #000 12%); + border-color: color-mix(in srgb, var(--mw-primary) 40%, var(--mw-border) 60%); +} + +.mw-file-search .file_search { + justify-content: flex-end; + align-self: flex-end; +} + +.mw-file-actions { + min-width: 0; + flex: 1 1 auto; +} + +.mw-file-breadcrumbs { + margin-left: 12px; +} + +.mw-file-view-toggle { + margin-left: auto; +} + +:root { + --mw-shell-panel-bg: color-mix(in srgb, var(--mw-surface) 82%, transparent); + --mw-shell-panel-border: color-mix(in srgb, var(--mw-border) 72%, transparent); +} + +:root.mdui-theme-dark { + --mw-shell-panel-bg: color-mix(in srgb, var(--mw-surface) 88%, transparent); + --mw-shell-panel-border: color-mix(in srgb, var(--mw-border) 82%, transparent); +} + +/* final layout lock-in: keep the shell, cards, and toolbars visually consistent */ +.mw-topbar-glass { + background-color: transparent !important; + background-image: var(--mw-app-background-overlay), var(--mw-app-background-image) !important; + background-size: auto, var(--mw-app-background-size) !important; + background-position: center, var(--mw-app-background-position) !important; + background-repeat: no-repeat, var(--mw-app-background-repeat) !important; + background-attachment: fixed, var(--mw-app-background-attachment) !important; +} + +.mw-topbar-card, +.mw-page-header, +.mw-card, +.mw-theme-settings, +.mw-file-toolbar, +.mw-observe-panel, +.mw-observe-chart-card, +.mw-login-card, +.mw-standalone-card { + background: var(--mw-shell-panel-bg) !important; + border-color: var(--mw-shell-panel-border) !important; + backdrop-filter: blur(var(--mw-glass-blur)) !important; + -webkit-backdrop-filter: blur(var(--mw-glass-blur)) !important; +} + +.mw-topbar-card, +.mw-page-header, +.mw-card, +.mw-theme-settings, +.mw-file-toolbar, +.mw-observe-panel, +.mw-observe-chart-card, +.mw-login-card, +.mw-standalone-card { + box-shadow: 0 18px 42px rgba(15, 23, 42, 0.10) !important; +} + +.mw-file-toolbar { + position: relative !important; + top: auto !important; + z-index: auto !important; + overflow: visible !important; + gap: 12px !important; +} + +#tipTools { + position: relative !important; + top: auto !important; + left: auto !important; + z-index: auto !important; + height: auto !important; + min-height: 0 !important; + overflow: visible !important; + background: transparent !important; + border-bottom: 0 !important; + padding: 0 !important; +} + +#tipTools::before, +#tipTools::after { + content: none !important; + display: none !important; +} + +.mw-file-toolbar-row { + width: 100% !important; + flex-wrap: wrap !important; + align-items: center !important; + gap: 12px !important; +} + +.mw-file-toolbar-primary { + display: flex !important; + justify-content: space-between !important; + align-items: center !important; + flex-wrap: wrap !important; + gap: 12px !important; +} + +.mw-file-toolbar-secondary { + display: flex !important; + justify-content: space-between !important; + align-items: center !important; + gap: 12px !important; + flex-wrap: wrap !important; +} + +.mw-file-path { + flex: 1 1 420px !important; + min-width: 0 !important; +} + +.mw-file-path-field { + flex: 1 1 auto !important; + min-width: 0 !important; + max-width: 280px !important; +} + +.mw-file-path-input input { + min-width: 0 !important; + width: min(100%, 360px) !important; + height: 36px !important; +} + +.mw-file-path-actions { + flex: 1 1 360px !important; + min-width: 0 !important; +} + +.mw-file-path-actions > div { + height: 34px !important; + padding: 0 8px !important; +} + +.mw-file-search { + flex: none !important; + width: clamp(200px, 20vw, 260px) !important; + min-width: 0 !important; + margin-left: 0 !important; + justify-self: end !important; +} + +.mw-file-search-form { + flex-direction: row !important; + align-items: center !important; + justify-content: flex-start !important; + flex-wrap: wrap !important; + gap: 8px !important; + width: 100% !important; +} + +.mw-file-search-field { + width: 100% !important; + max-width: none !important; + padding: 4px 6px !important; + border-radius: 999px !important; +} + +.mw-file-search-input { + min-width: 0 !important; + width: 100% !important; + height: 36px !important; +} + +.mw-file-search-btn { + width: 36px !important; + height: 36px !important; + min-width: 36px !important; + flex: 0 0 36px !important; +} + +.file_search, +.mw-file-search .file_search, +.mw-file-search-scope { + position: static !important; + right: auto !important; + top: auto !important; + width: auto !important; + height: auto !important; + line-height: normal !important; + background: transparent !important; + border: 0 !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + gap: 6px !important; + white-space: nowrap !important; +} + +#BarTools, +#Batch, +#comlist { + float: none !important; + display: inline-flex !important; + flex-wrap: wrap !important; + align-items: center !important; + gap: 8px !important; + margin: 0 !important; + width: auto !important; + min-width: 0 !important; +} + +#BarTools > button { + float: none !important; + top: auto !important; +} + +#BarTools .btn-group, +#Batch .btn-group { + float: none !important; + display: inline-flex !important; + align-items: center !important; +} + +.file_search label, +.mw-file-search .file_search label, +.mw-file-search-scope label { + position: static !important; + right: auto !important; + top: auto !important; +} + +.file_search .file_search_checked, +.mw-file-search-scope .file_search_checked { + margin: 0 !important; +} + +.mw-file-search-scope { + padding: 4px 10px !important; + border-radius: 999px !important; + border: 1px solid var(--mw-border) !important; + background: color-mix(in srgb, var(--mw-surface-container) 76%, transparent) !important; + color: var(--mw-muted) !important; +} + +.mw-file-actions { + width: 100% !important; + min-width: 0 !important; + flex: 1 1 100% !important; + align-items: center !important; + display: flex !important; + flex-wrap: wrap !important; + gap: 8px !important; + padding: 10px 12px !important; + border-radius: 18px !important; + border: 1px solid var(--mw-border) !important; + background: color-mix(in srgb, var(--mw-surface-container) 76%, transparent) !important; +} + +.mw-file-actions > * { + min-width: 0 !important; +} + +.mw-file-tool-items, +.mw-file-batch { + display: inline-flex !important; + flex-wrap: wrap !important; + align-items: center !important; + gap: 8px !important; +} + +.mw-file-tool-items .btn, +.mw-file-batch .btn, +.mw-file-actions .btn { + min-height: 38px !important; + padding: 0 14px !important; + border-radius: 999px !important; + border: 1px solid var(--mw-border) !important; + background: color-mix(in srgb, var(--mw-surface-container) 78%, transparent) !important; + color: var(--mw-text) !important; + box-shadow: none !important; +} + +.mw-file-tool-items .btn-group { + display: inline-flex !important; + flex-wrap: wrap !important; + gap: 6px !important; +} + +.mw-file-breadcrumbs span, +.comlist > span { + float: none !important; + margin-right: 0 !important; +} + +.mw-file-view-toggle { + margin-left: auto !important; + display: inline-flex !important; + gap: 8px !important; + padding: 8px 10px !important; + border-radius: 999px !important; + border: 1px solid var(--mw-border) !important; + background: color-mix(in srgb, var(--mw-surface-container) 76%, transparent) !important; +} + +.mw-file-view-toggle .mw-icon-button { + width: 40px !important; + height: 40px !important; +} + +#PathPlaceBtn { + width: 100% !important; + max-width: 100% !important; + min-width: 0 !important; + border-radius: 999px !important; + background: color-mix(in srgb, var(--mw-surface-container) 76%, transparent) !important; + border: 1px solid var(--mw-border) !important; +} + +#PathPlaceBtn > div { + width: 100% !important; + height: 34px !important; + overflow: hidden !important; + position: relative !important; + display: flex !important; + align-items: center !important; +} + +#PathPlaceBtn ul { + width: auto !important; + max-width: 100% !important; + height: 34px !important; + display: inline-flex !important; + align-items: center !important; + gap: 0 !important; +} + +#PathPlaceBtn li { + height: 34px !important; + line-height: 34px !important; +} + +#PathPlaceBtn li a { + height: 34px !important; + line-height: 34px !important; + padding: 0 10px !important; +} + +@media (max-width: 992px) { + .mw-file-toolbar-primary, + .mw-file-toolbar-secondary { + justify-content: flex-start !important; + } + + .mw-file-search { + justify-self: stretch !important; + } + + .mw-file-search-field { + width: 100% !important; + max-width: none !important; + } + + .mw-file-actions { + justify-content: flex-start !important; + } + + .mw-file-view-toggle { + margin-left: 0 !important; + justify-self: start !important; + } +} + +.mw-stat-grid { + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)) !important; + align-items: stretch !important; + gap: 16px !important; +} + +.mw-stat-grid > li { + width: auto !important; + min-width: 0 !important; + min-height: 260px !important; + padding: 18px !important; + display: flex !important; + flex-direction: column !important; + justify-content: flex-start !important; + align-items: flex-start !important; + gap: 10px !important; + box-sizing: border-box !important; +} + +.mw-stat-grid > li h3 { + margin: 0 !important; + min-height: 56px !important; + line-height: 1.35 !important; + display: flex !important; + align-items: flex-start !important; + gap: 6px !important; +} + +.mw-stat-progress { + margin: 12px 0 10px !important; + width: 100% !important; +} + +.mw-stat-progress mdui-linear-progress { + width: 100% !important; + height: 8px !important; + --mdui-linear-progress-height: 8px !important; + border-radius: 999px !important; +} + +.mw-stat-value { + margin-top: 0 !important; + font-size: 28px !important; + line-height: 1.1 !important; +} + +.mw-stat-status { + min-height: 48px !important; + margin-top: 2px !important; + color: var(--mw-muted) !important; +} + +.mw-stat-subtitle { + min-height: 20px !important; + margin-top: -2px !important; + color: var(--mw-muted) !important; + font-size: 14px !important; + line-height: 1.45 !important; +} + +.mw-stat-foot { + margin: 0 !important; + color: var(--mw-muted) !important; +} + +#systemInfoList .diskbox h4 { + margin-top: auto !important; + margin-bottom: 0 !important; +} + +.mw-mem-release-btn { + margin-top: auto !important; + align-self: flex-start !important; +} + +#indexsoft { + margin: 0 !important; + width: 100% !important; + display: grid !important; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)) !important; + gap: 14px !important; +} + +.soft-man .col-sm-3, +.soft-man .col-md-3, +.soft-man .col-lg-3 { + float: none !important; + width: auto !important; + margin: 0 !important; + padding: 0 !important; + height: auto !important; +} + +.soft-man .col-sm-3 > div, +.soft-man .col-md-3 > div, +.soft-man .col-lg-3 > div { + position: relative !important; + inset: auto !important; + width: 100% !important; + height: 100% !important; + min-height: 168px !important; + padding: 18px 16px !important; + display: flex !important; + flex-direction: column !important; + align-items: center !important; + justify-content: center !important; + gap: 12px !important; + border-radius: 18px !important; + border: 1px solid var(--mw-border) !important; + background: color-mix(in srgb, var(--mw-surface) 88%, transparent) !important; + box-shadow: var(--mw-shadow) !important; + box-sizing: border-box !important; +} + +.soft-man .spanmove { + top: 10px !important; + right: 10px !important; + width: 28px !important; + height: 28px !important; + z-index: 3 !important; +} + +.soft-man .col-sm-3:hover .spanmove, +.soft-man .col-md-3:hover .spanmove, +.soft-man .col-lg-3:hover .spanmove { + display: block !important; +} + +.soft-man .image { + margin: 0 !important; + height: auto !important; + text-align: center !important; +} + +.soft-man .image img { + max-width: 52px !important; + max-height: 52px !important; + object-fit: contain !important; +} + +.soft-man .sname { + margin: 0 !important; + color: var(--mw-text) !important; + text-align: center !important; + line-height: 1.35 !important; + word-break: break-word !important; + font-weight: 600 !important; + min-height: 44px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + gap: 6px !important; +} + +.soft-man .sname .glyphicon-True { + color: #20a53a !important; + margin-left: 0 !important; +} + +.soft-man .sname .glyphicon-False { + color: #ef4444 !important; + margin-left: 0 !important; +} + +.soft-man .dashed-border { + border: 1px dashed color-mix(in srgb, var(--mw-primary) 48%, var(--mw-border) 52%) !important; + background: transparent !important; + min-height: 168px !important; + box-shadow: none !important; +} + +.soft-man .no-bg { + display: none !important; +} diff --git a/web/static/css/site.css b/web/static/css/site.css new file mode 100755 index 000000000..ffb7900ea --- /dev/null +++ b/web/static/css/site.css @@ -0,0 +1,5338 @@ +body { + line-height: 1.4; + color: #333; + font-family: "微软雅黑", Arial, Helvetica, sans-serif; + font-size: 12px +} + +input, +textarea, +select { + font-size: 100%; + font-family: inherit; + outline: none; +} + +body, +h1, +h2, +h3, +h4, +h5, +h6, +p, +ul, +ol, +form { + margin: 0 +} + +h4, +h5, +h6 { + font-size: 1em +} + +ul, +ol { + padding-left: 0; + list-style-type: none +} + +fieldset, +img { + border: 0 +} + +a { + color: #333; + border: 0; + text-decoration: none +} + +a:hover { + text-decoration: none +} + +a:link { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-touch-callout: none +} + +:-moz-placeholder { + color: #999 +} + +::-moz-placeholder { + color: #999 +} + +input:-ms-input-placeholder { + color: #999 +} + +input::-webkit-input-placeholder { + color: #999 +} + +body, +html { + height: 100% +} + +.f12 { + font-size: 12px +} + +.f14 { + font-size: 14px +} + +.f15 { + font-size: 15px +} + +.f16 { + font-size: 16px +} + +.f18 { + font-size: 18px +} + +.f20 { + font-size: 20px +} + +.cw { + color: white +} + +.c0 { + color: #000 +} + +.c3 { + color: #333 +} + +.c4 { + color: #444 +} + +.c5 { + color: #555 +} + +.c6 { + color: #666 +} + +.c7 { + color: #777 +} + +.c8 { + color: #888 +} + +.c9 { + color: #999 +} + +.cbt { + color: #20a53a +} + +.bgw { + background-color: white +} +.move_class{ + float:right; +} +.bge6 { + background-color: #f2f2f2; +} +/* .bge6 { + background-color: #e6e9ee +} + */ + .btn{ + vertical-align: inherit; + } + .file-box .btn{ + vertical-align: middle; + } +.file-box .page{ + border:none; +} +.file-box .Pcount-item{ + border-right:1px solid #ececec; +} +.file-box{ + padding-bottom: 1px; +} + +.plr10 { + padding: 0 10px; +} + +.plr15 { + padding: 0 15px; +} + +.plr20 { + padding: 0 20px; +} + +.ptb10 { + padding: 10px 0; +} + +.ptb15 { + padding: 15px 0; +} + +.ptb20 { + padding: 20px 0; +} + +.pd0 { + padding: 0; +} + +.pd15 { + padding: 15px; +} + +.pd20 { + padding: 20px; +} + +.pr8 { + padding-right: 8px; +} + +.pl7 { + padding-left: 7px; +} + +.pb15 { + padding-bottom: 15px; +} + +.pb55 { + padding-bottom: 55px; +} + +.pb70 { + padding-bottom: 70px; +} + +.mt10 { + margin-top: 10px; +} + +.mtb10 { + margin: 10px 0; +} + +.mtb15 { + margin: 15px 0; +} + +.mtb20 { + margin: 20px 0; +} + +.mlr15 { + margin: 0 15px; +} + +.mlr20 { + margin: 0 20px; +} + +.mb15 { + margin-bottom: 15px; +} + +.mr50 { + margin-right: 50px; +} + +.mr20 { + margin-right: 20px; +} + +.ml33{ + margin-left: 38px; +} +.ml45 { + margin-left: 50px; +} + +.ml5 { + margin-left: 5px; +} + +.ml10 { + margin-left: 10px; +} + +.mr5 { + margin-right: 5px; +} + +.mr20 { + margin-right: 20px; +} + +.ml0{ + margin-left: 0; +} + +.mg10 { + margin: 10px; +} + +.va0 { + vertical-align: 0 +} + +.ico-font-ask { + border: 1px solid #999; + border-radius: 8px; + display: inline-block; + font-family: arial; + font-size: 11px; + font-style: normal; + height: 16px; + line-height: 16px; + margin-left: 5px; + text-align: center; + width: 16px; + cursor: help +} + +.btlink { + color: #20a53a +} + +.btlink:hover { + cursor: pointer +} + +.btn-btlink { + border-color: #20a53a; + color: #20a53a; + vertical-align: 1px +} + +.btn-btlink:hover { + border-color: orange; + color: orange +} +.input-edit{ + cursor: pointer; + border:transparent 1px solid; + min-height: 20px; + /*min-width: 150px;*/ + display: inline-block; +} +.input-edit:hover{ + border:#ccc 1px solid; + background-color: #fff; +} +.baktext{ + height: 20px; + border:#ccc 1px solid; + min-width: 150px; +} +.b-shadown { + transition: border-color .15s ease-in-out 0s, box-shadow .15s ease-in-out 0s +} + +.b-shadown:hover { + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + + + +.info-title-tips { + background-color: #fbfbfb; + border: 1px solid #eee; + line-height: 46px; + margin-bottom: 15px; + padding-left: 10px; +} + +.bt-input-text { + border: 1px solid #ccc; + height: 30px; + line-height: 30px; + padding-left: 5px; + border-radius: 2px; + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s +} + +.bt-input-text:focus, +.bt-input-text:active { + border: #66afe9 1px solid; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +.bt-submit { + background-color: #20a53a; + border-radius: 3px; + width: 140px; + height: 34px; + line-height: 34px; + text-align: center; + color: #fff; + cursor: pointer +} + +.cursor { + cursor: pointer; +} + +.help-info-text { + margin-top: 15px; +} + +.help-info-text>li { + list-style: inside disc; + line-height: 24px; +} + +.relative { + position: relative; +} + +.ico-copy { + background: url(/static/img/ico-copy.png) no-repeat; + height: 14px; + width: 12px; + display: inline-block; + vertical-align: -2px +} + +.zclip embed { + vertical-align: top; +} + +.webDelete .options { + padding: 20px 0; +} + +.webDelete .options label { + width: 30%; + float: left; + font-weight: normal +} + +.webDelete .options label input { + float: left; + margin: 0 10px 0 0; + margin-top: 1px +} + +.webDelete .options label span { + float: left; + margin: 0; + line-height: 16px +} + +.webDelete .vcode { + background-color: #f0f0f0; + clear: both; + font-size: 14px; + height: 40px; + line-height: 40px; + margin: 10px 0; + padding-left: 12px; + text-align: left; + color: #444 +} + +.webDelete .vcode .text { + margin-right: 10px; + margin-left: 10px +} + +.webDelete .vcode #vcodeResult { + display: inline; + height: 26px; + line-height: 26px; + margin-left: 10px; + width: 50px; + color: #444 +} + +.btswitch { + display: none +} + +.btswitch+.btswitch-btn { + outline: 0; + display: block; + width: 3em; + height: 1.8em; + position: relative; + cursor: pointer +} + +.btswitch+.btswitch-btn:after, +.btswitch+.btswitch-btn:before { + position: relative; + display: block; + content: ""; + width: 50%; + height: 100% +} + +.btswitch+.btswitch-btn:after { + left: 0 +} + +.btswitch+.btswitch-btn:before { + display: none +} + +.btswitch:checked+.btswitch-btn:after { + left: 50% +} + +.btswitch-ios+.btswitch-btn { + background: #cdcdcd; + border-radius: .9em; + padding: 2px; + -webkit-transition: all .4s ease; + transition: all .4s ease; + border: 1px solid #e8eae9 +} + +.btswitch-ios+.btswitch-btn:after { + border-radius: .9em; + background: #fbfbfb; + -webkit-transition: left .3s cubic-bezier(.175, .885, .32, 1.275), padding .3s ease, margin .3s ease; + transition: left .3s cubic-bezier(.175, .885, .32, 1.275), padding .3s ease, margin .3s ease; + -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1), 0 4px 0 rgba(0, 0, 0, .08); + box-shadow: 0 0 0 1px rgba(0, 0, 0, .1), 0 4px 0 rgba(0, 0, 0, .08) +} + +.btswitch-ios+.btswitch-btn:active { + -webkit-box-shadow: inset 0 0 0 2em #e8eae9; + box-shadow: inset 0 0 0 2em #e8eae9 +} + +.btswitch-ios+.btswitch-btn:active:after { + padding-right: .8em +} + +.btswitch-ios:checked+.btswitch-btn { + background: #20a53a +} + +.btswitch-ios:checked+.btswitch-btn:active { + -webkit-box-shadow: none; + box-shadow: none +} + +.btswitch-ios:checked+.btswitch-btn:active:after { + margin-left: -.8em +} + +.bt-warp { + position: relative; + min-height: 100% +} + +.bt-warp>.container-fluid { + padding: 0 +} + +.main-content { + margin-left: 180px +} + +.sidebar-scroll { + background-color: #3c444d; + width: 180px; + z-index: 100; + height: 100%; + position: fixed; + overflow: hidden +} + +.sidebar-auto { + overflow: auto; + height: 100%; + margin-right: -18px +} + +.mypcip { + display: block; + padding: 0 10px; + position: relative; + transition-duration: 500ms; + transition-property: background; + transition-timing-function: ease; + width: 100%; + cursor: pointer; + margin: 1px 0 +} + +.mypcip:hover { + background: #20a53a; + opacity: 1 +} + +.mypcip span { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAQCAYAAAAS7Y8mAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAGPSURBVDiNldQ9ixRBEAbgZ3Zn1fVOuQU/wK9AMDDS1MvuRxj5CwTBP2CiYmYmGBsYGRsaa2qkkSiCgRd4y3ri3XqzbdDV7Lgs7uwLRc90V1dXv29VVymluxiiRoVkNaoYG7wN+9chpTTBBn7jKA5YhYR+JPQEDxYd6rAPeIwDnOgQuMEIz3F8mUMdC9/wqkPAxb1P0Yv/6ziLX/hTy9c6jXPYXSPwBQywF+Mz7GCCSRGMbqK1Ufwbmb4beI9TuNZFqFWoZCH7Mp0XcaX33y3dUDKfmRdD1bM+BYuoWtaEKeIdyqSvg5+RZSWX6Ri3ZUqmRbxjcqnsx3cyF7Utbpmf4YxcapXcWFNcjiSbOk7Yxhu5BocRaGBeoyXYNII32MTJsJ7cvR/D92aN1xFsFA6HYZ/iiiWjTVyVGyrhBz7jXSQxxq3wPahxJ7JuX30sd9IoshviC77KzdSmag+XYm4QNq0tF+0e7sc1SwPs4xFeLPHvt2ibsfwl24rFl/KLV/jdCho2ZC3a+I6HOB9rR38B10ZjDE49T6kAAAAASUVORK5CYII=") no-repeat 0 center; + display: inline-block; + line-height: 46px; + padding-left: 30px; + white-space: nowrap; + max-width: 146px; + overflow: hidden; +} + +.btpc-plus { + line-height: 40px; + color: #aaa; + font-family: arial; + font-size: 26px; + cursor: pointer; + padding-left: 80px; + transition-duration: 500ms; + transition-property: background; + transition-timing-function: ease +} + +.btpc-plus:hover { + background-color: #20a53a; + color: #fff +} + +.mypcip .btedit { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFVSURBVDiNndO/ahRRFMfxz+zOav6goKC1S54gBBUECy0lIKTeyrXwFcROyBuENCHdNhaCoCgiWoggGPAJxFgIsbEJJBmTrCfFnBFZNjHrDy4z5849935/Z84tIsJ/6gneFhFR4AHuYHjM4jN4iZWM13ALSyWWcR+rOERrzAYFNvP9Ka7jKrZExKeIWI8IpxivIuJrRFxo5lqJXfzDbwfvcQU3cAnTUOIsfif6zeYDfuEDzuM5LuIatrGBu/hcInCAy1mH2STawyLmknI+56bykFZDsJsUP7CAdhIMUeELXo9YGmbOn4o3Nah6vd5Ot9vd6ff7lfr3dXKMVZnIVVp4MRgMZtLW6LpneJhxpB1lJnfwE49yblRtfPsrLhoLpbqA7fT15jjUERXNQeO6biK11DXYnyCnymdIjI+4jXuJdlJXHqq7cArf620izkXEu5hMj5u7cAQCwbENi6a1WQAAAABJRU5ErkJggg==) no-repeat center center; + width: 16px; + height: 16px; + display: none; + position: absolute; + left: 156px; + top: 14px +} + +.mypcip:hover .btedit { + display: block +} + +.task { + position: absolute; + right: 6px; + top: 14px; + height: 20px; + width: 20px; + line-height: 20px; + background-color: #fc6d26; + z-index: 99; + text-align: center; + border-radius: 6px; + cursor: pointer; + font-family: arial; + font-size: 14px; + font-weight: bold +} + +.softnum { + position: absolute; + left: 154px; + top: 12px; + height: 20px; + width: 20px; + line-height: 20px; + background-color: #fc6d26; + z-index: 99; + text-align: center; + border-radius: 6px; + cursor: pointer; + font-family: arial; + font-size: 14px; + font-weight: bold; + display: none; + color: #fff +} + +.cmdlist li { + border-bottom: 1px solid #dbdbea; + line-height: 48px +} + +.cmdlist li .titlename { + padding-left: 12px; + position: relative +} + +.cmdlist li .state { + float: right +} + +.cmdlist li .titlename:before { + background-color: #20a53a; + border-radius: 3px; + content: ""; + height: 5px; + left: 0; + position: absolute; + top: 6px; + width: 5px +} + +.cmdlist li .cmd { + height: 200px; + background-color: #424251; + overflow: auto; + line-height: 22px; + color: #fff; + padding-left: 10px; + font-family: arial +} + +#remind td { + vertical-align: middle +} + +#remind .titlename { + position: relative; + text-overflow: ellipsis; + height: 20px; + overflow: hidden; + width: 300px; + white-space: nowrap +} + +#remind .titlename:before { + background-color: #20a53a; + border-radius: 3px; + content: ""; + height: 5px; + left: -10px; + position: absolute; + top: 6px; + width: 5px +} + +.btn-default[disabled], +.btn-default:active[disabled] { + background-color: #f7f7f7; + color: #bbb; + opacity: 1 +} + +.table-page { + height: 32px +} + +.table-page a { + border: 1px solid #ccc; + float: left; + height: 30px; + margin-left: -1px; + outline: 0 none; + position: relative; + width: 34px; + z-index: 1; + color: #666; + vertical-align: middle; + text-align: center; + line-height: 30px +} + +.table-page a:hover { + border: 1px solid #20a53a; + color: #20a53a; + z-index: 3 +} + +.table-page a.disable { + background-color: #f3f3f3; + cursor: not-allowed; + color: #bbb +} + +.table-page a.disable:hover { + border: 1px solid #ccc +} + +.table-page-select { + float: left; + position: relative +} + +.table-page .table-page-num { + width: 60px +} + +.page-select-ul { + background-color: #fff; + border: 1px solid #ccc; + bottom: 29px; + left: -1px; + line-height: 22px; + max-height: 150px; + min-width: 100%; + overflow: auto; + position: absolute; + top: auto; + width: 60px; + display: none +} + +.page-selected .page-select-ul { + display: block +} + +.page-select-ul li { + padding: 0 12px +} + +.page-select-ul li:hover { + background-color: #f0f0f0 +} + +.sidebar-auto .menu { + background-color: #353d44 +} + +.menu li { + margin-bottom: 1px; + position: relative +} + +.menu li a { + font-size: 15px; + color: #d6d7d9; + display: block; + line-height: 44px; + padding-left: 52px; + background-repeat: no-repeat; + background-size: 16px auto; + background-position: 25px 14px; + border-left: #404040 2px solid +} + +.menu li.current a, +.menu li a:hover { + background-color: #2c3138; + color: #fff; + border-left: #20a53a 2px solid; +} + +.menu .menu_home { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKwwAACsMBNCkkqwAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFZSURBVDiNpdO9a1VBEAXw3zOioIUQEdFGkQdWVtr6D1ikERtRppLU6awUsUkh2CgoBNwhoPhsLESxERR7wcI2XSBNMAHRNFmLbB6Xa64RXFjYOXvmzMfOjmqt/mcd2I+QmUuZOcgbDWWQmTN4iFms4wGu4jC+RMT7wQwy8xBe4FtE3MBXPMdqE5sfLCEzj+Il3kXEE4iIJTzFFbzB9z0FMvMYJphExLPuXUQs4y1eYfmPHmTmbHN+FBGv92zMDm+ulXAzItZHtVaZeRH3sBgRn4ecOyKXcRt3dkuYwyJOZeaJfZyP42TjX1Nrne5SyqSUMu5ho559tpQy2bUP9gL8wHYnzbtYzczTuB8RHzs8/H0Sz+ETbuEDxg3f7pL6AhW/2vknNiNiCxvY6uDT8e2XMINLmbmC89js3I0z8wLOdP36Aht43KIcwULD1+w82/WGT+dk8DP96/oNlqecb6uu8YEAAAAASUVORK5CYII=") +} + +.menu .current .menu_home, +.menu .menu_home:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKTQAACk0BtZPkxgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFVSURBVDiNpdO9atVBFATw3/UjBEGidkJSRFFIiKJBsAmaBxDFQptUIoJPYCc2Wmpjo4haRUFMnsEmnXUIKQXFRlEvaiIxY/HfheXCNUUWht2dM+fsYXe2l8Ruxp4d4jN4gDNlvw/70dMQw8ZRXMdFbOAvpjGJ93iHrWEdHMdDfMMcPuMpjiC4hYPDOpjCPazgceGeYS8m8BYj2AZJWpxOspTk9gBfcTPJSpJrlRtMXk5yY0hyxUKSF0lOtQWuJHmV5NIOyRWXk7xOcrVe4iSW8BXj/3mZ+jpfiv5ErThS5udJzjcnHUgyXubKnU3yUrcerR38wSH0sVm4C3iDO+Xm5wu/hX44jI3WB4OePoaPeIIPOm9U3XYVtQV6JfC77PtYxyrW8KPwP9vDWiNFZ5BzOtvO4Fejm8ZJzGqM1BbYLHiE77pPc7fEPuE+FjCGxaLV2+13/gdXJgTGYi2BZQAAAABJRU5ErkJggg==") +} + +.menu .menu_web { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAHuSURBVDiNpZO/S9ZRGMU/5xZl0NJQDf2ybAgrGgylGloiCR0jDAO3+4WQlqA/oiHkpUGvgYtFRENL5g+CCEIiqSjMoFKsaJFqScMXfU9DV3vVluiZDs997odzuM+Vbf6n1v+tmVJqA9okHba9CXgF9McY+1fPqtpBSmkH0Al8B45Iema7DDRlyHagK8b4ZQ2gp6dnn6QB2+2SdgFvJW2xPQeUbdcCM5L6gNYY4/QyIKW0AbgGPAb2AueALuAQMA9MApeAu7YnJZ0ALscYyyE7aZf0JsZ4R1I/MAKMAwFYl+0/AW4URXFb0gTQTh4AOGP7Xta7gbEY43NJw8BA1hPAHoA821wdYUxSCZi13QhslPTI9kmgLGkUOG37m6QXwGbbF2OMTUvPuGD7JzCbM2N7FigD87bnJC3r7HyhOsK0pNEY46CkoRxhRNJDYDjGOGz7fdYPsqNP1YBB2y0Ai4uLH4GjKaUG281AS0qpAThQqVSmsrsWYAj+bOIt4Gpvb+/ZEMJ+4BTwLp8ZaASOhRA6UkofgHrgyupFqpN0H7gA7ATGJW21/SNvY10IYcZ2n+3WoiimVgAAuru7a0MIncBXoF7SU9sLwHHgJbDN9vWiKD4v3VkBACiVSqqpqekAzgMHc/s1vz/TTVbVGsC/1i9dw/hm1FHr2QAAAABJRU5ErkJggg==") +} + +.menu .current .menu_web, +.menu .menu_web:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAGCSURBVDiNpdLPaw9wGAfw13fExWVq1IzYJEkc5kezgws5zM2S+irOWi7KH+EgB0e1m9ZycMGWm0jLRYqUWNNyIDkw2lq9Hb7P5rsfDvKpT717Pp/n/byf53k3kvif0/GX+AXcx3t8wgQurvexsUrBDozgGw7jBRZwHK+wHbeKdA1BLx6iiZ14i078LJLd+IJRnMVMO8Em3MQT7MH5qnQQ8/iAq7hXeBDXsLCxqjfxBuPVRide4xA2lPxnuIPP2Fo5o5JIMp6ku/BAkuHCp5MMFr6S5Gjh7iRjSSwp6MUpzOEYNmMRJ6v/rmqnG7uwpVpdJljEryKYr9hcJc+3DXIJd1TOMsEMnmMW36vSYzQq4Sn21Vqn0INz/DHSBIYKf8QR9ONMxfuxH9P1ZwiT7Qru4gaGsbfm8a7eUnMZwCUtdx7AdVYaqQ8PtCzbo7XGLvyo/vusNNL0agJabhvB16oyVcM6gZfYhts1q9LX2mv7bSS5nGQyyWzdR0ma6/xdo+Cfz28JnsxkWP6vVAAAAABJRU5ErkJggg==") +} + +.menu .menu_ftp { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAH6SURBVDiNpdM/qJZlGAbw3/1+n31o/0BJrMFaGmoRRBsM14M4iINLixBxHs6pxSxBmhpCqEk5CL2Pp5ZAdBD/UIJNhdBQzTqJYKkoB8HEY+r53rvB99hnuXlNNzfXfT3X9dzPE5npWTB8WrPWuhvvYwvGOI+vSykn/8uNSQfz8/MvdV33DRaxBlMInMZ9LI7H449mZ2cfLM80y0Xbtiu7rjsZEecz8ws8xBEc7l3swfXBYHB8bm6u+Z9A0zSfYmtmXoyIg/gKV3AJLQ7hV+wcjUYzT0Sota7C2Yg4kJm7sQM/YQOWcAFb+ygn8HHXdVMzMzMPlx1swML09PSPEXEIpzCNc/gBH+D7zPyylHIGt5umeWsywou4B5n5BhZKKTdwFddKKTdxJyLe7PmLWD0pcB2v9PVlvN627WZsxKZa6ya8hj96zsuZuUD/DobD4YWlpaUVtdbP8A62RcRNrO+38SG2I2qtv2AUERcfXyLUWnfhu8zcGBEFv+PVPtp9vI2j+A3vlVKOPSHQi+zHrcz8NiLebZrmr3yE1ePx+OfBYLAfK0opnz9eY9u2WzJzHRJ/Yy3uRsTePnfgSmYexAu4gZX9/NUh9kXElH8xxgjPTfTWR8RmPMBg+XCcGOKTiHh+gtxFxKqu685gXd/7MyJ29g6bCe7teNbv/A8ZE8Q3GMBOFwAAAABJRU5ErkJggg==") +} + +.menu .current .menu_ftp, +.menu .menu_ftp:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAGVSURBVDiNpdLdaw9wFAbwzw+jzVspIxdIuXHhQqbGHVmSRLlx4w9YKbkhpVz4A6SkKOWlldA0uXA1Um645motL7NImsnYNI+LnZ9+W63IqW+dzvc8zznPOaeRxP/YgnnixzCISUzgEQ7/DcEK3MEefEMDbfiKI7iKxfMRtKMfT3EePwtwCdM4gVHcnoVL0nxnk0wl2ZvkYZLuJKeSHE+yO0lfkgOZsd4mrgnuSPI4SU+SW0nGkwwkeZ1kKMmDJGNJric5mGQwSVsrQXeSu+V3JbmRZE2SK0kuJuks4i2Vcz/J1iR/tCzH9/I34hM+YATv8bEGublyJrCqdYijWF3+MDagC9uwvd46vK2clVXEogq8rHWdwQ7sq6rraxu92F9rfYYleNVKMI3LuFlVh/ECQyVtEmPow3McLYzGnFM+jc+4hl0YR0rvk/pvw7kmoJFkJ9ZW4g90mrnCk6W7gTe4gGU13PbCjzSS9KOnpYvp0jjrZEvGFBY2i+NeI8kmLG1J/IUODFRn8A6HqsPW8/8ydwb/bL8B1eb4OuOuSusAAAAASUVORK5CYII=") +} + +.menu .menu_data { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAHISURBVDiNpZPPi81RGMY/z+3mpmasGBOZEgthQ5SFTDM2svMjOyt9X1eUEhshRbGx1e1cO5M/YVIzSWbjx4RidTeKbl3XgoyYW5rHwvlO35TZzFtn8fac5znPc855ZZvVVL3apJR2AGeAw5JGbI9m6Iukvu0ZIEVEp+TINimlOnANGAc6wCvbHUm/AAHrgZ3AHmA7MC3pVlEUS6WDWaALHIuIb/9xOw3QarVGa7XafduzwGQtg+PA+xXIy9VsNnvAHDBRvYMBcDyldEDSvO1nkj7lCNhea3uLpEO29wOjmbMsUAfOAYu2C+CC7XW2hzL+A1iw3QWu5/0vqgIAD4A7EXFxpQgppRPA1bIvBX4Dt4GJlNIV4DPQlTTIERrApryeA3eBh1WBBkBEnM+n7AO22R7O+ALwISJeZvxkySn/wQD4CDyV9Nj2XET0/7G+ATgIHAEmgbGIaJQO1gCngF22A7iRUloCfgLOpwnoA4+AFvC6GmEGOA1ciogpgHa7vRkYygLfi6LoVdzcI79CGUHAZf5+jh4wb/udpK/5EoclbQX2AruBt8DNiFhUdRpTSmPAWeCopI22R7KDfh6mJ8BURLwpOVrtOP8BlJPKP95zNKgAAAAASUVORK5CYII=") +} + +.menu .current .menu_data, +.menu .menu_data:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKdQAACnUBSiXd/QAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAF3SURBVDiNpdI5a5VhEAXg58qFRDBi4RIiBkSJoiIoihZi0EpsFWy0FAtbSwvB0uUn2LiUliIWMWgT90i0sRFcYxqFiCYQcizufHK9hUQcGIZh3jnvOYdpJfE/sayn34pLeI4PWKj8jJe4jJHuhVYxaOM8RvEGT6r+RAursQ27sBl3cBGLkkgynuRmklXV/y0Hk9xOMpbkt4RRvMK3JciexkMc6pYwhyl8wlM8wPuSAMuxAQexF4PYif4GYAH7C+g0hrASKwrgO2bxEdfKs0doN7oWkkwmObEED44leVY7f0g4Wbr24Uv9Nl8M+orVECZwH9fR3+56AGer7sEmDFQ/i7d4XP3xZqdhMI93GMfdcnmmx/01OIAjOIxh9DUAwW5sx6lyeRE/atanc1AzuIXXOtfaaoy5l+Rqj1nrk2xJMlLH0z27kmSi28QWzpWJ0zq3MIWvRX8AG4vlDkziAuYagCaGcQZHsQ5rS8JM5Rhu4EWz0Avwz/ELJiL9PSckq44AAAAASUVORK5CYII=") +} + +.menu .menu_set { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAH3SURBVDiNpZI/SJZhFMV/90kicsv+EdEQRUNQYC1S0R+ag2hxMKPgvc/r0Ba2FFEpJARhiF/P9QsaGippimgoG4QgI6egICcJEkoIEyI/9L0NvV98llLQmS6c5x7OPc8Rd+d/0LQcUa1W1xZFcR9ocvfOGOPkXwXMbDvQoqov3b0NuA00i8gBYNLM9gBfVXWiviP1E8xsB3AZmANmgHVAt4jMu/sl4DuwFfgI9KvqO4DQYKAFmFXVU8Bj4JqqfsiybAroBZ6r6jFglbtv+MNB6aIHeKKqL4aGhra4eycQRORelmXvU0pHgPYYoy4SSCltFJGDwEngCjANXHT3WyGEeXfvAq4Dze7eKyJ3i6IYyfN8KgCIyAjQDFxQ1VfAaaA/xjiWZdm4iPQBHao6LiLdwJoQwsiiX3D3z0VR1NNd4e5zDfnUACnnWeATsNB4wjYR2Q8cdfcbIYSau58D+kRkwd273X1QRGaAAWC4KIrRPM8nfg/xqrs/jTGOmtlOoKN08zDGOGZm+0SkPcuys7+W3J3Sxd6U0s1ybk0ptda5SqWyOqW0u+QepJTa6lxjD2rASjO7A5wBBszsEEAI4TzQZWaDwBfg23I92AWsV9VnZnYC2OTur0Vks6oOm9lhYFpV3ywp0IiySI/42bzjMca3S71bVuBf8QODpRL9eTmkdgAAAABJRU5ErkJggg==") +} + +.menu .current .menu_set, +.menu .menu_set:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAF0SURBVDiNpdJNiM5RGAXw38ukkJVvMYqZRsqYZmyYDTZWbCxsKLG3pCSaWNmNJU2NmhU7pQjFRhYsJCTWykfJRz5eH8fmefn39lLy1FPn3nPv7Zzz3FYS/1Oz/sItwg3cwuo/HWp1KRjEQtzBTizAfHzCDMbwDk97PTCECXzBWyzGYXzDCXzGGjzHJB6DJJ3ekuRc4R1JNja4VUl2FZ5OsrXDNTO4jRcYx1W8wTEcx1xcwna0cbM7xGXYgxF8LakTuIbLZWVdWevHXixvWniY5ECSkVqfbGBJBpOcKjyc5FCSR90WXjXSnV1hdqqNVuH3eInvTQUDSfYnmUkylmRDkvNJ1icZSjJV+wNJriQ5WKr01avPqtfW3O/hNPaVmrO1N14qp35pa/jclORM4dHqDjevMdYLSTZ3uD6/q405mMYHjOJojewIluJHjfdj51L3Vx7GElzHbqzAXazERWzDazzoZaG7+5PcT/Kkwux5rlvBP9dPgIpDWf6ENxgAAAAASUVORK5CYII=") +} + +.menu .menu_folder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAE3SURBVDiNpZO9SgNBFIW/OySiaBCtbCwE8ReUgGDpE9gExNJmd6O+hM8gmGJn3yKxt7WyEARBUUSbIPhTGFAwxyarSwoZ2AO3mjkf98y9Y5IoI1fKDVQA0jRtOOd2B8BRST5Jkk4IwCSRZVkbOAYeACdpD3iX9DrUpQO+gHaz2XwuRnCSLuM4vpU0AnSAvplNmdlkocbNbN3MTrz3de/9WKVA/07TdN85twbcA5/A8AsLuAO6wI6k2UrhoGZmy3EcH4Zk995vAAd5hA9gBXgJMQ+0ZWanOaAH1IGrEGer1TJgxszOckAFWATOQwDVanUO6EVR9JYD5oGJJEmeAtvfBG7gb4xLwEWgGWA1v59PoQs8Zlm2ANg/RgETwLRz7voXIOnIzBrAdgCgBvgoivowWOUyKv0bfwCvBmEVd9ynHgAAAABJRU5ErkJggg==") +} + +.menu .current .menu_folder, +.menu .menu_folder:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAADuSURBVDiNpdO9SkQxEIbh56ynUHQbQbCxEMR/BEGw9B4s1m6t9W5svQgL3d7Wyk4QLES0EcGfQkGbsTgJuywIwTMQEsh8b+ZLJlVEaBOdVmrUad7HQQJO4hQXJYAqWTjHCR4S5BAfeBursoOflP8CIkJEDCJiLq03ozymcgUD9NHDFu7xjfEbjmR7Oo2FemSjizUcl3jHDo6yv0+s47VQDHsYZMAXtnFTKK4wj8sMqLGCq0LAYjr0PQOWMIOnQsAu7hi+8SquC8WwkfPzKzzjEcsaf39FpEpnccuwE3uadp4oAHQ1rX42Cvh3tP6Nv5Cebn/RRiyLAAAAAElFTkSuQmCC") +} + +.menu .menu_day { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKwwAACsMBNCkkqwAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFrSURBVDiNpZMxSFxBEIa/ed49tQsBQdEqgrVgithFW1sr62PhKttXpjy0vWb2WjkQrk6rjWUgjY3ICorYGCFV9C7vxuJ24fE4Y8SBZf/9d3b2n/1ZMTPeE1l10ev1Frz3Wy8lq+pXVf1Y5cTM6Ha7jTzPd4BtYBUYAHO184/ALnAJnJRl+b3dbo8aAM1m8xB4AO6BT2b2C5ivFxCRv8AtsJ5l2SZQYGao6kBVFyNeNjOmDVVdifOSqh6Z2aQF7/0xcGNmF4BMkV9tw0RkDfjgnGs14kYJXInIOTALjGOhukUZ8AQ0gc+JIJI/nHNnIYRWCME5505CCC7i04hbzrkz4Gc8Q1IwEwedTmcvXfcSruZnNZKiKPpFUfT/hZnYX1YVyGu31hRk6X2SgnEi/lPBb6JTSUFuZsM3KPiTWk4FbkXki/d+CORMbK3aKEzsTXhDRK6rBb4B+2bWAkbVN5kSDRG5E5EDiJ/pPfEM08DH4VH64rEAAAAASUVORK5CYII=") +} + +.menu .current .menu_day, +.menu .menu_day:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAEPSURBVDiNpdO9SkNBEIbh58RoFKwEwT8QFOwEQQvt1NbW+7Cx8B7svAZLa1tbG0sbEQQh2KigjcZoxsI9YT2EGMnCMt8us+98s8sWEWGYUausp7HTJ38bU/lGPYt72MUyFjFeOfyOfdziAudol4BjPOMRS3jCRA/AJ5pYwxaORISIOIuImaTnU+w1F1KcjYjTiOi28IVD3KDoYT93EVhJ+hfgDtdooJNA1SeqoYVRbOSAFq5wmR0q+mhYzQEjaeYJ/XQ3v1bZlCrFH7rw03bXwSBVc10rYaWDzgBVc/0ivVTpYAwf/3DwJrVcAprYTJCx1F/15huZXsc9FOk3TuIAc2hXqlVHHQ84wWsx7Hf+BgvadUGnT3fcAAAAAElFTkSuQmCC") +} + +.menu .menu_logs { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAApJJREFUeJztm71y4yAQx3dvzn4t41dIm8pzAV+6K65LIblId8WVMeReIa9g/FaZcbFXBGdkjBD6iBAyv5kUktB6+QcW7bAAZDKZWwZDGimlGBEV5pJ9nTuDoBFRc853IY2/hTQiogN8dJx192s0GBGVSqmiuWmAAKGGpgYRlSHtGqeAlJIuXkAMMhyLascRcc05177239sYR8QydG7FQCnFrFsrANC+d4JiwJzJAsR2IDZZgNgOxCYLENuB2LT6DnAhpXwFgB8D+BIEEb0tl8v7zWbzPoS9XiNgv98/wYidBwBAxLvT6fR3KHs3PwV6CbDdbp8B4N9AvgRBRG+LxeLXUPZ6xwAhxAMAPAzgSxTyFIjtQGyyALEdiE0WILYDsbl5AXIu0OflnAvMgJwL9DWQc4HEyQLEdmBIOOe6sncZtEXeOwZMDdPp4P3LWY2ALiQ/As71C0RU2rvXSqnCbJdrRNy5tspb1QdMbXu80kEbDY5qFpf/U8oFXoQQjy1+90BErOax874pnYGqCFPKBX5KKX+HNkZE3fF3jtWLmw+CU8oFXoQQf0Ibe4a/rvy5WFUvkguCJvAxuJ7nV/WBpr7x4DDzuSokNQVM4CvBHeF39j/HfBmuHaYYER2UUkVSAkB9oaauK4ezPo8vIKIyNQGcIGLnaTkLAcAKbG2YiwBePCtGWgL45nJdTbO5z2pMprUKcM53Jqpr+5lLhLpcARFLRFwLIdbJfQec8SRCTWghxOfSmNQIsDg2N7nGziFSFqBz5K+SsgBduRg5ITHgfFzm44WRD0z4Yo4rDhj/jgCwsp45zxK1FmBsQgJvNfo7kqHCd4iqUQBPRjUKX73yNMYAzrkWQmCks0IaOkb7TCaTCeE/v6cw3cuvVicAAAAASUVORK5CYII=") +} + +.menu .current .menu_logs, +.menu .menu_logs:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAAf9JREFUaIHtmUFOwzAQRccWmyaN2HIEOAXpIag4BnsWcRfsOQZSD9EgNhyBI3SLnMnSw6IpciI7iUMdB8lv0zTxKDPyOF/6AxCJ/AnW9xARCyLKGWP5TPm0IKKSMVamabqzrbEWgIgFAAgfibmilNpkWVaanl31xInzBREZg30zZuf7CgCAU/Lr9XpzkYwckFL+ti7n/B4AStM6PmNOXogFhCYWEJrBr5AOEV0j4idj7PbCeezTNN1OCXTagbqunzwkDwDwUNf145RApwKSJHkloq8pLxpgnyTJ25RApxZijH0DwN2UF/ni3x/iWEBoYgGhiUJ2IaKQjSIK2ZwgIiEiVVV1CJhDMfn9SyhgDItsISll3vhSxv86TofYN10zDRFBdwYRUQCA0J26PmeOANq+0B+E7Mg5365Wqw/bAillzjkf1a66UzeXkN0opZ4nxA0yl5AdOecvvYmc3LcWRFQO2ZrBhayxEAsAyPX7XUO3qqqDZjUemvMQ/hAzxgqDiSu6bjQR7TrrBMBCP6OmeUCWZcZ2WmQBNkx2+yILMImWTciCF9BojOjcFl0l7q4holIptXESMp+4CBloahx8B87YZmBDLKYAF5RS7+drawvpwnHpIZ9tdGoY6wr9t1Hmnb5bIceswjb/bQ5tq62klLmpzQYH3eChiDED7EhkJn4A1qwofp3F9mcAAAAASUVORK5CYII=") +} + +.menu .menu_control { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAF5SURBVDiNpdM9ixNRFMbx3yRZ3/ANbCxEcBubpNJSLIQFaxs7D4JfQSz9CpYWItyPYCOIFtqvhQxopSyCb1gpEuNukrHImewQY7UHLsOce85z/vPMvVXTNA4SAyilfMI2Zpn/n2rV6RtGxOYgE9sITLK5Qa8jVGGezwpH8VgWwRTjiJigj6vYwrVcW5nrZ824pW0Jeh0xSVJhr4M86ez32/qBf6OH04nZCmzk1HbI0qN1AhO8zuYfOJ5i8xWKZq1AREzxpZRyAlfwJiI+r5RVrUCLNOvgtnER93FpDeV0laBnYcwMSimHcQpP7Z+V2xZelA7FkuA8opRyK9/P4gIeYq+Ucj2/fwM3Lc7Mua7AVzzH91LKHZxBExE/s/EudiwOz0u8wLeuwDgidvAMf3APr3LvHR7gfUT8joiPw+HwQwqrmqZRSnmSWL9wEpt4a/Hr5qPRaLeu60OdgcfwKCJutCZeTnNmuXZxpDWqruvG/j1o0vDhkuAg8RdE7nuSY6nc+gAAAABJRU5ErkJggg==") +} + +.menu .current .menu_control, +.menu .menu_control:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKwwAACsMBNCkkqwAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFaSURBVDiNpdM7a5RREAbg59uLJOgSkRQhfYSIhWWsQgj+gfgLTJEmBJLa0tZW0MbawtbaUkQEQ8Q/YCWIIAqb7G1S7JzNYdlUOfAxlzPznndevmkiwk1OJ+03nGKMBpNr6lsIdLGBrQJwimP05wCatFEBTHAHL2sGY/zHEMt4jBWMKqZ/8RkXVc8MoJX+MF8bJJth3nczV5i0s2cGUJ82eslgkLlbOU57vngRwAA/sqmftp35AhiFzSKAIX6mv4+3C2pa887YlTjlPMAJtjNeMtVC1k5qgA5Wa2Q8xQvsZnyI51jD3cK+jLCOA/xLyn1TET/m3U4+0M+xRpmfAfzGBzzBM/zB17Rf8B57+ISHpsI+AhEhIt6kbSLiKCLOImIzc72IOIyIbsblex0RMwZLuGf6t73Dd/zK/AVepYDLKV4v7zS5jfUyjXCO25Wok/TLnnRwH1vNTdf5EpkVg1v2kuagAAAAAElFTkSuQmCC") +} + +.menu .menu_soft { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAF7SURBVDiNrdPPi41hFAfwz72uoVsUWZOFJbKThYkVe1mpx13YjcmU/NwrZCKRLNRZsrSZErvJ1h+glFgiYxC6Pyze8zSvOwsLzuZ5T9/n/T7n+z3ndCaTiX+JHkTELlzHHozQxQrOl1JeRcReLGIbxtiAN7jcS6JrCT5OcIx9mMcAZ/EBz5N8hCO4UQl240kp5XYtLSJmsZDpdjwspTxr4V9xppv5EP0peVuzEnlumcL7GFaCDn5OXfj+l/wHulXCBIci4rXG2BFmMZP4RhyLiBmNR0McxqgSLGBOY1iNIW7l96LGyNMtfBXznf81B/vzhR0pB37hbillOSIO4hw2JdbBJ9yrEu7gIx7504OLWMYFvMcLax6cwv1K0MPLUsrTWlr2eS7TEZZKKUstfCcGtY3jVnk1+i05E+vnZDPGlaBnfZ+/aMZWnqtT+Df0qoR3ON7qc92FlcQ/YxARB6ztwlG8rQRXNdt4Mn/u5AuXEn+AmziRcroaU6/8BgTXdRpxDzi5AAAAAElFTkSuQmCC") +} + +.menu .current .menu_soft, +.menu .menu_soft:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKnAAACpwB9NLfEgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFsSURBVDiNrdO/jw1RFAfwz4xnVzYIydZeY0tEKETChkIoNGrNtkKyhQL/gUg0YgudQiciGpVEIRsdPdlkQ0usH4vNvHcUc27e9bbkm9zMmfnOPfP9nvneJiL8CwZ5PYS72I8xdmANN/EeQ9zGAkZosYHrTSp4gD14m+QIZ/ATl/AI83iRzcc4jC0RISIeR8S5rMu6EhFvsl6NiOUpfjEinrZpYZwKasyhy7rL+xp7MW6rB5tTL/xKO9Dg9xS/WQ9xJ85jJj12OJ2zgMBJvMs9IyxipgzxFK5lo4JvWMFrHMNV7Kv4DivN/8rBkVQwn3JhC/fwCiewjNnkGnzG/aLgJT7h4ZTHBVzEE3w0yUGHyxgWBQOs4lml7rvet2z4PFfBASzVOZj1N+YqO2F7DnapcjCwPQdfTXLQ6v9KjR8YFAsfcMEkByXrG8l/wRKOmpyVs1gvQzyoP23D3NzkF27oc3Acd7A77bT6od76A2AskgeNVoIQAAAAAElFTkSuQmCC") +} + +.menu .menu_firewall { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAG0SURBVDiNhdNPiI5RFAbw352ZsGBDMhkpYyE12cxOyYIFayW7SY2zkCxIlkrZKIrJjPe+X1FksrQQo/xp2IhZiPXUxIoaGxa+mGsx78dn+vDUvXVu53l6znO6qZSig6mpqbX9/f37cRB7sDOl1C6lzOIZHkXEK11IpRR1Xe8upRzFKNqYxzt8xGpsxy6swwCeYjIi5gcaoTt4i8BcRBQ90Gq11i8tLY3gCjZibABKKUu4FxGvexE7GB8fX8RsVVVPUkqDGjtQGqv/RM75CJ6nlH6gT+dCwvf/kE9hGlvwrZfAqrqu+3LOQz3Ix3EJpyPipeUwdQt8RbuUshkLjdUOeQzXcD4iLnfptvmdwXtsjYgPOefrmM45f0Y/buJiRJzrIm+wvOJfDl5gBCLiBFp4iPuYiIizK6YaSim96Ra4jeGc86ZG5BhyQz65Io9hDJZSZkApRSlFVVUTVVXd7dR/O1VVPaiq6mqn7jiQUjqD0Zxzd1B/IOc8iW1N7zKv+zM1I8zgE8YjYqF534EbTegHImKxp0DTnHABh3ALa3AY0ys20Vugg7qu95ZSHuML9kXEXK++ny1tzgEddf2OAAAAAElFTkSuQmCC") +} + +.menu .current .menu_firewall, +.menu .menu_firewall:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKdQAACnUBSiXd/QAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFrSURBVDiNhdMvaJZRGAXw36viFrRoENxw4IJYLDMJalkxC8OkrC5qNAwEi+CC4lAsikGMY0FQmGIQ/AMKyjDIUGYyqMXgmB7Dd9/P63j37cAN73OfczjPc+7bJFFhFyZxCsdxGGt4hqd4hFc1oSkCxzCNiUJYwXt8xRDGcQS7sQNPMI8VSST5lGQxydEkTal1nT1JTiR5k+RuEtuKkz9YwGv8N9MGfCvjLBUnfYEUq1vhDEbwu+W2Ag3WtyCfx32M4leXwM7yPdJBnsFVXMALvWWqBX7qbX8/PherLc7hBi5hrqqvURaBVRzAF9wsVr9jO+7gCmYr8l69iLXxXEyyUMV1O/9wrSPOpSRnk/QFxpK8S7Kvarq1CflgkuW2t764nuTBgEfUnoe1cH0xnORjkrkB5PkkH5IMdQkott4meVzGauuHkjxP8rI85z6n/ZlqNLiM07iHYUyVZGY3Ng+a9WSS9SQ/kkxs1vcXeVqZSyUF+yoAAAAASUVORK5CYII=") +} + +.menu .menu_exit { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAEYSURBVDiNpZOtTkNBEEbPFCQIUHgEBoJAYFDgERgSCBjSvU9Q0/AkX5+ApB6PI6lAYPhJUCgEgkApkHyI/nBpb28DPWozu3MyuzsTtpmG2f5CUs32BlABouBsAK8RUU8pPY4IImIfOLd9k4/n+IyII2AZGBUAM8BZlmXX48qVtAksFl6hx/y45B5N4C4fqExI+EVK6QJYkLT9L0GPJ+BU0hYUP9YAScfACtDh52c6wAPQlHRQKoiIN+B5WGC7DXzYfi8VVKvV5nCs0WgsRcSh7b0sy1qlgiJszwG1lFIL/viIknaAdkrpsh8bruBlgmOXbq8UduIXcCLpvkDc31+j20wDBgdty/Y6sMroMJnuda+A2/xGTDvO32OQXrvPg7l3AAAAAElFTkSuQmCC") +} + +.menu .current .menu_exit, +.menu .menu_exit:hover { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKdQAACnUBSiXd/QAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAADvSURBVDiNpdO7LkRRFAbg7wwlBZVeoSEKEtHSayUSGs+g8RwqbyDR6ycaiYRC45KoVAqFuAySpdhzODn2mWMyf7KSvdflz7oWEWEUjFfee1hCB0XGt8AL9vGQI9jECa5r+hKf2MZsE8EYjnA1IOMVTDeVAJMDguEYt1VFpyWgji6msNaUwX/wiAN84LSNYAdz6PmdTA/3UjlbbQSveMoQvPUzeBcRpVxExGrl3yQzEdGNiOWIGLqJMCEt3TnDT2G9n/5Zqaj34LmFYEPalewmfmEXdxni0r4gdf8HVcdDLGLe32MKqdxL3FQNxajn/A0ZS19hUhhlTwAAAABJRU5ErkJggg==") +} + +html .menu .menu_home:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAPPz8+np6d3d3c/Pz8vLy8XFxb+/v729vbu7u7e3t7W1tbGxsa+vr62traurq6mpqaenp6WlpaOjo6GhoZ2dnZubmwrPOpmZmQzLPJeXl5WVlRLFPhLDPhy5RI+Pjx63RImJiYeHhyypSiirSIODgzKfToGBgTabUDibUDiZUH5+fj6TUnx8fECRVEKPVHp6ekSNVkCPVESLVnZ2dnR0dEyDWnBwcFR4XlZ2Xlh2XlpyYGZmZgAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJHgA7ACwAAAEAEAAOAAAGhsCdcLgzJUjE5HCmECxMFYZHuWMRHDuJINIg2JIqAmQYOS6SpEEkORFciOkJlVIwCTUEDfVOyOweFyAzVDMgFw1DBSFDOkMkBUQ2B0g1HyIdNUUGX0OTOy0jNyMrRQecOzYIKjsyKDspMDsvpkQGFDklJzsoJTgVBkkIAAEYLjsuGAEACEJBACH5BAkeADsALAAAAQAQAA4AAAZxwJ1wuDMlSMTkcKYQLEwVhke5YxEcO4kg0iDYkioCZBg5LpKkQSQ5EVyI6QmVUjAJNQQN9U7I7B4XIDNUMyAXDUMFIVQkBUQ2B0hKJgZfQ5FUJgeWOzYIKlQvm0QGFEM6QxUGSQgAARYcIhsWAQAIQkEAIfkECR4AOwAsAAABABAADgAABnfAnXC4MyVIxORwphAsTBWGR7ljERw7iSDSINiSKgJkGDkukqRBJDkRXIjpCZVSMAk1BA31TsjsHhcgM1QzIBcNQwUhVCQFRDYHSEomBl9DkVQmB5Y7NggqQzpDL5tEBhQ5NR8iHTU4FQZJCAABGC47LhgBAAhCQQAh+QQJHgA7ACwAAAEAEAAOAAAGfcCdcLgzJUjE5HCmECxMFYZHuWMRHDuJINIg2JIqAmQYOS6SpEEkORFciOkJlVIwCTUEDfVOyOweFyAzVDMgFw1DBSFUJAVENgdISiYGX0ORQzpDJgeWOzYIKjs1HyIdNTsvnUQGFDklJzsoJTgVBkkIAAEYLjsuGAEACEJBACH5BAUeADsALAAAAQAQAA4AAAaCwJ1wuDMlSMTkcKYQLEwVhke5YxEcO4kg0iDYkioCZBg5LpKkQSQ5EVyI6QmVUjAJNQQN9U7I7B4XIDNUMyAXDUMFIVQkBUQ2B0hCOkMmBl9DkTs1HyIdNUUHmDs2CCo7Mig7KTA7L6JEBhQ5JSc7KCU4FQZJCAABGC47LhgBAAhCQQAh+QQFHgA7ACwGAAkABQADAAAGDsDdTiesfUSdWmt0G62CACH5BAkyADsALAAAAQAQAA4AAAYVwJ1wSCwaj8ikcslsOp/QqHRKdQYBADs=") +} + +html .menu .menu_web:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAPHx8e/v7+vr6+np6efn5+Xl5eHh4d/f393d3dXV1dHR0c3NzcvLy8nJycfHx7+/v729vbu7u7m5ube3t6+vr62traurq6mpqaenp6WlpaGhoZ+fn52dnZubm5mZmZeXlxTDPpOTkyqrSiqpSoeHhy6nSiynSoWFhS6lTDCjTDKhTjSfTjadTjadUDSdTjabUDyXUjqXUjyVUj6TUkCRVHp6ekSNVkaLVkSLVnh4eEqHWHZ2dkiHWEqFWE6BWlB+XFJ8XFJ6XFR6XFZ4XlZ2XlpyYFxwYmZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFDgBHACwAAAEADwAOAAAHbYBHgoIdCgIACRyDizsTFQ0XGA4WEjuLNQMnH4shJwU1gw8di4sdD4IaGKSkGBpHC5argzsMRwYbsoMbB0cHHrmCHghHDDnARzm1GhnHGa5HEL+yHxCDmJqcJASg1hEUDRgZkxLGqxoJAQGJi4EAIfkEBQ4ARwAsBAADAAgAAgAABxGAR0dDPkBDgi0gNCgqNCAsgQAh+QQFDgBHACwEAAUACAACAAAHEYBFKUAogyNGPjZHPT5HMD+BACH5BAUOAEcALAMABwAKAAIAAAcWgDcoJjclKDciKTc6Kig9KCo6Jis6gQAh+QQFDgBHACwEAAkACAABAAAHCoA+Nkc6PkcwP4EAIfkEBQ4ARwAsBAAKAAgAAQAABwqARShAJSlAI0aBACH5BAUOAEcALAQACwAIAAIAAAcRgCwgMyUpMiAvR0dDPkJDioEAIfkECTIARwAsAAABAA8ADgAABxaAR4KDhIWGh4iJiouMjY6PkJGSk5SBADs=") +} + +html .menu .menu_ftp:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAP////39/fv7+/f39+3t7evr6+Pj49/f39nZ2dfX19XV1dPT09HR0c/Pz83NzcvLy8nJycfHx8XFxcPDw8HBwb+/v729vbu7u7W1tbOzs7Gxsa+vr62trampqaenp6OjowDZNp+fnwLXNp2dnQTVOATTOJubmwrPOpmZmQrNOgzLPJeXl5WVlRi/QBa/QJGRkY+PjyC1RI2NjSSxRomJiYeHh4WFhYODg4GBgX5+fnx8fECRVESNVkSLVkaLVkiHWHR0dE6BWnJyclB+XFJ8XFR6XlR4Xlp0YFh0YGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJMgBJACwAAAEAEAAOAAAHvYBJgoIhCAICCiuDi4IOEQsBAQ4QE4yCCR06DRUWD0AYDYwaATALNBweMA8sAB6LCC8QAwwEBQwDESwIgzQOSTURQBMVQBA5SQw3grFJKBdJGRtJFCxJDzKCOApJNg01DA42DDZJCziDCRsMAhQGBxIBDBsJiygCORYjqB8XNgAmjDZ8SCLjxg0YSTRkGBSkh48fO3i0UKGiRY8dP3z4GBKDhMcSIkCIBCGihEcSM5AUWVlkyAmRKYiwLHIkEAAh+QQFDgBJACwAAAEADwALAAAHj4BJgoIhCAICCiuDi0kOEQsBAQ4QE4xJCR06DRUWD0AYDYsaATALNBweMA8sAB6DCC8QAwwEBQwDESwIgjQOSTURQBMVQBA5SQw3SbFJKBdJGRtJFCxJDzJJOApJNg01DA42DDZJCziCCRsMAhQGBxIBDBsJgygCORYjqB8XNgAmizZ8SCLjxg0YSTRkEBQIACH5BAUOAEkALAAADAACAAMAAAcIgEE9MSRIRYEAIfkEBQ4ASQAsAgAMAAEAAwAABwWAPiRFgQAh+QQJDgBJACwAAAEADwAOAAAHIIBJgoOEhYaHiImKi4yNjo+QkY8/OzyMJCUijEVFQ4mBACH5BAkOAEkALAAAAQAPAA4AAAepgEmCgiEIAgIKK4OLSQ4RCwEBDhATjEkJHToNFRYPQBgNixoBMAs0HB4wDywAHoMILxADDAQFDAMRLAiCNA5JNRFAExVAEDlJDDdJsUkoF0kZG0kULEkPMkk4Ckk2DTUMDjYMNkkLOIIJGwwCFAYHEgEMGwmDKAI5FiOoHxc2ACaLNnxIIuPGDRhJNGQQFKSHjx87eFgaFIOExRIiQGgEwQhJkY9FJg4KBAAh+QQFDgBJACwAAAEADwAOAAAHsYBJgoIhCAICCiuDi0kOEQsBAQ4QE4xJCR06DRUWD0AYDYsaATALNBweMA8sAB6DCC8QAwwEBQwDESwIgjQOSTURQBMVQBA5SQw3SbFJKBdJGRtJFCxJDzJJOApJNg01DA42DDZJCziCCRsMAhQGBxIBDBsJgygCORYjqB8XNgAmizZ8SCLjxg0YSTRkEBSkh48fO3gs6rHjh6AYJDKWEAGiIwgRJUgIQlKkpKUkJpMEAgAh+QQFDgBJACwNAAwAAwADAAAHC4A+PkMkJDNFRUeBACH5BAUOAEkALAQADAAGAAMAAAcOgElJLSoqLYKISUVDiYEAIfkECTIASQAsAAABABAADgAABx2ASYKDhIWGh4iJiouMjY6PkJGSk5SEJyAgKUSFgQA7") +} + +html .menu .menu_data:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAP////39/fv7+/Hx8e/v7+np6ePj4+Hh4d/f393d3dfX19XV1dPT08XFxcPDw8HBwb+/v7u7u7m5ube3t62traenp6OjowDZNgLXNp2dnZubmwrPOpmZmQrNOpeXlxLFPhTDQBTDPhbBQBTBQBa/QBq9Qhi9Qh63RBy5QiC1RCC1RiKzRiSxRouLi4mJiYeHhyirSCynSoWFhS6lTIODgzKhTjSfTjadTjKfToGBgTibUDiZUHx8fD6TUnh4eHZ2dkiHWHR0dEyDWnJyclB+XFZ4XlZ2Xlh0YFxwYGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFMgBJACwAAAEADwAOAAAHr4BJgkUzIB0XFxsfM0WCgjUlMD1CRERAOiwiNoIlLEiOoElIKSVJFzehoTsXSRggJzZCR0hIR0I2JyIYSQEyOTErKCQkKCoxNDIBvAYaqYIcB8oCHBAHDA4QEA4MBg8cAkkAHIIyGhYWGi/PAEkCBQ4cQaBBHg4F4AA0GQsGBgkJB6xloMFOQQRHP3jwkCcoAgJBFBg0qNAiRw4XGSIskADKxwQDAwIEIGAgAg1BgQAAIfkECQ8ASQAsAAABAA8ADgAABxaASYKDhIWGh4iJiouMjY6PkJGSk5SBACH5BAkPAEkALAAAAAAPAA8AAAe0gEmCRTMgHRcXGx8zRYKCNSUwPUJEREA6LCI2giUsSI6gSUgpJUkXN6GhOxdJGCAnNkJHSEhHQjYnIhigMSsoJCQoKjGgATI5qYI0MgFJAQYayUkcB80CHBAHDA4QEA4MBg8cAkkAHIIyGhYWGi+CHABJAgUOHEGgQR4OBeQANBkLDBhIkOCAtgw04imI4OgHDx73BEVAIIgCgwYVWuTI4SJDhAUSQPmYYGBAgAAEDESgISgQACH5BAkPAEkALAAAAQAPAA4AAAevgEmCRTMgHRcXGx8zRYKCNSUwPUJEREA6LCI2giUsSI6gSUgpJUkXN6GhOxdJGCAnNkJHSEhHQjYnIhhJATI5MSsoJCQoKjE0MgG8BhqpghwHygIcEAcMDhAQDgwGDxwCSQAcgjIaFhYaL88ASQIFDhxBoEEeDgXgADQZCwYGCQkHrGWgwU5BBEc/ePCQJygCAkEUGDSo0CJHDhcZIiyQAMrHBAMDAgQgYCACDUGBAAAh+QQJDwBJACwAAAAADwAPAAAHtIBJgkUzIB0XFxsfM0WCgjUlMD1CRERAOiwiNoIlLEiOoElIKSVJFzehoTsXSRggJzZCR0hIR0I2JyIYoDErKCQkKCoxoAEyOamCNDIBSQEGGslJHAfNAhwQBwwOEBAODAYPHAJJAByCMhoWFhovghwASQIFDhxBoEEeDgXkADQZCwwYSJDggLYMNOIpiODoBw8e9wRFQCCIAoMGFVrkyOEiQ4QFEkD5mGBgQIAABAxEoCEoEAA7") +} + +html .menu .menu_control:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAPcAAOTk5N/f38bUxsnQycTTxMfOx8XLxcPKw8bKxsfHx8TIxMbGxsXGxcXFxcfFx8PGw8TExMXExcbCxsfCx8PDw8PEw8PCw8PBw8HBwcW/xcHAwb7AvsK/whzVHBzTHCDOICLLIjC2MDO0MzOzMzKzMjWxNTOyMzWvNTavNjirOE2hTTqpOjunOzylPEmhSUqgSjqlOoaGhj+iPz6iPoSEhD6hPj6fPj+fP4ODg0CfQDqhOoKCgkGcQUObQ4CAgEWXRUaWRkWWRUqQSkiSSEiRSEyNTFuHW3p6eluGW3l5eU6KTliFWE6JTk2JTVaFVlWFVWB/YFGGUXV1dVGEUVWAVVSAVHRzdHNzc3FxcVV9VXZvdlp4Wm9vb2hyaGF0YVt2W1x1XFx0XGFyYWNwY15yXl1zXWtra15xXmNtY2FuYWpqamBvYG1obWJtYmlpaWJsYmhpaGNqY2hoaGJrYmRqZGRpZGdnZ2VpZWVoZWRoZGZmZmVnZWdlZ2VlZWdkZ2djZ2hjaGxdbG9ab2ZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCACDACwAAAEADwAOAAAIhgAHAUgAYcGCBggbGISQIMCgBlwG9dFjp6IdPX0GYaEwCMKgQWqO4NixA8cRNR89LvjoJglJkkncpBy0EqSPGDRoxPCBsiPNj3KwSLlyRQoWOTNrflzK1GeDplAHcWQQtWmFQQOqMi0wCIHWpQoGWfj6cSzEQXC63IG6UWCCBhA2KDS4sGFAACH5BAUIAIMALAIACQACAAMAAAgJAMUMcsHjSZSAACH5BAUIAIMALAMACgADAAQAAAgQAGGUGbSCxaBBJ4IM8gIlIAAh+QQFCACDACwEAAoAAwAEAAAIEAAHhTkzKAeVFiCoDDIyKCAAIfkEBQgAgwAsBQAIAAMABAAACBAABzFJM6jElj0pwAwaMiggACH5BAUIAIMALAYABwADAAUAAAgSAAfpGFSlQ5FBQEIMGpRiYZiAACH5BAUIAIMALAgACQADAAQAAAgQAAcN2jJIiIwpHogMujEoIAAh+QQFCACDACwJAAcABQAFAAAIGQAHCRw4kI2gHiK+iBlEwoQNFYOaBMqyJCAAIfkECTIAgwAsAAABAA8ADgAACBkABwkcSLCgwYMIEypcyLChw4cQI0qcSDEgADs=") +} + +html .menu .menu_firewall:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAP////39/fv7+/f39+3t7evr6+np6efn5+Xl5ePj4+Hh4d/f393d3dnZ2dfX19XV1dHR0cvLy8nJycfHx8PDw8HBwb+/v729vbu7u7m5ube3t7W1tbOzs7Gxsa+vr6enp6WlpaOjo5+fnwDZNpeXl42NjYuLi4mJiSitSCqpSiypSoeHhyirSC6lTIWFhTKhTjiZUDyXUnx8fECRVEKPVHp6enh4eEqHWEiHWHR0dFB+XHBwcFpyYGZmZgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFAwA9ACwAAAAADwAQAAAGdMCesEeiHAACR2c17J0oiUXEE/pgHAsFptYjQFzN4c6UmPQKoXAYIzl/1M1M5AyCDzdzwtveu8wRdXwXbQ8afD0SGD0dEIcNIj01CDl2NQeUPRZzcA8WTQWKYRUGYTkJDlw9MgwLO3AaBh0bBhyHJQEDYE1BACH5BAUEAD0ALAMABgACAAMAAAYHwB6PN+qlggAh+QQFBAA9ACwFAAcAAQADAAAGBUDaCBUEACH5BAUIAD0ALAYACAACAAMAAAYHwBtuNGKpggAh+QQFCAA9ACwIAAcAAQADAAAGBcDZSBUEACH5BAUIAD0ALAkABgABAAMAAAYFwNioFQQAIfkEBQgAPQAsCgAFAAEAAwAABgVA2OgVBAAh+QQFBgA9ACwLAAQAAgADAAAGB0Aab6SD9YIAIfkECTIAPQAsAAAAAA8AEAAABhXAnnBILBqPyKRyyWw6n9CodEqtJoMAOw==") +} + +html .menu .menu_day:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAANPT09HR0c3NzcvLy8nJycfHx8XFxcPDw8HBwb+/v729vbu7u7m5ubW1tbOzs7Gxsa2trampqaenp6WlpaOjo6GhoQDZNpubm5WVlY+Pj42NjYuLi4mJiYeHh4WFhYODg35+fnZ2dnR0dHJycnBwcGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgAlACwAAAEADwAOAAAGesCSUKQRGkua0RGjAFQm0EkFoMAIFY9HAHPpXjABiINREohKoaMwHSKUBgvKJEKvTygLw1vCyWj+gBkcEnoGHCUWFmpCHAglBYeLRhsHjxuSRhqOBJeYSI4FnZgeegMdniUgBSULER8dHrCxG7QRDUIPCQq7vLsJDyVBACH5BAUKACUALAcABwACAAEAAAYEQIslCAAh+QQFCgAlACwKAAcAAgABAAAGBECLJQgAIfkEBQoAJQAsBAAJAAIAAQAABgRAiyUIACH5BAUKACUALAcACQACAAEAAAYEQIslCAAh+QQFCgAlACwKAAkAAgABAAAGBECLJQgAIfkEBQoAJQAsBAALAAIAAQAABgRAiyUIACH5BAUKACUALAcACwACAAEAAAYEQIslCAAh+QQFCgAlACwKAAsAAgABAAAGBECLJQgAIfkECTIAJQAsAAABAA8ADgAABhTAknBILBqPyKRyyWw6n9CodEoNAgA7") +} + +html .menu .menu_soft:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAOPj4+Hh4d/f393d3dvb29nZ2dfX19XV1dPT09HR0c/Pz83NzcvLy8nJycfHx8XFxcPDw7+/v729vbu7u6+vr62traurq6mpqaenp6WlpZmZmZeXl5WVlRTDQBTDPpOTkxLDPhbBQBi/QBTBQBq9Qha/QJGRkRi9Qhi9QI+Pjxq7Qhy5RB63RBy5QiC1RiC1RI2NjSKzRiSxRouLiyKxRiavSCitSCSvSImJiSatSCypSiirSCynSoWFhYODg4GBgTadUDadTjabUDSdTjibUDiZUDyXUn5+fjqXUjyVUnx8fHh4eESLVkaLVkaJWEaJVkqHWEiHWEqFWE6DWkyDWk6BWlB+WlB8XFJ8XFJ6XFR6XlR4Xlh2Xlp0YGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFHgBeACwAAAEAEAAPAAAHtoBeXTEdIR0qWF4/BgECAQtKXjIrQkBAMjVeEAwVFBUHCl4iRV6lUy5eCyalXhgAXidArE6oCxqsFQGiRKxStR+sGLonLk5MTTwsXgoSHBscCwJeWDUu1jFUXjMLCt0OOKzh4uI+DwndDDNeOAzdCQ89XgUKzhwTCcsR9dFeAxasKUItAFZKmBcCFFhxEHirVK5+F1jBwKeKVYZXDA50qtDAgZcHnDwdwKdEQaMABcD1KHAywZFAACH5BAUeAF4ALAAAAQAQAAcAAAdhgF5LDAECAQY+XloqHSEdMVxeDQcVFBUND147MkBAQisuXgMXXqUwCV4uUKVeRh1eBBSsHApeMUysRK8EFqwptTGrpa6wChwbHBMIqTxNTE4xIV49DwrWCzBeUzEu3TVWgQAh+QQFHgBeACwJAAEABwAPAAAHWIA/BgECAQtKEAwVFBUHCgsmXpIYAAsakl4Vhh+YGAEKEhwbHAsCMwsKqQ44mK1WMS6xNlguOk1MTjEhMVCYRh0xTJhEHS69kkkdNjJAQEIrLlckHSHFWoEAIfkECR4AXgAsAAABABAADwAAB3CAXoKDhIWGh4iJiouMjY6PiVg2LpQxVV44DAoKCQ89XiQuTkxNPCxeChEcGxwLAl4iRINSLl4LH4MYAV4nQINOtQsagxW7IkWDU8EmgxkAXjErQkBAMjVeDwwVFBUHCV5cLh0hHSRWXj0FAQIBCUeBADs=") +} + +html .menu .menu_logs:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAPcAACszOzM7Q9vb2+Pj49PT0ztDQzMzOwPDM+vr6/Pz8wuzM5ubm2Nja3uDgys7O2tzc0NDS8vLy4ODi2trc7Ozs7u7u1NTWzs7Q3t7g8PLy0tTU0tTW8PDywPLMwurOyNbOyNLOwujMx9XLx9fNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQEBAD/ACwAAAAAEAAQAAAIcgABADBAsKBBggIFVhBAoKFDAgIELDCQcEDCiwINELhoEWNCjRw9ftxYUWRGkgI7igRZ0iTLlCYHUgi5EgODCS09SkiAIMEFmB41IFgAgIMAoBg1DJgYoaNKjBgQ9NQg0GjEqxEgXGhw82OACwG+hvUYEAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAQAAQAIAAQAAAgUAAkIHCgQgMGDCBMCCOHBgwOEAQEAIfkEBAQA/wAsBgAAAAgABQAACB8ALxgIQDDAhQAEEioUsACAw4cIH0KM4CFERQ8OEAYEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAQAAAAKAAgAAAgyAC8YCBBgIMELAQgoXKhQwAIAECNCNEBAosSEIRQcUMBRAYiEFiNSDDmRQIiTDiRSDAgAIfkEBAQA/wAsBwAAAAYACAAACCYALxgIYODCBQIIEQoAwJBhgIYOFSg4oEDEQ4gXGwYIwTGEgwABAQAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwEAAAABwALAAAIJgANCBR4IQCBgwgJAFjIsGHDEAcURIzosCLDEBgzhrBoMYQIhwEBACH5BAQEAP8ALAUAAAAJAAsAAAhDAA0YuDDQQIALAQgoXCiAwAIAECMCuEBAYsSEBxRkPHBAxIUIFiEmDAkgYQgPIVKGcDAypAEKLjEw4HggBEQJCRAEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIfkEBAQA/wAsAAAAAAEAAQAACAQAAQQEACH5BAQEAP8ALAAAAAABAAEAAAgEAAEEBAAh+QQEBAD/ACwAAAAAAQABAAAIBAABBAQAIf8LWE1QIERhdGFYTVA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA2LjAtYzAwMyA3OS4xNjQ1MjcsIDIwMjAvMTAvMTUtMTc6NDg6MzIgICAgICAgICI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcERNPSJodHRwOi8vbnMuYWRvYmUuY29tL3htcC8xLjAvRHluYW1pY01lZGlhLyIKICAgIHhtbG5zOnN0RGltPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvRGltZW5zaW9ucyMiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ZmYyZWY0OTItM2JkZC05OTQ3LTk5YWMtMmQzMzI3MDVhMzIzIgogICB4bXBNTTpEb2N1bWVudElEPSJmZWI4MmYyNS1mZmZiLTNkYjgtY2EzMC0wNjE4MDAwMDAwM2YiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxYTVlZTUyOS03NWE4LTg0NDctODMzMi01NDRiZTAyMTFlYzUiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjItMTItMTVUMTc6Mzc6NTErMDg6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDIyLTEyLTE1VDE3OjM3OjUxKzA4OjAwIgogICB4bXA6Q3JlYXRlRGF0ZT0iMjAyMi0xMi0xNVQxNzozNzoyMSswODowMCIKICAgZGM6Zm9ybWF0PSLliqjnlLsgR0lGIgogICB4bXBETTp2aWRlb0ZyYW1lUmF0ZT0iMjUuMDAwMDAwIgogICB4bXBETTp2aWRlb0ZpZWxkT3JkZXI9IlByb2dyZXNzaXZlIgogICB4bXBETTp2aWRlb1BpeGVsQXNwZWN0UmF0aW89IjEvMSIKICAgeG1wRE06c3RhcnRUaW1lU2NhbGU9IjI1IgogICB4bXBETTpzdGFydFRpbWVTYW1wbGVTaXplPSIxIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9IjlkMWU4YTJjLWE4NGUtM2U1Ni04ZDI0LWRjZjEwMDAwMDA2YyIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMi0xMi0xNVQxNzozNzo1MSswODowMCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgQWRvYmUgTWVkaWEgRW5jb2RlciAyMDIwLjAgKFdpbmRvd3MpIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249ImNyZWF0ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MTJjMmZmZmItZGM3NC02ODQyLWIxOTEtMDhhMzgzM2RhMDgyIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE0OjUwOjAyKzA4OjAwIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmYxNDI3ZWYyLTQ1ZjEtMmY0Zi1hNDY0LTU4MzgyOGMxNjJlNyIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMi0xMi0xNVQxNDo1MjoxNCswODowMCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iL2NvbnRlbnQiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NTZjODkxYmQtMzQ4YS01OTQ3LTk2MDItMjFkM2JlZmRiZjAwIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE1OjQwOjE5KzA4OjAwIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvY29udGVudCIvPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJkZXJpdmVkIgogICAgICBzdEV2dDpwYXJhbWV0ZXJzPSJzYXZlZCB0byBuZXcgbG9jYXRpb24iLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NThkMTQ4M2QtZjAyMy02ZTQ4LWE0NTYtZDg3YTA1YWEzMWQ1IgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE1OjQwOjI2KzA4OjAwIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmRmMzhmYzNiLTE5ZGEtNGY0MC1iMjU2LWVhYzIxOWFjZGNmMiIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMi0xMi0xNVQxNjowMzo0NiswODowMCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iL2NvbnRlbnQiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NzZjOTFlOGYtZTkyZC1iMjQ1LWJkYWItODdiYThmMDU5ZjkwIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE3OjM3OjQ4KzA4OjAwIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvY29udGVudCIvPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiNDJmZDUyMi00NGM4LTdmNDUtYTJjNS1iMjU0ZDdiNDdjMDciCiAgICAgIHN0RXZ0OndoZW49IjIwMjItMTItMTVUMTc6Mzc6NDgrMDg6MDAiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MThjMjc4NjktMDAyMS04YzRmLThjMDMtMzVlZTU3MzAwZGQ5IgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE3OjM3OjUxKzA4OjAwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBBZG9iZSBNZWRpYSBFbmNvZGVyIDIwMjAuMCAoV2luZG93cykiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZmYyZWY0OTItM2JkZC05OTQ3LTk5YWMtMmQzMzI3MDVhMzIzIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTE1VDE3OjM3OjUxKzA4OjAwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBBZG9iZSBNZWRpYSBFbmNvZGVyIDIwMjAuMCAoV2luZG93cykiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii9tZXRhZGF0YSIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgIDx4bXBNTTpEZXJpdmVkRnJvbQogICAgc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2ZGUxZmEzNC0wNzNlLTJiNDgtYTNmYi0yY2Y3ZjFhMzczNjAiCiAgICBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjZkZTFmYTM0LTA3M2UtMmI0OC1hM2ZiLTJjZjdmMWEzNzM2MCIKICAgIHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxMmMyZmZmYi1kYzc0LTY4NDItYjE5MS0wOGEzODMzZGEwODIiLz4KICAgPHhtcE1NOkluZ3JlZGllbnRzPgogICAgPHJkZjpCYWc+CiAgICAgPHJkZjpsaQogICAgICBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjg1NmYwYzA0LWU0MzUtNzk0Ny1iMmQwLTkwMDliMjlhYzdmNiIKICAgICAgc3RSZWY6ZnJvbVBhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgIHN0UmVmOnRvUGFydD0idGltZTowZDc2ODAwMGYyNTYwMCIKICAgICAgc3RSZWY6bWFza01hcmtlcnM9Ik5vbmUiLz4KICAgIDwvcmRmOkJhZz4KICAgPC94bXBNTTpJbmdyZWRpZW50cz4KICAgPHhtcE1NOlBhbnRyeT4KICAgIDxyZGY6QmFnPgogICAgIDxyZGY6bGk+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24KICAgICAgIGRjOmZvcm1hdD0iYXBwbGljYXRpb24vdm5kLmFkb2JlLmFmdGVyZWZmZWN0cy5sYXllciIKICAgICAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NWI1MGU1ZTktMTRlMy0zZDQyLWEyMmMtMDY5NjNjNGY3ZWJjIj4KICAgICAgPGRjOnRpdGxlPgogICAgICAgPHJkZjpBbHQ+CiAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij7mt7HoibIg5ZOB6JOd6ImyIOe6r+iJsiAxPC9yZGY6bGk+CiAgICAgICA8L3JkZjpBbHQ+CiAgICAgIDwvZGM6dGl0bGU+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgIDwvcmRmOmxpPgogICAgIDxyZGY6bGk+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24KICAgICAgIGRjOmZvcm1hdD0iYXBwbGljYXRpb24vdm5kLmFkb2JlLmFmdGVyZWZmZWN0cy5sYXllciIKICAgICAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6N2U1M2FmMjctYzQzNS1iNjQ5LTk4MTAtNTYzYjE4ZmI5ZDI1Ij4KICAgICAgPGRjOnRpdGxlPgogICAgICAgPHJkZjpBbHQ+CiAgICAgICAgPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij7nn6nlvaIgODEucG5nPC9yZGY6bGk+CiAgICAgICA8L3JkZjpBbHQ+CiAgICAgIDwvZGM6dGl0bGU+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgIDwvcmRmOmxpPgogICAgIDxyZGY6bGk+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24KICAgICAgIGRjOmZvcm1hdD0iYXBwbGljYXRpb24vdm5kLmFkb2JlLmFmdGVyZWZmZWN0cy5jb21wIgogICAgICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4NTZmMGMwNC1lNDM1LTc5NDctYjJkMC05MDA5YjI5YWM3ZjYiPgogICAgICA8ZGM6dGl0bGU+CiAgICAgICA8cmRmOkFsdD4KICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPuWQiOaIkCAxPC9yZGY6bGk+CiAgICAgICA8L3JkZjpBbHQ+CiAgICAgIDwvZGM6dGl0bGU+CiAgICAgIDx4bXBNTTpJbmdyZWRpZW50cz4KICAgICAgIDxyZGY6QmFnPgogICAgICAgIDxyZGY6bGkKICAgICAgICAgc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1YjUwZTVlOS0xNGUzLTNkNDItYTIyYy0wNjk2M2M0ZjdlYmMiCiAgICAgICAgIHN0UmVmOmZyb21QYXJ0PSJ0aW1lOjBkNzY4MDAwZjI1NjAwIgogICAgICAgICBzdFJlZjp0b1BhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgICAgIHN0UmVmOm1hc2tNYXJrZXJzPSJOb25lIi8+CiAgICAgICAgPHJkZjpsaQogICAgICAgICBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjdlNTNhZjI3LWM0MzUtYjY0OS05ODEwLTU2M2IxOGZiOWQyNSIKICAgICAgICAgc3RSZWY6ZnJvbVBhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgICAgIHN0UmVmOnRvUGFydD0idGltZTowZDc2ODAwMGYyNTYwMCIKICAgICAgICAgc3RSZWY6bWFza01hcmtlcnM9Ik5vbmUiLz4KICAgICAgICA8cmRmOmxpCiAgICAgICAgIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6YWM5NGVmNzAtMmVkNC03YjQzLTg5YTktNzU0NTkwMjExOGVmIgogICAgICAgICBzdFJlZjpmcm9tUGFydD0idGltZTowZDc2ODAwMGYyNTYwMCIKICAgICAgICAgc3RSZWY6dG9QYXJ0PSJ0aW1lOjBkNzY4MDAwZjI1NjAwIgogICAgICAgICBzdFJlZjptYXNrTWFya2Vycz0iTm9uZSIvPgogICAgICAgIDxyZGY6bGkKICAgICAgICAgc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMjY3NDgyNS05MjE4LTQ1NDYtYjA4ZS01ZTI3M2QyNDAyMjgiCiAgICAgICAgIHN0UmVmOmZyb21QYXJ0PSJ0aW1lOjBkNzY4MDAwZjI1NjAwIgogICAgICAgICBzdFJlZjp0b1BhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgICAgIHN0UmVmOm1hc2tNYXJrZXJzPSJOb25lIi8+CiAgICAgICAgPHJkZjpsaQogICAgICAgICBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmNiYmJlOTY0LTM0MmYtYTM0ZC1iM2Q2LTI3ZGUyOTE3MWFhYSIKICAgICAgICAgc3RSZWY6ZnJvbVBhcnQ9InRpbWU6MGQ3NjgwMDBmMjU2MDAiCiAgICAgICAgIHN0UmVmOnRvUGFydD0idGltZTowZDc2ODAwMGYyNTYwMCIKICAgICAgICAgc3RSZWY6bWFza01hcmtlcnM9Ik5vbmUiLz4KICAgICAgIDwvcmRmOkJhZz4KICAgICAgPC94bXBNTTpJbmdyZWRpZW50cz4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgPC9yZGY6bGk+CiAgICAgPHJkZjpsaT4KICAgICAgPHJkZjpEZXNjcmlwdGlvbgogICAgICAgZGM6Zm9ybWF0PSJhcHBsaWNhdGlvbi92bmQuYWRvYmUuYWZ0ZXJlZmZlY3RzLmxheWVyIgogICAgICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphYzk0ZWY3MC0yZWQ0LTdiNDMtODlhOS03NTQ1OTAyMTE4ZWYiPgogICAgICA8ZGM6dGl0bGU+CiAgICAgICA8cmRmOkFsdD4KICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPue7hOWQiCAxNS5wbmc8L3JkZjpsaT4KICAgICAgIDwvcmRmOkFsdD4KICAgICAgPC9kYzp0aXRsZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgPC9yZGY6bGk+CiAgICAgPHJkZjpsaT4KICAgICAgPHJkZjpEZXNjcmlwdGlvbgogICAgICAgZGM6Zm9ybWF0PSJhcHBsaWNhdGlvbi92bmQuYWRvYmUuYWZ0ZXJlZmZlY3RzLmxheWVyIgogICAgICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpiMjY3NDgyNS05MjE4LTQ1NDYtYjA4ZS01ZTI3M2QyNDAyMjgiPgogICAgICA8ZGM6dGl0bGU+CiAgICAgICA8cmRmOkFsdD4KICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPuefqeW9oiA2NS5wbmc8L3JkZjpsaT4KICAgICAgIDwvcmRmOkFsdD4KICAgICAgPC9kYzp0aXRsZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgPC9yZGY6bGk+CiAgICAgPHJkZjpsaT4KICAgICAgPHJkZjpEZXNjcmlwdGlvbgogICAgICAgZGM6Zm9ybWF0PSJhcHBsaWNhdGlvbi92bmQuYWRvYmUuYWZ0ZXJlZmZlY3RzLmxheWVyIgogICAgICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpjYmJiZTk2NC0zNDJmLWEzNGQtYjNkNi0yN2RlMjkxNzFhYWEiPgogICAgICA8ZGM6dGl0bGU+CiAgICAgICA8cmRmOkFsdD4KICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPuefqeW9oiA4MC5wbmc8L3JkZjpsaT4KICAgICAgIDwvcmRmOkFsdD4KICAgICAgPC9kYzp0aXRsZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgPC9yZGY6bGk+CiAgICA8L3JkZjpCYWc+CiAgIDwveG1wTU06UGFudHJ5PgogICA8eG1wRE06dmlkZW9GcmFtZVNpemUKICAgIHN0RGltOnc9IjE2IgogICAgc3REaW06aD0iMTYiCiAgICBzdERpbTp1bml0PSJwaXhlbCIvPgogICA8eG1wRE06ZHVyYXRpb24KICAgIHhtcERNOnZhbHVlPSIyNSIKICAgIHhtcERNOnNjYWxlPSIxLzI1Ii8+CiAgIDx4bXBETTpzdGFydFRpbWVjb2RlCiAgICB4bXBETTp0aW1lRm9ybWF0PSIyNVRpbWVjb2RlIgogICAgeG1wRE06dGltZVZhbHVlPSIwMDowMDowMDowMCIvPgogICA8eG1wRE06YWx0VGltZWNvZGUKICAgIHhtcERNOnRpbWVWYWx1ZT0iMDA6MDA6MDA6MDAiCiAgICB4bXBETTp0aW1lRm9ybWF0PSIyNVRpbWVjb2RlIi8+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAOw==") +} + +html .menu .menu_set:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQAOYAAMfHx8XFxbm5ube3t7W1tbOzs7GxsQbROGqpeg7JPBDHPpWVlRLFPmylemCnchTDQBbBQFqnbBTBQFSpaBi/QEqrYpGRkRq9QlinbBa/QI+RkRi9QBi9Qh65RI+Pjxq7QkqpYhy5RIuPjRy5Qh63RCC1RIePiSKzRiSxRn6Rg4uLiyKxRiavSCitSCSvSImJiSqrSiatSIeHhyypSiirSCqpSiynSoWFhS6lTHyJfjCjTHyHfjKhTDKhToODgzSfTmaNcIGBgTadTjKfTjabUDSdTjqZUDiZUH5+fjyXUjqXUDqXUj6VUjyVUj6TVHx8fD6TUkCRVEKPVHp6ekKNVnJ8dECPVESNVkaLVkSLVnh4eEiJWEaJVkaJWEiHWEqHWHZ2dkyFWkqFWE6DWkyDWk6BWlB+XFB+WlB8XFJ8XFR6XlR6XFJ6XGR0aFR4XlZ4Xlp0YFh2XlZ2XlxyYFh0YFpyYFxwYlxwYF5uYmZmZgAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJDwB5ACwAAAEADwAOAAAHpoB5goN4Hxdwg4l5b2Z5W1JMUnlpb4lvJywzKHBzMDYkMJWCZS15Fj6CWgt5LGSJOi95WgYFSHkeNINzVCQyUwEyNwFINyFSc3kKTml5BKh5TwR5akYKyVuCA7Z5UwN5cFIMeXFRKDdBAEhIATdPIU6ieTwqeUEDAjJ5LzOJZvw3NwahOlFmkBoYmSi46gGjITODYfJgUVLmSh4yahQNgsMgQcZEgQAAIfkECQ8AeQAsAAABAA8ADgAAB7CAeYJ5c15pgjo2eIODeEIXLExJQh1mjIM/aXhGUXlRW4xzZVcsGj6CWggwMEeLJxdMGHlaBgVIeSJfJGR5NUp5MlMBMjcBSDdwLId5QlJ5BKd5TwR5PE6DUUx5A7d5UwPMRIJpNnk3QQBISAE3T3MwOHlbJWMmeUEDAjJ5E1IbM3nsCNHRocKNQTtKYBmzKM8iL+LGJJmzJd6lPGQeMHESpUWJIxcdtgCYx0yYhnkCAQAh+QQJDwB5ACwAAAAADwAPAAAHqoB5goN5c4SHhEIoUYiDb3lvNk4wW2UoSYQlEEYhZXlqLFg2KIQtWGkNPoJVC2SMg2FOL3laBgVIeQ4zhFI2MlMBMjcBSDdOJFh5RD+CBKp5TwSCNUlwM06CA7h5UwN5Ui1feU41RjdBAEhIATdAEHCEFBF5QQMCMnkpRPCDRmo3OQS1AZEGgo5BaQ4YoQIjTZ4eLZpQ8DRISY85ZWjAqCEITyNBTpKMORQIACH5BAkPAHkALAAAAQAPAA4AAAemgHmCg3gfF3CDiXlvZnlbUkxSeWlviW8nLDMocHMwNiQwlYJlLXkWPoJaC3ksZIk6L3laBgVIeR40g3NUJDJTATI3AUg3IVJzeQpOaXkEqHlPBHlqRgrJW4IDtnlTA3lwUgx5cVEoN0EASEgBN08hTqJ5PCp5QQMCMnkvM4lm/Dc3BqE6UWaQGhiZKLjqAaMhM4Nh8mBRUuZKHjJqFA2CwyBBxkSBAAAh+QQJDwB5ACwAAAEADwAOAAAHsIB5gnlzXmmCOjZ4g4N4QhcsTElCHWaMgz9peEZReVFbjHNlVywaPoJaCDAwR4snF0wYeVoGBUh5Il8kZHk1SnkyUwEyNwFIN3Ash3lCUnkEp3lPBHk8ToNRTHkDt3lTA8xEgmk2eTdBAEhIATdPczA4eVslYyZ5QQMCMnkTUhszeewI0dGhwo1BO0pgGbMozyIv4sYkmbMl3qU8ZB4wcRKlRYkjFx22AJjHTJiGeQIBACH5BAkPAHkALAAAAAAPAA8AAAeqgHmCg3lzhIeEQihRiINveW82TjBbZShJhCUQRiFleWosWDYohC1YaQ0+glULZIyDYU4veVoGBUh5DjOEUjYyUwEyNwFIN04kWHlEP4IEqnlPBII1SXAzToIDuHlTA3lSLV95TjVGN0EASEgBN0AQcIQUEXlBAwIyeSlE8INGajc5BLUBkQaCjkFpDhihAiNNnh4tmlDwNEhJjzllaMCoIQhPI0FOkow5FAgAIfkECQ8AeQAsAAABAA8ADgAAB6aAeYKDeB8XcIOJeW9meVtSTFJ5aW+JbycsMyhwczA2JDCVgmUteRY+gloLeSxkiToveVoGBUh5HjSDc1QkMlMBMjcBSDchUnN5Ck5peQSoeU8EeWpGCslbggO2eVMDeXBSDHlxUSg3QQBISAE3TyFOonk8KnlBAwIyeS8ziWb8NzcGoTpRZpAaGJkouOoBoyEzg2HyYFFS5koeMmoUDYLDIEHGRIEAACH5BAkPAHkALAAAAQAPAA4AAAewgHmCeXNeaYI6NniDg3hCFyxMSUIdZoyDP2l4RlF5UVuMc2VXLBo+gloIMDBHiycXTBh5WgYFSHkiXyRkeTVKeTJTATI3AUg3cCyHeUJSeQSneU8EeTxOg1FMeQO3eVMDzESCaTZ5N0EASEgBN09zMDh5WyVjJnlBAwIyeRNSGzN57AjR0aHCjUE7SmAZsyjPIi/ixiSZsyXepTxkHjBxEqVFiSMXHbYAmMdMmIZ5AgEAOw==") +} + +html .menu .menu_folder:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAPX19efn593d3dXV1dPT09HR0c/Pz83NzcvLy8nJycfHx8XFxcHBwb29vbm5ube3t7W1tbOzs7Gxsa+vr62traurq6mpqaenp6WlpaOjo6GhoQDZNp+fn52dnZubm5mZmZeXl5WVlZOTk4+Pj4WFhX5+fnx8fHp6enh4eHZ2dnJycnBwcGZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFDgAsACwAAAIAEAAMAAAGYEAWCHEohFjIpJJVGJlKD0oGQ8VoVEkCdslFajEMiuVCJlskjwTz1OgmSQsWQhRxIysg1mLysbMcKywKCih2J3UsAgZ+Hh1IABJ+ECVIARwlmJmZJAxJHkQHoaKhA3ksQQAh+QQJDgAsACwAAAAAEAAOAAAGGUCWULgpGo/DpHLJbDqf0Kh0Sq1ar9isMAgAIfkECQ4ALAAsAAABABAADQAABmZAllC4KRqPQhDiUAgNn8/CyFR6UDKYLEajGhK60LDwi2FQLJd02iJ5JFiFU0M8JC1YCFGELqyAWAsTH3wsDissCgoofCd7LAIGhB4dQgAShBAlQgEcJZ6fnyQMQx5LB6eopwN/LEEAIfkECQ4ALAAsAAACABAADAAABmNAFghxKGyOyCSLVRiZSg9KBkPFaFRLFgGb7Xq1KgyDYrmYzRbJI8E8Nb5d0oKFEEXg2QqItZh88EsOKywKCiiAJ3csAgaALB4dSwASjhAlSwEcJZucnCQMWR5EB6SlpAN7LEEAIfkECQ4ALAAsAAACABAADAAABlxAFghxKIRYyKSSVRiZSpuodIokqJbYpBXDoFguYLBF8kgwT41skrRgIUQRNbICYi0mHznLsWIpFChyJ3EsAgZ6Hh1IABJ6ECVIARwllJWVJAxJHkQHnZ6dA3UsQQAh+QQFDgAsACwAAAIAEAAMAAAGZUAWCHEohFjIpJJVGJlKD0oGQ8VoVEkClrXper9ILYZBsVzOZ4vkkWCeGsslacFCiCJxZQXEWkw+eUkOKywKCiiBLCd4LAIGiSweHUgAEpAQJUgBHCWdnp4kDEkeRAemp6YDfCxBACH5BAkOACwALAAAAgAQAAwAAAYUQJZwSCwaj8ikcslsOp/QqHQqDAIAIfkECQ4ALAAsAAACABAADAAABlxAFghxKIRYyKSSVRiZSpuodIokqJbYpBXDoFguYLBF8kgwT41skrRgIUQRNbICYi0mHznLsWIpFChyJ3EsAgZ6Hh1IABJ6ECVIARwllJWVJAxJHkQHnZ6dA3UsQQAh+QQJDgAsACwAAAIAEAAMAAAGY0AWCHEobI7IJItVGJlKD0oGQ8VoVEsWAZvterUqDINiuZjNFskjwTw1vl3SgoUQReDZCoi1mHzwSw4rLAoKKIAndywCBoAsHh1LABKOECVLARwlm5ycJAxZHkQHpKWkA3ssQQAh+QQJDgAsACwAAAEAEAANAAAGZkCWULgpGo9CEOJQCA2fz8LIVHpQMpgsRqMaErrQsPCLYVAsl3TaInkkWIVTQzwkLVgIUYQurIBYCxMffCwOKywKCih8J3ssAgaEHh1CABKEECVCARwlnp+fJAxDHksHp6inA38sQQAh+QQJDgAsACwAAAAAEAAOAAAGZkCWULgpGo/DpHIJQhwKoaWyMDKVHpQMZovRqIaEr3QZxjAolotabZE8EqzCqTEWkhYshChSZ1VALAsTH30OKywKCih1J3wsAgZ9Hh1CABJ9ECVCARwlnp+fJAxDHk4Hp6inA4AsQQA7") +} + +html .menu .menu_exit:hover { + background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAOPj4+Hh4dnZ2dXV1c3NzcvLy8fHx8PDw8HBwb+/v729vbu7u7W1ta2trampqaenp6Ojo6GhoZ2dnQzNOgjPOpeXlxq9Qhi9QI+Pj4uLi4mJiSatSCqpSjCjTDSfToGBgTadTjKfTjibUDqZUDqXUDyVUkCRVESNVnZ2dkyFWnR0dE6BWlB+XFB8XFR4Xlh2XlxwYF5uYmZmZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCAAyACwCAAEACwAOAAAGQMBGgUAsEgyoAEMSaTYlgwzgI6taZYkKQHO1ZlDb7jUsrpLLZ3G6ux5zxRgwVayoBBQPh17/EGQgCQiCgwcLKkEAIfkEBQgAMgAsBwAHAAEAAgAABgRA0ykIACH5BAUIADIALAgABwABAAIAAAYEwI4oCAAh+QQFCAAyACwJAAcAAgACAAAGBkCPhzQKAgAh+QQFCAAyACwLAAcAAQACAAAGBEDPKAgAIfkEBQgAMgAsDAAHAAIAAgAABgZA0KbECQIAIfkEBQgAMgAsDAAFAAIAAgAABgZAVypmCQIAIfkEBQgAMgAsDgAGAAIAAwAABgfAlYySmqyCACH5BAUIADIALAwACQADAAEAAAYFQNiFFQQAIfkEBQgAMgAsDAAKAAIAAQAABgTAFysIACH5BAkyADIALAIAAQAOAA4AAAYUQJlwSCwaj8ikcslsOp/QqHRqDAIAOw==") +} + +.pos-box { + height: 50px; + width: 100%; + box-shadow: 0 3px 2px 0 rgba(0, 0, 0, 5%) !important; + border-radius: 4px; +} + +.conter-box{ + box-shadow: 0 1px 2px 0 rgba(0,0,0,.1); +} + +.position { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAKwwAACsMBNCkkqwAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAFZSURBVDiNpdO9a1VBEAXw3zOioIUQEdFGkQdWVtr6D1ikERtRppLU6awUsUkh2CgoBNwhoPhsLESxERR7wcI2XSBNMAHRNFmLbB6Xa64RXFjYOXvmzMfOjmqt/mcd2I+QmUuZOcgbDWWQmTN4iFms4wGu4jC+RMT7wQwy8xBe4FtE3MBXPMdqE5sfLCEzj+Il3kXEE4iIJTzFFbzB9z0FMvMYJphExLPuXUQs4y1eYfmPHmTmbHN+FBGv92zMDm+ulXAzItZHtVaZeRH3sBgRn4ecOyKXcRt3dkuYwyJOZeaJfZyP42TjX1Nrne5SyqSUMu5ho559tpQy2bUP9gL8wHYnzbtYzczTuB8RHzs8/H0Sz+ETbuEDxg3f7pL6AhW/2vknNiNiCxvY6uDT8e2XMINLmbmC89js3I0z8wLOdP36Aht43KIcwULD1+w82/WGT+dk8DP96/oNlqecb6uu8YEAAAAASUVORK5CYII="); + background-position: 17px 14px; + background-repeat: no-repeat; + background-size: 20px auto; + line-height: 50px; + padding-left: 30px; +} + +.search { + width: 306px; +} + +.ser-text { + border: #20a53a 1px solid; + height: 30px; + width: 250px; + padding: 0 8px; + margin-top: 10px; + outline: none; +} + +.ser-text:focus { + border-color: #20a53a; +} + +.ser-text:focus + span.glyphicon-search { + color: #20a53a; +} + +.ser-sub { + width: 38px; + height: 30px; + border: 0; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAxAAAAMQBz4pYTAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAE2SURBVDiNndJBS1VRFMXx9UzNQUQD0caFk6YhEUFCOPETiJQfIygQbNQgCCeOGjYLGjRJkPoKoSGIRKADs1nQoEIEfw3cry6v+3oPN2z2ufuu8z/7nruC9ORNvMRXHOIFbrTogjQfOniKX87iR2P9HSv/A1zA+xK/wRwmMYV5vKt3r/sBnpTgcb9R8aw0D3sB1/GzTum3uZsfSnupCVgr8vwQgKXSLnR7I0nuJJFkN4PjY9WZbmMkyXGtx4YAjFY9aQI2knSS3B4CMFd1508HV3CE3QHfP17G2sdo7298UJfzChMtmy9jszTLbT4InpfgCx6Vme5hFd/8jW1cbQME93Hg39jDYmOKT5hGOmi7rNkk15KcJvmcZKv6E0k2k9xNsp1keZBx2vIi3tYkG+cBxJmV13HrN6Szg7ZCfX3NAAAAAElFTkSuQmCC") no-repeat center #20a53a; + cursor: pointer; + margin-right: 2px; + margin-top: 10px +} + +.title { + height: 50px; + line-height: 50px; + border-bottom: 1px solid #eee +} + +.title h3 { + display: inline-block; + line-height: 50px; + margin-right: 12px +} +.tablescroll { + overflow: auto; + border: #e6e6e6 1px solid; +} +.divtable .table { + border: 1px solid #ddd; + color: #666; + font-size: 12px; + margin-bottom: 0; +} +.divtable{ + position: relative; +} +.table-fixed-box{ + overflow-y:auto; +} +.ui-selectable{ + padding-bottom: 0; +} + +.divtable .table thead th { + vertical-align: inherit; + background-color: #f6f6f6; + border-bottom: 1px solid #e6e6e6; + color: #666; + font-weight: normal; + padding: 8px; +} +.divtable .table_toolbar{ + left: 8px; + bottom: 6px; + position: absolute; +} +.table .btlink{ + word-break: break-word; +} +.table .btlinkbed { + color: #666; + padding: 1px 5px; + border: 1px solid transparent; + white-space: nowrap; + overflow: hidden; + float: left; + display: block +} + +.table .btlinkbed:hover { + border: #ddd 1px solid; +} + +.dataTables_paginate { + display: block; + height: 35px; + margin-bottom: 0px; + margin-top: 5px; + position:relative; +} + +#filePage{ + margin-bottom: 10px; +} + +.page { + /* line-height: 16px; */ + text-align: right; + padding: 8px 0; + /* margin-right: 1px; */ + height: 44px; + /* border: 1px solid #e6e6e6;*/ + border-top: none; + margin: 0; +} + +.site_type { + height: 30px; + line-height: 30px; + position: static; + margin-top: 12px; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +} + +.site_type span{ + display: inline-block; + height: 30px; + line-height: 30px; + padding: 0 10px; + font-family: "Arial"; + font-size: 13px; + color: #666; + float: none; + border: 1px solid #ececec; + border-right: none; + /* border: 1px solid #ececec; */ +} + +.site_type select{ + border-radius: 0px; +} +.site_type_table{ + padding: 15px; + min-height: 100px; +} +.edit_site_type{ + padding:0 15px; +} +.edit_site_type .line .info-r{ + margin-left:0px; +} +.edit_site_type input{ + width: 80% !important; +} +.edit_site_type .btn{ + margin-right: 0px; +} +.page div { + float: right +} + +.page span, +.page a { + display: inline-block; + height: 28px; + line-height: 28px; + padding: 0 10px; + font-family: "Arial"; + font-size: 13px; + color: #666; + float: none; + border: 1px solid #ececec; + border-right: none; +} + +.page a.prev { + border-left: #ddd solid 1px +} + +.page a:hover { + background: #e8e8e8; + color: #20a53a; + text-shadow: 0 1px 0 #fff; + cursor: pointer +} + +.page spanold { + font-size: 12px; + padding: 4px 10px!important; + border-style: solid; + border-width: 1px; + border-color: #ddd #ddd #ccc; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + display: inline-block; + line-height: 16px; + background: #f5f5f5; + color: #333; + text-shadow: 0 1px 0 #fff +} + +.page .Pcurrent { + color: #fff; + background: #20a53a; + cursor: default; + border-color: #20a53a; +} + +.page .Pcount { + color: #333; + padding: 0 10px; + margin-right: 0px; + border-right: 1px solid #ececec; +} + +.system-info-con { + width: 100% +} +.sys-li-box{ + height:100px; + background-color:#f9f9f9; + border:#f0f0f0 1px solid; + margin-left:20px; + max-width:230px; +} +.sys-li-box .name{ + line-height:40px; + margin-top:8px; +} +.sys-li-box .val a{ + font-size:26px; + font-family:arial; +} +.sys-li-box .val span{ + font-size:26px; + font-family:arial; +} +.sys-i-c-box { + box-sizing: border-box; + max-width: 350px +} + +.sys-i-c-box .siteinfo-box { + height: 110px; + position: relative; + width: 100% +} + +.sys-i-c-box .siteinfo-box h3 { + background: #20a53a; + color: #fff; + font-style: normal; + height: 110px; + left: 0; + line-height: 110px; + position: absolute; + text-align: center; + top: 0; + width: 56px +} + +.siteinfo-box-right { + border-bottom: 1px solid #ddd; + border-right: 1px solid #ddd; + border-top: 1px solid #ddd; + height: 110px; + margin-left: 56px; + overflow: hidden +} + +.siteinfo-box-right h4 { + font-size: 32px; + height: 68px; + line-height: 60px; + overflow: hidden; + padding-top: 8px; + text-align: center +} + +.siteinfo-box-right .sbr-btn { + color: #ccc; + display: block; + font-size: 14px; + height: 27px; + line-height: 22px; + padding-top: 5px; + text-align: center +} + +.siteinfo-box-right .sbr-btn a { + color: #20a53a; + font-size: 16px; + padding: 0 10px +} + +.circle-box h3, +.circle-box h4 { + line-height: 30px +} + +.mw-stat-item { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 6px; + text-align: left; + justify-content: flex-start; +} + +.mw-stat-progress { + width: 100%; + display: block; +} + +.mw-stat-progress mdui-linear-progress { + width: 100%; + height: 10px; + border-radius: 999px; + background: var(--mw-surface-container); + --mdui-linear-progress-height: 10px; + display: block; + opacity: 1; +} + +.mw-stat-value { + font-size: 20px; + font-weight: 600; + text-align: left; +} + +.mw-stat-status { + line-height: 22px; + text-align: left; +} + +.mw-stat-item .mask { + position: static; + width: auto; + height: auto; + border-radius: 0; + background: transparent; + line-height: normal; + font-size: 20px; + color: inherit; +} + +.mw-disk-progress { + margin: 0; +} + +.diskbox .mask { + position: static; + width: auto; + height: auto; + border-radius: 0; + background: transparent; + line-height: normal; + font-size: 20px; +} + +.mw-mem-release-btn { + margin-top: 6px; +} + +.mw-note-board { + display: flex; + flex-direction: column; +} + +.mw-note-preview { + min-height: 240px; + padding: 12px; + border: 1px dashed var(--mw-border); + border-radius: 8px; + background: var(--mw-surface-container); + color: var(--mw-text); + line-height: 1.6; + white-space: normal; +} + +.mw-note-preview ul, +.mw-note-preview ol { + padding-left: 20px; +} + +.mw-note-preview h1, +.mw-note-preview h2, +.mw-note-preview h3, +.mw-note-preview h4 { + margin-top: 12px; + margin-bottom: 8px; + font-weight: 600; +} + +.mw-note-editor { + display: flex; + flex-direction: column; + gap: 12px; +} + +.mw-note-input { + resize: vertical; + min-height: 240px; + font-family: inherit; + line-height: 1.6; +} + +.mw-note-actions { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.mw-note-footer { + display: flex; + justify-content: flex-end; + padding-top: 0; +} + +.circle { + width: 100px; + height: 100px; + position: relative; + border-radius: 50%; + background: #20a53a; + margin: 0 auto +} + +.pie_left, +.pie_right { + width: 100px; + height: 100px; + position: absolute; + top: 0; + left: 0 +} + +.left, +.right { + width: 100px; + height: 100px; + background: #ccc; + border-radius: 50%; + position: absolute; + top: 0; + left: 0 +} + +.pie_right, +.right { + clip: rect(0, auto, auto, 50px); + transition: transform 1s ease-in 0s; + -webkit-transition: -webkit-transform 1s ease-in 0s; + -moz-transition: -moz-transform 1s ease-in 0s +} + +.pie_left, +.left { + clip: rect(0, 50px, auto, 0); + transition: transform .4s ease-in 1s; + -webkit-transition: -webkit-transform .4s ease-in 1s; + -moz-transition: -moz-transform .4s ease-in 1s +} + +.mask { + width: 88px; + height: 88px; + border-radius: 50%; + left: 6px; + top: 6px; + background: #FFF; + position: absolute; + line-height: 88px; + font-size: 18px; + color: #20a53a +} + +@-webkit-keyframes shineGreen { + from { + -webkit-box-shadow: 0 0 10px #999 + } + 50% { + -webkit-box-shadow: 0 0 15px #20a53a + } + to { + -webkit-box-shadow: 0 0 10px #999 + } +} + +.shine_green { + -webkit-animation-name: shineGreen; + -webkit-animation-duration: 3s; + -webkit-animation-iteration-count: infinite +} + +.mem-release { + cursor: pointer +} + +.mem-re-min { + background: url(/static/img/ico/rocket_min.png) no-repeat center center; + height: 35px; + left: 35px; + position: absolute; + top: 2px; + width: 20px; + z-index: 9; + opacity: .9; +} + +.mem-re-con { + background: url(/static/img/ico/ico-rocket.gif) no-repeat center center; + width: 100%; + height: 100%; + position: absolute; + z-index: 9; + display: none; + top: 15px +} + +.mem-release:hover .mem-re-con { + display: block +} + +.soft-man .col-lg-3 { + border-bottom: 1px solid #ececfb; + border-left: 1px solid #ececfb; + border-right: 1px solid #ececfb; + margin-right: -1px; + margin-bottom: -1px; + height: 148px; + cursor: pointer; + padding: 0 +} + +.soft-man .col-lg-3:hover { + box-shadow: 0 0 38px rgba(0, 0, 0, .08) inset; + -webkit-transition: all .25s ease; + transition: all .25s ease +} + +.soft-man .dashed-border { + border: 1px dashed #20a53a; + z-index: 1 +} + +.soft-man .no-bg:hover { + box-shadow: 0 0 38px rgba(0, 0, 0, .01) inset; + -webkit-transition: all .15s ease; + transition: all .15s ease +} + +.soft-man .image { + height: 40px; + margin: 30px 0 20px; + text-align: center +} + +.soft-man .sname { + text-align: center; + color: #999 +} + +.soft-man .col-sm-3>div { + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 0 +} + +.soft-man .spanmove { + background: url(/static/images/move.png) no-repeat; + width: 30px; + height: 30px; + display: none; + position: absolute; + top: 5px; + right: 5px; + z-index: 3 +} + +.soft-man .col-lg-3:hover .spanmove { + display: block +} + +.soft-man .text { + background-color: rgba(0, 0, 0, 0.3); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + display: none +} + +.soft-man .text a { + background-color: #fff; + border-radius: 4px; + display: block; + height: 30px; + line-height: 30px; + margin: 59px auto; + text-align: center; + width: 100px +} + +.soft-man-con p.status { + line-height: 40px; + margin-bottom: 10px; + font-size: 14px +} + +.soft-man-con .sfm-opt .btn { + margin-right: 15px +} + +.soft-man .sname .glyphicon-True { + color: #20a53a; + margin-left: 10px +} + +.soft-man .sname .glyphicon-False { + color: red; + margin-left: 10px +} + +.soft-man { + position: relative +} + +.soft-man .rowbg { + position: absolute; + top: 0; + width: 100%; + z-index: 0; +} + +.soft-man .rowbg .col-lg-3 { + cursor: default; +} + +.soft-man .rowbg .col-lg-3:hover { + box-shadow: 0 0 38px rgba(0, 0, 0, .01) inset; + -webkit-transition: all .15s ease; + transition: all .15s ease +} +.bw-info{ + height:80px; +} +.bw-info > div{ + float:left; + padding-top:25px; + text-align:center; +} +.bw-info > div p{ + margin-bottom:7px; +} +.bw-info > div a{ + font-size:14px; + color: #666; +} +.bw-info .ico-up{ + width:14px; + height:8px; + background-color:#f7b851; + display:inline-block; + margin-right:3px; +} +.bw-info .ico-down{ + width:14px; + height:8px; + background-color:#52a9ff; + display:inline-block; + margin-right:3px; +} +.bw-info ul li { + height: 56px; + line-height: 56px; +} + +.bw-info ul li.bi-line { + border-bottom: #ddd solid 1px; +} + +.bw-info ul li span { + display: block; + margin-right: 15px; +} + +.btn-zhm { + border-radius: 0 3px 0 0; + display: none; + position: absolute; + right: 42px; + top: 0; +} + +.ellipsis_text { + width: 220px; + text-overflow: ellipsis; + overflow: hidden; +} + +.ssl_state_info{ + padding: 15px 30px; + background: #f5faf2; + margin-top: 5px; + border-radius: 4px; + border: 1px solid #EAF1F2; + font-size: 12px; + position: relative; +} + +.ssl_state_info .state_item{ + width: 49%; + height: 25px; + line-height: 25px; + display: inline-block; + vertical-align: top; +} + +.ssl_state_info .state_item span{ + height: 25px; + line-height: 25px; + vertical-align: top; +} + + +.ssl_state_info .state_item span:nth-child(1){ + font-weight: bold; + color: #666; +} + + + +.ssl_state_info .state_item .switch{ + display: inline-block; +} + + +.ssl_state_info .state_item .btswitch-btn{ + margin-bottom: 0; + height: 1.9rem; + width: 3.2rem; + position: relative; + top:2.5px; +} + +.custom_certificate_info{ + margin-top: 15px; +} + +.custom_certificate_info .state_item{ + width: 48%; + height: 50px; + display: inline-block; + margin-right: 4%; +} +.custom_certificate_info .state_item:nth-child(2){ + margin-right: 0; +} + +.custom_certificate_info .state_item span{ + width:100%; + margin-bottom: 5px; +} + +.custom_certificate_info .state_item textarea{ + width: 100%; + height: 250px; + line-height: 25px; +} + +.line { + padding: 5px 0 +} + +.line .span_tit { + display: inline-block; + text-align: right; + width: 64px +} + +.line .tname { + display: block; + float: left; + height: 32px; + line-height: 32px; + overflow: hidden; + padding-right: 20px; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + width: 100px +} + +.line .info-r { + margin-bottom: 5px; + margin-left: 100px; + position: relative +} + +.placeholder { + cursor: text; + left: 20px; + line-height: 24px; + position: absolute; + top: 27px +} + +.bt-form-submit-btn { + background: #f6f8f8; + border-top: 1px solid #edf1f2; + bottom: 0; + left: 0; + padding: 8px 20px 10px; + position: absolute; + text-align: right; + width: 100% +} + +.bt-form-submit-btn .btn:first-child { + margin-right: 4px +} + +.btn-danger { + background-color: #cbcbcb; + border-color: #cbcbcb; + color: #fff +} + +.bt-w-main { + height: 555px +} + +.bt-form { + height: 100% +} + +.bt-w-menu { + float: left; + background-color: #f0f0f1; + height: 100%; + width: 110px +} + +.bt-w-menu p { + cursor: pointer; + height: 40px; + line-height: 40px; + padding-left: 20px; + position: relative; + text-overflow: ellipsis; + overflow: hidden +} + +.bt-w-menu p a { + display: block +} + +.bt-w-menu p .spanmove { + display: none +} + +.bt-w-con { + margin-left: 110px; + position: relative +} + +.explain-describe-list{ + border: 1px solid #ececec; + border-radius: 6px; + margin: 0 auto; + margin-top: 20px; + margin-bottom: 20px; + background: #f7f7f7; + padding: 15px; + list-style-type: inherit; +} + + +.agreement-box label{ + font-weight: 400; + margin: 0 0 0 5px; + vertical-align: middle; + cursor: pointer; +} + +.agreement-box input{ + height: 15px; + width: 15px; + vertical-align: middle; + margin-top: 0; + cursor: pointer; +} + +.label-input-group input { + margin-top: 0; + margin-right: 5px; + vertical-align: -2px +} + +.label-input-group label { + margin-bottom: 0; + font-weight: normal +} + +.bingfa .line { + margin-bottom: 10px +} + +.bingfa .line .span_tit { + text-align: right; + width: 120px; + display: inline-block +} + +.bingfa .bt-input-text { + width: 100px +} + +.ssl-con-key { + width: 47% +} + +.ssl-con-key textarea { + height: 240px; + line-height: 18px; + width: 100%; + margin-top: 5px +} + +.ssh-item { + float: left; + margin-left: 10px; + padding-top: 5px +} + +.ss-text em { + color: #555; + float: left; + font-style: normal; + line-height: 32px; + padding-right: 2px +} + +.view1, +.view2 { + margin-bottom: 15px; +} + +.searcTime { + position: relative:z-index:999; + margin-top: 11px +} + +.searcTime .tit { + float: left; + padding: 5px 10px; + display: none +} + +.searcTime .gt { + padding: 5px 10px; + border: #ddd 1px solid; + margin-right: -1px; + cursor: pointer; + float: left; + line-height: 16px +} + +.searcTime .gt.on, +.searcTime .gt:hover, +.searcTime .ss .st:hover, +.searcTime .ss .st.on { + background-color: #20a53a; + color: #fff +} + +.searcTime .ss { + display: inline-block; + position: relative; + float: left; + line-height: 16px; +} + +.searcTime .ss .st { + padding: 5px 10px; + border: #ddd 1px solid; + margin-right: -1px; + cursor: pointer; + float: left; +} + +.searcTime .ss .time { + background-color: #fff; + border: 1px solid #ddd; + display: none; + padding: 10px; + position: absolute; + right: -1px; + top: 27px; + width: 306px; + z-index: 99; +} + +.searcTime .ss .time span { + margin-bottom: 10px; + display: block; +} + +.searcTime .ss .time input { + border: 1px solid #ddd; + height: 22px; + padding: 0 5px; + margin-left:5px; + width: 227px; +} + +.searcTime .ss .time input:focus { + border: #66afe9 1px solid; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); +} + +.searcTime .ss .sbtn { + background-color: #20a53a; + border: 1px solid #20a53a; + color: #fff; + line-height: 24px; + text-align: center; + cursor: pointer; +} + +.plan { + padding: 10px 20px; + margin-bottom: 10px; +} + +.plan .typename { + line-height: 34px; + width: 120px; + font-size: 12px; +} + +.plan .textname { + height: 34px; + line-height: 34px; + float: left; +} + +.plan .planname input { + height: 34px; + width: 260px; + padding: 0 12px; +} + +.plan .dropdown button b { + font-weight: normal; + font-size: 12px; +} + +.plan .dropdown-menu { + min-width: 100px +} + +.dropdown-menu>li>a { + padding: 3px 12px +} + +.plan-submit { + margin-left: 141px +} + +.plan_hms { + position: relative; + height: 34px +} + +.plan_hms span { + float: left; + height: 32px; + line-height: 32px; + position: relative +} + +.plan_hms .name { + border-left: 1px solid #ccc; + width: 44px; + text-align: center; + background-color: #f6f6f6 +} + +.planSign { + margin-left: 10px; + height: 35px; + line-height: 35px +} + +.planSign i { + margin-right: 8px; + font-size: 12px; + font-style: initial; + color: red +} + +.plan_hms span input { + float: left; + width: 48px; + height: 32px; + line-height: 32px; + border: 0; + text-align: center; + font-size: 12px +} + +.plan_hms span:first-child input { + border-radius: 4px 0 0 4px +} + +.plan_hms span:last-child { + border-radius: 0 4px 4px 0 +} + +.plan .dropdown button { + width: 94px +} + +.txtsjs { + width: 450px; + height: 80px; +} + +.sl-s-info { + width: 210px; + border: #ccc 1px solid; +} + +.important-title { + display: flex; + color:red; + background-color: #fef3e2; + line-height: 46px; + margin-bottom: 15px; + padding-left: 10px; + box-shadow: 0 1px 2px 0 rgba(0,0,0,.2); +} + +.important-title>p { + flex: 1; +} + +.rec-box-con .onekey { + font-family: "宋体"; + width: 90px; + height: 30px; + line-height: 30px; + text-align: center; + background-color: #20a53a; + border-radius: 3px; + color: #fff; + margin: 20px auto 15px; + cursor: pointer; +} + +.rec-install { + padding: 16px; + width: 100%; + float: left; +} + +.rec-install .rec-box { + width: 290px; + float: left; +} + +.rec-install h3 { + font-size: 20px; + margin-bottom: 5px +} + +.rec-box-con { + border: #ddd 1px solid; + border-radius: 3px; + padding: 10px +} + +.rec-install .rec-box:hover .rec-box-con,.rec-box-con-hover { + background-color: #f9f9f9; + border: #20a53a 1px solid +} + +.rec-box-con .ico img { + width: 22px; + margin-right: 10px +} + +.rec-box-con ul li { + line-height: 26px; + margin-bottom: 5px +} + +.fangshi { + line-height: 19px; + margin-top: 10px; + display: block; + margin-bottom: 15px +} + +.fangshi label { + font-weight: normal; + margin-right: 44px; + float: right +} + +.fangshi label input { + vertical-align: -2px; + margin-left: 5px +} + +.zun-form-new .fangshi { + padding: 0 30px 0; + line-height: 30px +} + +.zun-form-new .fangshi label { + height: 20px; + line-height: 20px; + margin: 4px 20px 0 0 +} + +.zun-form-new .version { + padding: 0 30px +} + +.zun-form-new .version select { + height: 28px; + margin-left: 30px; + width: 120px +} + +.sec-install-btn { + font-family: "宋体"; + width: 90px; + height: 30px; + line-height: 30px; + text-align: center; + background-color: #20a53a; + border-radius: 3px; + color: #fff; + cursor: pointer; + margin-top: 10px +} + +.rec-box-con .onekey { + font-family: "宋体"; + width: 90px; + height: 30px; + line-height: 30px; + text-align: center; + background-color: #20a53a; + border-radius: 3px; + color: #fff; + margin: 20px auto 15px; + cursor: pointer +} + +.rec-box-con .onekey:hover, +.sec-install-btn:hover { + background: #10952a; + background: linear-gradient(#10952a, #088d22) +} + +.phppz { + margin: 14px 0 +} + +.phppz span { + padding: 10px +} + +.phppz textarea { + width: 500px; + margin: 0; + vertical-align: bottom; + padding: 0 5px; + height: 38px; + margin-right: 20px +} + +.soft-man-con .user_pw { + display: none; + margin-top: 30px +} + +.soft-man-con .user_pw_tit { + margin-top: 30px; + padding-top: 30px; + border-top: #ccc 1px dashed +} + +.soft-man-con .user_pw p { + margin-bottom: 15px +} + +.soft-man-con .user_pw_tit .tit { + float: left; + line-height: 22px +} + +.soft-man-con .user_pw span, +.soft-man-con .user_pw_tit span { + width: 75px; + display: inline-block; + text-align: left; +} + +.soft-man-con .user_pw span { + margin-right: 10px +} + +.btswitch-p { + margin-left: 10px +} + +.soft-man-con .user_pw input { + width: 200px +} + +.soft-man-con .conf_p { + margin-bottom: 10px +} + +.soft-man-con .conf_p input { + width: 100px +} + +.soft-man-con .funarea { + width: 100%; + height: 80px; + line-height: 22px; + margin-bottom: 10px +} + +.soft-man-con .ver .btn { + vertical-align: 0; + margin-left: 10px +} + +.webEdit-box .soft-man-con .phpmyadmindk { + width: 100px; + display: inline-block; + margin: 0; + padding-right: 0 +} + +.softlist td img { + width: 24px; + height: 20px; + margin-right: 5px +} + +.softlist .glyphicon-folder-open { + cursor: pointer; + color: #efd566 +} + +.softlist .btswitch+.btswitch-btn { + width: 2.4em; + height: 1.4em; + margin-bottom: 0 +} + +.wafConf_checkbox label { + font-weight: normal; + margin-right: 20px +} + +.wafConf button { + vertical-align: 0 +} + +.wafConf_cc { + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + margin: 10px 0; + padding: 15px 0 +} + +.wafConf_cc span { + margin-right: 5px +} + +.wafConf fieldset { + border: 1px solid #ccc; + border-radius: 3px; + float: left; + padding-bottom: 0; + width: 240px +} + +.wafConf fieldset:nth-of-type(2) { + margin: 0 10px +} + +.wafConf legend { + border: 0 none; + font-size: 14px; + margin: 0 6px; + padding: 3px; + width: auto +} + +.wafConf fieldset input { + margin-left: 4px +} + +.wafConf fieldset .table { + margin-top: -1px; + margin-bottom: 0 +} + +.wafConf fieldset .table tr td:nth-of-type(2) { + width: 42px +} + +.wafConf fieldset .table-overflow { + height: 210px; + overflow: auto; + margin-top: 10px; + border-top: #ddd 1px solid +} + +.wafConf-btn { + border-bottom: #ddd 1px solid; + margin-bottom: 12px; + padding-bottom: 15px; + height: 45px +} + +.wafConf-btn span { + float: left; + margin-right: 8px; + line-height: 33px +} + +.wafConf-btn .btn { + margin-right: 10px +} + +.wafConf-btn .ssh-item { + margin-right: 50px +} + +.gzEdit { + padding: 13px 20px +} + +.gzEdit button { + margin: 8px; + width: 140px +} + +.setting-con p { + line-height: 30px +} + +.setting-con p .set-tit { + display: inline-block; + height: 22px; + margin-right: 10px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: -2px; + width: 90px +} + +.setting-con p .disable { + background-color: #ededed +} + +.setting-con p .inputtxt { + width: 260px +} + +.setting-con p .modify { + margin-left: -40px; + vertical-align: 0; + position: relative; + z-index: 10; +} + +.setting-con p .set-info { + margin-left: 10px; +} + +.set-submit { + margin: 20px 0 10px 100px; +} + +.changepath { + height: 500px; +} + +.changepath .path-top { + height: 50px; + line-height: 50px; + padding-left: 10px; + border-bottom: #aaa 1px solid; +} + +.changepath .path-top .btn { + margin-right: 10px +} + +.changepath .path-top .btn span { + -moz-transform: scaleX(-1); + -webkit-transform: scaleX(-1); + -o-transform: scaleX(-1); + transform: scaleX(-1); +} + +.changepath .path-top .place { + display: inline-block +} + +.changepath .path-top .place span { + color: #444; + font-size: 12px; + font-weight: bold +} + +.changepath .path-con-left { + width: 130px; + height: 450px; + float: left; + border-right: #aaa 1px solid; + padding-top: 5px +} + +.changepath .path-con-left dl dt { + background: url("../img/ico-computer.png") no-repeat left center; + height: 30px; + line-height: 30px; + padding-left: 23px; + font-size: 14px; + font-weight: normal; + margin-left: 10px +} + +.changepath .path-con-left dl dd { + line-height: 30px; + padding-left: 12px; + cursor: pointer +} + +.changepath .path-con-left dl dd span { + color: #666 +} + +.changepath .path-con-right { + float: left; + height: 450px; + overflow: auto; + width: 520px +} + +.changepath .path-con-right .default li { + width: 25%; + float: left; + text-align: center; + margin-top: 20px; + cursor: pointer +} + +.changepath .path-con-right .default li span { + font-size: 40px; + color: #666 +} + +.path-con-right .file-list { + display: none +} + +.path-con-right .list-list td { + padding-left: 10px; + cursor: pointer +} + +.list-list span.glyphicon { + color: #666; + margin-right: 10px; + font-size: 18px +} + +.list-list span.glyphicon-folder-open { + color: #edca5c +} + +.list-list span.glyphicon-cloud-download { + font-size: 12px +} + +.newFolderName { + border: #ccc 1px solid; + height: 30px; + padding: 0 5px; + width: 90% +} + +.path-con-right .list-list td .delfile-btn { + display: none; + float: right; + font-size: 14px; + text-align: center; + width: 18px +} + +.path-con-right .list-list td .delfile-btn:hover { + color: red +} + +.path-con-right .list-list tr:hover .delfile-btn { + display: block +} + +.path-con-right .list-list td .btn-xs { + margin-top: 4px +} + +.getfile-btn { + background: #f6f8f8 none repeat scroll 0 0; + border-top: 1px solid #edf1f2; + padding: 8px 20px 10px; + text-align: right; + width: 100% +} + +.warning_info { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; + height: 40px; + line-height: 20px; + padding: 10px 15px; + border-radius: 4px; +} + +.success-msg { + width: 100%; + padding: 30px 50px; + font-family: "Microsoft Yahei"; + float: left; + position: relative +} + +.success-msg .pic { + position: absolute; + top: 50%; + margin-top: -100px +} + +.success-msg .pic img { + width: 158px +} + +.success-msg .suc-con { + float: right; + width: 260px +} + +.success-msg .suc-con h3 { + font-size: 16px; + margin-bottom: 15px +} + +.success-msg .suc-con p { + line-height: 23px +} + +.success-msg .suc-con .p1 { + border-bottom: 1px dashed #aaa; + color: #000; + height: 30px; + margin-bottom: 8px +} + +.success-msg .bottom-btn { + margin-top: 60px; + width: 100%; + float: left +} + +.success-msg .bottom-btn a { + width: 200px; + height: 40px; + line-height: 40px; + text-align: center; + display: inline-block; + border-radius: 5px; + margin-left: 30px; + color: #fff; + font-size: 16px; + cursor: pointer +} + +.success-msg .bottom-btn .close-btn { + background-color: #cbcbcb +} + +.success-msg .bottom-btn .blue-btn { + background-color: #5cb85c +} + +.tasklist { + padding: 15px +} + +.tab-nav { + border-bottom: #cacad9 1px solid +} + +.tab-nav span { + background-color: #ddd; + background: -moz-linear-gradient(top, #f6f6f6, #ddd); + background: -webkit-gradient(linear, 0% 0, 0% 100%, from(#f6f6f6), to(#ddd)); + background: -ms-linear-gradient(top, #f6f6f6, #ddd); + height: 32px; + line-height: 32px; + padding: 0 12px; + border: #cacad9 1px solid; + color: #444; + display: inline-block; + margin: 0 -5px -1px 0; + cursor: pointer +} + +.tab-nav .on { + background: #fff; + border-bottom: #fff 1px solid; + color: #444 +} + +.tab-con { + overflow: auto; + padding: 10px +} + +.tab-con ul.cmdlist { + list-style-type: decimal +} + +.tab-con ul.cmdlist li { + position: relative; + list-style-type: decimal; + list-style-position: inside; + line-height: 40px; + border-bottom: #dbdbea 1px solid; + margin-top: 6px +} + +.tab-con ul.cmdlist li .com-progress, +.tab-con ul.cmdlist li .state, +.opencmd { + float: right; + margin-left: 20px; + color: #535362 +} + +.tab-con ul.cmdlist li .line-progress { + position: absolute; + bottom: -1px; + left: 0; + height: 1px; + background-color: #20a53a +} + +.tab-con ul.cmdlist li .cmd { + border: 0 none; + border-radius: 0; + display: block; + width: 570px; + height: 200px; + line-height: 22px; + padding: 0 10px; + background-color: #333; + color: #eee; + overflow: auto +} + +.yuandian { + width: 10px; + height: 10px; + border-radius: 5px; + background-color: #20a53a; + z-index: 999999 +} + +.DrawRecordL i, +.DrawRecordR-T i { + background: url(/static/img/Detailsbg.png) no-repeat +} + +.DrawRecordCon { + font-size: 14px; + height: 422px; + overflow: auto +} + +.DrawRecord { + background: url(/static/img/DrawRecordord.png) repeat-y -204px center; + margin: 0 auto; + overflow: hidden; + line-height: 22px +} + +.DrawRecordlist { + padding-top: 6px +} + +.DrawRecord .DrawRecordL { + float: left; + color: #9a9a9a; + margin-top: 9px; + padding-right: 22px; + position: relative; + text-align: right; + width: 110px +} + +.DrawRecord .DrawRecordL i { + background-position: 0 0; + display: inline-block; + height: 5px; + overflow: hidden; + position: absolute; + right: 0; + top: 9px; + width: 16px +} + +.DrawRecord .DrawRecordR { + margin: 1px 1px 30px 112px; + color: #666; + padding-right: 5px +} + +.DrawRecord .DrawRecordR h3 { + font-size: 14px; + font-weight: normal; + margin-bottom: 5px +} + +.footer { + bottom: 0; + position: absolute; + text-align: center; + width: 100%; + line-height: 50px +} + +.bingfa { + padding: 10px 0 60px 0 +} + +.bingfa p { + margin-bottom: 10px +} + +.bingfa p .span_tit { + display: inline-block; + text-align: right; + width: 120px +} + +.bingfa p input { + border: 1px solid #ccc; + height: 30px; + line-height: 30px; + padding-left: 5px; + border-radius: 3px; + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s +} + +.bingfa p input:focus, +.bingfa p input:active { + border: #66afe9 1px solid; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +.bingfa .submit-btn { + background: #f6f8f8 none repeat scroll 0 0; + border-top: 1px solid #edf1f2; + bottom: 0; + left: 0; + padding: 8px 20px 10px; + position: absolute; + text-align: right; + width: 100% +} + +.bingfa .submit-btn .btn-danger { + margin-right: 5px +} + +.transfer .backupdata { + height: 150px +} + +.selectdata .slabel { + font-weight: normal; + margin: 10px 41px 10px 0 +} + +.selectdata .slabel input { + margin-right: 5px; + margin-top: 0; + vertical-align: -2px +} + +.backupbtn { + margin: 20px 0; + width: 100% +} + +.neice_con { + padding: 7px 20px 20px +} + +.neice_con .tit { + font-weight: bold; + line-height: 30px; + color: #777 +} + +.neice_con .nc_con { + background-color: #fdfdfd; + border: 1px solid #ddd; + line-height: 28px; + padding: 10px; + border-radius: 3px +} + +.nc_opt { + padding-top: 6px; + text-align: right; + height: 24px +} + +.neice_con .nc_opt input { + margin-right: 6px; + vertical-align: -2px +} + +.neice_con .nc_opt label { + float: right; + width: 150px +} + +.nc_con_user p { + margin: 15px 0 +} + +.nc_con p span { + width: 80px; + display: inline-block +} + +.nc_con p input { + background-color: #fff; + border: #ccc 1px solid; + height: 30px; + line-height: 30px; + padding: 0 5px; + width: 280px +} + +.nc_con p input.disabled { + background-color: #eee +} + +.nc-tips { + padding: 20px +} + +.nc-tips p { + line-height: 30px +} + +.nc-tips p span { + font-size: 14px; + font-weight: bold +} + +.nc-tips p a { + color: #20a53a +} + +#BarTools .glyphicon { + color: #666 +} + +#BarTools .glyphicon:hover { + color: #fff +} + +.list-list .ico { + background-position: center center; + background-repeat: no-repeat; + display: inline-block; + height: 30px; + margin-right: 10px; + width: 33px; + z-index: 1; + float: left +} + +.fileList .ico { + background-position: center center; + background-repeat: no-repeat; + display: block; + height: 80px; + margin: 12px auto 4px; + width: 84px; + z-index: 1 +} + +.list-list .column-name .text { + color: #595c5f; + display: inline-block; + max-width: 85%; + overflow: hidden; + text-overflow: ellipsis; + line-height: 30px +} + +.list-list .column-name .cursor { + display: inline-block; + cursor: pointer; + width: 200px\9 +} + +.table>tbody>tr>td { + vertical-align: middle; +} + +.showpicdiv { + max-height: 400px; + overflow: hidden; + width: 400px; +} + +#BarTools { + font-size: 16px; + float: left; + margin-right: 20px; +} + +#BarTools>button { + top: 0; +} + +.comlist { + float: left; + line-height: 28px; + margin-right: 10px +} + +.comlist>span { + margin-right: 20px; + font-size: 14px; + float: left; + padding: 0 10px; + border: #fff 1px solid; + border-radius: 3px; + cursor: pointer +} + +.comlist>span:hover { + border: #ccc 1px solid; + border-radius: 3px; + background-color: #f3f3f3 +} + +.PathPlaceText { + position: absolute; + right: 0; + top: 0; + background-color: #fff +} + +.PathPlace { + color: #666; + float: left; + font-size: 16px; + line-height: 30px; + margin-right: 5px; + width: 170px\9; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.PathPlaceInfo { + font-size: 14px; + line-height: 30px; + float: left; + margin-right: 20px; + color: #999 +} + +.onPath { + float: left; + line-height: 30px; + font-size: 14px; + color: #666 +} + +.editmenu span { + display: none; + text-align: right; + width: 300px +} + +.table tr.hover, +.table tr.on, +.ui-selecting, +.ui-selected { + background-color: #f5f5f5 +} + +.table tr.on .editmenu span, +.table tr.ui-selected .editmenu span, +.table tr.ui-selecting .editmenu span, +.table tr:hover .editmenu span { + display: inline +} + +.ui-selectable-helper { + background: #6bb0c9; + border: 1px dotted #072246; + opacity: .25; + overflow: hidden; + position: absolute; + z-index: 99999 +} + +.list-list .ico-folder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAMpJREFUeNpi/P//P8NAAiaGAQYD7gAWdIFPpxIoMS8ZiOcQqfY5SD0LFT1jAsRTuaU0GZjZuAgq/vPto+S3V3fmUssBokC8llNEjp2ZFWjk/1+Eg56TE0RJsgCD3BPImAvikGs7IzMLAxuvKAMrNz9ZaWAul5iKJAsXPwUBAMzK//9AaDIcIAkODiKCbbQcGHXAqANGHTDqgFEHjDpg1AGjDqCVA57/+f6Z7hZD7XwBahOmfHv1ANQqlqCzG54CcRrjaOd0oB0AEGAAscwsxMSUtNsAAAAASUVORK5CYII=") +} + +.list-list .ico-folder-unempty { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQRJREFUeNpi/P//P8NAAiaGAQYD7gAWcjR9OpWASyoZiOcQacxzkHoWKnrGBIincktpMjCzcRFU/OfbR8lvr+7MZSQnEbIzosbc65NxokDqDKeInBwrNz/xIfnwMgPjx5PxnkD2XCCWJNfrjMwsDGy8ogzs/CKkRSXQAaAomMslpiLJwsVPQegDQ/H/HwhNRiKUZOHkZPjx4xvD799/6ZLyWVmZGTjYWVBzAchybsUKujjg6/0OuAMGvBwAJcL/A14Q8SkYD4jlnx6chaaB/79GK6NRB4w6YNQBow4YdcDIdsDfP98/0d1iqJ1/QdVx47dXDxsGIDRADdBGxtHO6UA7ACDAAIASR69Q0kB9AAAAAElFTkSuQmCC") +} + +.list-list .ico-access, +.list-list .ico-mdb, +.list-list .ico-accdb, +.list-list .ico-db, +.list-list .ico-sql { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAwZJREFUeNrMV0trE1EU/jKJkzTTR4oJCX1lqq6q+GwriiLarS4VfCwqFEFxKShYUKE+Kv4GcVkquBE3glq0Vqt9iGg3qfZhSVttU0tNJk2aGc+9dGSSJmlaJqkfHG7OvZOc757v3HMnFk3TsJGwGZ2hoaEjNNwnazQxxh6yT8aJurq69AQIHT5CSUmJKZEDgQAbHpOdTCWhQ0jxfZIkQVVVU4zB7/c/XyaxOxcCpkMUxUtEojsTCaEQhUYkmmVZHkhHwpbPwKWlpXodMJxaHgfJLOsiEB4eRqi7B+HvI1CjUUQnp/i8o7ICgihC2rYVmw8fgrNW5vMejwderzddYa49A7NvujHzsgvlBw+gnIJYHXbYfT6+Fg0GocbiiAx/w/jDR/CdOA5XYz1f04sxpz6QDTMvXqHy7Gk4t9SuWHNUVPDRKft5NqafPvtHYE2NKBvUpSVYbDaEet5B2CRCS5BvoRoWBKjxGKx2OxKLi7C73dDo2XV1wmxQxsZRVFPNLRsSigJlYsJ8Aq76fZjvH4CtrIzLYBGSTzDbdYSKMzo1BVdDg/kEfvf1Q758EbFfM5j/2M/TzqRgEiSUCEnggJNOgaO6CsGOTvhbzptLgOnPdil63NwyShAOr8iOSRLsJQkGYS2WYKX7wkFHUKCjqOu+OP0TSwsLiM+G1iRBzlTn3n+Aa38DP2oMf6ihzHa95sa0Z5CoNtgzc7295mdAl4Dt3klWcAk8TUcRaLvLOyHLgoVaLxs16nTK+A/empXRMd4n3E3HzCdQde4MIiOjCL3twWQfOwVx7rPdFvlrIFAjYneAfKGF+6YT4K2WAugXjVkoyPvAf00gSYJQ602EChH1SWfmGrDa8vqShETKTZkx2s57t/H52vUkXwebN/pG6N/R143PGn8vp1Ogk0hHJlc/dS1vRcgCZdp1pmyZSoAFMu40VxJJEgjFxQlNUay72u+sSOFqNbDa7tln9keYxUi6Y4z/jvvaH7TGvny9pYbDeekPgiSp4o7tN+qvXmlLS2Aj8FeAAQC23WzgeNtzYAAAAABJRU5ErkJggg==") +} + +.list-list .ico-c { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFuKz0K4QDTjbbc0N4qrvP7+p0yABWY5sKTqf7mmAHMuHVy5ATg9k5wJ+VoZ/n/79Z2JkZCQqyElNhKCOMB8Lwz+cDjAVYGw69f5P3ac/xIWMy4rLJPkW6MG/QDuaUeqXge6eAwQYABvvATpIEEu4AAAAAElFTkSuQmCC") +} + +.list-list .ico-cpp { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFuxZaFeIBpzttuYGSWIo1fef36RnQ5ChyIYh85EtxidG1TRAyFCy0wA5ABbk6CFErENZyLUUPbjJDSEUB/CzMvz79O8/EyMjI4aBhBIXMQDUEeZjYUBppTIi947LdlypP/X+f92nP7QpH4Ae/GsqwNjc5aHTiNUBAwEAAgwALWAgpic4qfUAAAAASUVORK5CYII=") +} + +.list-list .ico-cs { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoFJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFvxZaFeIBpzttuYGhjiyGNbq+89v8rMhyBJ0S0F8ZHF0NTRNAzCLYI4gJgRolgiRHUE3B8AsRPY9qY5gRO6cOq64/PfTPxYmRkZGmiRCkF28jH/+HYjUZcaaCE0FGJtOvf9T9+kP7pBxWXGZJHFkwM/K8BdoRzPOEBgIABBgAMrJHxYdaBS2AAAAAElFTkSuQmCC") +} + +.list-list .ico-fla, +.list-list .ico-flv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYFJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDkV3BAwwofEluLm5Gf79+0cVDALy8vK7oI4wIMYBVAdsbGxZQEccweUIJnokNKAjEhQUFM5hcwQLLS3m4+ODpQMQCIPS54GYkS4OEBUVZRAXF8eWMMkLgZf1fQx/37yD85lFhBjEG4sYPizfCOYLRPpj6IElRqLKAWIAyBIuG1OqhRITwwCDAXcAyVEAim9YnLNrKDMI5ybS1wGjaWDA0wAu8O3IaTCGAVDaAKURQoARuUECbA/8V1ZWpqmP7969C2oPMI6mgVEHDM5syNI+k+EhkXn0DyUWLp6I2w5cliq1laDw71X1gMVANDa12MSxOZykggiXoXRLAyCfoYcELt8TUkfTECDWcqo3StHjHl9awB4F3Fx//5NhKbE+/g+1A2dldGPCrBqm6/caGb59Z6JJNuTi/PdPU6leoyCtBasDBgIABBgAWP2ttPWI30cAAAAASUVORK5CYII=") +} + +.list-list .ico-css { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAALdSURBVFiF7ZfLTxNRFMZ/M/RBnwik0FYpoJIYhJioLFSIupCoWzVqotGNCeEPcGPiI+50Q9xAYli5MaEbFyTiI0EWhhghGkohlgVREVzQhpfals64qB067ZRpoaALv6Rp73dPzvnuud/cOxVkWeZvwpA+CAaDZ4BewFPEGoeBkXSisbFRWwDQ6/V6PTabrSiVQ6EQQB9woaGhYUQrRswYeywWC5IkFeUD4PP5BoC+UCh0KB8BRYfZbO6ora19A/i1RGy5AACTyXS9rq5uWEtEpgeKCqfTmfIBwKU/3+8BYVsEuFwuqqurVVyaoK0XAChmzIVNe0CS4drrGeLSxg60ggRMRKJMLcRU3Mf5X+yvMGMUBaYWYkxEosUXEEvIdAfC9IxHKDeXqOYGPi9zqsYOQLm5hJ7xCN2BMLFEfh3RFRAIR+kcmqXKaqCr1U1l6ZqAuCQTDEc5UFkKQGVpCV2tbqqsBjqHZgmE9buha8Jbw9/pOeHFY80OfTv3kyNuC6KwxgnAud1OjrqtdAx+49lZ37r5dTtQYzfSP72kabKXX5Zp99mz+Lgk0z+9RI3dqJdeX8CjNg8Ok0jn0KzKYMtxibkfq+xxmlTxE5HkljlMIo/a9C9V3S0QBbi4t4xjbisPP8xzo7GcpgozgzMrHPdaVbGBcJTHwQh3W1zstOmvHgp4DHfZjXS1uql3JBO/+rqiuD+FekcyJt/iUOBJKAA2Y1JzV6s7az41Vwi25Tb8L+CfFqAy4ZWRBIx8UsaiIX835wNpNQ7A6NpLcfZToFX0xfl9yu92/+SGedFgVETkFKBVPDN5u3+yYD4XhPR/RgefjMmZHVgvQWqlmfO5eEhuw+jVZuX62pQJ2/2Tyqrz4bVQsIBU0sx91uNzQbUFJ5+OJRYlgygIgiqoWCaUZRmHsCoNXm5W3mpUAm4+D9x5F5FvL65uzflQZiTRskO4/+B00z1NAQqZ0YFiI73mb2NmSwowsJSgAAAAAElFTkSuQmCC") +} + +.list-list .ico-js { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAALFSURBVFiF7ZfLTxNRFIe/GTotffJKoa1SQCUx1cSFslBLdKFNdKtGFya6MSH8CybG6NJNwwYS496EblywwEeCLAwx1mgshVgWREVwQRteamk746K2dNopM5WCLvwlTXvPPTn3u+f+5lFBURT+pkylg3g8fgF4BHjruMYJIFoaCAQC2gDAI5/P57Xb7XVZOZFIAIwCV3p7e6NaOWLZ2Gu1WpFluS4fAL/fPw6MJhKJ40YA6i6LxTLQ1dX1EohoQew6AIDZbL7Z3d09pQVR7oG6yuVyFXwAcO339xtA2BMAt9tNR0eHKlYCtPsAQNGM1bRjD8gK3HixQEb+sxtaTQAzqTRzK5uq2PvlnxxptSCJAnMrm8yk0vUH2MwpDMeSjEynaLE0qObGP61zvtMBQIulgZHpFMOxJJs5Yx3RBYgl0wxOLtJuMxEOemhr3ALIyArxZJpjbY0AtDU2EA56aLeZGJxcJJbU74auCW9PfWPkrA+vrTL11dIPTnqsiMJWTAAuHXBxymNjYOIrTy76t62v24FOh8TY/JqmyZ59Xifkd1TEM7LC2PwanQ5Jr7w+wFC/F6dZZHByUWWw9YzM0vcsB11mVf5MKn9kTrPIUL/+Q1X3CEQBrh5q4rTHxoN3y9wKtHC01cLEwgZnfDZVbiyZ5mE8xd0+N/vs+ruHGi7D/Q6JcNBDjzNf+PmXjaL7C+px5nOMLg413gkFwC7lmcNBT8V8Ya4W7cnT8D/APw2gMuH1aA6iH4tj0WTczUYkZzMAvN16Ka68Cowu+vTyYUKR2eLvggoxLYkmqQhRFaBWlYJojfVUFw8Y7YCWdtyBWo+hXDV1oHSR8lgoMlvz7qGsA00S8qqsiIIgVMuvgAhFZg3vXlEUXCZUb6kqgL5m4d7rVPbOala7M+cef9AsXC1eriaJXF+zcL80Jhj5e75dR7aTkdq/AFCYGAYKWHWoAAAAAElFTkSuQmCC") +} + +.list-list .ico-htm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFtQstCtEg8FtzQ04GxsAycPkYGqR1SOL/fvzm7IGCbJjkA2maRoAWYbL97hCglgHMhHra2r4li65ABYKxDoYxQH8rMAmHg07qyCz+VgYUFqpjMgWlu24Un/q/f+6T39oUz4APfjXVICxuctDpxGrAwYCAAQYAPufFoHFXwuVAAAAAElFTkSuQmCC") +} + +.list-list .ico-html { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnpJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFkgW2hWiweC25gacjQ2A5GFyMLXI6mHyyHL//vwmLxsiOwbZQJqmAZBluHyPKyRIdSATIYOp6Vua5gJs8U2yA/hZgU08GnRWYVEJMpuPhQGllcqIbGHZjiv1p97/r/v0hzblA9CDf00FGJu7PHQasTpgIABAgAEAooAb7fMLYAoAAAAASUVORK5CYII=") +} + +.list-list .ico-java { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAvRJREFUeNrUV0toE1EUPfPJTJNp82lNY1PaBGwtpn6q9bNQQRFFpSAKgrgQu3PpXlBEBMGNWwU3LtwoxYULhSoKgkJbawVDta3WT5XWEPpL2iSTifeNjqY6TeNkavDA5c375N1z7z3v8cLlcjmUE2J+JxqNHqDmOlmdjT42k/XlD0QiEXMCzHkwGKxTFMUWz0NDQ6y5RXa0ubm5z2wN/1u/zul0QtM0W4yhsbHxPiNBZNqLIWAZI+PzpuOyLJ8KhUKP6fO2GQlbCLyPpfAhll50XpKkk+Fw+JkZCbFU56qWw72BKRxq9/0x53a7DR0wHPvR9pJxthCYSmbR1RPHnlY32GEei6cRrJZ+7u73+xEIBMyEWXoJBt4ncfXBOLavrkLv2wSVIIX6POcGzIRZMoGZ+Szu9MbRuasW/aMJ+BQB25oqLQViiYDIcwj7ZfirRF0D6az129QSAafEY+uqSnyZzKBjow89IwlEx+b+HQGGNfVOVDg4uJ0C2kIuVMr88hNgwnv+LoHYjKr3fYoIjlS3b71Hzwq7jNJqDk9ez+Bu/yS0IipT9DFkG8dnVYxPZ/DyYxJM0FnyUOHg4RA5NNRIlAkFEn3vaKnCtYcTiH5KYm2Dyx4CbOPddN5Hv6b0aAMex4J5Rkbgvx9ClURpCNW2DBiYoAw8fTMLmerPiLhkQXfICO7f4NXHWIlO7FwBUeDsIZBMaxj8PId6n6Srn1kheFyCnhHbCDyKTuMFXThNKyvgdYkUNdWdNmcOHMKvtCdSml4iNn94S/Xfv4gWw8E2L/au8+jpj02rmJrLIp5QkSFhZsixTOmXSYw1dDF1bPLqp8PSk6wQWKSsBMzsBI8yo+wEFpTgUjddXd3Dy+70RqRABiRZXmCXO1tt7f/fGrh4vAlnbg7rrYH8vjFfaH3JGSi0mZX14lLRFtM3oix2/aIZUCT2btSW/JFVsL1d5CN/jMv/d3yl69W5wQmcTaaXR5wUYLalFhdOH2k9b0qgHPgmwABqjFw0GGyiFAAAAABJRU5ErkJggg==") +} + +.list-list .ico-log { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkLQSevnqLU05aTBivXlhiJKocINcSSgATwwADiqOA0pAbjYLRKBiNgtEoGHXA4MoFE+fsooulM/twt4pBzScMDY3VmQz1rdMxxGAAWQ6XOAz8+vWL9GxIyEEwPi7xAUsDhCynqQNAvkeODro7gBjfY6QBLk62f79+/2MCAqw+ghkMi290y9DFsTVQQXYgizEi946nzllbf+f+y7pv33/RJGS4ONn/qiiKNWenBDdidcBAAIAAAwBHPa+btF94QQAAAABJRU5ErkJggg==") +} + +.list-list .ico-mht { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57Tw5AImFtQstCtEA0y7rbmBVQzEhsnBxNEBst5/f37TtkGC7BhkiylKAzCf4jIYJIbL93RJhCAHEeNbihyAHt80yYb8rMAmHg07qyCz+VgYUFqpjMgWlu24Un/q/f+6T39oUz4APfjXVICxuctDpxGrAwYCAAQYAIH+FoEeflonAAAAAElFTkSuQmCC") +} + +.list-list .ico-php { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA1ZJREFUeNrsVktPU0EUPn3dPq73QmmbFlRaAw1Q5KUiYCIkmmjUGEMUdGWMSze6cmViTFwYVv4Aw8KNRhe6kA0SAonRGGNBHpWkBAXFIFAeLS307ZyB1ku90EJvdeOXTO7c6XS+b8755tyRxeNx+JdQCl9cLlcLeXSQdlRCjjrSBoUDDodDXADBUwsBx3GSMLvdbnw8J63NbrcPis2Rp7xbWJaFWCwmSUNYrdZuFEHE1GYiQHIwDHODiHizlQj53zAaEXHNZrM5xUQoc0nM83zCB4j2jecAabJdCVgLx8D5xQ9j06swOR+CGDnCkWgcWLUCrCY1VO3XQlWxDhTy9fVNJhOYzWYxY+48Akj8eniZkOjgdE0+mHgVMMp1In8wBnPeMJ3TM+KFC0f0YLdo6G8JM2ZUB7ZCn8tLCW6esYBG9ds27pk1sns5FOkZYEkEbKShmGfvPOBbjcKhA2zatdOacGI2SMIdhLZGwyZyBI5j+IVAQVebjfB+fIWK3lElFEM/2f3Zunzax0WnF0IgI5E/V6eHmaUwdA8tQa2N1A5S0Ycm/RAhET9RycOp6jzo/+yDSw0F2UVgZjkM5jwV7X+dC0JLBQ+lZg18J0ICJNwXGwzwaTJAhVXs1dL8D5H3YqMapkiEsk6B8GPl8UVAv0cJPxZDNNScVgFoeJVCRsONXvjmCdHxTJFWgJFTUUKaL0L08sMC5OmUEIrEwWpkqOlYzfoyvaNeGJ4KQH0JS71TSARl7YGTB3l45VyC9sYCKCvS0BQkaoKJV9LdN9k56B1ZhvOH9clj2eVchCvHDNlHoITku5Sc6SdvPVBWqE2O44nAhkUH09G8IQzT9Kh3Fo6XczQlWUcAga42kNx39s3SAlNtZYHTyJMEaEDfWhS6Bvy0QrbW66G8SLvzC8l2qLHqwLFPS3P8cWIF5slOkRhNiLnGyohV8nKTIVmKJRWAwHxjdcukwkl2CnKN/wI2eeBBD6l6PeM5J33s2MaEjFr9xx86rpcm+7c7x5NjqX2xeakIBYO7SwEumEqSzTxJ7oSZkkgiIEEmDK0wBdvNk0RApgtmOk/UAywD0XSXyGyAayOHcEwmvHA8fDF6Z+wn3AuEclMfdAzEys1w91Zr5X1RAf8CvwQYAB0rcvQMbg3OAAAAAElFTkSuQmCC") +} + +.list-list .ico-url { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAr5JREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUJLi/n4+GDpAATCoPR5IGakmgP+/P/LsP/FJYZzb+8wMDEyMchyizD4yVgwcLGwM4iKijKIi4tjS5jkh8DLHx8Ytj45xXDj4xOG59/fMfCwcDBYi2kxBMpZMajxSYPVgMRBDgABWGIkqhwgBO5+fs7QcWUVw4+/vxn42bgZtATkGAwElRjWPDzCsPf5RYZcTT8GIyFlBklOIfIKInwAZOnE6xsZ/gB9FKfszOAqaYgoWIAOmXlrG8P0m1sYuoyTGQTZeIh2ANG54Oirqwzvf31hiFC0R7EcnNe5xRhKtUMY/gGL9d3PzpNfFOMN/i8vGJR5JYHBfZhhyb19eNQ9J8kBRIeAOIcAgzA7Lzgq8IGfBOTJDgE9QUVwYmszTABnNWoBokPg+OvrYHrD42NY5S+9v88Qe6SH4dy7u7RxAMgCHQF5hlNvboFzw+sfH8Hiv/79AWbBC2AxDX5ZcDakSRR8+v2dockglmHu7Z0Mx4ChcebtbXBh8+3PT7A8yHHZGr6UNUjwATEOfgYWRmaGTHVvBlMRNYbTwJB4+u0tg7iAANByBQZHCT3KW0T4QISCHcPBl5eBiVGBwUBIicFEWJX6TTJ8ABS/IExtQJf2wKB2AEoUTHixg4HhBe0tnYXUKiYqBNqdMrDyQTQyxqWeau0BbKBy3wwUi5H5Qy8NkAOQg5tU3w+KKEBxADcT+9+v/34yY7OEVJ/C1KOrBdmBzGdE7h1PPbGm5vaPF43f/v2iSdrgYmL7p8ohUZ9tEdKC1QEDAQACDADzFSd59WqSKgAAAABJRU5ErkJggg==") +} + +.list-list .ico-xml { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnhJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gIUp4F/wHIsfu9Tht//yCvQSHLA9fc/Ge58/IUidvHtDwZtIXYGViZGsBxIDdUd8Ovvf4bpV94xzLj6nkGQnRlFbuejLwyusjxgNkgOpAakFqSHKg648u4nQ9ah5wxiXCwME2wkGIQ5EA4ABfs1oLy+MAeYD5IDqQGpBekB6SWpLsAGqk+8ZJjhIMUgyYWp9NiL7wyWEpwMTIwIMRAzWImPwUqCiyHjwDOGjV5ylIWALA8rw9YHn7Emst2PvzC4yfFgiIPUgvSA9FIcBZNsJRl42ZjAQYqcwL78/sfw4tsfBmU+NoyEClIL0gPSS3EUgII3XIWfwRoYpN0X3jKkagky6ABT/YGnXxnspbgw0svsa+8ZGkxFGaS5WambDWWAwQlKYIq8EIP3PPkKT/0wAJIDqSHWcpJLQlAC42aFuBlkETqAydGsIKIFGHXAgDsAJRHGnP0LbL/eorml57TwhAATCysK3hOhi5UPorHJIcujmwXCJEeB25obDLtCNMBsEA3i0z0NwByBbjk1HDc0cgHMdzDfEhM6ZDmAnxXYxEPrrCIbjssR+ByOrB5kNh8LA0orlRHZwrIdV+pPvf9f9+kPbaIG6MG/pgKMzV0eOo1YHTAQACDAAOGRHaFJlGXVAAAAAElFTkSuQmCC") +} + +.list-list .ico-ai { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAg9JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7DQ0mI+Pj5YOgCBCCh9BogZ6eIAAQEBsCMYGRnh+OHDh7SLgvdXNjK82N8K57OysjIwMzMTXw4QC96dX87w7elRBin3HgYmVg64uKCOP5D0R7WAhYXh79+/OM0iOQT+/f4BtpyJjZfh65MzROkBhQITExM4Cih2AMhSNkFVBl5Vb4bvz1DLljen5oCjARuApQGKHQCylFPKmIFTQofh1/vbDL8/vSBaL8UOAFkGspRbxoSBhVOAgUPMgOHbi8sUJVySHACyDGQpLOGxi2owfH2wj3q1IcH4B1r279dnhidbclCj5cU1YJRo0dYBIEtAAD3rgbLk9+cXyXYA0VEAsoRDVA/FchDglNQHZ0tQ9iQHMCK3iIANkv/Kyso0rZju3r0LapAw0rU2pFouGHUAzVvFLEfSGR4eoYOFWodxhwA7C3FYLWweXj4uPDzSgHzQPIaH65LA9IAlQmpYTlaTDOZ7XHzKQoCV798/GvZVwWaz8v7DWRfc2Fxbz/TmbB3D78+0SZxsfH//CRs1a/g2N2J1wEAAgAADAIk82p3e8s5qAAAAAElFTkSuQmCC") +} + +.list-list .ico-bmp { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmtJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7anvAKhs1Sx6biiIO46OLIetDVvPt708yygE0S5D5xOjDp4eoNAAyAIaRDUL2KSkOo1sIYIsmqldGsFDAZgkxDkVxABcT2z9gxcPEzMiEMyGSE8zIlRrIDpTGDXLveOqJNfW3f7yo+/bvF03KB24m9r8qHOLN2RYhjVgdMBAAIMAA9C4L2PHEWagAAAAASUVORK5CYII=") +} + +.list-list .ico-cdr { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmRJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7Cr/WPhHObj44H0OMGHFk8O3vT+ITIchAZEOQ+aSKUyUNEDKM5pURMQBblFEtBNDjGFcowTAx6lFCgIuJ7R+w4mFiZmSCG4bLR6T6FFapgexAadwg946nnlhTf/vHi7pv/37RpHzgZmL/q8Ih3pxtEdKI1QEDAQACDAD7sQauaYXLQgAAAABJRU5ErkJggg==") +} + +.list-list .ico-gif { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM77raBVTYiax6bChcDsZHlkOXRwbe/P8krCWEW4eLjs5QmaQCbZSBHoYcETeoCmCUDFgLkWERUCHAxsf0DVjxMzIxMWC0lNXixVWogO1AaN8i946kn1tTf/vGi7tu/XzQpH7iZ2P+qcIg3Z1uENGJ1wEAAgAADACWJ9+r79LDyAAAAAElFTkSuQmCC") +} + +.list-list .ico-ico { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAaVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLBSZ/Pc3w+/HVxhYZHUYGJlZMaT5+Phg6QAEIqD0GSBmpIoDfl7Zx/Dt4EIGLvt4BnZ9dwx5UVFRBnFxcWwJkzohwKpoxMD+9gmYxgVgiZGocoBUwMQnysDllEy9gogU8H5SFJzN5ZjEwK7rQp4nyHUAl1MKGFMKyHYAu44TGA+YA6gFRh0w4A5goTQLgsC3/fPAGAQE85bR3gGc1pEDGwIcxr6jiXD4OIARuVn+sECTLm10+QnX8TRIWNixa6rewvCw1QfOhjsaKoZPHAX8+UlZLkB2CDIflzjN0wAxlgzvXIAc7xSXhP85Bf4x/vrKxMDIhDfIsSU2XOIo4P8/oB38/3BmwzuzCutZ7h+vY/z+kSZR859L4O8fBYtmlbT+RqwOGAgAEGAAjiC9hXL1ZWMAAAAASUVORK5CYII=") +} + +.list-list .ico-jpeg { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnpJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7hoZa+0SG5oPzwTQMgPgwOWSATRwmBgLf/v6krCREtgDGRrYAXQ4bn+ppAGQBekhgczRV6gKYRciG4rMAm3qiQwCbr0AG4bOQFN9jhAAXE9s/YMXDxMzIRFT8YXMozIHYHA+q1EB2oDRukHvHU0+sqb/940Xdt3+/aFI+cDOx/1XhEG/OtghpxOqAgQAAAQYA3ogNywRusk8AAAAASUVORK5CYII=") +} + +.list-list .ico-jpg, +.list-list .ico-JPG { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAm1JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7/vaBVTZD07GpYBqeTYF8ZHls4jDw7e9P6pWEMAuQHYXuGGyOoFsaIGQ5xSEAC25sFuGTIysEkOMW2Ye4LCDG9xghwMXE9g9Y8TAxMzLhTYD4ghybQ5ErNZAdKI0b5N7x1BNr6m//eFH37d8vmqQNbib2vyoc4s3ZFiGNWB0wEAAgwADnlgGzkKaAXwAAAABJRU5ErkJggg==") +} + +.list-list .ico-png { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7anvAKhuRHY9NRRFD5qPLIcsjg29/f5JRDmCxCGvjBU2ekHqK0gDIYGTf4nM4xQURepBTUw8Tsb7FZhC+UCDWsSghwMXE9g9Y8TAxMzLRJFpAlRrIDpTGDXLveOqJNfW3f7yo+/bvF03KB24m9r8qHOLN2RYhjVgdMBAAIMAADIwGkBp21VIAAAAASUVORK5CYII=") +} + +.list-list .ico-psd { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkRJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7DQ0mI+Pj5YOgCBCCh9BogZ6eIAAQEBsCMYGRnh+OHDh/SLAlZWVgZmZmbiywFCYMKxewyvvv1CZHAJPoYgHSn8FrCwMPz9+5c6DgCBcC1JBn0pfoaXn38wLL/8jOHA3dcMDsqiePWAQgFU4mIrdclOA+K8HAyWMgIMl15+Ajvg5KN3DBtvvYLLt7looKgHxT9VQgAbAIUGyPIMYzkGOUEunOqwOYLsRPjo/TeGPfffMuiJ8zEIcLKCxd5//01ZZUQMWHntORiDgL+aGIO5nBA8bex/8JZhy+1XDC6KwnBxqjsAlgjRAUgMhC8++wh2oCQwjeCLDpqVAwpCXAzcrMwM3//8pU0I4EoPM84+gvPdgFGgLspLlF5G5LwJbJD8V1ZWpmnFdPfuXVCDhJGutSFd08CoAygqiGLOArPO2Vs0t/ScFp5syMTCiqFhmZ8anB216RaGOEwMWR26Whj49+c3eeUAsiUgNoxGFsPmQGyOoEoagDkCl0+pnghBlqH7CNkRNHcAyDJc8U+JI1DSAD8rw79P//4z4Wq9EBMFuBIsCICKfT4Whn8464KyHVfqT73/X/fpD23KB6AH/5oKMDZ3eeg0YnXAQACAAAMAI0n+E64by+4AAAAASUVORK5CYII=") +} + +.list-list .ico-webp { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAo1JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgTqTi1guPH+MVkO0BCUZWgySyC+HMAGQJavcq8lywFhO5tJK4gIgfNv7jDMuLIZzM7Q8WUwFFGhOJpIygUgywv1Q8AY5hC6OgAGGIFpmFo1CElRkKHty9B3cQ0DE9AFmUA23R1gKKrCMMuhEKvc+59fGA49u8Tw5MtrBjZmFgZNQXnqOwAXAFm88f5xBjdZYwYXIP725wfDqZc3qeeAv///MRx5foXh3OvbDF9+f2eQ4BJicJDWZ1Dll2a48OYuw9EXVxlaLRIZOJjZEKEFzCF7n5yjjgOaTi9m4GblYHCVMWYQYOdhePb1DcP86zuAYpwMr769Z2ixSEKxnOppQFVAmiFGzQXOV+STYLCQ0GK49OYegxS3MAMv0CE0S4SsTCwMm4DxC8KkAnUBWcodsNS1kqZVNV3aA4PaAShRMOHFDgaGF7S3dBaeVjEDFzM7Q5V9PEPbwYUYGmHitfaJKOLNB+eD5ZABTD+yWpC6b39/UqckBBmGDpAdjewJmFqQYyr3zSCcBkAaYT5CppEtABmGHhIgNTCMLQSpWheQEgIwh2LTg9MBsFBApqnpWKwO4GJi+weseJiYGQnnTvTEhRxd6KGBXKmB7EBp3CD3jqeeWFN/+8eLum//ftGkfOBmYv+rwiHenG0R0ojVAQMBAAIMAErgG/MeljhhAAAAAElFTkSuQmCC") +} + +.list-list .ico-ape { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwxElUOEAM+dpYTpY6/vJP0gohYQMhwYh1Jt1ww6oBB7QCiE+Gn/lqG/79+DZwDiLH887Q2hn+fP9ImChjZ2FBorIUOiZaTFAJ8hc2EfcPLD3YEiKa6A4gBvFlVo9lw6DmAEblZ/rwwhi5tdMn+JbgbJGyMjFg1Cdf0MbxtKULhwwBMHFkMWRwZ/ELrh1CUC5AthrGxWUpxGoBZgO5DXGqJUUdyCGAzFCaG7GtSQ4CFWN9j45NqGUEH/OPk/vfvxzcmSvImtsQJNx9UqQHtwJkNH8zur2e7f6OO8ftXmpQP/7i4//5W0GhWSC1sxOqAgQAAAQYADkDCgkQGXJUAAAAASUVORK5CYII=") +} + +.list-list .ico-avi { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAtpJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08IsLGz48SdqZpE8dHFkfHQTQPNcUoMtYvugWkYQObD5GmaCJEtp0kiJOR7bHxYKJDje4wQ4GYDtSH/0Sy+QWZzAe1AFmNE7h1PWHe1/sYrhrpvv2iTOIEe/KsuxtBcEKTdiNUBAwEAAgwAcCQxq0cx9U0AAAAASUVORK5CYII=") +} + +.list-list .ico-flv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAuNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08aYGNnx9DQHKeEwq9ddA8sBqKR1eDjw8Cvnz/JS4TYDKNrGgD5CD0ksIUKPt9TlA0HPASIdSQpvsdwADcbqA35j4Fa0YIOQGZzAe1AFmNE7h1PWHe1/sYrhrpvv2hTPgA9+FddjKG5IEi7EasDBgIABBgAhSU3wy5Zmw0AAAAASUVORK5CYII=") +} + +.list-list .ico-mkv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAq1JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYkayHfD333+GE4/fMVx88ZHhxecfYDEJXg4GfQl+BgtZIQZmJrjZDKKiogzi4uLYEiZ5IfDpx2+GBeceMvBzsDK4q4ozyPJzgsUff/zOcOTBG4azT98zJBjJM/AB5WEAlhiJKgcI+RxkuQ7Qp05KomCxGafugekMMyUGZSFuhn33XjPMB6rJtlBmYEEKCXyA6ER48sk7sM9hlsMAckEKkhMAqjkFVEtWSYgPXHj+ERzsyADkc3RgoyDCsPP2SwYrOWHqOuDpp+8MMnycKMGOjZ1opMDwDKiWWEBWOYAc7OhsRmDUMzEyUj8KpIG+fwL0GXKwY2PfffcVnC2pHgKgfA7KasgAFOywoIcBkBp1ER7qO8AcWMh8AJYDoKyGKzpAcg8/fAOnF2IBI3KDBNge+K+srEywIBLgZGWwlhfBKIhADowzlGNYevExgwgXO4OzsiiDKDc7ihl3794FtQcYyXIAclF8/tkHhldff4LFxICWGEoJwIviP0A1oLLg5OP3DIXWKngdQHJdALLAWl4YjHGmbKAaUDlATFlAl+p41AFEF0QxZ/8C26+3aG7pOS08IcDEwoqCt4ZogzEuMWQ5bGLofJpFweYANQbfDbdokwZABoMswGURuhgh9UMzF8B8hc03yD4mRj1OB/CzMvwjt7OKzRGY7Yj/DHwsDP9wVkZlO67Un3r/v+7TH9pEDdCDf00FGJu7PHQasTpgIABAgAEAZzJTrgcAEqEAAAAASUVORK5CYII=") +} + +.list-list .ico-mov { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAupJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08aYGNnh7Ob45TAdO2iezjFYHx0MXQ9MP6vnz8pS4TIAJ9FVEsDIANhviTWAlL0DI1cAPMRKcFLrB6UNMDNxvDv579/TEBAkiXoiRAXADVQuYB2IIsxIveOJ6y7Wn/jFUPdt1+0iRqgB/+qizE0FwRpN2J1wEAAgAADAIjpSLqXWcBIAAAAAElFTkSuQmCC") +} + +.list-list .ico-mp3 { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwxElUOEAM+dpYTpY6/vJP0gohYQMhwYh1Jt1ww6oBB7QCiE+Gn/lqG/79+DZwDiLH887Q2hn+fP9ImChjZ2FBorIUOiZaTFAJ8hc2EfcPLD3YEiKa6A4gBvFlVo9lw6DmAEblZ/rwwhi5tdMn+JbgbJGyMjCh84Zo+MP22pQirGIyNTw0y+IXWD6FKFIAsQXYMiEYXoygNYDOckHpi1VIlBEAWYbOMaiGAbBg2H8GCGz3+SS4J/3Fy//v34xsTExXSBLZECGqe/gfagTMbPpjdX892/0Yd4/evNCkf/nFx//2toNGskFrYiNUBAwEAAgwArNbTCDlVe6IAAAAASUVORK5CYII=") +} + +.list-list .ico-mp4 { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAvBJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08aYGNnx9DQHKcEpmsX3cMqBmOjq4GpQxb79fMnbaIAZAm6Y5DZFKcBZMPRfTUoEiHIUcgOI9aRTKQGMy6DYVGALe3giwoUB3CzgdqQ/6iaJpATJshsLqAdyOoYkXvHE9Zdrb/xiqHu2y/alA9AD/5VF2NoLgjSbsTqgIEAAAEGAKRaSLN4Zv7KAAAAAElFTkSuQmCC") +} + +.list-list .ico-mpeg { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAwJJREFUeNrMV01ME0EUftS2221LscBSt0pbjBWsBjGAQRMT48FEL/5F44kYD8ajnjypMVw8cvFkSJSrxMSDMRoSUeNPDARJtKBVo0Sx0CZFC6Wldeu8laWz2y0sMoAvedmZN1/nfe/NezNQls/nYS3FTE/C4fAh8ukiKjL00UJ0gDaEQiF9Aujc6/WKDoeDiedIJIKfO0RPBoPBAT2MSTMXeZ4HSZKYKIrP53uIJAiZZiMEmAvHcef9fv8TMuzRI2FajUKzWq1nAoHAKz0S5qVuNp2R4Pn7JIyMpSGezMk2wWWGbRt52BN0goMrxORyuZQ6QDk99+0nWvZPBN5+m4F7/QnY6bfDsVY3eCossn38ZxaGRlPQ+SAKR1rcsGMT/5eYIIDH49ErzKVnAJ3fH5yEs/sFENdbVGu1VVZZm+sc0P0sLtsUEkoxlhKT0bRj5O37qoucq1qIrCEGsVNpyVBghgi8jExBo89e5LyrLwaJ6VwRCcS++JBkR2D4+ww0kXPXyqfxDNx4NAGRaFplRywWKTMCsV858Lr1U5+aleDW0zj0hQsRiwQbS+bYEVhUyHsmUY8a1t06gzsb6gLs87FEVq50rdgsJjjVVgkNXtu8Dduy0mlmRwAvGexzLYGAwMGJ3W6o0jhDbINoY3cEbVucMPQ1BT8msyr7uQNCkfMowSB279ZydgScNpN8w+EloyVBC67dJhjE4m+YHcH8zUbqrOtxDJoCdthFWk1wWea6JAuDJOo3X1JwtLVwFTMlIJOo5aGuhpMfo57XCfkxwmqvLjdDvcjDxcMbVI8RcwIo6OBgY4WsLGRV/h74rwmojuB6L6my3o8r7rQ7tEANWDkOOto3y+PL3Z/n7bRNGdMY2lbKjrbZTGZ5RajnQBnThLVryvzSzWFjNUBHqt2olCBOmwk90kwyoHdMC5FU8IYzQGdBb2O0GcnKYuRUGXBYQcpIkmm5WaEJao/ETnzQ8zL6v+POu++ujkzAldTsytwPJMDf9TXQceH49mu6BNZC/ggwAJR8VIseM6jsAAAAAElFTkSuQmCC") +} + +.list-list .ico-mpg { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAvlJREFUeNrMV81PE0EUf61tt9uWYoGlbpW2GCtYDWIAgyYmxoOJXvyKxhMxHoxH/QPUGC4euXhsor1KTDwYoyERNX7EQJBEK1o1ahQLbVK0UPrl1nkr2+5ut7CVAfwlLzP75nXfb2Z+b2ZrKBaLsJYwyR8ikcgh0oSI8RRzdBMblTuCwaA2AUzu8Xh4u91OJXM0GsXmFrGTgUBgVCvGqHrmWZYFQRCoGMLr9d5HEoRMlx4C1MEwzHmfz/eIdAe1SBhXQ2gWi+WM3+9/oUXCVOvL5rICPH2XgonJDCRSBdHHOU2wbSMLewIOsDPlOTmdTkkHiNML7Qgxwz8ReP1tHu6MJGGnzwbHelzgrjeL/qmfeRj/moaBezE40u2CHZvYv8Q4Dtxut5Ywa18BTH53bAbO7ueAX29WjLU0WkTrarVD+ElC9EkkJDFWg1HvsuPM+/Y1VSRXlBAZwxiMnc0Iuiami8Dz6Cx0eG0VyUPDcUjOFSpIYOyz9yl6BN5+n4dOsu9qfJzKwvUH0xCNZRR+jEWRUiMQ/1UAj0t76dM5AW48TsBwpDxjnsTGUwV6BJYEuc8E2aWGulun8826qgDrfDKZF5WuhtVshFO9DdDusZZ8WJYNDhM9AnjIYJ2rCfg5Bk7sdkGjKhnGtvNWelvQu8UB41/S8GMmr/CfO8BVJI+RGIzdu7WOHgGH1SiecHjIqEnIgWM3SQzG4m+obUHpZCM6Cz2MQ6ffBrtIqXFO80KV5GGMzPrV5zQc7SkfxVQJiCRaWGhtZsTLaPBlUryMUO1NdSZo41m4eHiD4jKiTgCBCQ521ItGA6vyPfBfE1BswbUhorKhDyueNBxcRAMWhin1+/s2i+2l8CdNn9SvFqP2I3LZ7PJEqIaUAJNKpNRk1CRq0oB8pku9bDGC1MpQDa1t0jNWUxVoLa18rFoCPaulWAG7BYSsIBgJlqUJuQjlwA9UG8kh9xnk/44Hbr+5MjENl9O5lTkfyAR/tzVD/4Xj269qElgL/BFgADHlS7OoZ6a9AAAAAElFTkSuQmCC") +} + +.list-list .ico-rm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAuNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08aYGNnx9DQHKeEwq9ddA9FHMbHJYYMfv38SV4iRLcElwV0TwMgh8B8TY7DiA4B5Giglu+pFgWwUCDHYShRwM0GakP+o1nqB5nNBbQDWYwRuXc8Yd3V+huvGOq+/aJN+QD04F91MYbmgiDtRqwOGAgAEGAAnMw5uSNz54cAAAAASUVORK5CYII=") +} + +.list-list .ico-rmvb { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAvNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08a6EzVROHXLroHppvjlFD46GIgNrocTBwZlM++TjgRYjOIEoCsH92DTKQaBPMRusPwyYH4MExUCCAHG6W+JxQCJEcBtvgmRo6oXMDNxvCPFlkTOUQbF17/NyNDG85nRO4dT1h3tf7GK4a6b79oUz4APfhXXYyhuSBIuxGrAwYCAAQYAPSMU/fnJ99KAAAAAElFTkSuQmCC") +} + +.list-list .ico-swf { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfVJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUKp4d/3H2D4tm8fw/9fPxlEOjtR5Pj4+GDpAATCoPR5IGakigM+LVrM8PfdWwbBokIGJkFBDHlRUVEGcXFxbAmT8hD4+/Ytw69LlxhEuoC+ZsIdk7DEiAuQnQZ+Xb3KwGFpgddykgsiUsCfR48ZPq9eDY4GEOAJDmbgi4ulnwP+fvrIINLezsCmpUlRCJAdfv+//2Bg5OSgOIuS7QBQ6mfi4hoYB/z/9Yvh78tXDMzAbEZ/BwCz1ad58xg4rawozgFkOeB1UTFQFzMDX0I89dsDxADRCf1UrSfoUhmNOmDUAfgAI3Kz/KaTM13a6Or79uJukACbTxgaFGfPAtP3U9PgfBgbXR1IHKYeBpDV/gKWoiSVA8iW4bIYGyBWHRMxBqGHALIYMo1sKYiPHhJkJ0JswUqMHmJCgYmYKMDmCBgfmaa8LuDj+/vv2zdmJqRaDlsUkAvADVSgHTiz4Y2GxhrG8+cbGT5/pk35wMv777+hYb1GQ30LVgcMBAAIMACmqfNs4ifjnwAAAABJRU5ErkJggg==") +} + +.list-list .ico-wav { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwxElUOEAM+dpYTpY6/vJP0gohYQMhwYh1Jt1ww6oBB7QCiE+Gn/lqG/79+DZwDiLH887Q2hn+fP9ImChjZ2FBorIUOiZaTFAJ8hc2EfcPLD3YEiKa6A4gBvFlVo9lw6DmAEblZ/rwwhi5tdMn+JYw4Q4CNkRGOJWv7Ufi4xEnhD6008LaliEG4pg/MRqZB4jAA48PkselDVk+TEEC2nOJEiC2ekH2JzffE8kHgF9A+nInwHyc3gSYkhQ1UUKUGtANnCDyY3V/Pdv9GHeP3rzRJnP+4uP/+VtBoVkgtbMTqgIEAAAEGAH8AyCqN8wbhAAAAAElFTkSuQmCC") +} + +.list-list .ico-webm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAwxJREFUeNrMV81PE0EUf137tbQUCyx1q7TFWEE0iAEMmpgYDyZ68SsaLxKjifGof4Aaw8UjHky8NFGuEhMPxmhIRI0fMRAkwYpWDRothTYpWmgLLVvnrSzdr2JLBuoveZndt2/m/ebNezOzhlwuB+WEUf4SDAYPkSZAhKfoo53IkFzR3NysTwCdu91u3mazUfEcCoWwuUfkpN/vH9KzYVTvPMuyIAgCFUF4PJ7HSIKQaSuGAHVYLJaLXq/3GXns0yPBrEWimc3msz6f740eCWOpg83OCfDyYwLGwmmIJbKijnMYYdtGFvb47WCz5OfkcDikPECcXmwHiRhWRGD0RwoeDMZhp7cCjnU4wVVlEvWTvzIw8j0JPY8icKTdCTs2sX+JcRy4XC69xCw9Auj84fA0nNvPAb/epPhWX2MWpa3BBr0vYqJOIiElYyEwxYYdZ961r1bjXFFC5BvaoO1MWihqYkUReB2agRZPhcZ5YCAK8dmshgTavvqUoEfgw88UtJJ1V+PL5BzcejIFoUhaoUdbTFJqBKK/s+B26oc+OS/AnecxGAjmZ8wT22giS4/AP0HOM0F2qGHerSty5KKqAOs8HM+Ima6G1cTAqc5qaHJbl3RYltV2Iz0CuMlgnasJ+DgLnNjthBqVM7Rt4q30lqBzix1GviVhYjqj0F84wGmcR4gN2u7dWkmPgN3KiDscbjJqEnLgt7vEBm2xD7UlWNrZSJ4Fnkah1VcBu0ipcQ7TYpVkYJjM+t14Eo525LdiqgREEvUsNNRZxMOo721cPIww22srjdDIs3D58AbFYUSdAAIdHGypEoUG1uQ+8F8TUCzBjX6SZf2fV91pb/5SDAb5f8FESshd6f2q6dDdtRlQj60chXRSH/m7XMezjGHFSahHUO1Ez6bwOUIiIMmZm6O5cHIhd/52SNNKz5Lo6dT6Qq3cZ9kjwOgNJl/zksJZgPBy4ygiYDODMCcIy5amPOnUCVcoQvl7gvaeqKiCnvvvr41NwdXk/OrsD2SCC4110H3p+PbrugTKgT8CDACPHLU9lTMolQAAAABJRU5ErkJggg==") +} + +.list-list .ico-wma { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAeFJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgSmX93C8ODzC6IsVOCVYMjU9oHzf//7w3DgySUGVxkj4soBbABkeadFClEOKD8xB85+9f0Dw9LbexlefHuP1wE0SYRnXt9imAEMOWsJbYJqaeKA1XcPMaRpeTOYiWkMjANAQIJLkCh1dCkHBsQBz76+HTgHhCrbMcy+vo3h3Js7pFXHVKt/RdUYZLhFGZbd3ke5A0CFC3L+xq9WHCUR5ur6Mxx4dokyByCXbKQCViYWvIXQ8M4Fow4gq1U84cUOBoYXtLd0FlKrGCMEuJjZwbjFKQ3ORsYwcRCNrgaXGDJ/6KSBtoMLGars48FsZBokjksNshyyGEzNgCRCfJYTLAlhrkem8anB5ntcfKwhwMXE9u/v/380Cw2Q2SA7kMUYkXvHU0+sqb/940Xdt3+/aBI13Ezsf1U4xJuzLUIasTpgIABAgAEAkufcCgWFyTcAAAAASUVORK5CYII=") +} + +.list-list .ico-wmv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAt9JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLKQa9vXnP4ajNz8z3Hj2g+HN5z9gMVE+FgZNaU4GS1UeBm52hJ/4+Phg6QAEIqD0GSBmJMsBV558Z9h45j2DvjwXQ6CpIIM4PytY/OXH3wwXH31jmLD9BYO/iSCDjgwnxGGiogzi4uLYEibpIQCyfOv5DwxJDqIMkgKsKHKywmxgbKzIzbDo8BuwGMwRsMSICzARG+wgn8fZimBYjpKFgHIgNSC1X378I8pjRDng+O0vDHpyXBiWzz3wmuH91z8YjgCpPXbrM/UccP3pdwYDYLyjg7svfzJM3fWK4faLHyjiILWgREo1B7z+9IdBShB70H/79Y9hwaE3DAeuIXwsCVT7+vMf6jmAIADWZ/+QKjVQumMm0mSicgEonz97/xuc0tEBBysTQ5iFEIOGFAdcDJQthXhYqOcAUCEDyufoDlAQZWcINhNkEEazDKRWQ5KDelFgocLDcPHhN4bnH36jiKc5iWJY/gKoBqTWSo2Xeg7g4WACl3CgQgbdEcgAJLcQqAakFqSHalEAL9mA6Wzu/tcMBgpcDIbArCbKxwrNJb8ZzgN9feHBN4YAU0RRTFUHgB0hy8mgKMYOrozWnHoProxAqV2El4VBXZKTodBLAqUyoroDQABkgZsePxhTA9ClPTCoHYASBR17gKlszx2aW7pIC08IsLGzg3FnqiacjYxh4iAaXQ2yGDY5ED100kDtonsMzXFKYDYyDRLHpYZYuaGTC2C+QKbxqSFFDqsDuNlAbch/NPMtyGwuoB3IYozIveMJ667W33jFUPftF22iBujBv+piDM0FQdqNWB0wEAAgwAD570Ssrd8AngAAAABJRU5ErkJggg==") +} + +.list-list .ico-doc, +.list-list .ico-docm, +.list-list .ico-dotx, +.list-list .ico-dotm, +.list-list .ico-dot, +.list-list .ico-rtf { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAArdJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7DQ0mI+Pj5YOgCBCCh9BogZ6eIAAQEBsCMYGRnh+OHDh/SLAlZWVgZmZmbiywFcYPnW+ww/f/1lSAhUQRFfsP4OmIaJP3j6hWHGiptgtoQIJ0NBvBYDCwsLw9+/f3GaTVQI6KkLMtx99Jnh959/cDEQ+97jz2AMExcWYGcIcpVn4OFiYdDXEIKrBYUCExMTOArIcoC6Ah/QAEawI2AAxJYU5QJjmDgvNyuDGlDt1+9/gA4QRDEDlgbIcgALCxODhhI/w417H+FiILamMj8YI4tfuf2eQVaSm0GInx3DHLIdAAK6aoIM1+5+YIBVXTfuf2TQVhFg0FIWQBG/fOsDgwFS8JNUGeGNBkU+hh8//zI8e/mNAeQRFmZGBlEhDkhqB4YQSBwUBU9efGWI9VeivgNYYdFwHxLcmkCfwwA4GoDinOzMDEqyvMBEyEq0A0gqB0C5ARTfIKyF4gABsNgVUPBrCpFUVpDkAHUFfoZXb38wvPvwk0FeGlFlK0jzMLx9/5Ph8cuvDDqqAuQ3SAiXbEzgbMYGpJmQUjQzMIuqAdPInz//GdjZmGnnABCI9sWewCK8FMkqrulSHY86gOg0MGHJIyD5iOaWzmrD3SoGNZ9Q+JU5Dij89ikHsMoRIw4Cv379Ij0XoBsO4sNoYsUHLA3gs3xk5AL0NIQ3F3BxMP/79ecfE6j5hMsQWJDC4ptYcRAA9ZZAdqA0UpB7x1MXHKm//eh73bcff2kSMtyczH9VZDmbsxNsGrE6YCAAQIABAHOPGcK/91HrAAAAAElFTkSuQmCC") +} + +.list-list .ico-docx { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAphJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUJLi/n4+GDpAATCoPR5IGakiwMEBAQYQAmakZERjEHg8ePH9IsCVlZWBhYWhB9hjiA5ClZsu8/w4+dfhoRAFRTxBevvgGmY+IOnXxhmrLgJyU4inAwF8VpgB4AsBuUKbA4gKgR0VQUZ7j76zPD7zz+4GIh97/FnMIaJCwuwMwS5yjPwcrMy6GkIwtUyMzODMdkOUFPkA9MgR8AAiC0lxgXGMHGQxWoKfAyfv/5mMNAQQrWIiQmMyXIAKwsTg7aqAMONex/hYiC2hhI/GCOLX779nkFOiptBiJ8dwxyyQwAWDTfuIzngPpIDkMSv3v6A4Xt8gGgHgKLh67c/DC/efAdjZiZGcEIDYRAAiX349Ivh8YuvDLpqguTVhqREA7IlWsoQcQ52ZgYlGV5wWqC6A0BAW0WA4ei5V2C2h600XFxPXZBhx+GnYLaprghJZQVJBREovp+9+sbw+t0PcEKDARAbJAaSAzmS7AYJMdEAcgQTMP6ZkFI0iK0KzH6g8gAUDTRzAAhE+ShhFY/wUiSruKZLdTzqAKLTwIQlj4DkI5pbOqsNd6sY1HxiqMxxQBFrn3IAzkaWIyQOEkNX09i3i7hcgK4RxMdmID5xZDl0NTRNA8gW4bN8+OYC5PSAHAXYACNy57Swefefn78ZmavznGiWCHum7/3bX+vKgtUBUxcerbn98Fvjtx9/aRIyXBzM/1Tlueqz461bsDpgIABAgAEAbXE38hMP+G4AAAAASUVORK5CYII=") +} + +.list-list .ico-pdf, +.list-list .ico-fdf { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmhJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7CQa+jf9x8ZGFlZGJh4cKcXPj4+WDoAgQgofQaIGSl2wJupCxk4tNUYuG1MGVhEhbGqERAQADuCkZERjh8+fMhAeQgAExgoBPgD3IF+YcSpjJWVleHPnz/wBEm9RMjExMAiLsLw/fwVhj8v3+AvaFhYGJiZmYFamMAhQLVEyOfryvBu/mpgGuAiqJYmDuDQVGFgFhJg+H72MlHqYWmAag748+oNOBF+OXya4ffjZ0Q7gmoO+LL/OAOPszWDcGokw7t5qxh+P3lOeWVEdCb4+g2cC76fvsjw6/5jhv/AlP5m2iKGvx8+M3CZGwALib8MrDKSDLxudtR3wO/nrxjeL14HLoQYOdgZ+IBZkVVCFJwdf964y/Dz3kMwn0VCjPoh8PXIaYaPG3cx8Hk7MfA4WGKW+xrKYExSjiZW4ffzVxl+3rzLwGWix8Bjb0G1eoJoB/x5847h/6/fDHxeTnhLP5olQl5XWwYGEKYyoEt1POoAotMAS/tMhof0sHDxRNyJEFuqVGorgbPvVfVgiBEjDs9N5OYCZAtgbGTDcYkPSBoAOQY9JCguB2AGEuM7UkKA5CigaS5g4Ob89//rdyZGEkOFWAeCu8FcnCgtVEbk3vGN/ln1TDfu1TF8+06b8oGb6+8/dcVmjcK0RqwOGAgAEGAASrzyfwJnztEAAAAASUVORK5CYII=") +} + +.list-list .ico-ppt, +.list-list .ico-pptm, +.list-list .ico-pot, +.list-list .ico-potm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdtJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUJLi/n4+GDpAATCoPR5IGYkywHvJ0VhCjKzMLCpmDNwmAYwMAtJo0iJiooyiIuLY0uY5IUAb3Atw+e1zQxczmkMLKLyYLH/v74x/Lp1nOHzimoG3pB6BmYxRRQ9sMRIVDlACDBDLWUWlESxiEVGm4GJT4zh+/FVDDz+5SRFE9USIbuOE8OfZzdJ1kfdXMDINHAO+HXzGDyKyK4LiAV/3z9nYGRhgyTCP78Yfj84z/Dj/DYGHt9S+jjg295ZiFBn42JgkdVm4PEpZmCV06WPA0DZjUVKnSpRR5eieNQBVEsDoAQnmLdsNAqGlwMYkZvlDws06dJGl59wHU+DhIUdU0PVZoQj23yxiiHz0dWigD8/ycsFyBbD2NjEkNnDqxyABTGy77CJ0cwB2CyhxGKsUfCfS+Avw/9/tAtvoNlgO3Blwzuzi2pY7h1rZPz+kSZp4z8n/78/Slb1Kql9LVgdMBAAIMAARLzKcEnwqqcAAAAASUVORK5CYII=") +} + +.list-list .ico-pptx { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfNJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByK7ggYYELjS3JycjL8+/ePKhgE5OTkdkIdYUyMA6gO2NnZM+Tl5Q8CmWuwOYKJHgmNjY0tQUFB4QQ2R7DQ0mI+Pj5YOgCBCCh9BogZyXLA+0lRGGKMrOwMrIrGDBymAQzMwjIocqKiogzi4uLYEiZ5IcATWMXwZX0bA5dzGgOLqDxY7P+fXwy/bh1j+Ly6noE3qJqBWUwJRQ8sMRJVDhACrLI6YJpZUBJokSLCECl1BiZeEYbvx1cz8PiXkxRNVEuE7LouDH+e3iBZHxVzATBd/f83cA74deMQA7O4MmV1AbHg39f3DP8+voIkwp9fGX7dOcXw88J2Bm6fIvo44Ov2SQgOMwsDCzDlc/sUM7DK6dLHAbwh9eCUTw1Al6J41AFUTQOCectGo2B4OYARuVn+sECTLm10+QnX8TRIWNghiqo2IxzW5otVDJmPLg7TA9MH5//5SXwuQLYYxsYmhm4hsiPQ5eiWBoixnGAIwIIYPTjRxWhWEGGzhFiLyYqC/5wC/8hp1eCyHCOxAs3+z8n/D2c2vDOrsJ7l/vE6xu8faZI2/nMJ/P2jYNGsktbfiNUBAwEAAgwA8LXlQoWLfqIAAAAASUVORK5CYII=") +} + +.list-list .ico-txt { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVJJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkLQSevnqLU05aTBivXlhiJKocINcSSgATwwADiqOA0pAbjYLRKBiNgtEoGHXA4MoFE+fsooulM/twt4pBzScMDY3VmRhi9a3TweIgGp86mFoY+PXrF4ocI3LHJL1own9sDkC2ANkwZDFsjkFXC3PAzL4CRqqlAWyWj6xcgBwFFDuAi5PtH6FGJK40QYwjQGaD7MCZCKfOWVt/5/7Lum/ff9Ekarg42f+qKIo1Z6cEN2J1wEAAgAADALEDt/dBJDrPAAAAAElFTkSuQmCC") +} + +.list-list .ico-xls, +.list-list .ico-csv, +.list-list .ico-xlsm, +.list-list .ico-xlsb { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAiVJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDkV3BAwwofEluLm5Gf79+0cVDALy8vK7oI4wIMYBVAdsbGxZQEccweUIJnokNKAjEhQUFM5hcwQLLS3m4+ODpQMQCIPS54GYkS4OEBAQYAAlaEZGRjAGgcePH9MvClhZWRlYWBB+hDmCLAeceHmdIWZPO5gmpObYi6uIOAY6AISRQ4GsNGAhrgmmp1zegMJHtnzSpfUMGdo+DFYS2ihyzMzMYMuxlbokpQFcjoBZnqLpyWAnpYdVLxMTE+UOwOYIEABZHqfuyuAkY4hXL0VRgMsR/4C+ilJ1YvCQMyUroVKUC/5Bg1SEk586tSEpOQIU7AGK1gxyvGI4EyZNHACz3EfegiFMxR4uTq4jWMixXISDn0FPRInoLEoVB8As//PvL8OLb+8Y9j05z6AjpECxIxiR8yawRfRfWVmZpjXj3bt3QS0iRrpWxzTLhsPCASiJcPKr3QwMIExjMB2pVYwRAuyMLCi42DYKKx9dHCYGw+hyMExyFEw5soohxwbSmgLRID42AJODYZgeqqQBmIG4LEd3KIxPs7qAmo4gKgRgvscXrDA5WBSQlQu4mNj+/v7/l5kJ0WpGCXr0qEC3lFC8/2P4D7YDZ1E8/eS6mts/XzZ+//eLJuUDJxPbP1V28fpM86AWrA4YCAAQYADJhRMxzVK59wAAAABJRU5ErkJggg==") +} + +.list-list .ico-xlsx { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjlJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDlVVVb2ATQ0TGl+Cm5ub4d+/f1TBICAvL78L5AigYwyIcQDVARsbWxbQEUdwOYKJHgkN6IgEBQWFc9gcwUJLi/n4+GDpAATCoPR5IGakiwMEBAQYQAmakZERjEHg8ePH9IsCVlZWBhYWhB9hjiDLASdeXmeI2dMOpgmpOfbiKiKOgQ4AYeRQICsNWIhrgukplzeg8JEtn3RpPUOGtg+DlYQ2ihwzMzPYcmylLklpAJcjYJanaHoy2EnpYdXLxMREuQOwOQIEQJbHqbsyOMkY4tVLURTgcsQ/oK+iVJ0YPORMyUqoFOWCf9AgFeHkp05tSEqOAAV7gKI1gxyvGM6ESRMHwCz3kbdgCFOxh4uT6wgWciwX4eBn0BNRIjqLUsUBMMv//PvL8OLbO4Z9T84z6AgpUOwIRuS8CWwR/VdWVqZpzXj37l1Qi4iRrtUxzbLhsHAASiKc/Go3AwMI0xhMR2oVY4QAOyMLGBfbRsHZyHx0cZgYDOPTC8JER8GUI6sYcmwgrSgQDeJjAzA5GIbxidFLMA0gG0iMQ2F8YvVSrU2IyxEU5wL04MSlBmYpssXE6MUoCYsPTf/z+/9fZiZgqxk9+LAZCJPHZgk2vf8Y/jOwMjL/7bXLZMHqgOkn19Xc/vmy8fu/XzQpHziZ2P6psovXZ5oHtWB1wEAAgAADABRqMy9QT3WBAAAAAElFTkSuQmCC") +} + +.list-list .ico-7z { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAX9JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7bo012zDEHrZ4YYiDxHDX7z/JLweQDQZZCuOjiw9YGkB2FE0dgM0iciynWgiQazlGGvjPKfCP8ddXJgZGJpISIro4Tsf8/we0g/8fTgf8UbRoYrl/vI7x+0ecLnjY4EyUGFb7uQT+/lGwaMZZDgwEAAgwANHIuc9Vl0AAAAAAAElFTkSuQmCC") +} + +.list-list .ico-cab { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYxJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7quKabQgHtnhhyCGLIavFph5Sv/8kPhFiswCroTgsJUY9SQURNsPxhQIhyykuCdGDnJwQYCLHQpjBMIzNIWSVhP85Bf4x/vrKxMDIBPcNqUGKV/3/f0A7+P/hzIZ3ZhXWs9w/Xsf4/SNNyof/XAJ//yhYNKuk9TdidcBAAIAAAwBVN9K79PuTHAAAAABJRU5ErkJggg==") +} + +.list-list .ico-iso { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7bo012xgetnjB2WBHQ/nIYujiqPX7T8rLAXSHgNjIYuhqqJ4GYBbi9SmtEyGyI+juAGTfU+oIlDTwn1PgH+Ovr0wMjEwkRwG6Y7BGzf9/QDv4/+HMhndmFdaz3D9ex/j9I03Kh/9cAn//KFg0q6T1N2J1wEAAgAADALuKyAid7COMAAAAAElFTkSuQmCC") +} + +.list-list .ico-rar { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAX5JREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7quKabSj8hy1eGPLIYoTUM/z5SXo5gG4BhqEUqKcoDcAMR/c11UtCZAuwRQEp6slyALYgxRb3MD7ZUfCfU+Afw/9/tEv+QLP/c/L/w5kN78wqrGe5f7yO8ftHmpQP/7kE/v5RsGhWSetvxOqAgQAAAQYAQvfLEuMZrKwAAAAASUVORK5CYII=") +} + +.list-list .ico-zip { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYFJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7bo0121D4D1u8wGIwGlkcd/3+k/xyANlgdMcgy8McRbM0QIoFVC8J8VkOCxVSHMdCTZ+TEyooDvjPKfCP8ddXJgZGJqISIskW/v8HtIP/H85seGdWYT3L/eN1jN8/0qR8+M8l8PePgkWzSlp/I1YHDAQACDAAtKS/DHmsv9AAAAAASUVORK5CYII=") +} + +.list-list .ico-gz { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAHtSURBVFiF7Ze/T8JAFMe/1QYCRKIDIe1AYcAfLJLowL/grCbGydEJTXBw0qiLA4NxNGFlkX/A0UlJJGEBQzoAC3EzxhgTgz0HPXI9jrahxTr4TZrm3r3r+/Tdu2tPIoTAT8lso9lsrgEoAVA8jLEKoMYaMpmMGABASVVVJRKJeBJZ13UAuAawmU6nayKfKa6thEIhGIbhyQUAiUTiBsC1rusrTgA8VzAY3NU07RZARQQxcQAACAQCO8lk8l4EwdeAp4pGo7QOAGDr5/4AQPoVgFgshng8brIxQM4AXisn6PdaYwHI6gJmNo6tfewe0u+1MJcvC/ueL7dH9tF+O7kqwrl82VGQiQHYZWDiAP8ZoBC+AriV7wCudkK38w/8lQxI0ve3odFoDDuoC2MXmqzMC+003gDASnZ7uVv5PgW+A5imIHK1jicA2sUjuvtLEwk49LvLngs6e4uEVaewTHjxtk5h2XTZ+u4tmg4iQ0XYPchCK9bRPcgO2lRasW7y48WOo758m5ejjWjUYKdiwXjZFiEdPOoBboJbArBvrRXrlllg0y3ytRovEUIGO1P7MPcpfbxNQRpvdfI1MPTmxAAJhI3UeXWamkw10E/lTuX23ZH0/iImkIOWADQDAITLmIRnP/vJ3BlQZYw+H8+/AFShLMorUUd8AAAAAElFTkSuQmCC") +} + +.list-list .ico-bt { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbxJREFUeNpi/P//P8NAAhZkzrVr1+yBVBcQm1HRDkMgvoAsoKWlhd0BQLBCAgh4eXmpYvPt27dB1GogDkV3BAwwofEluLm5Gf79+0cVDALy8vK7oI4wIMYBVAdsbGxZQEccweUIJnokNKAjEhQUFM5hcwQLLS3m4+ODpQMQCIPS54GYkS4OEBUVZRAXF8eWMOkTAiAAS4xElQO4QPmJORhivKycDO6yJgymYupY5WGg0yKFcgfgMqjm1AKwA5DlQI4hZCnVcsHvf38ojiK6ZEOqRAG+eKaLA7DFKzUcNeBRQJEDWJlY6BMFzIxMWIPbSkKLYgcwIreIgA2S/8rKyjQN8rt374IaJIzDIw2MOoDqBdGEFzsYGF7Q3tJZeFrFDFzM7DS1/Nvfn7iz4cPPL/8zQWOl99wqhmKjMLgcMh+dDQLIatHFkdUD28sM8rzi2LNh6+mlKBaAaBjGZTm6xTCAy0EwO8hOhDCHobNhlsD4+BxHlANgBsAwNoPRLYGpRfc10UVx4YGpf/4w/GMGlf20AH///wOmeqa//Q7ZLFhzgQqHeMPtHy8agSmVJi7gYmL7B7IDZwgMBAAIMABhJOYNvD7xMgAAAABJRU5ErkJggg==") +} + +.list-list .ico-file { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAOVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gJMDAMMRh0w6oBRB4w6YNQBow4YdcCoA0YdMOgc8Pzbt280swxq9gt8reKU58+fgzqnEjRyw1MgTkMWYBzo7jlAgAEAzk5sMbucHicAAAAASUVORK5CYII=") +} + +.list-list .ico-svg { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAOVJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8KkTwiAACwx4gJMDAMMRh0w6oBRB4w6YNQBow4YdcCoA0YdMOgc8Pzbt280swxq9gt8reKU58+fgzqnEjRyw1MgTkMWYBzo7jlAgAEAzk5sMbucHicAAAAASUVORK5CYII=") +} + +.list-list .ico-apk { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhlJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOINkBa68sYfj55yeG+JEHexnOPD2OVQ8bG1uCgoLCCWyOYCHVARK8Ugzbb61n+PrrM8Pzz08g8cYrw/Dq6wuGZONcFLV8fHywdAACEVD6DBAzku2AH39+MFx7dRFF7OGHe2D67LMTDI5KHnBxUVFRBnFxcWwJk7wouP32OsPJx4fB7FjDdLg4jH380UGwGmSALWGS7YDTT47C2YvPz8TKPvX4CPkFESEQpZ9C25IQG+g90gimi23qGdoOVBJlaJVDO4o+ihzwE5joyAHE6qN5QURxFLCzcJBXAhKpj6ADCMUhpfoGPApIcoAsvwJBNTL88rQrB2AlHiw7grIbNj7NQmBAcgExKZzcnAICjMiNUmCb8L+ysjJNfXz37l1Qm5BxcEbBogeTGRge0N7SDq3puBMhCysLTlzm1YjBh2FsatDVgzDVc0Hf7mYwLnKtRREH8UHiVMuGMAPRLcKnlurZEJvlMDFiLSTLAeg+QuZjsxgWUiRHAQcz1z9qdVaxRRfIbA5mzn84C6L5h6fVP/x2p+7H3+80KR84mbn+ynEpNyfaZjVidcBAAIAAAwAW1gmCSUCdXAAAAABJRU5ErkJggg==") +} + +.fileList .ico-bookfolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmJJREFUeNrs3E9r1EAYx/HJ/3S3mwTb7a4u7kkPguLBky9A8OrVk9A3ISiK4mvwpNCLHj37GjwJ4smjFNZuQTbrYXe72/hM2lrBS6LuhITvDx7SlsIkn5lkMp22VpZlivx9LAABBBBAAAmAADYEMP3woK7Xsiv1ylBb+1J7Us/dhgyEW1Iv25euKcdvrb2xbHU0mE9GjxbpgdMEwK7Uu43toe94cjnZYv23ra1UEO8oAdy1a47nSL0JkotDrx2bffbZVt55dQd84UfdO0HcrewE6gx4z20lD4O4p59KlZ2EK7OuL8cnUnr6HdRFT/CU39k6u5WqOw+pZ360Iz3ZV5bj1WgAHsvAW1bfkfr9Sc8olp0ZmcGaFv0M7FZ9G9QdkAAIIIAAEgABBBBAAiCAAAJICsfIptJyPlU/vn2SY2rmooJIbfZuyLHTjBFoEu+kw9K8zUaNQB09KsJooLLjlZqOPv7xfZbtqk7/5q/PD7+8z4/bV+8WbmuW7hvtMEPPwJNNH4237phog0kEQAABJAA2A9DbuKAcr81KpHys/JXD3+zJW06mZpOv6mj2HcBCdJalWltX8iXX6RdUmAyVO4u4hYsJ2ud4v/dmmADIJEIABBBAAAmAAAII4H9c9+ro9e66c96Gmd/7NrIW1tuL+U7Zwee8yuZsc6lUm2HcnBGY79GG5v6WTbfV6V1v1ghMLt/mGUgABBBAAAmAAAIIIAEQQAABJBpwnK2WSJTMqdlYA76eT8YKxHJ42kyyp38e+HQxPfSl7svHfXgKZST1Vuox/8HyHwMggAACCCABsKr8FGAAiCO50cIM93UAAAAASUVORK5CYII=") +} + +.fileList .ico-folder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAY1JREFUeNrs3E9Kw0AUx/FM/kwwxSagqdVC9649gwdw60rIJQRFUTyDK4VudOlBvIdQjJvWVWPr9I3o3gaaNOH7g0eyCUM+mcybbKKMMQ4pHxcCAAEEEEACIIDNjF/mounr2SbeSyb1UNFYb1IjqVu/JRPhSOq+c3DoeDpa+2Bm8TWYTcYXxfTdawNgKvWytTvUXiC3Y4q1D6hk4QvjniOAWdPXQE/qKUz2h0EnrnRg5aqfh9d0wDvdTY/DOKULl8iJHyXnYbxnV6X6urB0VC3HKynbWgeN2T5EiaO3d/5epVq3MTe625Mn2XeUFzRoAn7LxJtvxD4wsx1FuaaSDtbGL5G07teATzkACYAAAgggARBAAAEkAAIIIIAEQAABBJAACCCAABIAAQQQQAIggAACSAAEEEAACYAAAgggARBAAAEEkAAIIIAAEgABBBBAAmD1gLlZzJFYMb9muQV8nE1yB8TV8KyZZGT/G3NdfH5oqVM578Pzr4ylnqUuFT/ipokACCCABEAAm5mlAAMAvOdJo4EAKcIAAAAASUVORK5CYII=") +} + +.fileList .ico-folder-empty { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABBJJREFUeNrs3MFPE3kUB/D3m+lMp62kpQgEiXDw5CaYuGsUXTxtOOllr3oxcvI/0JiYaEzWv0AvHrzgcU941kQPxuxmEzl42gNshAXRpSC0nbbz871fmdpGBdoK7LTfl/zSMm0ymU/f7/febyZBaa0J0XpYIAAgAAEIQAQAARjNiIVvVv+8GvVrmeLxcJ/O9ZbHIx53Yh2SCD/xuJ/M/kCWk9zzk+nAH/Y3lm6WNpfsTgDs5/F7vGfUteRyAn/PT6h4uMkBYsCpqK+BNo9pNzU04njpfT2xUsr8eFEHvOskBiYlG1CFm49fY/He625qUFalg6vCXH1dfr3F4wqP4ci0D/FechJ94VQ60DbmtpMcNL+kstzo5J8OeJT/F33glKwh5nfchwrWiTuR/oOeBtjKARABQABGM2LShAYrT8ny50jpva/CJTVEvnU00mjKdiiWyG71o+V/yA4WvvnlfKHaayW89u87qMxJ8vrGyYt41ulKiYq5f0kFmmJOsMz9X3GrKW3cEq3nFanBS9X3S4+pJ9H6lkn1niIr+yOfIvq9puKFL54eIH9tmWIq2OCLKtU+rHCDb1uf8dInfjPHc685G1emKeG2iHf4545a+5SlttbAuu2QoPn2CKniPLnDVbzc6xvms3j/BP03/5jKFaJDnqbd9t6diPdFEZEQmLI3RgPnZ6iUmyUnPUbrb+6R5aTJTjEqvw5dnDPH8gv3KRkHXkMbE7N5FGbp498PDJ5knpMZo+ToJYMoqMWVF7TJWbibaayypzseL7yZ0FAwDh27ZrJMc9HYXKxOWW2nKTs+XRUPclTgJTNfVJSI669iGry+s93TSNcXDMk8ybqgZ4JcRzfgfXh52aBtlDKUOTdDBeeEAe5WPAMYrn2CJ5knU9ROjpjpKlCCJ1NY8FLOau2Yqdib86ZidyueARQAgZA1zjtygeKHJ8zfghbiCag3dKEBLwQNq3E34pnrLj8/qvPc2wpOz/HrVNngFoZbFoELoQRPMlRgg1Kuhheufyp7hvHGuw5vbW62ugYKRMZbpcKbauWVKlyfZYXFJ8DbqQpLG+O5mnu8J7QmVZix6tc84O0AaHYbjuTlDKX4qOxYv7bmAW8bwBoiVfu87DngNQ0YhkznAk9naWvq8QROABE7AAqY7Hn7klRrVYDXBKCg1d8wAN4ONxO2/RLwms/Az3hnzS4D0UIGAq8NQOC1AQi8NgCB1wYg8NqowvL8Qh4CIVrIQOC1Aejbx4DXDmDJHoXCXm/lEAAEIAABiAAgAAEIQAQAAQhAACIACEAAAhABwO8CGOhKGRJNxpZZYGmlXhVz7wiIzeEZM6X+kOfCk8WP75/56ysnMaV3HQHj/cWvvyj8I24UEQACEIAIAAIwmvFJgAEAn5Nwqgtf3PMAAAAASUVORK5CYII=") +} + +.fileList .ico-folder-unempty { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlFJREFUeNrs3M1qE1EYxvH3zNcx09ovaUot6FIUNyJ4A+IFeAfRXINQFIS60IV7Vy7cuPUaXEtEkG68ANEYjaZNE+YjM56ZhBIxlIykMVP+D7zMJnCY35wz7zmBRKVpKuTfY0EAIIAAAkgABLCccYp8+Nf7+4t8L3VTL+c01mdTr0w9cc7IRLhp6oW/cU0s1z/1wdIk3AmPmo+iXtM+C4Cbpt7o85c9K7udJDz1AZUpz6+KAayX/R1om3rtLW1fcs+tznVgpVT+8FSRs7BWi+XdbNSeuZXqrl7ezhbW3Mfvtj6WtwsbvLuOXt/1lrb+C97xTPzZuOeZ62NTNVM7pdk+6HVxKxfEnkPTOGkGZk1kz/W38iepLK88UzBNTMULsQ+sZx0lfyWOOlgQxhJFA3bJY3FdW7TnTATcHHWU42R4K1ceoDaWg0/PJwJylJvlUa7XDyUIYjnqBTJot9EZy2Gnl69MrR3xK95kwAxv7fpDWcPrr6zc2ht+H7D/9A9AlvAslnDU/SJxvy16EEm/UUPlpNOYqX7LFaeyMQRUSWr2f6ksX7wqynYRmmYLaiZa0PkqmZ1jDsOiV6s5oqQhOtMc38yLLzMLD74N34HKUqgURlQ0kVkEQAABBBBAAiCAAAJIAAQQQAAJgAACCCABEEAAASQAAggggARAAAEEkAAIIIAAEgABBBBAAiCAAAJIAARwIQCTdBAjUTAjs8RKlXoXdFoCYjG83EypRvaL9TtB98fb8PD7DZb01EkM3gdzva34I26aCIAAAkgABLCc+S3AAB9MnrCxlmzHAAAAAElFTkSuQmCC") +} + +.fileList .ico-fromchromefolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABCVJREFUeNrs3F1oW2UcBvDn5KQnbdYmTfPRLMnafdCLUcfGIhM3O2WgOPFiu/FCZBY6BooyvJAJiqNTL4ao6IV4oVJERbwYKoKiBTdlaLeVIV3Z/KhzW9O0OUuWpGnaJWnP3vd01LUGbba2W9LngT9Jc5JzyC/vef9vT0MVwzDA3HwsJCAgAQlIQIaABCzPWG/8IX2ivZzfS4eo95foWBFRXaIOWStkIIRFvbsisB6qZl/0gxmT+eDV1PCLuXRMrQRAr6gjNZ4mTa0Sb8fILfoBFTHx2Zw+CMCOcp8DVVGf2OpXNlWtcC7pgRWLYn545Q74qubwPmhzeu+MJlIs7ZEDi3LgruDhW93Fbqu9/gWbs1HOSrcNUEn1PKmJ25ellajgUkAuAB4EHrQ6N6zV9tuGl77QZ47ATs3hE5+kH4pa9d+Ne4HiWB1egL1MiYFXuCPWgR2yoygWY0k62PQ6IIdKiWwi3usdheGvcgQkIAEZAhKQgMsnC3I5q8VVgz13+dDqseO3xDi+P38FRy+lMGVwBP5v1jir8UpbM+Ra/KMzMRNw36aVaN/QyBE4n4QcGk5GR/HWqcjMiOu/nMWBe0L4OTKKs/EsAYtlVZ0N69016NOz+OlSetY2CZoYz+O17c0YyebxzqmhioUsGVCeqk9vDmBH0z9XgLsvJPHe6eisOa/z+EXUV1vxyFoXOu9rwv7uvxAdy1UcYMlz4KPrGrA1UIc3Tkbw2Jfn8HbvELaHnNi70T/reZFMzjyVXz8REaN0DE+0eityBJYMuFMAHvk9juODaRTEkDt2MSWQBvHQahfuDTqKvkZ25I2+WgLKuMVpeT41Meux3uEMvvgjjqc2+eGx//uibCY3BU1VCCgTnyiY674bU6up+OysDl00jufuDmDu5cWwvxYDyQkCynwzkMCuFjfuF02kUYy2h9e48OHOFrMrvynmO7kufFY0mWrr9K7ldlnfisU1u7DI1wKwoaYK+8OBmcc+7o/NnNaHewbx/JYQtoUc5hypqRZ8fk4350oCisilSlffCL76Mw6vgJTdNpObnNn+a2wMz3QPYIs4bSXe6ZGM+RwupOckMV4wq1iSYp787u8kr8YwBCQgAQnIELAcAO1qAds8UTRoE8sK8JauSFsUA2FXDA94I+JWh2aZXlD3pxvwQyyEX+J+ZCetBJybVkcCbd4hbHVHUWvNF90ua9/afvQkGvGjHkDvFd/yBlxlz6DNM4QdvsF5n6ZyRMrXyErmbTgaC+KYHqwoQPkNVcPRvIHd4CYiv6HKLsxlDAEJSECGgAQkIAEZAhKQgARkCEhAAhKQISABCUhAZl6AujFZoESJuW6mS8APrqZ0ELE0PGkm0iX/sH4wN3pZE/W4uO8nz7wyLOpTUS8p/EfcbCIEJCABGQISsDxzTYABAF5pTWwmgxlgAAAAAElFTkSuQmCC") +} + +.fileList .ico-documentfolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlZJREFUeNrsnE1rE1EUhu98ZCZOTSbYpB9GKgguBHelP8BFodtuuxLyJwqKovQ3uFLoRpeu/Q2CgktxJdgPTChNQtskTTI9N1hw4cKkk5nMzfPCyc3qXu4z57znTi7EiqJIocllgwCAAAQgABEAAZhNufqj9fmpCXupSbxNaK0DiX2J164hibAu8Wbh7iPleMHUF4sGl9Vu8/hZr/XbMQFgReLjrfKa5+RkO1Fv6gtaYnx+uKQEYC3rHuhIvPdLq2u5hTDRhS3bGj28rAPc84qVTT+s0IUn0LYblHb9cFm7UnpdWDqwJ+MLCd2Kq5k5PgQl5RUWr0sp1WPMK6+4JE9yRVlOLkMJOJTE68/EObCmO4plR4l0MBPfRCpplwGvcgBEAAQgAAGIAAhAAAIQARCAAJwbJXqpdHlxos4a31W/04zn6bt5VVxdV26+MB8Z2D76Fhs8rWG/o1pHX2XO9nxk4HDQHY1hdUPlgsUbz9f48UkgXqhO+5fK2/eVm8CdcKoArxUHvL/VOf05GvOFe4mXszFNRENMo5yN6sK6nDVE40s4LpUfbv3TE8lAzoHjddJJsg2AMw6GEgYgHhirB86qBeCBlDAA8UA8EA+khAEIQACirADsndXjn/O8YX4X1teQo5u0wy9TXcPYDLy9/FjZjj+9zcjceg1jM9ALyurOgyd4IAIgAAEIQARAAAIQgABEAAQgAOcbYD0a9CExpv4wq2uA77rNugLiePA0M9G+/j3wZa/d8CR25PsKeP5LxxIfJJ5b/BE3TQSAAAQgAiAAs6krAQYAXiGbDBfBqf8AAAAASUVORK5CYII=") +} + +.fileList .ico-fromphonefolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjNJREFUeNrs3E1O20AYxvGxndgQq04ESfhIxaalgjVnQOq2264q5RJIIKoiztBVkdi0S9acoVfox6qoEQkgjNriNLF5J1Cpm6glJHEm/j/SqySbsfLzeGZsTWIlSaLI4LEhABBAAAEkAAJoZnJ/fwg/vjL5u9Sl3o3pWCdSh1J7uSnpCBtSb/3ldeW4hZEfLOn+rkWXje12eOpMA2BF6mi2vOI6efk6SXvkB7Rk4POKVSWAddPHQEfqvVdaWsn7xbEe2LKt3skzHXDfDSqbXrHCLDxAXuQKpS2vuKBHpfRmYZl5XXndldJTcM2Y5UOhpNxH838upVSXMW/coCpnclFZTt6gDhhLx+tMxDqwrmcUy07GMoNN451IJe3LgFs5AAmAAAIIIAEQQNOSzvPAJFY/z7+o6/BExZ3rh/WA3IyaCWqqMPfk9kFdFgB/nH1Wvy6+DueOWE6APhl6i4pffpaNSzgKvxnR5sQCxt22EW1O7hh4l/Lq86G00/p0zCzMMgZAAiCAAAJIAAQQQAAJgAACCCABEEAAASQAAggggARAAAGcwqS6tSPNLRlGA9qO23cz0L/2y/RD121mBtALHvfdHzhor/SCWnYA/fmnSv9YMAq/S0+MHtibPcFbkjZXMzQGWrbyy2u9YhZmGUMABBBAAAmAAJoK2Ey6HSTumTuzpgY8iC6bCsT74WkzyaG+lXvdvmq5Ui/l/SI8/5WG1AepHYs/4mYSARBAAAmAAJqZGwEGAN+Yh/QHkxJLAAAAAElFTkSuQmCC") +} + +.fileList .ico-mix { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAhFJREFUeNrsnL9KA0EQh/fukjtNNBc08S9oJSJo5TMItrY2CnkJQVEUn8FKwUZLa19BO7ESKwtRTBASEU1Mcs4GtVFRA5u44fvBcOF2YG+/m53Z4SBOFEUKNS8XBAAEIAABiAAIQDsV+8mhdLpsy1pyYrstmutabF9sK9YhgTArtpMcmVKenzA+WVR7GS0Xb1crpTuvEwBmxY66M2O+F5flRBXjEzqS+IJwQAnAnO050BM7CNLDY/Fk2NKJHddpvDzbAW77qexcEGapwk1oIZZIrwThoM5K7avCUmV9ua6L6XI7as3xIZFWfm//+1Zq6zFm008NyJscUo4XtygA6xJ41X9xDszpiuK4UUsqWCd2Itl2bwNaOQAiAAIQgABEAAQgAAGIAAhAAAIQARCANqgl34Vfnu7VY+FCVZ+Lnx+gK1TJzKSKd/cZ9bU6Ah9uzr5cpJa+r8dN+1odgfVauXHNTMx/GitcHn+Mm/QlB1JEAIgAaGkRcWNdql59biT278ZN+1odgT2D08r1gq8fQO7rcdO+puQUT5ai1PgMe7EJla7OyYG0crRytHK0cgiAAAQgxxhaOVo5WjlaOYoIAiAAAQhABEAAAhCACIAABCAAEQANA8xHtSok/qg3ZnkNcK9czCsg/g2eZiba199ENioPBV9sUX4PgedXuhU7FFtz+CNuiggAAQhABEAA2qlXAQYAvkAprthin2kAAAAASUVORK5CYII=") +} + +.fileList .ico-musicfolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAexJREFUeNrs3L9OwlAUx/HbFopCBKKAKImJg4OzPoCLiaurkwkvYaLRaHwGJ01cdHR28An0EYwjCREYwMTI33qukcVJVHq59ftLTiAMtP309B5yB5wgCBT5eRwAAQQQQAAJgAD+A8DW/e4kX0tZ6jykY1WkLqVOYhFphDWps9TiqvL85NgPFvS7pXazut9pPXtRAMxL3UznlnwvLpcTdMb/2LpKJTIFJYBl13I8T+oqkV1Yiqcy4a59rvNx82wHPPXT+c1EJm/sBGwG3I4ls3uJzLxelYydREwmqy+vh1J6xJZs0RM85c/MDR8lc+chdeynC3Ini8rx4hY14EAar2f+RurfT3qiOG4QygSLWvQamDf9GNgOSAAEEEAACYAAAgggARBAq2JsS7/xdKeCwfh2U9zYlJpd3ohuB44TT2fQe4t2Bw6TW9n68++sP96yBjJEACQAAggggARAAAEEkAAIIICRTCjbWc3Kg+q+NujAnyaqeKF1YJS72/o10HR3M0RsAPSTuZE+Zw38knRpnQ6c1JjubuunsOnuZogACCCAABIAAQQQQAIggAACSAAE0DLAWtDvITFiPs1qGvCi3awpEEfD02aSS70jfdR5qftSO/K+CM+3UpW6ljrgHyx/GQABBBBAAAmApvIuwAAZ2pRkuq79SQAAAABJRU5ErkJggg==") +} + +.fileList .ico-picturefolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+tJREFUeNrsnE1PE2EQx2d3u7vttts3WlqKKYrBiO8Rj179AF49mfAlTDQajZ/BkyZc9OgX8O7BA0ExctGIECmlon2DvnedeaAQA9JW0227nX+YLIGQ7v52npn/PLtBsiwLWP8uiQEyQAbIABkgiwEywBEAmH93d5CvZR7juU2f9R1jAeOJyyGJMIfxzJuYBUUzev5hVqM2Wclt3q/mtxQnAIxivPZEkpqi4uVY1d4vWxlAD4wDApyXhxyegvFSD04kVW/A3tonS+LmDTvAp5o/eksPRPt2AsMM8LbLCN7TAzGqSn07CRd2Vg2PDzGoxU4OCz2EB5o51lpK/TsPjMeafxzvZBwkRR2iBGxi4tX7fyPJP1FHkWTLlg7mNFENjPZ7GQw7QBYDZIAMkAGyGCADZIAMkMUAGSADZICsjtXzh0r1SgGK6WU85u25IN0PvtglcXREBtoJb++G5fEzPzorA0n+xBxo3t4+/KkUUlDYfG/rDbOhBlogya6ewyPp5oTzamBbvFYD6qUsNOolUN0hUDQvN5FO1ajuQD61KI4tuQNJ8I1fYICdiOpVs1be65qaCaVfX6GcWwOXOwBu/3A8Ye2bD6RCT+EJnUZYpwQ0M34FZEXHZrDBRrqD4icO1GAOJMn4peDvmgywbe1w+0F2uXHZru7bDgt2f36GRm0XVCPCNbC9JGwWF7EOLkF27e3BT1VPCIzQGQbYicgbBpM3oVpMoY0pCxvTqZejzt3Ev6GMdemmyGaKkfOBiurBRjLdsSmnJV/KfhPwjs7B5ugB7MYzFtIfoF7OYf0MgieYRNPtE1lH42KjWoBKPsUA/zbjig0CSUKrc/XIMm/tvBjhGdj+8sb5AKs7Gax76T+Gfsom3YyDRh1Yko/srtCI509cP7HOCQvk5Ays7W5DMbOCy614eNGKSm+9C1BkoBXVACNyDnRfXCzN/MYiKLoPAokbA/n+om0Ay9k1AY8ageaLYQ2bQssSPqxxtRKOcevYIFahkFqCImYTZRs1CzN2eWBf/rQHIE4dxcwnHNM08EZnj7Uq1I29mHme0BTsbK1ApbgpvKI3ch6N9dho+0DasiIYtKlKM++JoxHOwubENRCGhEY6abAf29h2dkZ4ui28Y7pCt7fKuQCpOfS+zq4fjImOWMLUGFoqpJdF2HJRNk4kPc1A8mVG+KzwePbt8gTAh13bERlIXdcYmxHhVPGbCQyQATJABshigAyQATJAFgNkgAyQAbIYoN0AM1ajziS61D6zDAF8UcllgCF2B4+YoRZoQ/VRtfBDw7iD38cZT0eiZ66vMB7wf7D8TzFABsgAGSADZDHAfum3AAMAO2tj3VmRR4EAAAAASUVORK5CYII=") +} + +.fileList .ico-videofolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABSdJREFUeNrsnNtuG2UQgGft9a7Ph8R27KQyLVSgCCICBSTENQ/ALVdIeQkkEAjEM3BFpd7AJQ9RiQsqJGgJIIFapS0hjp3E8dler73MjO3QSNheO+na3sxIv7KKvPNrP88/x00Uy7JAZHbxCAIBKAAFoAAUEYACcDlFneWmyr2PFvFZdnB949Be+7ju4PpSdYkh3ML1dWh9E7xa8LlvZnU7G+1y/hOjUvC6AWAK1/eBZE7z+vBxLOO5b6ig49NjaUCAO8vuA724vtXj2ZwvFHN0Y8Wj8Je37AC/0qKp9/VYSqLwDPKBGox/rMfWyCvNLwpjRNXw52e4KLRuLE36EIyDFlkdHqW5pjFfaNE0fpMZULy+JTLAHhqeuRB54A5FFMVjORLB3FiJpOZ9DKSUE4AiAlAACkABKCIABaAAFIAiAlAACsArI4szVLJ6YDSOwagXwGyVwTRq/DsWxQOqFgbVHwMtlAYtuNqf7AhAZGR1oVnag+bpHo0LR8I12xVerfJTbvwG4tchkLgOiuK9ugA7zRLUDh9At9Ps+xPVD3okgxaWYkiqHiV6CK4Kva4BRq2A6xCv29A4/othRjKvgy+QuHoAW5V9hLfLgAhccPUm+KPX/ueTygAkINgkQHqTwdURYM9sQfnvexBeew3v3bg6ANvVA4T3K1/r4QwC2EKXZvcoKuCP5fC+LFTyv0AH/SbpUtAn6pGs+6MwBYjqAF4g/gJEsttTwHsGIx7x2MZbZ1ZLOs3WqdsBWlDN3+egQJYXSm1eUJ+C1vsq+Cgqo85q/gE4PSN2FCD5rm6nwT4vnNm6JK0KRDPbbJGkm/ZwJ0C0kMbxQ74MJV8Zm36cPvkBaoXfOMWxe5yDiRf5unHy6L/80U0AjcYRpx/DVGWsn8S0hSyp/PRHjrR2hPwpgaTPG/WiCwHWj/pRN7LOx85WwMHEuYTWSJF2shl6ODL3v6xj9wEcRkgtlJzu5GN1Ut7/CY/mw4mf9QVXzu3lKoDk4Dnx1CIzRW+qPCoHP6N7G+0XqV5+di8XAbTwwc0zhz+zG8AyrlbYHRNMtEG8Mh1LZxwCeHnv3li2I6ziriPsGVrHqI6LDaGmQXhM8m11jQtb+eIC9PXfnjeN6kz3BzDPi117h9OgkYGKeogoXl/IfQCHLadpczSypuj6LUy+X554LDuNk3N7uQqgFkqddWLsOngCkci9d3bvpEqnXTs4t5cTojpngSvg1ULQNeoMsZ9Qj4AdXgNVj0Bw5SXbwaB5+pj9K+1Be7mymRBI3OCf9aM/x9a50ewbCO+mbXjUrW6UHp3bw5UAqWus+uNcr1YP7l9ak4JaZGR9pNvpzrTD/UAFrWubUxqavtWLf1wc3uEu18qkk3Q7lf/NCWB/cBRdf5PbWeS3KljnjivPJtXI7eo/rIt0jktxXAOQIxceNc7pvDq3uUp7d6FVfmIvOqPV0Rj05PFd6DRPWAfpIp1zeRaYk9CQPJ57l2cZPBgq/M4NVy2c5uG5R9UxIQ5S7QZds9nv82EtTHlkb1BxUCs/srY1F8ubO8DhcY5tvM1W2MDIPBycT2rL05gziIk1jznnLAvxageB0HJJHrAbmAx3WmXo4fXQ0ihAeHwB8NGrHeEsWmYAFkUW6g+uCQzVvAFYHpG3swSgABSAAlBEAApAASgARQTgfAAWra4pJKaUAbMiAbzdLhdBIE4Hj5ih3KFmwudG9UjD9SFeZwSPLcnj+g7Xp4r8I24JIgJQAApAEQEoAJdT/hVgAK5f795U9dlNAAAAAElFTkSuQmCC") +} + +.fileList .ico-sefolder { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/9JREFUeNrs3G9IG2ccB/Dv5ZKLuUSN1miMq3/atVU6aauu615so6ulY+2gK0ihrFhwZRR801LGcLJRcXtRxmAwBhtzyFYHZUPYi/XNBlspdK3rnznHWitpFfvHNkprUqOJJrfnuZYiYiA6tRq/X/jhn3uSI588d7/nzqBiGAaY2cdCAgISkIAEZAhIwKUZa7IDgx0HFvtrqRP19QLt65aoVlFN1hSZCJWivnD6yqBq+rzvzIiNF0SGB96PBu+pqQDoEdXuyCnUVJt4OUZ03neoiBOfPTMXArBuqZ8DVVFtdnd+oc2ZuaA7ViyK+eYtdcBmLcOz3Z7pWbxNpP5c0bzs+GPL1v/7FG9adfd79sw8eVZ6aoDK8PlaTXz9QJRsswWJBjbEf1tMeBB40NJXwJqmPzW8YF+XOQOPaRm54p30QlFtiUdfnzvAjOLKOXiWuJh4E4tiHVgnO4piMRakgz1aB0SRKpFNxPO4ozC8lCMgAQnIEJCABFw+mfPbWT69FNW+d5Cl+TAWC6F7+Cx+H/iGgNNFVWzYln8QG7J34BnnevN3ofEhE6zN/y5ixjgcajqy7D7cj9wm4OR4HWtwcO1XyHOsMn8eivTjzmgP7BYduwsbsDlnD1p6DuHu6HVE42OcgZOjW92oLzsBt+bFucAPaO9rRnjigblNzridK4/g5bxa1Je2ofnvakRiI2wik7Mt/20TTx6qJ/xHn+DJjIrz3o+9x/DrnS/NQ/dVMZZdeEo2Zu80oU7d/CzhmJ/7PzVhN2S9xi48NTlpK3F6oBXHqzq5DpzNg87c/Q7lWduTGpvKDWTWM9Af/NNsEg0XqxAcD3AGzjQVOW/AoqioKWlKOKamuAmfb+lLeqYuK8ASVwX6Hv6FTdmv463Vn8CuOp9ss1nSsKeoEa94a9F1/xezeAhPiVzrfXSlGvtWH8cWT425aO4f6ULciKPQVW5eoXQMtuPkjUZeC0+X2+FuqBYNLdcO4cXcvXgpbz+KXBvNbTdCl8wmIwF5MyFBvvUfRuWKXbg8dAoXBn/CH/dO8m7MTBIY6xXrwF4wvB9IQAISkIBzEodY1lQ418KtutiFk9dXsF4vwWZXKZ7Ti2FTHj1dz9gtnA/9i86wH6PxKAGnZk1aAapc67DJ+Sx0cek23XZZe42t6Bzx48JIN/4J9y5vQK8tG88LtBfSy5I+TOWMlNCygrEwOh5eFXUlpQDlJ1SNjKJydoNZRH5ClV2YyxgCEpCADAEJSEACMgQkIAEJyBCQgAQkIENAAhKQgExSgAEjNkGJGeaxWUACtkSGAyDizPCkmUir/MP6h9HQoCZqn/jeS56kMiDqe1GNCv8RN5sIAQlIQIaABFya+U+AAQBLjB3QxtGdmwAAAABJRU5ErkJggg==") +} + +.fileList .ico-access, +.fileList .ico-mdb, +.fileList .ico-accdb, +.fileList .ico-sql, +.fileList .ico-db { + background-image: url("../img/ico/ico-access.png") +} + +.fileList .ico-c { + background-image: url("../img/ico/ico-c.png") +} + +.fileList .ico-cpp { + background-image: url("../img/ico/ico-cpp.png") +} + +.fileList .ico-cs { + background-image: url("../img/ico/ico-cs.png") +} + +.fileList .ico-js { + background-image: url("../img/ico/ico-js.png") +} + +.fileList .ico-fla, +.fileList .ico-flv { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAz5JREFUeNrsnL1vEmEcxx8OOE1LEUyKthATgqYJ6eKEM8QRZ1+WmrropJs6mLjof6CJL9HFl7nMJY7SmLg0JIaBRBKrkBhDW2Io3Hm/B69eT6D3Qjm4+36TJwcctL1Pvr+3p+F8siwzyLoEIABARxUYdrJcLovK4YGyVpQVd8tFxxOJp8rhltH3nwiHrQFU9DASidyNRqMsEAi4Al6lUmGNev3mfCzGzEC0CnCV4AmCwCRJck3YnV5YYN+3tkYC8bAcOE/w3KZjosghkhOVp09QRByE6OkqPAqInm9j7EJEH2gTIgDahAiANiECoE2IAGgTom/YdpYyC8upVMpVcBqNBms2m6Y+k06nfVZHOddJme1ZKBTqucf3j8ugx7VaDSGsVTAY3N8Y0Uaf1Y1lzzlQhUjqdrv/uW6QEwGwD0QC1Ol0+NEsOM8D5Bf/N5S1W3UT78AfK9dNvf/U61d9P69/3Q5ECmVa+jBGCBuU3+/fd6IZeI4CHJWDRgmRoGmrsRGImES0MATBtAMBsA9EM//GAMB+862urUERsQgRDhwDRAC0mzOBAACdHQed+sWHjXST1mjDgW5z4LQ4DA4EQAAEQAgAARAAARCaqEb6KEfBcTbpcOC0OdCuOyZtBIQDARAAARAAIQAEQAAEQAgAARAAARACQACcIg39tuaXbA53JlO0VFy3/m1NURQ9Da/dbiOEj1Jj3dIP5/MscuVy33P1R4+ZmEzy81+vXhv6c44vL7PY/XusU6+zb7fveAegqkGACKARzVzIMGl3lwViMQ7z9+YmqrDhP3h2loWyWdZcK3AHEky0MSZE8Eg7xSJrlUpsJpPhUD0VwmfevjnwnGD8fPHScPi2PpZ4CNOR8ip3ZKGAHHhoS6XkSFo768Vei1Gt8kVQPQXQcvjmeuF78sYqX3q4BBMAhxQPynfktF/v3h94PfH8GXehEwCnpoioxYLynlZqLqQ86EQxmUgH6ouMCkrNeXq1NkrcgU4Uk+GbCflLXXFvz9PjXjsYlJYKa35LISyfO7tBd7Pw4u3i6Zr5tadSn6yH8OLixY4kf/BVq+fZ9ra3nDg3J8nJ5GeWiOcshzDkwlEOAF2mPwIMAMxjFWM7SdA6AAAAAElFTkSuQmCC") +} + +.fileList .ico-htm { + background-image: url("../img/ico/ico-htm.png") +} + +.fileList .ico-html { + background-image: url("../img/ico/ico-html.png") +} + +.fileList .ico-java { + background-image: url("../img/ico/ico-java.png") +} + +.fileList .ico-log { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA9lJREFUeNrsnE9IFFEcx9/srlMbq+2fXHZdD8pG0WIHKQ92iP6QYhRFtzyVHiI7FJ2KIAiMTkoeMoSwTnoqhKLQQ0kHgzI8FAaZGIiu7JKKRVuLO9P83jrjpOu4M7Pu6rzfF5aZffNmYT78/nzfWxhOFEWCMi4bIkCABZVD6+Lo6CgvHW5LnwvSJ2SVhw6Vlz+UDi3Zzt9ZUmIMoKQ7brf7hsfjIQ6HwxLwxsbGSDwWu1zq9xM9EI0CbAZ4NpuNCIJgmbQLBINkJhrNCcT1amApwLOatvE8hQiRKH3txCZSQIhMd+FcQGTexpiFiD7QJEQEaBIiAjQJEQGahIgATULktLazpLWwGA6HLQUnHo+ThYUFXfdEIhHO6FLOcpLW9sTlcqWjh1vmstb55OQkprBaRUVFysaIOvuMbiwzF4EyRFAqlVoVdWtFYt4BTsV+5AVGyO8zDBEALS4u0qNecBsO0OiD5VNyKqu36vSCZN7GAES73b4qjVdGJdZADckAIRIz1UIEmCVEgKbuxpjCOgW773ojEAFmgKjnbwxmbYzm+nYp+rIx10zbmGwgYgpvMEQEaLZmIgIEiAC39FJwq9uYQrsCtDGYwggQASJAFAJEG4M2Bm0MCgEiQASIAFFoY9DGYApjDUQhQASIABEgCgEiQASIAFEIEAFuWmluJnQ8GkBCkrraI8YAgnieZxpeMpnEFC5YCudKh2urSP2xg+TW3SdrzvF6iknd0QNk/74KZezbxDQZePORTEX/35wNBX107u7KsnXnWgLgegIgFxvryOzcT9LZ/ZxCAKBnGmpJS9Np0t3TT8YnonRuuDJImhrrKbDWth6S+JOk98tz2zqf0t9hqgtDNCUSSQneCyWCAMLjngEK6mzDIWUunMMYXAN4ILgn/f0vjXamIhCiB1Kx//VwxusfRr6S8+eOkKql1IbI7JdSdaUAZmtbrzVroJY87uJ0FM1krl3TS+Net0sZG5cikKkmspGC2qluJpD6UAeZATg3ny74oYBPaRRqlQXS/+7Nzv9aHgsuz4Xap9THk7UkXFHG1lIOGgA0hZrqvRmv11TvoVH1+ct3mrrQKDLNdW7n8w5v03Rh8G9OJy/ZkFO0qcjNQk7PvldDSqPofTZIvSJcA2hqGwT35Fuaby66dP2+mIulnGykM6nv5RDttGaN9Nt3n+i8emk8lzUQlnJd7dc4QwCv3nyQEkSO6eWejROFjntX7IZSuDzofQ8vp2HxdfHwzPDsoYBn2HAX9nldJwRRHJyJzVf/TiSZisQdTl4I+H0ju3zFxw3XQNQW6cIIkGH9E2AAawxpPqroZK8AAAAASUVORK5CYII=") +} + +.fileList .ico-mht { + background-image: url("../img/ico/ico-mht.png") +} + +.fileList .ico-php { + background-image: url("../img/ico/ico-php.png") +} + +.fileList .ico-url { + background-image: url("../img/ico/ico-url.png") +} + +.fileList .ico-xml { + background-image: url("../img/ico/ico-xml.png") +} + +.fileList .ico-ai { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABLNJREFUeNrsml2LE1cYx59MZpKNSTYb2tUsG0trqsX0YrVUL1pvpNSXFgr1poIgpUVEP4FXgvQ72ItCL7wuxStxlQVfSxFBS+lCXSPupl2XTeu62d1uncyL5zmzEyfZ3UlmEpJJzvOHw8mZOSdkfjxv52RCpmkCyb8kQkAAuyrZ7ebk5GSEdedZ+5q10X556NFs9nvWnW12fmpw0B9ApgtDQ0Pn0uk0yLLcF/CmpqagND9/ZnjrVvAC0S/AbxGeJElgGEbfuF1mZATmnj1rC8RGMXAY4fWbopEIh4iWyIYXKYl0EaLQWbgdEIUvY1qFSHVgixAJYIsQCWCLEAlgixAJYIsQQ27HWWwvbOZyub6CUyqVoFwue1qTz+dDfrdyfSe2t4dEImFZT+g1l80+F4tFcmGnFEWpHow4vc/vwbJwFmhDROm6vs7qNrNEArgBRASkaRrvvYITHiB/+DVXdh7V9bwFlsdPgbH0F0jJLAwe/qEjENGVsdW7cTMAA5VEtH/+4PC4VbAex51QOBzmzSu8wAFUn45bDzS0o2bcSYjOeNhTFmhWVqBSvMk/x/ZYp+w4xuudEp6+96wFqjM3wNRVCKfeBvnN93mPY7zeSSFEL39jSEFzX2Vkf02vTl9vav3yrXO8tUP1ZU3gAeqLT0FfmLKyYmZ/Ta8//5Pfb/jQksJbu9QsRDlI1heKJEF+Y7f1w1iPY1Nd4vdjY6ddvyN+4Lu2/66eSCI8zk1PWG6b+RBNyTYpa8zdeILPC6K6DrDy911uZZbb7qt1j7Ux3sd5bnrx01HehANYrfUcFlfdrzosspM1Yc8ANJZnQZv/rSbm1cQgR0zEeTifADqtz1Gi2Fl3XZZzXG+2pBEDoGkwt3wNxK776uW8zuebwXrJqWtlTGXuPhir/1bHS9fPNHZ5Nh/XbQZbKAv0mxSClky6YoHG/wtQmf3VShTRFAx+dglC4YhrrVi+chLMl4t8Ha6XBtLiWqAzlkVzn7vC45DZ/Wjuiw1jp6AAx9eDaSAn6CC5cccBaqXfq/Vc5J0j3IWb2peyeTi/vn4UDmC1lmM7jOjOLz2t5fPtncnMhHgAnafOSvYASPGMtx/L5uM6XgZ1+LQ6EADtU2fUwHtf+foOe103Tqu7D3At+MvbPqj+ceRVuA7XByWZCPd2llcVCgXXt7Po/cBePo0hgCQCSAAJIAEkgCQCSAAJIAEkEUACSACFk+v/wvKd0zB9hwBB/rY/gKioLDbAlxq5sHgxcPjgBcgevwzx3KFab9g2tuF1AuiMKYkMB2VUlmHLWx+TBXoVWhfCW3x4iYNEoATQC8B3D8HK42uwWrReKo9t/4gANqsYc1lJSXB4hrrC+2T+GAFsVluYtWnLc6A+L/Dxf8VfONBYgGNhYKo8jHWx7RYozLT1YFdn7hJAV/ddi3WzP5/g7msrNXYSkruPgRS5SC7cKPvasc8pO5nEc4cJoFvyQBfGmFcvjIfqQoEDlpSBwAF0fbno0Y9H9AisSCEQU0hGhbix65urYV8WaKZ23dMN64tEhMefPbXzvu8kYsZHP9XAuKGXn+yFypJYBw9K0jDTOx6Y8ewnvl2Y1INbOQIomF4JMACqJM9nuTUMmgAAAABJRU5ErkJggg==") +} + +.fileList .ico-bmp { + background-image: url("../img/ico/ico-bmp.png") +} + +.fileList .ico-cdr { + background-image: url("../img/ico/ico-cdr.png") +} + +.fileList .ico-gif { + background-image: url("../img/ico/ico-gif.png") +} + +.fileList .ico-ico { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA7lJREFUeNrsnEFIFGEUx9/szu66uOvumCtuSlREkB2ChII6dEgisFtCEQVBetBD4KU8lBAFURejg0HSqYgKjwke8tBBrEPSaYMMCrRW1NpCxTZmdvrep7vsrLObO+vubPO9P4zfOPNWmB//9733fYsj6boOJOtyEQICaKvkQjdjsZiXDQPsuMiOZqc8dHNLywM29G42PlRXZw0g041wONyvKArIsuwIeNPT07AwP98TaWyEYiBaBXgJ4blcLkilUo5Ju6ZoFObi8S2B+K85MILwnCaf18shohPZr0NURGyEKHQV3gqIwrcxpUKkPrBEiASwRIgEsESIBLBEiASwRIiyaHDq2LoWl3Ob1dfZ2Z5Qa2svAVwXW9tDIBDg55IkZa7nO5+ZmaEUzpbH48lsjGRvJlvdWBbOgWmIKE3TNrgunxMJoAlEBKSqKh+LBSc8QP7w66mcvVVXLMiqnwNXJ19A4v45PpYLotvt3pDGua78bwEm348ZxnIIAZpBdMRKxHfghGEsN8Rs5zmiiPiPnOVHJZT+6gJbGsc4sNJCiMV8jUEATbTZAlLVKYyVN1fK5acVhUgOrADEqnVgttvM3Fg1cyb5jAASQAJIAEkEkAASQAJIIoAEkAASQFIxqrrdmEI7L3bvEZIDRXCg3Y4iBxJAAkgASQSQABJAAkgigASQABJAEgEkgATQwSq4H1j78DTMCQ6oFn/c+2AN4FqET2yCapJS2LYULod8uw5C5MJdSIwOwsq70cz1UHsXBLP+H+RP/COsxl7D0sRzw+fN4lamRg1/y9EAzdTYPQSumoAB6rbO6xA63s3PESLeb2Dgc+OUU32gdPTx67mwhajCwaNnwBvdCz9Gbhlc9H3kJiQ/T7H7a26rbevgcYtPrhriEi8H4df4MIctK1HxAPpbj/FUxVTM1cLjKzA/3MvdhSAxTk3EN8SlgSJk4QCiq9Sf8fxFcB2YqyZoChmV+r3M4r6BHBbQgbQSKVHoKnRhvvmxZeAVuPxB5rKlvHGY4rKyvaCTHQsQ5zVsbczg1Oxu46mJabw08YzPl2aFIj332dHK2A4QHxoh1XdeMxQB7BUR7K/xR5k4dGvD+TuGOGxjsAJjJTYrMI7vA7EAYKUNtXfzfg6PdGpjv4cONcZ1mcbZ1UhLhV4486X/kCapSeZSCcSUDrrHn9p5+43bUgpr0f2TkNLwtT4CsmPPzJ5da9o3aTmF9fodJ1WAMffCp8PS8qJQ75jRAw2qFtnzFhlYTmESNdIEsNr1V4ABAKATVWjNArx4AAAAAElFTkSuQmCC") +} + +.fileList .ico-jpeg { + background-image: url("../img/ico/ico-jpeg.png") +} + +.fileList .ico-jpg, +.fileList .ico-JPG { + background-image: url("../img/ico/ico-jpg.png") +} + +.fileList .ico-png { + background-image: url("../img/ico/ico-png.png") +} + +.fileList .ico-psd { + background-image: url("../img/ico/ico-psd.png") +} + +.fileList .ico-webp { + background-image: url("../img/ico/ico-webp.png") +} + +.fileList .ico-ape { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA7JJREFUeNrsnE9M01Acx9+27g9jIkOZTMAYiTEZF4melJtwNJ6MHgwx/jlovHhSL0ZOejNGo4l6Mh7wZMSjHDyoB4NycolZNJpFZhiJSmRs0K6+X6FzsNKtLdtK+/0mTbf2tUs/+/5+v/fe8uaRZZlB5uUFAgBsqgS9k8lkMsB31/l2mm/dTnno7p6eB3x3sdb2W9vazAHkGm1vb78ajUaZIAiOgJdKpVh2ZuZCZyzGjEA0C/AswfN6vaxYLDom7LricfYzk9kQiNVyYCfBc5qCgYACkZzI395HEWkiRFdX4Y2A6PpujFWI6AdahAiAFiECoEWIAGgRIgBahOjRm87iY2G5r6/PUXCy2Sybm5szdE0ikfCYHco5TnxszyKRyLJ7PP+5rPc6nU4jhMvl9/tLEyPl0Wd2Ytl1DlQhkiRJqnDdek4EQA2IBEgURWVvFFzTAM4/e8zEb6m6f46wey9rPXFOv81KKJdP1RkF2fAc2Ah4Rj6HIPp8voowXutK24VwZOQS88V7N/y+UibN/j65Z+gaFSA5USsX2hJgPeBZuS9BJGjl1diWIWxn0ey7UQcCoAZEIz9jAKDW+LbGAuLqfmAtEOHABkAEQKs5EwgAEAABEAAhAARAAHSl6jaUmx97yMTvX+BAs3IDvLo6sBGyg8s3dQ60g8tRRADQoQCFPftMnUMRWVHr8TNwoO2/fRu4fFN3Y+zgchQRAARAAARACAABEAABEAJAAARAAIQMSXe1ZubyKfwzGVf89lPzqzUDBpY9OVGLVRYhIoQtqmkTqltGLimzxrmXY6zw4W3puJ8fo1VMayXnc2zh1bjSdr02pOKvWfbnzqizAXqj2xV4BCXQP7AKoCparrX09fMq4OGjJ1cdW9vGNVU4eOBwyVEEkoBWU27iRcmhru/GBA8eYoXJd2zx09RyoerfjxxYqyhkPaEwW0x+VFxIEEODwyz/ZkL3uvDQMWVPIeuLblNea+XBBe7Uavfa1AD9iQEl0UvTy3/msJScUqDSpjpSCw7BpoJD16oA7ZADGwqQch2BIkVv3K0AWw7QDnBsB1DNdb9vXVEcpaqFh2docIg7LIyxcLXqSy4rh6f09nk+VIsLiohO8aAQXpoYrzhH+ZA2ApybfV7zPbWKSKM70rqTCdPXzkvBQt7Vw71CMFTcefORz1QIi1273kscsBunZOiZ6dnFrt5J0yEsdcSG87L8Wpj5MeBZmHeVE+WW1qIY656SOnYcMR3CkE2HcgAIlfRPgAEAIIZMFb61acMAAAAASUVORK5CYII=") +} + +.fileList .ico-avi { + background-image: url("../img/ico/ico-avi.png") +} + +.fileList .ico-flv { + background-image: url("../img/ico/ico-flv.png") +} + +.fileList .ico-mkv { + background-image: url("../img/ico/ico-mkv.png") +} + +.fileList .ico-mov { + background-image: url("../img/ico/ico-mov.png") +} + +.fileList .ico-mp3 { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/xJREFUeNrsnLtv01AUxm8SO0mbUOKKhj4RaoUqpQsFJuhGOyImHhNCBQYQf0BZECwgJgYQSBQxIIaiTsBIBwZgQIVOREIRCBTRogaBVEjzsmPucevgJm4aX+fhXp9Pct3G17b883fOufe6jkdVVYJilxcRIMCWSqi2MR6P++nqKl3O0qWPl4vu6++/T1eXam2/s6ODDSDV9UgkMiVJEhEEgQt4iUSCpJaXL3ZFo8QKRFaA5wCe1+slxWKRm7Dr7ukhP5aW6gJxqxzYBfB4U8Dv1yCCE+mf97CItBCiq6twPSC6vhtjFyL2A21CRIA2ISJAmxARoE2ICNAmRE+16Sw6FlaHhoa4gpNKpcjKyoqlfWKxmId1KMed6NiehMPhNfd4/nPZ7PdkMokhbJQoiqWJEWP0sU4su86BOkSQoigVrtvMiQjQBCIAkmVZW1sF1zKA6acPifw10fDzCHv3kdCp89XbrIeycarOKsim58BmwLNyHoDo8/kqwrjclY4L4fCZy8TXM1D34ypLSfL38V1L++gAwYlmudCRABsBz85xASJAM1ZjR4awkwWz71YdiABNIFp5jIEAzca3NRYQV/cDa4GIDmwCRARoN2ciAgSIABEgAkQhQASIAF2phg3l0jMPiPztMzqQVW6A11AHNkNOcPm2zoFOcDkWEQTIKUBhcJhpGxaRdYVOTKIDHX/3HeDybd2NcYLLsYggQASIABEgCgEiQASIAFEIEAEiQASIsqSqszHS9A3yy+WAJPhx+wkbQJDfwmtPPCq/xUuIGMKNDOF6Kjg2TtrGjxP5yyfyx+RNItgGbXLv35DVFzNEHBzW3mYql5pdJZmXz7V2xv10ZeaekezrOf4Alk5IwXilXaT4++eGzwOHDpu2h9e2ChS6rh0Uavux09pnABngAXAAGjh4RNumZjIlwNxVYXCQf2T/xjw7Mko8wfYKqGZapQ4DATy4GcpisgRLX/t6B/h1YG7+reYUY5iJsVEttK0qPfuoIk2ACh8X+O0H5uMftBAW15+agfPAgfkaL7qd5jwNkgE43BDp2p1Sji0w3Ixt40AIOVhECg0uFHIfhDUABJDlKi8k0BZynjHcIXRhgZsSOjmpPa0rdyc3AHUXBscmtGrqjx3Q4AGYWopINUE7SBEQyulZjodycJEQugAREj5rzopM3dLC1qxQcT0W1kMWnAKhyJqz9GPo+RRyIaSEfBOLSMv+M6EQX8t5dvprkAvL86TeEW+Wqn710+KVC0ogl3X1cC8XCBZ7b077mEJY7t7zTqGA3fhl8XDNcO1y98A8cwgrndGJrKq+Epa/j3oyaVc5UW0LFeVo34LSufsocwijHFqFESCqpH8CDACOqX4es1WjewAAAABJRU5ErkJggg==") +} + +.fileList .ico-mp4 { + background-image: url("../img/ico/ico-mp4.png") +} + +.fileList .ico-mpeg { + background-image: url("../img/ico/ico-mpeg.png") +} + +.fileList .ico-mpg { + background-image: url("../img/ico/ico-mpg.png") +} + +.fileList .ico-rm { + background-image: url("../img/ico/ico-rm.png") +} + +.fileList .ico-rmvb { + background-image: url("../img/ico/ico-rmvb.png") +} + +.fileList .ico-swf { + background-image: url("../img/ico/ico-swf.png") +} + +.fileList .ico-wav { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABE9JREFUeNrsnDts01AUhm8S59E2lKbQ0PQl1AgVpQsFJuhGOzMhFlQhXhKoCwuPBcEEGwMIJGBCDLDCyMICAyqEpRFV1AqIaFADKqpIX/EDn5vc5CZO09hpXNc+R4qcHD+i+/n/z7Fv5LgURSEYxsONCBDgtoZQa2UikfCpi1vq66z66rXLoHv7+h6riyv1br+7vd0YQDXudHR03AiFQkQQBFvASyaTJLOwcLkrHCZ6IBoFeB7gud1uIsuybWzXHYmQX+n0lkDcrAZ2ATy7hd/noxBBierHR9hEthGio7vwVkB0/GVMoxDxOrBBiAiwQYgIsEGICLBBiAiwQYiuWtNZ6r2wEo1GbQUnk8mQpaUlXfvEYjGX0Vs524V6b0+CwWBePa4Sl43ep1IptDAfXq+3ODHCu8/oxLLjFMggQkiSpFHdRkpEgFUgAiBRFOlSL7htA5h99YyI35JN/x5h/wHSdvpC7W0KVuan6vSCNL0GmgFPz/cARI/Ho7FxpSotZ+HgxCTxRPq3/LhSOkX+PX+oax8GEJRYrRZaEmAz4DVyXIAI0PhubEkLWzlg9l2vAhFgFYh6fsZAgNXub+tsII6+DqwHIirQBIgIsNGaiQgQIAJEgAgQAwEiQAToyGjarVz25RMifp9FBRoNJ8BrqgLNCCuofEfXQCuoHJsIArQpQGFwyNA6bCKFaDt1DhVo+bNvAZXv6MsYK6gcmwgCRIAIEAFiIEAEiAARIAYCRIAIEAFi6IqaT2umr57BfyZTI3L/hfGnNX06HnuyY6xv8hAiWtjqNdAd2ktCtx+QlrGTZfn2S9do3tNTejDGFWituu2uiUma9x85Xsx5B4doLjA6pnXN8AhdB8sdD1Be/E2k+RQdMA+KgePz3mj+vZT+UXYCYHpeWV0uA5Kbm6HH9cUOa77TGxuh269Px+3RhWGw7s49GlCimvdEBop5eA8Dz83OFHOgOsitvH1NQQLQYn1KfKYngs/ByYHjr019sM9lDCiKVx2AAvXk5r5SVcE6pkaAB8CKAI8eozCYmnzDh0oAp79ocgAPjgdwbQOQKYrZFZYwQFAmP2gAzNuXwYVtmSUDo+Oa8sDbGOzL8rYBCIOHAYHyGChWw1heKKiTQa0GI5eI0/35WsjbuGjfT+9N68Km/awJYMCOMHgGlOW9tElky/IAhIGCjlrZJJilwd7QtcHG8uKfvGIL1rYVwHwdHKOXHXyTgDzkvBXNg9W1v/eul9VEgAXbL79ppXl4ifQkHCTySv4EgGptdy/M4ICy+DrH8tBh+Tx0X1AZD49ZljWXUjOJ0/3BvmY1D9MB8vbkLVZpZ9Y8ADTUPI2SC3UTALPuzewMn826fKlrMmH+5kXJv7bq6Nu9NX9A7rn71GNIgWL3wEdJBezEKRkYM4xd7O6fMtxEpM7w+KqivBMWfo64VrKOUqLS0iaL4d641LnvhGELY1ioiSBAjKrxX4ABABrs14sDnYP6AAAAAElFTkSuQmCC") +} + +.fileList .ico-webm { + background-image: url("../img/ico/ico-webm.png") +} + +.fileList .ico-wma { + background-image: url("../img/ico/ico-wma.png") +} + +.fileList .ico-wmv { + background-image: url("../img/ico/ico-wmv.png") +} + +.fileList .ico-doc, +.fileList .ico-docm, +.fileList .ico-dotx, +.fileList .ico-dotm, +.fileList .ico-dot, +.fileList .ico-rtf { + background-image: url("../img/ico/ico-doc.png") +} + +.fileList .ico-docx { + background-image: url("../img/ico/ico-docx.png") +} + +.fileList .ico-pdf, +.fileList .ico-fdf { + background-image: url("../img/ico/ico-pdf.png") +} + +.fileList .ico-ppt, +.fileList .ico-pptm, +.fileList .ico-pot, +.fileList .ico-potm { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABWZJREFUeNrsnF1oHFUUgM/O7272J7s1iVkT2mrAmBRREQUpiFCtCj74pFAoCOJDlIogSJ8K+qL45A8qqPhSEH0VLAit+lKF0tIXiaQBUaLd2m3zs5tkd+fnjufObtIkZmdmZzLb2Z1zYJifPXdm7rfn3HPO3ZlNWJYFJP4lQQAJYHQBzs7OKrg6hctLuIz1S6fHxsc/w9WrXvUHc7m2n0kubd/O5/MnC4UCSJLUF/Dm5+ehfP36zPDICHQC0S/Alzk8QRCAMdY3bjdaLMK1UmlPIAounw9zeP0mqqLYELkl4u6nYQLsW9kriLEFuFcQYw1wLyDGHmBQiAQwIEQCGBAiAQwIkQAGhOhWC1sTExO+b2Tth09AmzvvSVcsFEF98FlQp5/AnfDKxnK5DJVKpaM209PTCb+lXNfEXCrB+k9fQf3id5A+OgPS2FQo18HaHjKZTNN6Ere4tNteWFiA22aBG7L00TF7XXj967Y6xtU5G6B5c8G2wMxzb4J84AFP529UFyEhSCDKKoiK6qqv6zoYhtEW3E6AThYYmTFQumsScsfeBfmeh9EcDdv92frydshaHWEtQW25DGs3rm4eZwhDkhUw9ebnlmk4XkuWZXt2yQ3e1u3eCCIJAdLPnABx5G6w6qug/fZj8zh6SW35Bhi1VTROCdRU+v9fgKpCMpMDCS2wjhCN+roniBugNpat+z2ZxiQkBVKPvWBva1d+bfJjDC1MhmQaAaGbCg5BRk6mYCBXQLM0QUfgjlaPANtZYm9a4BZ3tl2zUm52RBRBVlPevwSuP5BB8CaY6PZuEEXU3+nGXq0wmnmgxVqe638Sl/edQ9dra/YQ4JhCIcDdIPZsJcKqN5s3ly5sEPV1HhHdXpRkMPSGu24L4s6xsCcB6n9carnyfUH4tSAqwHTNmzUJQscWGLlfinj0rV8+0yypph/f5tKOVc9i2SG4i96npxAiz429/twbKYCWocHqmQ9tiPL++0EaP+QJYKowsreZQMv6vECMDECjNA/r5z4Hc/EfELJDMHB0pjUgMtcgEEo6FUUX3ijp3FKY9NOvgTCQb4KzzN3zPTW5rRpxQAHpoWJoECNjgeK+MUg+8jwok4dvuS3mce2sT0ll7MU5G2JQW62Eet9dBeg0mbBtZgaTX17P8vo20AyPqdupTJgSyTSGD96m1gh8HlPTQFSS8QMoYadNE8swQ/cPD9vyc0hxBMjrMCWdBb1R95VD8za8LT8HeIym/QWQWyHWsQKWYXxGxergwSauy9vwtlIHExB9B9COtOkcr8VgvbKEFlVzLwHR6rgub2O37cYXDREXZSBrz6rUq8s4rmFkVhQst8TNOUGG0ZphumNgwODBJ5UfRuvrXrd64qnJBMJK5YfAQAsztBqmOHVgrQDDXZXP/0nJNLpssvtDTZTyP/dxMXlbIPXsGNgLQgAJ4G1Ot8I8eaO6HJmOqtl87wEM66bJhQkgCQEkgBSFKQpTFCYXJoAEkAASQBICSAAJIAEkIYAEsAfF8V25v96Yov+FQjnwwe8B3taU1HjTMxrkwmFKVx/tGD91dtfj1V++gZWzX7rqrF36HkZPnHa8xrWPj4OxVOpPgFxWzn0B1fPfbu5nD78Ig0desTvNAbnp/P3Ok5vHh4+/b6/Lp9+KbxTmoFi9Ckrx3kA6lMZQHuhPuHsKySxopSuBdGIRRLjwsYwvuwWITnRiC3BngPCrQy5MYyAJAQx7MuHPk4+aCaMh8Dce4ykWWJLKDr53QfRlgWbx0AX7jUmwYgmP952NTl30HYWtffufMi32s/Dv3EOJ2kqs3N1KDTJ25+RldsfBI75dmCTgGEhCAEOX/wQYAOhdFmkKB8pMAAAAAElFTkSuQmCC") +} + +.fileList .ico-pptx { + background-image: url("../img/ico/ico-pptx.png") +} + +.fileList .ico-txt { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2JJREFUeNrsnN9LU2EYx5/98KgxbXM6tuZFMSgaeiHmhV4IGSVFkX+C1FV20W0hBF1IVwVeaAQR/QNBERR5UeGFg1K8MAyySBCbbP1cP6zhzul9js6t1NP5oW6e8/3C4WznbBfvZ8/3fb7vOzguRVEIMi83EABgSeXVujk9PS2J02Vx9IojapdBRxsbb4hTn97P766tNQdQ6Irf778YCATI6/XaAt7MzAylU6lzDaEQGYFoFuBZhud2u0mWZdvYLhyJ0EIyuSkQ/zcHNjA8u6lSklSIXIni7TCaSAkhOroLbwZEx8cYqxCRAy1CBECLEAHQIkQAtAgRAC1CdGltZ4m1sBKLxWwFJ51OUyaTMfSdeDzuMruUs53E2p58Pt9y9bgKXDZ6PTc3BwsXq6KiYnVjpNh9ZjeWHVeBeYisXC63puo2qsRtBzif+rgtMKKhoGmIDGhpaUk9GwW35QDNDmw7lbdy8VadUZCOjzEM0ePxrLHxv1WJOVBDeYBcievNhQCoEyJDK+7GsLBB8e670QoEwHUgGvkbw7ExRnN9u1J9esK1o2OMHoiw8BZDBECrcyYQACAA7uil4E6PMaVOBYgxsDAAAiAAQgCIGIMYgxgDASAAAiAAQogxiDGwMOZACAABEAABEAJAAARAAIQAEADLVpqbCYO3RkBI6Ob1uDmALEmSHA0vm83CwiWz8GaqrWU/9Zzo2PD+18wP+v5jkeoCNXRt6C4t/ir88n1nTlJ1VaV6T0v3Ho7Ri8nX9gTIAyse3EB/Lz1+Mk6jiZer16KRoIB1irq7WgWMhHqts71JXK+n4dsPaD5Z2OXm691dh6h/4A66cF4MiKG2tRyg2L6ICpQhjSam/oLnSAvrFVdk08G91HO8Q9j4twD3QUCdQA40ovuPEup8x9bl1wjSBhXwF5rFnnB5/zlVdhaurpJEt26nqVez6ntuKG9nk/Tp8zdUoB6dXok6I08n1IN17HArLKw3KzaLBsJNgyuOj9GxKfUaxxYA1NByZGlVrVucF7krv3n3njo7mtVoU27SfPTThUtDOVlxOXq553Yp8uDV8x5TFdgYqXvOD6dx4uPiecw89mg4MG66CwfrfEdlRXm2kPrS8nMx66hK3FUtyeFQcLI+WHPEtIWhHRqkAdBB+iPAAD3HE9ONhnJoAAAAAElFTkSuQmCC") +} + +.fileList .ico-xls, +.fileList .ico-csv, +.fileList .ico-xlsm, +.fileList .ico-xlsb { + background-image: url("../img/ico/ico-xls.png") +} + +.fileList .ico-xlsx { + background-image: url("../img/ico/ico-xlsx.png") +} + +.fileList .ico-7z { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABAFJREFUeNrsnF1IU2EYx5+znW3aNvcR6qZS0shwXmUQVFAXKgTrKoSiiwjMiwRDb0ToA/q46E5JsCDpoiAKuuhCIajsAyqQspua5IgKq6mLmV+zyc45nfcs15pnunO2M7ed5w8vZ+Ocs5fz4/9/n/d956Q4jgOUfFEIEAHmLkCv16vnD+f5doJvlYXy0JVVVdf4Q1uq11tKSpKeo9e594LVau222WxA03RBwPP5fBCYnj5VWlYGUiDKBdhC4Gk0GmBZtmBi53A6YdLvzwhEzTrnSwm8QpNBrxcgEifyb/uVBFiwyhRE1QLMFERVA8wERNUDTBciAkwTIgJMEyICTBMiAkwT4nprYc7lchUUnEAgAHNzc5LucbvdlNylXMGJX9uDyWSKuof6xyXZ64mJCYxwvHQ6XWxjJD59crf1VOfAFYhEDMOscl0yJyJAEYgEUCQSEY5SwakeoPDwf6Mcv1WXcw6MTH2CxaEeYBeCWQekMdnB6OkEuty1JkQSZdISY5wKQMWLSGh4YEPgCc7i+w09ur7udVqtVmhS4WXFgczP6DTAeLAd9DV7Ur5v5uqx2Gvb6TuS+10efw2LD/uACX5P6XoCkECLr8Y54UDgouOLFHiZkJz+yO67VAfiUk4EopSvMXK2CsfHNt04S9WK+1KZXNPoubUhYoQVhpizDsx2bGWPmegzBLixy0GswuhABIgRVrgKowMxwqi8XMolVtxcjHVOA8yHcRAjrIaJNDoQq3AGxGX5r/yz1J/yAKloF+H3w1nlF+uPUvYRFR8DaYcLIn4fhJ7eFFrWB3mHK78daGxqA7qqbmMqJN8v6T+vHaixloP58BksIigEiAARIAJEIUAEiAARIAoBIkAEiABRRGv+WvNrRy3+XyheW3vH0vi1Jm1QN71IGCOspDb8a03boU4w1ntEz3272AiO9ltA2ypEz4c/j0Lgdpe6Ac4M9ggtFokiE5S19sPS2Avh/WTf8VX3mPcdAUtDKyy8HcIqLOZI9vcCzD4eED1f7D4gwJt/dReWvM8RYCIc0n4N9orHxebkAXfAsn88KWBVA7Q0tMDi6JAASEz25nPALs1D8P4lnEiLuY8Ui9AH8VhaGk+C3lnDw7sMkRk/AkzUJvd+HswPobImyrjLA+a9R2H2yY2k7lQ9QMO2+ljljRdxnc3TKRSM+Zf3cC0sJgJJU2ReFU0ypbE3nxWcGT/VwYl0gnQV26OrpuD/AHU82JVJdEXXg9WrLB6s2DwxZzYTvnTvZqhImHcpBeoUBxxtYKuvjGhlRZhx1o0AywgfpEZ45NlZR+0b2RHm7FuaGI59ppn6uJNamlXVxgNXbGHZ8h3v2M3VDbIjjEpzDEQhQMX1R4ABANtbXMt+ZZZBAAAAAElFTkSuQmCC") +} + +.fileList .ico-gz { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAP3SURBVHic7ZzBa9NQHMe/L8k6pd3WTra1rDihoGy7zJueZSB48+hJ8LSBMmEHQRC8C4qCepAd/Q8EQdi8CSroQSpaPJQhc61MHFtl0iQeutSsbV6T/vqS1/Z9Lm2T9/JLPvv98l6ypsy2bSg6R4t6B3odJZCIwVuZz+djAO4AuApgOowdCoPpbPYJgGW/7cdGRz3XcQUCuJtMJm+lUikYRrumvUGhUEC5VFqamJwEAkj0op2Va6lUCpqmwbIsaixpSGcy+LG11RWJ7c6BE5rWf6fJ4VgM6UwG5VJpCcBjyrb6z45PuiVxYAUC3ZE40AIBusSBFwjQJCqBh3QqUQl00YlEJbCBoBKVwBYEkch4t7Py+bydy+W6vX+RUi6Xsbu7G6jP3Nwc81rXHxe4AUgmk0gkEgAAxv578Xq/ubnJ3d7AlfDQ0FD9xoi7+jq9sTxwGQjUJAKAaZr1ZU7WeWWiFwMpEKhJZIyhWq2CMRZYnMPACgRQL2X3rTrpMrC6/Q37L+7D2tsRHaoJLTGO+KWbMKa8ZxKGYcA0TZim2VTGfgQKH0Qq688ikQcA1t4OKq+etm2n6zp0XQcQTB4QQgaaP2vTgPjF64idPu+736+HV+rvUzeeB4779+sb7L98BHPnu6/2uq6DMXZkNJYiA2HXzi9B5HWDTuJpmhY4AwduHtgOTdMQ5N8Y0o7C7rKllnNQnOzzM7mWVmDUqBLuAlLMAzsl7LLtFJWBRJRAItKWcJSjcBBUBhJRAolIW8LuspUZlYFElEAi0pYw0DziyljWUguUUVgjqoSJSJuBsk2YvVAZSCQ8gXbI3/IPKZ54gawW4uDTuvBQburxmNhDFH4ONNI5VLcKqGysobKxJjpcy/giEZ6B8cVlGNl50WFaYmTnEV8kP4zEjyF06wC05BRGLt8WHSYy1ChMRAkkogQSUQKJKIFElEAiSiARJZCIEkhECSSiBBJRAokogUS4T2sWV2bVL5MBmHnwmfC0pjHc1Z3pOaoH3NXCS3jm3kfRISJFnQOJCL0j7WSf81pcXTjy2cFZ7qwrri409Wm1XWd9Y59GWm2jWwgV6D44h8bPrZa1asNr647HayuC0EvYzwEFOWi/okUR2Vc7+mVwiUQgrwxFbF8koZdwmAcXBpFPY6jZ19g/7D9QKCXsnpI0Tjd40w93f0cKr3/jq7uNKNpfC/fApZzQrKsecK+FuSVsHxuxALnuJ4Q7etuHDrzhlrCZmX9rFN+dg6YD8P9bKiJpVfJiss8GLBNWevY9rxVXoD1+ctG0rdfa9pez7M/vyAcch+LKrPAY9vExy5o688E6ceoCrx33HOiXIL/0IxvU45cmq3oVJZDIP8OiU3PHSqspAAAAAElFTkSuQmCC") +} + +.fileList .ico-cab { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABLVJREFUeNrsnF1oU2cYx59zepp+JTSp1CZaNl3AYXo1hQ0V9EIFobsYQ5gMBMFVWGFjvRHBWZjbhXjTMaEKftwIoqCXBWGrzsI2lM3dzAwt4kbR1GZr1takq5wP3+dtczxJTr5OkpP0nOcPh3ydnJfzy///PO970lTQNA1I1iUSAgJYV0mFXoxGox52M8y2Q2xb75STXt/be/a/4fcGzV5LHrmR81wkErEGkOkrv99/LBAIgCRJjoA3OTkJ8ZmZT7tP3oV8EKvmQKbDCE8URVBV1TGxC4ZCMB2LVQVisRrYjfCcphaPh0NEJ/pP3h2lJlJHiK7uwtWA6PppTKUQaR6YBZFNcUYJoI0QCWCFEAlghRAJYIUQhUKXs9haWAuHw46CE4/HYX5+vqz3sLWwYHUp5zixtT14vd5l9wivueS7PzU1RRE2qrm5Wb8wYkyf1QvLrnNgGiJKUZQc1+VzIgE0gYiAZFnmt+WCcz1AfvIrUTZeqms4B8rPH0NybATUF7O2AxK9XdDRPwRST7ggRIwybtkxLgVgzZtI6taFusDjzmLjpr4/V3S/pqYmvpULzxYHKv8sTwM69n0Gnk3bSn5f4ruP9fuBz6+UPe7LR79A8uYZUGaflrQ/AkRoxm7cEA4Ebbm+lAOvGrIyHl59L9eBtJQzgVjO1xgN24WNsa00zuUq7b5SJtcSea4wRIpwjSE2rAPtjq3lmkk+I4D1XQ5SFyYHEkCKcI27MDmQIkxalUu57I7biLFuaICroQ5ShN0wkSYHUheugjSb/8rfpvFqD1BYHmLpj1u28tPHE2p7ijWvgVIwDHJsElK3L/HN9iIfDK9uB3bsHQSpt68+HZKNi+OvageK/h7wfXicmgiJABJAAkgASQSQABJAAkgigASQABJAEqrgrzX//mIz/Wcypje//bOCX2tKLe6mJy9RhGsp27/W7NzzCfi2H9Afv4w9gsXoHVj46VrOvt0HT0PLxi2QGBuB5G9j+vP4HL5mprnx86bHcgTAtQOjILZ6M4Cs2X8COncP8PvGE5cCIQ5K/X8B2iO7MgCmFb98FJae3NcfB94f4seSE9P8Q3FUF/bt+Ag8oU0we/2bDBj/Xv+aQ/DtOJCxf8fWfg5vbvwCB4lAiyn14M4K/KDzpjFtzEXoCoysmZNmzg9yd+oAt/RD8v4YLK5AaYvsLDpGe98uDn0xOuG8CKP7Fgxxy2l2iVgGbLHVx+BNMCAvOHh0aHZtM6uDWAONx3JcEylF7cxtcuKZ7tYUcxRCTbs4Xw3E2Af6h3LqqSMAIgx0Yb76iMV/+sxB3YGo3uEfcsAWag5YWzH6rW9tdR5APHGEhBCz6yCeMDoOo4cwUc9Of8Djmz39EVtHii+vDLXUMU0E3YGQuvZ/yaOWPdebG7+oNw+EbYTHP4AHE3pM8wlfww8Im4/jaiACwU7buWeA16l0rUI34rwQoWF0pcA6HWZ2CcANAcuJi3mbyMLPV03njHW5mPDXsXcVQV5iLhXAndJAk1rUDafuNVmKsBLquweqwg/kRnh47mpw86+WI6x1vbFX0dQfxecP3xEW51x14UFr61TVnrd/V9ds2G05wqQG6sIEkGSqVwIMAG021uWJKtY8AAAAAElFTkSuQmCC") +} + +.fileList .ico-iso { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABH9JREFUeNrsnEtoE1EUhs9MJmlLUpu0tk20+CCotK5UEMSFCxWEuhARFFERbBcWFN1oQRTUjbpRFFRocVMRBRduCoJvQQTxsdGIFlGpNm1TW9qa1pZ5eM9NZ4w1jZlJ85xz4DLp5N7czpf/v+fMTVNB0zSgsB4iISCAOQ0p2ZOhUMjFDidY28va/GK56Pl1dVfYoSXV/hVz5lgDyOKk1+tt9fl8IElSUcDr6uqCSH///uqaGjAD0SrAfQhPFEVQVbVobOcPBKA3HJ4ViP9bA6sRXrFFicvFIaIS2Y+XKYnkEKKts/BsQLR9GZMuRKoD04RIANOESADThEgA04RIANOEKCTbzmL3wlowGCwqOJFIBEZGRkyNaWhoEKzeyhVdsHt78Hg8MfUIf7jM9Li7u5ssHB9Op9PYGIl3n9WNZdspUIeIoSjKP6qbSYkEMAFEBCTLMj+aBWd7gPzip6wcv1WXdwqU+z5BtPM8qD8Hsw5I9FSCu/EwSLXBpBDRytim2zgVgBlPImMP23MCjyuLzTt27+p/+zkcDt7MwsuKApWBWBng3nQAXEvXpDxu6OJO47Hv4A3T805+fA7Ru5dAGfyeUn8EiNDis3FeKBC02PpiBt5shJX5cPfdrALpVi4BRDMfY+RtFo63bbp2Nhu6+lIpriXSXHKIZOEMQ8xbBWbbtpbXTNIZAczt7SBlYVIgASQLZzgLkwLJwhQFeSs3PePmo63zGmAhrINkYTsU0qRAysKzEFqW/8o/S/NlHqAQm2Li7cOs8jPmEzJ7iRlfAyV/EORwF4w9usZb1hd5f7CwFeje2AJS3fLcZEg2L85f0AoUvbVQvvUYJREKAkgACSABpCCABJAAEkAKAkgACSABpMBI+m3Nr4fq6T+TsVh44X0a39aUSuxNT54gC2cycvKxZvXuc+Dw+qH30p7YL+ELQMX6JihrWGf0GQ89geEH7SAPhY1zJYtXQsWGJnAFlhrnoq87Yfh+G6i/ftoziYilHpi76yw4GMSec1vg26kNEOk4As7AEn5eD/eqRg5eYUCn96tpvszfBFsCdDI1Sb55TEXthoomPr/mysLzqDaEjArFcz9un/6r3wCDKJaVQ/naHfYEqE3BQGsiKD1Gn93iKpsMf+TqE0vLIfqq85/xCBPBulc2/jW+qNfA+EBAww/amMKaYd6RO8a6Jg/1cIj8XWYK0/smTJRT6yQqdqY+RQtQVxs2VJqLWzrAFYVt4PpRysLJImZPDweIFo1CzKYIEpMDPq+OjxrnEilMTyCoWltmYbTv9PULQSEQfe1Tf41ymInGo1LR9rkoZXIOcDz0lMOZy0qU+PrOt/kwX9PG3j3hYLAmRFBV244bsLEuxHGo0NFnN+1pYUwAAx1HWRmynVs2XoFY52GpwhMLU6E8GObZWk82+VBIJ91M+NK6WhHkCaZSAewZGmhSibrozAuHJQsrgeUvQFX4C9kRHl676q9/adnCWuWCjYqmPhb7PqwQxodttfGglVWoau2yN2rVovWWLUxRAFmYANo8fgswAF4ssuGz3PXWAAAAAElFTkSuQmCC") +} + +.fileList .ico-rar { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABGNJREFUeNrsnEtoE1EUhs+k07Q1qclUahMtvoJK05UtCCrUhQpCXblRBEFQFxaUdiMFQVA34qaioC6KG0EU3BYE6xNUEI0bqWjxRZWo0UpfaSOZGe+5adIkjdPMpJkkM+fAZSYzk3uZL/85/52bpoKqqkBhPByEgACWNEStk0NDQ062Oc3aIdZWWuWmVzY3X2Wbrnyv9yxdagwgizNer7dXkiQQRdES8IaHhyHy8+exxuXLQQ9EowAPIzyHwwGKolgm7Xx+P3wPhxcF4kI1sBHhWS1qnE4OEZXIXl4hEykhRFu78GJAtP00plCINA8sECIBLBAiASwQIgEsECIBLBCioLWcxZ6F1UAgYCk4kUgExsfHdb0nGAwKRh/lLBfs2R7cbndCPcIcl//tj4yMUAqnR3V1dWphJD37jC4s206BSYgYsizPU93/lEgAc0BEQPF4nG/1grM9QH7zs6mcvlRXdgqM//gAUwN9oEyOmg7I4W4AV2cPiE0BTYiYytiy0zgfgEU3keiD/pLA48pi40bvXVvwuqqqKt70wjNFgfKvxDTAtfs4ODdsyft9fy4dSO1LJ27qHvfv++cwdfcyyKPf8roeASK0dDcuCwWCmqgveuAtRhgZD1ff9SqQHuVyQNTzNUbZunB62haaznojqb58JtciaU4bIqVwkSGWrQLNTlvDNZN0RgBL+zhILkwKJICUwkV2YVIgpTBFRT7KZTtuOaZ1WQOshDpIKWyHiTQpkFx4EUI1+a/8TRqv+ACFxBCxNw9M5ZcaTyjuLRa9Boq+AMTDwxB9eJ0304u8L1DZCnTt6gKxubU0DsnGxfErWoEObxPU7z1FJkJBAAkgASSAFASQABJAAkhBAAkgASSAFBiav9b80t1C/5mMxeqLbwv4taZYY2968RilcDHDtK81a9a2QePBCznPTTy7BWOD/ZmfbK0bfCdusG09fL98EOJ/wqlz9dv2gWfH0Xn9KDMTMHa/H6ZeDVgPYDIiN05C7FMo9drV3glSZw8HlH7jda3bOTyEUhfsgImnt+f19fXszozX0p4e3lfsYygDuKVdGKEhJKd/Q8ZxV1snTA89Zu0J38+3L672dW32m8ZgyiYDYWKLMngzTE2itIKXACN9WR4g1jNM1cmM9O3gqkwo8DHfd7cvrELPziP8WkvXwFxGgiaSURd5+j6ZS83QANRv3c9A9zFAk6njzacH5/X1+865jGssbyJJR535EOLH0VRQkQgxu/bhuXQzyTYR/HCkPd3MRF6ZBrEsTITXvRXr+XZJcDv8Db/ncNIbHqtj57QCywDCx5ppmxqISsG6xdNB8nOzwLqXHXgMjUXLTOTZqYtgJxPhEKcnuLpcs0aRywSSx7TMJPlB1AbMm8ZoLiZ87t0sC/EYgyyAPUMFVaxR1px/UWVIgbK/9QUoMu/IjvDw3hVfy0vDLqw2rNolq8ojx493m4TpMVstPKh1HkVp2vhaWbZmh+EUpqigRzkCaNP4J8AArrWcCArY0yoAAAAASUVORK5CYII=") +} + +.fileList .ico-zip { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+hJREFUeNrsnF1IU2EYx5+zHTdkU7eZuuUocWA4r/QiqC68UCHQqxCKiAiqiwRD74RKyKu6UhIqULoJouhWIcjsAyqQsJtY5IgKq5krze8m56P3PWtruA/Px77P84fDmTvvu5fz2/95n+d958aIoggo9TIgAgSYU7GpLvp8PhM5DZLjDDlqi+Wma93uW+TUI7d9RXm5OoBEV20224DdbgeWZYsCnt/vh+Di4oWq6mpQAlEtwLMUnsFgAEEQiibsnC4XLAQCaYG42xxYReEVm8wmkwSROpH8eROTSA4h6joLpwOi7ssYrRCxDtQIEQFqhIgANUJEgBohIkCNEJlU21lkLSx6PJ6ighMMBmF1dVVRH6/Xy6hdyhWdyNoerFZr2D3Mfy7JHs/Pz2MIx6qkpCS6MRIbfWo3lnXnwAhEKp7n41yXzIkIMAFECojjOOmsFJzuAUo3/y+UY7fq8s6B3I+PsDE5DML6UtYBGawOsHT2A1vjSQmRhjI9doaxHIAZTyKb0+M5gSc5i4y7+fj2ru2MRqN0KIWXFQfyP8NlgOVoL5gaDsnut3zjZPSx/eI9xeNuz72GjUejwC99k9WeAqTQYrNxXjgQxPD8ogReOqRmPLr7rtSBuJRLAFHJxxh5m4Vjw1ZrOCtVxH1yimsWPZcaIoZwhiHmrQOzHbaq50z0GQLM7XIQszA6EAFiCGc4C6MDMYRRBbmU25lx8zGs8xpgIcyDGMJ6KKTRgZiF0yAxy//ln6XxMg+QCQ8RejedVX7R8ZjM3mLG50DW6QEu4IfNp3ekI+uTvNNT2A60dPQA627KTYYk49LxC9qBBlsNlB27hEkEhQARIAJEgCgEiAARIAJEIUAEiAARIIoq5bc1v/Q14i+TEe0fea/h25qsWd/0uBCGcCaVk481WbsLnL13k15feTIGay8fgHtwCjZmJ2F5YhjKjhyHirbzcW23A3Pwe2JEOusGILccgK9D7XHPV3ZfAXN9C2z5XiTtG9uPvhF7Tl0HR/dlWBg9re8sXNF+Dkq9rcRtIxJguW8EdShr3yvB1C1ACq7s8AlYe3WfuO851oFK50N7Vx+EPs3CytS44r6Wlk7ixO+yXVsUc2CsHGTeE7bW4NfDIVntaWJJlER0lYUjsnf1g8nVAItjPSD8WZfVJ1Hy0WUI07KEhh8tWXJVghQsQOo6WtPRhEHrPSykFcpc3xzNvu7B1rjrkeK54DcTPg8c5BkuRFzKgD4lgsiahbprM0ZVIcy7mmZA4KUX0iM8eu+Cs/GN6hAWHfs6eFF4ZvjxoZnZWtHVxoNYWiEINQfeCpV1bapDGFVAa2EEqFP9FWAAVhNMgyXjjfoAAAAASUVORK5CYII=") +} + +.fileList .ico-bt { + background-image: url("../img/ico/ico-bt.png") +} + +.fileList .ico-file { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAehJREFUeNrs3D9PwkAcxvFrKXUhCAMEAhsbL8nJxElfgYPR+CZ0MnHyJbEwMkDoQGAG6h2CNlIo16t/cvd9kktL2g73ye9a7gj14jgWJH88AAH8v4CDwSCUm3vZLmXr2NLpTrf7LDc3p55/Xq0ePBZkXPtYq9Vu6/W6CILACrzhcCii6fS60WwKHcS8gFcKz/d9sV6vrRl2rXZbTMbjQhD9jOMNhWdbzsJwg6gqUX58+klAa1MUorOARSE6DVgEovOApogAGiICaIgIoCEigIaIWXPhuNfrWYUTRZFYLBZa1/T7fS/vVM66yLm9qFQqH9Xjfbkc2h+NRgzhZMrl8ufCSHL05V3Wc64Cd4gqq9Vqr+oOVSKAKYgKaLlcbra6cM4Dbjq/HcrJpTpdSOe/xijEUqm0N4y/VyUVeCQ7QFWJafdCAE9EVGjJpzFDWDNq9V23AgFMQdT5GQPAtPntiQ8Q7oEZiFTgLyACaHrPhABAAAEEkAAIIIAAEgABBBBAAiCAAAJIAAQQQAAJgAACCCABEEAAASQAAggggARAAAEEkAAIIIAAEgABBNBywCj5bhXXsu17ZAL4MpvNhIuIqs+q7zKvx87L+sP1w3w+D2W7kPstxwwnsr3JdnfsJF4FbxgAAfzbvAswAK/3kejP2oZLAAAAAElFTkSuQmCC") +} + +.fileList .ico-svg,.ico-css { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAehJREFUeNrs3D9PwkAcxvFrKXUhCAMEAhsbL8nJxElfgYPR+CZ0MnHyJbEwMkDoQGAG6h2CNlIo16t/cvd9kktL2g73ye9a7gj14jgWJH88AAH8v4CDwSCUm3vZLmXr2NLpTrf7LDc3p55/Xq0ePBZkXPtYq9Vu6/W6CILACrzhcCii6fS60WwKHcS8gFcKz/d9sV6vrRl2rXZbTMbjQhD9jOMNhWdbzsJwg6gqUX58+klAa1MUorOARSE6DVgEovOApogAGiICaIgIoCEigIaIWXPhuNfrWYUTRZFYLBZa1/T7fS/vVM66yLm9qFQqH9Xjfbkc2h+NRgzhZMrl8ufCSHL05V3Wc64Cd4gqq9Vqr+oOVSKAKYgKaLlcbra6cM4Dbjq/HcrJpTpdSOe/xijEUqm0N4y/VyUVeCQ7QFWJafdCAE9EVGjJpzFDWDNq9V23AgFMQdT5GQPAtPntiQ8Q7oEZiFTgLyACaHrPhABAAAEEkAAIIIAAEgABBBBAAiCAAAJIAAQQQAAJgAACCCABEEAAASQAAggggARAAAEEkAAIIIAAEgABBNBywCj5bhXXsu17ZAL4MpvNhIuIqs+q7zKvx87L+sP1w3w+D2W7kPstxwwnsr3JdnfsJF4FbxgAAfzbvAswAK/3kejP2oZLAAAAAElFTkSuQmCC") +} + +.fileList .ico-apk { + background-image: url("../img/ico/ico-apk.png") +} + +.ico-cmd { + background: url("../img/soft_ico/ico-cmd.png") no-repeat center center; + height: 18px; + width: 16px; + display: block +} + +.btn:hover .ico-cmd { + background: url("../img/soft_ico/ico-cmd-hover.png") no-repeat center center; + height: 18px; + width: 16px; + display: block +} + +.fileUploadDiv { + padding: 10px +} + +#file_input { + display: none +} + +#up_box { + background-color: #fafbfe; + border: #e4e5e9 1px solid; + margin: 0; + padding: 10px; + clear: both; + height: 160px; + overflow: auto; +} + +#up_box li { + list-style-type: none; + height: 30px; + line-height: 30px; + font-size: 12px; + width: 100%; + margin: 0 0 5px; + padding: 0 +} + +#up_box li .filename { + display: inline-block; + height: 30px; + margin-right: 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 240px +} + +#up_box li .filesize { + color: #999; + display: inline-block; + height: 30px; + overflow: hidden +} + +.fileUploadDiv button { + float: left; + border-radius: 3px; + border-style: solid; + border-width: 1px; + width: 75px; + height: 30px; + color: #fff; + margin-bottom: 10px; + cursor: pointer +} + +#up { + background-color: #5cb85c; + border-color: #4cae4c; + color: #fff; + text-align: center; + margin-left: 10px; + position: absolute; + bottom: 0; + right: 10px +} + +#filesClose { + position: absolute; + bottom: 0; + right: 95px; + background-color: #cbcbcb; + border-color: #cbcbcb; + color: #fff +} + +#opt { + background-color: #4592f0; + border-color: #367fa9 +} + +#up_box li em { + font-style: normal; + color: #06F; + float: right; + margin-right: 10px +} + +.cancel { + float: right; + cursor: pointer; + color: #c00 +} + +#textBody { + min-height: 450px; + _height: 450px +} + +#filesClose:hover { + background-color: #c9302c; + border-color: #ac2925; + color: #fff +} + +#up:hover { + background-color: #449d44; + border-color: #398439 +} + +#totalProgress progress { + width: 180px; + height: 10px; + border: 1px solid #ccc; + background-color: #e6e6e6; + color: #0064b4; + opacity: .9 +} + +#totalProgress p { + color: #666 +} + +.setchmod fieldset { + margin-left: 15px; + margin-bottom: 15px; + border: 1px solid #ccc; + float: left; + width: 114px; + padding-bottom: 10px; + border-radius: 3px +} + +.setchmod legend { + padding: 3px; + border: 0; + width: auto; + margin: 0 6px; + font-size: 14px +} + +.setchmod fieldset p { + margin-left: 10px +} + +.setchmod fieldset p input { + position: relative; + top: 2px; + margin-right: 5px +} + +.setchmodnum { + clear: both; + margin: 0 15px -10px +} + +.setchmodnum #access, +#chown { + width: 60px; + margin-right: 5px; + height: 28px +} + +.setchmodnum span { + margin-left: 10px +} + +#chown { + margin-left: 5px +} + +.shellcode { + margin-bottom: 5px; + height: 490px; + overflow: auto +} + +.shellcode pre { + background-color: #111; + color: white; + padding: 5px 10px; + border: 0 none; + border-radius: 0; + min-height: 490px; + margin: 0 +} + +#mExec { + width: 89%; + margin-top: 0; + float: left +} + +.editmenu { + text-align: right +} + +#filesBody { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none +} + +.folderBox input { + display: none +} + +.fileList>div:hover, +.fileList div.ui-selecting { + background: #f6f8fd; + border-color: #d3dfec +} + +.fileList div.active, +.fileList div.focus, +.fileList div.ui-selected { + background: #eff3f9 none repeat scroll 0 0; + border-color: #bcccde +} + +.fileList .file { + background: rgba(0, 0, 0, 0) none repeat scroll 0 0; + border: 1px solid #fff; + border-radius: 2px; + display: inline-block; + height: 136px; + margin: 0 5px 10px 0; + text-align: center; + width: 112px +} + +.fileList div.file { + transition: all .2s ease 0s +} + +.fileList .file .titleBox { + color: #595c5f; + display: block; + height: 23px; + line-height: 23px; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + width: 100px; + margin-left: 5px +} + +#DirPathPlace { + display: none; + padding: 0 +} + +#DirPathPlace>input { + border: 1px solid #ccc; + height: 28px; + line-height: 28px; + padding: 0 5px; + width: 280px +} + +#DirPathPlace>input:active, +#DirPathPlace>input:focus { + border: #66afe9 1px solid; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +#PathPlaceBtn { + background-color: #f3f3f3; + border: 1px solid #ccc; + height: 28px; + line-height: 26px; + padding: 0; + width: 290px; + cursor: text; + overflow: hidden +} + +#PathPlaceBtn ul { + display: inline-block; + height: 26px; + width: auto; + position: relative +} + +#PathPlaceBtn li { + background: url("../img/ico/ico-ltr.png") no-repeat right center; + padding-left: 10px; + padding-right: 18px; + float: left; + height: 26px; + line-height: 26px +} + +#PathPlaceBtn li a { + display: inline-block; + cursor: pointer; + height: 26px; + line-height: 26px +} + +.backBtn { + color: #666; + border-radius: 0; + height: 28px; + line-height: 8px; + margin-right: -1px; + margin-top: -1px +} + +#tipTools { + background: #fff url(/static/img/ico_line.png) repeat-x 0 55px; + height: 110px; + border-bottom: #ccc 1px solid; + position: absolute; + top: 0; + left: 0; + z-index: 999; +} + +.re-head { + border-bottom: 1px solid #ccc; + float: left; + padding: 15px 20px; + width: 100% +} + +.re-con { + float: left; + height: 500px; + width: 100% +} + +.re-con-menu { + background-color: #f0f0f1; + float: left; + height: 100%; + width: 120px +} + +.re-con-menu p { + cursor: pointer; + line-height: 40px; + padding-left: 30px; + position: relative +} + +.re-con-menu p.on { + background-color: #fff +} + +.re-con-con { + height: 500px; + margin-left: 120px; + overflow: auto +} + +@media only screen and (max-width:768px) { + .sys-i-c-box:nth-of-type(3){ + margin-top: 20px; + } +} +@media only screen and (max-width:990px) { + .pr8 { + padding-right: 0 + } + .pl7 { + padding-left: 0; + margin-top: 15px + } +} +@media only screen and (max-width:1200px) { + .btvipbox{ + margin-top: 20px; + } +} + +.bt-ico-ask { + border: 1px solid #fb7d00; + border-radius: 8px; + color: #fb7d00; + cursor: help; + display: inline-block; + font-family: arial; + font-size: 11px; + font-style: normal; + height: 16px; + line-height: 16px; + margin-left: 5px; + text-align: center; + width: 16px +} + +.bt-ico-ask:hover { + background-color: #fb7d00; + color: #fff +} + +#RecycleBody .tname { + line-height: 30px +} + +.menu-sub { + border-bottom: 1px solid #ccc; + height: 40px; + line-height: 30px; + margin-bottom: 15px +} + +.menu-sub span { + display: inline-block; + font-size: 13px; + height: 40px; + padding: 0 25px; + cursor: pointer +} + +.menu-sub .on { + border-bottom: 2px solid #20a53a; + color: #20a53a; + font-weight: bold +} + +.bt-progress { + background-color: #e2e2e2; + border-radius: 8px; + height: 16px; + line-height: 16px; + position: relative; + margin: 5px 0 +} + +.bt-progress-bar { + background-color: #5ab76c; + border-radius: 8px; + height: 16px; + max-width: 100%; + position: absolute; + text-align: right; + transition: all .3s ease 0s; + width: 0 +} + +.bt-progress-text { + font-size: 12px; + color: #fff; + padding: 0 10px; + position: static +} +.conf_p{ + overflow: hidden; +} +.conf_p p{ + margin-bottom: 1px; +} +.conf_p span{ + display: inline-block; + margin-right: 10px; + width: 140px; + text-align: right; +} +.btvipbox{ + border: #ddd 1px solid; +} +.btvip{ + border-right: #ddd 1px solid; + float: left; + width: 40%; + height: 108px; + text-align: center; +} +.btvip-r{ + width: 60%; + float: right; + overflow: hidden; +} +.btvip .t2{ + font-size: 16px; + padding: 10px 0 5px; + float: left; + width: 100%; +} +.btvip p{ + float: left; + width: 100%; + margin-bottom: 8px; + font-size: 14px; +} +.btvip p .price{ + margin-right: 3px; +} +.btvipinfo{ + width: 200px; + float: left; + padding:5px 10px; + line-height: 32px; +} +/*付费插件*/ +.libLogin{ + width: 340px; + margin: 0 auto 20px; +} +.libLogin .bt-input-text { + width: 300px; + height: 34px; + line-height: 34px; +} +.libLogin .line{ + margin-bottom: 10px; +} +.libLogin .login-button,.shuoming .login-button{ + width: 300px; + height: 36px; + background: #20a53a; + background: linear-gradient(#20a53a,#189d32); + box-shadow: inset 0 1px 2px #30ad42; + color: #fff; + text-shadow: #00851a 0 -1px 0; + border: 1px solid #20a53a; + text-align: center; + font-size: 16px; + color: #fff; + border-radius: 3px; + cursor: pointer; +} +.libLogin .login-button:hover,.shuoming .login-button:hover{background:#10952a;background:linear-gradient(#10952a,#088d22);border:1px solid #10952a} +.shuoming{ + line-height: 24px; +} +.shuoming .login-button{ + width: 100px; + height: 30px; + font-size: 14px; + margin: 15px; +} +.sm-content{ + border: #ccc 1px solid; + padding: 10px; + height: 200px; + background-color: #f9f9f9; + overflow: auto; +} +.libPay .btn-group .btn{ + width: 80px; +} +.libPay .btn-group .btn em{ + position: absolute; + right: -2px; + top: -9px; + width: 33px; + height: 19px; + background: #ff7300; + border-radius: 2px; + font-size: 10px; + line-height: 19px; + color: #fff; + transform: scale(.9, .8); + font-style: normal; +} +.libPay .price{ + font-size: 28px; + color: #FF6600; + margin-right: 3px; + line-height: 30px; +} +.libPay .sprice{ + font-size: 14px; + display: inline-block; + line-height: 30px; + margin-right: 3px; +} +.wxPay{ + width: 160px; + margin: 10px auto 20px; +} +.wxPay .ewm canvas{ + border: #ccc 1px solid; + padding: 6px; + width: 160px; +} +.wx-pic{ + width: 160px; + height: 36px; + background: url(/static/images/weixin.png) no-repeat top center; + margin-bottom: 10px; +} +.wx-pay-info{ + width: 160px; + height: 30px; + background-color: #00C800; + color: #fff; + font-size: 14px; + line-height: 30px; + text-align: center; + margin-top: 10px; +} +.color-red{ + color:red; +} +.color-green{ + color:green; +} +.btpro{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADsAAAAUCAMAAAAEAbViAAAAA3NCSVQICAjb4U/gAAABXFBMVEX///+7jAC7jAC7jAC7jAC7jAC7jAC7jAC7jAC7jAC7jAC7jAC7jAC7jAC7jAD////+/fv+/fr8+vX8+vP69uv38eD28N3179v07NXz69Lz6tHz6c/x6Mzv48Pu48Hr3rX74jnq27D54Djp2av43zfo16fo2Kj33TX33Tbm1aLl1Z/12Tbl057z1zLk0Znj0Jfx1DDizpPu0DPv0S7fyIbqzCroyC/cxH3oySjawXXZv3DkwyTYvWzivyzXu2jWumXevCDUtVzctinathzSslXSs1bRsVHRsVLWsBjVrSbNqkPTrBXLqD3PpCLJozTOphHIozLIojDIoS/HoCzKoQ7GninGnyrFnifJnB/FnCTFnSbEmiDEmyLFmQnCmBvBlxjBlhbBlRXAlRPCkhy/kxDAkwW+kQq9kAi9jwa9jwK9jQC8jgS8jQO8ihm7jAC2gRWweBKpbw+jZgydXgnhk9RtAAAAdHRSTlMAESIzRFVmd4iZqrvM3e7//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wol0KMAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDUvMzEvMTikOWwBAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAAg5JREFUOI2dlGlz0zAQhhUHJ45jST2gXKUVR4BijmKKCwX1CKcKLoVQcCmFYgcSjqiy8/9nWCtOJvlEwusZe3Q80rurlRFCyLDsSi7bMtAkKlSlU602W61o+3VDvrnOxlTGliUxkdFcfRjN0aWm3ByXBdgglaqN7N2pmb2t6edba0e3xmctBxWxjecoPfPh2afTtPGkN+J6/2SLxC6ajrQvLC4uPJXvLs6/aC7rkZD35/Awb8RMMc5FGAc9FktMiJS42W5HX+SP6Nv+n13GlFJdlWmEVfCkYcD9fF9MTNO0iHX15o3aRnTncq0W7WcDQvQmeEHO1rlm1ZBnw6lAvrBJQY+3s9fOPSbCMA21BEvdHnvssa7qCm3Hz3N1gpQRsu1ZSk8drFC60nrAmM/DmGv5LAg06yVM75mF0fFyFpWkiUrEoHTx6wKt/d7U/R2hUZjkppoFBxCrYCrkfd9ZcVSkUcAlOrW+Q8+3X13S/SIzHHeFXiZjlctYwiFevwNrDdgCdsD09OzbtZmPh9d6/QFQbiJGzsgFksM5Zcv0WVSUlumcPHdwdv3Xcp5DF5KVowMWvioW7nHKh1gIuYznlxq35aOhshFJCBvEaqC4ztKkntQ9FQ+xEDK5u3r08+Vo0dVHm3Ayvu9nhr1htuDIvc/y/RU2ifqX2MBSHo59g0ZZZJLv9/8ThXyZE/1tQH8B/ly2lqVFKXEAAAAASUVORK5CYII="); + background-repeat:no-repeat; + padding:2px 0 2px 64px; + margin-right:5px; +} +.btpro-gray{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADsAAAAUCAMAAAAEAbViAAAAA3NCSVQICAjb4U/gAAAA51BMVEX////6+vr4+Pj29vbx8fHu7u7r6+vp6eno6Ojl5eXk5OTj4+Pi4uLf39/e3t7d3d3b29vU1NTT09PNzc3Ly8vIyMjFxcXDw8PAwMC+vr67u7u6urq5ubm4uLi3t7e2tra0tLSzs7OysrKxsbGvr6+srKyrq6upqamoqKikpKSjo6OgoKCfn5+enp6dnZ2cnJybm5uampqZmZmYmJiXl5eWlpaVlZWTk5OSkpKQkJCPj4+NjY2MjIyLi4uKioqIiIiHh4eGhoaFhYWDg4OCgoKBgYGAgIB/f39+fn59fX14eHh0dHT///9HdDDXAAAATXRSTlP/////////////////////////////////////////////////////////////////////////////////////////////////////ANNqEYAAAAAJcEhZcwAADpwAAA6cAQeUU90AAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAFnRFWHRDcmVhdGlvbiBUaW1lADA1LzMxLzE4pDlsAQAAAeZJREFUOI2VlH1XmzAUxjNbZutxL2hnXeiGA1kiNdspbJlEC9FmLtD7/T/PkkC78tfw4Rw4IfeXPDf3AgIALyBRJxJ4MFgIYLTU+XJZbTYluVnp4jMeKMvOtfTBq84/lAAnlV4OZTECT0ZLAoQB5AuIFhcPX4azQQ5TQYQxgLKrnwhWq3YmjP/LTiWZ+rkmr8bjoyv93UNR1UKc7mIo7wYFVphSxousZYUWUmotKqVKoZ/Kkj8zjJVSW2XVY5W5Gp7RpNtXSN/3Axkcn55OPpZvX08mJbcTjLUBcdaxKXWsOvDs5ZGpsPBtwebE3m6vMeO84U4MN2HLPsZ4q7bM2Um6s3on5wDEUnB3BnC2IRgnlBfUKcFZ5ti4xm5Pm8Y67liYaR9m0vTT+P4IJr/b+q6ZQ01Q2DjWODC5Mqw43fk2LETaG4kZwOUtQoov3HtmDRdb5paxrAoxrqnJN1lT/o8didyZ/naBflRdS2aGCmvWq1FoSGrqZJfZsTDVgZ8DukOXf3YNEZrD6tA9a56qYOFjQw9Yk/JcoJPVG31z0Das5maDQu1VpLip0zqNVXHAmpTl+/OH57zfdGl/aCqTJIk1HB+yo1znv/T9J/wSdazpDq2rwV9QnwVfbr6+DIU9CzN/+N+m1V+TNr+ixpmAWwAAAABJRU5ErkJggg=="); + background-repeat:no-repeat; + padding:2px 0 2px 64px; + margin-right:5px; +} + +/*插件付费*/ +.wx-pay-ico{ + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAMAAAAR8Wy4AAAAA3NCSVQICAjb4U/gAAAAtFBMVEUAzABM2Ezx/PHB8sEazhqf6p+B5IE61TrU9tRm3mYq0Sqz77P///8Lygvj+ePI88iP549U2lR54nlF10Wm7KY31Dcj0CPS9dL1/fW58Lnr++sx0zHY99jD8sMSzBJc3FxA1kCU6JQfzx+t7a1s32zm+uaj66O377cPyw+q7ar////c99zH88fL9Mu177VG10Yk0CSR55Eczxw81TzX9tdp32k41Ti78Lsy0zLF88UhzyHB88EyB+unAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAFnRFWHRDcmVhdGlvbiBUaW1lADA1LzI5LzE4XO1tXgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAACTSURBVAiZRU8JDsIwDHNXGMcCjHNjMBhnQTAVcQjE/x9G0qHOUtLYal0HJEiiXaQzN4JrU/TAmL2WTgjG8GiyEHxqvj2yEHra0pdYEW7AgwtqTnkBXMF+w35n3aanlWtnEZATpfvq3QRd7jaNV3+jAWI5Gt6Yc0zrb3EqJZjy/Ktd9DdwF7oYZdUuoU0OxpjS7UY/XkQJtfApswsAAAAASUVORK5CYII=") no-repeat; + display: inline-block; + width: 16px; + height: 14px; +} +.libPay-item{ + margin-bottom: 30px; +} +.libPay-item .li-tit{ + margin-bottom: 10px; +} +.libPay-item .li-c-item li{ + font-size: 0; + margin-bottom:20px; + border: #ddd 1px solid; + border-radius: 4px; + height: 42px; + line-height: 40px; + padding: 0 20px; + cursor: pointer; + box-sizing: border-box; + transition: border-color .1s ease-in,color .1s ease-in; +} +.libPay-item .li-c-item li:hover{ + border: #20a53a 1px solid; + color: #20A53A; +} +.libPay-item .li-c-item li.active,.libPay-item .li-c-item li.active:hover{ + border: #20a53a 2px solid; + color: #20A53A; + padding: 0 19px; + line-height: 38px; +} +.libPay-item .li-c-item li .item-name{ + font-size: 14px; + vertical-align: middle; +} +.libPay-item .li-c-item li .item-info{ + font-size: 14px; + vertical-align: middle; +} +.pay-btn-group{ + position: relative; + display: inline-block; + vertical-align: middle; +} +.pay-btn-group>.pay-cycle-btn { + position: relative; + float: left; +} +.pay-cycle-btn { + font-size: 0; + width: 106px; + height: 40px; + line-height: 38px; + display: block; + margin-bottom: -1px; + text-align: center; + vertical-align: middle; + border: 1px solid #ddd; + box-sizing: border-box; + margin-left: -1px; + transition: border-color .1s ease-in,color .1s ease-in; +} +.pay-cycle-btn span{ + font-size: 14px; + line-height: 25px; + vertical-align: middle; +} +.pay-btn-group>.pay-cycle-btn:first-child { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pay-btn-group>.pay-cycle-btn:nth-of-type(5n+1){ + margin-left: 0; +} +.pay-btn-group>.pay-cycle-btn:last-child { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pay-btn-group .active,.li-c-item .active{ + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAMAAADzapwJAAAAA3NCSVQICAjb4U/gAAAAclBMVEX///8fpTofpTogpTogpTogpTrw+fLt+O/i8+be8uLb8d/T7djP7NXR7NbE58u548G34sCr3bWU1KGT05+FzpN+y413yIZ0x4RxxoJwxYFnwnliv3RWu2pUumhMt2FGtVxDs1k6sFE4r1Avq0cjpjwgpTpRW86VAAAAJnRSTlMAEXd3iO7//////////////////////////////////////////0c8mUAAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDUvMjkvMThc7W1eAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAAHhJREFUGJVtzskSgkAMBFB2AUcR2QTcEPr/fxFmmFiW0znk8CrVac9jE1CNYqoAV8JaXTbq8K7/bBVcwRVcwRVcwbRTIDqcWrjaJ3cJ0VrdjDaHh2Sb2/F43XadPuWlTfjkJersLU2+ufNFnSdb0P/5thQv6R2CzQo9Oh+AlBF9bAAAAABJRU5ErkJggg==") no-repeat right bottom; +} +.pay-btn-group .pay-cycle-btn:hover{ + border: 1px solid #20a53a; + color: #20a53a; + z-index: 2; + cursor: pointer; +} +.pay-btn-group .active,.pay-btn-group .active:hover{ + border: 2px solid #20a53a; + color: #20a53a; + z-index: 2; + cursor: pointer; +} +.pay-btn-group .active span{ + line-height: 22px; +} +.pay-cycle-btn em { + position: absolute; + right: -2px; + top: -9px; + width: 33px; + height: 19px; + background: #ff7300; + border-radius: 2px; + font-size: 10px; + line-height: 19px; + color: #fff; + transform: scale(.9, .8); + font-style: normal; +} +.pay-btn-group .active em{ + right: -3px; + top: -10px; +} +.paymethod{ + margin: 0 auto; + width: 160px; +} +.paymethod .pay-wx{ + height:175px; +} +.paymethod .pay-wx canvas { + width: 160px; + height: 160px; + border: #ddd 1px solid; + margin: 6px auto; + padding: 5px; + border-bottom-color: #00c800; +} +.lib-price-box{ + padding-bottom: 5px; +} +.price-txt{ + color:#FF7301 +} +.sale-price{ + font-size: 20px; + margin-right: 5px; +} +.lib-price-name,.price-txt{ + margin-right: 15px; +} +/*input样式*/ +.bt-label{ + margin:20px 20px 0 0; + display:inline-block; + font-weight:normal +} +.bt-radio,.bt-checked{ + display:none +} +.bt-radioinput{ + background-color:#fff; + border:1px solid #ccc; + border-radius:100%; + display:inline-block; + height:16px; + margin-right:10px; + margin-top:-1px; + vertical-align:middle; + width:16px; + line-height:1 +} +.bt-radio:checked + .bt-radioinput,.bt-checked:checked + .bt-radioinput{ + border:1px solid #20a53a; + background-color: #20A53A; +} +.bt-radio:checked + .bt-radioinput:after{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAKBAMAAAB293L0AAAAA3NCSVQICAjb4U/gAAAAKlBMVEX///////////////////////////////////////////////////////9q+00tAAAADnRSTlMAETNEVWZ3iJmqu93u/wlUiAQAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDYvMDIvMTjAxKOiAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAADpJREFUCJljYACDTAjFcgRC+xpAuFcYGCqAtK0DA4P2BAbmq0A2404G3QSQpPTi62BFjGsTIJpEQQQATscJDpRQCfQAAAAASUVORK5CYII="); + background-repeat: no-repeat; + border-radius:100%; + content:""; + display:inline-block; + height:12px; + width:12px; + margin: 1px; +} +.bt-checkbox.bt-radioinput,.bt-radio:checked + .bt-checkbox.bt-radioinput:after{ + border-radius:0 +} +.loading{ + background-image: url("data:image/gif;base64,R0lGODlhJQAlAJECAL3L2AYrTv///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgACACwAAAAAJQAlAAACi5SPqcvtDyGYIFpF690i8xUw3qJBwUlSadmcLqYmGQu6KDIeM13beGzYWWy3DlB4IYaMk+Dso2RWkFCfLPcRvFbZxFLUDTt21BW56TyjRep1e20+i+eYMR145W2eefj+6VFmgTQi+ECVY8iGxcg35phGo/iDFwlTyXWphwlm1imGRdcnuqhHeop6UAAAIfkEBQoAAgAsEAACAAQACwAAAgWMj6nLXAAh+QQFCgACACwVAAUACgALAAACFZQvgRi92dyJcVJlLobUdi8x4bIhBQAh+QQFCgACACwXABEADAADAAACBYyPqcsFACH5BAUKAAIALBUAFQAKAAsAAAITlGKZwWoMHYxqtmplxlNT7ixGAQAh+QQFCgACACwQABgABAALAAACBYyPqctcACH5BAUKAAIALAUAFQAKAAsAAAIVlC+BGL3Z3IlxUmUuhtR2LzHhsiEFACH5BAUKAAIALAEAEQAMAAMAAAIFjI+pywUAIfkEBQoAAgAsBQAFAAoACwAAAhOUYJnAagwdjGq2amXGU1PuLEYBACH5BAUKAAIALBAAAgAEAAsAAAIFhI+py1wAIfkEBQoAAgAsFQAFAAoACwAAAhWUL4AIvdnciXFSZS6G1HYvMeGyIQUAIfkEBQoAAgAsFwARAAwAAwAAAgWEj6nLBQAh+QQFCgACACwVABUACgALAAACE5RgmcBqDB2MarZqZcZTU+4sRgEAIfkEBQoAAgAsEAAYAAQACwAAAgWEj6nLXAAh+QQFCgACACwFABUACgALAAACFZQvgAi92dyJcVJlLobUdi8x4bIhBQAh+QQFCgACACwBABEADAADAAACBYSPqcsFADs="); + background-repeat:no-repeat; + background-position:62px 41px; + border: #ddd 1px solid; + width: 160px; + height: 160px; + padding:5px; + background-color: #787878; + color: #fff; + display: inline-block; + margin: 6px auto; + line-height: 200px; + font-size: 14px; + text-align:center; +} + +.cloading { + background-image: url("data:image/gif;base64,R0lGODlhJQAlAJECAL3L2AYrTv///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgACACwAAAAAJQAlAAACi5SPqcvtDyGYIFpF690i8xUw3qJBwUlSadmcLqYmGQu6KDIeM13beGzYWWy3DlB4IYaMk+Dso2RWkFCfLPcRvFbZxFLUDTt21BW56TyjRep1e20+i+eYMR145W2eefj+6VFmgTQi+ECVY8iGxcg35phGo/iDFwlTyXWphwlm1imGRdcnuqhHeop6UAAAIfkEBQoAAgAsEAACAAQACwAAAgWMj6nLXAAh+QQFCgACACwVAAUACgALAAACFZQvgRi92dyJcVJlLobUdi8x4bIhBQAh+QQFCgACACwXABEADAADAAACBYyPqcsFACH5BAUKAAIALBUAFQAKAAsAAAITlGKZwWoMHYxqtmplxlNT7ixGAQAh+QQFCgACACwQABgABAALAAACBYyPqctcACH5BAUKAAIALAUAFQAKAAsAAAIVlC+BGL3Z3IlxUmUuhtR2LzHhsiEFACH5BAUKAAIALAEAEQAMAAMAAAIFjI+pywUAIfkEBQoAAgAsBQAFAAoACwAAAhOUYJnAagwdjGq2amXGU1PuLEYBACH5BAUKAAIALBAAAgAEAAsAAAIFhI+py1wAIfkEBQoAAgAsFQAFAAoACwAAAhWUL4AIvdnciXFSZS6G1HYvMeGyIQUAIfkEBQoAAgAsFwARAAwAAwAAAgWEj6nLBQAh+QQFCgACACwVABUACgALAAACE5RgmcBqDB2MarZqZcZTU+4sRgEAIfkEBQoAAgAsEAAYAAQACwAAAgWEj6nLXAAh+QQFCgACACwFABUACgALAAACFZQvgAi92dyJcVJlLobUdi8x4bIhBQAh+QQFCgACACwBABEADAADAAACBYSPqcsFADs="); + background-repeat: no-repeat; + background-position: center 45px; + height: 150px; + width: 100%; + line-height: 210px; + text-align: center; + font-size: 14px; +} + +/*重启*/ +.rebt-con { + height: 106px; + padding: 15px; + width: 330px; +} +.rebt-li { + float: left; + line-height: 40px; + margin: 15px; + text-align: center; + width: 120px; +} +.rebt-li a { + display: block; + background: #eee; + border: #ddd 1px solid; +} +.rebt-li a:hover{ + background-color:#20a53a; + color:#fff; +} +/*绑定宝塔微信*/ +.bind-weixin{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAASBAMAAACtCzMeAAAAA3NCSVQICAjb4U/gAAAAKlBMVEX19fXr6+vg4ODW1tbMzMzCwsK4uLitra2jo6OZmZmPj4+FhYV6enr////oR1GpAAAADnRSTlP/////////////////AEXA3MgAAAAJcEhZcwAADpwAAA6cAQeUU90AAAAWdEVYdENyZWF0aW9uIFRpbWUAMDgvMTYvMTgi+WYYAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAAK9JREFUCJljuHuXyS1I4C4IMNy9MiNYpF0XzL5Qznv37uUpYPbVWBDF4Qtii9+N8b2y5WIRiB1810L3UuHdEBC7FqTkYlhnqyyUbcJ798rSuwxBQOYVkE5OX6Deu3dVedMulF1oYLgCNDPpbujd1LtJDEDqbhDYCc4Md9WUBIrB7ESGu15pLiUg5oUAhrvKd+9etQWy2WUZwEJAPUCbwey7zCHu04ug7LsMDExL7gIAh3d+c1Jd4JYAAAAASUVORK5CYII="); + background-repeat: no-repeat; + padding-left:26px; + margin-right:30px; +} +.bind-user{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASBAMAAACk4JNkAAAAA3NCSVQICAjb4U/gAAAAMFBMVEX19fXr6+vg4ODW1tbMzMzCwsK4uLitra2jo6OZmZmPj4+FhYV6enpwcHBmZmb///+wFcT2AAAAEHRSTlP///////////////////8A4CNdGQAAAAlwSFlzAAAOnAAADpwBB5RT3QAAABZ0RVh0Q3JlYXRpb24gVGltZQAwOC8xNi8xOCL5ZhgAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAh0lEQVQImWP4//+/2KxiIMnw//+X2+1rCsCsuP7/n26CWB+PA6X04oGsbweBrJ/rgawf64GsbxtQWBDZ+TAdNv5gUxL+s74EmfJpz6mOtWdAYnYTNFZP/vb0P8OHU///f/j/P0+f4duF/yDwaz4Ddz2Y9ekQg448mPV/G4M3hPG/g8ESygoHALeIhpF8DGw6AAAAAElFTkSuQmCC"); + background-repeat: no-repeat; + padding-left:24px; + margin-right:20px; +} +/*系统图标*/ +.ico-linux{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAATBAMAAACEi/vCAAAAA3NCSVQICAjb4U/gAAAAMFBMVEX///9mZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmambAcKAAAAEHRSTlMAESIzRFVmd4iZqrvM3e7/dpUBFQAAAAlwSFlzAAAOwwAADsMBx2+oZAAAABZ0RVh0Q3JlYXRpb24gVGltZQAwOC8xNi8xOCL5ZhgAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAmUlEQVQImWNgYGCQ/GvAAAHz/1+CMBjPvvoOYTFVLXkNZZ2u+isAZjE/4vyvAGaxlgbnJ4BZHAoM3A/BLA8GBpafYNYiIL4PMpt1A5DYPwFIcDWArDkAJPgWAInzF4BE9jMGBvb/QM2SBfEGDFy7UhkY3Bk4CxisGdgCGMwYGI97vGKwSmAQC1G8e+aN2WagDuP0IEGbawIMAPDJKG706GgtAAAAAElFTkSuQmCC"); + background-repeat:no-repeat; + padding-left: 20px; +} +.ico-windows{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAA3NCSVQICAjb4U/gAAAALVBMVEX///+ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZny8jBSAAAAD3RSTlMAESIzRFVmd4iZqrvM7v/Y8bBbAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAFnRFWHRDcmVhdGlvbiBUaW1lADA4LzE2LzE4IvlmGAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAABGSURBVAiZY2AAAkbTqrMMDEJuHvfevXvHcPbdu+53IMY7Ihi7d+9O7QACBiB2BTOAol0wNbgY59696wQzGFQqw8GWwpwBAGURWs03JAEWAAAAAElFTkSuQmCC"); + background-repeat:no-repeat; + background-position: 0 2px; + padding-left: 20px; +} +.ico-centos{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAMAAADjyg5GAAAAA3NCSVQICAjb4U/gAAAASFBMVEX///9mZmZmZmZiYmJaWlpYWFhmZmZiYmJgYGBmZmZgYGBmZmZiYmJmZmZiYmJmZmZiYmJmZmZmZmZmZmZmZmZmZmZmZmZmZmbwNFvuAAAAGHRSTlMAESIiIiIzMzNERFVVZmZ3d4iZqrvM3f93YyfbAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAFnRFWHRDcmVhdGlvbiBUaW1lADA4LzE3LzE4mkUBfQAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAB6SURBVAiZVY5ZEsIwDEMdUpYQghIv9f1vWpNCp/hH1oz1LKI5if5G5ezuzLff7ibO7KK+H2LaBp1WDMLcYDNf3A31CXMvRCy995rSK0SExhjAkh/XEK5E2YOCN5r75RNGUJgtZJLXoMQjw/ot2LR3baJHSbN8Ll3LrhtQxAfrvVHLpQAAAABJRU5ErkJggg=="); + background-repeat:no-repeat; + padding-left: 20px; +} +.ico-ubuntu{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAA3NCSVQICAjb4U/gAAAAOVBMVEX///9mZmZhYWFmZmZiYmJmZmZiYmJmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZVQ1Z+AAAAE3RSTlMAEREiIjMzRFVmd4iZqrvM3e7/67N/KAAAAAlwSFlzAAAOwwAADsMBx2+oZAAAABZ0RVh0Q3JlYXRpb24gVGltZQAwOC8xNy8xOJpFAX0AAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAbElEQVQImU2OWQ6AMAhEcW3Z2jL3P6xWjHE+CMPwCESfDOXtFmutAp6OtwC0jUwZVlWHaaaBniyYVq6B/Rn3G3aEjDxRuPwtN6NFzkB97IAlE3ddHTimPUKkd3TlhLb5huvLzAWWE2j0k0elCynoBUe/zXGJAAAAAElFTkSuQmCC"); + background-repeat:no-repeat; + padding-left: 20px; +} +.ico-debian{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAA3NCSVQICAjb4U/gAAAAPFBMVEX///9paWlmZmZgYGBdXV1mZmZgYGBmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYdQ+x6AAAAFHRSTlMAERERESIiM0RVZneImaq7zN3u/ynguF0AAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDgvMTcvMTiaRQF9AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAAHJJREFUCJlVjlESwjAIRLFFIRhCaO5/VyHijN2vt+zCABBq87IhT/hK1+zdfOl2Y71r2rPp1UKfAOdFgaTtAWIRWgacMUUquSi7TR6U2LflFuW0nOVjYB7JQBqwHklOe49f9dOAf+HvjRIZ3L3ePTIWfQAgiAODqr7Z+QAAAABJRU5ErkJggg=="); + background-repeat:no-repeat; + padding-left: 20px; +} +.ico-fedora{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAA3NCSVQICAjb4U/gAAAAPFBMVEX///9VVVVLS0tmZmZaWlpmZmZiYmJmZmZiYmJmZmZiYmJiYmJmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYd2jWRAAAAFHRSTlMAEREiIjMzRERVVWaImaq7zN3u/2KENScAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDgvMTcvMTiaRQF9AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAAGJJREFUCJlFj1kSwCAIQ9N9Lxjuf9faUKf5IPNQiAJAZ4yg9ZCWkMj5pxWwGCqK5skLg8AphMv2dIJC0106nNmu5VLA1bBmfqclF7fZO9HTYqzO4tMuOt5XGLA1enMzQ194ADH/CrSqGfFbAAAAAElFTkSuQmCC"); + background-repeat:no-repeat; + padding-left: 20px; +} +.ico-mac{ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAA9ElEQVQ4T5XSvyvGURTH8dej+AsMyGAykEWSTSkyPFJMyvAsYjFZJWZ/gQyKYjEZLcrkRzZZTXomgyzKj3TkW99u6l63bt3OPe/z43NOQ/6sI+5g6trIsMs4wjUm/gs/og+TuMrBA1jAO06wgn3MohfHeKqC1MvexBY6fz/fcI+xWsZXNHEZtgpu4SCvnRfM4KYOn2O6AF7DXr3syB4ldhXAw3hIe/5ERwEc/d+lcBs9BfAOtlM4RrBUAH8hprKLj0rtRZwWwOES4xupqx3vWMHxggDzOEvhftxmer/A1F8bFrYhbKAbh3jGKOYQoq4ituznfAP/VikQF8NdKgAAAABJRU5ErkJggg=="); + background-repeat:no-repeat; + padding-top: 2px; + padding-left: 20px; +} + +.index-pos-box{ + height: 52px; + box-shadow: 0 1px 2px 0 rgba(0,0,0,.1); +} +.index-pos-box .position{ + line-height: 52px; +} +.change-id{ + margin-left: 8px; + color: #20a53a; +} +.bind-user .glyphicon{ + color: #20a53a; + font-size: 12px; + margin-right: 3px; +} +/*微信二维码*/ +.boxConter { + height: 458px; + position: relative; + overflow: auto; +} + +.iconCode { + padding: 50px 60px; +} + +.box-conter { + width: 100%; +} + +#QRcode { + margin-bottom: 25px; + text-align: center; +} + +.iconCode #QRcode, +.iconCode .codeTip { + width: 100%; + text-align: center; + font-size: 17px; +} + +.iconCode .weChatSamll img { + width: 100%; +} + +.iconCode .weChatSamll { + display: none; + width: 200px; + height: 200px; + position: absolute; + border: 1px solid #ececec; + border-radius: 5px; + bottom: 150px; + right: 50px; + padding: 20px; + background-color: #fff; +} + +.iconCode .weChatSamll:after { + content: ''; + width: 15px; + height: 15px; + background: #ffffff; + border-bottom: 1px solid #ececec; + border-right: 1px solid #ececec; + transform: rotate(45deg); + position: absolute; + border-radius: 4px; + left: 90px; + bottom: -8px; +} + +.iconCode .weChat { + margin-left: 15px; +} + +.iconCode .weChat:hover .weChatSamll { + display: block; +} + +.iconCode .QRcode { + margin-bottom: 15px; + text-align: center; +} + +.codeTip ul li { + margin-bottom: 10px; +} + +.personalDetails .head_img { + width: 50px; + height: 50px; + float: left; + margin-right: 30px; +} + +.personalDetails .head_img img { + height: 100%; + border-radius: 50%; +} + +.personalDetails .nick_name { + height: 50px; + line-height: 50px; + width: 148px; + float: left; + font-size: 15px; + color: #808080; +} + +.personalDetails .userList { + height: 100%; +} + +.personalDetails .userList .addweChat { + height: 50px; + text-align: center; + padding-top: 20px; + color: #20a53a; + font-size: 16px; +} + +.personalDetails .userList .addweChat:hover { +} + +.personalDetails .userList .item { + height: 70px; + padding: 10px 15px; + border: 1px solid #ececec; + margin: 15px 65px; + border-radius: 5px; +} + +.personalDetails .userList .cancelBind { + height: 50px; + width: 60px; + float: right; + line-height: 50px; + text-align: center; +} + +.libLogin .line{ + position:relative; +} +.libLogin .tips { + position: absolute; + top: 13px; + right: 28px; + background: #ff4949; + color: #fff; + padding: 4px 6px; + border-radius: 5px; +} +.change-id{ + cursor:pointer; +} +.red{ + color: red; +} + +select[disabled]{ + border:1px solid #DDD; + background-color:#F5F5F5; + color:#ACA899; +} + +.cicle { + position: relative; + width: 88px; + height: 88px; + margin: 0 auto; + border-width: 6px; + border-color: #ccc; + border-style: solid; + border-radius: 50%; +} + +.cicle *, +.cicle{ + box-sizing:content-box; + -webkit-box-sizing:content-box; + -moz-box-sizing: content-box; +} + +.cicle .bar { + position: absolute; + width: 50px; + height: 100px; + overflow: hidden; +} + +.cicle .bar-left { + top: -6px; + left: -6px; +} + +.cicle .bar-an { + position: absolute; + width: 88px; + height: 88px; + border-width:6px; + border-style: solid; + border-radius: 50%; + transform: rotate(-135deg); +} + +.cicle .bar-left .bar-left-an { + z-index: 10; + border-color: transparent transparent #20a53a #20a53a; +} + +.cicle .bar-right { + top: -6px; + left: 44px; +} + +.cicle .bar-right .bar-right-an { + left: -50px; + z-index: 20; + border-color: #20a53a #20a53a transparent transparent; +} + +.cicle .occupy { + position: absolute; + width: 88px; + height: 88px; + line-height: 88px; + text-align: center; + font-size: 18px; + color: #20a53a; +} + +.mem-action .occupy{ + font-size: 14px; +} +.mem-action .occupy.line{ + line-height: 22px; + padding-top: 22px; +} + +.change-default button{ + width: 188px; + margin: 10px +} +.bt-w-con .line .info-r{ + margin-left:0; +} + +.bt-logs textarea{ + white-space: pre; + margin: 0px; + width: 500px; + height: 520px; + background-color: #333; + color: #fff; + padding: 0 5px; +} +.dir-rewrite-man-con{ + margin-left: 15px; +} + +.domain-ul-list{ + padding: 5px 10px; + max-height: 180px; + overflow: auto; + width: 260px; + border: #ccc 1px solid; + border-radius: 3px; +} +.domain-ul-list li{ + line-height:26px; +} +.domain-ul-list li .checkbox-text{ + margin-right:5px; + vertical-align:-2px +} +.btswitch-btn.bt-waf-firewall{ + width:2.4em;height:1.4em;margin-bottom: 0 +} + + +/*6.0终端样式*/ +.term-box{ + padding:5px 10px 10px; + background-color:#333333; +} +.shell-text-input .bt-input-text-shell{ + width:100%; + padding:10px 10px 0; + border:0 none; + height:60px; + overflow:auto; + resize:none; +} + +.shell-text-input .bt-input-text-shell:focus,.shell-text-input .bt-input-text-shell:active{ + border:0 none; + outline:none; +} + +.shell-btn-group { + height:30px; + position:absolute; + bottom:20px; + right:10px; +} +.shell_btn_close { + margin-right:8px; +} + +.file_search { + position: absolute; + right: 58px; + font-size: 12px; + color: #666; + width: 85px; + border-top: none; + background: #fff; + height: 8px; + line-height: 48px; + top: 0; +} + +.file_search .file_search_checked { + position: relative; + height: 15px; + width: 15px; + border-radius: 1px; + cursor: pointer; + text-align: center; + margin: 0; + vertical-align: sub; + border:1px solid #aaa; + border-radius: 2px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: 400; + line-height: 1; + -webkit-font-smoothing: antialiased; +} + +.file_search .file_search_checked.active { + background: #20a53a; + color: #fff; + border-color:#20a53a ; +} + +.file_search .file_search_checked.active:after { + content: "\e013"; + font-size: 12px; + transform: scale(.85); + position: absolute; + left: .75px; + top: .75px; +} + + +.file_search label { + position: absolute; + right: 5px; + cursor: pointer; + top: 0; + font-weight: 400; +} + +#form_proxy .sub-groud .bt-input-text{ + margin-right: 10px; +} +#form_proxy .sub-groud { + margin-bottom: 15px; +} +#form_proxy .btswitch-btn{ + display:inline-block; +} +#form_proxy .add-replace-prosy { + position: relative; + top: -10px; +} +#form_proxy .disabled{ + background-color: #eee !important; + cursor: default; + border: 1px solid #dbdbea; +} + + +/* site的tab切换 */ +.tab-list .tabs-item:hover { + background: #20a53a08; +} + +.tab-list .tabs-item:first-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.tab-list .tabs-item { + display: inline-block; + height: 45px; + min-width: 100px; + padding: 0 20px; + line-height: 45px; + font-size: 13px; + cursor: pointer; + white-space: nowrap; + position: relative; + text-align: center; +} +@media screen and (max-width: 1300px) { + .tab-list .tabs-item{ + padding: 0 10px; + min-width: 80px; + } +} +.tab-list .tabs-item.active:after { + content: ''; + width: 20px; + height: 2px; + position: absolute; + left: 50%; + bottom: 0px; + background: red; + margin-left: -10px; + background: #20a53a; +} + +.tab-list .tabs-item.active { + color: #20a53a; + background: #20a53a10; +} + +.tab-view-box { + padding: 15px; + border-radius: 4px; + box-shadow: 0 0 1px 0 rgb(0 0 0 / 5%); +} + +.radius4 { + border-radius: 4px; + box-shadow: 0 0 1px 0 rgb(0 0 0 / 5%); +} +.radius2 { + border-radius: 2px; +} + + +.tootls_group { + height: 30px; + line-height: 30px; +} + +.tootls_group .search { + display: inline-block; + height: 30px; + line-height: 30px; + position: relative; + width: 230px; +} + +.tootls_group .search .search_input { + height: 30px; + line-height: 30px; + border-radius: 2px; + border: 1px solid #ccc; + outline: none; + padding-left: 8px; + vertical-align: top; + width: 230px; +} + +.tootls_group .search .search_input:focus { + border-color: #20a53a; +} + +.tootls_group .search .search_input:focus + span.glyphicon-search { + color: #20a53a; +} + +.tootls_group .search .glyphicon-remove-sign { + position: absolute; + top: 8px; + right: 30px; + font-size: 15px; + cursor: pointer; +} + +.tootls_group .search .glyphicon-search { + height: 28px; + line-height: 28px; + padding: 0 10px; + color: #888; + position: absolute; + right: 0; + font-size: 14px; + cursor: pointer; + float: left; +} + +.tootls_group .page .page_jump_group .page_jump_btn:hover { + background-color: #10952a; + border-color: #398439; +} + +.tootls_group .search .search-btn span { + font-size: 15px; + vertical-align: middle; + position: relative; + top: -1px; +} + +.tootls_group .page { + padding: 0; + height: 30px; +} + +.tootls_group .page .page_select_number, +.tootls_group .page .page_jump_group { + height: 28px; + line-height: 26px; + margin-left: 8px; + vertical-align: top; + float: left; +} + +.tootls_group .page .page_jump_group span { + border: none; + padding: 0 8px; +} + +.tootls_group .page .page_select_number { + border: 1px solid #ececec; + border-radius: 0; + padding: 0 5px; + border-left: 0; + margin-left: 0; +} + +.tootls_group .page .page_jump_group input { + height: 28px; + line-height: 28px; + width: 30px; + max-width: 50px; + vertical-align: top; + border: 1px solid #ececec; + outline: none; + text-align: center; + float: left; +} + +.tootls_group .page .page_jump_group input:focus { + border-color: #20a53a; +} + +.tootls_group .page .page_jump_group .page-jump-title { + height: 28px; + line-height: 28px; + color: #555; + border: none; + padding: 0 5px; +} + +.tootls_group .page .page_jump_group .page_jump_btn { + height: 28px; + line-height: 24px; + border-radius: 0; + outline: none; + margin-left: 8px; + border-radius: 2px; + color: #555; + background-color: #fff; + border: 1px solid #ccc; +} + +.tootls_group .page .page_jump_group .page_jump_btn:hover { + color: #fff; + background-color: #20a53a; + border-color: #20a53a; +} + +.size_ellipsis { + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + + +.title.tabs-nav { + padding: 0; + position: relative; +} + +.title.tabs-nav span { + display: inline-block; + height: 50px; + min-width: 100px; + padding: 0 20px; + line-height: 50px; + font-size: 15px; + cursor: pointer; + position: relative; + text-align: center; +} + +.title.tabs-nav span.active { + color: #20a53a; + background: #20a53a08; +} + + +.title.tabs-nav select { + position: absolute; + right: 5px; + top: 50%; + margin-top: -12px; +} + +select[name="network-io"], select[name="disk-io"] { + font-size: 13px; + height: 24px; + line-height: 24px; + padding: 2px; + border-radius: 0; + border-color: #ececec; +} + +.tabs-content .tabs-item { + display: none; + height: 100%; + width: 100%; + background-color: #fff; +} + +.tabs-content .tabs-active.tabs-item { + display: inline-block; +} + +.bw-info { + height: 80px; +} + +.bw-info>div { + float: left; + padding-top: 35px; + text-align: center; +} + +.bw-info>div p { + margin-bottom: 7px; +} + +.bw-info>div a { + font-size: 14px; + color: #666; +} + +.bw-info .ico-down, +.bw-info .ico-up, +.bw-info .ico-write, +.bw-info .ico-read { + width: 10px; + height: 10px; + display: inline-block; + margin-right: 3px; + border-radius: 5px; +} + +.bw-info .ico-down { + background-color: #52a9ff; +} + +.bw-info .ico-up { + background-color: #f7b851; +} + +.bw-info .ico-write { + background-color: #6CC0CF; +} + +.bw-info .ico-read { + background-color: #FF4683; +} + +.bw-info ul li { + height: 56px; + line-height: 56px; +} + +.bw-info ul li.bi-line { + border-bottom: #ddd solid 1px; +} diff --git a/web/static/favicon-original.ico b/web/static/favicon-original.ico new file mode 100644 index 000000000..17e918a80 Binary files /dev/null and b/web/static/favicon-original.ico differ diff --git a/web/static/favicon.ico b/web/static/favicon.ico new file mode 100644 index 000000000..b4fa5f627 Binary files /dev/null and b/web/static/favicon.ico differ diff --git a/web/static/fonts/2.ttf b/web/static/fonts/2.ttf new file mode 100755 index 000000000..3a452b68f Binary files /dev/null and b/web/static/fonts/2.ttf differ diff --git a/web/static/fonts/material-icons.ttf b/web/static/fonts/material-icons.ttf new file mode 100644 index 000000000..9d09b0feb Binary files /dev/null and b/web/static/fonts/material-icons.ttf differ diff --git a/web/static/images/ico-ts-cpu-active.png b/web/static/images/ico-ts-cpu-active.png new file mode 100755 index 000000000..1abf4f009 Binary files /dev/null and b/web/static/images/ico-ts-cpu-active.png differ diff --git a/web/static/images/ico-ts-cpu.png b/web/static/images/ico-ts-cpu.png new file mode 100755 index 000000000..5c9d3eb38 Binary files /dev/null and b/web/static/images/ico-ts-cpu.png differ diff --git a/web/static/images/ico-ts-disk-active.png b/web/static/images/ico-ts-disk-active.png new file mode 100755 index 000000000..acaca696e Binary files /dev/null and b/web/static/images/ico-ts-disk-active.png differ diff --git a/web/static/images/ico-ts-disk.png b/web/static/images/ico-ts-disk.png new file mode 100755 index 000000000..73da82c16 Binary files /dev/null and b/web/static/images/ico-ts-disk.png differ diff --git a/web/static/images/ico-ts-mem-active.png b/web/static/images/ico-ts-mem-active.png new file mode 100755 index 000000000..d71de28ed Binary files /dev/null and b/web/static/images/ico-ts-mem-active.png differ diff --git a/web/static/images/ico-ts-mem.png b/web/static/images/ico-ts-mem.png new file mode 100755 index 000000000..d12b65438 Binary files /dev/null and b/web/static/images/ico-ts-mem.png differ diff --git a/web/static/images/ico-ts-refresh.png b/web/static/images/ico-ts-refresh.png new file mode 100755 index 000000000..b292189d8 Binary files /dev/null and b/web/static/images/ico-ts-refresh.png differ diff --git a/web/static/images/ico-ts-score.gif b/web/static/images/ico-ts-score.gif new file mode 100755 index 000000000..e4c8a65ca Binary files /dev/null and b/web/static/images/ico-ts-score.gif differ diff --git a/web/static/images/ico-ts-score.jpg b/web/static/images/ico-ts-score.jpg new file mode 100755 index 000000000..8eb5d62e5 Binary files /dev/null and b/web/static/images/ico-ts-score.jpg differ diff --git a/web/static/images/ico-ts-score1.png b/web/static/images/ico-ts-score1.png new file mode 100755 index 000000000..8219d32af Binary files /dev/null and b/web/static/images/ico-ts-score1.png differ diff --git a/web/static/images/ico-ts-score2.png b/web/static/images/ico-ts-score2.png new file mode 100755 index 000000000..1eff7f3b3 Binary files /dev/null and b/web/static/images/ico-ts-score2.png differ diff --git a/web/static/images/ico-ts-score3.png b/web/static/images/ico-ts-score3.png new file mode 100755 index 000000000..43cf9600f Binary files /dev/null and b/web/static/images/ico-ts-score3.png differ diff --git a/web/static/images/index.html b/web/static/images/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/images/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/images/logo.png b/web/static/images/logo.png new file mode 100755 index 000000000..4d96d6b6e Binary files /dev/null and b/web/static/images/logo.png differ diff --git a/web/static/images/move.png b/web/static/images/move.png new file mode 100755 index 000000000..85763763e Binary files /dev/null and b/web/static/images/move.png differ diff --git a/web/static/images/right.png b/web/static/images/right.png new file mode 100755 index 000000000..86e3aa594 Binary files /dev/null and b/web/static/images/right.png differ diff --git a/web/static/images/weixin.png b/web/static/images/weixin.png new file mode 100755 index 000000000..b0083a93b Binary files /dev/null and b/web/static/images/weixin.png differ diff --git a/web/static/img/Detailsbg.png b/web/static/img/Detailsbg.png new file mode 100755 index 000000000..28f5277d9 Binary files /dev/null and b/web/static/img/Detailsbg.png differ diff --git a/web/static/img/DrawRecordord.png b/web/static/img/DrawRecordord.png new file mode 100755 index 000000000..72c3966bd Binary files /dev/null and b/web/static/img/DrawRecordord.png differ diff --git a/web/static/img/account.png b/web/static/img/account.png new file mode 100755 index 000000000..8755ebff1 Binary files /dev/null and b/web/static/img/account.png differ diff --git a/web/static/img/alipay_zz.png b/web/static/img/alipay_zz.png new file mode 100644 index 000000000..98d99a533 Binary files /dev/null and b/web/static/img/alipay_zz.png differ diff --git a/web/static/img/ico-close.png b/web/static/img/ico-close.png new file mode 100755 index 000000000..d5b8a22c9 Binary files /dev/null and b/web/static/img/ico-close.png differ diff --git a/web/static/img/ico-computer.png b/web/static/img/ico-computer.png new file mode 100755 index 000000000..e383922da Binary files /dev/null and b/web/static/img/ico-computer.png differ diff --git a/web/static/img/ico-copy.png b/web/static/img/ico-copy.png new file mode 100755 index 000000000..adede38d2 Binary files /dev/null and b/web/static/img/ico-copy.png differ diff --git a/web/static/img/ico-home.png b/web/static/img/ico-home.png new file mode 100755 index 000000000..9de21b9d0 Binary files /dev/null and b/web/static/img/ico-home.png differ diff --git a/web/static/img/ico-success.png b/web/static/img/ico-success.png new file mode 100755 index 000000000..ba677b2c3 Binary files /dev/null and b/web/static/img/ico-success.png differ diff --git a/web/static/img/ico/ico-access.png b/web/static/img/ico/ico-access.png new file mode 100755 index 000000000..1a69b17de Binary files /dev/null and b/web/static/img/ico/ico-access.png differ diff --git a/web/static/img/ico/ico-apk.png b/web/static/img/ico/ico-apk.png new file mode 100755 index 000000000..27880ca2b Binary files /dev/null and b/web/static/img/ico/ico-apk.png differ diff --git a/web/static/img/ico/ico-avi.png b/web/static/img/ico/ico-avi.png new file mode 100755 index 000000000..e35454ca9 Binary files /dev/null and b/web/static/img/ico/ico-avi.png differ diff --git a/web/static/img/ico/ico-bmp.png b/web/static/img/ico/ico-bmp.png new file mode 100755 index 000000000..83f127829 Binary files /dev/null and b/web/static/img/ico/ico-bmp.png differ diff --git a/web/static/img/ico/ico-bt.png b/web/static/img/ico/ico-bt.png new file mode 100755 index 000000000..387293f80 Binary files /dev/null and b/web/static/img/ico/ico-bt.png differ diff --git a/web/static/img/ico/ico-c.png b/web/static/img/ico/ico-c.png new file mode 100755 index 000000000..21e4deb3e Binary files /dev/null and b/web/static/img/ico/ico-c.png differ diff --git a/web/static/img/ico/ico-cdr.png b/web/static/img/ico/ico-cdr.png new file mode 100755 index 000000000..2e16c24e0 Binary files /dev/null and b/web/static/img/ico/ico-cdr.png differ diff --git a/web/static/img/ico/ico-cpp.png b/web/static/img/ico/ico-cpp.png new file mode 100755 index 000000000..7a8a5cf90 Binary files /dev/null and b/web/static/img/ico/ico-cpp.png differ diff --git a/web/static/img/ico/ico-cs.png b/web/static/img/ico/ico-cs.png new file mode 100755 index 000000000..658831976 Binary files /dev/null and b/web/static/img/ico/ico-cs.png differ diff --git a/web/static/img/ico/ico-doc.png b/web/static/img/ico/ico-doc.png new file mode 100755 index 000000000..ca27a9666 Binary files /dev/null and b/web/static/img/ico/ico-doc.png differ diff --git a/web/static/img/ico/ico-docx.png b/web/static/img/ico/ico-docx.png new file mode 100755 index 000000000..702d2633d Binary files /dev/null and b/web/static/img/ico/ico-docx.png differ diff --git a/web/static/img/ico/ico-flv.png b/web/static/img/ico/ico-flv.png new file mode 100755 index 000000000..973435f5d Binary files /dev/null and b/web/static/img/ico/ico-flv.png differ diff --git a/web/static/img/ico/ico-gif.png b/web/static/img/ico/ico-gif.png new file mode 100755 index 000000000..53a135168 Binary files /dev/null and b/web/static/img/ico/ico-gif.png differ diff --git a/web/static/img/ico/ico-htm.png b/web/static/img/ico/ico-htm.png new file mode 100755 index 000000000..14665dda0 Binary files /dev/null and b/web/static/img/ico/ico-htm.png differ diff --git a/web/static/img/ico/ico-html.png b/web/static/img/ico/ico-html.png new file mode 100755 index 000000000..a29b6297f Binary files /dev/null and b/web/static/img/ico/ico-html.png differ diff --git a/web/static/img/ico/ico-java.png b/web/static/img/ico/ico-java.png new file mode 100755 index 000000000..20affd320 Binary files /dev/null and b/web/static/img/ico/ico-java.png differ diff --git a/web/static/img/ico/ico-jpeg.png b/web/static/img/ico/ico-jpeg.png new file mode 100755 index 000000000..f28f52905 Binary files /dev/null and b/web/static/img/ico/ico-jpeg.png differ diff --git a/web/static/img/ico/ico-jpg.png b/web/static/img/ico/ico-jpg.png new file mode 100755 index 000000000..989551b66 Binary files /dev/null and b/web/static/img/ico/ico-jpg.png differ diff --git a/web/static/img/ico/ico-js.png b/web/static/img/ico/ico-js.png new file mode 100755 index 000000000..35691771d Binary files /dev/null and b/web/static/img/ico/ico-js.png differ diff --git a/web/static/img/ico/ico-ltr.png b/web/static/img/ico/ico-ltr.png new file mode 100755 index 000000000..dc6d48718 Binary files /dev/null and b/web/static/img/ico/ico-ltr.png differ diff --git a/web/static/img/ico/ico-mht.png b/web/static/img/ico/ico-mht.png new file mode 100755 index 000000000..296a7ca14 Binary files /dev/null and b/web/static/img/ico/ico-mht.png differ diff --git a/web/static/img/ico/ico-mkv.png b/web/static/img/ico/ico-mkv.png new file mode 100755 index 000000000..00abc0be9 Binary files /dev/null and b/web/static/img/ico/ico-mkv.png differ diff --git a/web/static/img/ico/ico-mov.png b/web/static/img/ico/ico-mov.png new file mode 100755 index 000000000..4ab1a5c3d Binary files /dev/null and b/web/static/img/ico/ico-mov.png differ diff --git a/web/static/img/ico/ico-mp4.png b/web/static/img/ico/ico-mp4.png new file mode 100755 index 000000000..9dd90f5c4 Binary files /dev/null and b/web/static/img/ico/ico-mp4.png differ diff --git a/web/static/img/ico/ico-mpeg.png b/web/static/img/ico/ico-mpeg.png new file mode 100755 index 000000000..c092f67ed Binary files /dev/null and b/web/static/img/ico/ico-mpeg.png differ diff --git a/web/static/img/ico/ico-mpg.png b/web/static/img/ico/ico-mpg.png new file mode 100755 index 000000000..4cc52b6fe Binary files /dev/null and b/web/static/img/ico/ico-mpg.png differ diff --git a/web/static/img/ico/ico-pdf.png b/web/static/img/ico/ico-pdf.png new file mode 100755 index 000000000..2dbcafb38 Binary files /dev/null and b/web/static/img/ico/ico-pdf.png differ diff --git a/web/static/img/ico/ico-php.png b/web/static/img/ico/ico-php.png new file mode 100755 index 000000000..7145c652f Binary files /dev/null and b/web/static/img/ico/ico-php.png differ diff --git a/web/static/img/ico/ico-png.png b/web/static/img/ico/ico-png.png new file mode 100755 index 000000000..f39804a9c Binary files /dev/null and b/web/static/img/ico/ico-png.png differ diff --git a/web/static/img/ico/ico-pptx.png b/web/static/img/ico/ico-pptx.png new file mode 100755 index 000000000..f1be14b5c Binary files /dev/null and b/web/static/img/ico/ico-pptx.png differ diff --git a/web/static/img/ico/ico-psd.png b/web/static/img/ico/ico-psd.png new file mode 100755 index 000000000..f25968f48 Binary files /dev/null and b/web/static/img/ico/ico-psd.png differ diff --git a/web/static/img/ico/ico-rm.png b/web/static/img/ico/ico-rm.png new file mode 100755 index 000000000..e4cb6ca3e Binary files /dev/null and b/web/static/img/ico/ico-rm.png differ diff --git a/web/static/img/ico/ico-rmvb.png b/web/static/img/ico/ico-rmvb.png new file mode 100755 index 000000000..5b9805a01 Binary files /dev/null and b/web/static/img/ico/ico-rmvb.png differ diff --git a/web/static/img/ico/ico-rocket.gif b/web/static/img/ico/ico-rocket.gif new file mode 100755 index 000000000..470b2e1c5 Binary files /dev/null and b/web/static/img/ico/ico-rocket.gif differ diff --git a/web/static/img/ico/ico-swf.png b/web/static/img/ico/ico-swf.png new file mode 100755 index 000000000..d043a2e2a Binary files /dev/null and b/web/static/img/ico/ico-swf.png differ diff --git a/web/static/img/ico/ico-url.png b/web/static/img/ico/ico-url.png new file mode 100755 index 000000000..ab86ae7f9 Binary files /dev/null and b/web/static/img/ico/ico-url.png differ diff --git a/web/static/img/ico/ico-webm.png b/web/static/img/ico/ico-webm.png new file mode 100755 index 000000000..e568c4250 Binary files /dev/null and b/web/static/img/ico/ico-webm.png differ diff --git a/web/static/img/ico/ico-webp.png b/web/static/img/ico/ico-webp.png new file mode 100755 index 000000000..6b8cc4dc7 Binary files /dev/null and b/web/static/img/ico/ico-webp.png differ diff --git a/web/static/img/ico/ico-wma.png b/web/static/img/ico/ico-wma.png new file mode 100755 index 000000000..036e0b642 Binary files /dev/null and b/web/static/img/ico/ico-wma.png differ diff --git a/web/static/img/ico/ico-wmv.png b/web/static/img/ico/ico-wmv.png new file mode 100755 index 000000000..ba03d507f Binary files /dev/null and b/web/static/img/ico/ico-wmv.png differ diff --git a/web/static/img/ico/ico-xls.png b/web/static/img/ico/ico-xls.png new file mode 100755 index 000000000..e141b11b2 Binary files /dev/null and b/web/static/img/ico/ico-xls.png differ diff --git a/web/static/img/ico/ico-xlsx.png b/web/static/img/ico/ico-xlsx.png new file mode 100755 index 000000000..66fb34215 Binary files /dev/null and b/web/static/img/ico/ico-xlsx.png differ diff --git a/web/static/img/ico/ico-xml.png b/web/static/img/ico/ico-xml.png new file mode 100755 index 000000000..8368fb67b Binary files /dev/null and b/web/static/img/ico/ico-xml.png differ diff --git a/web/static/img/ico/index.html b/web/static/img/ico/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/img/ico/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/img/ico/menu_icon_control.png b/web/static/img/ico/menu_icon_control.png new file mode 100755 index 000000000..c2ecbc080 Binary files /dev/null and b/web/static/img/ico/menu_icon_control.png differ diff --git a/web/static/img/ico/menu_icon_control_active.gif b/web/static/img/ico/menu_icon_control_active.gif new file mode 100755 index 000000000..ba56df8e9 Binary files /dev/null and b/web/static/img/ico/menu_icon_control_active.gif differ diff --git a/web/static/img/ico/menu_icon_control_active.png b/web/static/img/ico/menu_icon_control_active.png new file mode 100755 index 000000000..d698ab70e Binary files /dev/null and b/web/static/img/ico/menu_icon_control_active.png differ diff --git a/web/static/img/ico/menu_icon_data.png b/web/static/img/ico/menu_icon_data.png new file mode 100755 index 000000000..c8ee474bf Binary files /dev/null and b/web/static/img/ico/menu_icon_data.png differ diff --git a/web/static/img/ico/menu_icon_data_active.gif b/web/static/img/ico/menu_icon_data_active.gif new file mode 100755 index 000000000..04b6e0d42 Binary files /dev/null and b/web/static/img/ico/menu_icon_data_active.gif differ diff --git a/web/static/img/ico/menu_icon_data_active.png b/web/static/img/ico/menu_icon_data_active.png new file mode 100755 index 000000000..fe30c4b63 Binary files /dev/null and b/web/static/img/ico/menu_icon_data_active.png differ diff --git a/web/static/img/ico/menu_icon_day.png b/web/static/img/ico/menu_icon_day.png new file mode 100755 index 000000000..9fbccf0be Binary files /dev/null and b/web/static/img/ico/menu_icon_day.png differ diff --git a/web/static/img/ico/menu_icon_day_active.gif b/web/static/img/ico/menu_icon_day_active.gif new file mode 100755 index 000000000..6f98a0f01 Binary files /dev/null and b/web/static/img/ico/menu_icon_day_active.gif differ diff --git a/web/static/img/ico/menu_icon_day_active.png b/web/static/img/ico/menu_icon_day_active.png new file mode 100755 index 000000000..e128e163f Binary files /dev/null and b/web/static/img/ico/menu_icon_day_active.png differ diff --git a/web/static/img/ico/menu_icon_exit.png b/web/static/img/ico/menu_icon_exit.png new file mode 100755 index 000000000..75feec4b1 Binary files /dev/null and b/web/static/img/ico/menu_icon_exit.png differ diff --git a/web/static/img/ico/menu_icon_exit_active.gif b/web/static/img/ico/menu_icon_exit_active.gif new file mode 100755 index 000000000..b11bd5d3d Binary files /dev/null and b/web/static/img/ico/menu_icon_exit_active.gif differ diff --git a/web/static/img/ico/menu_icon_exit_active.png b/web/static/img/ico/menu_icon_exit_active.png new file mode 100755 index 000000000..dc81d480f Binary files /dev/null and b/web/static/img/ico/menu_icon_exit_active.png differ diff --git a/web/static/img/ico/menu_icon_firewall.png b/web/static/img/ico/menu_icon_firewall.png new file mode 100755 index 000000000..f05938dd8 Binary files /dev/null and b/web/static/img/ico/menu_icon_firewall.png differ diff --git a/web/static/img/ico/menu_icon_firewall_active.gif b/web/static/img/ico/menu_icon_firewall_active.gif new file mode 100755 index 000000000..3597a5b42 Binary files /dev/null and b/web/static/img/ico/menu_icon_firewall_active.gif differ diff --git a/web/static/img/ico/menu_icon_firewall_active.png b/web/static/img/ico/menu_icon_firewall_active.png new file mode 100755 index 000000000..5c2f49f79 Binary files /dev/null and b/web/static/img/ico/menu_icon_firewall_active.png differ diff --git a/web/static/img/ico/menu_icon_folder.png b/web/static/img/ico/menu_icon_folder.png new file mode 100755 index 000000000..eb3461c42 Binary files /dev/null and b/web/static/img/ico/menu_icon_folder.png differ diff --git a/web/static/img/ico/menu_icon_folder_active.gif b/web/static/img/ico/menu_icon_folder_active.gif new file mode 100755 index 000000000..ac4564368 Binary files /dev/null and b/web/static/img/ico/menu_icon_folder_active.gif differ diff --git a/web/static/img/ico/menu_icon_folder_active.png b/web/static/img/ico/menu_icon_folder_active.png new file mode 100755 index 000000000..871688f67 Binary files /dev/null and b/web/static/img/ico/menu_icon_folder_active.png differ diff --git a/web/static/img/ico/menu_icon_ftp.png b/web/static/img/ico/menu_icon_ftp.png new file mode 100755 index 000000000..d57b21cd9 Binary files /dev/null and b/web/static/img/ico/menu_icon_ftp.png differ diff --git a/web/static/img/ico/menu_icon_ftp_active.gif b/web/static/img/ico/menu_icon_ftp_active.gif new file mode 100755 index 000000000..84d708723 Binary files /dev/null and b/web/static/img/ico/menu_icon_ftp_active.gif differ diff --git a/web/static/img/ico/menu_icon_ftp_active.png b/web/static/img/ico/menu_icon_ftp_active.png new file mode 100755 index 000000000..394a71a80 Binary files /dev/null and b/web/static/img/ico/menu_icon_ftp_active.png differ diff --git a/web/static/img/ico/menu_icon_home.png b/web/static/img/ico/menu_icon_home.png new file mode 100755 index 000000000..e198671c0 Binary files /dev/null and b/web/static/img/ico/menu_icon_home.png differ diff --git a/web/static/img/ico/menu_icon_home_active.gif b/web/static/img/ico/menu_icon_home_active.gif new file mode 100755 index 000000000..96c3bcbf9 Binary files /dev/null and b/web/static/img/ico/menu_icon_home_active.gif differ diff --git a/web/static/img/ico/menu_icon_home_active.png b/web/static/img/ico/menu_icon_home_active.png new file mode 100755 index 000000000..636e26ebe Binary files /dev/null and b/web/static/img/ico/menu_icon_home_active.png differ diff --git a/web/static/img/ico/menu_icon_log.png b/web/static/img/ico/menu_icon_log.png new file mode 100755 index 000000000..774743d9c Binary files /dev/null and b/web/static/img/ico/menu_icon_log.png differ diff --git a/web/static/img/ico/menu_icon_log_active.png b/web/static/img/ico/menu_icon_log_active.png new file mode 100755 index 000000000..0d0e2f156 Binary files /dev/null and b/web/static/img/ico/menu_icon_log_active.png differ diff --git a/web/static/img/ico/menu_icon_set.png b/web/static/img/ico/menu_icon_set.png new file mode 100755 index 000000000..74b247163 Binary files /dev/null and b/web/static/img/ico/menu_icon_set.png differ diff --git a/web/static/img/ico/menu_icon_set_active.gif b/web/static/img/ico/menu_icon_set_active.gif new file mode 100755 index 000000000..82fa4f264 Binary files /dev/null and b/web/static/img/ico/menu_icon_set_active.gif differ diff --git a/web/static/img/ico/menu_icon_set_active.png b/web/static/img/ico/menu_icon_set_active.png new file mode 100755 index 000000000..1f61f2539 Binary files /dev/null and b/web/static/img/ico/menu_icon_set_active.png differ diff --git a/web/static/img/ico/menu_icon_soft.png b/web/static/img/ico/menu_icon_soft.png new file mode 100755 index 000000000..0ea48356b Binary files /dev/null and b/web/static/img/ico/menu_icon_soft.png differ diff --git a/web/static/img/ico/menu_icon_soft_active.gif b/web/static/img/ico/menu_icon_soft_active.gif new file mode 100755 index 000000000..779c78f23 Binary files /dev/null and b/web/static/img/ico/menu_icon_soft_active.gif differ diff --git a/web/static/img/ico/menu_icon_soft_active.png b/web/static/img/ico/menu_icon_soft_active.png new file mode 100755 index 000000000..f0c8e4a98 Binary files /dev/null and b/web/static/img/ico/menu_icon_soft_active.png differ diff --git a/web/static/img/ico/menu_icon_web.png b/web/static/img/ico/menu_icon_web.png new file mode 100755 index 000000000..cad87fbe3 Binary files /dev/null and b/web/static/img/ico/menu_icon_web.png differ diff --git a/web/static/img/ico/menu_icon_web_active.gif b/web/static/img/ico/menu_icon_web_active.gif new file mode 100755 index 000000000..81c90c85d Binary files /dev/null and b/web/static/img/ico/menu_icon_web_active.gif differ diff --git a/web/static/img/ico/menu_icon_web_active.png b/web/static/img/ico/menu_icon_web_active.png new file mode 100755 index 000000000..262302881 Binary files /dev/null and b/web/static/img/ico/menu_icon_web_active.png differ diff --git a/web/static/img/ico/rocket_min.png b/web/static/img/ico/rocket_min.png new file mode 100755 index 000000000..2198d4086 Binary files /dev/null and b/web/static/img/ico/rocket_min.png differ diff --git a/web/static/img/ico_line.png b/web/static/img/ico_line.png new file mode 100755 index 000000000..330c8a93b Binary files /dev/null and b/web/static/img/ico_line.png differ diff --git a/web/static/img/icon-item.png b/web/static/img/icon-item.png new file mode 100755 index 000000000..4f7afd85f Binary files /dev/null and b/web/static/img/icon-item.png differ diff --git a/web/static/img/icon3.png b/web/static/img/icon3.png new file mode 100755 index 000000000..d2b25e28d Binary files /dev/null and b/web/static/img/icon3.png differ diff --git a/web/static/img/icon4.png b/web/static/img/icon4.png new file mode 100755 index 000000000..ef34ed5f9 Binary files /dev/null and b/web/static/img/icon4.png differ diff --git a/web/static/img/index.html b/web/static/img/index.html new file mode 100755 index 000000000..e69de29bb diff --git a/web/static/img/ing.gif b/web/static/img/ing.gif new file mode 100755 index 000000000..6b87c9271 Binary files /dev/null and b/web/static/img/ing.gif differ diff --git a/web/static/img/ings.gif b/web/static/img/ings.gif new file mode 100755 index 000000000..88a262116 Binary files /dev/null and b/web/static/img/ings.gif differ diff --git a/web/static/img/label-icon.png b/web/static/img/label-icon.png new file mode 100755 index 000000000..5d0aa3dce Binary files /dev/null and b/web/static/img/label-icon.png differ diff --git a/web/static/img/loading.gif b/web/static/img/loading.gif new file mode 100755 index 000000000..e1691ac98 Binary files /dev/null and b/web/static/img/loading.gif differ diff --git a/web/static/img/morebtn.png b/web/static/img/morebtn.png new file mode 100755 index 000000000..caf1d593f Binary files /dev/null and b/web/static/img/morebtn.png differ diff --git a/web/static/img/ns-loading.gif b/web/static/img/ns-loading.gif new file mode 100755 index 000000000..095a1b5af Binary files /dev/null and b/web/static/img/ns-loading.gif differ diff --git a/web/static/img/ref-icon.png b/web/static/img/ref-icon.png new file mode 100755 index 000000000..8e6435218 Binary files /dev/null and b/web/static/img/ref-icon.png differ diff --git a/web/static/img/return-icon.png b/web/static/img/return-icon.png new file mode 100755 index 000000000..538fcdc67 Binary files /dev/null and b/web/static/img/return-icon.png differ diff --git a/web/static/img/safety_ico.png b/web/static/img/safety_ico.png new file mode 100755 index 000000000..c3fd93edf Binary files /dev/null and b/web/static/img/safety_ico.png differ diff --git a/web/static/img/ser-icon.png b/web/static/img/ser-icon.png new file mode 100755 index 000000000..6ac0130be Binary files /dev/null and b/web/static/img/ser-icon.png differ diff --git a/web/static/img/soft_ico/ico-app.png b/web/static/img/soft_ico/ico-app.png new file mode 100755 index 000000000..1f00d6712 Binary files /dev/null and b/web/static/img/soft_ico/ico-app.png differ diff --git a/web/static/img/soft_ico/ico-beta.png b/web/static/img/soft_ico/ico-beta.png new file mode 100755 index 000000000..bdbd32afb Binary files /dev/null and b/web/static/img/soft_ico/ico-beta.png differ diff --git a/web/static/img/soft_ico/ico-cmd-hover.png b/web/static/img/soft_ico/ico-cmd-hover.png new file mode 100755 index 000000000..cad45fc5d Binary files /dev/null and b/web/static/img/soft_ico/ico-cmd-hover.png differ diff --git a/web/static/img/soft_ico/ico-cmd.png b/web/static/img/soft_ico/ico-cmd.png new file mode 100755 index 000000000..981e03f4c Binary files /dev/null and b/web/static/img/soft_ico/ico-cmd.png differ diff --git a/web/static/img/soft_ico/ico-deployment.png b/web/static/img/soft_ico/ico-deployment.png new file mode 100755 index 000000000..dd2d565aa Binary files /dev/null and b/web/static/img/soft_ico/ico-deployment.png differ diff --git a/web/static/img/soft_ico/ico-load_leveling.png b/web/static/img/soft_ico/ico-load_leveling.png new file mode 100755 index 000000000..c20e1edc6 Binary files /dev/null and b/web/static/img/soft_ico/ico-load_leveling.png differ diff --git a/web/static/img/soft_ico/ico-log.png b/web/static/img/soft_ico/ico-log.png new file mode 100755 index 000000000..efe1b77c1 Binary files /dev/null and b/web/static/img/soft_ico/ico-log.png differ diff --git a/web/static/img/soft_ico/ico-logs.png b/web/static/img/soft_ico/ico-logs.png new file mode 100755 index 000000000..a27db7bba Binary files /dev/null and b/web/static/img/soft_ico/ico-logs.png differ diff --git a/web/static/img/soft_ico/ico-mongodb.png b/web/static/img/soft_ico/ico-mongodb.png new file mode 100755 index 000000000..9c1eaac3e Binary files /dev/null and b/web/static/img/soft_ico/ico-mongodb.png differ diff --git a/web/static/img/soft_ico/ico-node.png b/web/static/img/soft_ico/ico-node.png new file mode 100755 index 000000000..0474de49a Binary files /dev/null and b/web/static/img/soft_ico/ico-node.png differ diff --git a/web/static/img/soft_ico/ico-phpsafe.png b/web/static/img/soft_ico/ico-phpsafe.png new file mode 100755 index 000000000..a3e1b6115 Binary files /dev/null and b/web/static/img/soft_ico/ico-phpsafe.png differ diff --git a/web/static/img/soft_ico/ico-pm2.png b/web/static/img/soft_ico/ico-pm2.png new file mode 100755 index 000000000..62cd3d1a1 Binary files /dev/null and b/web/static/img/soft_ico/ico-pm2.png differ diff --git a/web/static/img/soft_ico/ico-psync.png b/web/static/img/soft_ico/ico-psync.png new file mode 100755 index 000000000..31015316d Binary files /dev/null and b/web/static/img/soft_ico/ico-psync.png differ diff --git a/web/static/img/soft_ico/ico-rsync.png b/web/static/img/soft_ico/ico-rsync.png new file mode 100755 index 000000000..2843739f1 Binary files /dev/null and b/web/static/img/soft_ico/ico-rsync.png differ diff --git a/web/static/img/soft_ico/ico-safelogin.png b/web/static/img/soft_ico/ico-safelogin.png new file mode 100755 index 000000000..452dc0fbf Binary files /dev/null and b/web/static/img/soft_ico/ico-safelogin.png differ diff --git a/web/static/img/soft_ico/ico-score.png b/web/static/img/soft_ico/ico-score.png new file mode 100755 index 000000000..68e9077a6 Binary files /dev/null and b/web/static/img/soft_ico/ico-score.png differ diff --git a/web/static/img/soft_ico/ico-task_manager.png b/web/static/img/soft_ico/ico-task_manager.png new file mode 100755 index 000000000..ad97d2181 Binary files /dev/null and b/web/static/img/soft_ico/ico-task_manager.png differ diff --git a/web/static/img/soft_ico/ico-test.png b/web/static/img/soft_ico/ico-test.png new file mode 100755 index 000000000..cb28b12e5 Binary files /dev/null and b/web/static/img/soft_ico/ico-test.png differ diff --git a/web/static/img/soft_ico/ico-tomcat.png b/web/static/img/soft_ico/ico-tomcat.png new file mode 100755 index 000000000..2e3e7dd5b Binary files /dev/null and b/web/static/img/soft_ico/ico-tomcat.png differ diff --git a/web/static/img/soft_ico/ico-txcos.png b/web/static/img/soft_ico/ico-txcos.png new file mode 100755 index 000000000..9bc7d21d2 Binary files /dev/null and b/web/static/img/soft_ico/ico-txcos.png differ diff --git a/web/static/img/soft_ico/ico-webhook.png b/web/static/img/soft_ico/ico-webhook.png new file mode 100755 index 000000000..bca24ab1b Binary files /dev/null and b/web/static/img/soft_ico/ico-webhook.png differ diff --git a/web/static/img/soft_ico/index.html b/web/static/img/soft_ico/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/img/soft_ico/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/img/success-pic.png b/web/static/img/success-pic.png new file mode 100755 index 000000000..c46be849e Binary files /dev/null and b/web/static/img/success-pic.png differ diff --git a/web/static/img/tip_suu.png b/web/static/img/tip_suu.png new file mode 100755 index 000000000..3884ec96c Binary files /dev/null and b/web/static/img/tip_suu.png differ diff --git a/web/static/img/weixin_zz.jpg b/web/static/img/weixin_zz.jpg new file mode 100644 index 000000000..4d3c6105b Binary files /dev/null and b/web/static/img/weixin_zz.jpg differ diff --git a/web/static/index.html b/web/static/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/js/Validform_v5.3.2_min.js b/web/static/js/Validform_v5.3.2_min.js new file mode 100755 index 000000000..4eb4f7d89 --- /dev/null +++ b/web/static/js/Validform_v5.3.2_min.js @@ -0,0 +1,8 @@ +/* + Validform version 5.3.2 + By sean during April 7, 2010 - March 26, 2013 + For more information, please visit http://validform.rjboy.cn + Validform is available under the terms of the MIT license. +*/ + +(function(d,f,b){var g=null,j=null,i=true;var e={tit:"提示信息",w:{"*":"不能为空!","*6-16":"请填写6到16位任意字符!","n":"请填写数字!","n6-16":"请填写6到16位数字!","s":"不能输入特殊字符!","s6-18":"请填写6到18位字符!","p":"请填写邮政编码!","m":"请填写手机号码!","e":"邮箱地址格式不对!","url":"请填写网址!"},def:"请填写正确信息!",undef:"datatype未定义!",reck:"两次输入的内容不一致!",r:"",c:"正在检测信息…",s:"请{填写|选择}{0|信息}!",v:"所填信息没有经过验证,请稍后…",p:"正在提交数据…"};d.Tipmsg=e;var a=function(l,n,k){var n=d.extend({},a.defaults,n);n.datatype&&d.extend(a.util.dataType,n.datatype);var m=this;m.tipmsg={w:{}};m.forms=l;m.objects=[];if(k===true){return false}l.each(function(){if(this.validform_inited=="inited"){return true}this.validform_inited="inited";var p=this;p.settings=d.extend({},n);var o=d(p);p.validform_status="normal";o.data("tipmsg",m.tipmsg);o.delegate("[datatype]","blur",function(){var q=arguments[1];a.util.check.call(this,o,q)});o.delegate(":text","keypress",function(q){if(q.keyCode==13&&o.find(":submit").length==0){o.submit()}});a.util.enhance.call(o,p.settings.tiptype,p.settings.usePlugin,p.settings.tipSweep);p.settings.btnSubmit&&o.find(p.settings.btnSubmit).bind("click",function(){o.trigger("submit");return false});o.submit(function(){var q=a.util.submitForm.call(o,p.settings);q===b&&(q=true);return q});o.find("[type='reset']").add(o.find(p.settings.btnReset)).bind("click",function(){a.util.resetForm.call(o)})});if(n.tiptype==1||(n.tiptype==2||n.tiptype==3)&&n.ajaxPost){c()}};a.defaults={tiptype:1,tipSweep:false,showAllError:false,postonce:false,ajaxPost:false};a.util={dataType:{"*":/[\w\W]+/,"*6-16":/^[\w\W]{6,16}$/,n:/^\d+$/,"n6-16":/^\d{6,16}$/,s:/^[\u4E00-\u9FA5\uf900-\ufa2d\w\.\s]+$/,"s6-18":/^[\u4E00-\u9FA5\uf900-\ufa2d\w\.\s]{6,18}$/,p:/^[0-9]{6}$/,m:/^13[0-9]{9}$|14[0-9]{9}|15[0-9]{9}$|18[0-9]{9}$/,e:/^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,url:/^(\w+:\/\/)?\w+(\.\w+)+.*$/},toString:Object.prototype.toString,isEmpty:function(k){return k===""||k===d.trim(this.attr("tip"))},getValue:function(m){var l,k=this;if(m.is(":radio")){l=k.find(":radio[name='"+m.attr("name")+"']:checked").val();l=l===b?"":l}else{if(m.is(":checkbox")){l="";k.find(":checkbox[name='"+m.attr("name")+"']:checked").each(function(){l+=d(this).val()+","});l=l===b?"":l}else{l=m.val()}}l=d.trim(l);return a.util.isEmpty.call(m,l)?"":l},enhance:function(l,m,n,k){var o=this;o.find("[datatype]").each(function(){if(l==2){if(d(this).parent().next().find(".Validform_checktip").length==0){d(this).parent().next().append("");d(this).siblings(".Validform_checktip").remove()}}else{if(l==3||l==4){if(d(this).siblings(".Validform_checktip").length==0){d(this).parent().append("");d(this).parent().next().find(".Validform_checktip").remove()}}}});o.find("input[recheck]").each(function(){if(this.validform_inited=="inited"){return true}this.validform_inited="inited";var q=d(this);var p=o.find("input[name='"+d(this).attr("recheck")+"']");p.bind("keyup",function(){if(p.val()==q.val()&&p.val()!=""){if(p.attr("tip")){if(p.attr("tip")==p.val()){return false}}q.trigger("blur")}}).bind("blur",function(){if(p.val()!=q.val()&&q.val()!=""){if(q.attr("tip")){if(q.attr("tip")==q.val()){return false}}q.trigger("blur")}})});o.find("[tip]").each(function(){if(this.validform_inited=="inited"){return true}this.validform_inited="inited";var q=d(this).attr("tip");var p=d(this).attr("altercss");d(this).focus(function(){if(d(this).val()==q){d(this).val("");if(p){d(this).removeClass(p)}}}).blur(function(){if(d.trim(d(this).val())===""){d(this).val(q);if(p){d(this).addClass(p)}}})});o.find(":checkbox[datatype],:radio[datatype]").each(function(){if(this.validform_inited=="inited"){return true}this.validform_inited="inited";var q=d(this);var p=q.attr("name");o.find("[name='"+p+"']").filter(":checkbox,:radio").bind("click",function(){setTimeout(function(){q.trigger("blur")},0)})});o.find("select[datatype][multiple]").bind("click",function(){var p=d(this);setTimeout(function(){p.trigger("blur")},0)});a.util.usePlugin.call(o,m,l,n,k)},usePlugin:function(o,l,n,r){var s=this,o=o||{};if(s.find("input[plugin='swfupload']").length&&typeof(swfuploadhandler)!="undefined"){var k={custom_settings:{form:s,showmsg:function(v,t,u){a.util.showmsg.call(s,v,l,{obj:s.find("input[plugin='swfupload']"),type:t,sweep:n})}}};k=d.extend(true,{},o.swfupload,k);s.find("input[plugin='swfupload']").each(function(t){if(this.validform_inited=="inited"){return true}this.validform_inited="inited";d(this).val("");swfuploadhandler.init(k,t)})}if(s.find("input[plugin='datepicker']").length&&d.fn.datePicker){o.datepicker=o.datepicker||{};if(o.datepicker.format){Date.format=o.datepicker.format;delete o.datepicker.format}if(o.datepicker.firstDayOfWeek){Date.firstDayOfWeek=o.datepicker.firstDayOfWeek;delete o.datepicker.firstDayOfWeek}s.find("input[plugin='datepicker']").each(function(t){if(this.validform_inited=="inited"){return true}this.validform_inited="inited";o.datepicker.callback&&d(this).bind("dateSelected",function(){var u=new Date(d.event._dpCache[this._dpId].getSelected()[0]).asString(Date.format);o.datepicker.callback(u,this)});d(this).datePicker(o.datepicker)})}if(s.find("input[plugin*='passwordStrength']").length&&d.fn.passwordStrength){o.passwordstrength=o.passwordstrength||{};o.passwordstrength.showmsg=function(u,v,t){a.util.showmsg.call(s,v,l,{obj:u,type:t,sweep:n})};s.find("input[plugin='passwordStrength']").each(function(t){if(this.validform_inited=="inited"){return true}this.validform_inited="inited";d(this).passwordStrength(o.passwordstrength)})}if(r!="addRule"&&o.jqtransform&&d.fn.jqTransSelect){if(s[0].jqTransSelected=="true"){return}s[0].jqTransSelected="true";var m=function(t){var u=d(".jqTransformSelectWrapper ul:visible");u.each(function(){var v=d(this).parents(".jqTransformSelectWrapper:first").find("select").get(0);if(!(t&&v.oLabel&&v.oLabel.get(0)==t.get(0))){d(this).hide()}})};var p=function(t){if(d(t.target).parents(".jqTransformSelectWrapper").length===0){m(d(t.target))}};var q=function(){d(document).mousedown(p)};if(o.jqtransform.selector){s.find(o.jqtransform.selector).filter('input:submit, input:reset, input[type="button"]').jqTransInputButton();s.find(o.jqtransform.selector).filter("input:text, input:password").jqTransInputText();s.find(o.jqtransform.selector).filter("input:checkbox").jqTransCheckBox();s.find(o.jqtransform.selector).filter("input:radio").jqTransRadio();s.find(o.jqtransform.selector).filter("textarea").jqTransTextarea();if(s.find(o.jqtransform.selector).filter("select").length>0){s.find(o.jqtransform.selector).filter("select").jqTransSelect();q()}}else{s.jqTransform()}s.find(".jqTransformSelectWrapper").find("li a").click(function(){d(this).parents(".jqTransformSelectWrapper").find("select").trigger("blur")})}},getNullmsg:function(o){var n=this;var m=/[\u4E00-\u9FA5\uf900-\ufa2da-zA-Z\s]+/g;var k;var l=o[0].settings.label||".Validform_label";l=n.siblings(l).eq(0).text()||n.siblings().find(l).eq(0).text()||n.parent().siblings(l).eq(0).text()||n.parent().siblings().find(l).eq(0).text();l=l.replace(/\s(?![a-zA-Z])/g,"").match(m);l=l?l.join(""):[""];m=/\{(.+)\|(.+)\}/;k=o.data("tipmsg").s||e.s;if(l!=""){k=k.replace(/\{0\|(.+)\}/,l);if(n.attr("recheck")){k=k.replace(/\{(.+)\}/,"");n.attr("nullmsg",k);return k}}else{k=n.is(":checkbox,:radio,select")?k.replace(/\{0\|(.+)\}/,""):k.replace(/\{0\|(.+)\}/,"$1")}k=n.is(":checkbox,:radio,select")?k.replace(m,"$2"):k.replace(m,"$1");n.attr("nullmsg",k);return k},getErrormsg:function(s,n,u){var o=/^(.+?)((\d+)-(\d+))?$/,m=/^(.+?)(\d+)-(\d+)$/,l=/(.*?)\d+(.+?)\d+(.*)/,q=n.match(o),t,r;if(u=="recheck"){r=s.data("tipmsg").reck||e.reck;return r}var p=d.extend({},e.w,s.data("tipmsg").w);if(q[0] in p){return s.data("tipmsg").w[q[0]]||e.w[q[0]]}for(var k in p){if(k.indexOf(q[1])!=-1&&m.test(k)){r=(s.data("tipmsg").w[k]||e.w[k]).replace(l,"$1"+q[3]+"$2"+q[4]+"$3");s.data("tipmsg").w[q[0]]=r;return r}}return s.data("tipmsg").def||e.def},_regcheck:function(t,n,u,A){var A=A,y=null,v=false,o=/\/.+\//g,k=/^(.+?)(\d+)-(\d+)$/,l=3;if(o.test(t)){var s=t.match(o)[0].slice(1,-1);var r=t.replace(o,"");var q=RegExp(s,r);v=q.test(n)}else{if(a.util.toString.call(a.util.dataType[t])=="[object Function]"){v=a.util.dataType[t](n,u,A,a.util.dataType);if(v===true||v===b){v=true}else{y=v;v=false}}else{if(!(t in a.util.dataType)){var m=t.match(k),z;if(!m){v=false;y=A.data("tipmsg").undef||e.undef}else{for(var B in a.util.dataType){z=B.match(k);if(!z){continue}if(m[1]===z[1]){var w=a.util.dataType[B].toString(),r=w.match(/\/[mgi]*/g)[1].replace("/",""),x=new RegExp("\\{"+z[2]+","+z[3]+"\\}","g");w=w.replace(/\/[mgi]*/g,"/").replace(x,"{"+m[2]+","+m[3]+"}").replace(/^\//,"").replace(/\/$/,"");a.util.dataType[t]=new RegExp(w,r);break}}}}if(a.util.toString.call(a.util.dataType[t])=="[object RegExp]"){v=a.util.dataType[t].test(n)}}}if(v){l=2;y=u.attr("sucmsg")||A.data("tipmsg").r||e.r;if(u.attr("recheck")){var p=A.find("input[name='"+u.attr("recheck")+"']:first");if(n!=p.val()){v=false;l=3;y=u.attr("errormsg")||a.util.getErrormsg.call(u,A,t,"recheck")}}}else{y=y||u.attr("errormsg")||a.util.getErrormsg.call(u,A,t);if(a.util.isEmpty.call(u,n)){y=u.attr("nullmsg")||a.util.getNullmsg.call(u,A)}}return{passed:v,type:l,info:y}},regcheck:function(n,s,m){var t=this,k=null,l=false,r=3;if(m.attr("ignore")==="ignore"&&a.util.isEmpty.call(m,s)){if(m.data("cked")){k=""}return{passed:true,type:4,info:k}}m.data("cked","cked");var u=a.util.parseDatatype(n);var q;for(var p=0;p=k.forms.length){return null}if(!(l in k.objects)){k.objects[l]=new a(d(k.forms[l]).get(),{},true)}return k.objects[l]},resetStatus:function(){var k=this;d(k.forms).each(function(){this.validform_status="normal"});return this},setStatus:function(k){var l=this;d(l.forms).each(function(){this.validform_status=k||"posting"});return this},getStatus:function(){var l=this;var k=d(l.forms)[0].validform_status;return k},ignore:function(k){var l=this;var k=k||"[datatype]";d(l.forms).find(k).each(function(){d(this).data("dataIgnore","dataIgnore").removeClass("Validform_error")});return this},unignore:function(k){var l=this;var k=k||"[datatype]";d(l.forms).find(k).each(function(){d(this).removeData("dataIgnore")});return this},addRule:function(n){var m=this;var n=n||[];for(var l=0;l0?k:0);n.css({left:l}).animate({top:k},{duration:m,queue:false})}function c(){if(d("#Validform_msg").length!==0){return false}j=d('
                                            ').appendTo("body");j.find("a.Validform_close").click(function(){j.hide();i=true;if(g){g.focus().addClass("Validform_error")}return false}).focus(function(){this.blur()});d(window).bind("scroll resize",function(){!i&&h(j,400)})}d.Showmsg=function(k){c();a.util.showmsg.call(f,k,1,{})};d.Hidemsg=function(){j.hide();i=true}})(jQuery,window); diff --git a/web/static/js/bootstrap.min.js b/web/static/js/bootstrap.min.js new file mode 100755 index 000000000..133aeecb9 --- /dev/null +++ b/web/static/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
                                            ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/web/static/js/clipboard.min.js b/web/static/js/clipboard.min.js new file mode 100755 index 000000000..7a4fde688 --- /dev/null +++ b/web/static/js/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.1 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e,n){var o,r,i;!function(a,c){r=[t,n(7)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var o=function(t){return t&&t.__esModule?t:{default:t}}(e),r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,o.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,o.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})},function(t,e,n){function o(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!c.string(e))throw new TypeError("Second argument must be a String");if(!c.fn(n))throw new TypeError("Third argument must be a Function");if(c.node(t))return r(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function r(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return u(document.body,t,e,n)}var c=n(6),u=n(5);t.exports=o},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function o(){r.off(t,o),e.apply(n,arguments)}var r=this;return o._=e,this.on(t,o,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;for(o;o0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,f.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return u("action",t)}},{key:"defaultTarget",value:function(t){var e=u("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return u("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(s.default);t.exports=p})},function(t,e){function n(t,e){for(;t&&t.nodeType!==o;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}var o=9;if("undefined"!=typeof Element&&!Element.prototype.matches){var r=Element.prototype;r.matches=r.matchesSelector||r.mozMatchesSelector||r.msMatchesSelector||r.oMatchesSelector||r.webkitMatchesSelector}t.exports=n},function(t,e,n){function o(t,e,n,o,r){var a=i.apply(this,arguments);return t.addEventListener(n,a,r),{destroy:function(){t.removeEventListener(n,a,r)}}}function r(t,e,n,r,i){return"function"==typeof t.addEventListener?o.apply(null,arguments):"function"==typeof n?o.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return o(t,e,n,r,i)}))}function i(t,e,n,o){return function(n){n.delegateTarget=a(n.target,e),n.delegateTarget&&o.call(t,n)}}var a=n(4);t.exports=r},function(t,e){e.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},e.nodeList=function(t){var n=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===n||"[object HTMLCollection]"===n)&&"length"in t&&(0===t.length||e.node(t[0]))},e.string=function(t){return"string"==typeof t||t instanceof String},e.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e){function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}t.exports=n}])}); \ No newline at end of file diff --git a/web/static/js/echarts.min.js b/web/static/js/echarts.min.js new file mode 100644 index 000000000..85daf7e8d --- /dev/null +++ b/web/static/js/echarts.min.js @@ -0,0 +1,45 @@ + +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).echarts={})}(this,(function(t){"use strict"; +/*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},e(t,n)};function n(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");function i(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(i.prototype=n.prototype,new i)}var i=function(){this.firefox=!1,this.ie=!1,this.edge=!1,this.newEdge=!1,this.weChat=!1},r=new function(){this.browser=new i,this.node=!1,this.wxa=!1,this.worker=!1,this.svgSupported=!1,this.touchEventsSupported=!1,this.pointerEventsSupported=!1,this.domSupported=!1,this.transformSupported=!1,this.transform3dSupported=!1,this.hasGlobalWindow="undefined"!=typeof window};"object"==typeof wx&&"function"==typeof wx.getSystemInfoSync?(r.wxa=!0,r.touchEventsSupported=!0):"undefined"==typeof document&&"undefined"!=typeof self?r.worker=!0:"undefined"==typeof navigator?(r.node=!0,r.svgSupported=!0):function(t,e){var n=e.browser,i=t.match(/Firefox\/([\d.]+)/),r=t.match(/MSIE\s([\d.]+)/)||t.match(/Trident\/.+?rv:(([\d.]+))/),o=t.match(/Edge?\/([\d.]+)/),a=/micromessenger/i.test(t);i&&(n.firefox=!0,n.version=i[1]);r&&(n.ie=!0,n.version=r[1]);o&&(n.edge=!0,n.version=o[1],n.newEdge=+o[1].split(".")[0]>18);a&&(n.weChat=!0);e.svgSupported="undefined"!=typeof SVGRect,e.touchEventsSupported="ontouchstart"in window&&!n.ie&&!n.edge,e.pointerEventsSupported="onpointerdown"in window&&(n.edge||n.ie&&+n.version>=11),e.domSupported="undefined"!=typeof document;var s=document.documentElement.style;e.transform3dSupported=(n.ie&&"transition"in s||n.edge||"WebKitCSSMatrix"in window&&"m11"in new WebKitCSSMatrix||"MozPerspective"in s)&&!("OTransition"in s),e.transformSupported=e.transform3dSupported||n.ie&&+n.version>=9}(navigator.userAgent,r);var o="sans-serif",a="12px sans-serif";var s,l,u=function(t){var e={};if("undefined"==typeof JSON)return e;for(var n=0;n=0)o=r*t.length;else for(var c=0;c>1)%2;a.style.cssText=["position: absolute","visibility: hidden","padding: 0","margin: 0","border-width: 0","user-select: none","width:0","height:0",i[s]+":0",r[l]+":0",i[1-s]+":auto",r[1-l]+":auto",""].join("!important;"),t.appendChild(a),n.push(a)}return n}(e,a),l=function(t,e,n){for(var i=n?"invTrans":"trans",r=e[i],o=e.srcCoords,a=[],s=[],l=!0,u=0;u<4;u++){var h=t[u].getBoundingClientRect(),c=2*u,p=h.left,d=h.top;a.push(p,d),l=l&&o&&p===o[c]&&d===o[c+1],s.push(t[u].offsetLeft,t[u].offsetTop)}return l&&r?r:(e.srcCoords=a,e[i]=n?$t(s,a):$t(a,s))}(s,a,o);if(l)return l(t,n,i),!0}return!1}function te(t){return"CANVAS"===t.nodeName.toUpperCase()}var ee=/([&<>"'])/g,ne={"&":"&","<":"<",">":">",'"':""","'":"'"};function ie(t){return null==t?"":(t+"").replace(ee,(function(t,e){return ne[e]}))}var re=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,oe=[],ae=r.browser.firefox&&+r.browser.version.split(".")[0]<39;function se(t,e,n,i){return n=n||{},i?le(t,e,n):ae&&null!=e.layerX&&e.layerX!==e.offsetX?(n.zrX=e.layerX,n.zrY=e.layerY):null!=e.offsetX?(n.zrX=e.offsetX,n.zrY=e.offsetY):le(t,e,n),n}function le(t,e,n){if(r.domSupported&&t.getBoundingClientRect){var i=e.clientX,o=e.clientY;if(te(t)){var a=t.getBoundingClientRect();return n.zrX=i-a.left,void(n.zrY=o-a.top)}if(Qt(oe,t,i,o))return n.zrX=oe[0],void(n.zrY=oe[1])}n.zrX=n.zrY=0}function ue(t){return t||window.event}function he(t,e,n){if(null!=(e=ue(e)).zrX)return e;var i=e.type;if(i&&i.indexOf("touch")>=0){var r="touchend"!==i?e.targetTouches[0]:e.changedTouches[0];r&&se(t,r,e,n)}else{se(t,e,e,n);var o=function(t){var e=t.wheelDelta;if(e)return e;var n=t.deltaX,i=t.deltaY;if(null==n||null==i)return e;return 3*(0!==i?Math.abs(i):Math.abs(n))*(i>0?-1:i<0?1:n>0?-1:1)}(e);e.zrDelta=o?o/120:-(e.detail||0)/3}var a=e.button;return null==e.which&&void 0!==a&&re.test(e.type)&&(e.which=1&a?1:2&a?3:4&a?2:0),e}function ce(t,e,n,i){t.addEventListener(e,n,i)}var pe=function(t){t.preventDefault(),t.stopPropagation(),t.cancelBubble=!0};function de(t){return 2===t.which||3===t.which}var fe=function(){function t(){this._track=[]}return t.prototype.recognize=function(t,e,n){return this._doTrack(t,e,n),this._recognize(t)},t.prototype.clear=function(){return this._track.length=0,this},t.prototype._doTrack=function(t,e,n){var i=t.touches;if(i){for(var r={points:[],touches:[],target:e,event:t},o=0,a=i.length;o1&&r&&r.length>1){var a=ge(r)/ge(o);!isFinite(a)&&(a=1),e.pinchScale=a;var s=[((i=r)[0][0]+i[1][0])/2,(i[0][1]+i[1][1])/2];return e.pinchX=s[0],e.pinchY=s[1],{type:"pinch",target:t[0].target,event:e}}}}};function ve(){return[1,0,0,1,0,0]}function me(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t}function xe(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4],t[5]=e[5],t}function _e(t,e,n){var i=e[0]*n[0]+e[2]*n[1],r=e[1]*n[0]+e[3]*n[1],o=e[0]*n[2]+e[2]*n[3],a=e[1]*n[2]+e[3]*n[3],s=e[0]*n[4]+e[2]*n[5]+e[4],l=e[1]*n[4]+e[3]*n[5]+e[5];return t[0]=i,t[1]=r,t[2]=o,t[3]=a,t[4]=s,t[5]=l,t}function be(t,e,n){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4]+n[0],t[5]=e[5]+n[1],t}function we(t,e,n){var i=e[0],r=e[2],o=e[4],a=e[1],s=e[3],l=e[5],u=Math.sin(n),h=Math.cos(n);return t[0]=i*h+a*u,t[1]=-i*u+a*h,t[2]=r*h+s*u,t[3]=-r*u+h*s,t[4]=h*o+u*l,t[5]=h*l-u*o,t}function Se(t,e,n){var i=n[0],r=n[1];return t[0]=e[0]*i,t[1]=e[1]*r,t[2]=e[2]*i,t[3]=e[3]*r,t[4]=e[4]*i,t[5]=e[5]*r,t}function Me(t,e){var n=e[0],i=e[2],r=e[4],o=e[1],a=e[3],s=e[5],l=n*a-o*i;return l?(l=1/l,t[0]=a*l,t[1]=-o*l,t[2]=-i*l,t[3]=n*l,t[4]=(i*s-a*r)*l,t[5]=(o*r-n*s)*l,t):null}function Ie(t){var e=[1,0,0,1,0,0];return xe(e,t),e}var Te=Object.freeze({__proto__:null,create:ve,identity:me,copy:xe,mul:_e,translate:be,rotate:we,scale:Se,invert:Me,clone:Ie}),Ce=function(){function t(t,e){this.x=t||0,this.y=e||0}return t.prototype.copy=function(t){return this.x=t.x,this.y=t.y,this},t.prototype.clone=function(){return new t(this.x,this.y)},t.prototype.set=function(t,e){return this.x=t,this.y=e,this},t.prototype.equal=function(t){return t.x===this.x&&t.y===this.y},t.prototype.add=function(t){return this.x+=t.x,this.y+=t.y,this},t.prototype.scale=function(t){this.x*=t,this.y*=t},t.prototype.scaleAndAdd=function(t,e){this.x+=t.x*e,this.y+=t.y*e},t.prototype.sub=function(t){return this.x-=t.x,this.y-=t.y,this},t.prototype.dot=function(t){return this.x*t.x+this.y*t.y},t.prototype.len=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},t.prototype.lenSquare=function(){return this.x*this.x+this.y*this.y},t.prototype.normalize=function(){var t=this.len();return this.x/=t,this.y/=t,this},t.prototype.distance=function(t){var e=this.x-t.x,n=this.y-t.y;return Math.sqrt(e*e+n*n)},t.prototype.distanceSquare=function(t){var e=this.x-t.x,n=this.y-t.y;return e*e+n*n},t.prototype.negate=function(){return this.x=-this.x,this.y=-this.y,this},t.prototype.transform=function(t){if(t){var e=this.x,n=this.y;return this.x=t[0]*e+t[2]*n+t[4],this.y=t[1]*e+t[3]*n+t[5],this}},t.prototype.toArray=function(t){return t[0]=this.x,t[1]=this.y,t},t.prototype.fromArray=function(t){this.x=t[0],this.y=t[1]},t.set=function(t,e,n){t.x=e,t.y=n},t.copy=function(t,e){t.x=e.x,t.y=e.y},t.len=function(t){return Math.sqrt(t.x*t.x+t.y*t.y)},t.lenSquare=function(t){return t.x*t.x+t.y*t.y},t.dot=function(t,e){return t.x*e.x+t.y*e.y},t.add=function(t,e,n){t.x=e.x+n.x,t.y=e.y+n.y},t.sub=function(t,e,n){t.x=e.x-n.x,t.y=e.y-n.y},t.scale=function(t,e,n){t.x=e.x*n,t.y=e.y*n},t.scaleAndAdd=function(t,e,n,i){t.x=e.x+n.x*i,t.y=e.y+n.y*i},t.lerp=function(t,e,n,i){var r=1-i;t.x=r*e.x+i*n.x,t.y=r*e.y+i*n.y},t}(),De=Math.min,Ae=Math.max,ke=new Ce,Le=new Ce,Pe=new Ce,Oe=new Ce,Re=new Ce,Ne=new Ce,Ee=function(){function t(t,e,n,i){n<0&&(t+=n,n=-n),i<0&&(e+=i,i=-i),this.x=t,this.y=e,this.width=n,this.height=i}return t.prototype.union=function(t){var e=De(t.x,this.x),n=De(t.y,this.y);isFinite(this.x)&&isFinite(this.width)?this.width=Ae(t.x+t.width,this.x+this.width)-e:this.width=t.width,isFinite(this.y)&&isFinite(this.height)?this.height=Ae(t.y+t.height,this.y+this.height)-n:this.height=t.height,this.x=e,this.y=n},t.prototype.applyTransform=function(e){t.applyTransform(this,this,e)},t.prototype.calculateTransform=function(t){var e=this,n=t.width/e.width,i=t.height/e.height,r=[1,0,0,1,0,0];return be(r,r,[-e.x,-e.y]),Se(r,r,[n,i]),be(r,r,[t.x,t.y]),r},t.prototype.intersect=function(e,n){if(!e)return!1;e instanceof t||(e=t.create(e));var i=this,r=i.x,o=i.x+i.width,a=i.y,s=i.y+i.height,l=e.x,u=e.x+e.width,h=e.y,c=e.y+e.height,p=!(of&&(f=x,gf&&(f=_,v=n.x&&t<=n.x+n.width&&e>=n.y&&e<=n.y+n.height},t.prototype.clone=function(){return new t(this.x,this.y,this.width,this.height)},t.prototype.copy=function(e){t.copy(this,e)},t.prototype.plain=function(){return{x:this.x,y:this.y,width:this.width,height:this.height}},t.prototype.isFinite=function(){return isFinite(this.x)&&isFinite(this.y)&&isFinite(this.width)&&isFinite(this.height)},t.prototype.isZero=function(){return 0===this.width||0===this.height},t.create=function(e){return new t(e.x,e.y,e.width,e.height)},t.copy=function(t,e){t.x=e.x,t.y=e.y,t.width=e.width,t.height=e.height},t.applyTransform=function(e,n,i){if(i){if(i[1]<1e-5&&i[1]>-1e-5&&i[2]<1e-5&&i[2]>-1e-5){var r=i[0],o=i[3],a=i[4],s=i[5];return e.x=n.x*r+a,e.y=n.y*o+s,e.width=n.width*r,e.height=n.height*o,e.width<0&&(e.x+=e.width,e.width=-e.width),void(e.height<0&&(e.y+=e.height,e.height=-e.height))}ke.x=Pe.x=n.x,ke.y=Oe.y=n.y,Le.x=Oe.x=n.x+n.width,Le.y=Pe.y=n.y+n.height,ke.transform(i),Oe.transform(i),Le.transform(i),Pe.transform(i),e.x=De(ke.x,Le.x,Pe.x,Oe.x),e.y=De(ke.y,Le.y,Pe.y,Oe.y);var l=Ae(ke.x,Le.x,Pe.x,Oe.x),u=Ae(ke.y,Le.y,Pe.y,Oe.y);e.width=l-e.x,e.height=u-e.y}else e!==n&&t.copy(e,n)},t}(),ze="silent";function Ve(){pe(this.event)}var Be=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.handler=null,e}return n(e,t),e.prototype.dispose=function(){},e.prototype.setCursor=function(){},e}(jt),Fe=function(t,e){this.x=t,this.y=e},Ge=["click","dblclick","mousewheel","mouseout","mouseup","mousedown","mousemove","contextmenu"],We=new Ee(0,0,0,0),He=function(t){function e(e,n,i,r,o){var a=t.call(this)||this;return a._hovered=new Fe(0,0),a.storage=e,a.painter=n,a.painterRoot=r,a._pointerSize=o,i=i||new Be,a.proxy=null,a.setHandlerProxy(i),a._draggingMgr=new Zt(a),a}return n(e,t),e.prototype.setHandlerProxy=function(t){this.proxy&&this.proxy.dispose(),t&&(E(Ge,(function(e){t.on&&t.on(e,this[e],this)}),this),t.handler=this),this.proxy=t},e.prototype.mousemove=function(t){var e=t.zrX,n=t.zrY,i=Xe(this,e,n),r=this._hovered,o=r.target;o&&!o.__zr&&(o=(r=this.findHover(r.x,r.y)).target);var a=this._hovered=i?new Fe(e,n):this.findHover(e,n),s=a.target,l=this.proxy;l.setCursor&&l.setCursor(s?s.cursor:"default"),o&&s!==o&&this.dispatchToElement(r,"mouseout",t),this.dispatchToElement(a,"mousemove",t),s&&s!==o&&this.dispatchToElement(a,"mouseover",t)},e.prototype.mouseout=function(t){var e=t.zrEventControl;"only_globalout"!==e&&this.dispatchToElement(this._hovered,"mouseout",t),"no_globalout"!==e&&this.trigger("globalout",{type:"globalout",event:t})},e.prototype.resize=function(){this._hovered=new Fe(0,0)},e.prototype.dispatch=function(t,e){var n=this[t];n&&n.call(this,e)},e.prototype.dispose=function(){this.proxy.dispose(),this.storage=null,this.proxy=null,this.painter=null},e.prototype.setCursorStyle=function(t){var e=this.proxy;e.setCursor&&e.setCursor(t)},e.prototype.dispatchToElement=function(t,e,n){var i=(t=t||{}).target;if(!i||!i.silent){for(var r="on"+e,o=function(t,e,n){return{type:t,event:n,target:e.target,topTarget:e.topTarget,cancelBubble:!1,offsetX:n.zrX,offsetY:n.zrY,gestureEvent:n.gestureEvent,pinchX:n.pinchX,pinchY:n.pinchY,pinchScale:n.pinchScale,wheelDelta:n.zrDelta,zrByTouch:n.zrByTouch,which:n.which,stop:Ve}}(e,t,n);i&&(i[r]&&(o.cancelBubble=!!i[r].call(i,o)),i.trigger(e,o),i=i.__hostTarget?i.__hostTarget:i.parent,!o.cancelBubble););o.cancelBubble||(this.trigger(e,o),this.painter&&this.painter.eachOtherLayer&&this.painter.eachOtherLayer((function(t){"function"==typeof t[r]&&t[r].call(t,o),t.trigger&&t.trigger(e,o)})))}},e.prototype.findHover=function(t,e,n){var i=this.storage.getDisplayList(),r=new Fe(t,e);if(Ue(i,r,t,e,n),this._pointerSize&&!r.target){for(var o=[],a=this._pointerSize,s=a/2,l=new Ee(t-s,e-s,a,a),u=i.length-1;u>=0;u--){var h=i[u];h===n||h.ignore||h.ignoreCoarsePointer||h.parent&&h.parent.ignoreCoarsePointer||(We.copy(h.getBoundingRect()),h.transform&&We.applyTransform(h.transform),We.intersect(l)&&o.push(h))}if(o.length)for(var c=Math.PI/12,p=2*Math.PI,d=0;d=0;o--){var a=t[o],s=void 0;if(a!==r&&!a.ignore&&(s=Ye(a,n,i))&&(!e.topTarget&&(e.topTarget=a),s!==ze)){e.target=a;break}}}function Xe(t,e,n){var i=t.painter;return e<0||e>i.getWidth()||n<0||n>i.getHeight()}E(["click","mousedown","mouseup","mousewheel","dblclick","contextmenu"],(function(t){He.prototype[t]=function(e){var n,i,r=e.zrX,o=e.zrY,a=Xe(this,r,o);if("mouseup"===t&&a||(i=(n=this.findHover(r,o)).target),"mousedown"===t)this._downEl=i,this._downPoint=[e.zrX,e.zrY],this._upEl=i;else if("mouseup"===t)this._upEl=i;else if("click"===t){if(this._downEl!==this._upEl||!this._downPoint||Vt(this._downPoint,[e.zrX,e.zrY])>4)return;this._downPoint=null}this.dispatchToElement(n,t,e)}}));function Ze(t,e,n,i){var r=e+1;if(r===n)return 1;if(i(t[r++],t[e])<0){for(;r=0;)r++;return r-e}function je(t,e,n,i,r){for(i===e&&i++;i>>1])<0?l=o:s=o+1;var u=i-s;switch(u){case 3:t[s+3]=t[s+2];case 2:t[s+2]=t[s+1];case 1:t[s+1]=t[s];break;default:for(;u>0;)t[s+u]=t[s+u-1],u--}t[s]=a}}function qe(t,e,n,i,r,o){var a=0,s=0,l=1;if(o(t,e[n+r])>0){for(s=i-r;l0;)a=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s),a+=r,l+=r}else{for(s=r+1;ls&&(l=s);var u=a;a=r-l,l=r-u}for(a++;a>>1);o(t,e[n+h])>0?a=h+1:l=h}return l}function Ke(t,e,n,i,r,o){var a=0,s=0,l=1;if(o(t,e[n+r])<0){for(s=r+1;ls&&(l=s);var u=a;a=r-l,l=r-u}else{for(s=i-r;l=0;)a=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s),a+=r,l+=r}for(a++;a>>1);o(t,e[n+h])<0?l=h:a=h+1}return l}function $e(t,e){var n,i,r=7,o=0;t.length;var a=[];function s(s){var l=n[s],u=i[s],h=n[s+1],c=i[s+1];i[s]=u+c,s===o-3&&(n[s+1]=n[s+2],i[s+1]=i[s+2]),o--;var p=Ke(t[h],t,l,u,0,e);l+=p,0!==(u-=p)&&0!==(c=qe(t[l+u-1],t,h,c,c-1,e))&&(u<=c?function(n,i,o,s){var l=0;for(l=0;l=7||d>=7);if(f)break;g<0&&(g=0),g+=2}if((r=g)<1&&(r=1),1===i){for(l=0;l=0;l--)t[d+l]=t[p+l];return void(t[c]=a[h])}var f=r;for(;;){var g=0,y=0,v=!1;do{if(e(a[h],t[u])<0){if(t[c--]=t[u--],g++,y=0,0==--i){v=!0;break}}else if(t[c--]=a[h--],y++,g=0,1==--s){v=!0;break}}while((g|y)=0;l--)t[d+l]=t[p+l];if(0===i){v=!0;break}}if(t[c--]=a[h--],1==--s){v=!0;break}if(0!==(y=s-qe(t[u],a,0,s,s-1,e))){for(s-=y,d=(c-=y)+1,p=(h-=y)+1,l=0;l=7||y>=7);if(v)break;f<0&&(f=0),f+=2}(r=f)<1&&(r=1);if(1===s){for(d=(c-=i)+1,p=(u-=i)+1,l=i-1;l>=0;l--)t[d+l]=t[p+l];t[c]=a[h]}else{if(0===s)throw new Error;for(p=c-(s-1),l=0;l1;){var t=o-2;if(t>=1&&i[t-1]<=i[t]+i[t+1]||t>=2&&i[t-2]<=i[t]+i[t-1])i[t-1]i[t+1])break;s(t)}},forceMergeRuns:function(){for(;o>1;){var t=o-2;t>0&&i[t-1]=32;)e|=1&t,t>>=1;return t+e}(r);do{if((o=Ze(t,n,i,e))s&&(l=s),je(t,n,n+l,n+o,e),o=l}a.pushRun(n,o),a.mergeRuns(),r-=o,n+=o}while(0!==r);a.forceMergeRuns()}}}var Qe=!1;function tn(){Qe||(Qe=!0,console.warn("z / z2 / zlevel of displayable is invalid, which may cause unexpected errors"))}function en(t,e){return t.zlevel===e.zlevel?t.z===e.z?t.z2-e.z2:t.z-e.z:t.zlevel-e.zlevel}var nn=function(){function t(){this._roots=[],this._displayList=[],this._displayListLen=0,this.displayableSortFunc=en}return t.prototype.traverse=function(t,e){for(var n=0;n0&&(u.__clipPaths=[]),isNaN(u.z)&&(tn(),u.z=0),isNaN(u.z2)&&(tn(),u.z2=0),isNaN(u.zlevel)&&(tn(),u.zlevel=0),this._displayList[this._displayListLen++]=u}var h=t.getDecalElement&&t.getDecalElement();h&&this._updateAndAddDisplayable(h,e,n);var c=t.getTextGuideLine();c&&this._updateAndAddDisplayable(c,e,n);var p=t.getTextContent();p&&this._updateAndAddDisplayable(p,e,n)}},t.prototype.addRoot=function(t){t.__zr&&t.__zr.storage===this||this._roots.push(t)},t.prototype.delRoot=function(t){if(t instanceof Array)for(var e=0,n=t.length;e=0&&this._roots.splice(i,1)}},t.prototype.delAllRoots=function(){this._roots=[],this._displayList=[],this._displayListLen=0},t.prototype.getRoots=function(){return this._roots},t.prototype.dispose=function(){this._displayList=null,this._roots=null},t}(),rn=r.hasGlobalWindow&&(window.requestAnimationFrame&&window.requestAnimationFrame.bind(window)||window.msRequestAnimationFrame&&window.msRequestAnimationFrame.bind(window)||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame)||function(t){return setTimeout(t,16)},on={linear:function(t){return t},quadraticIn:function(t){return t*t},quadraticOut:function(t){return t*(2-t)},quadraticInOut:function(t){return(t*=2)<1?.5*t*t:-.5*(--t*(t-2)-1)},cubicIn:function(t){return t*t*t},cubicOut:function(t){return--t*t*t+1},cubicInOut:function(t){return(t*=2)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},quarticIn:function(t){return t*t*t*t},quarticOut:function(t){return 1- --t*t*t*t},quarticInOut:function(t){return(t*=2)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},quinticIn:function(t){return t*t*t*t*t},quinticOut:function(t){return--t*t*t*t*t+1},quinticInOut:function(t){return(t*=2)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},sinusoidalIn:function(t){return 1-Math.cos(t*Math.PI/2)},sinusoidalOut:function(t){return Math.sin(t*Math.PI/2)},sinusoidalInOut:function(t){return.5*(1-Math.cos(Math.PI*t))},exponentialIn:function(t){return 0===t?0:Math.pow(1024,t-1)},exponentialOut:function(t){return 1===t?1:1-Math.pow(2,-10*t)},exponentialInOut:function(t){return 0===t?0:1===t?1:(t*=2)<1?.5*Math.pow(1024,t-1):.5*(2-Math.pow(2,-10*(t-1)))},circularIn:function(t){return 1-Math.sqrt(1-t*t)},circularOut:function(t){return Math.sqrt(1- --t*t)},circularInOut:function(t){return(t*=2)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},elasticIn:function(t){var e,n=.1;return 0===t?0:1===t?1:(!n||n<1?(n=1,e=.1):e=.4*Math.asin(1/n)/(2*Math.PI),-n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/.4))},elasticOut:function(t){var e,n=.1;return 0===t?0:1===t?1:(!n||n<1?(n=1,e=.1):e=.4*Math.asin(1/n)/(2*Math.PI),n*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/.4)+1)},elasticInOut:function(t){var e,n=.1,i=.4;return 0===t?0:1===t?1:(!n||n<1?(n=1,e=.1):e=i*Math.asin(1/n)/(2*Math.PI),(t*=2)<1?n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*-.5:n*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*.5+1)},backIn:function(t){var e=1.70158;return t*t*((e+1)*t-e)},backOut:function(t){var e=1.70158;return--t*t*((e+1)*t+e)+1},backInOut:function(t){var e=2.5949095;return(t*=2)<1?t*t*((e+1)*t-e)*.5:.5*((t-=2)*t*((e+1)*t+e)+2)},bounceIn:function(t){return 1-on.bounceOut(1-t)},bounceOut:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},bounceInOut:function(t){return t<.5?.5*on.bounceIn(2*t):.5*on.bounceOut(2*t-1)+.5}},an=Math.pow,sn=Math.sqrt,ln=1e-8,un=1e-4,hn=sn(3),cn=1/3,pn=Mt(),dn=Mt(),fn=Mt();function gn(t){return t>-1e-8&&tln||t<-1e-8}function vn(t,e,n,i,r){var o=1-r;return o*o*(o*t+3*r*e)+r*r*(r*i+3*o*n)}function mn(t,e,n,i,r){var o=1-r;return 3*(((e-t)*o+2*(n-e)*r)*o+(i-n)*r*r)}function xn(t,e,n,i,r,o){var a=i+3*(e-n)-t,s=3*(n-2*e+t),l=3*(e-t),u=t-r,h=s*s-3*a*l,c=s*l-9*a*u,p=l*l-3*s*u,d=0;if(gn(h)&&gn(c)){if(gn(s))o[0]=0;else(M=-l/s)>=0&&M<=1&&(o[d++]=M)}else{var f=c*c-4*h*p;if(gn(f)){var g=c/h,y=-g/2;(M=-s/a+g)>=0&&M<=1&&(o[d++]=M),y>=0&&y<=1&&(o[d++]=y)}else if(f>0){var v=sn(f),m=h*s+1.5*a*(-c+v),x=h*s+1.5*a*(-c-v);(M=(-s-((m=m<0?-an(-m,cn):an(m,cn))+(x=x<0?-an(-x,cn):an(x,cn))))/(3*a))>=0&&M<=1&&(o[d++]=M)}else{var _=(2*h*s-3*a*c)/(2*sn(h*h*h)),b=Math.acos(_)/3,w=sn(h),S=Math.cos(b),M=(-s-2*w*S)/(3*a),I=(y=(-s+w*(S+hn*Math.sin(b)))/(3*a),(-s+w*(S-hn*Math.sin(b)))/(3*a));M>=0&&M<=1&&(o[d++]=M),y>=0&&y<=1&&(o[d++]=y),I>=0&&I<=1&&(o[d++]=I)}}return d}function _n(t,e,n,i,r){var o=6*n-12*e+6*t,a=9*e+3*i-3*t-9*n,s=3*e-3*t,l=0;if(gn(a)){if(yn(o))(h=-s/o)>=0&&h<=1&&(r[l++]=h)}else{var u=o*o-4*a*s;if(gn(u))r[0]=-o/(2*a);else if(u>0){var h,c=sn(u),p=(-o-c)/(2*a);(h=(-o+c)/(2*a))>=0&&h<=1&&(r[l++]=h),p>=0&&p<=1&&(r[l++]=p)}}return l}function bn(t,e,n,i,r,o){var a=(e-t)*r+t,s=(n-e)*r+e,l=(i-n)*r+n,u=(s-a)*r+a,h=(l-s)*r+s,c=(h-u)*r+u;o[0]=t,o[1]=a,o[2]=u,o[3]=c,o[4]=c,o[5]=h,o[6]=l,o[7]=i}function wn(t,e,n,i,r,o,a,s,l,u,h){var c,p,d,f,g,y=.005,v=1/0;pn[0]=l,pn[1]=u;for(var m=0;m<1;m+=.05)dn[0]=vn(t,n,r,a,m),dn[1]=vn(e,i,o,s,m),(f=Ft(pn,dn))=0&&f=0&&y=1?1:xn(0,i,o,1,t,s)&&vn(0,r,a,1,s[0])}}}var Pn=function(){function t(t){this._inited=!1,this._startTime=0,this._pausedTime=0,this._paused=!1,this._life=t.life||1e3,this._delay=t.delay||0,this.loop=t.loop||!1,this.onframe=t.onframe||bt,this.ondestroy=t.ondestroy||bt,this.onrestart=t.onrestart||bt,t.easing&&this.setEasing(t.easing)}return t.prototype.step=function(t,e){if(this._inited||(this._startTime=t+this._delay,this._inited=!0),!this._paused){var n=this._life,i=t-this._startTime-this._pausedTime,r=i/n;r<0&&(r=0),r=Math.min(r,1);var o=this.easingFunc,a=o?o(r):r;if(this.onframe(a),1===r){if(!this.loop)return!0;var s=i%n;this._startTime=t-s,this._pausedTime=0,this.onrestart()}return!1}this._pausedTime+=e},t.prototype.pause=function(){this._paused=!0},t.prototype.resume=function(){this._paused=!1},t.prototype.setEasing=function(t){this.easing=t,this.easingFunc=U(t)?t:on[t]||Ln(t)},t}(),On=function(t){this.value=t},Rn=function(){function t(){this._len=0}return t.prototype.insert=function(t){var e=new On(t);return this.insertEntry(e),e},t.prototype.insertEntry=function(t){this.head?(this.tail.next=t,t.prev=this.tail,t.next=null,this.tail=t):this.head=this.tail=t,this._len++},t.prototype.remove=function(t){var e=t.prev,n=t.next;e?e.next=n:this.head=n,n?n.prev=e:this.tail=e,t.next=t.prev=null,this._len--},t.prototype.len=function(){return this._len},t.prototype.clear=function(){this.head=this.tail=null,this._len=0},t}(),Nn=function(){function t(t){this._list=new Rn,this._maxSize=10,this._map={},this._maxSize=t}return t.prototype.put=function(t,e){var n=this._list,i=this._map,r=null;if(null==i[t]){var o=n.len(),a=this._lastRemovedEntry;if(o>=this._maxSize&&o>0){var s=n.head;n.remove(s),delete i[s.key],r=s.value,this._lastRemovedEntry=s}a?a.value=e:a=new On(e),a.key=t,n.insertEntry(a),i[t]=a}return r},t.prototype.get=function(t){var e=this._map[t],n=this._list;if(null!=e)return e!==n.tail&&(n.remove(e),n.insertEntry(e)),e.value},t.prototype.clear=function(){this._list.clear(),this._map={}},t.prototype.len=function(){return this._list.len()},t}(),En={transparent:[0,0,0,0],aliceblue:[240,248,255,1],antiquewhite:[250,235,215,1],aqua:[0,255,255,1],aquamarine:[127,255,212,1],azure:[240,255,255,1],beige:[245,245,220,1],bisque:[255,228,196,1],black:[0,0,0,1],blanchedalmond:[255,235,205,1],blue:[0,0,255,1],blueviolet:[138,43,226,1],brown:[165,42,42,1],burlywood:[222,184,135,1],cadetblue:[95,158,160,1],chartreuse:[127,255,0,1],chocolate:[210,105,30,1],coral:[255,127,80,1],cornflowerblue:[100,149,237,1],cornsilk:[255,248,220,1],crimson:[220,20,60,1],cyan:[0,255,255,1],darkblue:[0,0,139,1],darkcyan:[0,139,139,1],darkgoldenrod:[184,134,11,1],darkgray:[169,169,169,1],darkgreen:[0,100,0,1],darkgrey:[169,169,169,1],darkkhaki:[189,183,107,1],darkmagenta:[139,0,139,1],darkolivegreen:[85,107,47,1],darkorange:[255,140,0,1],darkorchid:[153,50,204,1],darkred:[139,0,0,1],darksalmon:[233,150,122,1],darkseagreen:[143,188,143,1],darkslateblue:[72,61,139,1],darkslategray:[47,79,79,1],darkslategrey:[47,79,79,1],darkturquoise:[0,206,209,1],darkviolet:[148,0,211,1],deeppink:[255,20,147,1],deepskyblue:[0,191,255,1],dimgray:[105,105,105,1],dimgrey:[105,105,105,1],dodgerblue:[30,144,255,1],firebrick:[178,34,34,1],floralwhite:[255,250,240,1],forestgreen:[34,139,34,1],fuchsia:[255,0,255,1],gainsboro:[220,220,220,1],ghostwhite:[248,248,255,1],gold:[255,215,0,1],goldenrod:[218,165,32,1],gray:[128,128,128,1],green:[0,128,0,1],greenyellow:[173,255,47,1],grey:[128,128,128,1],honeydew:[240,255,240,1],hotpink:[255,105,180,1],indianred:[205,92,92,1],indigo:[75,0,130,1],ivory:[255,255,240,1],khaki:[240,230,140,1],lavender:[230,230,250,1],lavenderblush:[255,240,245,1],lawngreen:[124,252,0,1],lemonchiffon:[255,250,205,1],lightblue:[173,216,230,1],lightcoral:[240,128,128,1],lightcyan:[224,255,255,1],lightgoldenrodyellow:[250,250,210,1],lightgray:[211,211,211,1],lightgreen:[144,238,144,1],lightgrey:[211,211,211,1],lightpink:[255,182,193,1],lightsalmon:[255,160,122,1],lightseagreen:[32,178,170,1],lightskyblue:[135,206,250,1],lightslategray:[119,136,153,1],lightslategrey:[119,136,153,1],lightsteelblue:[176,196,222,1],lightyellow:[255,255,224,1],lime:[0,255,0,1],limegreen:[50,205,50,1],linen:[250,240,230,1],magenta:[255,0,255,1],maroon:[128,0,0,1],mediumaquamarine:[102,205,170,1],mediumblue:[0,0,205,1],mediumorchid:[186,85,211,1],mediumpurple:[147,112,219,1],mediumseagreen:[60,179,113,1],mediumslateblue:[123,104,238,1],mediumspringgreen:[0,250,154,1],mediumturquoise:[72,209,204,1],mediumvioletred:[199,21,133,1],midnightblue:[25,25,112,1],mintcream:[245,255,250,1],mistyrose:[255,228,225,1],moccasin:[255,228,181,1],navajowhite:[255,222,173,1],navy:[0,0,128,1],oldlace:[253,245,230,1],olive:[128,128,0,1],olivedrab:[107,142,35,1],orange:[255,165,0,1],orangered:[255,69,0,1],orchid:[218,112,214,1],palegoldenrod:[238,232,170,1],palegreen:[152,251,152,1],paleturquoise:[175,238,238,1],palevioletred:[219,112,147,1],papayawhip:[255,239,213,1],peachpuff:[255,218,185,1],peru:[205,133,63,1],pink:[255,192,203,1],plum:[221,160,221,1],powderblue:[176,224,230,1],purple:[128,0,128,1],red:[255,0,0,1],rosybrown:[188,143,143,1],royalblue:[65,105,225,1],saddlebrown:[139,69,19,1],salmon:[250,128,114,1],sandybrown:[244,164,96,1],seagreen:[46,139,87,1],seashell:[255,245,238,1],sienna:[160,82,45,1],silver:[192,192,192,1],skyblue:[135,206,235,1],slateblue:[106,90,205,1],slategray:[112,128,144,1],slategrey:[112,128,144,1],snow:[255,250,250,1],springgreen:[0,255,127,1],steelblue:[70,130,180,1],tan:[210,180,140,1],teal:[0,128,128,1],thistle:[216,191,216,1],tomato:[255,99,71,1],turquoise:[64,224,208,1],violet:[238,130,238,1],wheat:[245,222,179,1],white:[255,255,255,1],whitesmoke:[245,245,245,1],yellow:[255,255,0,1],yellowgreen:[154,205,50,1]};function zn(t){return(t=Math.round(t))<0?0:t>255?255:t}function Vn(t){return t<0?0:t>1?1:t}function Bn(t){var e=t;return e.length&&"%"===e.charAt(e.length-1)?zn(parseFloat(e)/100*255):zn(parseInt(e,10))}function Fn(t){var e=t;return e.length&&"%"===e.charAt(e.length-1)?Vn(parseFloat(e)/100):Vn(parseFloat(e))}function Gn(t,e,n){return n<0?n+=1:n>1&&(n-=1),6*n<1?t+(e-t)*n*6:2*n<1?e:3*n<2?t+(e-t)*(2/3-n)*6:t}function Wn(t,e,n){return t+(e-t)*n}function Hn(t,e,n,i,r){return t[0]=e,t[1]=n,t[2]=i,t[3]=r,t}function Yn(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t}var Un=new Nn(20),Xn=null;function Zn(t,e){Xn&&Yn(Xn,e),Xn=Un.put(t,Xn||e.slice())}function jn(t,e){if(t){e=e||[];var n=Un.get(t);if(n)return Yn(e,n);var i=(t+="").replace(/ /g,"").toLowerCase();if(i in En)return Yn(e,En[i]),Zn(t,e),e;var r,o=i.length;if("#"===i.charAt(0))return 4===o||5===o?(r=parseInt(i.slice(1,4),16))>=0&&r<=4095?(Hn(e,(3840&r)>>4|(3840&r)>>8,240&r|(240&r)>>4,15&r|(15&r)<<4,5===o?parseInt(i.slice(4),16)/15:1),Zn(t,e),e):void Hn(e,0,0,0,1):7===o||9===o?(r=parseInt(i.slice(1,7),16))>=0&&r<=16777215?(Hn(e,(16711680&r)>>16,(65280&r)>>8,255&r,9===o?parseInt(i.slice(7),16)/255:1),Zn(t,e),e):void Hn(e,0,0,0,1):void 0;var a=i.indexOf("("),s=i.indexOf(")");if(-1!==a&&s+1===o){var l=i.substr(0,a),u=i.substr(a+1,s-(a+1)).split(","),h=1;switch(l){case"rgba":if(4!==u.length)return 3===u.length?Hn(e,+u[0],+u[1],+u[2],1):Hn(e,0,0,0,1);h=Fn(u.pop());case"rgb":return u.length>=3?(Hn(e,Bn(u[0]),Bn(u[1]),Bn(u[2]),3===u.length?h:Fn(u[3])),Zn(t,e),e):void Hn(e,0,0,0,1);case"hsla":return 4!==u.length?void Hn(e,0,0,0,1):(u[3]=Fn(u[3]),qn(u,e),Zn(t,e),e);case"hsl":return 3!==u.length?void Hn(e,0,0,0,1):(qn(u,e),Zn(t,e),e);default:return}}Hn(e,0,0,0,1)}}function qn(t,e){var n=(parseFloat(t[0])%360+360)%360/360,i=Fn(t[1]),r=Fn(t[2]),o=r<=.5?r*(i+1):r+i-r*i,a=2*r-o;return Hn(e=e||[],zn(255*Gn(a,o,n+1/3)),zn(255*Gn(a,o,n)),zn(255*Gn(a,o,n-1/3)),1),4===t.length&&(e[3]=t[3]),e}function Kn(t,e){var n=jn(t);if(n){for(var i=0;i<3;i++)n[i]=e<0?n[i]*(1-e)|0:(255-n[i])*e+n[i]|0,n[i]>255?n[i]=255:n[i]<0&&(n[i]=0);return ii(n,4===n.length?"rgba":"rgb")}}function $n(t,e,n){if(e&&e.length&&t>=0&&t<=1){n=n||[];var i=t*(e.length-1),r=Math.floor(i),o=Math.ceil(i),a=e[r],s=e[o],l=i-r;return n[0]=zn(Wn(a[0],s[0],l)),n[1]=zn(Wn(a[1],s[1],l)),n[2]=zn(Wn(a[2],s[2],l)),n[3]=Vn(Wn(a[3],s[3],l)),n}}var Jn=$n;function Qn(t,e,n){if(e&&e.length&&t>=0&&t<=1){var i=t*(e.length-1),r=Math.floor(i),o=Math.ceil(i),a=jn(e[r]),s=jn(e[o]),l=i-r,u=ii([zn(Wn(a[0],s[0],l)),zn(Wn(a[1],s[1],l)),zn(Wn(a[2],s[2],l)),Vn(Wn(a[3],s[3],l))],"rgba");return n?{color:u,leftIndex:r,rightIndex:o,value:i}:u}}var ti=Qn;function ei(t,e,n,i){var r=jn(t);if(t)return r=function(t){if(t){var e,n,i=t[0]/255,r=t[1]/255,o=t[2]/255,a=Math.min(i,r,o),s=Math.max(i,r,o),l=s-a,u=(s+a)/2;if(0===l)e=0,n=0;else{n=u<.5?l/(s+a):l/(2-s-a);var h=((s-i)/6+l/2)/l,c=((s-r)/6+l/2)/l,p=((s-o)/6+l/2)/l;i===s?e=p-c:r===s?e=1/3+h-p:o===s&&(e=2/3+c-h),e<0&&(e+=1),e>1&&(e-=1)}var d=[360*e,n,u];return null!=t[3]&&d.push(t[3]),d}}(r),null!=e&&(r[0]=function(t){return(t=Math.round(t))<0?0:t>360?360:t}(e)),null!=n&&(r[1]=Fn(n)),null!=i&&(r[2]=Fn(i)),ii(qn(r),"rgba")}function ni(t,e){var n=jn(t);if(n&&null!=e)return n[3]=Vn(e),ii(n,"rgba")}function ii(t,e){if(t&&t.length){var n=t[0]+","+t[1]+","+t[2];return"rgba"!==e&&"hsva"!==e&&"hsla"!==e||(n+=","+t[3]),e+"("+n+")"}}function ri(t,e){var n=jn(t);return n?(.299*n[0]+.587*n[1]+.114*n[2])*n[3]/255+(1-n[3])*e:0}var oi=Object.freeze({__proto__:null,parse:jn,lift:Kn,toHex:function(t){var e=jn(t);if(e)return((1<<24)+(e[0]<<16)+(e[1]<<8)+ +e[2]).toString(16).slice(1)},fastLerp:$n,fastMapToColor:Jn,lerp:Qn,mapToColor:ti,modifyHSL:ei,modifyAlpha:ni,stringify:ii,lum:ri,random:function(){return ii([Math.round(255*Math.random()),Math.round(255*Math.random()),Math.round(255*Math.random())],"rgb")}}),ai=Math.round;function si(t){var e;if(t&&"transparent"!==t){if("string"==typeof t&&t.indexOf("rgba")>-1){var n=jn(t);n&&(t="rgb("+n[0]+","+n[1]+","+n[2]+")",e=n[3])}}else t="none";return{color:t,opacity:null==e?1:e}}var li=1e-4;function ui(t){return t-1e-4}function hi(t){return ai(1e3*t)/1e3}function ci(t){return ai(1e4*t)/1e4}var pi={left:"start",right:"end",center:"middle",middle:"middle"};function di(t){return t&&!!t.image}function fi(t){return di(t)||function(t){return t&&!!t.svgElement}(t)}function gi(t){return"linear"===t.type}function yi(t){return"radial"===t.type}function vi(t){return t&&("linear"===t.type||"radial"===t.type)}function mi(t){return"url(#"+t+")"}function xi(t){var e=t.getGlobalScale(),n=Math.max(e[0],e[1]);return Math.max(Math.ceil(Math.log(n)/Math.log(10)),1)}function _i(t){var e=t.x||0,n=t.y||0,i=(t.rotation||0)*wt,r=rt(t.scaleX,1),o=rt(t.scaleY,1),a=t.skewX||0,s=t.skewY||0,l=[];return(e||n)&&l.push("translate("+e+"px,"+n+"px)"),i&&l.push("rotate("+i+")"),1===r&&1===o||l.push("scale("+r+","+o+")"),(a||s)&&l.push("skew("+ai(a*wt)+"deg, "+ai(s*wt)+"deg)"),l.join(" ")}var bi=r.hasGlobalWindow&&U(window.btoa)?function(t){return window.btoa(unescape(encodeURIComponent(t)))}:"undefined"!=typeof Buffer?function(t){return Buffer.from(t).toString("base64")}:function(t){return null},wi=Array.prototype.slice;function Si(t,e,n){return(e-t)*n+t}function Mi(t,e,n,i){for(var r=e.length,o=0;oi?e:t,o=Math.min(n,i),a=r[o-1]||{color:[0,0,0,0],offset:0},s=o;sa)i.length=a;else for(var s=o;s=1},t.prototype.getAdditiveTrack=function(){return this._additiveTrack},t.prototype.addKeyframe=function(t,e,n){this._needsSort=!0;var i=this.keyframes,r=i.length,o=!1,a=6,s=e;if(N(e)){var l=function(t){return N(t&&t[0])?2:1}(e);a=l,(1===l&&!j(e[0])||2===l&&!j(e[0][0]))&&(o=!0)}else if(j(e)&&!nt(e))a=0;else if(X(e))if(isNaN(+e)){var u=jn(e);u&&(s=u,a=3)}else a=0;else if(Q(e)){var h=A({},s);h.colorStops=z(e.colorStops,(function(t){return{offset:t.offset,color:jn(t.color)}})),gi(e)?a=4:yi(e)&&(a=5),s=h}0===r?this.valType=a:a===this.valType&&6!==a||(o=!0),this.discrete=this.discrete||o;var c={time:t,value:s,rawValue:e,percent:0};return n&&(c.easing=n,c.easingFunc=U(n)?n:on[n]||Ln(n)),i.push(c),c},t.prototype.prepare=function(t,e){var n=this.keyframes;this._needsSort&&n.sort((function(t,e){return t.time-e.time}));for(var i=this.valType,r=n.length,o=n[r-1],a=this.discrete,s=Pi(i),l=Li(i),u=0;u=0&&!(l[n].percent<=e);n--);n=d(n,u-2)}else{for(n=p;ne);n++);n=d(n-1,u-2)}r=l[n+1],i=l[n]}if(i&&r){this._lastFr=n,this._lastFrP=e;var f=r.percent-i.percent,g=0===f?1:d((e-i.percent)/f,1);r.easingFunc&&(g=r.easingFunc(g));var y=o?this._additiveValue:c?Oi:t[h];if(!Pi(s)&&!c||y||(y=this._additiveValue=[]),this.discrete)t[h]=g<1?i.rawValue:r.rawValue;else if(Pi(s))1===s?Mi(y,i[a],r[a],g):function(t,e,n,i){for(var r=e.length,o=r&&e[0].length,a=0;a0&&s.addKeyframe(0,Ai(l),i),this._trackKeys.push(a)}s.addKeyframe(t,Ai(e[a]),i)}return this._maxTime=Math.max(this._maxTime,t),this},t.prototype.pause=function(){this._clip.pause(),this._paused=!0},t.prototype.resume=function(){this._clip.resume(),this._paused=!1},t.prototype.isPaused=function(){return!!this._paused},t.prototype.duration=function(t){return this._maxTime=t,this._force=!0,this},t.prototype._doneCallback=function(){this._setTracksFinished(),this._clip=null;var t=this._doneCbs;if(t)for(var e=t.length,n=0;n0)){this._started=1;for(var e=this,n=[],i=this._maxTime||0,r=0;r1){var a=o.pop();r.addKeyframe(a.time,t[i]),r.prepare(this._maxTime,r.getAdditiveTrack())}}}},t}();function Ei(){return(new Date).getTime()}var zi,Vi,Bi=function(t){function e(e){var n=t.call(this)||this;return n._running=!1,n._time=0,n._pausedTime=0,n._pauseStart=0,n._paused=!1,e=e||{},n.stage=e.stage||{},n}return n(e,t),e.prototype.addClip=function(t){t.animation&&this.removeClip(t),this._head?(this._tail.next=t,t.prev=this._tail,t.next=null,this._tail=t):this._head=this._tail=t,t.animation=this},e.prototype.addAnimator=function(t){t.animation=this;var e=t.getClip();e&&this.addClip(e)},e.prototype.removeClip=function(t){if(t.animation){var e=t.prev,n=t.next;e?e.next=n:this._head=n,n?n.prev=e:this._tail=e,t.next=t.prev=t.animation=null}},e.prototype.removeAnimator=function(t){var e=t.getClip();e&&this.removeClip(e),t.animation=null},e.prototype.update=function(t){for(var e=Ei()-this._pausedTime,n=e-this._time,i=this._head;i;){var r=i.next;i.step(e,n)?(i.ondestroy(),this.removeClip(i),i=r):i=r}this._time=e,t||(this.trigger("frame",n),this.stage.update&&this.stage.update())},e.prototype._startLoop=function(){var t=this;this._running=!0,rn((function e(){t._running&&(rn(e),!t._paused&&t.update())}))},e.prototype.start=function(){this._running||(this._time=Ei(),this._pausedTime=0,this._startLoop())},e.prototype.stop=function(){this._running=!1},e.prototype.pause=function(){this._paused||(this._pauseStart=Ei(),this._paused=!0)},e.prototype.resume=function(){this._paused&&(this._pausedTime+=Ei()-this._pauseStart,this._paused=!1)},e.prototype.clear=function(){for(var t=this._head;t;){var e=t.next;t.prev=t.next=t.animation=null,t=e}this._head=this._tail=null},e.prototype.isFinished=function(){return null==this._head},e.prototype.animate=function(t,e){e=e||{},this.start();var n=new Ni(t,e.loop);return this.addAnimator(n),n},e}(jt),Fi=r.domSupported,Gi=(Vi={pointerdown:1,pointerup:1,pointermove:1,pointerout:1},{mouse:zi=["click","dblclick","mousewheel","wheel","mouseout","mouseup","mousedown","mousemove","contextmenu"],touch:["touchstart","touchend","touchmove"],pointer:z(zi,(function(t){var e=t.replace("mouse","pointer");return Vi.hasOwnProperty(e)?e:t}))}),Wi=["mousemove","mouseup"],Hi=["pointermove","pointerup"],Yi=!1;function Ui(t){var e=t.pointerType;return"pen"===e||"touch"===e}function Xi(t){t&&(t.zrByTouch=!0)}function Zi(t,e){for(var n=e,i=!1;n&&9!==n.nodeType&&!(i=n.domBelongToZr||n!==e&&n===t.painterRoot);)n=n.parentNode;return i}var ji=function(t,e){this.stopPropagation=bt,this.stopImmediatePropagation=bt,this.preventDefault=bt,this.type=e.type,this.target=this.currentTarget=t.dom,this.pointerType=e.pointerType,this.clientX=e.clientX,this.clientY=e.clientY},qi={mousedown:function(t){t=he(this.dom,t),this.__mayPointerCapture=[t.zrX,t.zrY],this.trigger("mousedown",t)},mousemove:function(t){t=he(this.dom,t);var e=this.__mayPointerCapture;!e||t.zrX===e[0]&&t.zrY===e[1]||this.__togglePointerCapture(!0),this.trigger("mousemove",t)},mouseup:function(t){t=he(this.dom,t),this.__togglePointerCapture(!1),this.trigger("mouseup",t)},mouseout:function(t){Zi(this,(t=he(this.dom,t)).toElement||t.relatedTarget)||(this.__pointerCapturing&&(t.zrEventControl="no_globalout"),this.trigger("mouseout",t))},wheel:function(t){Yi=!0,t=he(this.dom,t),this.trigger("mousewheel",t)},mousewheel:function(t){Yi||(t=he(this.dom,t),this.trigger("mousewheel",t))},touchstart:function(t){Xi(t=he(this.dom,t)),this.__lastTouchMoment=new Date,this.handler.processGesture(t,"start"),qi.mousemove.call(this,t),qi.mousedown.call(this,t)},touchmove:function(t){Xi(t=he(this.dom,t)),this.handler.processGesture(t,"change"),qi.mousemove.call(this,t)},touchend:function(t){Xi(t=he(this.dom,t)),this.handler.processGesture(t,"end"),qi.mouseup.call(this,t),+new Date-+this.__lastTouchMoment<300&&qi.click.call(this,t)},pointerdown:function(t){qi.mousedown.call(this,t)},pointermove:function(t){Ui(t)||qi.mousemove.call(this,t)},pointerup:function(t){qi.mouseup.call(this,t)},pointerout:function(t){Ui(t)||qi.mouseout.call(this,t)}};E(["click","dblclick","contextmenu"],(function(t){qi[t]=function(e){e=he(this.dom,e),this.trigger(t,e)}}));var Ki={pointermove:function(t){Ui(t)||Ki.mousemove.call(this,t)},pointerup:function(t){Ki.mouseup.call(this,t)},mousemove:function(t){this.trigger("mousemove",t)},mouseup:function(t){var e=this.__pointerCapturing;this.__togglePointerCapture(!1),this.trigger("mouseup",t),e&&(t.zrEventControl="only_globalout",this.trigger("mouseout",t))}};function $i(t,e){var n=e.domHandlers;r.pointerEventsSupported?E(Gi.pointer,(function(i){Qi(e,i,(function(e){n[i].call(t,e)}))})):(r.touchEventsSupported&&E(Gi.touch,(function(i){Qi(e,i,(function(r){n[i].call(t,r),function(t){t.touching=!0,null!=t.touchTimer&&(clearTimeout(t.touchTimer),t.touchTimer=null),t.touchTimer=setTimeout((function(){t.touching=!1,t.touchTimer=null}),700)}(e)}))})),E(Gi.mouse,(function(i){Qi(e,i,(function(r){r=ue(r),e.touching||n[i].call(t,r)}))})))}function Ji(t,e){function n(n){Qi(e,n,(function(i){i=ue(i),Zi(t,i.target)||(i=function(t,e){return he(t.dom,new ji(t,e),!0)}(t,i),e.domHandlers[n].call(t,i))}),{capture:!0})}r.pointerEventsSupported?E(Hi,n):r.touchEventsSupported||E(Wi,n)}function Qi(t,e,n,i){t.mounted[e]=n,t.listenerOpts[e]=i,ce(t.domTarget,e,n,i)}function tr(t){var e,n,i,r,o=t.mounted;for(var a in o)o.hasOwnProperty(a)&&(e=t.domTarget,n=a,i=o[a],r=t.listenerOpts[a],e.removeEventListener(n,i,r));t.mounted={}}var er=function(t,e){this.mounted={},this.listenerOpts={},this.touching=!1,this.domTarget=t,this.domHandlers=e},nr=function(t){function e(e,n){var i=t.call(this)||this;return i.__pointerCapturing=!1,i.dom=e,i.painterRoot=n,i._localHandlerScope=new er(e,qi),Fi&&(i._globalHandlerScope=new er(document,Ki)),$i(i,i._localHandlerScope),i}return n(e,t),e.prototype.dispose=function(){tr(this._localHandlerScope),Fi&&tr(this._globalHandlerScope)},e.prototype.setCursor=function(t){this.dom.style&&(this.dom.style.cursor=t||"default")},e.prototype.__togglePointerCapture=function(t){if(this.__mayPointerCapture=null,Fi&&+this.__pointerCapturing^+t){this.__pointerCapturing=t;var e=this._globalHandlerScope;t?Ji(this,e):tr(e)}},e}(jt),ir=1;r.hasGlobalWindow&&(ir=Math.max(window.devicePixelRatio||window.screen&&window.screen.deviceXDPI/window.screen.logicalXDPI||1,1));var rr=ir,or="#333",ar="#ccc",sr=me,lr=5e-5;function ur(t){return t>lr||t<-5e-5}var hr=[],cr=[],pr=[1,0,0,1,0,0],dr=Math.abs,fr=function(){function t(){}return t.prototype.getLocalTransform=function(e){return t.getLocalTransform(this,e)},t.prototype.setPosition=function(t){this.x=t[0],this.y=t[1]},t.prototype.setScale=function(t){this.scaleX=t[0],this.scaleY=t[1]},t.prototype.setSkew=function(t){this.skewX=t[0],this.skewY=t[1]},t.prototype.setOrigin=function(t){this.originX=t[0],this.originY=t[1]},t.prototype.needLocalTransform=function(){return ur(this.rotation)||ur(this.x)||ur(this.y)||ur(this.scaleX-1)||ur(this.scaleY-1)||ur(this.skewX)||ur(this.skewY)},t.prototype.updateTransform=function(){var t=this.parent&&this.parent.transform,e=this.needLocalTransform(),n=this.transform;e||t?(n=n||[1,0,0,1,0,0],e?this.getLocalTransform(n):sr(n),t&&(e?_e(n,t,n):xe(n,t)),this.transform=n,this._resolveGlobalScaleRatio(n)):n&&sr(n)},t.prototype._resolveGlobalScaleRatio=function(t){var e=this.globalScaleRatio;if(null!=e&&1!==e){this.getGlobalScale(hr);var n=hr[0]<0?-1:1,i=hr[1]<0?-1:1,r=((hr[0]-n)*e+n)/hr[0]||0,o=((hr[1]-i)*e+i)/hr[1]||0;t[0]*=r,t[1]*=r,t[2]*=o,t[3]*=o}this.invTransform=this.invTransform||[1,0,0,1,0,0],Me(this.invTransform,t)},t.prototype.getComputedTransform=function(){for(var t=this,e=[];t;)e.push(t),t=t.parent;for(;t=e.pop();)t.updateTransform();return this.transform},t.prototype.setLocalTransform=function(t){if(t){var e=t[0]*t[0]+t[1]*t[1],n=t[2]*t[2]+t[3]*t[3],i=Math.atan2(t[1],t[0]),r=Math.PI/2+i-Math.atan2(t[3],t[2]);n=Math.sqrt(n)*Math.cos(r),e=Math.sqrt(e),this.skewX=r,this.skewY=0,this.rotation=-i,this.x=+t[4],this.y=+t[5],this.scaleX=e,this.scaleY=n,this.originX=0,this.originY=0}},t.prototype.decomposeTransform=function(){if(this.transform){var t=this.parent,e=this.transform;t&&t.transform&&(_e(cr,t.invTransform,e),e=cr);var n=this.originX,i=this.originY;(n||i)&&(pr[4]=n,pr[5]=i,_e(cr,e,pr),cr[4]-=n,cr[5]-=i,e=cr),this.setLocalTransform(e)}},t.prototype.getGlobalScale=function(t){var e=this.transform;return t=t||[],e?(t[0]=Math.sqrt(e[0]*e[0]+e[1]*e[1]),t[1]=Math.sqrt(e[2]*e[2]+e[3]*e[3]),e[0]<0&&(t[0]=-t[0]),e[3]<0&&(t[1]=-t[1]),t):(t[0]=1,t[1]=1,t)},t.prototype.transformCoordToLocal=function(t,e){var n=[t,e],i=this.invTransform;return i&&Wt(n,n,i),n},t.prototype.transformCoordToGlobal=function(t,e){var n=[t,e],i=this.transform;return i&&Wt(n,n,i),n},t.prototype.getLineScale=function(){var t=this.transform;return t&&dr(t[0]-1)>1e-10&&dr(t[3]-1)>1e-10?Math.sqrt(dr(t[0]*t[3]-t[2]*t[1])):1},t.prototype.copyTransform=function(t){yr(this,t)},t.getLocalTransform=function(t,e){e=e||[];var n=t.originX||0,i=t.originY||0,r=t.scaleX,o=t.scaleY,a=t.anchorX,s=t.anchorY,l=t.rotation||0,u=t.x,h=t.y,c=t.skewX?Math.tan(t.skewX):0,p=t.skewY?Math.tan(-t.skewY):0;if(n||i||a||s){var d=n+a,f=i+s;e[4]=-d*r-c*f*o,e[5]=-f*o-p*d*r}else e[4]=e[5]=0;return e[0]=r,e[3]=o,e[1]=p*r,e[2]=c*o,l&&we(e,e,l),e[4]+=n+u,e[5]+=i+h,e},t.initDefaultProps=function(){var e=t.prototype;e.scaleX=e.scaleY=e.globalScaleRatio=1,e.x=e.y=e.originX=e.originY=e.skewX=e.skewY=e.rotation=e.anchorX=e.anchorY=0}(),t}(),gr=["x","y","originX","originY","anchorX","anchorY","rotation","scaleX","scaleY","skewX","skewY"];function yr(t,e){for(var n=0;n=0?parseFloat(t)/100*e:parseFloat(t):t}function Ir(t,e,n){var i=e.position||"inside",r=null!=e.distance?e.distance:5,o=n.height,a=n.width,s=o/2,l=n.x,u=n.y,h="left",c="top";if(i instanceof Array)l+=Mr(i[0],n.width),u+=Mr(i[1],n.height),h=null,c=null;else switch(i){case"left":l-=r,u+=s,h="right",c="middle";break;case"right":l+=r+a,u+=s,c="middle";break;case"top":l+=a/2,u-=r,h="center",c="bottom";break;case"bottom":l+=a/2,u+=o+r,h="center";break;case"inside":l+=a/2,u+=s,h="center",c="middle";break;case"insideLeft":l+=r,u+=s,c="middle";break;case"insideRight":l+=a-r,u+=s,h="right",c="middle";break;case"insideTop":l+=a/2,u+=r,h="center";break;case"insideBottom":l+=a/2,u+=o-r,h="center",c="bottom";break;case"insideTopLeft":l+=r,u+=r;break;case"insideTopRight":l+=a-r,u+=r,h="right";break;case"insideBottomLeft":l+=r,u+=o-r,c="bottom";break;case"insideBottomRight":l+=a-r,u+=o-r,h="right",c="bottom"}return(t=t||{}).x=l,t.y=u,t.align=h,t.verticalAlign=c,t}var Tr="__zr_normal__",Cr=gr.concat(["ignore"]),Dr=V(gr,(function(t,e){return t[e]=!0,t}),{ignore:!1}),Ar={},kr=new Ee(0,0,0,0),Lr=function(){function t(t){this.id=M(),this.animators=[],this.currentStates=[],this.states={},this._init(t)}return t.prototype._init=function(t){this.attr(t)},t.prototype.drift=function(t,e,n){switch(this.draggable){case"horizontal":e=0;break;case"vertical":t=0}var i=this.transform;i||(i=this.transform=[1,0,0,1,0,0]),i[4]+=t,i[5]+=e,this.decomposeTransform(),this.markRedraw()},t.prototype.beforeUpdate=function(){},t.prototype.afterUpdate=function(){},t.prototype.update=function(){this.updateTransform(),this.__dirty&&this.updateInnerText()},t.prototype.updateInnerText=function(t){var e=this._textContent;if(e&&(!e.ignore||t)){this.textConfig||(this.textConfig={});var n=this.textConfig,i=n.local,r=e.innerTransformable,o=void 0,a=void 0,s=!1;r.parent=i?this:null;var l=!1;if(r.copyTransform(e),null!=n.position){var u=kr;n.layoutRect?u.copy(n.layoutRect):u.copy(this.getBoundingRect()),i||u.applyTransform(this.transform),this.calculateTextPosition?this.calculateTextPosition(Ar,n,u):Ir(Ar,n,u),r.x=Ar.x,r.y=Ar.y,o=Ar.align,a=Ar.verticalAlign;var h=n.origin;if(h&&null!=n.rotation){var c=void 0,p=void 0;"center"===h?(c=.5*u.width,p=.5*u.height):(c=Mr(h[0],u.width),p=Mr(h[1],u.height)),l=!0,r.originX=-r.x+c+(i?0:u.x),r.originY=-r.y+p+(i?0:u.y)}}null!=n.rotation&&(r.rotation=n.rotation);var d=n.offset;d&&(r.x+=d[0],r.y+=d[1],l||(r.originX=-d[0],r.originY=-d[1]));var f=null==n.inside?"string"==typeof n.position&&n.position.indexOf("inside")>=0:n.inside,g=this._innerTextDefaultStyle||(this._innerTextDefaultStyle={}),y=void 0,v=void 0,m=void 0;f&&this.canBeInsideText()?(y=n.insideFill,v=n.insideStroke,null!=y&&"auto"!==y||(y=this.getInsideTextFill()),null!=v&&"auto"!==v||(v=this.getInsideTextStroke(y),m=!0)):(y=n.outsideFill,v=n.outsideStroke,null!=y&&"auto"!==y||(y=this.getOutsideFill()),null!=v&&"auto"!==v||(v=this.getOutsideStroke(y),m=!0)),(y=y||"#000")===g.fill&&v===g.stroke&&m===g.autoStroke&&o===g.align&&a===g.verticalAlign||(s=!0,g.fill=y,g.stroke=v,g.autoStroke=m,g.align=o,g.verticalAlign=a,e.setDefaultTextStyle(g)),e.__dirty|=1,s&&e.dirtyStyle(!0)}},t.prototype.canBeInsideText=function(){return!0},t.prototype.getInsideTextFill=function(){return"#fff"},t.prototype.getInsideTextStroke=function(t){return"#000"},t.prototype.getOutsideFill=function(){return this.__zr&&this.__zr.isDarkMode()?ar:or},t.prototype.getOutsideStroke=function(t){var e=this.__zr&&this.__zr.getBackgroundColor(),n="string"==typeof e&&jn(e);n||(n=[255,255,255,1]);for(var i=n[3],r=this.__zr.isDarkMode(),o=0;o<3;o++)n[o]=n[o]*i+(r?0:255)*(1-i);return n[3]=1,ii(n,"rgba")},t.prototype.traverse=function(t,e){},t.prototype.attrKV=function(t,e){"textConfig"===t?this.setTextConfig(e):"textContent"===t?this.setTextContent(e):"clipPath"===t?this.setClipPath(e):"extra"===t?(this.extra=this.extra||{},A(this.extra,e)):this[t]=e},t.prototype.hide=function(){this.ignore=!0,this.markRedraw()},t.prototype.show=function(){this.ignore=!1,this.markRedraw()},t.prototype.attr=function(t,e){if("string"==typeof t)this.attrKV(t,e);else if(q(t))for(var n=G(t),i=0;i0},t.prototype.getState=function(t){return this.states[t]},t.prototype.ensureState=function(t){var e=this.states;return e[t]||(e[t]={}),e[t]},t.prototype.clearStates=function(t){this.useState(Tr,!1,t)},t.prototype.useState=function(t,e,n,i){var r=t===Tr;if(this.hasState()||!r){var o=this.currentStates,a=this.stateTransition;if(!(P(o,t)>=0)||!e&&1!==o.length){var s;if(this.stateProxy&&!r&&(s=this.stateProxy(t)),s||(s=this.states&&this.states[t]),s||r){r||this.saveCurrentToNormalState(s);var l=!!(s&&s.hoverLayer||i);l&&this._toggleHoverLayerFlag(!0),this._applyStateObj(t,s,this._normalState,e,!n&&!this.__inHover&&a&&a.duration>0,a);var u=this._textContent,h=this._textGuide;return u&&u.useState(t,e,n,l),h&&h.useState(t,e,n,l),r?(this.currentStates=[],this._normalState={}):e?this.currentStates.push(t):this.currentStates=[t],this._updateAnimationTargets(),this.markRedraw(),!l&&this.__inHover&&(this._toggleHoverLayerFlag(!1),this.__dirty&=-2),s}I("State "+t+" not exists.")}}},t.prototype.useStates=function(t,e,n){if(t.length){var i=[],r=this.currentStates,o=t.length,a=o===r.length;if(a)for(var s=0;s0,d);var f=this._textContent,g=this._textGuide;f&&f.useStates(t,e,c),g&&g.useStates(t,e,c),this._updateAnimationTargets(),this.currentStates=t.slice(),this.markRedraw(),!c&&this.__inHover&&(this._toggleHoverLayerFlag(!1),this.__dirty&=-2)}else this.clearStates()},t.prototype._updateAnimationTargets=function(){for(var t=0;t=0){var n=this.currentStates.slice();n.splice(e,1),this.useStates(n)}},t.prototype.replaceState=function(t,e,n){var i=this.currentStates.slice(),r=P(i,t),o=P(i,e)>=0;r>=0?o?i.splice(r,1):i[r]=e:n&&!o&&i.push(e),this.useStates(i)},t.prototype.toggleState=function(t,e){e?this.useState(t,!0):this.removeState(t)},t.prototype._mergeStates=function(t){for(var e,n={},i=0;i=0&&e.splice(n,1)})),this.animators.push(t),n&&n.animation.addAnimator(t),n&&n.wakeUp()},t.prototype.updateDuringAnimation=function(t){this.markRedraw()},t.prototype.stopAnimation=function(t,e){for(var n=this.animators,i=n.length,r=[],o=0;o0&&n.during&&o[0].during((function(t,e){n.during(e)}));for(var p=0;p0||r.force&&!a.length){var w,S=void 0,M=void 0,I=void 0;if(s){M={},p&&(S={});for(_=0;_=0&&(n.splice(i,0,t),this._doAdd(t))}return this},e.prototype.replace=function(t,e){var n=P(this._children,t);return n>=0&&this.replaceAt(e,n),this},e.prototype.replaceAt=function(t,e){var n=this._children,i=n[e];if(t&&t!==this&&t.parent!==this&&t!==i){n[e]=t,i.parent=null;var r=this.__zr;r&&i.removeSelfFromZr(r),this._doAdd(t)}return this},e.prototype._doAdd=function(t){t.parent&&t.parent.remove(t),t.parent=this;var e=this.__zr;e&&e!==t.__zr&&t.addSelfToZr(e),e&&e.refresh()},e.prototype.remove=function(t){var e=this.__zr,n=this._children,i=P(n,t);return i<0||(n.splice(i,1),t.parent=null,e&&t.removeSelfFromZr(e),e&&e.refresh()),this},e.prototype.removeAll=function(){for(var t=this._children,e=this.__zr,n=0;n0&&(this._stillFrameAccum++,this._stillFrameAccum>this._sleepAfterStill&&this.animation.stop())},t.prototype.setSleepAfterStill=function(t){this._sleepAfterStill=t},t.prototype.wakeUp=function(){this.animation.start(),this._stillFrameAccum=0},t.prototype.refreshHover=function(){this._needsRefreshHover=!0},t.prototype.refreshHoverImmediately=function(){this._needsRefreshHover=!1,this.painter.refreshHover&&"canvas"===this.painter.getType()&&this.painter.refreshHover()},t.prototype.resize=function(t){t=t||{},this.painter.resize(t.width,t.height),this.handler.resize()},t.prototype.clearAnimation=function(){this.animation.clear()},t.prototype.getWidth=function(){return this.painter.getWidth()},t.prototype.getHeight=function(){return this.painter.getHeight()},t.prototype.setCursorStyle=function(t){this.handler.setCursorStyle(t)},t.prototype.findHover=function(t,e){return this.handler.findHover(t,e)},t.prototype.on=function(t,e,n){return this.handler.on(t,e,n),this},t.prototype.off=function(t,e){this.handler.off(t,e)},t.prototype.trigger=function(t,e){this.handler.trigger(t,e)},t.prototype.clear=function(){for(var t=this.storage.getRoots(),e=0;e0){if(t<=r)return a;if(t>=o)return s}else{if(t>=r)return a;if(t<=o)return s}else{if(t===r)return a;if(t===o)return s}return(t-r)/l*u+a}function Ur(t,e){switch(t){case"center":case"middle":t="50%";break;case"left":case"top":t="0%";break;case"right":case"bottom":t="100%"}return X(t)?(n=t,n.replace(/^\s+|\s+$/g,"")).match(/%$/)?parseFloat(t)/100*e:parseFloat(t):null==t?NaN:+t;var n}function Xr(t,e,n){return null==e&&(e=10),e=Math.min(Math.max(0,e),20),t=(+t).toFixed(e),n?t:+t}function Zr(t){return t.sort((function(t,e){return t-e})),t}function jr(t){if(t=+t,isNaN(t))return 0;if(t>1e-14)for(var e=1,n=0;n<15;n++,e*=10)if(Math.round(t*e)/e===t)return n;return qr(t)}function qr(t){var e=t.toString().toLowerCase(),n=e.indexOf("e"),i=n>0?+e.slice(n+1):0,r=n>0?n:e.length,o=e.indexOf("."),a=o<0?0:r-1-o;return Math.max(0,a-i)}function Kr(t,e){var n=Math.log,i=Math.LN10,r=Math.floor(n(t[1]-t[0])/i),o=Math.round(n(Math.abs(e[1]-e[0]))/i),a=Math.min(Math.max(-r+o,0),20);return isFinite(a)?a:20}function $r(t,e){var n=V(t,(function(t,e){return t+(isNaN(e)?0:e)}),0);if(0===n)return[];for(var i=Math.pow(10,e),r=z(t,(function(t){return(isNaN(t)?0:t)/n*i*100})),o=100*i,a=z(r,(function(t){return Math.floor(t)})),s=V(a,(function(t,e){return t+e}),0),l=z(r,(function(t,e){return t-a[e]}));su&&(u=l[c],h=c);++a[h],l[h]=0,++s}return z(a,(function(t){return t/i}))}function Jr(t,e){var n=Math.max(jr(t),jr(e)),i=t+e;return n>20?i:Xr(i,n)}var Qr=9007199254740991;function to(t){var e=2*Math.PI;return(t%e+e)%e}function eo(t){return t>-1e-4&&t=10&&e++,e}function ao(t,e){var n=oo(t),i=Math.pow(10,n),r=t/i;return t=(e?r<1.5?1:r<2.5?2:r<4?3:r<7?5:10:r<1?1:r<2?2:r<3?3:r<5?5:10)*i,n>=-20?+t.toFixed(n<0?-n:0):t}function so(t,e){var n=(t.length-1)*e+1,i=Math.floor(n),r=+t[i-1],o=n-i;return o?r+o*(t[i]-r):r}function lo(t){t.sort((function(t,e){return s(t,e,0)?-1:1}));for(var e=-1/0,n=1,i=0;i=0||r&&P(r,s)<0)){var l=n.getShallow(s,e);null!=l&&(o[t[a][0]]=l)}}return o}}var Jo=$o([["fill","color"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["opacity"],["shadowColor"]]),Qo=function(){function t(){}return t.prototype.getAreaStyle=function(t,e){return Jo(this,t,e)},t}(),ta=new Nn(50);function ea(t){if("string"==typeof t){var e=ta.get(t);return e&&e.image}return t}function na(t,e,n,i,r){if(t){if("string"==typeof t){if(e&&e.__zrImageSrc===t||!n)return e;var o=ta.get(t),a={hostEl:n,cb:i,cbPayload:r};return o?!ra(e=o.image)&&o.pending.push(a):((e=h.loadImage(t,ia,ia)).__zrImageSrc=t,ta.put(t,e.__cachedImgObj={image:e,pending:[a]})),e}return t}return e}function ia(){var t=this.__cachedImgObj;this.onload=this.onerror=this.__cachedImgObj=null;for(var e=0;e=a;l++)s-=a;var u=mr(n,e);return u>s&&(n="",u=0),s=t-u,r.ellipsis=n,r.ellipsisWidth=u,r.contentWidth=s,r.containerWidth=t,r}function la(t,e){var n=e.containerWidth,i=e.font,r=e.contentWidth;if(!n)return"";var o=mr(t,i);if(o<=n)return t;for(var a=0;;a++){if(o<=r||a>=e.maxIterations){t+=e.ellipsis;break}var s=0===a?ua(t,r,e.ascCharWidth,e.cnCharWidth):o>0?Math.floor(t.length*r/o):0;o=mr(t=t.substr(0,s),i)}return""===t&&(t=e.placeholder),t}function ua(t,e,n,i){for(var r=0,o=0,a=t.length;o0&&f+i.accumWidth>i.width&&(o=e.split("\n"),c=!0),i.accumWidth=f}else{var g=ya(e,h,i.width,i.breakAll,i.accumWidth);i.accumWidth=g.accumWidth+d,a=g.linesWidths,o=g.lines}}else o=e.split("\n");for(var y=0;y=33&&e<=383}(t)||!!fa[t]}function ya(t,e,n,i,r){for(var o=[],a=[],s="",l="",u=0,h=0,c=0;cn:r+h+d>n)?h?(s||l)&&(f?(s||(s=l,l="",h=u=0),o.push(s),a.push(h-u),l+=p,s="",h=u+=d):(l&&(s+=l,l="",u=0),o.push(s),a.push(h),s=p,h=d)):f?(o.push(l),a.push(u),l=p,u=d):(o.push(p),a.push(d)):(h+=d,f?(l+=p,u+=d):(l&&(s+=l,l="",u=0),s+=p))}else l&&(s+=l,h+=u),o.push(s),a.push(h),s="",l="",u=0,h=0}return o.length||s||(s=t,l="",u=0),l&&(s+=l),s&&(o.push(s),a.push(h)),1===o.length&&(h+=r),{accumWidth:h,lines:o,linesWidths:a}}var va="__zr_style_"+Math.round(10*Math.random()),ma={shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,shadowColor:"#000",opacity:1,blend:"source-over"},xa={style:{shadowBlur:!0,shadowOffsetX:!0,shadowOffsetY:!0,shadowColor:!0,opacity:!0}};ma[va]=!0;var _a=["z","z2","invisible"],ba=["invisible"],wa=function(t){function e(e){return t.call(this,e)||this}var i;return n(e,t),e.prototype._init=function(e){for(var n=G(e),i=0;i1e-4)return s[0]=t-n,s[1]=e-i,l[0]=t+n,void(l[1]=e+i);if(ka[0]=Da(r)*n+t,ka[1]=Ca(r)*i+e,La[0]=Da(o)*n+t,La[1]=Ca(o)*i+e,u(s,ka,La),h(l,ka,La),(r%=Aa)<0&&(r+=Aa),(o%=Aa)<0&&(o+=Aa),r>o&&!a?o+=Aa:rr&&(Pa[0]=Da(d)*n+t,Pa[1]=Ca(d)*i+e,u(s,Pa,s),h(l,Pa,l))}var Fa={M:1,L:2,C:3,Q:4,A:5,Z:6,R:7},Ga=[],Wa=[],Ha=[],Ya=[],Ua=[],Xa=[],Za=Math.min,ja=Math.max,qa=Math.cos,Ka=Math.sin,$a=Math.abs,Ja=Math.PI,Qa=2*Ja,ts="undefined"!=typeof Float32Array,es=[];function ns(t){return Math.round(t/Ja*1e8)/1e8%2*Ja}function is(t,e){var n=ns(t[0]);n<0&&(n+=Qa);var i=n-t[0],r=t[1];r+=i,!e&&r-n>=Qa?r=n+Qa:e&&n-r>=Qa?r=n-Qa:!e&&n>r?r=n+(Qa-ns(n-r)):e&&n0&&(this._ux=$a(n/rr/t)||0,this._uy=$a(n/rr/e)||0)},t.prototype.setDPR=function(t){this.dpr=t},t.prototype.setContext=function(t){this._ctx=t},t.prototype.getContext=function(){return this._ctx},t.prototype.beginPath=function(){return this._ctx&&this._ctx.beginPath(),this.reset(),this},t.prototype.reset=function(){this._saveData&&(this._len=0),this._pathSegLen&&(this._pathSegLen=null,this._pathLen=0),this._version++},t.prototype.moveTo=function(t,e){return this._drawPendingPt(),this.addData(Fa.M,t,e),this._ctx&&this._ctx.moveTo(t,e),this._x0=t,this._y0=e,this._xi=t,this._yi=e,this},t.prototype.lineTo=function(t,e){var n=$a(t-this._xi),i=$a(e-this._yi),r=n>this._ux||i>this._uy;if(this.addData(Fa.L,t,e),this._ctx&&r&&this._ctx.lineTo(t,e),r)this._xi=t,this._yi=e,this._pendingPtDist=0;else{var o=n*n+i*i;o>this._pendingPtDist&&(this._pendingPtX=t,this._pendingPtY=e,this._pendingPtDist=o)}return this},t.prototype.bezierCurveTo=function(t,e,n,i,r,o){return this._drawPendingPt(),this.addData(Fa.C,t,e,n,i,r,o),this._ctx&&this._ctx.bezierCurveTo(t,e,n,i,r,o),this._xi=r,this._yi=o,this},t.prototype.quadraticCurveTo=function(t,e,n,i){return this._drawPendingPt(),this.addData(Fa.Q,t,e,n,i),this._ctx&&this._ctx.quadraticCurveTo(t,e,n,i),this._xi=n,this._yi=i,this},t.prototype.arc=function(t,e,n,i,r,o){this._drawPendingPt(),es[0]=i,es[1]=r,is(es,o),i=es[0];var a=(r=es[1])-i;return this.addData(Fa.A,t,e,n,n,i,a,0,o?0:1),this._ctx&&this._ctx.arc(t,e,n,i,r,o),this._xi=qa(r)*n+t,this._yi=Ka(r)*n+e,this},t.prototype.arcTo=function(t,e,n,i,r){return this._drawPendingPt(),this._ctx&&this._ctx.arcTo(t,e,n,i,r),this},t.prototype.rect=function(t,e,n,i){return this._drawPendingPt(),this._ctx&&this._ctx.rect(t,e,n,i),this.addData(Fa.R,t,e,n,i),this},t.prototype.closePath=function(){this._drawPendingPt(),this.addData(Fa.Z);var t=this._ctx,e=this._x0,n=this._y0;return t&&t.closePath(),this._xi=e,this._yi=n,this},t.prototype.fill=function(t){t&&t.fill(),this.toStatic()},t.prototype.stroke=function(t){t&&t.stroke(),this.toStatic()},t.prototype.len=function(){return this._len},t.prototype.setData=function(t){var e=t.length;this.data&&this.data.length===e||!ts||(this.data=new Float32Array(e));for(var n=0;nu.length&&(this._expandData(),u=this.data);for(var h=0;h0&&(this._ctx&&this._ctx.lineTo(this._pendingPtX,this._pendingPtY),this._pendingPtDist=0)},t.prototype._expandData=function(){if(!(this.data instanceof Array)){for(var t=[],e=0;e11&&(this.data=new Float32Array(t)))}},t.prototype.getBoundingRect=function(){Ha[0]=Ha[1]=Ua[0]=Ua[1]=Number.MAX_VALUE,Ya[0]=Ya[1]=Xa[0]=Xa[1]=-Number.MAX_VALUE;var t,e=this.data,n=0,i=0,r=0,o=0;for(t=0;tn||$a(y)>i||c===e-1)&&(f=Math.sqrt(A*A+y*y),r=g,o=x);break;case Fa.C:var v=t[c++],m=t[c++],x=(g=t[c++],t[c++]),_=t[c++],b=t[c++];f=Sn(r,o,v,m,g,x,_,b,10),r=_,o=b;break;case Fa.Q:f=An(r,o,v=t[c++],m=t[c++],g=t[c++],x=t[c++],10),r=g,o=x;break;case Fa.A:var w=t[c++],S=t[c++],M=t[c++],I=t[c++],T=t[c++],C=t[c++],D=C+T;c+=1;t[c++];d&&(a=qa(T)*M+w,s=Ka(T)*I+S),f=ja(M,I)*Za(Qa,Math.abs(C)),r=qa(D)*M+w,o=Ka(D)*I+S;break;case Fa.R:a=r=t[c++],s=o=t[c++],f=2*t[c++]+2*t[c++];break;case Fa.Z:var A=a-r;y=s-o;f=Math.sqrt(A*A+y*y),r=a,o=s}f>=0&&(l[h++]=f,u+=f)}return this._pathLen=u,u},t.prototype.rebuildPath=function(t,e){var n,i,r,o,a,s,l,u,h,c,p=this.data,d=this._ux,f=this._uy,g=this._len,y=e<1,v=0,m=0,x=0;if(!y||(this._pathSegLen||this._calculateLength(),l=this._pathSegLen,u=e*this._pathLen))t:for(var _=0;_0&&(t.lineTo(h,c),x=0),b){case Fa.M:n=r=p[_++],i=o=p[_++],t.moveTo(r,o);break;case Fa.L:a=p[_++],s=p[_++];var S=$a(a-r),M=$a(s-o);if(S>d||M>f){if(y){if(v+(j=l[m++])>u){var I=(u-v)/j;t.lineTo(r*(1-I)+a*I,o*(1-I)+s*I);break t}v+=j}t.lineTo(a,s),r=a,o=s,x=0}else{var T=S*S+M*M;T>x&&(h=a,c=s,x=T)}break;case Fa.C:var C=p[_++],D=p[_++],A=p[_++],k=p[_++],L=p[_++],P=p[_++];if(y){if(v+(j=l[m++])>u){bn(r,C,A,L,I=(u-v)/j,Ga),bn(o,D,k,P,I,Wa),t.bezierCurveTo(Ga[1],Wa[1],Ga[2],Wa[2],Ga[3],Wa[3]);break t}v+=j}t.bezierCurveTo(C,D,A,k,L,P),r=L,o=P;break;case Fa.Q:C=p[_++],D=p[_++],A=p[_++],k=p[_++];if(y){if(v+(j=l[m++])>u){Cn(r,C,A,I=(u-v)/j,Ga),Cn(o,D,k,I,Wa),t.quadraticCurveTo(Ga[1],Wa[1],Ga[2],Wa[2]);break t}v+=j}t.quadraticCurveTo(C,D,A,k),r=A,o=k;break;case Fa.A:var O=p[_++],R=p[_++],N=p[_++],E=p[_++],z=p[_++],V=p[_++],B=p[_++],F=!p[_++],G=N>E?N:E,W=$a(N-E)>.001,H=z+V,Y=!1;if(y)v+(j=l[m++])>u&&(H=z+V*(u-v)/j,Y=!0),v+=j;if(W&&t.ellipse?t.ellipse(O,R,N,E,B,z,H,F):t.arc(O,R,G,z,H,F),Y)break t;w&&(n=qa(z)*N+O,i=Ka(z)*E+R),r=qa(H)*N+O,o=Ka(H)*E+R;break;case Fa.R:n=r=p[_],i=o=p[_+1],a=p[_++],s=p[_++];var U=p[_++],X=p[_++];if(y){if(v+(j=l[m++])>u){var Z=u-v;t.moveTo(a,s),t.lineTo(a+Za(Z,U),s),(Z-=U)>0&&t.lineTo(a+U,s+Za(Z,X)),(Z-=X)>0&&t.lineTo(a+ja(U-Z,0),s+X),(Z-=U)>0&&t.lineTo(a,s+ja(X-Z,0));break t}v+=j}t.rect(a,s,U,X);break;case Fa.Z:if(y){var j;if(v+(j=l[m++])>u){I=(u-v)/j;t.lineTo(r*(1-I)+n*I,o*(1-I)+i*I);break t}v+=j}t.closePath(),r=n,o=i}}},t.prototype.clone=function(){var e=new t,n=this.data;return e.data=n.slice?n.slice():Array.prototype.slice.call(n),e._len=this._len,e},t.CMD=Fa,t.initDefaultProps=function(){var e=t.prototype;e._saveData=!0,e._ux=0,e._uy=0,e._pendingPtDist=0,e._version=0}(),t}();function os(t,e,n,i,r,o,a){if(0===r)return!1;var s=r,l=0;if(a>e+s&&a>i+s||at+s&&o>n+s||oe+c&&h>i+c&&h>o+c&&h>s+c||ht+c&&u>n+c&&u>r+c&&u>a+c||ue+u&&l>i+u&&l>o+u||lt+u&&s>n+u&&s>r+u||sn||h+ur&&(r+=hs);var p=Math.atan2(l,s);return p<0&&(p+=hs),p>=i&&p<=r||p+hs>=i&&p+hs<=r}function ps(t,e,n,i,r,o){if(o>e&&o>i||or?s:0}var ds=rs.CMD,fs=2*Math.PI;var gs=[-1,-1,-1],ys=[-1,-1];function vs(t,e,n,i,r,o,a,s,l,u){if(u>e&&u>i&&u>o&&u>s||u1&&(h=void 0,h=ys[0],ys[0]=ys[1],ys[1]=h),f=vn(e,i,o,s,ys[0]),d>1&&(g=vn(e,i,o,s,ys[1]))),2===d?ve&&s>i&&s>o||s=0&&h<=1&&(r[l++]=h);else{var u=a*a-4*o*s;if(gn(u))(h=-a/(2*o))>=0&&h<=1&&(r[l++]=h);else if(u>0){var h,c=sn(u),p=(-a-c)/(2*o);(h=(-a+c)/(2*o))>=0&&h<=1&&(r[l++]=h),p>=0&&p<=1&&(r[l++]=p)}}return l}(e,i,o,s,gs);if(0===l)return 0;var u=Tn(e,i,o);if(u>=0&&u<=1){for(var h=0,c=Mn(e,i,o,u),p=0;pn||s<-n)return 0;var l=Math.sqrt(n*n-s*s);gs[0]=-l,gs[1]=l;var u=Math.abs(i-r);if(u<1e-4)return 0;if(u>=fs-1e-4){i=0,r=fs;var h=o?1:-1;return a>=gs[0]+t&&a<=gs[1]+t?h:0}if(i>r){var c=i;i=r,r=c}i<0&&(i+=fs,r+=fs);for(var p=0,d=0;d<2;d++){var f=gs[d];if(f+t>a){var g=Math.atan2(s,f);h=o?1:-1;g<0&&(g=fs+g),(g>=i&&g<=r||g+fs>=i&&g+fs<=r)&&(g>Math.PI/2&&g<1.5*Math.PI&&(h=-h),p+=h)}}return p}function _s(t,e,n,i,r){for(var o,a,s,l,u=t.data,h=t.len(),c=0,p=0,d=0,f=0,g=0,y=0;y1&&(n||(c+=ps(p,d,f,g,i,r))),m&&(f=p=u[y],g=d=u[y+1]),v){case ds.M:p=f=u[y++],d=g=u[y++];break;case ds.L:if(n){if(os(p,d,u[y],u[y+1],e,i,r))return!0}else c+=ps(p,d,u[y],u[y+1],i,r)||0;p=u[y++],d=u[y++];break;case ds.C:if(n){if(as(p,d,u[y++],u[y++],u[y++],u[y++],u[y],u[y+1],e,i,r))return!0}else c+=vs(p,d,u[y++],u[y++],u[y++],u[y++],u[y],u[y+1],i,r)||0;p=u[y++],d=u[y++];break;case ds.Q:if(n){if(ss(p,d,u[y++],u[y++],u[y],u[y+1],e,i,r))return!0}else c+=ms(p,d,u[y++],u[y++],u[y],u[y+1],i,r)||0;p=u[y++],d=u[y++];break;case ds.A:var x=u[y++],_=u[y++],b=u[y++],w=u[y++],S=u[y++],M=u[y++];y+=1;var I=!!(1-u[y++]);o=Math.cos(S)*b+x,a=Math.sin(S)*w+_,m?(f=o,g=a):c+=ps(p,d,o,a,i,r);var T=(i-x)*w/b+x;if(n){if(cs(x,_,w,S,S+M,I,e,T,r))return!0}else c+=xs(x,_,w,S,S+M,I,T,r);p=Math.cos(S+M)*b+x,d=Math.sin(S+M)*w+_;break;case ds.R:if(f=p=u[y++],g=d=u[y++],o=f+u[y++],a=g+u[y++],n){if(os(f,g,o,g,e,i,r)||os(o,g,o,a,e,i,r)||os(o,a,f,a,e,i,r)||os(f,a,f,g,e,i,r))return!0}else c+=ps(o,g,o,a,i,r),c+=ps(f,a,f,g,i,r);break;case ds.Z:if(n){if(os(p,d,f,g,e,i,r))return!0}else c+=ps(p,d,f,g,i,r);p=f,d=g}}return n||(s=d,l=g,Math.abs(s-l)<1e-4)||(c+=ps(p,d,f,g,i,r)||0),0!==c}var bs=k({fill:"#000",stroke:null,strokePercent:1,fillOpacity:1,strokeOpacity:1,lineDashOffset:0,lineWidth:1,lineCap:"butt",miterLimit:10,strokeNoScale:!1,strokeFirst:!1},ma),ws={style:k({fill:!0,stroke:!0,strokePercent:!0,fillOpacity:!0,strokeOpacity:!0,lineDashOffset:!0,lineWidth:!0,miterLimit:!0},xa.style)},Ss=gr.concat(["invisible","culling","z","z2","zlevel","parent"]),Ms=function(t){function e(e){return t.call(this,e)||this}var i;return n(e,t),e.prototype.update=function(){var n=this;t.prototype.update.call(this);var i=this.style;if(i.decal){var r=this._decalEl=this._decalEl||new e;r.buildPath===e.prototype.buildPath&&(r.buildPath=function(t){n.buildPath(t,n.shape)}),r.silent=!0;var o=r.style;for(var a in i)o[a]!==i[a]&&(o[a]=i[a]);o.fill=i.fill?i.decal:null,o.decal=null,o.shadowColor=null,i.strokeFirst&&(o.stroke=null);for(var s=0;s.5?or:e>.2?"#eee":ar}if(t)return ar}return or},e.prototype.getInsideTextStroke=function(t){var e=this.style.fill;if(X(e)){var n=this.__zr;if(!(!n||!n.isDarkMode())===ri(t,0)<.4)return e}},e.prototype.buildPath=function(t,e,n){},e.prototype.pathUpdated=function(){this.__dirty&=-5},e.prototype.getUpdatedPathProxy=function(t){return!this.path&&this.createPathProxy(),this.path.beginPath(),this.buildPath(this.path,this.shape,t),this.path},e.prototype.createPathProxy=function(){this.path=new rs(!1)},e.prototype.hasStroke=function(){var t=this.style,e=t.stroke;return!(null==e||"none"===e||!(t.lineWidth>0))},e.prototype.hasFill=function(){var t=this.style.fill;return null!=t&&"none"!==t},e.prototype.getBoundingRect=function(){var t=this._rect,e=this.style,n=!t;if(n){var i=!1;this.path||(i=!0,this.createPathProxy());var r=this.path;(i||4&this.__dirty)&&(r.beginPath(),this.buildPath(r,this.shape,!1),this.pathUpdated()),t=r.getBoundingRect()}if(this._rect=t,this.hasStroke()&&this.path&&this.path.len()>0){var o=this._rectStroke||(this._rectStroke=t.clone());if(this.__dirty||n){o.copy(t);var a=e.strokeNoScale?this.getLineScale():1,s=e.lineWidth;if(!this.hasFill()){var l=this.strokeContainThreshold;s=Math.max(s,null==l?4:l)}a>1e-10&&(o.width+=s/a,o.height+=s/a,o.x-=s/a/2,o.y-=s/a/2)}return o}return t},e.prototype.contain=function(t,e){var n=this.transformCoordToLocal(t,e),i=this.getBoundingRect(),r=this.style;if(t=n[0],e=n[1],i.contain(t,e)){var o=this.path;if(this.hasStroke()){var a=r.lineWidth,s=r.strokeNoScale?this.getLineScale():1;if(s>1e-10&&(this.hasFill()||(a=Math.max(a,this.strokeContainThreshold)),function(t,e,n,i){return _s(t,e,!0,n,i)}(o,a/s,t,e)))return!0}if(this.hasFill())return function(t,e,n){return _s(t,0,!1,e,n)}(o,t,e)}return!1},e.prototype.dirtyShape=function(){this.__dirty|=4,this._rect&&(this._rect=null),this._decalEl&&this._decalEl.dirtyShape(),this.markRedraw()},e.prototype.dirty=function(){this.dirtyStyle(),this.dirtyShape()},e.prototype.animateShape=function(t){return this.animate("shape",t)},e.prototype.updateDuringAnimation=function(t){"style"===t?this.dirtyStyle():"shape"===t?this.dirtyShape():this.markRedraw()},e.prototype.attrKV=function(e,n){"shape"===e?this.setShape(n):t.prototype.attrKV.call(this,e,n)},e.prototype.setShape=function(t,e){var n=this.shape;return n||(n=this.shape={}),"string"==typeof t?n[t]=e:A(n,t),this.dirtyShape(),this},e.prototype.shapeChanged=function(){return!!(4&this.__dirty)},e.prototype.createStyle=function(t){return mt(bs,t)},e.prototype._innerSaveToNormal=function(e){t.prototype._innerSaveToNormal.call(this,e);var n=this._normalState;e.shape&&!n.shape&&(n.shape=A({},this.shape))},e.prototype._applyStateObj=function(e,n,i,r,o,a){t.prototype._applyStateObj.call(this,e,n,i,r,o,a);var s,l=!(n&&r);if(n&&n.shape?o?r?s=n.shape:(s=A({},i.shape),A(s,n.shape)):(s=A({},r?this.shape:i.shape),A(s,n.shape)):l&&(s=i.shape),s)if(o){this.shape=A({},this.shape);for(var u={},h=G(s),c=0;c0},e.prototype.hasFill=function(){var t=this.style.fill;return null!=t&&"none"!==t},e.prototype.createStyle=function(t){return mt(Is,t)},e.prototype.setBoundingRect=function(t){this._rect=t},e.prototype.getBoundingRect=function(){var t=this.style;if(!this._rect){var e=t.text;null!=e?e+="":e="";var n=_r(e,t.font,t.textAlign,t.textBaseline);if(n.x+=t.x||0,n.y+=t.y||0,this.hasStroke()){var i=t.lineWidth;n.x-=i/2,n.y-=i/2,n.width+=i,n.height+=i}this._rect=n}return this._rect},e.initDefaultProps=void(e.prototype.dirtyRectTolerance=10),e}(wa);Ts.prototype.type="tspan";var Cs=k({x:0,y:0},ma),Ds={style:k({x:!0,y:!0,width:!0,height:!0,sx:!0,sy:!0,sWidth:!0,sHeight:!0},xa.style)};var As=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.createStyle=function(t){return mt(Cs,t)},e.prototype._getSize=function(t){var e=this.style,n=e[t];if(null!=n)return n;var i,r=(i=e.image)&&"string"!=typeof i&&i.width&&i.height?e.image:this.__image;if(!r)return 0;var o="width"===t?"height":"width",a=e[o];return null==a?r[t]:r[t]/r[o]*a},e.prototype.getWidth=function(){return this._getSize("width")},e.prototype.getHeight=function(){return this._getSize("height")},e.prototype.getAnimationStyleProps=function(){return Ds},e.prototype.getBoundingRect=function(){var t=this.style;return this._rect||(this._rect=new Ee(t.x||0,t.y||0,this.getWidth(),this.getHeight())),this._rect},e}(wa);As.prototype.type="image";var ks=Math.round;function Ls(t,e,n){if(e){var i=e.x1,r=e.x2,o=e.y1,a=e.y2;t.x1=i,t.x2=r,t.y1=o,t.y2=a;var s=n&&n.lineWidth;return s?(ks(2*i)===ks(2*r)&&(t.x1=t.x2=Os(i,s,!0)),ks(2*o)===ks(2*a)&&(t.y1=t.y2=Os(o,s,!0)),t):t}}function Ps(t,e,n){if(e){var i=e.x,r=e.y,o=e.width,a=e.height;t.x=i,t.y=r,t.width=o,t.height=a;var s=n&&n.lineWidth;return s?(t.x=Os(i,s,!0),t.y=Os(r,s,!0),t.width=Math.max(Os(i+o,s,!1)-t.x,0===o?0:1),t.height=Math.max(Os(r+a,s,!1)-t.y,0===a?0:1),t):t}}function Os(t,e,n){if(!e)return t;var i=ks(2*t);return(i+ks(e))%2==0?i/2:(i+(n?1:-1))/2}var Rs=function(){this.x=0,this.y=0,this.width=0,this.height=0},Ns={},Es=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new Rs},e.prototype.buildPath=function(t,e){var n,i,r,o;if(this.subPixelOptimize){var a=Ps(Ns,e,this.style);n=a.x,i=a.y,r=a.width,o=a.height,a.r=e.r,e=a}else n=e.x,i=e.y,r=e.width,o=e.height;e.r?function(t,e){var n,i,r,o,a,s=e.x,l=e.y,u=e.width,h=e.height,c=e.r;u<0&&(s+=u,u=-u),h<0&&(l+=h,h=-h),"number"==typeof c?n=i=r=o=c:c instanceof Array?1===c.length?n=i=r=o=c[0]:2===c.length?(n=r=c[0],i=o=c[1]):3===c.length?(n=c[0],i=o=c[1],r=c[2]):(n=c[0],i=c[1],r=c[2],o=c[3]):n=i=r=o=0,n+i>u&&(n*=u/(a=n+i),i*=u/a),r+o>u&&(r*=u/(a=r+o),o*=u/a),i+r>h&&(i*=h/(a=i+r),r*=h/a),n+o>h&&(n*=h/(a=n+o),o*=h/a),t.moveTo(s+n,l),t.lineTo(s+u-i,l),0!==i&&t.arc(s+u-i,l+i,i,-Math.PI/2,0),t.lineTo(s+u,l+h-r),0!==r&&t.arc(s+u-r,l+h-r,r,0,Math.PI/2),t.lineTo(s+o,l+h),0!==o&&t.arc(s+o,l+h-o,o,Math.PI/2,Math.PI),t.lineTo(s,l+n),0!==n&&t.arc(s+n,l+n,n,Math.PI,1.5*Math.PI)}(t,e):t.rect(n,i,r,o)},e.prototype.isZeroArea=function(){return!this.shape.width||!this.shape.height},e}(Ms);Es.prototype.type="rect";var zs={fill:"#000"},Vs={style:k({fill:!0,stroke:!0,fillOpacity:!0,strokeOpacity:!0,lineWidth:!0,fontSize:!0,lineHeight:!0,width:!0,height:!0,textShadowColor:!0,textShadowBlur:!0,textShadowOffsetX:!0,textShadowOffsetY:!0,backgroundColor:!0,padding:!0,borderColor:!0,borderWidth:!0,borderRadius:!0},xa.style)},Bs=function(t){function e(e){var n=t.call(this)||this;return n.type="text",n._children=[],n._defaultStyle=zs,n.attr(e),n}return n(e,t),e.prototype.childrenRef=function(){return this._children},e.prototype.update=function(){t.prototype.update.call(this),this.styleChanged()&&this._updateSubTexts();for(var e=0;ed&&h){var f=Math.floor(d/l);n=n.slice(0,f)}if(t&&a&&null!=c)for(var g=sa(c,o,e.ellipsis,{minChar:e.truncateMinChar,placeholder:e.placeholder}),y=0;y0,T=null!=t.width&&("truncate"===t.overflow||"break"===t.overflow||"breakAll"===t.overflow),C=i.calculatedLineHeight,D=0;Dl&&da(n,t.substring(l,u),e,s),da(n,i[2],e,s,i[1]),l=oa.lastIndex}lo){b>0?(m.tokens=m.tokens.slice(0,b),y(m,_,x),n.lines=n.lines.slice(0,v+1)):n.lines=n.lines.slice(0,v);break t}var C=w.width,D=null==C||"auto"===C;if("string"==typeof C&&"%"===C.charAt(C.length-1))P.percentWidth=C,h.push(P),P.contentWidth=mr(P.text,I);else{if(D){var A=w.backgroundColor,k=A&&A.image;k&&ra(k=ea(k))&&(P.width=Math.max(P.width,k.width*T/k.height))}var L=f&&null!=r?r-_:null;null!=L&&L=0&&"right"===(C=x[T]).align;)this._placeToken(C,t,b,f,I,"right",y),w-=C.width,I-=C.width,T--;for(M+=(n-(M-d)-(g-I)-w)/2;S<=T;)C=x[S],this._placeToken(C,t,b,f,M+C.width/2,"center",y),M+=C.width,S++;f+=b}},e.prototype._placeToken=function(t,e,n,i,r,o,s){var l=e.rich[t.styleName]||{};l.text=t.text;var u=t.verticalAlign,h=i+n/2;"top"===u?h=i+t.height/2:"bottom"===u&&(h=i+n-t.height/2),!t.isLineHolder&&$s(l)&&this._renderBackground(l,e,"right"===o?r-t.width:"center"===o?r-t.width/2:r,h-t.height/2,t.width,t.height);var c=!!l.backgroundColor,p=t.textPadding;p&&(r=qs(r,o,p),h-=t.height/2-p[0]-t.innerHeight/2);var d=this._getOrCreateChild(Ts),f=d.createStyle();d.useStyle(f);var g=this._defaultStyle,y=!1,v=0,m=js("fill"in l?l.fill:"fill"in e?e.fill:(y=!0,g.fill)),x=Zs("stroke"in l?l.stroke:"stroke"in e?e.stroke:c||s||g.autoStroke&&!y?null:(v=2,g.stroke)),_=l.textShadowBlur>0||e.textShadowBlur>0;f.text=t.text,f.x=r,f.y=h,_&&(f.shadowBlur=l.textShadowBlur||e.textShadowBlur||0,f.shadowColor=l.textShadowColor||e.textShadowColor||"transparent",f.shadowOffsetX=l.textShadowOffsetX||e.textShadowOffsetX||0,f.shadowOffsetY=l.textShadowOffsetY||e.textShadowOffsetY||0),f.textAlign=o,f.textBaseline="middle",f.font=t.font||a,f.opacity=ot(l.opacity,e.opacity,1),Ys(f,l),x&&(f.lineWidth=ot(l.lineWidth,e.lineWidth,v),f.lineDash=rt(l.lineDash,e.lineDash),f.lineDashOffset=e.lineDashOffset||0,f.stroke=x),m&&(f.fill=m);var b=t.contentWidth,w=t.contentHeight;d.setBoundingRect(new Ee(br(f.x,b,f.textAlign),wr(f.y,w,f.textBaseline),b,w))},e.prototype._renderBackground=function(t,e,n,i,r,o){var a,s,l,u=t.backgroundColor,h=t.borderWidth,c=t.borderColor,p=u&&u.image,d=u&&!p,f=t.borderRadius,g=this;if(d||t.lineHeight||h&&c){(a=this._getOrCreateChild(Es)).useStyle(a.createStyle()),a.style.fill=null;var y=a.shape;y.x=n,y.y=i,y.width=r,y.height=o,y.r=f,a.dirtyShape()}if(d)(l=a.style).fill=u||null,l.fillOpacity=rt(t.fillOpacity,1);else if(p){(s=this._getOrCreateChild(As)).onload=function(){g.dirtyStyle()};var v=s.style;v.image=u.image,v.x=n,v.y=i,v.width=r,v.height=o}h&&c&&((l=a.style).lineWidth=h,l.stroke=c,l.strokeOpacity=rt(t.strokeOpacity,1),l.lineDash=t.borderDash,l.lineDashOffset=t.borderDashOffset||0,a.strokeContainThreshold=0,a.hasFill()&&a.hasStroke()&&(l.strokeFirst=!0,l.lineWidth*=2));var m=(a||s).style;m.shadowBlur=t.shadowBlur||0,m.shadowColor=t.shadowColor||"transparent",m.shadowOffsetX=t.shadowOffsetX||0,m.shadowOffsetY=t.shadowOffsetY||0,m.opacity=ot(t.opacity,e.opacity,1)},e.makeFont=function(t){var e="";return Us(t)&&(e=[t.fontStyle,t.fontWeight,Hs(t.fontSize),t.fontFamily||"sans-serif"].join(" ")),e&&ut(e)||t.textFont||t.font},e}(wa),Fs={left:!0,right:1,center:1},Gs={top:1,bottom:1,middle:1},Ws=["fontStyle","fontWeight","fontSize","fontFamily"];function Hs(t){return"string"!=typeof t||-1===t.indexOf("px")&&-1===t.indexOf("rem")&&-1===t.indexOf("em")?isNaN(+t)?"12px":t+"px":t}function Ys(t,e){for(var n=0;n=0,o=!1;if(t instanceof Ms){var a=nl(t),s=r&&a.selectFill||a.normalFill,l=r&&a.selectStroke||a.normalStroke;if(pl(s)||pl(l)){var u=(i=i||{}).style||{};"inherit"===u.fill?(o=!0,i=A({},i),(u=A({},u)).fill=s):!pl(u.fill)&&pl(s)?(o=!0,i=A({},i),(u=A({},u)).fill=fl(s)):!pl(u.stroke)&&pl(l)&&(o||(i=A({},i),u=A({},u)),u.stroke=fl(l)),i.style=u}}if(i&&null==i.z2){o||(i=A({},i));var h=t.z2EmphasisLift;i.z2=t.z2+(null!=h?h:al)}return i}(this,0,e,n);if("blur"===t)return function(t,e,n){var i=P(t.currentStates,e)>=0,r=t.style.opacity,o=i?null:function(t,e,n,i){for(var r=t.style,o={},a=0;a0){var o={dataIndex:r,seriesIndex:t.seriesIndex};null!=i&&(o.dataType=i),e.push(o)}}))})),e}function Wl(t,e,n){jl(t,!0),Sl(t,Tl),Yl(t,e,n)}function Hl(t,e,n,i){i?function(t){jl(t,!1)}(t):Wl(t,e,n)}function Yl(t,e,n){var i=Js(t);null!=e?(i.focus=e,i.blurScope=n):i.focus&&(i.focus=null)}var Ul=["emphasis","blur","select"],Xl={itemStyle:"getItemStyle",lineStyle:"getLineStyle",areaStyle:"getAreaStyle"};function Zl(t,e,n,i){n=n||"itemStyle";for(var r=0;r1&&(a*=iu(f),s*=iu(f));var g=(r===o?-1:1)*iu((a*a*(s*s)-a*a*(d*d)-s*s*(p*p))/(a*a*(d*d)+s*s*(p*p)))||0,y=g*a*d/s,v=g*-s*p/a,m=(t+n)/2+ou(c)*y-ru(c)*v,x=(e+i)/2+ru(c)*y+ou(c)*v,_=uu([1,0],[(p-y)/a,(d-v)/s]),b=[(p-y)/a,(d-v)/s],w=[(-1*p-y)/a,(-1*d-v)/s],S=uu(b,w);if(lu(b,w)<=-1&&(S=au),lu(b,w)>=1&&(S=0),S<0){var M=Math.round(S/au*1e6)/1e6;S=2*au+M%2*au}h.addData(u,m,x,a,s,_,S,c,o)}var cu=/([mlvhzcqtsa])([^mlvhzcqtsa]*)/gi,pu=/-?([0-9]*\.)?[0-9]+([eE]-?[0-9]+)?/g;var du=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.applyTransform=function(t){},e}(Ms);function fu(t){return null!=t.setData}function gu(t,e){var n=function(t){var e=new rs;if(!t)return e;var n,i=0,r=0,o=i,a=r,s=rs.CMD,l=t.match(cu);if(!l)return e;for(var u=0;uk*k+L*L&&(M=T,I=C),{cx:M,cy:I,x0:-h,y0:-c,x1:M*(r/b-1),y1:I*(r/b-1)}}function Ru(t,e){var n,i=ku(e.r,0),r=ku(e.r0||0,0),o=i>0;if(o||r>0){if(o||(i=r,r=0),r>i){var a=i;i=r,r=a}var s=e.startAngle,l=e.endAngle;if(!isNaN(s)&&!isNaN(l)){var u=e.cx,h=e.cy,c=!!e.clockwise,p=Du(l-s),d=p>Su&&p%Su;if(d>Pu&&(p=d),i>Pu)if(p>Su-Pu)t.moveTo(u+i*Iu(s),h+i*Mu(s)),t.arc(u,h,i,s,l,!c),r>Pu&&(t.moveTo(u+r*Iu(l),h+r*Mu(l)),t.arc(u,h,r,l,s,c));else{var f=void 0,g=void 0,y=void 0,v=void 0,m=void 0,x=void 0,_=void 0,b=void 0,w=void 0,S=void 0,M=void 0,I=void 0,T=void 0,C=void 0,D=void 0,A=void 0,k=i*Iu(s),L=i*Mu(s),P=r*Iu(l),O=r*Mu(l),R=p>Pu;if(R){var N=e.cornerRadius;N&&(n=function(t){var e;if(Y(t)){var n=t.length;if(!n)return t;e=1===n?[t[0],t[0],0,0]:2===n?[t[0],t[0],t[1],t[1]]:3===n?t.concat(t[2]):t}else e=[t,t,t,t];return e}(N),f=n[0],g=n[1],y=n[2],v=n[3]);var E=Du(i-r)/2;if(m=Lu(E,y),x=Lu(E,v),_=Lu(E,f),b=Lu(E,g),M=w=ku(m,x),I=S=ku(_,b),(w>Pu||S>Pu)&&(T=i*Iu(l),C=i*Mu(l),D=r*Iu(s),A=r*Mu(s),pPu){var U=Lu(y,M),X=Lu(v,M),Z=Ou(D,A,k,L,i,U,c),j=Ou(T,C,P,O,i,X,c);t.moveTo(u+Z.cx+Z.x0,h+Z.cy+Z.y0),M0&&t.arc(u+Z.cx,h+Z.cy,U,Cu(Z.y0,Z.x0),Cu(Z.y1,Z.x1),!c),t.arc(u,h,i,Cu(Z.cy+Z.y1,Z.cx+Z.x1),Cu(j.cy+j.y1,j.cx+j.x1),!c),X>0&&t.arc(u+j.cx,h+j.cy,X,Cu(j.y1,j.x1),Cu(j.y0,j.x0),!c))}else t.moveTo(u+k,h+L),t.arc(u,h,i,s,l,!c);else t.moveTo(u+k,h+L);if(r>Pu&&R)if(I>Pu){U=Lu(f,I),Z=Ou(P,O,T,C,r,-(X=Lu(g,I)),c),j=Ou(k,L,D,A,r,-U,c);t.lineTo(u+Z.cx+Z.x0,h+Z.cy+Z.y0),I0&&t.arc(u+Z.cx,h+Z.cy,X,Cu(Z.y0,Z.x0),Cu(Z.y1,Z.x1),!c),t.arc(u,h,r,Cu(Z.cy+Z.y1,Z.cx+Z.x1),Cu(j.cy+j.y1,j.cx+j.x1),c),U>0&&t.arc(u+j.cx,h+j.cy,U,Cu(j.y1,j.x1),Cu(j.y0,j.x0),!c))}else t.lineTo(u+P,h+O),t.arc(u,h,r,l,s,c);else t.lineTo(u+P,h+O)}else t.moveTo(u,h);t.closePath()}}}var Nu=function(){this.cx=0,this.cy=0,this.r0=0,this.r=0,this.startAngle=0,this.endAngle=2*Math.PI,this.clockwise=!0,this.cornerRadius=0},Eu=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new Nu},e.prototype.buildPath=function(t,e){Ru(t,e)},e.prototype.isZeroArea=function(){return this.shape.startAngle===this.shape.endAngle||this.shape.r===this.shape.r0},e}(Ms);Eu.prototype.type="sector";var zu=function(){this.cx=0,this.cy=0,this.r=0,this.r0=0},Vu=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new zu},e.prototype.buildPath=function(t,e){var n=e.cx,i=e.cy,r=2*Math.PI;t.moveTo(n+e.r,i),t.arc(n,i,e.r,0,r,!1),t.moveTo(n+e.r0,i),t.arc(n,i,e.r0,0,r,!0)},e}(Ms);function Bu(t,e,n){var i=e.smooth,r=e.points;if(r&&r.length>=2){if(i){var o=function(t,e,n,i){var r,o,a,s,l=[],u=[],h=[],c=[];if(i){a=[1/0,1/0],s=[-1/0,-1/0];for(var p=0,d=t.length;prh[1]){if(a=!1,r)return a;var u=Math.abs(rh[0]-ih[1]),h=Math.abs(ih[0]-rh[1]);Math.min(u,h)>i.len()&&(u0){var c={duration:h.duration,delay:h.delay||0,easing:h.easing,done:o,force:!!o||!!a,setToFinal:!u,scope:t,during:a};l?e.animateFrom(n,c):e.animateTo(n,c)}else e.stopAnimation(),!l&&e.attr(n),a&&a(1),o&&o()}function dh(t,e,n,i,r,o){ph("update",t,e,n,i,r,o)}function fh(t,e,n,i,r,o){ph("enter",t,e,n,i,r,o)}function gh(t){if(!t.__zr)return!0;for(var e=0;eMath.abs(o[1])?o[0]>0?"right":"left":o[1]>0?"bottom":"top"}function Vh(t){return!t.isGroup}function Bh(t,e,n){if(t&&e){var i,r=(i={},t.traverse((function(t){Vh(t)&&t.anid&&(i[t.anid]=t)})),i);e.traverse((function(t){if(Vh(t)&&t.anid){var e=r[t.anid];if(e){var i=o(t);t.attr(o(e)),dh(t,i,n,Js(t).dataIndex)}}}))}function o(t){var e={x:t.x,y:t.y,rotation:t.rotation};return function(t){return null!=t.shape}(t)&&(e.shape=A({},t.shape)),e}}function Fh(t,e){return z(t,(function(t){var n=t[0];n=_h(n,e.x),n=bh(n,e.x+e.width);var i=t[1];return i=_h(i,e.y),[n,i=bh(i,e.y+e.height)]}))}function Gh(t,e){var n=_h(t.x,e.x),i=bh(t.x+t.width,e.x+e.width),r=_h(t.y,e.y),o=bh(t.y+t.height,e.y+e.height);if(i>=n&&o>=r)return{x:n,y:r,width:i-n,height:o-r}}function Wh(t,e,n){var i=A({rectHover:!0},e),r=i.style={strokeNoScale:!0};if(n=n||{x:-1,y:-1,width:2,height:2},t)return 0===t.indexOf("image://")?(r.image=t.slice(8),k(r,n),new As(i)):Dh(t.replace("path://",""),i,n,"center")}function Hh(t,e,n,i,r){for(var o=0,a=r[r.length-1];o=-1e-6)return!1;var f=t-r,g=e-o,y=Uh(f,g,u,h)/d;if(y<0||y>1)return!1;var v=Uh(f,g,c,p)/d;return!(v<0||v>1)}function Uh(t,e,n,i){return t*i-n*e}function Xh(t){var e=t.itemTooltipOption,n=t.componentModel,i=t.itemName,r=X(e)?{formatter:e}:e,o=n.mainType,a=n.componentIndex,s={componentType:o,name:i,$vars:["name"]};s[o+"Index"]=a;var l=t.formatterParamsExtra;l&&E(G(l),(function(t){_t(s,t)||(s[t]=l[t],s.$vars.push(t))}));var u=Js(t.el);u.componentMainType=o,u.componentIndex=a,u.tooltipConfig={name:i,option:k({content:i,formatterParams:s},r)}}function Zh(t,e){var n;t.isGroup&&(n=e(t)),n||t.traverse(e)}function jh(t,e){if(t)if(Y(t))for(var n=0;n-1?Cc:Ac;function Oc(t,e){t=t.toUpperCase(),Lc[t]=new Sc(e),kc[t]=e}function Rc(t){return Lc[t]}Oc(Dc,{time:{month:["January","February","March","April","May","June","July","August","September","October","November","December"],monthAbbr:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayOfWeek:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayOfWeekAbbr:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},legend:{selector:{all:"All",inverse:"Inv"}},toolbox:{brush:{title:{rect:"Box Select",polygon:"Lasso Select",lineX:"Horizontally Select",lineY:"Vertically Select",keep:"Keep Selections",clear:"Clear Selections"}},dataView:{title:"Data View",lang:["Data View","Close","Refresh"]},dataZoom:{title:{zoom:"Zoom",back:"Zoom Reset"}},magicType:{title:{line:"Switch to Line Chart",bar:"Switch to Bar Chart",stack:"Stack",tiled:"Tile"}},restore:{title:"Restore"},saveAsImage:{title:"Save as Image",lang:["Right Click to Save Image"]}},series:{typeNames:{pie:"Pie chart",bar:"Bar chart",line:"Line chart",scatter:"Scatter plot",effectScatter:"Ripple scatter plot",radar:"Radar chart",tree:"Tree",treemap:"Treemap",boxplot:"Boxplot",candlestick:"Candlestick",k:"K line chart",heatmap:"Heat map",map:"Map",parallel:"Parallel coordinate map",lines:"Line graph",graph:"Relationship graph",sankey:"Sankey diagram",funnel:"Funnel chart",gauge:"Gauge",pictorialBar:"Pictorial bar",themeRiver:"Theme River Map",sunburst:"Sunburst"}},aria:{general:{withTitle:'This is a chart about "{title}"',withoutTitle:"This is a chart"},series:{single:{prefix:"",withName:" with type {seriesType} named {seriesName}.",withoutName:" with type {seriesType}."},multiple:{prefix:". It consists of {seriesCount} series count.",withName:" The {seriesId} series is a {seriesType} representing {seriesName}.",withoutName:" The {seriesId} series is a {seriesType}.",separator:{middle:"",end:""}}},data:{allData:"The data is as follows: ",partialData:"The first {displayCnt} items are: ",withName:"the data for {name} is {value}",withoutName:"{value}",separator:{middle:", ",end:". "}}}}),Oc(Cc,{time:{month:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthAbbr:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayOfWeek:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayOfWeekAbbr:["日","一","二","三","四","五","六"]},legend:{selector:{all:"全选",inverse:"反选"}},toolbox:{brush:{title:{rect:"矩形选择",polygon:"圈选",lineX:"横向选择",lineY:"纵向选择",keep:"保持选择",clear:"清除选择"}},dataView:{title:"数据视图",lang:["数据视图","关闭","刷新"]},dataZoom:{title:{zoom:"区域缩放",back:"区域缩放还原"}},magicType:{title:{line:"切换为折线图",bar:"切换为柱状图",stack:"切换为堆叠",tiled:"切换为平铺"}},restore:{title:"还原"},saveAsImage:{title:"保存为图片",lang:["右键另存为图片"]}},series:{typeNames:{pie:"饼图",bar:"柱状图",line:"折线图",scatter:"散点图",effectScatter:"涟漪散点图",radar:"雷达图",tree:"树图",treemap:"矩形树图",boxplot:"箱型图",candlestick:"K线图",k:"K线图",heatmap:"热力图",map:"地图",parallel:"平行坐标图",lines:"线图",graph:"关系图",sankey:"桑基图",funnel:"漏斗图",gauge:"仪表盘图",pictorialBar:"象形柱图",themeRiver:"主题河流图",sunburst:"旭日图"}},aria:{general:{withTitle:"这是一个关于“{title}”的图表。",withoutTitle:"这是一个图表,"},series:{single:{prefix:"",withName:"图表类型是{seriesType},表示{seriesName}。",withoutName:"图表类型是{seriesType}。"},multiple:{prefix:"它由{seriesCount}个图表系列组成。",withName:"第{seriesId}个系列是一个表示{seriesName}的{seriesType},",withoutName:"第{seriesId}个系列是一个{seriesType},",separator:{middle:";",end:"。"}}},data:{allData:"其数据是——",partialData:"其中,前{displayCnt}项是——",withName:"{name}的数据是{value}",withoutName:"{value}",separator:{middle:",",end:""}}}});var Nc=1e3,Ec=6e4,zc=36e5,Vc=864e5,Bc=31536e6,Fc={year:"{yyyy}",month:"{MMM}",day:"{d}",hour:"{HH}:{mm}",minute:"{HH}:{mm}",second:"{HH}:{mm}:{ss}",millisecond:"{HH}:{mm}:{ss} {SSS}",none:"{yyyy}-{MM}-{dd} {HH}:{mm}:{ss} {SSS}"},Gc="{yyyy}-{MM}-{dd}",Wc={year:"{yyyy}",month:"{yyyy}-{MM}",day:Gc,hour:"{yyyy}-{MM}-{dd} "+Fc.hour,minute:"{yyyy}-{MM}-{dd} "+Fc.minute,second:"{yyyy}-{MM}-{dd} "+Fc.second,millisecond:Fc.none},Hc=["year","month","day","hour","minute","second","millisecond"],Yc=["year","half-year","quarter","month","week","half-week","day","half-day","quarter-day","hour","minute","second","millisecond"];function Uc(t,e){return"0000".substr(0,e-(t+="").length)+t}function Xc(t){switch(t){case"half-year":case"quarter":return"month";case"week":case"half-week":return"day";case"half-day":case"quarter-day":return"hour";default:return t}}function Zc(t){return t===Xc(t)}function jc(t,e,n,i){var r=io(t),o=r[$c(n)](),a=r[Jc(n)]()+1,s=Math.floor((a-1)/3)+1,l=r[Qc(n)](),u=r["get"+(n?"UTC":"")+"Day"](),h=r[tp(n)](),c=(h-1)%12+1,p=r[ep(n)](),d=r[np(n)](),f=r[ip(n)](),g=(i instanceof Sc?i:Rc(i||Pc)||Lc.EN).getModel("time"),y=g.get("month"),v=g.get("monthAbbr"),m=g.get("dayOfWeek"),x=g.get("dayOfWeekAbbr");return(e||"").replace(/{yyyy}/g,o+"").replace(/{yy}/g,o%100+"").replace(/{Q}/g,s+"").replace(/{MMMM}/g,y[a-1]).replace(/{MMM}/g,v[a-1]).replace(/{MM}/g,Uc(a,2)).replace(/{M}/g,a+"").replace(/{dd}/g,Uc(l,2)).replace(/{d}/g,l+"").replace(/{eeee}/g,m[u]).replace(/{ee}/g,x[u]).replace(/{e}/g,u+"").replace(/{HH}/g,Uc(h,2)).replace(/{H}/g,h+"").replace(/{hh}/g,Uc(c+"",2)).replace(/{h}/g,c+"").replace(/{mm}/g,Uc(p,2)).replace(/{m}/g,p+"").replace(/{ss}/g,Uc(d,2)).replace(/{s}/g,d+"").replace(/{SSS}/g,Uc(f,3)).replace(/{S}/g,f+"")}function qc(t,e){var n=io(t),i=n[Jc(e)]()+1,r=n[Qc(e)](),o=n[tp(e)](),a=n[ep(e)](),s=n[np(e)](),l=0===n[ip(e)](),u=l&&0===s,h=u&&0===a,c=h&&0===o,p=c&&1===r;return p&&1===i?"year":p?"month":c?"day":h?"hour":u?"minute":l?"second":"millisecond"}function Kc(t,e,n){var i=j(t)?io(t):t;switch(e=e||qc(t,n)){case"year":return i[$c(n)]();case"half-year":return i[Jc(n)]()>=6?1:0;case"quarter":return Math.floor((i[Jc(n)]()+1)/4);case"month":return i[Jc(n)]();case"day":return i[Qc(n)]();case"half-day":return i[tp(n)]()/24;case"hour":return i[tp(n)]();case"minute":return i[ep(n)]();case"second":return i[np(n)]();case"millisecond":return i[ip(n)]()}}function $c(t){return t?"getUTCFullYear":"getFullYear"}function Jc(t){return t?"getUTCMonth":"getMonth"}function Qc(t){return t?"getUTCDate":"getDate"}function tp(t){return t?"getUTCHours":"getHours"}function ep(t){return t?"getUTCMinutes":"getMinutes"}function np(t){return t?"getUTCSeconds":"getSeconds"}function ip(t){return t?"getUTCMilliseconds":"getMilliseconds"}function rp(t){return t?"setUTCFullYear":"setFullYear"}function op(t){return t?"setUTCMonth":"setMonth"}function ap(t){return t?"setUTCDate":"setDate"}function sp(t){return t?"setUTCHours":"setHours"}function lp(t){return t?"setUTCMinutes":"setMinutes"}function up(t){return t?"setUTCSeconds":"setSeconds"}function hp(t){return t?"setUTCMilliseconds":"setMilliseconds"}function cp(t){if(!ho(t))return X(t)?t:"-";var e=(t+"").split(".");return e[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,"$1,")+(e.length>1?"."+e[1]:"")}function pp(t,e){return t=(t||"").toLowerCase().replace(/-(.)/g,(function(t,e){return e.toUpperCase()})),e&&t&&(t=t.charAt(0).toUpperCase()+t.slice(1)),t}var dp=st;function fp(t,e,n){function i(t){return t&&ut(t)?t:"-"}function r(t){return!(null==t||isNaN(t)||!isFinite(t))}var o="time"===e,a=t instanceof Date;if(o||a){var s=o?io(t):t;if(!isNaN(+s))return jc(s,"{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}",n);if(a)return"-"}if("ordinal"===e)return Z(t)?i(t):j(t)&&r(t)?t+"":"-";var l=uo(t);return r(l)?cp(l):Z(t)?i(t):"boolean"==typeof t?t+"":"-"}var gp=["a","b","c","d","e","f","g"],yp=function(t,e){return"{"+t+(null==e?"":e)+"}"};function vp(t,e,n){Y(e)||(e=[e]);var i=e.length;if(!i)return"";for(var r=e[0].$vars||[],o=0;o':'':{renderMode:o,content:"{"+(n.markerId||"markerX")+"|} ",style:"subItem"===r?{width:4,height:4,borderRadius:2,backgroundColor:i}:{width:10,height:10,borderRadius:5,backgroundColor:i}}:""}function xp(t,e){return e=e||"transparent",X(t)?t:q(t)&&t.colorStops&&(t.colorStops[0]||{}).color||e}function _p(t,e){if("_blank"===e||"blank"===e){var n=window.open();n.opener=null,n.location.href=t}else window.open(t,e)}var bp=E,wp=["left","right","top","bottom","width","height"],Sp=[["width","left","right"],["height","top","bottom"]];function Mp(t,e,n,i,r){var o=0,a=0;null==i&&(i=1/0),null==r&&(r=1/0);var s=0;e.eachChild((function(l,u){var h,c,p=l.getBoundingRect(),d=e.childAt(u+1),f=d&&d.getBoundingRect();if("horizontal"===t){var g=p.width+(f?-f.x+p.x:0);(h=o+g)>i||l.newline?(o=0,h=g,a+=s+n,s=p.height):s=Math.max(s,p.height)}else{var y=p.height+(f?-f.y+p.y:0);(c=a+y)>r||l.newline?(o+=s+n,a=0,c=y,s=p.width):s=Math.max(s,p.width)}l.newline||(l.x=o,l.y=a,l.markRedraw(),"horizontal"===t?o=h+n:a=c+n)}))}var Ip=Mp;H(Mp,"vertical"),H(Mp,"horizontal");function Tp(t,e,n){n=dp(n||0);var i=e.width,r=e.height,o=Ur(t.left,i),a=Ur(t.top,r),s=Ur(t.right,i),l=Ur(t.bottom,r),u=Ur(t.width,i),h=Ur(t.height,r),c=n[2]+n[0],p=n[1]+n[3],d=t.aspect;switch(isNaN(u)&&(u=i-s-p-o),isNaN(h)&&(h=r-l-c-a),null!=d&&(isNaN(u)&&isNaN(h)&&(d>i/r?u=.8*i:h=.8*r),isNaN(u)&&(u=d*h),isNaN(h)&&(h=u/d)),isNaN(o)&&(o=i-s-u-p),isNaN(a)&&(a=r-l-h-c),t.left||t.right){case"center":o=i/2-u/2-n[3];break;case"right":o=i-u-p}switch(t.top||t.bottom){case"middle":case"center":a=r/2-h/2-n[0];break;case"bottom":a=r-h-c}o=o||0,a=a||0,isNaN(u)&&(u=i-p-o-(s||0)),isNaN(h)&&(h=r-c-a-(l||0));var f=new Ee(o+n[3],a+n[0],u,h);return f.margin=n,f}function Cp(t,e,n,i,r,o){var a,s=!r||!r.hv||r.hv[0],l=!r||!r.hv||r.hv[1],u=r&&r.boundingMode||"all";if((o=o||t).x=t.x,o.y=t.y,!s&&!l)return!1;if("raw"===u)a="group"===t.type?new Ee(0,0,+e.width||0,+e.height||0):t.getBoundingRect();else if(a=t.getBoundingRect(),t.needLocalTransform()){var h=t.getLocalTransform();(a=a.clone()).applyTransform(h)}var c=Tp(k({width:a.width,height:a.height},e),n,i),p=s?c.x-a.x:0,d=l?c.y-a.y:0;return"raw"===u?(o.x=p,o.y=d):(o.x+=p,o.y+=d),o===t&&t.markRedraw(),!0}function Dp(t){var e=t.layoutMode||t.constructor.layoutMode;return q(e)?e:e?{type:e}:null}function Ap(t,e,n){var i=n&&n.ignoreSize;!Y(i)&&(i=[i,i]);var r=a(Sp[0],0),o=a(Sp[1],1);function a(n,r){var o={},a=0,u={},h=0;if(bp(n,(function(e){u[e]=t[e]})),bp(n,(function(t){s(e,t)&&(o[t]=u[t]=e[t]),l(o,t)&&a++,l(u,t)&&h++})),i[r])return l(e,n[1])?u[n[2]]=null:l(e,n[2])&&(u[n[1]]=null),u;if(2!==h&&a){if(a>=2)return o;for(var c=0;c=0;a--)o=C(o,n[a],!0);e.defaultOption=o}return e.defaultOption},e.prototype.getReferringComponents=function(t,e){var n=t+"Index",i=t+"Id";return Vo(this.ecModel,t,{index:this.get(n,!0),id:this.get(i,!0)},e)},e.prototype.getBoxLayoutParams=function(){var t=this;return{left:t.get("left"),top:t.get("top"),right:t.get("right"),bottom:t.get("bottom"),width:t.get("width"),height:t.get("height")}},e.prototype.getZLevelKey=function(){return""},e.prototype.setZLevel=function(t){this.option.zlevel=t},e.protoInitialize=function(){var t=e.prototype;t.type="component",t.id="",t.name="",t.mainType="",t.subType="",t.componentIndex=0}(),e}(Sc);Xo(Op,Sc),Ko(Op),function(t){var e={};t.registerSubTypeDefaulter=function(t,n){var i=Yo(t);e[i.main]=n},t.determineSubType=function(n,i){var r=i.type;if(!r){var o=Yo(n).main;t.hasSubTypes(n)&&e[o]&&(r=e[o](i))}return r}}(Op),function(t,e){function n(t,e){return t[e]||(t[e]={predecessor:[],successor:[]}),t[e]}t.topologicalTravel=function(t,i,r,o){if(t.length){var a=function(t){var i={},r=[];return E(t,(function(o){var a=n(i,o),s=function(t,e){var n=[];return E(t,(function(t){P(e,t)>=0&&n.push(t)})),n}(a.originalDeps=e(o),t);a.entryCount=s.length,0===a.entryCount&&r.push(o),E(s,(function(t){P(a.predecessor,t)<0&&a.predecessor.push(t);var e=n(i,t);P(e.successor,t)<0&&e.successor.push(o)}))})),{graph:i,noEntryList:r}}(i),s=a.graph,l=a.noEntryList,u={};for(E(t,(function(t){u[t]=!0}));l.length;){var h=l.pop(),c=s[h],p=!!u[h];p&&(r.call(o,h,c.originalDeps.slice()),delete u[h]),E(c.successor,p?f:d)}E(u,(function(){var t="";throw new Error(t)}))}function d(t){s[t].entryCount--,0===s[t].entryCount&&l.push(t)}function f(t){u[t]=!0,d(t)}}}(Op,(function(t){var e=[];E(Op.getClassesByMainType(t),(function(t){e=e.concat(t.dependencies||t.prototype.dependencies||[])})),e=z(e,(function(t){return Yo(t).main})),"dataset"!==t&&P(e,"dataset")<=0&&e.unshift("dataset");return e}));var Rp="";"undefined"!=typeof navigator&&(Rp=navigator.platform||"");var Np="rgba(0, 0, 0, 0.2)",Ep={darkMode:"auto",colorBy:"series",color:["#5470c6","#91cc75","#fac858","#ee6666","#73c0de","#3ba272","#fc8452","#9a60b4","#ea7ccc"],gradientColor:["#f6efa6","#d88273","#bf444c"],aria:{decal:{decals:[{color:Np,dashArrayX:[1,0],dashArrayY:[2,5],symbolSize:1,rotation:Math.PI/6},{color:Np,symbol:"circle",dashArrayX:[[8,8],[0,8,8,0]],dashArrayY:[6,0],symbolSize:.8},{color:Np,dashArrayX:[1,0],dashArrayY:[4,3],rotation:-Math.PI/4},{color:Np,dashArrayX:[[6,6],[0,6,6,0]],dashArrayY:[6,0]},{color:Np,dashArrayX:[[1,0],[1,6]],dashArrayY:[1,0,6,0],rotation:Math.PI/4},{color:Np,symbol:"triangle",dashArrayX:[[9,9],[0,9,9,0]],dashArrayY:[7,2],symbolSize:.75}]}},textStyle:{fontFamily:Rp.match(/^Win/)?"Microsoft YaHei":"sans-serif",fontSize:12,fontStyle:"normal",fontWeight:"normal"},blendMode:null,stateAnimation:{duration:300,easing:"cubicOut"},animation:"auto",animationDuration:1e3,animationDurationUpdate:500,animationEasing:"cubicInOut",animationEasingUpdate:"cubicInOut",animationThreshold:2e3,progressiveThreshold:3e3,progressive:400,hoverLayerThreshold:3e3,useUTC:!1},zp=yt(["tooltip","label","itemName","itemId","itemGroupId","seriesName"]),Vp="original",Bp="arrayRows",Fp="objectRows",Gp="keyedColumns",Wp="typedArray",Hp="unknown",Yp="column",Up="row",Xp=1,Zp=2,jp=3,qp=Po();function Kp(t,e,n){var i={},r=Jp(e);if(!r||!t)return i;var o,a,s=[],l=[],u=e.ecModel,h=qp(u).datasetMap,c=r.uid+"_"+n.seriesLayoutBy;E(t=t.slice(),(function(e,n){var r=q(e)?e:t[n]={name:e};"ordinal"===r.type&&null==o&&(o=n,a=f(r)),i[r.name]=[]}));var p=h.get(c)||h.set(c,{categoryWayDim:a,valueWayDim:0});function d(t,e,n){for(var i=0;ie)return t[i];return t[n-1]}(i,a):n;if((h=h||n)&&h.length){var c=h[l];return r&&(u[r]=c),s.paletteIdx=(l+1)%h.length,c}}var hd=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.init=function(t,e,n,i,r,o){i=i||{},this.option=null,this._theme=new Sc(i),this._locale=new Sc(r),this._optionManager=o},e.prototype.setOption=function(t,e,n){var i=dd(e);this._optionManager.setOption(t,n,i),this._resetOption(null,i)},e.prototype.resetOption=function(t,e){return this._resetOption(t,dd(e))},e.prototype._resetOption=function(t,e){var n=!1,i=this._optionManager;if(!t||"recreate"===t){var r=i.mountOption("recreate"===t);0,this.option&&"recreate"!==t?(this.restoreData(),this._mergeOption(r,e)):rd(this,r),n=!0}if("timeline"!==t&&"media"!==t||this.restoreData(),!t||"recreate"===t||"timeline"===t){var o=i.getTimelineOption(this);o&&(n=!0,this._mergeOption(o,e))}if(!t||"recreate"===t||"media"===t){var a=i.getMediaOption(this);a.length&&E(a,(function(t){n=!0,this._mergeOption(t,e)}),this)}return n},e.prototype.mergeOption=function(t){this._mergeOption(t,null)},e.prototype._mergeOption=function(t,e){var n=this.option,i=this._componentsMap,r=this._componentsCount,o=[],a=yt(),s=e&&e.replaceMergeMainTypeMap;qp(this).datasetMap=yt(),E(t,(function(t,e){null!=t&&(Op.hasClass(e)?e&&(o.push(e),a.set(e,!0)):n[e]=null==n[e]?T(t):C(n[e],t,!0))})),s&&s.each((function(t,e){Op.hasClass(e)&&!a.get(e)&&(o.push(e),a.set(e,!0))})),Op.topologicalTravel(o,Op.getAllClassMainTypes(),(function(e){var o=function(t,e,n){var i=ed.get(e);if(!i)return n;var r=i(t);return r?n.concat(r):n}(this,e,_o(t[e])),a=i.get(e),l=a?s&&s.get(e)?"replaceMerge":"normalMerge":"replaceAll",u=Io(a,o,l);(function(t,e,n){E(t,(function(t){var i=t.newOption;q(i)&&(t.keyInfo.mainType=e,t.keyInfo.subType=function(t,e,n,i){return e.type?e.type:n?n.subType:i.determineSubType(t,e)}(e,i,t.existing,n))}))})(u,e,Op),n[e]=null,i.set(e,null),r.set(e,0);var h,c=[],p=[],d=0;E(u,(function(t,n){var i=t.existing,r=t.newOption;if(r){var o="series"===e,a=Op.getClass(e,t.keyInfo.subType,!o);if(!a)return;if("tooltip"===e){if(h)return void 0;h=!0}if(i&&i.constructor===a)i.name=t.keyInfo.name,i.mergeOption(r,this),i.optionUpdated(r,!1);else{var s=A({componentIndex:n},t.keyInfo);A(i=new a(r,this,this,s),s),t.brandNew&&(i.__requireNewView=!0),i.init(r,this,this),i.optionUpdated(null,!0)}}else i&&(i.mergeOption({},this),i.optionUpdated({},!1));i?(c.push(i.option),p.push(i),d++):(c.push(void 0),p.push(void 0))}),this),n[e]=c,i.set(e,p),r.set(e,d),"series"===e&&nd(this)}),this),this._seriesIndices||nd(this)},e.prototype.getOption=function(){var t=T(this.option);return E(t,(function(e,n){if(Op.hasClass(n)){for(var i=_o(e),r=i.length,o=!1,a=r-1;a>=0;a--)i[a]&&!ko(i[a])?o=!0:(i[a]=null,!o&&r--);i.length=r,t[n]=i}})),delete t["\0_ec_inner"],t},e.prototype.getTheme=function(){return this._theme},e.prototype.getLocaleModel=function(){return this._locale},e.prototype.setUpdatePayload=function(t){this._payload=t},e.prototype.getUpdatePayload=function(){return this._payload},e.prototype.getComponent=function(t,e){var n=this._componentsMap.get(t);if(n){var i=n[e||0];if(i)return i;if(null==e)for(var r=0;r=e:"max"===n?t<=e:t===e})(i[a],t,o)||(r=!1)}})),r}var bd=E,wd=q,Sd=["areaStyle","lineStyle","nodeStyle","linkStyle","chordStyle","label","labelLine"];function Md(t){var e=t&&t.itemStyle;if(e)for(var n=0,i=Sd.length;n=0;g--){var y=t[g];if(s||(p=y.data.rawIndexOf(y.stackedByDimension,c)),p>=0){var v=y.data.getByRawIndex(y.stackResultDimension,p);if("all"===l||"positive"===l&&v>0||"negative"===l&&v<0||"samesign"===l&&d>=0&&v>0||"samesign"===l&&d<=0&&v<0){d=Jr(d,v),f=v;break}}}return i[0]=d,i[1]=f,i}))}))}var Wd,Hd,Yd,Ud,Xd,Zd=function(t){this.data=t.data||(t.sourceFormat===Gp?{}:[]),this.sourceFormat=t.sourceFormat||Hp,this.seriesLayoutBy=t.seriesLayoutBy||Yp,this.startIndex=t.startIndex||0,this.dimensionsDetectedCount=t.dimensionsDetectedCount,this.metaRawOption=t.metaRawOption;var e=this.dimensionsDefine=t.dimensionsDefine;if(e)for(var n=0;nu&&(u=d)}s[0]=l,s[1]=u}},i=function(){return this._data?this._data.length/this._dimSize:0};function r(t){for(var e=0;e=0&&(s=o.interpolatedValue[l])}return null!=s?s+"":""})):void 0},t.prototype.getRawValue=function(t,e){return df(this.getData(e),t)},t.prototype.formatTooltip=function(t,e,n){},t}();function yf(t){var e,n;return q(t)?t.type&&(n=t):e=t,{text:e,frag:n}}function vf(t){return new mf(t)}var mf=function(){function t(t){t=t||{},this._reset=t.reset,this._plan=t.plan,this._count=t.count,this._onDirty=t.onDirty,this._dirty=!0}return t.prototype.perform=function(t){var e,n=this._upstream,i=t&&t.skip;if(this._dirty&&n){var r=this.context;r.data=r.outputData=n.context.outputData}this.__pipeline&&(this.__pipeline.currentTask=this),this._plan&&!i&&(e=this._plan(this.context));var o,a=h(this._modBy),s=this._modDataCount||0,l=h(t&&t.modBy),u=t&&t.modDataCount||0;function h(t){return!(t>=1)&&(t=1),t}a===l&&s===u||(e="reset"),(this._dirty||"reset"===e)&&(this._dirty=!1,o=this._doReset(i)),this._modBy=l,this._modDataCount=u;var c=t&&t.step;if(this._dueEnd=n?n._outputDueEnd:this._count?this._count(this.context):1/0,this._progress){var p=this._dueIndex,d=Math.min(null!=c?this._dueIndex+c:1/0,this._dueEnd);if(!i&&(o||p1&&i>0?s:a}};return o;function a(){return e=t?null:oe},gte:function(t,e){return t>=e}},Mf=function(){function t(t,e){if(!j(e)){var n="";0,yo(n)}this._opFn=Sf[t],this._rvalFloat=uo(e)}return t.prototype.evaluate=function(t){return j(t)?this._opFn(t,this._rvalFloat):this._opFn(uo(t),this._rvalFloat)},t}(),If=function(){function t(t,e){var n="desc"===t;this._resultLT=n?1:-1,null==e&&(e=n?"min":"max"),this._incomparable="min"===e?-1/0:1/0}return t.prototype.evaluate=function(t,e){var n=j(t)?t:uo(t),i=j(e)?e:uo(e),r=isNaN(n),o=isNaN(i);if(r&&(n=this._incomparable),o&&(i=this._incomparable),r&&o){var a=X(t),s=X(e);a&&(n=s?t:0),s&&(i=a?e:0)}return ni?-this._resultLT:0},t}(),Tf=function(){function t(t,e){this._rval=e,this._isEQ=t,this._rvalTypeof=typeof e,this._rvalFloat=uo(e)}return t.prototype.evaluate=function(t){var e=t===this._rval;if(!e){var n=typeof t;n===this._rvalTypeof||"number"!==n&&"number"!==this._rvalTypeof||(e=uo(t)===this._rvalFloat)}return this._isEQ?e:!e},t}();function Cf(t,e){return"eq"===t||"ne"===t?new Tf("eq"===t,e):_t(Sf,t)?new Mf(t,e):null}var Df=function(){function t(){}return t.prototype.getRawData=function(){throw new Error("not supported")},t.prototype.getRawDataItem=function(t){throw new Error("not supported")},t.prototype.cloneRawData=function(){},t.prototype.getDimensionInfo=function(t){},t.prototype.cloneAllDimensionInfo=function(){},t.prototype.count=function(){},t.prototype.retrieveValue=function(t,e){},t.prototype.retrieveValueFromItem=function(t,e){},t.prototype.convertValue=function(t,e){return _f(t,e)},t}();function Af(t){var e=t.sourceFormat;if(!Nf(e)){var n="";0,yo(n)}return t.data}function kf(t){var e=t.sourceFormat,n=t.data;if(!Nf(e)){var i="";0,yo(i)}if(e===Bp){for(var r=[],o=0,a=n.length;o65535?Vf:Bf}function Yf(t,e,n,i,r){var o=Wf[n||"float"];if(r){var a=t[e],s=a&&a.length;if(s!==i){for(var l=new o(i),u=0;ug[1]&&(g[1]=f)}return this._rawCount=this._count=s,{start:a,end:s}},t.prototype._initDataFromProvider=function(t,e,n){for(var i=this._provider,r=this._chunks,o=this._dimensions,a=o.length,s=this._rawExtent,l=z(o,(function(t){return t.property})),u=0;uy[1]&&(y[1]=g)}}!i.persistent&&i.clean&&i.clean(),this._rawCount=this._count=e,this._extent=[]},t.prototype.count=function(){return this._count},t.prototype.get=function(t,e){if(!(e>=0&&e=0&&e=this._rawCount||t<0)return-1;if(!this._indices)return t;var e=this._indices,n=e[t];if(null!=n&&nt))return o;r=o-1}}return-1},t.prototype.indicesOfNearest=function(t,e,n){var i=this._chunks[t],r=[];if(!i)return r;null==n&&(n=1/0);for(var o=1/0,a=-1,s=0,l=0,u=this.count();l=0&&a<0)&&(o=c,a=h,s=0),h===a&&(r[s++]=l))}return r.length=s,r},t.prototype.getIndices=function(){var t,e=this._indices;if(e){var n=e.constructor,i=this._count;if(n===Array){t=new n(i);for(var r=0;r=u&&x<=h||isNaN(x))&&(a[s++]=d),d++}p=!0}else if(2===r){f=c[i[0]];var y=c[i[1]],v=t[i[1]][0],m=t[i[1]][1];for(g=0;g=u&&x<=h||isNaN(x))&&(_>=v&&_<=m||isNaN(_))&&(a[s++]=d),d++}p=!0}}if(!p)if(1===r)for(g=0;g=u&&x<=h||isNaN(x))&&(a[s++]=b)}else for(g=0;gt[M][1])&&(w=!1)}w&&(a[s++]=e.getRawIndex(g))}return sy[1]&&(y[1]=g)}}}},t.prototype.lttbDownSample=function(t,e){var n,i,r,o=this.clone([t],!0),a=o._chunks[t],s=this.count(),l=0,u=Math.floor(1/e),h=this.getRawIndex(0),c=new(Hf(this._rawCount))(Math.min(2*(Math.ceil(s/u)+2),s));c[l++]=h;for(var p=1;pn&&(n=i,r=I)}M>0&&M<_-x&&(c[l++]=Math.min(S,r),r=Math.max(S,r)),c[l++]=r,h=r}return c[l++]=this.getRawIndex(s-1),o._count=l,o._indices=c,o.getRawIndex=this._getRawIdx,o},t.prototype.downSample=function(t,e,n,i){for(var r=this.clone([t],!0),o=r._chunks,a=[],s=Math.floor(1/e),l=o[t],u=this.count(),h=r._rawExtent[t]=[1/0,-1/0],c=new(Hf(this._rawCount))(Math.ceil(u/s)),p=0,d=0;du-d&&(s=u-d,a.length=s);for(var f=0;fh[1]&&(h[1]=y),c[p++]=v}return r._count=p,r._indices=c,r._updateGetRawIdx(),r},t.prototype.each=function(t,e){if(this._count)for(var n=t.length,i=this._chunks,r=0,o=this.count();ra&&(a=l)}return i=[o,a],this._extent[t]=i,i},t.prototype.getRawDataItem=function(t){var e=this.getRawIndex(t);if(this._provider.persistent)return this._provider.getItem(e);for(var n=[],i=this._chunks,r=0;r=0?this._indices[t]:-1},t.prototype._updateGetRawIdx=function(){this.getRawIndex=this._indices?this._getRawIdx:this._getRawIdxIdentity},t.internalField=function(){function t(t,e,n,i){return _f(t[i],this._dimensions[i])}Ef={arrayRows:t,objectRows:function(t,e,n,i){return _f(t[e],this._dimensions[i])},keyedColumns:t,original:function(t,e,n,i){var r=t&&(null==t.value?t:t.value);return _f(r instanceof Array?r[i]:r,this._dimensions[i])},typedArray:function(t,e,n,i){return t[i]}}}(),t}(),Xf=function(){function t(t){this._sourceList=[],this._storeList=[],this._upstreamSignList=[],this._versionSignBase=0,this._dirty=!0,this._sourceHost=t}return t.prototype.dirty=function(){this._setLocalSource([],[]),this._storeList=[],this._dirty=!0},t.prototype._setLocalSource=function(t,e){this._sourceList=t,this._upstreamSignList=e,this._versionSignBase++,this._versionSignBase>9e10&&(this._versionSignBase=0)},t.prototype._getVersionSign=function(){return this._sourceHost.uid+"_"+this._versionSignBase},t.prototype.prepareSource=function(){this._isDirty()&&(this._createSource(),this._dirty=!1)},t.prototype._createSource=function(){this._setLocalSource([],[]);var t,e,n=this._sourceHost,i=this._getUpstreamSourceManagers(),r=!!i.length;if(jf(n)){var o=n,a=void 0,s=void 0,l=void 0;if(r){var u=i[0];u.prepareSource(),a=(l=u.getSource()).data,s=l.sourceFormat,e=[u._getVersionSign()]}else s=$(a=o.get("data",!0))?Wp:Vp,e=[];var h=this._getSourceMetaRawOption()||{},c=l&&l.metaRawOption||{},p=rt(h.seriesLayoutBy,c.seriesLayoutBy)||null,d=rt(h.sourceHeader,c.sourceHeader),f=rt(h.dimensions,c.dimensions);t=p!==c.seriesLayoutBy||!!d!=!!c.sourceHeader||f?[qd(a,{seriesLayoutBy:p,sourceHeader:d,dimensions:f},s)]:[]}else{var g=n;if(r){var y=this._applyTransform(i);t=y.sourceList,e=y.upstreamSignList}else{t=[qd(g.get("source",!0),this._getSourceMetaRawOption(),null)],e=[]}}this._setLocalSource(t,e)},t.prototype._applyTransform=function(t){var e,n=this._sourceHost,i=n.get("transform",!0),r=n.get("fromTransformResult",!0);if(null!=r){var o="";1!==t.length&&qf(o)}var a,s=[],l=[];return E(t,(function(t){t.prepareSource();var e=t.getSource(r||0),n="";null==r||e||qf(n),s.push(e),l.push(t._getVersionSign())})),i?e=function(t,e,n){var i=_o(t),r=i.length,o="";r||yo(o);for(var a=0,s=r;a1||n>0&&!t.noHeader;return E(t.blocks,(function(t){var n=ng(t);n>=e&&(e=n+ +(i&&(!n||tg(t)&&!t.noHeader)))})),e}return 0}function ig(t,e,n,i){var r,o=e.noHeader,a=(r=ng(e),{html:$f[r],richText:Jf[r]}),s=[],l=e.blocks||[];lt(!l||Y(l)),l=l||[];var u=t.orderMode;if(e.sortBlocks&&u){l=l.slice();var h={valueAsc:"asc",valueDesc:"desc"};if(_t(h,u)){var c=new If(h[u],null);l.sort((function(t,e){return c.evaluate(t.sortParam,e.sortParam)}))}else"seriesDesc"===u&&l.reverse()}E(l,(function(n,r){var o=e.valueFormatter,l=eg(n)(o?A(A({},t),{valueFormatter:o}):t,n,r>0?a.html:0,i);null!=l&&s.push(l)}));var p="richText"===t.renderMode?s.join(a.richText):ag(s.join(""),o?n:a.html);if(o)return p;var d=fp(e.header,"ordinal",t.useUTC),f=Kf(i,t.renderMode).nameStyle;return"richText"===t.renderMode?sg(t,d,f)+a.richText+p:ag('
                                            '+ie(d)+"
                                            "+p,n)}function rg(t,e,n,i){var r=t.renderMode,o=e.noName,a=e.noValue,s=!e.markerType,l=e.name,u=t.useUTC,h=e.valueFormatter||t.valueFormatter||function(t){return z(t=Y(t)?t:[t],(function(t,e){return fp(t,Y(d)?d[e]:d,u)}))};if(!o||!a){var c=s?"":t.markupStyleCreator.makeTooltipMarker(e.markerType,e.markerColor||"#333",r),p=o?"":fp(l,"ordinal",u),d=e.valueType,f=a?[]:h(e.value),g=!s||!o,y=!s&&o,v=Kf(i,r),m=v.nameStyle,x=v.valueStyle;return"richText"===r?(s?"":c)+(o?"":sg(t,p,m))+(a?"":function(t,e,n,i,r){var o=[r],a=i?10:20;return n&&o.push({padding:[0,0,0,a],align:"right"}),t.markupStyleCreator.wrapRichTextStyle(Y(e)?e.join(" "):e,o)}(t,f,g,y,x)):ag((s?"":c)+(o?"":function(t,e,n){return''+ie(t)+""}(p,!s,m))+(a?"":function(t,e,n,i){var r=n?"10px":"20px",o=e?"float:right;margin-left:"+r:"";return t=Y(t)?t:[t],''+z(t,(function(t){return ie(t)})).join("  ")+""}(f,g,y,x)),n)}}function og(t,e,n,i,r,o){if(t)return eg(t)({useUTC:r,renderMode:n,orderMode:i,markupStyleCreator:e,valueFormatter:t.valueFormatter},t,0,o)}function ag(t,e){return'
                                            '+t+'
                                            '}function sg(t,e,n){return t.markupStyleCreator.wrapRichTextStyle(e,n)}function lg(t,e){return xp(t.getData().getItemVisual(e,"style")[t.visualDrawType])}function ug(t,e){var n=t.get("padding");return null!=n?n:"richText"===e?[8,10]:10}var hg=function(){function t(){this.richTextStyles={},this._nextStyleNameId=co()}return t.prototype._generateStyleName=function(){return"__EC_aUTo_"+this._nextStyleNameId++},t.prototype.makeTooltipMarker=function(t,e,n){var i="richText"===n?this._generateStyleName():null,r=mp({color:e,type:t,renderMode:n,markerId:i});return X(r)?r:(this.richTextStyles[i]=r.style,r.content)},t.prototype.wrapRichTextStyle=function(t,e){var n={};Y(e)?E(e,(function(t){return A(n,t)})):A(n,e);var i=this._generateStyleName();return this.richTextStyles[i]=n,"{"+i+"|"+t+"}"},t}();function cg(t){var e,n,i,r,o=t.series,a=t.dataIndex,s=t.multipleSeries,l=o.getData(),u=l.mapDimensionsAll("defaultedTooltip"),h=u.length,c=o.getRawValue(a),p=Y(c),d=lg(o,a);if(h>1||p&&!h){var f=function(t,e,n,i,r){var o=e.getData(),a=V(t,(function(t,e,n){var i=o.getDimensionInfo(n);return t||i&&!1!==i.tooltip&&null!=i.displayName}),!1),s=[],l=[],u=[];function h(t,e){var n=o.getDimensionInfo(e);n&&!1!==n.otherDims.tooltip&&(a?u.push(Qf("nameValue",{markerType:"subItem",markerColor:r,name:n.displayName,value:t,valueType:n.type})):(s.push(t),l.push(n.type)))}return i.length?E(i,(function(t){h(df(o,n,t),t)})):E(t,h),{inlineValues:s,inlineValueTypes:l,blocks:u}}(c,o,a,u,d);e=f.inlineValues,n=f.inlineValueTypes,i=f.blocks,r=f.inlineValues[0]}else if(h){var g=l.getDimensionInfo(u[0]);r=e=df(l,a,u[0]),n=g.type}else r=e=p?c[0]:c;var y=Ao(o),v=y&&o.name||"",m=l.getName(a),x=s?v:m;return Qf("section",{header:v,noHeader:s||!y,sortParam:r,blocks:[Qf("nameValue",{markerType:"item",markerColor:d,name:x,noName:!ut(x),value:e,valueType:n})].concat(i||[])})}var pg=Po();function dg(t,e){return t.getName(e)||t.getId(e)}var fg=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e._selectedDataIndicesMap={},e}return n(e,t),e.prototype.init=function(t,e,n){this.seriesIndex=this.componentIndex,this.dataTask=vf({count:yg,reset:vg}),this.dataTask.context={model:this},this.mergeDefaultAndTheme(t,n),(pg(this).sourceManager=new Xf(this)).prepareSource();var i=this.getInitialData(t,n);xg(i,this),this.dataTask.context.data=i,pg(this).dataBeforeProcessed=i,gg(this),this._initSelectedMapFromData(i)},e.prototype.mergeDefaultAndTheme=function(t,e){var n=Dp(this),i=n?kp(t):{},r=this.subType;Op.hasClass(r)&&(r+="Series"),C(t,e.getTheme().get(this.subType)),C(t,this.getDefaultOption()),bo(t,"label",["show"]),this.fillDataTextStyle(t.data),n&&Ap(t,i,n)},e.prototype.mergeOption=function(t,e){t=C(this.option,t,!0),this.fillDataTextStyle(t.data);var n=Dp(this);n&&Ap(this.option,t,n);var i=pg(this).sourceManager;i.dirty(),i.prepareSource();var r=this.getInitialData(t,e);xg(r,this),this.dataTask.dirty(),this.dataTask.context.data=r,pg(this).dataBeforeProcessed=r,gg(this),this._initSelectedMapFromData(r)},e.prototype.fillDataTextStyle=function(t){if(t&&!$(t))for(var e=["show"],n=0;nthis.getShallow("animationThreshold")&&(e=!1),!!e},e.prototype.restoreData=function(){this.dataTask.dirty()},e.prototype.getColorFromPalette=function(t,e,n){var i=this.ecModel,r=sd.prototype.getColorFromPalette.call(this,t,e,n);return r||(r=i.getColorFromPalette(t,e,n)),r},e.prototype.coordDimToDataDim=function(t){return this.getRawData().mapDimensionsAll(t)},e.prototype.getProgressive=function(){return this.get("progressive")},e.prototype.getProgressiveThreshold=function(){return this.get("progressiveThreshold")},e.prototype.select=function(t,e){this._innerSelect(this.getData(e),t)},e.prototype.unselect=function(t,e){var n=this.option.selectedMap;if(n){var i=this.option.selectedMode,r=this.getData(e);if("series"===i||"all"===n)return this.option.selectedMap={},void(this._selectedDataIndicesMap={});for(var o=0;o=0&&n.push(r)}return n},e.prototype.isSelected=function(t,e){var n=this.option.selectedMap;if(!n)return!1;var i=this.getData(e);return("all"===n||n[dg(i,t)])&&!i.getItemModel(t).get(["select","disabled"])},e.prototype.isUniversalTransitionEnabled=function(){if(this.__universalTransitionEnabled)return!0;var t=this.option.universalTransition;return!!t&&(!0===t||t&&t.enabled)},e.prototype._innerSelect=function(t,e){var n,i,r=this.option,o=r.selectedMode,a=e.length;if(o&&a)if("series"===o)r.selectedMap="all";else if("multiple"===o){q(r.selectedMap)||(r.selectedMap={});for(var s=r.selectedMap,l=0;l0&&this._innerSelect(t,e)}},e.registerClass=function(t){return Op.registerClass(t)},e.protoInitialize=function(){var t=e.prototype;t.type="series.__base__",t.seriesIndex=0,t.ignoreStyleOnData=!1,t.hasSymbolVisual=!1,t.defaultSymbol="circle",t.visualStyleAccessPath="itemStyle",t.visualDrawType="fill"}(),e}(Op);function gg(t){var e=t.name;Ao(t)||(t.name=function(t){var e=t.getRawData(),n=e.mapDimensionsAll("seriesName"),i=[];return E(n,(function(t){var n=e.getDimensionInfo(t);n.displayName&&i.push(n.displayName)})),i.join(" ")}(t)||e)}function yg(t){return t.model.getRawData().count()}function vg(t){var e=t.model;return e.setData(e.getRawData().cloneShallow()),mg}function mg(t,e){e.outputData&&t.end>e.outputData.count()&&e.model.getRawData().cloneShallow(e.outputData)}function xg(t,e){E(vt(t.CHANGABLE_METHODS,t.DOWNSAMPLE_METHODS),(function(n){t.wrapMethod(n,H(_g,e))}))}function _g(t,e){var n=bg(t);return n&&n.setOutputEnd((e||this).count()),e}function bg(t){var e=(t.ecModel||{}).scheduler,n=e&&e.getPipeline(t.uid);if(n){var i=n.currentTask;if(i){var r=i.agentStubMap;r&&(i=r.get(t.uid))}return i}}R(fg,gf),R(fg,sd),Xo(fg,Op);var wg=function(){function t(){this.group=new Er,this.uid=Ic("viewComponent")}return t.prototype.init=function(t,e){},t.prototype.render=function(t,e,n,i){},t.prototype.dispose=function(t,e){},t.prototype.updateView=function(t,e,n,i){},t.prototype.updateLayout=function(t,e,n,i){},t.prototype.updateVisual=function(t,e,n,i){},t.prototype.toggleBlurSeries=function(t,e,n){},t.prototype.eachRendered=function(t){var e=this.group;e&&e.traverse(t)},t}();function Sg(){var t=Po();return function(e){var n=t(e),i=e.pipelineContext,r=!!n.large,o=!!n.progressiveRender,a=n.large=!(!i||!i.large),s=n.progressiveRender=!(!i||!i.progressiveRender);return!(r===a&&o===s)&&"reset"}}Uo(wg),Ko(wg);var Mg=Po(),Ig=Sg(),Tg=function(){function t(){this.group=new Er,this.uid=Ic("viewChart"),this.renderTask=vf({plan:Ag,reset:kg}),this.renderTask.context={view:this}}return t.prototype.init=function(t,e){},t.prototype.render=function(t,e,n,i){0},t.prototype.highlight=function(t,e,n,i){var r=t.getData(i&&i.dataType);r&&Dg(r,i,"emphasis")},t.prototype.downplay=function(t,e,n,i){var r=t.getData(i&&i.dataType);r&&Dg(r,i,"normal")},t.prototype.remove=function(t,e){this.group.removeAll()},t.prototype.dispose=function(t,e){},t.prototype.updateView=function(t,e,n,i){this.render(t,e,n,i)},t.prototype.updateLayout=function(t,e,n,i){this.render(t,e,n,i)},t.prototype.updateVisual=function(t,e,n,i){this.render(t,e,n,i)},t.prototype.eachRendered=function(t){jh(this.group,t)},t.markUpdateMethod=function(t,e){Mg(t).updateMethod=e},t.protoInitialize=void(t.prototype.type="chart"),t}();function Cg(t,e,n){t&&ql(t)&&("emphasis"===e?Al:kl)(t,n)}function Dg(t,e,n){var i=Lo(t,e),r=e&&null!=e.highlightKey?function(t){var e=el[t];return null==e&&tl<=32&&(e=el[t]=tl++),e}(e.highlightKey):null;null!=i?E(_o(i),(function(e){Cg(t.getItemGraphicEl(e),n,r)})):t.eachItemGraphicEl((function(t){Cg(t,n,r)}))}function Ag(t){return Ig(t.model)}function kg(t){var e=t.model,n=t.ecModel,i=t.api,r=t.payload,o=e.pipelineContext.progressiveRender,a=t.view,s=r&&Mg(r).updateMethod,l=o?"incrementalPrepareRender":s&&a[s]?s:"render";return"render"!==l&&a[l](e,n,i,r),Lg[l]}Uo(Tg),Ko(Tg);var Lg={incrementalPrepareRender:{progress:function(t,e){e.view.incrementalRender(t,e.model,e.ecModel,e.api,e.payload)}},render:{forceFirstProgress:!0,progress:function(t,e){e.view.render(e.model,e.ecModel,e.api,e.payload)}}},Pg="\0__throttleOriginMethod",Og="\0__throttleRate",Rg="\0__throttleType";function Ng(t,e,n){var i,r,o,a,s,l=0,u=0,h=null;function c(){u=(new Date).getTime(),h=null,t.apply(o,a||[])}e=e||0;var p=function(){for(var t=[],p=0;p=0?c():h=setTimeout(c,-r),l=i};return p.clear=function(){h&&(clearTimeout(h),h=null)},p.debounceNextCall=function(t){s=t},p}function Eg(t,e,n,i){var r=t[e];if(r){var o=r[Pg]||r,a=r[Rg];if(r[Og]!==n||a!==i){if(null==n||!i)return t[e]=o;(r=t[e]=Ng(o,n,"debounce"===i))[Pg]=o,r[Rg]=i,r[Og]=n}return r}}function zg(t,e){var n=t[e];n&&n[Pg]&&(n.clear&&n.clear(),t[e]=n[Pg])}var Vg=Po(),Bg={itemStyle:$o(_c,!0),lineStyle:$o(vc,!0)},Fg={lineStyle:"stroke",itemStyle:"fill"};function Gg(t,e){var n=t.visualStyleMapper||Bg[e];return n||(console.warn("Unknown style type '"+e+"'."),Bg.itemStyle)}function Wg(t,e){var n=t.visualDrawType||Fg[e];return n||(console.warn("Unknown style type '"+e+"'."),"fill")}var Hg={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){var n=t.getData(),i=t.visualStyleAccessPath||"itemStyle",r=t.getModel(i),o=Gg(t,i)(r),a=r.getShallow("decal");a&&(n.setVisual("decal",a),a.dirty=!0);var s=Wg(t,i),l=o[s],u=U(l)?l:null,h="auto"===o.fill||"auto"===o.stroke;if(!o[s]||u||h){var c=t.getColorFromPalette(t.name,null,e.getSeriesCount());o[s]||(o[s]=c,n.setVisual("colorFromPalette",!0)),o.fill="auto"===o.fill||U(o.fill)?c:o.fill,o.stroke="auto"===o.stroke||U(o.stroke)?c:o.stroke}if(n.setVisual("style",o),n.setVisual("drawType",s),!e.isSeriesFiltered(t)&&u)return n.setVisual("colorFromPalette",!1),{dataEach:function(e,n){var i=t.getDataParams(n),r=A({},o);r[s]=u(i),e.setItemVisual(n,"style",r)}}}},Yg=new Sc,Ug={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){if(!t.ignoreStyleOnData&&!e.isSeriesFiltered(t)){var n=t.getData(),i=t.visualStyleAccessPath||"itemStyle",r=Gg(t,i),o=n.getVisual("drawType");return{dataEach:n.hasItemOption?function(t,e){var n=t.getRawDataItem(e);if(n&&n[i]){Yg.option=n[i];var a=r(Yg);A(t.ensureUniqueItemVisual(e,"style"),a),Yg.option.decal&&(t.setItemVisual(e,"decal",Yg.option.decal),Yg.option.decal.dirty=!0),o in a&&t.setItemVisual(e,"colorFromPalette",!1)}}:null}}}},Xg={performRawSeries:!0,overallReset:function(t){var e=yt();t.eachSeries((function(t){var n=t.getColorBy();if(!t.isColorBySeries()){var i=t.type+"-"+n,r=e.get(i);r||(r={},e.set(i,r)),Vg(t).scope=r}})),t.eachSeries((function(e){if(!e.isColorBySeries()&&!t.isSeriesFiltered(e)){var n=e.getRawData(),i={},r=e.getData(),o=Vg(e).scope,a=e.visualStyleAccessPath||"itemStyle",s=Wg(e,a);r.each((function(t){var e=r.getRawIndex(t);i[e]=t})),n.each((function(t){var a=i[t];if(r.getItemVisual(a,"colorFromPalette")){var l=r.ensureUniqueItemVisual(a,"style"),u=n.getName(t)||t+"",h=n.count();l[s]=e.getColorFromPalette(u,o,h)}}))}}))}},Zg=Math.PI;var jg=function(){function t(t,e,n,i){this._stageTaskMap=yt(),this.ecInstance=t,this.api=e,n=this._dataProcessorHandlers=n.slice(),i=this._visualHandlers=i.slice(),this._allHandlers=n.concat(i)}return t.prototype.restoreData=function(t,e){t.restoreData(e),this._stageTaskMap.each((function(t){var e=t.overallTask;e&&e.dirty()}))},t.prototype.getPerformArgs=function(t,e){if(t.__pipeline){var n=this._pipelineMap.get(t.__pipeline.id),i=n.context,r=!e&&n.progressiveEnabled&&(!i||i.progressiveRender)&&t.__idxInPipeline>n.blockIndex?n.step:null,o=i&&i.modDataCount;return{step:r,modBy:null!=o?Math.ceil(o/r):null,modDataCount:o}}},t.prototype.getPipeline=function(t){return this._pipelineMap.get(t)},t.prototype.updateStreamModes=function(t,e){var n=this._pipelineMap.get(t.uid),i=t.getData().count(),r=n.progressiveEnabled&&e.incrementalPrepareRender&&i>=n.threshold,o=t.get("large")&&i>=t.get("largeThreshold"),a="mod"===t.get("progressiveChunkMode")?i:null;t.pipelineContext=n.context={progressiveRender:r,modDataCount:a,large:o}},t.prototype.restorePipelines=function(t){var e=this,n=e._pipelineMap=yt();t.eachSeries((function(t){var i=t.getProgressive(),r=t.uid;n.set(r,{id:r,head:null,tail:null,threshold:t.getProgressiveThreshold(),progressiveEnabled:i&&!(t.preventIncremental&&t.preventIncremental()),blockIndex:-1,step:Math.round(i||700),count:0}),e._pipe(t,t.dataTask)}))},t.prototype.prepareStageTasks=function(){var t=this._stageTaskMap,e=this.api.getModel(),n=this.api;E(this._allHandlers,(function(i){var r=t.get(i.uid)||t.set(i.uid,{}),o="";lt(!(i.reset&&i.overallReset),o),i.reset&&this._createSeriesStageTask(i,r,e,n),i.overallReset&&this._createOverallStageTask(i,r,e,n)}),this)},t.prototype.prepareView=function(t,e,n,i){var r=t.renderTask,o=r.context;o.model=e,o.ecModel=n,o.api=i,r.__block=!t.incrementalPrepareRender,this._pipe(e,r)},t.prototype.performDataProcessorTasks=function(t,e){this._performStageTasks(this._dataProcessorHandlers,t,e,{block:!0})},t.prototype.performVisualTasks=function(t,e,n){this._performStageTasks(this._visualHandlers,t,e,n)},t.prototype._performStageTasks=function(t,e,n,i){i=i||{};var r=!1,o=this;function a(t,e){return t.setDirty&&(!t.dirtyMap||t.dirtyMap.get(e.__pipeline.id))}E(t,(function(t,s){if(!i.visualType||i.visualType===t.visualType){var l=o._stageTaskMap.get(t.uid),u=l.seriesTaskMap,h=l.overallTask;if(h){var c,p=h.agentStubMap;p.each((function(t){a(i,t)&&(t.dirty(),c=!0)})),c&&h.dirty(),o.updatePayload(h,n);var d=o.getPerformArgs(h,i.block);p.each((function(t){t.perform(d)})),h.perform(d)&&(r=!0)}else u&&u.each((function(s,l){a(i,s)&&s.dirty();var u=o.getPerformArgs(s,i.block);u.skip=!t.performRawSeries&&e.isSeriesFiltered(s.context.model),o.updatePayload(s,n),s.perform(u)&&(r=!0)}))}})),this.unfinished=r||this.unfinished},t.prototype.performSeriesTasks=function(t){var e;t.eachSeries((function(t){e=t.dataTask.perform()||e})),this.unfinished=e||this.unfinished},t.prototype.plan=function(){this._pipelineMap.each((function(t){var e=t.tail;do{if(e.__block){t.blockIndex=e.__idxInPipeline;break}e=e.getUpstream()}while(e)}))},t.prototype.updatePayload=function(t,e){"remain"!==e&&(t.context.payload=e)},t.prototype._createSeriesStageTask=function(t,e,n,i){var r=this,o=e.seriesTaskMap,a=e.seriesTaskMap=yt(),s=t.seriesType,l=t.getTargetSeries;function u(e){var s=e.uid,l=a.set(s,o&&o.get(s)||vf({plan:Qg,reset:ty,count:iy}));l.context={model:e,ecModel:n,api:i,useClearVisual:t.isVisual&&!t.isLayout,plan:t.plan,reset:t.reset,scheduler:r},r._pipe(e,l)}t.createOnAllSeries?n.eachRawSeries(u):s?n.eachRawSeriesByType(s,u):l&&l(n,i).each(u)},t.prototype._createOverallStageTask=function(t,e,n,i){var r=this,o=e.overallTask=e.overallTask||vf({reset:qg});o.context={ecModel:n,api:i,overallReset:t.overallReset,scheduler:r};var a=o.agentStubMap,s=o.agentStubMap=yt(),l=t.seriesType,u=t.getTargetSeries,h=!0,c=!1,p="";function d(t){var e=t.uid,n=s.set(e,a&&a.get(e)||(c=!0,vf({reset:Kg,onDirty:Jg})));n.context={model:t,overallProgress:h},n.agent=o,n.__block=h,r._pipe(t,n)}lt(!t.createOnAllSeries,p),l?n.eachRawSeriesByType(l,d):u?u(n,i).each(d):(h=!1,E(n.getSeries(),d)),c&&o.dirty()},t.prototype._pipe=function(t,e){var n=t.uid,i=this._pipelineMap.get(n);!i.head&&(i.head=e),i.tail&&i.tail.pipe(e),i.tail=e,e.__idxInPipeline=i.count++,e.__pipeline=i},t.wrapStageHandler=function(t,e){return U(t)&&(t={overallReset:t,seriesType:ry(t)}),t.uid=Ic("stageHandler"),e&&(t.visualType=e),t},t}();function qg(t){t.overallReset(t.ecModel,t.api,t.payload)}function Kg(t){return t.overallProgress&&$g}function $g(){this.agent.dirty(),this.getDownstream().dirty()}function Jg(){this.agent&&this.agent.dirty()}function Qg(t){return t.plan?t.plan(t.model,t.ecModel,t.api,t.payload):null}function ty(t){t.useClearVisual&&t.data.clearAllVisual();var e=t.resetDefines=_o(t.reset(t.model,t.ecModel,t.api,t.payload));return e.length>1?z(e,(function(t,e){return ny(e)})):ey}var ey=ny(0);function ny(t){return function(e,n){var i=n.data,r=n.resetDefines[t];if(r&&r.dataEach)for(var o=e.start;o0&&h===r.length-u.length){var c=r.slice(0,h);"data"!==c&&(e.mainType=c,e[u.toLowerCase()]=t,s=!0)}}a.hasOwnProperty(r)&&(n[r]=t,s=!0),s||(i[r]=t)}))}return{cptQuery:e,dataQuery:n,otherQuery:i}},t.prototype.filter=function(t,e){var n=this.eventInfo;if(!n)return!0;var i=n.targetEl,r=n.packedEvent,o=n.model,a=n.view;if(!o||!a)return!0;var s=e.cptQuery,l=e.dataQuery;return u(s,o,"mainType")&&u(s,o,"subType")&&u(s,o,"index","componentIndex")&&u(s,o,"name")&&u(s,o,"id")&&u(l,r,"name")&&u(l,r,"dataIndex")&&u(l,r,"dataType")&&(!a.filterForExposedEvent||a.filterForExposedEvent(t,e.otherQuery,i,r));function u(t,e,n,i){return null==t[n]||e[i||n]===t[n]}},t.prototype.afterTrigger=function(){this.eventInfo=null},t}(),vy=["symbol","symbolSize","symbolRotate","symbolOffset"],my=vy.concat(["symbolKeepAspect"]),xy={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){var n=t.getData();if(t.legendIcon&&n.setVisual("legendIcon",t.legendIcon),t.hasSymbolVisual){for(var i={},r={},o=!1,a=0;a=0&&Gy(l)?l:.5,t.createRadialGradient(a,s,0,a,s,l)}(t,e,n):function(t,e,n){var i=null==e.x?0:e.x,r=null==e.x2?1:e.x2,o=null==e.y?0:e.y,a=null==e.y2?0:e.y2;return e.global||(i=i*n.width+n.x,r=r*n.width+n.x,o=o*n.height+n.y,a=a*n.height+n.y),i=Gy(i)?i:0,r=Gy(r)?r:1,o=Gy(o)?o:0,a=Gy(a)?a:0,t.createLinearGradient(i,o,r,a)}(t,e,n),r=e.colorStops,o=0;o0&&(e=i.lineDash,n=i.lineWidth,e&&"solid"!==e&&n>0?"dashed"===e?[4*n,2*n]:"dotted"===e?[n]:j(e)?[e]:Y(e)?e:null:null),o=i.lineDashOffset;if(r){var a=i.strokeNoScale&&t.getLineScale?t.getLineScale():1;a&&1!==a&&(r=z(r,(function(t){return t/a})),o/=a)}return[r,o]}var Xy=new rs(!0);function Zy(t){var e=t.stroke;return!(null==e||"none"===e||!(t.lineWidth>0))}function jy(t){return"string"==typeof t&&"none"!==t}function qy(t){var e=t.fill;return null!=e&&"none"!==e}function Ky(t,e){if(null!=e.fillOpacity&&1!==e.fillOpacity){var n=t.globalAlpha;t.globalAlpha=e.fillOpacity*e.opacity,t.fill(),t.globalAlpha=n}else t.fill()}function $y(t,e){if(null!=e.strokeOpacity&&1!==e.strokeOpacity){var n=t.globalAlpha;t.globalAlpha=e.strokeOpacity*e.opacity,t.stroke(),t.globalAlpha=n}else t.stroke()}function Jy(t,e,n){var i=na(e.image,e.__image,n);if(ra(i)){var r=t.createPattern(i,e.repeat||"repeat");if("function"==typeof DOMMatrix&&r&&r.setTransform){var o=new DOMMatrix;o.translateSelf(e.x||0,e.y||0),o.rotateSelf(0,0,(e.rotation||0)*wt),o.scaleSelf(e.scaleX||1,e.scaleY||1),r.setTransform(o)}return r}}var Qy=["shadowBlur","shadowOffsetX","shadowOffsetY"],tv=[["lineCap","butt"],["lineJoin","miter"],["miterLimit",10]];function ev(t,e,n,i,r){var o=!1;if(!i&&e===(n=n||{}))return!1;if(i||e.opacity!==n.opacity){rv(t,r),o=!0;var a=Math.max(Math.min(e.opacity,1),0);t.globalAlpha=isNaN(a)?ma.opacity:a}(i||e.blend!==n.blend)&&(o||(rv(t,r),o=!0),t.globalCompositeOperation=e.blend||ma.blend);for(var s=0;s0&&t.unfinished);t.unfinished||this._zr.flush()}}},e.prototype.getDom=function(){return this._dom},e.prototype.getId=function(){return this.id},e.prototype.getZr=function(){return this._zr},e.prototype.isSSR=function(){return this._ssr},e.prototype.setOption=function(t,e,n){if(!this.__flagInMainProcess)if(this._disposed)qv(this.id);else{var i,r,o;if(q(e)&&(n=e.lazyUpdate,i=e.silent,r=e.replaceMerge,o=e.transition,e=e.notMerge),this.__flagInMainProcess=!0,!this._model||e){var a=new xd(this._api),s=this._theme,l=this._model=new hd;l.scheduler=this._scheduler,l.ssr=this._ssr,l.init(null,null,null,s,this._locale,a)}this._model.setOption(t,{replaceMerge:r},Qv);var u={seriesTransition:o,optionChanged:!0};if(n)this.__pendingUpdate={silent:i,updateParams:u},this.__flagInMainProcess=!1,this.getZr().wakeUp();else{try{Tv(this),Av.update.call(this,null,u)}catch(t){throw this.__pendingUpdate=null,this.__flagInMainProcess=!1,t}this._ssr||this._zr.flush(),this.__pendingUpdate=null,this.__flagInMainProcess=!1,Ov.call(this,i),Rv.call(this,i)}}},e.prototype.setTheme=function(){go()},e.prototype.getModel=function(){return this._model},e.prototype.getOption=function(){return this._model&&this._model.getOption()},e.prototype.getWidth=function(){return this._zr.getWidth()},e.prototype.getHeight=function(){return this._zr.getHeight()},e.prototype.getDevicePixelRatio=function(){return this._zr.painter.dpr||r.hasGlobalWindow&&window.devicePixelRatio||1},e.prototype.getRenderedCanvas=function(t){return this.renderToCanvas(t)},e.prototype.renderToCanvas=function(t){t=t||{};var e=this._zr.painter;return e.getRenderedCanvas({backgroundColor:t.backgroundColor||this._model.get("backgroundColor"),pixelRatio:t.pixelRatio||this.getDevicePixelRatio()})},e.prototype.renderToSVGString=function(t){t=t||{};var e=this._zr.painter;return e.renderToString({useViewBox:t.useViewBox})},e.prototype.getSvgDataURL=function(){if(r.svgSupported){var t=this._zr;return E(t.storage.getDisplayList(),(function(t){t.stopAnimation(null,!0)})),t.painter.toDataURL()}},e.prototype.getDataURL=function(t){if(!this._disposed){var e=(t=t||{}).excludeComponents,n=this._model,i=[],r=this;E(e,(function(t){n.eachComponent({mainType:t},(function(t){var e=r._componentsMap[t.__viewId];e.group.ignore||(i.push(e),e.group.ignore=!0)}))}));var o="svg"===this._zr.painter.getType()?this.getSvgDataURL():this.renderToCanvas(t).toDataURL("image/"+(t&&t.type||"png"));return E(i,(function(t){t.group.ignore=!1})),o}qv(this.id)},e.prototype.getConnectedDataURL=function(t){if(!this._disposed){var e="svg"===t.type,n=this.group,i=Math.min,r=Math.max,o=1/0;if(rm[n]){var a=o,s=o,l=-1/0,u=-1/0,c=[],p=t&&t.pixelRatio||this.getDevicePixelRatio();E(im,(function(o,h){if(o.group===n){var p=e?o.getZr().painter.getSvgDom().innerHTML:o.renderToCanvas(T(t)),d=o.getDom().getBoundingClientRect();a=i(d.left,a),s=i(d.top,s),l=r(d.right,l),u=r(d.bottom,u),c.push({dom:p,left:d.left,top:d.top})}}));var d=(l*=p)-(a*=p),f=(u*=p)-(s*=p),g=h.createCanvas(),y=Fr(g,{renderer:e?"svg":"canvas"});if(y.resize({width:d,height:f}),e){var v="";return E(c,(function(t){var e=t.left-a,n=t.top-s;v+=''+t.dom+""})),y.painter.getSvgRoot().innerHTML=v,t.connectedBackgroundColor&&y.painter.setBackgroundColor(t.connectedBackgroundColor),y.refreshImmediately(),y.painter.toDataURL()}return t.connectedBackgroundColor&&y.add(new Es({shape:{x:0,y:0,width:d,height:f},style:{fill:t.connectedBackgroundColor}})),E(c,(function(t){var e=new As({style:{x:t.left*p-a,y:t.top*p-s,image:t.dom}});y.add(e)})),y.refreshImmediately(),g.toDataURL("image/"+(t&&t.type||"png"))}return this.getDataURL(t)}qv(this.id)},e.prototype.convertToPixel=function(t,e){return kv(this,"convertToPixel",t,e)},e.prototype.convertFromPixel=function(t,e){return kv(this,"convertFromPixel",t,e)},e.prototype.containPixel=function(t,e){var n;if(!this._disposed)return E(Ro(this._model,t),(function(t,i){i.indexOf("Models")>=0&&E(t,(function(t){var r=t.coordinateSystem;if(r&&r.containPoint)n=n||!!r.containPoint(e);else if("seriesModels"===i){var o=this._chartsMap[t.__viewId];o&&o.containPoint&&(n=n||o.containPoint(e,t))}else 0}),this)}),this),!!n;qv(this.id)},e.prototype.getVisual=function(t,e){var n=Ro(this._model,t,{defaultMainType:"series"}),i=n.seriesModel;var r=i.getData(),o=n.hasOwnProperty("dataIndexInside")?n.dataIndexInside:n.hasOwnProperty("dataIndex")?r.indexOfRawIndex(n.dataIndex):null;return null!=o?by(r,o,e):wy(r,e)},e.prototype.getViewOfComponentModel=function(t){return this._componentsMap[t.__viewId]},e.prototype.getViewOfSeriesModel=function(t){return this._chartsMap[t.__viewId]},e.prototype._initEvents=function(){var t,e,n,i=this;E(jv,(function(t){var e=function(e){var n,r=i.getModel(),o=e.target,a="globalout"===t;if(a?n={}:o&&Ty(o,(function(t){var e=Js(t);if(e&&null!=e.dataIndex){var i=e.dataModel||r.getSeriesByIndex(e.seriesIndex);return n=i&&i.getDataParams(e.dataIndex,e.dataType)||{},!0}if(e.eventData)return n=A({},e.eventData),!0}),!0),n){var s=n.componentType,l=n.componentIndex;"markLine"!==s&&"markPoint"!==s&&"markArea"!==s||(s="series",l=n.seriesIndex);var u=s&&null!=l&&r.getComponent(s,l),h=u&&i["series"===u.mainType?"_chartsMap":"_componentsMap"][u.__viewId];0,n.event=e,n.type=t,i._$eventProcessor.eventInfo={targetEl:o,packedEvent:n,model:u,view:h},i.trigger(t,n)}};e.zrEventfulCallAtLast=!0,i._zr.on(t,e,i)})),E($v,(function(t,e){i._messageCenter.on(e,(function(t){this.trigger(e,t)}),i)})),E(["selectchanged"],(function(t){i._messageCenter.on(t,(function(e){this.trigger(t,e)}),i)})),t=this._messageCenter,e=this,n=this._api,t.on("selectchanged",(function(t){var i=n.getModel();t.isFromClick?(Iy("map","selectchanged",e,i,t),Iy("pie","selectchanged",e,i,t)):"select"===t.fromAction?(Iy("map","selected",e,i,t),Iy("pie","selected",e,i,t)):"unselect"===t.fromAction&&(Iy("map","unselected",e,i,t),Iy("pie","unselected",e,i,t))}))},e.prototype.isDisposed=function(){return this._disposed},e.prototype.clear=function(){this._disposed?qv(this.id):this.setOption({series:[]},!0)},e.prototype.dispose=function(){if(this._disposed)qv(this.id);else{this._disposed=!0,this.getDom()&&Bo(this.getDom(),sm,"");var t=this,e=t._api,n=t._model;E(t._componentsViews,(function(t){t.dispose(n,e)})),E(t._chartsViews,(function(t){t.dispose(n,e)})),t._zr.dispose(),t._dom=t._model=t._chartsMap=t._componentsMap=t._chartsViews=t._componentsViews=t._scheduler=t._api=t._zr=t._throttledZrFlush=t._theme=t._coordSysMgr=t._messageCenter=null,delete im[t.id]}},e.prototype.resize=function(t){if(!this.__flagInMainProcess)if(this._disposed)qv(this.id);else{this._zr.resize(t);var e=this._model;if(this._loadingFX&&this._loadingFX.resize(),e){var n=e.resetOption("media"),i=t&&t.silent;this.__pendingUpdate&&(null==i&&(i=this.__pendingUpdate.silent),n=!0,this.__pendingUpdate=null),this.__flagInMainProcess=!0;try{n&&Tv(this),Av.update.call(this,{type:"resize",animation:A({duration:0},t&&t.animation)})}catch(t){throw this.__flagInMainProcess=!1,t}this.__flagInMainProcess=!1,Ov.call(this,i),Rv.call(this,i)}}},e.prototype.showLoading=function(t,e){if(this._disposed)qv(this.id);else if(q(t)&&(e=t,t=""),t=t||"default",this.hideLoading(),nm[t]){var n=nm[t](this._api,e),i=this._zr;this._loadingFX=n,i.add(n)}},e.prototype.hideLoading=function(){this._disposed?qv(this.id):(this._loadingFX&&this._zr.remove(this._loadingFX),this._loadingFX=null)},e.prototype.makeActionFromEvent=function(t){var e=A({},t);return e.type=$v[t.type],e},e.prototype.dispatchAction=function(t,e){if(this._disposed)qv(this.id);else if(q(e)||(e={silent:!!e}),Kv[t.type]&&this._model)if(this.__flagInMainProcess)this._pendingActions.push(t);else{var n=e.silent;Pv.call(this,t,n);var i=e.flush;i?this._zr.flush():!1!==i&&r.browser.weChat&&this._throttledZrFlush(),Ov.call(this,n),Rv.call(this,n)}},e.prototype.updateLabelLayout=function(){gv.trigger("series:layoutlabels",this._model,this._api,{updatedSeries:[]})},e.prototype.appendData=function(t){if(this._disposed)qv(this.id);else{var e=t.seriesIndex,n=this.getModel().getSeriesByIndex(e);0,n.appendData(t),this._scheduler.unfinished=!0,this.getZr().wakeUp()}},e.internalField=function(){function t(t){t.clearColorPalette(),t.eachSeries((function(t){t.clearColorPalette()}))}function e(t){for(var e=[],n=t.currentStates,i=0;i0?{duration:o,delay:i.get("delay"),easing:i.get("easing")}:null;n.eachRendered((function(t){if(t.states&&t.states.emphasis){if(gh(t))return;if(t instanceof Ms&&function(t){var e=nl(t);e.normalFill=t.style.fill,e.normalStroke=t.style.stroke;var n=t.states.select||{};e.selectFill=n.style&&n.style.fill||null,e.selectStroke=n.style&&n.style.stroke||null}(t),t.__dirty){var n=t.prevStates;n&&t.useStates(n)}if(r){t.stateTransition=a;var i=t.getTextContent(),o=t.getTextGuideLine();i&&(i.stateTransition=a),o&&(o.stateTransition=a)}t.__dirty&&e(t)}}))}Tv=function(t){var e=t._scheduler;e.restorePipelines(t._model),e.prepareStageTasks(),Cv(t,!0),Cv(t,!1),e.plan()},Cv=function(t,e){for(var n=t._model,i=t._scheduler,r=e?t._componentsViews:t._chartsViews,o=e?t._componentsMap:t._chartsMap,a=t._zr,s=t._api,l=0;le.get("hoverLayerThreshold")&&!r.node&&!r.worker&&e.eachSeries((function(e){if(!e.preventUsingHoverLayer){var n=t._chartsMap[e.__viewId];n.__alive&&n.eachRendered((function(t){t.states.emphasis&&(t.states.emphasis.hoverLayer=!0)}))}}))}(t,e),gv.trigger("series:afterupdate",e,n,l)},Wv=function(t){t.__needsUpdateStatus=!0,t.getZr().wakeUp()},Hv=function(t){t.__needsUpdateStatus&&(t.getZr().storage.traverse((function(t){gh(t)||e(t)})),t.__needsUpdateStatus=!1)},Fv=function(t){return new(function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return n(i,e),i.prototype.getCoordinateSystems=function(){return t._coordSysMgr.getCoordinateSystems()},i.prototype.getComponentByElement=function(e){for(;e;){var n=e.__ecComponentInfo;if(null!=n)return t._model.getComponent(n.mainType,n.index);e=e.parent}},i.prototype.enterEmphasis=function(e,n){Al(e,n),Wv(t)},i.prototype.leaveEmphasis=function(e,n){kl(e,n),Wv(t)},i.prototype.enterBlur=function(e){Ll(e),Wv(t)},i.prototype.leaveBlur=function(e){Pl(e),Wv(t)},i.prototype.enterSelect=function(e){Ol(e),Wv(t)},i.prototype.leaveSelect=function(e){Rl(e),Wv(t)},i.prototype.getModel=function(){return t.getModel()},i.prototype.getViewOfComponentModel=function(e){return t.getViewOfComponentModel(e)},i.prototype.getViewOfSeriesModel=function(e){return t.getViewOfSeriesModel(e)},i}(gd))(t)},Gv=function(t){function e(t,e){for(var n=0;n=0)){bm.push(n);var o=jg.wrapStageHandler(n,r);o.__prio=e,o.__raw=n,t.push(o)}}function Sm(t,e){nm[t]=e}function Mm(t,e,n){var i=vv("registerMap");i&&i(t,e,n)}var Im=function(t){var e=(t=T(t)).type,n="";e||yo(n);var i=e.split(":");2!==i.length&&yo(n);var r=!1;"echarts"===i[0]&&(e=i[1],r=!0),t.__isBuiltIn=r,Of.set(e,t)};_m(mv,Hg),_m(xv,Ug),_m(xv,Xg),_m(mv,xy),_m(xv,_y),_m(7e3,(function(t,e){t.eachRawSeries((function(n){if(!t.isSeriesFiltered(n)){var i=n.getData();i.hasItemVisual()&&i.each((function(t){var n=i.getItemVisual(t,"decal");n&&(i.ensureUniqueItemVisual(t,"style").decal=cv(n,e))}));var r=i.getVisual("decal");if(r)i.getVisual("style").decal=cv(r,e)}}))})),pm(Fd),dm(900,(function(t){var e=yt();t.eachSeries((function(t){var n=t.get("stack");if(n){var i=e.get(n)||e.set(n,[]),r=t.getData(),o={stackResultDimension:r.getCalculationInfo("stackResultDimension"),stackedOverDimension:r.getCalculationInfo("stackedOverDimension"),stackedDimension:r.getCalculationInfo("stackedDimension"),stackedByDimension:r.getCalculationInfo("stackedByDimension"),isStackedByIndex:r.getCalculationInfo("isStackedByIndex"),data:r,seriesModel:t};if(!o.stackedDimension||!o.isStackedByIndex&&!o.stackedByDimension)return;i.length&&r.setCalculationInfo("stackedOnSeries",i[i.length-1].seriesModel),i.push(o)}})),e.each(Gd)})),Sm("default",(function(t,e){k(e=e||{},{text:"loading",textColor:"#000",fontSize:12,fontWeight:"normal",fontStyle:"normal",fontFamily:"sans-serif",maskColor:"rgba(255, 255, 255, 0.8)",showSpinner:!0,color:"#5470c6",spinnerRadius:10,lineWidth:5,zlevel:0});var n=new Er,i=new Es({style:{fill:e.maskColor},zlevel:e.zlevel,z:1e4});n.add(i);var r,o=new Bs({style:{text:e.text,fill:e.textColor,fontSize:e.fontSize,fontWeight:e.fontWeight,fontStyle:e.fontStyle,fontFamily:e.fontFamily},zlevel:e.zlevel,z:10001}),a=new Es({style:{fill:"none"},textContent:o,textConfig:{position:"right",distance:10},zlevel:e.zlevel,z:10001});return n.add(a),e.showSpinner&&((r=new Ju({shape:{startAngle:-Zg/2,endAngle:-Zg/2+.1,r:e.spinnerRadius},style:{stroke:e.color,lineCap:"round",lineWidth:e.lineWidth},zlevel:e.zlevel,z:10001})).animateShape(!0).when(1e3,{endAngle:3*Zg/2}).start("circularInOut"),r.animateShape(!0).when(1e3,{startAngle:3*Zg/2}).delay(300).start("circularInOut"),n.add(r)),n.resize=function(){var n=o.getBoundingRect().width,s=e.showSpinner?e.spinnerRadius:0,l=(t.getWidth()-2*s-(e.showSpinner&&n?10:0)-n)/2-(e.showSpinner&&n?0:5+n/2)+(e.showSpinner?0:n/2)+(n?0:s),u=t.getHeight()/2;e.showSpinner&&r.setShape({cx:l,cy:u}),a.setShape({x:l-s,y:u-s,width:2*s,height:2*s}),i.setShape({x:0,y:0,width:t.getWidth(),height:t.getHeight()})},n.resize(),n})),vm({type:sl,event:sl,update:sl},bt),vm({type:ll,event:ll,update:ll},bt),vm({type:ul,event:ul,update:ul},bt),vm({type:hl,event:hl,update:hl},bt),vm({type:cl,event:cl,update:cl},bt),cm("light",hy),cm("dark",gy);var Tm=[],Cm={registerPreprocessor:pm,registerProcessor:dm,registerPostInit:fm,registerPostUpdate:gm,registerUpdateLifecycle:ym,registerAction:vm,registerCoordinateSystem:mm,registerLayout:xm,registerVisual:_m,registerTransform:Im,registerLoading:Sm,registerMap:Mm,registerImpl:function(t,e){yv[t]=e},PRIORITY:_v,ComponentModel:Op,ComponentView:wg,SeriesModel:fg,ChartView:Tg,registerComponentModel:function(t){Op.registerClass(t)},registerComponentView:function(t){wg.registerClass(t)},registerSeriesModel:function(t){fg.registerClass(t)},registerChartView:function(t){Tg.registerClass(t)},registerSubTypeDefaulter:function(t,e){Op.registerSubTypeDefaulter(t,e)},registerPainter:function(t,e){Gr(t,e)}};function Dm(t){Y(t)?E(t,(function(t){Dm(t)})):P(Tm,t)>=0||(Tm.push(t),U(t)&&(t={install:t}),t.install(Cm))}function Am(t){return null==t?0:t.length||1}function km(t){return t}var Lm=function(){function t(t,e,n,i,r,o){this._old=t,this._new=e,this._oldKeyGetter=n||km,this._newKeyGetter=i||km,this.context=r,this._diffModeMultiple="multiple"===o}return t.prototype.add=function(t){return this._add=t,this},t.prototype.update=function(t){return this._update=t,this},t.prototype.updateManyToOne=function(t){return this._updateManyToOne=t,this},t.prototype.updateOneToMany=function(t){return this._updateOneToMany=t,this},t.prototype.updateManyToMany=function(t){return this._updateManyToMany=t,this},t.prototype.remove=function(t){return this._remove=t,this},t.prototype.execute=function(){this[this._diffModeMultiple?"_executeMultiple":"_executeOneToOne"]()},t.prototype._executeOneToOne=function(){var t=this._old,e=this._new,n={},i=new Array(t.length),r=new Array(e.length);this._initIndexMap(t,null,i,"_oldKeyGetter"),this._initIndexMap(e,n,r,"_newKeyGetter");for(var o=0;o1){var u=s.shift();1===s.length&&(n[a]=s[0]),this._update&&this._update(u,o)}else 1===l?(n[a]=null,this._update&&this._update(s,o)):this._remove&&this._remove(o)}this._performRestAdd(r,n)},t.prototype._executeMultiple=function(){var t=this._old,e=this._new,n={},i={},r=[],o=[];this._initIndexMap(t,n,r,"_oldKeyGetter"),this._initIndexMap(e,i,o,"_newKeyGetter");for(var a=0;a1&&1===c)this._updateManyToOne&&this._updateManyToOne(u,l),i[s]=null;else if(1===h&&c>1)this._updateOneToMany&&this._updateOneToMany(u,l),i[s]=null;else if(1===h&&1===c)this._update&&this._update(u,l),i[s]=null;else if(h>1&&c>1)this._updateManyToMany&&this._updateManyToMany(u,l),i[s]=null;else if(h>1)for(var p=0;p1)for(var a=0;a30}var Hm,Ym,Um,Xm,Zm,jm,qm,Km=q,$m=z,Jm="undefined"==typeof Int32Array?Array:Int32Array,Qm=["hasItemOption","_nameList","_idList","_invertedIndicesMap","_dimSummary","userOutput","_rawData","_dimValueGetter","_nameDimIdx","_idDimIdx","_nameRepeatCount"],tx=["_approximateExtent"],ex=function(){function t(t,e){var n;this.type="list",this._dimOmitted=!1,this._nameList=[],this._idList=[],this._visual={},this._layout={},this._itemVisuals=[],this._itemLayouts=[],this._graphicEls=[],this._approximateExtent={},this._calculationInfo={},this.hasItemOption=!1,this.TRANSFERABLE_METHODS=["cloneShallow","downSample","lttbDownSample","map"],this.CHANGABLE_METHODS=["filterSelf","selectRange"],this.DOWNSAMPLE_METHODS=["downSample","lttbDownSample"];var i=!1;Bm(t)?(n=t.dimensions,this._dimOmitted=t.isDimensionOmitted(),this._schema=t):(i=!0,n=t),n=n||["x","y"];for(var r={},o=[],a={},s=!1,l={},u=0;u=e)){var n=this._store.getProvider();this._updateOrdinalMeta();var i=this._nameList,r=this._idList;if(n.getSource().sourceFormat===Vp&&!n.pure)for(var o=[],a=t;a0},t.prototype.ensureUniqueItemVisual=function(t,e){var n=this._itemVisuals,i=n[t];i||(i=n[t]={});var r=i[e];return null==r&&(Y(r=this.getVisual(e))?r=r.slice():Km(r)&&(r=A({},r)),i[e]=r),r},t.prototype.setItemVisual=function(t,e,n){var i=this._itemVisuals[t]||{};this._itemVisuals[t]=i,Km(e)?A(i,e):i[e]=n},t.prototype.clearAllVisual=function(){this._visual={},this._itemVisuals=[]},t.prototype.setLayout=function(t,e){Km(t)?A(this._layout,t):this._layout[t]=e},t.prototype.getLayout=function(t){return this._layout[t]},t.prototype.getItemLayout=function(t){return this._itemLayouts[t]},t.prototype.setItemLayout=function(t,e,n){this._itemLayouts[t]=n?A(this._itemLayouts[t]||{},e):e},t.prototype.clearItemLayouts=function(){this._itemLayouts.length=0},t.prototype.setItemGraphicEl=function(t,e){var n=this.hostModel&&this.hostModel.seriesIndex;Qs(n,this.dataType,t,e),this._graphicEls[t]=e},t.prototype.getItemGraphicEl=function(t){return this._graphicEls[t]},t.prototype.eachItemGraphicEl=function(t,e){E(this._graphicEls,(function(n,i){n&&t&&t.call(e,n,i)}))},t.prototype.cloneShallow=function(e){return e||(e=new t(this._schema?this._schema:$m(this.dimensions,this._getDimInfo,this),this.hostModel)),Zm(e,this),e._store=this._store,e},t.prototype.wrapMethod=function(t,e){var n=this[t];U(n)&&(this.__wrappedMethods=this.__wrappedMethods||[],this.__wrappedMethods.push(t),this[t]=function(){var t=n.apply(this,arguments);return e.apply(this,[t].concat(at(arguments)))})},t.internalField=(Hm=function(t){var e=t._invertedIndicesMap;E(e,(function(n,i){var r=t._dimInfos[i],o=r.ordinalMeta,a=t._store;if(o){n=e[i]=new Jm(o.categories.length);for(var s=0;s1&&(s+="__ec__"+u),i[e]=s}})),t}();function nx(t,e){jd(t)||(t=Kd(t));var n=(e=e||{}).coordDimensions||[],i=e.dimensionsDefine||t.dimensionsDefine||[],r=yt(),o=[],a=function(t,e,n,i){var r=Math.max(t.dimensionsDetectedCount||1,e.length,n.length,i||0);return E(e,(function(t){var e;q(t)&&(e=t.dimsDef)&&(r=Math.max(r,e.length))})),r}(t,n,i,e.dimensionsCount),s=e.canOmitUnusedDimensions&&Wm(a),l=i===t.dimensionsDefine,u=l?Gm(t):Fm(i),h=e.encodeDefine;!h&&e.encodeDefaulter&&(h=e.encodeDefaulter(t,a));for(var c=yt(h),p=new Ff(a),d=0;d0&&(i.name=r+(o-1)),o++,e.set(r,o)}}(o),new Vm({source:t,dimensions:o,fullDimensionCount:a,dimensionOmitted:s})}function ix(t,e,n){if(n||e.hasKey(t)){for(var i=0;e.hasKey(t+i);)i++;t+=i}return e.set(t,!0),t}var rx=function(t){this.coordSysDims=[],this.axisMap=yt(),this.categoryAxisMap=yt(),this.coordSysName=t};var ox={cartesian2d:function(t,e,n,i){var r=t.getReferringComponents("xAxis",Eo).models[0],o=t.getReferringComponents("yAxis",Eo).models[0];e.coordSysDims=["x","y"],n.set("x",r),n.set("y",o),ax(r)&&(i.set("x",r),e.firstCategoryDimIndex=0),ax(o)&&(i.set("y",o),null==e.firstCategoryDimIndex&&(e.firstCategoryDimIndex=1))},singleAxis:function(t,e,n,i){var r=t.getReferringComponents("singleAxis",Eo).models[0];e.coordSysDims=["single"],n.set("single",r),ax(r)&&(i.set("single",r),e.firstCategoryDimIndex=0)},polar:function(t,e,n,i){var r=t.getReferringComponents("polar",Eo).models[0],o=r.findAxisModel("radiusAxis"),a=r.findAxisModel("angleAxis");e.coordSysDims=["radius","angle"],n.set("radius",o),n.set("angle",a),ax(o)&&(i.set("radius",o),e.firstCategoryDimIndex=0),ax(a)&&(i.set("angle",a),null==e.firstCategoryDimIndex&&(e.firstCategoryDimIndex=1))},geo:function(t,e,n,i){e.coordSysDims=["lng","lat"]},parallel:function(t,e,n,i){var r=t.ecModel,o=r.getComponent("parallel",t.get("parallelIndex")),a=e.coordSysDims=o.dimensions.slice();E(o.parallelAxisIndex,(function(t,o){var s=r.getComponent("parallelAxis",t),l=a[o];n.set(l,s),ax(s)&&(i.set(l,s),null==e.firstCategoryDimIndex&&(e.firstCategoryDimIndex=o))}))}};function ax(t){return"category"===t.get("type")}function sx(t,e,n){var i,r,o,a=(n=n||{}).byIndex,s=n.stackedCoordDimension;!function(t){return!Bm(t.schema)}(e)?(r=e.schema,i=r.dimensions,o=e.store):i=e;var l,u,h,c,p=!(!t||!t.get("stack"));if(E(i,(function(t,e){X(t)&&(i[e]=t={name:t}),p&&!t.isExtraCoord&&(a||l||!t.ordinalMeta||(l=t),u||"ordinal"===t.type||"time"===t.type||s&&s!==t.coordDim||(u=t))})),!u||a||l||(a=!0),u){h="__\0ecstackresult_"+t.id,c="__\0ecstackedover_"+t.id,l&&(l.createInvertedIndices=!0);var d=u.coordDim,f=u.type,g=0;E(i,(function(t){t.coordDim===d&&g++}));var y={name:h,coordDim:d,coordDimIndex:g,type:f,isExtraCoord:!0,isCalculationCoord:!0,storeDimIndex:i.length},v={name:c,coordDim:c,coordDimIndex:g+1,type:f,isExtraCoord:!0,isCalculationCoord:!0,storeDimIndex:i.length+1};r?(o&&(y.storeDimIndex=o.ensureCalculationDimension(c,f),v.storeDimIndex=o.ensureCalculationDimension(h,f)),r.appendCalculationDimension(y),r.appendCalculationDimension(v)):(i.push(y),i.push(v))}return{stackedDimension:u&&u.name,stackedByDimension:l&&l.name,isStackedByIndex:a,stackedOverDimension:c,stackResultDimension:h}}function lx(t,e){return!!e&&e===t.getCalculationInfo("stackedDimension")}function ux(t,e){return lx(t,e)?t.getCalculationInfo("stackResultDimension"):e}function hx(t,e,n){n=n||{};var i,r=e.getSourceManager(),o=!1;t?(o=!0,i=Kd(t)):o=(i=r.getSource()).sourceFormat===Vp;var a=function(t){var e=t.get("coordinateSystem"),n=new rx(e),i=ox[e];if(i)return i(t,n,n.axisMap,n.categoryAxisMap),n}(e),s=function(t,e){var n,i=t.get("coordinateSystem"),r=vd.get(i);return e&&e.coordSysDims&&(n=z(e.coordSysDims,(function(t){var n={name:t},i=e.axisMap.get(t);if(i){var r=i.get("type");n.type=Rm(r)}return n}))),n||(n=r&&(r.getDimensionsInfo?r.getDimensionsInfo():r.dimensions.slice())||["x","y"]),n}(e,a),l=n.useEncodeDefaulter,u=U(l)?l:l?H(Kp,s,e):null,h=nx(i,{coordDimensions:s,generateCoord:n.generateCoord,encodeDefine:e.getEncode(),encodeDefaulter:u,canOmitUnusedDimensions:!o}),c=function(t,e,n){var i,r;return n&&E(t,(function(t,o){var a=t.coordDim,s=n.categoryAxisMap.get(a);s&&(null==i&&(i=o),t.ordinalMeta=s.getOrdinalMeta(),e&&(t.createInvertedIndices=!0)),null!=t.otherDims.itemName&&(r=!0)})),r||null==i||(t[i].otherDims.itemName=0),i}(h.dimensions,n.createInvertedIndices,a),p=o?null:r.getSharedDataStore(h),d=sx(e,{schema:h,store:p}),f=new ex(h,e);f.setCalculationInfo(d);var g=null!=c&&function(t){if(t.sourceFormat===Vp){var e=function(t){var e=0;for(;ee[1]&&(e[1]=t[1])},t.prototype.unionExtentFromData=function(t,e){this.unionExtent(t.getApproximateExtent(e))},t.prototype.getExtent=function(){return this._extent.slice()},t.prototype.setExtent=function(t,e){var n=this._extent;isNaN(t)||(n[0]=t),isNaN(e)||(n[1]=e)},t.prototype.isInExtentRange=function(t){return this._extent[0]<=t&&this._extent[1]>=t},t.prototype.isBlank=function(){return this._isBlank},t.prototype.setBlank=function(t){this._isBlank=t},t}();Ko(cx);var px=0,dx=function(){function t(t){this.categories=t.categories||[],this._needCollect=t.needCollect,this._deduplication=t.deduplication,this.uid=++px}return t.createByAxisModel=function(e){var n=e.option,i=n.data,r=i&&z(i,fx);return new t({categories:r,needCollect:!r,deduplication:!1!==n.dedplication})},t.prototype.getOrdinal=function(t){return this._getOrCreateMap().get(t)},t.prototype.parseAndCollect=function(t){var e,n=this._needCollect;if(!X(t)&&!n)return t;if(n&&!this._deduplication)return e=this.categories.length,this.categories[e]=t,e;var i=this._getOrCreateMap();return null==(e=i.get(t))&&(n?(e=this.categories.length,this.categories[e]=t,i.set(t,e)):e=NaN),e},t.prototype._getOrCreateMap=function(){return this._map||(this._map=yt(this.categories))},t}();function fx(t){return q(t)&&null!=t.value?t.value:t+""}function gx(t){return"interval"===t.type||"log"===t.type}function yx(t,e,n,i){var r={},o=t[1]-t[0],a=r.interval=ao(o/e,!0);null!=n&&ai&&(a=r.interval=i);var s=r.intervalPrecision=mx(a);return function(t,e){!isFinite(t[0])&&(t[0]=e[0]),!isFinite(t[1])&&(t[1]=e[1]),xx(t,0,e),xx(t,1,e),t[0]>t[1]&&(t[0]=t[1])}(r.niceTickExtent=[Xr(Math.ceil(t[0]/a)*a,s),Xr(Math.floor(t[1]/a)*a,s)],t),r}function vx(t){var e=Math.pow(10,oo(t)),n=t/e;return n?2===n?n=3:3===n?n=5:n*=2:n=1,Xr(n*e)}function mx(t){return jr(t)+2}function xx(t,e,n){t[e]=Math.max(Math.min(t[e],n[1]),n[0])}function _x(t,e){return t>=e[0]&&t<=e[1]}function bx(t,e){return e[1]===e[0]?.5:(t-e[0])/(e[1]-e[0])}function Sx(t,e){return t*(e[1]-e[0])+e[0]}var Mx=function(t){function e(e){var n=t.call(this,e)||this;n.type="ordinal";var i=n.getSetting("ordinalMeta");return i||(i=new dx({})),Y(i)&&(i=new dx({categories:z(i,(function(t){return q(t)?t.value:t}))})),n._ordinalMeta=i,n._extent=n.getSetting("extent")||[0,i.categories.length-1],n}return n(e,t),e.prototype.parse=function(t){return null==t?NaN:X(t)?this._ordinalMeta.getOrdinal(t):Math.round(t)},e.prototype.contain=function(t){return _x(t=this.parse(t),this._extent)&&null!=this._ordinalMeta.categories[t]},e.prototype.normalize=function(t){return bx(t=this._getTickNumber(this.parse(t)),this._extent)},e.prototype.scale=function(t){return t=Math.round(Sx(t,this._extent)),this.getRawOrdinalNumber(t)},e.prototype.getTicks=function(){for(var t=[],e=this._extent,n=e[0];n<=e[1];)t.push({value:n}),n++;return t},e.prototype.getMinorTicks=function(t){},e.prototype.setSortInfo=function(t){if(null!=t){for(var e=t.ordinalNumbers,n=this._ordinalNumbersByTick=[],i=this._ticksByOrdinalNumber=[],r=0,o=this._ordinalMeta.categories.length,a=Math.min(o,e.length);r=0&&t=0&&t=t},e.prototype.getOrdinalMeta=function(){return this._ordinalMeta},e.prototype.calcNiceTicks=function(){},e.prototype.calcNiceExtent=function(){},e.type="ordinal",e}(cx);cx.registerClass(Mx);var Ix=Xr,Tx=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type="interval",e._interval=0,e._intervalPrecision=2,e}return n(e,t),e.prototype.parse=function(t){return t},e.prototype.contain=function(t){return _x(t,this._extent)},e.prototype.normalize=function(t){return bx(t,this._extent)},e.prototype.scale=function(t){return Sx(t,this._extent)},e.prototype.setExtent=function(t,e){var n=this._extent;isNaN(t)||(n[0]=parseFloat(t)),isNaN(e)||(n[1]=parseFloat(e))},e.prototype.unionExtent=function(t){var e=this._extent;t[0]e[1]&&(e[1]=t[1]),this.setExtent(e[0],e[1])},e.prototype.getInterval=function(){return this._interval},e.prototype.setInterval=function(t){this._interval=t,this._niceExtent=this._extent.slice(),this._intervalPrecision=mx(t)},e.prototype.getTicks=function(t){var e=this._interval,n=this._extent,i=this._niceExtent,r=this._intervalPrecision,o=[];if(!e)return o;n[0]1e4)return[];var s=o.length?o[o.length-1].value:i[1];return n[1]>s&&(t?o.push({value:Ix(s+e,r)}):o.push({value:n[1]})),o},e.prototype.getMinorTicks=function(t){for(var e=this.getTicks(!0),n=[],i=this.getExtent(),r=1;ri[0]&&h0&&(o=null===o?s:Math.min(o,s))}n[i]=o}}return n}(t),n=[];return E(t,(function(t){var i,r=t.coordinateSystem.getBaseAxis(),o=r.getExtent();if("category"===r.type)i=r.getBandWidth();else if("value"===r.type||"time"===r.type){var a=r.dim+"_"+r.index,s=e[a],l=Math.abs(o[1]-o[0]),u=r.scale.getExtent(),h=Math.abs(u[1]-u[0]);i=s?l/h*s:l}else{var c=t.getData();i=Math.abs(o[1]-o[0])/c.count()}var p=Ur(t.get("barWidth"),i),d=Ur(t.get("barMaxWidth"),i),f=Ur(t.get("barMinWidth")||(Bx(t)?.5:1),i),g=t.get("barGap"),y=t.get("barCategoryGap");n.push({bandWidth:i,barWidth:p,barMaxWidth:d,barMinWidth:f,barGap:g,barCategoryGap:y,axisKey:Px(r),stackId:Lx(t)})})),Nx(n)}function Nx(t){var e={};E(t,(function(t,n){var i=t.axisKey,r=t.bandWidth,o=e[i]||{bandWidth:r,remainedWidth:r,autoWidthCount:0,categoryGap:null,gap:"20%",stacks:{}},a=o.stacks;e[i]=o;var s=t.stackId;a[s]||o.autoWidthCount++,a[s]=a[s]||{width:0,maxWidth:0};var l=t.barWidth;l&&!a[s].width&&(a[s].width=l,l=Math.min(o.remainedWidth,l),o.remainedWidth-=l);var u=t.barMaxWidth;u&&(a[s].maxWidth=u);var h=t.barMinWidth;h&&(a[s].minWidth=h);var c=t.barGap;null!=c&&(o.gap=c);var p=t.barCategoryGap;null!=p&&(o.categoryGap=p)}));var n={};return E(e,(function(t,e){n[e]={};var i=t.stacks,r=t.bandWidth,o=t.categoryGap;if(null==o){var a=G(i).length;o=Math.max(35-4*a,15)+"%"}var s=Ur(o,r),l=Ur(t.gap,1),u=t.remainedWidth,h=t.autoWidthCount,c=(u-s)/(h+(h-1)*l);c=Math.max(c,0),E(i,(function(t){var e=t.maxWidth,n=t.minWidth;if(t.width){i=t.width;e&&(i=Math.min(i,e)),n&&(i=Math.max(i,n)),t.width=i,u-=i+l*i,h--}else{var i=c;e&&ei&&(i=n),i!==c&&(t.width=i,u-=i+l*i,h--)}})),c=(u-s)/(h+(h-1)*l),c=Math.max(c,0);var p,d=0;E(i,(function(t,e){t.width||(t.width=c),p=t,d+=t.width*(1+l)})),p&&(d-=p.width*l);var f=-d/2;E(i,(function(t,i){n[e][i]=n[e][i]||{bandWidth:r,offset:f,width:t.width},f+=t.width*(1+l)}))})),n}function Ex(t,e){var n=Ox(t,e),i=Rx(n);E(n,(function(t){var e=t.getData(),n=t.coordinateSystem.getBaseAxis(),r=Lx(t),o=i[Px(n)][r],a=o.offset,s=o.width;e.setLayout({bandWidth:o.bandWidth,offset:a,size:s})}))}function zx(t){return{seriesType:t,plan:Sg(),reset:function(t){if(Vx(t)){var e=t.getData(),n=t.coordinateSystem,i=n.getBaseAxis(),r=n.getOtherAxis(i),o=e.getDimensionIndex(e.mapDimension(r.dim)),a=e.getDimensionIndex(e.mapDimension(i.dim)),s=t.get("showBackground",!0),l=e.mapDimension(r.dim),u=e.getCalculationInfo("stackResultDimension"),h=lx(e,l)&&!!e.getCalculationInfo("stackedOnSeries"),c=r.isHorizontal(),p=function(t,e){return e.toGlobalCoord(e.dataToCoord("log"===e.type?1:0))}(0,r),d=Bx(t),f=t.get("barMinHeight")||0,g=u&&e.getDimensionIndex(u),y=e.getLayout("size"),v=e.getLayout("offset");return{progress:function(t,e){for(var i,r=t.count,l=d&&Ax(3*r),u=d&&s&&Ax(3*r),m=d&&Ax(r),x=n.master.getRect(),_=c?x.width:x.height,b=e.getStore(),w=0;null!=(i=t.next());){var S=b.get(h?g:o,i),M=b.get(a,i),I=p,T=void 0;h&&(T=+S-b.get(o,i));var C=void 0,D=void 0,A=void 0,k=void 0;if(c){var L=n.dataToPoint([S,M]);if(h)I=n.dataToPoint([T,M])[0];C=I,D=L[1]+v,A=L[0]-I,k=y,Math.abs(A)0)for(var s=0;s=0;--s)if(l[u]){o=l[u];break}o=o||a.none}if(Y(o)){var h=null==t.level?0:t.level>=0?t.level:o.length+t.level;o=o[h=Math.min(h,o.length-1)]}}return jc(new Date(t.value),o,r,i)}(t,e,n,this.getSetting("locale"),i)},e.prototype.getTicks=function(){var t=this._interval,e=this._extent,n=[];if(!t)return n;n.push({value:e[0],level:0});var i=this.getSetting("useUTC"),r=function(t,e,n,i){var r=1e4,o=Yc,a=0;function s(t,e,n,r,o,a,s){for(var l=new Date(e),u=e,h=l[r]();u1&&0===u&&o.unshift({value:o[0].value-p})}}for(u=0;u=i[0]&&v<=i[1]&&c++)}var m=(i[1]-i[0])/e;if(c>1.5*m&&p>m/1.5)break;if(u.push(g),c>m||t===o[d])break}h=[]}}0;var x=B(z(u,(function(t){return B(t,(function(t){return t.value>=i[0]&&t.value<=i[1]&&!t.notAdd}))})),(function(t){return t.length>0})),_=[],b=x.length-1;for(d=0;dn&&(this._approxInterval=n);var o=Gx.length,a=Math.min(function(t,e,n,i){for(;n>>1;t[r][1]16?16:t>7.5?7:t>3.5?4:t>1.5?2:1}function Hx(t){return(t/=2592e6)>6?6:t>3?3:t>2?2:1}function Yx(t){return(t/=zc)>12?12:t>6?6:t>3.5?4:t>2?2:1}function Ux(t,e){return(t/=e?Ec:Nc)>30?30:t>20?20:t>15?15:t>10?10:t>5?5:t>2?2:1}function Xx(t){return ao(t,!0)}function Zx(t,e,n){var i=new Date(t);switch(Xc(e)){case"year":case"month":i[op(n)](0);case"day":i[ap(n)](1);case"hour":i[sp(n)](0);case"minute":i[lp(n)](0);case"second":i[up(n)](0),i[hp(n)](0)}return i.getTime()}cx.registerClass(Fx);var jx=cx.prototype,qx=Tx.prototype,Kx=Xr,$x=Math.floor,Jx=Math.ceil,Qx=Math.pow,t_=Math.log,e_=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type="log",e.base=10,e._originalScale=new Tx,e._interval=0,e}return n(e,t),e.prototype.getTicks=function(t){var e=this._originalScale,n=this._extent,i=e.getExtent();return z(qx.getTicks.call(this,t),(function(t){var e=t.value,r=Xr(Qx(this.base,e));return r=e===n[0]&&this._fixMin?i_(r,i[0]):r,{value:r=e===n[1]&&this._fixMax?i_(r,i[1]):r}}),this)},e.prototype.setExtent=function(t,e){var n=t_(this.base);t=t_(Math.max(0,t))/n,e=t_(Math.max(0,e))/n,qx.setExtent.call(this,t,e)},e.prototype.getExtent=function(){var t=this.base,e=jx.getExtent.call(this);e[0]=Qx(t,e[0]),e[1]=Qx(t,e[1]);var n=this._originalScale.getExtent();return this._fixMin&&(e[0]=i_(e[0],n[0])),this._fixMax&&(e[1]=i_(e[1],n[1])),e},e.prototype.unionExtent=function(t){this._originalScale.unionExtent(t);var e=this.base;t[0]=t_(t[0])/t_(e),t[1]=t_(t[1])/t_(e),jx.unionExtent.call(this,t)},e.prototype.unionExtentFromData=function(t,e){this.unionExtent(t.getApproximateExtent(e))},e.prototype.calcNiceTicks=function(t){t=t||10;var e=this._extent,n=e[1]-e[0];if(!(n===1/0||n<=0)){var i=ro(n);for(t/n*i<=.5&&(i*=10);!isNaN(i)&&Math.abs(i)<1&&Math.abs(i)>0;)i*=10;var r=[Xr(Jx(e[0]/i)*i),Xr($x(e[1]/i)*i)];this._interval=i,this._niceExtent=r}},e.prototype.calcNiceExtent=function(t){qx.calcNiceExtent.call(this,t),this._fixMin=t.fixMin,this._fixMax=t.fixMax},e.prototype.parse=function(t){return t},e.prototype.contain=function(t){return _x(t=t_(t)/t_(this.base),this._extent)},e.prototype.normalize=function(t){return bx(t=t_(t)/t_(this.base),this._extent)},e.prototype.scale=function(t){return t=Sx(t,this._extent),Qx(this.base,t)},e.type="log",e}(cx),n_=e_.prototype;function i_(t,e){return Kx(t,jr(e))}n_.getMinorTicks=qx.getMinorTicks,n_.getLabel=qx.getLabel,cx.registerClass(e_);var r_=function(){function t(t,e,n){this._prepareParams(t,e,n)}return t.prototype._prepareParams=function(t,e,n){n[1]0&&s>0&&!l&&(a=0),a<0&&s<0&&!u&&(s=0));var c=this._determinedMin,p=this._determinedMax;return null!=c&&(a=c,l=!0),null!=p&&(s=p,u=!0),{min:a,max:s,minFixed:l,maxFixed:u,isBlank:h}},t.prototype.modifyDataMinMax=function(t,e){this[a_[t]]=e},t.prototype.setDeterminedMinMax=function(t,e){var n=o_[t];this[n]=e},t.prototype.freeze=function(){this.frozen=!0},t}(),o_={min:"_determinedMin",max:"_determinedMax"},a_={min:"_dataMin",max:"_dataMax"};function s_(t,e,n){var i=t.rawExtentInfo;return i||(i=new r_(t,e,n),t.rawExtentInfo=i,i)}function l_(t,e){return null==e?null:nt(e)?NaN:t.parse(e)}function u_(t,e){var n=t.type,i=s_(t,e,t.getExtent()).calculate();t.setBlank(i.isBlank);var r=i.min,o=i.max,a=e.ecModel;if(a&&"time"===n){var s=Ox("bar",a),l=!1;if(E(s,(function(t){l=l||t.getBaseAxis()===e.axis})),l){var u=Rx(s),h=function(t,e,n,i){var r=n.axis.getExtent(),o=r[1]-r[0],a=function(t,e,n){if(t&&e){var i=t[Px(e)];return null!=i&&null!=n?i[Lx(n)]:i}}(i,n.axis);if(void 0===a)return{min:t,max:e};var s=1/0;E(a,(function(t){s=Math.min(t.offset,s)}));var l=-1/0;E(a,(function(t){l=Math.max(t.offset+t.width,l)})),s=Math.abs(s),l=Math.abs(l);var u=s+l,h=e-t,c=h/(1-(s+l)/o)-h;return{min:t-=c*(s/u),max:e+=c*(l/u)}}(r,o,e,u);r=h.min,o=h.max}}return{extent:[r,o],fixMin:i.minFixed,fixMax:i.maxFixed}}function h_(t,e){var n=e,i=u_(t,n),r=i.extent,o=n.get("splitNumber");t instanceof e_&&(t.base=n.get("logBase"));var a=t.type,s=n.get("interval"),l="interval"===a||"time"===a;t.setExtent(r[0],r[1]),t.calcNiceExtent({splitNumber:o,fixMin:i.fixMin,fixMax:i.fixMax,minInterval:l?n.get("minInterval"):null,maxInterval:l?n.get("maxInterval"):null}),null!=s&&t.setInterval&&t.setInterval(s)}function c_(t,e){if(e=e||t.get("type"))switch(e){case"category":return new Mx({ordinalMeta:t.getOrdinalMeta?t.getOrdinalMeta():t.getCategories(),extent:[1/0,-1/0]});case"time":return new Fx({locale:t.ecModel.getLocaleModel(),useUTC:t.ecModel.get("useUTC")});default:return new(cx.getClass(e)||Tx)}}function p_(t){var e,n,i=t.getLabelModel().get("formatter"),r="category"===t.type?t.scale.getExtent()[0]:null;return"time"===t.scale.type?(n=i,function(e,i){return t.scale.getFormattedLabel(e,i,n)}):X(i)?function(e){return function(n){var i=t.scale.getLabel(n);return e.replace("{value}",null!=i?i:"")}}(i):U(i)?(e=i,function(n,i){return null!=r&&(i=n.value-r),e(d_(t,n),i,null!=n.level?{level:n.level}:null)}):function(e){return t.scale.getLabel(e)}}function d_(t,e){return"category"===t.type?t.scale.getLabel(e):e.value}function f_(t,e){var n=e*Math.PI/180,i=t.width,r=t.height,o=i*Math.abs(Math.cos(n))+Math.abs(r*Math.sin(n)),a=i*Math.abs(Math.sin(n))+Math.abs(r*Math.cos(n));return new Ee(t.x,t.y,o,a)}function g_(t){var e=t.get("interval");return null==e?"auto":e}function y_(t){return"category"===t.type&&0===g_(t.getLabelModel())}function v_(t,e){var n={};return E(t.mapDimensionsAll(e),(function(e){n[ux(t,e)]=!0})),G(n)}var m_=function(){function t(){}return t.prototype.getNeedCrossZero=function(){return!this.option.scale},t.prototype.getCoordSysModel=function(){},t}();var x_={isDimensionStacked:lx,enableDataStack:sx,getStackedDimension:ux};var __=Object.freeze({__proto__:null,createList:function(t){return hx(null,t)},getLayoutRect:Tp,dataStack:x_,createScale:function(t,e){var n=e;e instanceof Sc||(n=new Sc(e));var i=c_(n);return i.setExtent(t[0],t[1]),h_(i,n),i},mixinAxisModelCommonMethods:function(t){R(t,m_)},getECData:Js,createTextStyle:function(t,e){return ec(t,null,null,"normal"!==(e=e||{}).state)},createDimensions:function(t,e){return nx(t,e).dimensions},createSymbol:Vy,enableHoverEmphasis:Wl});function b_(t,e){return Math.abs(t-e)<1e-8}function w_(t,e,n){var i=0,r=t[0];if(!r)return!1;for(var o=1;on&&(t=r,n=a)}if(t)return function(t){for(var e=0,n=0,i=0,r=t.length,o=t[r-1][0],a=t[r-1][1],s=0;s>1^-(1&s),l=l>>1^-(1&l),r=s+=r,o=l+=o,i.push([s/n,l/n])}return i}function O_(t,e){return z(B((t=function(t){if(!t.UTF8Encoding)return t;var e=t,n=e.UTF8Scale;return null==n&&(n=1024),E(e.features,(function(t){var e=t.geometry,i=e.encodeOffsets,r=e.coordinates;if(i)switch(e.type){case"LineString":e.coordinates=P_(r,i,n);break;case"Polygon":case"MultiLineString":L_(r,i,n);break;case"MultiPolygon":E(r,(function(t,e){return L_(t,i[e],n)}))}})),e.UTF8Encoding=!1,e}(t)).features,(function(t){return t.geometry&&t.properties&&t.geometry.coordinates.length>0})),(function(t){var n=t.properties,i=t.geometry,r=[];switch(i.type){case"Polygon":var o=i.coordinates;r.push(new C_(o[0],o.slice(1)));break;case"MultiPolygon":E(i.coordinates,(function(t){t[0]&&r.push(new C_(t[0],t.slice(1)))}));break;case"LineString":r.push(new D_([i.coordinates]));break;case"MultiLineString":r.push(new D_(i.coordinates))}var a=new A_(n[e||"name"],r,n.cp);return a.properties=n,a}))}var R_=Object.freeze({__proto__:null,linearMap:Yr,round:Xr,asc:Zr,getPrecision:jr,getPrecisionSafe:qr,getPixelPrecision:Kr,getPercentWithPrecision:function(t,e,n){return t[e]&&$r(t,n)[e]||0},MAX_SAFE_INTEGER:Qr,remRadian:to,isRadianAroundZero:eo,parseDate:io,quantity:ro,quantityExponent:oo,nice:ao,quantile:so,reformIntervals:lo,isNumeric:ho,numericToNumber:uo}),N_=Object.freeze({__proto__:null,parse:io,format:jc}),E_=Object.freeze({__proto__:null,extendShape:Sh,extendPath:Ih,makePath:Dh,makeImage:Ah,mergePath:Lh,resizePath:Ph,createIcon:Wh,updateProps:dh,initProps:fh,getTransform:Nh,clipPointsByRect:Fh,clipRectByRect:Gh,registerShape:Th,getShapeClass:Ch,Group:Er,Image:As,Text:Bs,Circle:xu,Ellipse:bu,Sector:Eu,Ring:Vu,Polygon:Gu,Polyline:Hu,Rect:Es,Line:Xu,BezierCurve:Ku,Arc:Ju,IncrementalDisplayable:uh,CompoundPath:Qu,LinearGradient:eh,RadialGradient:nh,BoundingRect:Ee}),z_=Object.freeze({__proto__:null,addCommas:cp,toCamelCase:pp,normalizeCssArray:dp,encodeHTML:ie,formatTpl:vp,getTooltipMarker:mp,formatTime:function(t,e,n){"week"!==t&&"month"!==t&&"quarter"!==t&&"half-year"!==t&&"year"!==t||(t="MM-dd\nyyyy");var i=io(e),r=n?"getUTC":"get",o=i[r+"FullYear"](),a=i[r+"Month"]()+1,s=i[r+"Date"](),l=i[r+"Hours"](),u=i[r+"Minutes"](),h=i[r+"Seconds"](),c=i[r+"Milliseconds"]();return t=t.replace("MM",Uc(a,2)).replace("M",a).replace("yyyy",o).replace("yy",Uc(o%100+"",2)).replace("dd",Uc(s,2)).replace("d",s).replace("hh",Uc(l,2)).replace("h",l).replace("mm",Uc(u,2)).replace("m",u).replace("ss",Uc(h,2)).replace("s",h).replace("SSS",Uc(c,3))},capitalFirst:function(t){return t?t.charAt(0).toUpperCase()+t.substr(1):t},truncateText:aa,getTextRect:function(t,e,n,i,r,o,a,s){return new Bs({style:{text:t,font:e,align:n,verticalAlign:i,padding:r,rich:o,overflow:a?"truncate":null,lineHeight:s}}).getBoundingRect()}}),V_=Object.freeze({__proto__:null,map:z,each:E,indexOf:P,inherits:O,reduce:V,filter:B,bind:W,curry:H,isArray:Y,isString:X,isObject:q,isFunction:U,extend:A,defaults:k,clone:T,merge:C}),B_=Po();function F_(t){return"category"===t.type?function(t){var e=t.getLabelModel(),n=W_(t,e);return!e.get("show")||t.scale.isBlank()?{labels:[],labelCategoryInterval:n.labelCategoryInterval}:n}(t):function(t){var e=t.scale.getTicks(),n=p_(t);return{labels:z(e,(function(e,i){return{level:e.level,formattedLabel:n(e,i),rawLabel:t.scale.getLabel(e),tickValue:e.value}}))}}(t)}function G_(t,e){return"category"===t.type?function(t,e){var n,i,r=H_(t,"ticks"),o=g_(e),a=Y_(r,o);if(a)return a;e.get("show")&&!t.scale.isBlank()||(n=[]);if(U(o))n=Z_(t,o,!0);else if("auto"===o){var s=W_(t,t.getLabelModel());i=s.labelCategoryInterval,n=z(s.labels,(function(t){return t.tickValue}))}else n=X_(t,i=o,!0);return U_(r,o,{ticks:n,tickCategoryInterval:i})}(t,e):{ticks:z(t.scale.getTicks(),(function(t){return t.value}))}}function W_(t,e){var n,i,r=H_(t,"labels"),o=g_(e),a=Y_(r,o);return a||(U(o)?n=Z_(t,o):(i="auto"===o?function(t){var e=B_(t).autoInterval;return null!=e?e:B_(t).autoInterval=t.calculateCategoryInterval()}(t):o,n=X_(t,i)),U_(r,o,{labels:n,labelCategoryInterval:i}))}function H_(t,e){return B_(t)[e]||(B_(t)[e]=[])}function Y_(t,e){for(var n=0;n1&&h/l>2&&(u=Math.round(Math.ceil(u/l)*l));var c=y_(t),p=a.get("showMinLabel")||c,d=a.get("showMaxLabel")||c;p&&u!==o[0]&&g(o[0]);for(var f=u;f<=o[1];f+=l)g(f);function g(t){var e={value:t};s.push(n?t:{formattedLabel:i(e),rawLabel:r.getLabel(e),tickValue:t})}return d&&f-l!==o[1]&&g(o[1]),s}function Z_(t,e,n){var i=t.scale,r=p_(t),o=[];return E(i.getTicks(),(function(t){var a=i.getLabel(t),s=t.value;e(t.value,a)&&o.push(n?s:{formattedLabel:r(t),rawLabel:a,tickValue:s})})),o}var j_=[0,1],q_=function(){function t(t,e,n){this.onBand=!1,this.inverse=!1,this.dim=t,this.scale=e,this._extent=n||[0,0]}return t.prototype.contain=function(t){var e=this._extent,n=Math.min(e[0],e[1]),i=Math.max(e[0],e[1]);return t>=n&&t<=i},t.prototype.containData=function(t){return this.scale.contain(t)},t.prototype.getExtent=function(){return this._extent.slice()},t.prototype.getPixelPrecision=function(t){return Kr(t||this.scale.getExtent(),this._extent)},t.prototype.setExtent=function(t,e){var n=this._extent;n[0]=t,n[1]=e},t.prototype.dataToCoord=function(t,e){var n=this._extent,i=this.scale;return t=i.normalize(t),this.onBand&&"ordinal"===i.type&&K_(n=n.slice(),i.count()),Yr(t,j_,n,e)},t.prototype.coordToData=function(t,e){var n=this._extent,i=this.scale;this.onBand&&"ordinal"===i.type&&K_(n=n.slice(),i.count());var r=Yr(t,n,j_,e);return this.scale.scale(r)},t.prototype.pointToData=function(t,e){},t.prototype.getTicksCoords=function(t){var e=(t=t||{}).tickModel||this.getTickModel(),n=z(G_(this,e).ticks,(function(t){return{coord:this.dataToCoord("ordinal"===this.scale.type?this.scale.getRawOrdinalNumber(t):t),tickValue:t}}),this);return function(t,e,n,i){var r=e.length;if(!t.onBand||n||!r)return;var o,a,s=t.getExtent();if(1===r)e[0].coord=s[0],o=e[1]={coord:s[0]};else{var l=e[r-1].tickValue-e[0].tickValue,u=(e[r-1].coord-e[0].coord)/l;E(e,(function(t){t.coord-=u/2})),a=1+t.scale.getExtent()[1]-e[r-1].tickValue,o={coord:e[r-1].coord+u*a},e.push(o)}var h=s[0]>s[1];c(e[0].coord,s[0])&&(i?e[0].coord=s[0]:e.shift());i&&c(s[0],e[0].coord)&&e.unshift({coord:s[0]});c(s[1],o.coord)&&(i?o.coord=s[1]:e.pop());i&&c(o.coord,s[1])&&e.push({coord:s[1]});function c(t,e){return t=Xr(t),e=Xr(e),h?t>e:t0&&t<100||(t=5),z(this.scale.getMinorTicks(t),(function(t){return z(t,(function(t){return{coord:this.dataToCoord(t),tickValue:t}}),this)}),this)},t.prototype.getViewLabels=function(){return F_(this).labels},t.prototype.getLabelModel=function(){return this.model.getModel("axisLabel")},t.prototype.getTickModel=function(){return this.model.getModel("axisTick")},t.prototype.getBandWidth=function(){var t=this._extent,e=this.scale.getExtent(),n=e[1]-e[0]+(this.onBand?1:0);0===n&&(n=1);var i=Math.abs(t[1]-t[0]);return Math.abs(i)/n},t.prototype.calculateCategoryInterval=function(){return function(t){var e=function(t){var e=t.getLabelModel();return{axisRotate:t.getRotate?t.getRotate():t.isHorizontal&&!t.isHorizontal()?90:0,labelRotate:e.get("rotate")||0,font:e.getFont()}}(t),n=p_(t),i=(e.axisRotate-e.labelRotate)/180*Math.PI,r=t.scale,o=r.getExtent(),a=r.count();if(o[1]-o[0]<1)return 0;var s=1;a>40&&(s=Math.max(1,Math.floor(a/40)));for(var l=o[0],u=t.dataToCoord(l+1)-t.dataToCoord(l),h=Math.abs(u*Math.cos(i)),c=Math.abs(u*Math.sin(i)),p=0,d=0;l<=o[1];l+=s){var f,g,y=_r(n({value:l}),e.font,"center","top");f=1.3*y.width,g=1.3*y.height,p=Math.max(p,f,7),d=Math.max(d,g,7)}var v=p/h,m=d/c;isNaN(v)&&(v=1/0),isNaN(m)&&(m=1/0);var x=Math.max(0,Math.floor(Math.min(v,m))),_=B_(t.model),b=t.getExtent(),w=_.lastAutoInterval,S=_.lastTickCount;return null!=w&&null!=S&&Math.abs(w-x)<=1&&Math.abs(S-a)<=1&&w>x&&_.axisExtent0===b[0]&&_.axisExtent1===b[1]?x=w:(_.lastTickCount=a,_.lastAutoInterval=x,_.axisExtent0=b[0],_.axisExtent1=b[1]),x}(this)},t}();function K_(t,e){var n=(t[1]-t[0])/e/2;t[0]+=n,t[1]-=n}var $_=2*Math.PI,J_=rs.CMD,Q_=["top","right","bottom","left"];function tb(t,e,n,i,r){var o=n.width,a=n.height;switch(t){case"top":i.set(n.x+o/2,n.y-e),r.set(0,-1);break;case"bottom":i.set(n.x+o/2,n.y+a+e),r.set(0,1);break;case"left":i.set(n.x-e,n.y+a/2),r.set(-1,0);break;case"right":i.set(n.x+o+e,n.y+a/2),r.set(1,0)}}function eb(t,e,n,i,r,o,a,s,l){a-=t,s-=e;var u=Math.sqrt(a*a+s*s),h=(a/=u)*n+t,c=(s/=u)*n+e;if(Math.abs(i-r)%$_<1e-4)return l[0]=h,l[1]=c,u-n;if(o){var p=i;i=us(r),r=us(p)}else i=us(i),r=us(r);i>r&&(r+=$_);var d=Math.atan2(s,a);if(d<0&&(d+=$_),d>=i&&d<=r||d+$_>=i&&d+$_<=r)return l[0]=h,l[1]=c,u-n;var f=n*Math.cos(i)+t,g=n*Math.sin(i)+e,y=n*Math.cos(r)+t,v=n*Math.sin(r)+e,m=(f-a)*(f-a)+(g-s)*(g-s),x=(y-a)*(y-a)+(v-s)*(v-s);return m0){e=e/180*Math.PI,sb.fromArray(t[0]),lb.fromArray(t[1]),ub.fromArray(t[2]),Ce.sub(hb,sb,lb),Ce.sub(cb,ub,lb);var n=hb.len(),i=cb.len();if(!(n<.001||i<.001)){hb.scale(1/n),cb.scale(1/i);var r=hb.dot(cb);if(Math.cos(e)1&&Ce.copy(fb,ub),fb.toArray(t[1])}}}}function yb(t,e,n){if(n<=180&&n>0){n=n/180*Math.PI,sb.fromArray(t[0]),lb.fromArray(t[1]),ub.fromArray(t[2]),Ce.sub(hb,lb,sb),Ce.sub(cb,ub,lb);var i=hb.len(),r=cb.len();if(!(i<.001||r<.001))if(hb.scale(1/i),cb.scale(1/r),hb.dot(e)=a)Ce.copy(fb,ub);else{fb.scaleAndAdd(cb,o/Math.tan(Math.PI/2-s));var l=ub.x!==lb.x?(fb.x-lb.x)/(ub.x-lb.x):(fb.y-lb.y)/(ub.y-lb.y);if(isNaN(l))return;l<0?Ce.copy(fb,lb):l>1&&Ce.copy(fb,ub)}fb.toArray(t[1])}}}function vb(t,e,n,i){var r="normal"===n,o=r?t:t.ensureState(n);o.ignore=e;var a=i.get("smooth");a&&!0===a&&(a=.3),o.shape=o.shape||{},a>0&&(o.shape.smooth=a);var s=i.getModel("lineStyle").getLineStyle();r?t.useStyle(s):o.style=s}function mb(t,e){var n=e.smooth,i=e.points;if(i)if(t.moveTo(i[0][0],i[0][1]),n>0&&i.length>=3){var r=Vt(i[0],i[1]),o=Vt(i[1],i[2]);if(!r||!o)return t.lineTo(i[1][0],i[1][1]),void t.lineTo(i[2][0],i[2][1]);var a=Math.min(r,o)*n,s=Gt([],i[1],i[0],a/r),l=Gt([],i[1],i[2],a/o),u=Gt([],s,l,.5);t.bezierCurveTo(s[0],s[1],s[0],s[1],u[0],u[1]),t.bezierCurveTo(l[0],l[1],l[0],l[1],i[2][0],i[2][1])}else for(var h=1;h0&&o&&_(-h/a,0,a);var f,g,y=t[0],v=t[a-1];return m(),f<0&&b(-f,.8),g<0&&b(g,.8),m(),x(f,g,1),x(g,f,-1),m(),f<0&&w(-f),g<0&&w(g),u}function m(){f=y.rect[e]-i,g=r-v.rect[e]-v.rect[n]}function x(t,e,n){if(t<0){var i=Math.min(e,-t);if(i>0){_(i*n,0,a);var r=i+t;r<0&&b(-r*n,1)}else b(-t*n,1)}}function _(n,i,r){0!==n&&(u=!0);for(var o=i;o0)for(l=0;l0;l--){_(-(o[l-1]*c),l,a)}}}function w(t){var e=t<0?-1:1;t=Math.abs(t);for(var n=Math.ceil(t/(a-1)),i=0;i0?_(n,0,i+1):_(-n,a-i-1,a),(t-=n)<=0)return}}function Sb(t,e,n,i){return wb(t,"y","height",e,n,i)}function Mb(t){var e=[];t.sort((function(t,e){return e.priority-t.priority}));var n=new Ee(0,0,0,0);function i(t){if(!t.ignore){var e=t.ensureState("emphasis");null==e.ignore&&(e.ignore=!1)}t.ignore=!0}for(var r=0;r=0&&n.attr(d.oldLayoutSelect),P(u,"emphasis")>=0&&n.attr(d.oldLayoutEmphasis)),dh(n,s,e,a)}else if(n.attr(s),!lc(n).valueAnimation){var h=rt(n.style.opacity,1);n.style.opacity=0,fh(n,{style:{opacity:h}},e,a)}if(d.oldLayout=s,n.states.select){var c=d.oldLayoutSelect={};Lb(c,s,Pb),Lb(c,n.states.select,Pb)}if(n.states.emphasis){var p=d.oldLayoutEmphasis={};Lb(p,s,Pb),Lb(p,n.states.emphasis,Pb)}hc(n,a,l,e,e)}if(i&&!i.ignore&&!i.invisible){r=(d=kb(i)).oldLayout;var d,f={points:i.shape.points};r?(i.attr({shape:r}),dh(i,{shape:f},e)):(i.setShape(f),i.style.strokePercent=0,fh(i,{style:{strokePercent:1}},e)),d.oldLayout=f}},t}(),Rb=Po();var Nb=Math.sin,Eb=Math.cos,zb=Math.PI,Vb=2*Math.PI,Bb=180/zb,Fb=function(){function t(){}return t.prototype.reset=function(t){this._start=!0,this._d=[],this._str="",this._p=Math.pow(10,t||4)},t.prototype.moveTo=function(t,e){this._add("M",t,e)},t.prototype.lineTo=function(t,e){this._add("L",t,e)},t.prototype.bezierCurveTo=function(t,e,n,i,r,o){this._add("C",t,e,n,i,r,o)},t.prototype.quadraticCurveTo=function(t,e,n,i){this._add("Q",t,e,n,i)},t.prototype.arc=function(t,e,n,i,r,o){this.ellipse(t,e,n,n,0,i,r,o)},t.prototype.ellipse=function(t,e,n,i,r,o,a,s){var l=a-o,u=!s,h=Math.abs(l),c=ui(h-Vb)||(u?l>=Vb:-l>=Vb),p=l>0?l%Vb:l%Vb+Vb,d=!1;d=!!c||!ui(h)&&p>=zb==!!u;var f=t+n*Eb(o),g=e+i*Nb(o);this._start&&this._add("M",f,g);var y=Math.round(r*Bb);if(c){var v=1/this._p,m=(u?1:-1)*(Vb-v);this._add("A",n,i,y,1,+u,t+n*Eb(o+m),e+i*Nb(o+m)),v>.01&&this._add("A",n,i,y,0,+u,f,g)}else{var x=t+n*Eb(a),_=e+i*Nb(a);this._add("A",n,i,y,+d,+u,x,_)}},t.prototype.rect=function(t,e,n,i){this._add("M",t,e),this._add("l",n,0),this._add("l",0,i),this._add("l",-n,0),this._add("Z")},t.prototype.closePath=function(){this._d.length>0&&this._add("Z")},t.prototype._add=function(t,e,n,i,r,o,a,s,l){for(var u=[],h=this._p,c=1;c"}(r,e.attrs)+ie(e.text)+(i?""+n+z(i,(function(e){return t(e)})).join(n)+n:"")+("")}(t)}function $b(t){return{zrId:t,shadowCache:{},patternCache:{},gradientCache:{},clipPathCache:{},defs:{},cssNodes:{},cssAnims:{},cssClassIdx:0,cssAnimIdx:0,shadowIdx:0,gradientIdx:0,patternIdx:0,clipPathIdx:0}}function Jb(t,e,n,i){return qb("svg","root",{width:t,height:e,xmlns:Xb,"xmlns:xlink":Zb,version:"1.1",baseProfile:"full",viewBox:!!i&&"0 0 "+t+" "+e},n)}var Qb={cubicIn:"0.32,0,0.67,0",cubicOut:"0.33,1,0.68,1",cubicInOut:"0.65,0,0.35,1",quadraticIn:"0.11,0,0.5,0",quadraticOut:"0.5,1,0.89,1",quadraticInOut:"0.45,0,0.55,1",quarticIn:"0.5,0,0.75,0",quarticOut:"0.25,1,0.5,1",quarticInOut:"0.76,0,0.24,1",quinticIn:"0.64,0,0.78,0",quinticOut:"0.22,1,0.36,1",quinticInOut:"0.83,0,0.17,1",sinusoidalIn:"0.12,0,0.39,0",sinusoidalOut:"0.61,1,0.88,1",sinusoidalInOut:"0.37,0,0.63,1",exponentialIn:"0.7,0,0.84,0",exponentialOut:"0.16,1,0.3,1",exponentialInOut:"0.87,0,0.13,1",circularIn:"0.55,0,1,0.45",circularOut:"0,0.55,0.45,1",circularInOut:"0.85,0,0.15,1"},tw="transform-origin";function ew(t,e,n){var i=A({},t.shape);A(i,e),t.buildPath(n,i);var r=new Fb;return r.reset(xi(t)),n.rebuildPath(r,1),r.generateStr(),r.getStr()}function nw(t,e){var n=e.originX,i=e.originY;(n||i)&&(t[tw]=n+"px "+i+"px")}var iw={fill:"fill",opacity:"opacity",lineWidth:"stroke-width",lineDashOffset:"stroke-dashoffset"};function rw(t,e){var n=e.zrId+"-ani-"+e.cssAnimIdx++;return e.cssAnims[n]=t,n}function ow(t){return X(t)?Qb[t]?"cubic-bezier("+Qb[t]+")":Ln(t)?t:"":""}function aw(t,e,n,i){var r=t.animators,o=r.length,a=[];if(t instanceof Qu){var s=function(t,e,n){var i,r,o=t.shape.paths,a={};if(E(o,(function(t){var e=$b(n.zrId);e.animation=!0,aw(t,{},e,!0);var o=e.cssAnims,s=e.cssNodes,l=G(o),u=l.length;if(u){var h=o[r=l[u-1]];for(var c in h){var p=h[c];a[c]=a[c]||{d:""},a[c].d+=p.d||""}for(var d in s){var f=s[d].animation;f.indexOf(r)>=0&&(i=f)}}})),i){e.d=!1;var s=rw(a,n);return i.replace(r,s)}}(t,e,n);if(s)a.push(s);else if(!o)return}else if(!o)return;for(var l={},u=0;u0})).length)return rw(h,n)+" "+r[0]+" both"}for(var y in l){(s=g(l[y]))&&a.push(s)}if(a.length){var v=n.zrId+"-cls-"+n.cssClassIdx++;n.cssNodes["."+v]={animation:a.join(",")},e.class=v}}var sw=Math.round;function lw(t){return t&&X(t.src)}function uw(t){return t&&U(t.toDataURL)}function hw(t,e,n,i){Ub((function(r,o){var a="fill"===r||"stroke"===r;a&&vi(o)?_w(e,t,r,i):a&&fi(o)?bw(n,t,r,i):t[r]=o}),e,n,!1),function(t,e,n){var i=t.style;if(function(t){return t&&(t.shadowBlur||t.shadowOffsetX||t.shadowOffsetY)}(i)){var r=function(t){var e=t.style,n=t.getGlobalScale();return[e.shadowColor,(e.shadowBlur||0).toFixed(2),(e.shadowOffsetX||0).toFixed(2),(e.shadowOffsetY||0).toFixed(2),n[0],n[1]].join(",")}(t),o=n.shadowCache,a=o[r];if(!a){var s=t.getGlobalScale(),l=s[0],u=s[1];if(!l||!u)return;var h=i.shadowOffsetX||0,c=i.shadowOffsetY||0,p=i.shadowBlur,d=si(i.shadowColor),f=d.opacity,g=d.color,y=p/2/l+" "+p/2/u;a=n.zrId+"-s"+n.shadowIdx++,n.defs[a]=qb("filter",a,{id:a,x:"-100%",y:"-100%",width:"300%",height:"300%"},[qb("feDropShadow","",{dx:h/l,dy:c/u,stdDeviation:y,"flood-color":g,"flood-opacity":f})]),o[r]=a}e.filter=mi(a)}}(n,t,i)}function cw(t){return ui(t[0]-1)&&ui(t[1])&&ui(t[2])&&ui(t[3]-1)}function pw(t,e,n){if(e&&(!function(t){return ui(t[4])&&ui(t[5])}(e)||!cw(e))){var i=n?10:1e4;t.transform=cw(e)?"translate("+sw(e[4]*i)/i+" "+sw(e[5]*i)/i+")":function(t){return"matrix("+hi(t[0])+","+hi(t[1])+","+hi(t[2])+","+hi(t[3])+","+ci(t[4])+","+ci(t[5])+")"}(e)}}function dw(t,e,n){for(var i=t.points,r=[],o=0;ol?Ew(t,null==n[c+1]?null:n[c+1].elm,n,s,c):zw(t,e,a,l))}(n,i,r):Pw(r)?(Pw(t.text)&&Aw(n,""),Ew(n,null,r,0,r.length-1)):Pw(i)?zw(n,i,0,i.length-1):Pw(t.text)&&Aw(n,""):t.text!==e.text&&(Pw(i)&&zw(n,i,0,i.length-1),Aw(n,e.text)))}var Fw=0,Gw=function(){function t(t,e,n){if(this.type="svg",this.refreshHover=Ww("refreshHover"),this.configLayer=Ww("configLayer"),this.storage=e,this._opts=n=A({},n),this.root=t,this._id="zr"+Fw++,this._oldVNode=Jb(n.width,n.height),t&&!n.ssr){var i=this._viewport=document.createElement("div");i.style.cssText="position:relative;overflow:hidden";var r=this._svgDom=this._oldVNode.elm=jb("svg");Vw(null,this._oldVNode),i.appendChild(r),t.appendChild(i)}this.resize(n.width,n.height)}return t.prototype.getType=function(){return this.type},t.prototype.getViewportRoot=function(){return this._viewport},t.prototype.getViewportRootOffset=function(){var t=this.getViewportRoot();if(t)return{offsetLeft:t.offsetLeft||0,offsetTop:t.offsetTop||0}},t.prototype.getSvgDom=function(){return this._svgDom},t.prototype.refresh=function(){if(this.root){var t=this.renderToVNode({willUpdate:!0});t.attrs.style="position:absolute;left:0;top:0;user-select:none",function(t,e){if(Rw(t,e))Bw(t,e);else{var n=t.elm,i=Cw(n);Nw(e),null!==i&&(Mw(i,e.elm,Dw(n)),zw(i,[t],0,0))}}(this._oldVNode,t),this._oldVNode=t}},t.prototype.renderOneToVNode=function(t){return xw(t,$b(this._id))},t.prototype.renderToVNode=function(t){t=t||{};var e=this.storage.getDisplayList(!0),n=this._width,i=this._height,r=$b(this._id);r.animation=t.animation,r.willUpdate=t.willUpdate,r.compress=t.compress;var o=[],a=this._bgVNode=function(t,e,n,i){var r;if(n&&"none"!==n)if(r=qb("rect","bg",{width:t,height:e,x:"0",y:"0",id:"0"}),vi(n))_w({fill:n},r.attrs,"fill",i);else if(fi(n))bw({style:{fill:n},dirty:bt,getBoundingRect:function(){return{width:t,height:e}}},r.attrs,"fill",i);else{var o=si(n),a=o.color,s=o.opacity;r.attrs.fill=a,s<1&&(r.attrs["fill-opacity"]=s)}return r}(n,i,this._backgroundColor,r);a&&o.push(a);var s=t.compress?null:this._mainVNode=qb("g","main",{},[]);this._paintList(e,r,s?s.children:o),s&&o.push(s);var l=z(G(r.defs),(function(t){return r.defs[t]}));if(l.length&&o.push(qb("defs","defs",{},l)),t.animation){var u=function(t,e,n){var i=(n=n||{}).newline?"\n":"",r=" {"+i,o=i+"}",a=z(G(t),(function(e){return e+r+z(G(t[e]),(function(n){return n+":"+t[e][n]+";"})).join(i)+o})).join(i),s=z(G(e),(function(t){return"@keyframes "+t+r+z(G(e[t]),(function(n){return n+r+z(G(e[t][n]),(function(i){var r=e[t][n][i];return"d"===i&&(r='path("'+r+'")'),i+":"+r+";"})).join(i)+o})).join(i)+o})).join(i);return a||s?[""].join(i):""}(r.cssNodes,r.cssAnims,{newline:!0});if(u){var h=qb("style","stl",{},[],u);o.push(h)}}return Jb(n,i,o,t.useViewBox)},t.prototype.renderToString=function(t){return t=t||{},Kb(this.renderToVNode({animation:rt(t.cssAnimation,!0),willUpdate:!1,compress:!0,useViewBox:rt(t.useViewBox,!0)}),{newline:!0})},t.prototype.setBackgroundColor=function(t){this._backgroundColor=t},t.prototype.getSvgRoot=function(){return this._mainVNode&&this._mainVNode.elm},t.prototype._paintList=function(t,e,n){for(var i,r,o=t.length,a=[],s=0,l=0,u=0;u=0&&(!c||!r||c[f]!==r[f]);f--);for(var g=d-1;g>f;g--)i=a[--s-1];for(var y=f+1;y=a)}}for(var h=this.__startIndex;h15)break}n.prevElClipPaths&&u.restore()};if(p)if(0===p.length)s=l.__endIndex;else for(var _=d.dpr,b=0;b0&&t>i[0]){for(s=0;st);s++);a=n[i[s]]}if(i.splice(s+1,0,t),n[t]=e,!e.virtual)if(a){var l=a.dom;l.nextSibling?o.insertBefore(e.dom,l.nextSibling):o.appendChild(e.dom)}else o.firstChild?o.insertBefore(e.dom,o.firstChild):o.appendChild(e.dom);e.__painter=this}},t.prototype.eachLayer=function(t,e){for(var n=this._zlevelList,i=0;i0?Zw:0),this._needsManuallyCompositing),u.__builtin__||I("ZLevel "+l+" has been used by unkown layer "+u.id),u!==o&&(u.__used=!0,u.__startIndex!==r&&(u.__dirty=!0),u.__startIndex=r,u.incremental?u.__drawIndex=-1:u.__drawIndex=r,e(r),o=u),1&s.__dirty&&!s.__inHover&&(u.__dirty=!0,u.incremental&&u.__drawIndex<0&&(u.__drawIndex=r))}e(r),this.eachBuiltinLayer((function(t,e){!t.__used&&t.getElementCount()>0&&(t.__dirty=!0,t.__startIndex=t.__endIndex=t.__drawIndex=0),t.__dirty&&t.__drawIndex<0&&(t.__drawIndex=t.__startIndex)}))},t.prototype.clear=function(){return this.eachBuiltinLayer(this._clearLayer),this},t.prototype._clearLayer=function(t){t.clear()},t.prototype.setBackgroundColor=function(t){this._backgroundColor=t,E(this._layers,(function(t){t.setUnpainted()}))},t.prototype.configLayer=function(t,e){if(e){var n=this._layerConfig;n[t]?C(n[t],e,!0):n[t]=e;for(var i=0;i-1&&(s.style.stroke=s.style.fill,s.style.fill="#fff",s.style.lineWidth=2),e},e.type="series.line",e.dependencies=["grid","polar"],e.defaultOption={z:3,coordinateSystem:"cartesian2d",legendHoverLink:!0,clip:!0,label:{position:"top"},endLabel:{show:!1,valueAnimation:!0,distance:8},lineStyle:{width:2,type:"solid"},emphasis:{scale:!0},step:!1,smooth:!1,smoothMonotone:null,symbol:"emptyCircle",symbolSize:4,symbolRotate:null,showSymbol:!0,showAllSymbol:"auto",connectNulls:!1,sampling:"none",animationEasing:"linear",progressive:0,hoverLayerThreshold:1/0,universalTransition:{divideShape:"clone"},triggerLineEvent:!1},e}(fg);function Kw(t,e){var n=t.mapDimensionsAll("defaultedLabel"),i=n.length;if(1===i){var r=df(t,e,n[0]);return null!=r?r+"":null}if(i){for(var o=[],a=0;a=0&&i.push(e[o])}return i.join(" ")}var Jw=function(t){function e(e,n,i,r){var o=t.call(this)||this;return o.updateData(e,n,i,r),o}return n(e,t),e.prototype._createSymbol=function(t,e,n,i,r){this.removeAll();var o=Vy(t,-1,-1,2,2,null,r);o.attr({z2:100,culling:!0,scaleX:i[0]/2,scaleY:i[1]/2}),o.drift=Qw,this._symbolType=t,this.add(o)},e.prototype.stopSymbolAnimation=function(t){this.childAt(0).stopAnimation(null,t)},e.prototype.getSymbolType=function(){return this._symbolType},e.prototype.getSymbolPath=function(){return this.childAt(0)},e.prototype.highlight=function(){Al(this.childAt(0))},e.prototype.downplay=function(){kl(this.childAt(0))},e.prototype.setZ=function(t,e){var n=this.childAt(0);n.zlevel=t,n.z=e},e.prototype.setDraggable=function(t,e){var n=this.childAt(0);n.draggable=t,n.cursor=!e&&t?"move":n.cursor},e.prototype.updateData=function(t,n,i,r){this.silent=!1;var o=t.getItemVisual(n,"symbol")||"circle",a=t.hostModel,s=e.getSymbolSize(t,n),l=o!==this._symbolType,u=r&&r.disableAnimation;if(l){var h=t.getItemVisual(n,"symbolKeepAspect");this._createSymbol(o,t,n,s,h)}else{(p=this.childAt(0)).silent=!1;var c={scaleX:s[0]/2,scaleY:s[1]/2};u?p.attr(c):dh(p,c,a,n),xh(p)}if(this._updateCommon(t,n,s,i,r),l){var p=this.childAt(0);if(!u){c={scaleX:this._sizeX,scaleY:this._sizeY,style:{opacity:p.style.opacity}};p.scaleX=p.scaleY=0,p.style.opacity=0,fh(p,c,a,n)}}u&&this.childAt(0).stopAnimation("leave")},e.prototype._updateCommon=function(t,e,n,i,r){var o,a,s,l,u,h,c,p,d,f=this.childAt(0),g=t.hostModel;if(i&&(o=i.emphasisItemStyle,a=i.blurItemStyle,s=i.selectItemStyle,l=i.focus,u=i.blurScope,c=i.labelStatesModels,p=i.hoverScale,d=i.cursorStyle,h=i.emphasisDisabled),!i||t.hasItemOption){var y=i&&i.itemModel?i.itemModel:t.getItemModel(e),v=y.getModel("emphasis");o=v.getModel("itemStyle").getItemStyle(),s=y.getModel(["select","itemStyle"]).getItemStyle(),a=y.getModel(["blur","itemStyle"]).getItemStyle(),l=v.get("focus"),u=v.get("blurScope"),h=v.get("disabled"),c=tc(y),p=v.getShallow("scale"),d=y.getShallow("cursor")}var m=t.getItemVisual(e,"symbolRotate");f.attr("rotation",(m||0)*Math.PI/180||0);var x=Fy(t.getItemVisual(e,"symbolOffset"),n);x&&(f.x=x[0],f.y=x[1]),d&&f.attr("cursor",d);var _=t.getItemVisual(e,"style"),b=_.fill;if(f instanceof As){var w=f.style;f.useStyle(A({image:w.image,x:w.x,y:w.y,width:w.width,height:w.height},_))}else f.__isEmptyBrush?f.useStyle(A({},_)):f.useStyle(_),f.style.decal=null,f.setColor(b,r&&r.symbolInnerColor),f.style.strokeNoScale=!0;var S=t.getItemVisual(e,"liftZ"),M=this._z2;null!=S?null==M&&(this._z2=f.z2,f.z2+=S):null!=M&&(f.z2=M,this._z2=null);var I=r&&r.useNameLabel;Qh(f,c,{labelFetcher:g,labelDataIndex:e,defaultText:function(e){return I?t.getName(e):Kw(t,e)},inheritColor:b,defaultOpacity:_.opacity}),this._sizeX=n[0]/2,this._sizeY=n[1]/2;var T=f.ensureState("emphasis");T.style=o,f.ensureState("select").style=s,f.ensureState("blur").style=a;var C=null==p||!0===p?Math.max(1.1,3/this._sizeY):isFinite(p)&&p>0?+p:1;T.scaleX=this._sizeX*C,T.scaleY=this._sizeY*C,this.setSymbolScale(1),Hl(this,l,u,h)},e.prototype.setSymbolScale=function(t){this.scaleX=this.scaleY=t},e.prototype.fadeOut=function(t,e,n){var i=this.childAt(0),r=Js(this).dataIndex,o=n&&n.animation;if(this.silent=i.silent=!0,n&&n.fadeLabel){var a=i.getTextContent();a&&yh(a,{style:{opacity:0}},e,{dataIndex:r,removeOpt:o,cb:function(){i.removeTextContent()}})}else i.removeTextContent();yh(i,{style:{opacity:0},scaleX:0,scaleY:0},e,{dataIndex:r,cb:t,removeOpt:o})},e.getSymbolSize=function(t,e){return By(t.getItemVisual(e,"symbolSize"))},e}(Er);function Qw(t,e){this.parent.drift(t,e)}function tS(t,e,n,i){return e&&!isNaN(e[0])&&!isNaN(e[1])&&!(i.isIgnore&&i.isIgnore(n))&&!(i.clipShape&&!i.clipShape.contain(e[0],e[1]))&&"none"!==t.getItemVisual(n,"symbol")}function eS(t){return null==t||q(t)||(t={isIgnore:t}),t||{}}function nS(t){var e=t.hostModel,n=e.getModel("emphasis");return{emphasisItemStyle:n.getModel("itemStyle").getItemStyle(),blurItemStyle:e.getModel(["blur","itemStyle"]).getItemStyle(),selectItemStyle:e.getModel(["select","itemStyle"]).getItemStyle(),focus:n.get("focus"),blurScope:n.get("blurScope"),emphasisDisabled:n.get("disabled"),hoverScale:n.get("scale"),labelStatesModels:tc(e),cursorStyle:e.get("cursor")}}var iS=function(){function t(t){this.group=new Er,this._SymbolCtor=t||Jw}return t.prototype.updateData=function(t,e){this._progressiveEls=null,e=eS(e);var n=this.group,i=t.hostModel,r=this._data,o=this._SymbolCtor,a=e.disableAnimation,s=nS(t),l={disableAnimation:a},u=e.getSymbolPoint||function(e){return t.getItemLayout(e)};r||n.removeAll(),t.diff(r).add((function(i){var r=u(i);if(tS(t,r,i,e)){var a=new o(t,i,s,l);a.setPosition(r),t.setItemGraphicEl(i,a),n.add(a)}})).update((function(h,c){var p=r.getItemGraphicEl(c),d=u(h);if(tS(t,d,h,e)){var f=t.getItemVisual(h,"symbol")||"circle",g=p&&p.getSymbolType&&p.getSymbolType();if(!p||g&&g!==f)n.remove(p),(p=new o(t,h,s,l)).setPosition(d);else{p.updateData(t,h,s,l);var y={x:d[0],y:d[1]};a?p.attr(y):dh(p,y,i)}n.add(p),t.setItemGraphicEl(h,p)}else n.remove(p)})).remove((function(t){var e=r.getItemGraphicEl(t);e&&e.fadeOut((function(){n.remove(e)}),i)})).execute(),this._getSymbolPoint=u,this._data=t},t.prototype.updateLayout=function(){var t=this,e=this._data;e&&e.eachItemGraphicEl((function(e,n){var i=t._getSymbolPoint(n);e.setPosition(i),e.markRedraw()}))},t.prototype.incrementalPrepareUpdate=function(t){this._seriesScope=nS(t),this._data=null,this.group.removeAll()},t.prototype.incrementalUpdate=function(t,e,n){function i(t){t.isGroup||(t.incremental=!0,t.ensureState("emphasis").hoverLayer=!0)}this._progressiveEls=[],n=eS(n);for(var r=t.start;r0?n=i[0]:i[1]<0&&(n=i[1]);return n}(r,n),a=i.dim,s=r.dim,l=e.mapDimension(s),u=e.mapDimension(a),h="x"===s||"radius"===s?1:0,c=z(t.dimensions,(function(t){return e.mapDimension(t)})),p=!1,d=e.getCalculationInfo("stackResultDimension");return lx(e,c[0])&&(p=!0,c[0]=d),lx(e,c[1])&&(p=!0,c[1]=d),{dataDimsForPoint:c,valueStart:o,valueAxisDim:s,baseAxisDim:a,stacked:!!p,valueDim:l,baseDim:u,baseDataOffset:h,stackedOverDimension:e.getCalculationInfo("stackedOverDimension")}}function oS(t,e,n,i){var r=NaN;t.stacked&&(r=n.get(n.getCalculationInfo("stackedOverDimension"),i)),isNaN(r)&&(r=t.valueStart);var o=t.baseDataOffset,a=[];return a[o]=n.get(t.baseDim,i),a[1-o]=r,e.dataToPoint(a)}var aS=Math.min,sS=Math.max;function lS(t,e){return isNaN(t)||isNaN(e)}function uS(t,e,n,i,r,o,a,s,l){for(var u,h,c,p,d,f,g=n,y=0;y=r||g<0)break;if(lS(v,m)){if(l){g+=o;continue}break}if(g===n)t[o>0?"moveTo":"lineTo"](v,m),c=v,p=m;else{var x=v-u,_=m-h;if(x*x+_*_<.5){g+=o;continue}if(a>0){for(var b=g+o,w=e[2*b],S=e[2*b+1];w===v&&S===m&&y=i||lS(w,S))d=v,f=m;else{T=w-u,C=S-h;var k=v-u,L=w-v,P=m-h,O=S-m,R=void 0,N=void 0;if("x"===s){var E=T>0?1:-1;d=v-E*(R=Math.abs(k))*a,f=m,D=v+E*(N=Math.abs(L))*a,A=m}else if("y"===s){var z=C>0?1:-1;d=v,f=m-z*(R=Math.abs(P))*a,D=v,A=m+z*(N=Math.abs(O))*a}else R=Math.sqrt(k*k+P*P),d=v-T*a*(1-(I=(N=Math.sqrt(L*L+O*O))/(N+R))),f=m-C*a*(1-I),A=m+C*a*I,D=aS(D=v+T*a*I,sS(w,v)),A=aS(A,sS(S,m)),D=sS(D,aS(w,v)),f=m-(C=(A=sS(A,aS(S,m)))-m)*R/N,d=aS(d=v-(T=D-v)*R/N,sS(u,v)),f=aS(f,sS(h,m)),D=v+(T=v-(d=sS(d,aS(u,v))))*N/R,A=m+(C=m-(f=sS(f,aS(h,m))))*N/R}t.bezierCurveTo(c,p,d,f,v,m),c=D,p=A}else t.lineTo(v,m)}u=v,h=m,g+=o}return y}var hS=function(){this.smooth=0,this.smoothConstraint=!0},cS=function(t){function e(e){var n=t.call(this,e)||this;return n.type="ec-polyline",n}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:"#000",fill:null}},e.prototype.getDefaultShape=function(){return new hS},e.prototype.buildPath=function(t,e){var n=e.points,i=0,r=n.length/2;if(e.connectNulls){for(;r>0&&lS(n[2*r-2],n[2*r-1]);r--);for(;i=0){var y=a?(h-i)*g+i:(u-n)*g+n;return a?[t,y]:[y,t]}n=u,i=h;break;case o.C:u=r[l++],h=r[l++],c=r[l++],p=r[l++],d=r[l++],f=r[l++];var v=a?xn(n,u,c,d,t,s):xn(i,h,p,f,t,s);if(v>0)for(var m=0;m=0){y=a?vn(i,h,p,f,x):vn(n,u,c,d,x);return a?[t,y]:[y,t]}}n=d,i=f}}},e}(Ms),pS=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e}(hS),dS=function(t){function e(e){var n=t.call(this,e)||this;return n.type="ec-polygon",n}return n(e,t),e.prototype.getDefaultShape=function(){return new pS},e.prototype.buildPath=function(t,e){var n=e.points,i=e.stackedOnPoints,r=0,o=n.length/2,a=e.smoothMonotone;if(e.connectNulls){for(;o>0&&lS(n[2*o-2],n[2*o-1]);o--);for(;r=0;a--){var s=t.getDimensionInfo(i[a].dimension);if("x"===(r=s&&s.coordDim)||"y"===r){o=i[a];break}}if(o){var l=e.getAxis(r),u=z(o.stops,(function(t){return{coord:l.toGlobalCoord(l.dataToCoord(t.value)),color:t.color}})),h=u.length,c=o.outerColors.slice();h&&u[0].coord>u[h-1].coord&&(u.reverse(),c.reverse());var p=function(t,e){var n,i,r=[],o=t.length;function a(t,e,n){var i=t.coord;return{coord:n,color:Qn((n-i)/(e.coord-i),[t.color,e.color])}}for(var s=0;se){i?r.push(a(i,l,e)):n&&r.push(a(n,l,0),a(n,l,e));break}n&&(r.push(a(n,l,0)),n=null),r.push(l),i=l}}return r}(u,"x"===r?n.getWidth():n.getHeight()),d=p.length;if(!d&&h)return u[0].coord<0?c[1]?c[1]:u[h-1].color:c[0]?c[0]:u[0].color;var f=p[0].coord-10,g=p[d-1].coord+10,y=g-f;if(y<.001)return"transparent";E(p,(function(t){t.offset=(t.coord-f)/y})),p.push({offset:d?p[d-1].offset:.5,color:c[1]||"transparent"}),p.unshift({offset:d?p[0].offset:.5,color:c[0]||"transparent"});var v=new eh(0,0,0,0,p,!0);return v[r]=f,v[r+"2"]=g,v}}}function MS(t,e,n){var i=t.get("showAllSymbol"),r="auto"===i;if(!i||r){var o=n.getAxesByScale("ordinal")[0];if(o&&(!r||!function(t,e){var n=t.getExtent(),i=Math.abs(n[1]-n[0])/t.scale.count();isNaN(i)&&(i=0);for(var r=e.count(),o=Math.max(1,Math.round(r/5)),a=0;ai)return!1;return!0}(o,e))){var a=e.mapDimension(o.dim),s={};return E(o.getViewLabels(),(function(t){var e=o.scale.getRawOrdinalNumber(t.tickValue);s[e]=1})),function(t){return!s.hasOwnProperty(e.get(a,t))}}}}function IS(t,e){return[t[2*e],t[2*e+1]]}function TS(t){if(t.get(["endLabel","show"]))return!0;for(var e=0;e0&&"bolder"===t.get(["emphasis","lineStyle","width"]))&&(d.getState("emphasis").style.lineWidth=+d.style.lineWidth+1);Js(d).seriesIndex=t.seriesIndex,Hl(d,L,P,O);var R=bS(t.get("smooth")),N=t.get("smoothMonotone");if(d.setShape({smooth:R,smoothMonotone:N,connectNulls:w}),f){var E=a.getCalculationInfo("stackedOnSeries"),z=0;f.useStyle(k(l.getAreaStyle(),{fill:C,opacity:.7,lineJoin:"bevel",decal:a.getVisual("style").decal})),E&&(z=bS(E.get("smooth"))),f.setShape({smooth:R,stackedOnSmooth:z,smoothMonotone:N,connectNulls:w}),Zl(f,t,"areaStyle"),Js(f).seriesIndex=t.seriesIndex,Hl(f,L,P,O)}var V=function(t){i._changePolyState(t)};a.eachItemGraphicEl((function(t){t&&(t.onHoverStateChange=V)})),this._polyline.onHoverStateChange=V,this._data=a,this._coordSys=r,this._stackedOnPoints=_,this._points=u,this._step=T,this._valueOrigin=m,t.get("triggerLineEvent")&&(this.packEventData(t,d),f&&this.packEventData(t,f))},e.prototype.packEventData=function(t,e){Js(e).eventData={componentType:"series",componentSubType:"line",componentIndex:t.componentIndex,seriesIndex:t.seriesIndex,seriesName:t.name,seriesType:"line"}},e.prototype.highlight=function(t,e,n,i){var r=t.getData(),o=Lo(r,i);if(this._changePolyState("emphasis"),!(o instanceof Array)&&null!=o&&o>=0){var a=r.getLayout("points"),s=r.getItemGraphicEl(o);if(!s){var l=a[2*o],u=a[2*o+1];if(isNaN(l)||isNaN(u))return;if(this._clipShapeForSymbol&&!this._clipShapeForSymbol.contain(l,u))return;var h=t.get("zlevel")||0,c=t.get("z")||0;(s=new Jw(r,o)).x=l,s.y=u,s.setZ(h,c);var p=s.getSymbolPath().getTextContent();p&&(p.zlevel=h,p.z=c,p.z2=this._polyline.z2+1),s.__temp=!0,r.setItemGraphicEl(o,s),s.stopSymbolAnimation(!0),this.group.add(s)}s.highlight()}else Tg.prototype.highlight.call(this,t,e,n,i)},e.prototype.downplay=function(t,e,n,i){var r=t.getData(),o=Lo(r,i);if(this._changePolyState("normal"),null!=o&&o>=0){var a=r.getItemGraphicEl(o);a&&(a.__temp?(r.setItemGraphicEl(o,null),this.group.remove(a)):a.downplay())}else Tg.prototype.downplay.call(this,t,e,n,i)},e.prototype._changePolyState=function(t){var e=this._polygon;Ml(this._polyline,t),e&&Ml(e,t)},e.prototype._newPolyline=function(t){var e=this._polyline;return e&&this._lineGroup.remove(e),e=new cS({shape:{points:t},segmentIgnoreThreshold:2,z2:10}),this._lineGroup.add(e),this._polyline=e,e},e.prototype._newPolygon=function(t,e){var n=this._polygon;return n&&this._lineGroup.remove(n),n=new dS({shape:{points:t,stackedOnPoints:e},segmentIgnoreThreshold:2}),this._lineGroup.add(n),this._polygon=n,n},e.prototype._initSymbolLabelAnimation=function(t,e,n){var i,r,o=e.getBaseAxis(),a=o.inverse;"cartesian2d"===e.type?(i=o.isHorizontal(),r=!1):"polar"===e.type&&(i="angle"===o.dim,r=!0);var s=t.hostModel,l=s.get("animationDuration");U(l)&&(l=l(null));var u=s.get("animationDelay")||0,h=U(u)?u(null):u;t.eachItemGraphicEl((function(t,o){var s=t;if(s){var c=[t.x,t.y],p=void 0,d=void 0,f=void 0;if(n)if(r){var g=n,y=e.pointToCoord(c);i?(p=g.startAngle,d=g.endAngle,f=-y[1]/180*Math.PI):(p=g.r0,d=g.r,f=y[0])}else{var v=n;i?(p=v.x,d=v.x+v.width,f=t.x):(p=v.y+v.height,d=v.y,f=t.y)}var m=d===p?0:(f-p)/(d-p);a&&(m=1-m);var x=U(u)?u(o):l*m+h,_=s.getSymbolPath(),b=_.getTextContent();s.attr({scaleX:0,scaleY:0}),s.animateTo({scaleX:1,scaleY:1},{duration:200,setToFinal:!0,delay:x}),b&&b.animateFrom({style:{opacity:0}},{duration:300,delay:x}),_.disableLabelAnimation=!0}}))},e.prototype._initOrUpdateEndLabel=function(t,e,n){var i=t.getModel("endLabel");if(TS(t)){var r=t.getData(),o=this._polyline,a=r.getLayout("points");if(!a)return o.removeTextContent(),void(this._endLabel=null);var s=this._endLabel;s||((s=this._endLabel=new Bs({z2:200})).ignoreClip=!0,o.setTextContent(this._endLabel),o.disableLabelAnimation=!0);var l=function(t){for(var e,n,i=t.length/2;i>0&&(e=t[2*i-2],n=t[2*i-1],isNaN(e)||isNaN(n));i--);return i-1}(a);l>=0&&(Qh(o,tc(t,"endLabel"),{inheritColor:n,labelFetcher:t,labelDataIndex:l,defaultText:function(t,e,n){return null!=n?$w(r,n):Kw(r,t)},enableTextSetter:!0},function(t,e){var n=e.getBaseAxis(),i=n.isHorizontal(),r=n.inverse,o=i?r?"right":"left":"center",a=i?"middle":r?"top":"bottom";return{normal:{align:t.get("align")||o,verticalAlign:t.get("verticalAlign")||a}}}(i,e)),o.textConfig.position=null)}else this._endLabel&&(this._polyline.removeTextContent(),this._endLabel=null)},e.prototype._endLabelOnDuring=function(t,e,n,i,r,o,a){var s=this._endLabel,l=this._polyline;if(s){t<1&&null==i.originalX&&(i.originalX=s.x,i.originalY=s.y);var u=n.getLayout("points"),h=n.hostModel,c=h.get("connectNulls"),p=o.get("precision"),d=o.get("distance")||0,f=a.getBaseAxis(),g=f.isHorizontal(),y=f.inverse,v=e.shape,m=y?g?v.x:v.y+v.height:g?v.x+v.width:v.y,x=(g?d:0)*(y?-1:1),_=(g?0:-d)*(y?-1:1),b=g?"x":"y",w=function(t,e,n){for(var i,r,o=t.length/2,a="x"===n?0:1,s=0,l=-1,u=0;u=e||i>=e&&r<=e){l=u;break}s=u,i=r}else i=r;return{range:[s,l],t:(e-i)/(r-i)}}(u,m,b),S=w.range,M=S[1]-S[0],I=void 0;if(M>=1){if(M>1&&!c){var T=IS(u,S[0]);s.attr({x:T[0]+x,y:T[1]+_}),r&&(I=h.getRawValue(S[0]))}else{(T=l.getPointOn(m,b))&&s.attr({x:T[0]+x,y:T[1]+_});var C=h.getRawValue(S[0]),D=h.getRawValue(S[1]);r&&(I=Go(n,p,C,D,w.t))}i.lastFrameIndex=S[0]}else{var A=1===t||i.lastFrameIndex>0?S[0]:0;T=IS(u,A);r&&(I=h.getRawValue(A)),s.attr({x:T[0]+x,y:T[1]+_})}r&&lc(s).setLabelText(I)}},e.prototype._doUpdateAnimation=function(t,e,n,i,r,o,a){var s=this._polyline,l=this._polygon,u=t.hostModel,h=function(t,e,n,i,r,o,a,s){for(var l=function(t,e){var n=[];return e.diff(t).add((function(t){n.push({cmd:"+",idx:t})})).update((function(t,e){n.push({cmd:"=",idx:e,idx1:t})})).remove((function(t){n.push({cmd:"-",idx:t})})).execute(),n}(t,e),u=[],h=[],c=[],p=[],d=[],f=[],g=[],y=rS(r,e,a),v=t.getLayout("points")||[],m=e.getLayout("points")||[],x=0;x3e3||l&&_S(p,f)>3e3)return s.stopAnimation(),s.setShape({points:d}),void(l&&(l.stopAnimation(),l.setShape({points:d,stackedOnPoints:f})));s.shape.__points=h.current,s.shape.points=c;var g={shape:{points:d}};h.current!==c&&(g.shape.__points=h.next),s.stopAnimation(),dh(s,g,u),l&&(l.setShape({points:c,stackedOnPoints:p}),l.stopAnimation(),dh(l,{shape:{stackedOnPoints:f}},u),s.shape.points!==l.shape.points&&(l.shape.points=s.shape.points));for(var y=[],v=h.status,m=0;me&&(e=t[n]);return isFinite(e)?e:NaN},min:function(t){for(var e=1/0,n=0;n10&&"cartesian2d"===o.type&&r){var s=o.getBaseAxis(),l=o.getOtherAxis(s),u=s.getExtent(),h=n.getDevicePixelRatio(),c=Math.abs(u[1]-u[0])*(h||1),p=Math.round(a/c);if(isFinite(p)&&p>1){"lttb"===r&&t.setData(i.lttbDownSample(i.mapDimension(l.dim),1/p));var d=void 0;X(r)?d=kS[r]:U(r)&&(d=r),d&&t.setData(i.downSample(i.mapDimension(l.dim),1/p,d,LS))}}}}}var OS=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.getInitialData=function(t,e){return hx(null,this,{useEncodeDefaulter:!0})},e.prototype.getMarkerPosition=function(t,e,n){var i=this.coordinateSystem;if(i&&i.clampData){var r=i.dataToPoint(i.clampData(t));if(n)E(i.getAxes(),(function(n,o){if("category"===n.type){var a=n.getTicksCoords(),s=i.clampData(t)[o];!e||"x1"!==e[o]&&"y1"!==e[o]||(s+=1),s>a.length-1&&(s=a.length-1),s<0&&(s=0),a[s]&&(r[o]=n.toGlobalCoord(a[s].coord))}}));else{var o=this.getData(),a=o.getLayout("offset"),s=o.getLayout("size"),l=i.getBaseAxis().isHorizontal()?0:1;r[l]+=a+s/2}return r}return[NaN,NaN]},e.type="series.__base_bar__",e.defaultOption={z:2,coordinateSystem:"cartesian2d",legendHoverLink:!0,barMinHeight:0,barMinAngle:0,large:!1,largeThreshold:400,progressive:3e3,progressiveChunkMode:"mod"},e}(fg);fg.registerClass(OS);var RS=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.getInitialData=function(){return hx(null,this,{useEncodeDefaulter:!0,createInvertedIndices:!!this.get("realtimeSort",!0)||null})},e.prototype.getProgressive=function(){return!!this.get("large")&&this.get("progressive")},e.prototype.getProgressiveThreshold=function(){var t=this.get("progressiveThreshold"),e=this.get("largeThreshold");return e>t&&(t=e),t},e.prototype.brushSelector=function(t,e,n){return n.rect(e.getItemLayout(t))},e.type="series.bar",e.dependencies=["grid","polar"],e.defaultOption=Tc(OS.defaultOption,{clip:!0,roundCap:!1,showBackground:!1,backgroundStyle:{color:"rgba(180, 180, 180, 0.2)",borderColor:null,borderWidth:0,borderType:"solid",borderRadius:0,shadowBlur:0,shadowColor:null,shadowOffsetX:0,shadowOffsetY:0,opacity:1},select:{itemStyle:{borderColor:"#212121"}},realtimeSort:!1}),e}(OS),NS=function(){this.cx=0,this.cy=0,this.r0=0,this.r=0,this.startAngle=0,this.endAngle=2*Math.PI,this.clockwise=!0},ES=function(t){function e(e){var n=t.call(this,e)||this;return n.type="sausage",n}return n(e,t),e.prototype.getDefaultShape=function(){return new NS},e.prototype.buildPath=function(t,e){var n=e.cx,i=e.cy,r=Math.max(e.r0||0,0),o=Math.max(e.r,0),a=.5*(o-r),s=r+a,l=e.startAngle,u=e.endAngle,h=e.clockwise,c=2*Math.PI,p=h?u-lo)return!0;o=u}return!1},e.prototype._isOrderDifferentInView=function(t,e){for(var n=e.scale,i=n.getExtent(),r=Math.max(0,i[0]),o=Math.min(i[1],n.getOrdinalMeta().categories.length-1);r<=o;++r)if(t.ordinalNumbers[r]!==n.getRawOrdinalNumber(r))return!0},e.prototype._updateSortWithinSameData=function(t,e,n,i){if(this._isOrderChangedWithinSameData(t,e,n)){var r=this._dataSort(t,n,e);this._isOrderDifferentInView(r,n)&&(this._removeOnRenderedListener(i),i.dispatchAction({type:"changeAxisOrder",componentType:n.dim+"Axis",axisId:n.index,sortInfo:r}))}},e.prototype._dispatchInitSort=function(t,e,n){var i=e.baseAxis,r=this._dataSort(t,i,(function(n){return t.get(t.mapDimension(e.otherAxis.dim),n)}));n.dispatchAction({type:"changeAxisOrder",componentType:i.dim+"Axis",isInitSort:!0,axisId:i.index,sortInfo:r})},e.prototype.remove=function(t,e){this._clear(this._model),this._removeOnRenderedListener(e)},e.prototype.dispose=function(t,e){this._removeOnRenderedListener(e)},e.prototype._removeOnRenderedListener=function(t){this._onRendered&&(t.getZr().off("rendered",this._onRendered),this._onRendered=null)},e.prototype._clear=function(t){var e=this.group,n=this._data;t&&t.isAnimationEnabled()&&n&&!this._isLargeDraw?(this._removeBackground(),this._backgroundEls=[],n.eachItemGraphicEl((function(e){mh(e,t,Js(e).dataIndex)}))):e.removeAll(),this._data=null,this._isFirstFrame=!0},e.prototype._removeBackground=function(){this.group.remove(this._backgroundGroup),this._backgroundGroup=null},e.type="bar",e}(Tg),WS={cartesian2d:function(t,e){var n=e.width<0?-1:1,i=e.height<0?-1:1;n<0&&(e.x+=e.width,e.width=-e.width),i<0&&(e.y+=e.height,e.height=-e.height);var r=t.x+t.width,o=t.y+t.height,a=BS(e.x,t.x),s=FS(e.x+e.width,r),l=BS(e.y,t.y),u=FS(e.y+e.height,o),h=sr?s:a,e.y=c&&l>o?u:l,e.width=h?0:s-a,e.height=c?0:u-l,n<0&&(e.x+=e.width,e.width=-e.width),i<0&&(e.y+=e.height,e.height=-e.height),h||c},polar:function(t,e){var n=e.r0<=e.r?1:-1;if(n<0){var i=e.r;e.r=e.r0,e.r0=i}var r=FS(e.r,t.r),o=BS(e.r0,t.r0);e.r=r,e.r0=o;var a=r-o<0;if(n<0){i=e.r;e.r=e.r0,e.r0=i}return a}},HS={cartesian2d:function(t,e,n,i,r,o,a,s,l){var u=new Es({shape:A({},i),z2:1});(u.__dataIndex=n,u.name="item",o)&&(u.shape[r?"height":"width"]=0);return u},polar:function(t,e,n,i,r,o,a,s,l){var u=!r&&l?ES:Eu,h=new u({shape:i,z2:1});h.name="item";var c,p,d=KS(r);if(h.calculateTextPosition=(c=d,p=({isRoundCap:u===ES}||{}).isRoundCap,function(t,e,n){var i=e.position;if(!i||i instanceof Array)return Ir(t,e,n);var r=c(i),o=null!=e.distance?e.distance:5,a=this.shape,s=a.cx,l=a.cy,u=a.r,h=a.r0,d=(u+h)/2,f=a.startAngle,g=a.endAngle,y=(f+g)/2,v=p?Math.abs(u-h)/2:0,m=Math.cos,x=Math.sin,_=s+u*m(f),b=l+u*x(f),w="left",S="top";switch(r){case"startArc":_=s+(h-o)*m(y),b=l+(h-o)*x(y),w="center",S="top";break;case"insideStartArc":_=s+(h+o)*m(y),b=l+(h+o)*x(y),w="center",S="bottom";break;case"startAngle":_=s+d*m(f)+zS(f,o+v,!1),b=l+d*x(f)+VS(f,o+v,!1),w="right",S="middle";break;case"insideStartAngle":_=s+d*m(f)+zS(f,-o+v,!1),b=l+d*x(f)+VS(f,-o+v,!1),w="left",S="middle";break;case"middle":_=s+d*m(y),b=l+d*x(y),w="center",S="middle";break;case"endArc":_=s+(u+o)*m(y),b=l+(u+o)*x(y),w="center",S="bottom";break;case"insideEndArc":_=s+(u-o)*m(y),b=l+(u-o)*x(y),w="center",S="top";break;case"endAngle":_=s+d*m(g)+zS(g,o+v,!0),b=l+d*x(g)+VS(g,o+v,!0),w="left",S="middle";break;case"insideEndAngle":_=s+d*m(g)+zS(g,-o+v,!0),b=l+d*x(g)+VS(g,-o+v,!0),w="right",S="middle";break;default:return Ir(t,e,n)}return(t=t||{}).x=_,t.y=b,t.align=w,t.verticalAlign=S,t}),o){var f=r?"r":"endAngle",g={};h.shape[f]=r?0:i.startAngle,g[f]=i[f],(s?dh:fh)(h,{shape:g},o)}return h}};function YS(t,e,n,i,r,o,a,s){var l,u;o?(u={x:i.x,width:i.width},l={y:i.y,height:i.height}):(u={y:i.y,height:i.height},l={x:i.x,width:i.width}),s||(a?dh:fh)(n,{shape:l},e,r,null),(a?dh:fh)(n,{shape:u},e?t.baseAxis.model:null,r)}function US(t,e){for(var n=0;n0?1:-1,a=i.height>0?1:-1;return{x:i.x+o*r/2,y:i.y+a*r/2,width:i.width-o*r,height:i.height-a*r}},polar:function(t,e,n){var i=t.getItemLayout(e);return{cx:i.cx,cy:i.cy,r0:i.r0,r:i.r,startAngle:i.startAngle,endAngle:i.endAngle,clockwise:i.clockwise}}};function KS(t){return function(t){var e=t?"Arc":"Angle";return function(t){switch(t){case"start":case"insideStart":case"end":case"insideEnd":return t+e;default:return t}}}(t)}function $S(t,e,n,i,r,o,a,s){var l=e.getItemVisual(n,"style");s||t.setShape("r",i.get(["itemStyle","borderRadius"])||0),t.useStyle(l);var u=i.getShallow("cursor");u&&t.attr("cursor",u);var h=s?a?r.r>=r.r0?"endArc":"startArc":r.endAngle>=r.startAngle?"endAngle":"startAngle":a?r.height>=0?"bottom":"top":r.width>=0?"right":"left",c=tc(i);Qh(t,c,{labelFetcher:o,labelDataIndex:n,defaultText:Kw(o.getData(),n),inheritColor:l.fill,defaultOpacity:l.opacity,defaultOutsidePosition:h});var p=t.getTextContent();if(s&&p){var d=i.get(["label","position"]);t.textConfig.inside="middle"===d||null,function(t,e,n,i){if(j(i))t.setTextConfig({rotation:i});else if(Y(e))t.setTextConfig({rotation:0});else{var r,o=t.shape,a=o.clockwise?o.startAngle:o.endAngle,s=o.clockwise?o.endAngle:o.startAngle,l=(a+s)/2,u=n(e);switch(u){case"startArc":case"insideStartArc":case"middle":case"insideEndArc":case"endArc":r=l;break;case"startAngle":case"insideStartAngle":r=a;break;case"endAngle":case"insideEndAngle":r=s;break;default:return void t.setTextConfig({rotation:0})}var h=1.5*Math.PI-r;"middle"===u&&h>Math.PI/2&&h<1.5*Math.PI&&(h-=Math.PI),t.setTextConfig({rotation:h})}}(t,"outside"===d?h:d,KS(a),i.get(["label","rotate"]))}uc(p,c,o.getRawValue(n),(function(t){return $w(e,t)}));var f=i.getModel(["emphasis"]);Hl(t,f.get("focus"),f.get("blurScope"),f.get("disabled")),Zl(t,i),function(t){return null!=t.startAngle&&null!=t.endAngle&&t.startAngle===t.endAngle}(r)&&(t.style.fill="none",t.style.stroke="none",E(t.states,(function(t){t.style&&(t.style.fill=t.style.stroke="none")})))}var JS=function(){},QS=function(t){function e(e){var n=t.call(this,e)||this;return n.type="largeBar",n}return n(e,t),e.prototype.getDefaultShape=function(){return new JS},e.prototype.buildPath=function(t,e){for(var n=e.points,i=this.baseDimIdx,r=1-this.baseDimIdx,o=[],a=[],s=this.barWidth,l=0;l=s[0]&&e<=s[0]+l[0]&&n>=s[1]&&n<=s[1]+l[1])return a[h]}return-1}(this,t.offsetX,t.offsetY);Js(this).dataIndex=e>=0?e:null}),30,!1);function nM(t,e,n){if(vS(n,"cartesian2d")){var i=e,r=n.getArea();return{x:t?i.x:r.x,y:t?r.y:i.y,width:t?i.width:r.width,height:t?r.height:i.height}}var o=e;return{cx:(r=n.getArea()).cx,cy:r.cy,r0:t?r.r0:o.r0,r:t?r.r:o.r,startAngle:t?o.startAngle:0,endAngle:t?o.endAngle:2*Math.PI}}var iM=2*Math.PI,rM=Math.PI/180;function oM(t,e){return Tp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}function aM(t,e){var n=oM(t,e),i=t.get("center"),r=t.get("radius");Y(r)||(r=[0,r]);var o,a,s=Ur(n.width,e.getWidth()),l=Ur(n.height,e.getHeight()),u=Math.min(s,l),h=Ur(r[0],u/2),c=Ur(r[1],u/2),p=t.coordinateSystem;if(p){var d=p.dataToPoint(i);o=d[0]||0,a=d[1]||0}else Y(i)||(i=[i,i]),o=Ur(i[0],s)+n.x,a=Ur(i[1],l)+n.y;return{cx:o,cy:a,r0:h,r:c}}function sM(t,e,n){e.eachSeriesByType(t,(function(t){var e=t.getData(),i=e.mapDimension("value"),r=oM(t,n),o=aM(t,n),a=o.cx,s=o.cy,l=o.r,u=o.r0,h=-t.get("startAngle")*rM,c=t.get("minAngle")*rM,p=0;e.each(i,(function(t){!isNaN(t)&&p++}));var d=e.getSum(i),f=Math.PI/(d||p)*2,g=t.get("clockwise"),y=t.get("roseType"),v=t.get("stillShowZeroSum"),m=e.getDataExtent(i);m[0]=0;var x=iM,_=0,b=h,w=g?1:-1;if(e.setLayout({viewRect:r,r:l}),e.each(i,(function(t,n){var i;if(isNaN(t))e.setItemLayout(n,{angle:NaN,startAngle:NaN,endAngle:NaN,clockwise:g,cx:a,cy:s,r0:u,r:y?NaN:l});else{(i="area"!==y?0===d&&v?f:t*f:iM/p)n?a:o,h=Math.abs(l.label.y-n);if(h>=u.maxY){var c=l.label.x-e-l.len2*r,p=i+l.len,f=Math.abs(c)t.unconstrainedWidth?null:d:null;i.setStyle("width",f)}var g=i.getBoundingRect();o.width=g.width;var y=(i.style.margin||0)+2.1;o.height=g.height+y,o.y-=(o.height-c)/2}}}function pM(t){return"center"===t.position}function dM(t){var e,n,i=t.getData(),r=[],o=!1,a=(t.get("minShowLabelAngle")||0)*uM,s=i.getLayout("viewRect"),l=i.getLayout("r"),u=s.width,h=s.x,c=s.y,p=s.height;function d(t){t.ignore=!0}i.each((function(t){var s=i.getItemGraphicEl(t),c=s.shape,p=s.getTextContent(),f=s.getTextGuideLine(),g=i.getItemModel(t),y=g.getModel("label"),v=y.get("position")||g.get(["emphasis","label","position"]),m=y.get("distanceToLabelLine"),x=y.get("alignTo"),_=Ur(y.get("edgeDistance"),u),b=y.get("bleedMargin"),w=g.getModel("labelLine"),S=w.get("length");S=Ur(S,u);var M=w.get("length2");if(M=Ur(M,u),Math.abs(c.endAngle-c.startAngle)0?"right":"left":k>0?"left":"right"}var B=Math.PI,F=0,G=y.get("rotate");if(j(G))F=G*(B/180);else if("center"===v)F=0;else if("radial"===G||!0===G){F=k<0?-A+B:-A}else if("tangential"===G&&"outside"!==v&&"outer"!==v){var W=Math.atan2(k,L);W<0&&(W=2*B+W),L>0&&(W=B+W),F=W-B}if(o=!!F,p.x=I,p.y=T,p.rotation=F,p.setStyle({verticalAlign:"middle"}),P){p.setStyle({align:D});var H=p.states.select;H&&(H.x+=p.x,H.y+=p.y)}else{var Y=p.getBoundingRect().clone();Y.applyTransform(p.getComputedTransform());var U=(p.style.margin||0)+2.1;Y.y-=U/2,Y.height+=U,r.push({label:p,labelLine:f,position:v,len:S,len2:M,minTurnAngle:w.get("minTurnAngle"),maxSurfaceAngle:w.get("maxSurfaceAngle"),surfaceNormal:new Ce(k,L),linePoints:C,textAlign:D,labelDistance:m,labelAlignTo:x,edgeDistance:_,bleedMargin:b,rect:Y,unconstrainedWidth:Y.width,labelStyleWidth:p.style.width})}s.setTextConfig({inside:P})}})),!o&&t.get("avoidLabelOverlap")&&function(t,e,n,i,r,o,a,s){for(var l=[],u=[],h=Number.MAX_VALUE,c=-Number.MAX_VALUE,p=0;p0){for(var l=o.getItemLayout(0),u=1;isNaN(l&&l.startAngle)&&u=n.r0}},e.type="pie",e}(Tg);function vM(t,e,n){e=Y(e)&&{coordDimensions:e}||A({encodeDefine:t.getEncode()},e);var i=t.getSource(),r=nx(i,e).dimensions,o=new ex(r,t);return o.initData(i,n),o}var mM=function(){function t(t,e){this._getDataWithEncodedVisual=t,this._getRawData=e}return t.prototype.getAllNames=function(){var t=this._getRawData();return t.mapArray(t.getName)},t.prototype.containName=function(t){return this._getRawData().indexOfName(t)>=0},t.prototype.indexOfName=function(t){return this._getDataWithEncodedVisual().indexOfName(t)},t.prototype.getItemVisual=function(t,e){return this._getDataWithEncodedVisual().getItemVisual(t,e)},t}(),xM=Po(),_M=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments),this.legendVisualProvider=new mM(W(this.getData,this),W(this.getRawData,this)),this._defaultLabelLine(e)},e.prototype.mergeOption=function(){t.prototype.mergeOption.apply(this,arguments)},e.prototype.getInitialData=function(){return vM(this,{coordDimensions:["value"],encodeDefaulter:H($p,this)})},e.prototype.getDataParams=function(e){var n=this.getData(),i=xM(n),r=i.seats;if(!r){var o=[];n.each(n.mapDimension("value"),(function(t){o.push(t)})),r=i.seats=$r(o,n.hostModel.get("percentPrecision"))}var a=t.prototype.getDataParams.call(this,e);return a.percent=r[e]||0,a.$vars.push("percent"),a},e.prototype._defaultLabelLine=function(t){bo(t,"labelLine",["show"]);var e=t.labelLine,n=t.emphasis.labelLine;e.show=e.show&&t.label.show,n.show=n.show&&t.emphasis.label.show},e.type="series.pie",e.defaultOption={z:2,legendHoverLink:!0,colorBy:"data",center:["50%","50%"],radius:[0,"75%"],clockwise:!0,startAngle:90,minAngle:0,minShowLabelAngle:0,selectedOffset:10,percentPrecision:2,stillShowZeroSum:!0,left:0,top:0,right:0,bottom:0,width:null,height:null,label:{rotate:0,show:!0,overflow:"truncate",position:"outer",alignTo:"none",edgeDistance:"25%",bleedMargin:10,distanceToLabelLine:5},labelLine:{show:!0,length:15,length2:15,smooth:!1,minTurnAngle:90,maxSurfaceAngle:90,lineStyle:{width:1,type:"solid"}},itemStyle:{borderWidth:1,borderJoin:"round"},showEmptyCircle:!0,emptyCircleStyle:{color:"lightgray",opacity:1},labelLayout:{hideOverlap:!0},emphasis:{scale:!0,scaleSize:5},avoidLabelOverlap:!0,animationType:"expansion",animationDuration:1e3,animationTypeUpdate:"transition",animationEasingUpdate:"cubicInOut",animationDurationUpdate:500,animationEasing:"cubicInOut"},e}(fg);var bM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){return hx(null,this,{useEncodeDefaulter:!0})},e.prototype.getProgressive=function(){var t=this.option.progressive;return null==t?this.option.large?5e3:this.get("progressive"):t},e.prototype.getProgressiveThreshold=function(){var t=this.option.progressiveThreshold;return null==t?this.option.large?1e4:this.get("progressiveThreshold"):t},e.prototype.brushSelector=function(t,e,n){return n.point(e.getItemLayout(t))},e.prototype.getZLevelKey=function(){return this.getData().count()>this.getProgressiveThreshold()?this.id:""},e.type="series.scatter",e.dependencies=["grid","polar","geo","singleAxis","calendar"],e.defaultOption={coordinateSystem:"cartesian2d",z:2,legendHoverLink:!0,symbolSize:10,large:!1,largeThreshold:2e3,itemStyle:{opacity:.8},emphasis:{scale:!0},clip:!0,select:{itemStyle:{borderColor:"#212121"}},universalTransition:{divideShape:"clone"}},e}(fg),wM=function(){},SM=function(t){function e(e){var n=t.call(this,e)||this;return n._off=0,n.hoverDataIdx=-1,n}return n(e,t),e.prototype.getDefaultShape=function(){return new wM},e.prototype.reset=function(){this.notClear=!1,this._off=0},e.prototype.buildPath=function(t,e){var n,i=e.points,r=e.size,o=this.symbolProxy,a=o.shape,s=t.getContext?t.getContext():t,l=s&&r[0]<4,u=this.softClipShape;if(l)this._ctx=s;else{for(this._ctx=null,n=this._off;n=0;s--){var l=2*s,u=i[l]-o/2,h=i[l+1]-a/2;if(t>=u&&e>=h&&t<=u+o&&e<=h+a)return s}return-1},e.prototype.contain=function(t,e){var n=this.transformCoordToLocal(t,e),i=this.getBoundingRect();return t=n[0],e=n[1],i.contain(t,e)?(this.hoverDataIdx=this.findDataIndex(t,e))>=0:(this.hoverDataIdx=-1,!1)},e.prototype.getBoundingRect=function(){var t=this._rect;if(!t){for(var e=this.shape,n=e.points,i=e.size,r=i[0],o=i[1],a=1/0,s=1/0,l=-1/0,u=-1/0,h=0;h=0&&(l.dataIndex=n+(t.startIndex||0))}))},t.prototype.remove=function(){this._clear()},t.prototype._clear=function(){this._newAdded=[],this.group.removeAll()},t}(),IM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData();this._updateSymbolDraw(i,t).updateData(i,{clipShape:this._getClipShape(t)}),this._finished=!0},e.prototype.incrementalPrepareRender=function(t,e,n){var i=t.getData();this._updateSymbolDraw(i,t).incrementalPrepareUpdate(i),this._finished=!1},e.prototype.incrementalRender=function(t,e,n){this._symbolDraw.incrementalUpdate(t,e.getData(),{clipShape:this._getClipShape(e)}),this._finished=t.end===e.getData().count()},e.prototype.updateTransform=function(t,e,n){var i=t.getData();if(this.group.dirty(),!this._finished||i.count()>1e4)return{update:!0};var r=AS("").reset(t,e,n);r.progress&&r.progress({start:0,end:i.count(),count:i.count()},i),this._symbolDraw.updateLayout(i)},e.prototype.eachRendered=function(t){this._symbolDraw&&this._symbolDraw.eachRendered(t)},e.prototype._getClipShape=function(t){var e=t.coordinateSystem,n=e&&e.getArea&&e.getArea();return t.get("clip",!0)?n:null},e.prototype._updateSymbolDraw=function(t,e){var n=this._symbolDraw,i=e.pipelineContext.large;return n&&i===this._isLargeDraw||(n&&n.remove(),n=this._symbolDraw=i?new MM:new iS,this._isLargeDraw=i,this.group.removeAll()),this.group.add(n.group),n},e.prototype.remove=function(t,e){this._symbolDraw&&this._symbolDraw.remove(!0),this._symbolDraw=null},e.prototype.dispose=function(){},e.type="scatter",e}(Tg),TM=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.type="grid",e.dependencies=["xAxis","yAxis"],e.layoutMode="box",e.defaultOption={show:!1,z:0,left:"10%",top:60,right:"10%",bottom:70,containLabel:!1,backgroundColor:"rgba(0,0,0,0)",borderWidth:1,borderColor:"#ccc"},e}(Op),CM=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getCoordSysModel=function(){return this.getReferringComponents("grid",Eo).models[0]},e.type="cartesian2dAxis",e}(Op);R(CM,m_);var DM={show:!0,z:0,inverse:!1,name:"",nameLocation:"end",nameRotate:null,nameTruncate:{maxWidth:null,ellipsis:"...",placeholder:"."},nameTextStyle:{},nameGap:15,silent:!1,triggerEvent:!1,tooltip:{show:!1},axisPointer:{},axisLine:{show:!0,onZero:!0,onZeroAxisIndex:null,lineStyle:{color:"#6E7079",width:1,type:"solid"},symbol:["none","none"],symbolSize:[10,15]},axisTick:{show:!0,inside:!1,length:5,lineStyle:{width:1}},axisLabel:{show:!0,inside:!1,rotate:0,showMinLabel:null,showMaxLabel:null,margin:8,fontSize:12},splitLine:{show:!0,lineStyle:{color:["#E0E6F1"],width:1,type:"solid"}},splitArea:{show:!1,areaStyle:{color:["rgba(250,250,250,0.2)","rgba(210,219,238,0.2)"]}}},AM=C({boundaryGap:!0,deduplication:null,splitLine:{show:!1},axisTick:{alignWithLabel:!1,interval:"auto"},axisLabel:{interval:"auto"}},DM),kM=C({boundaryGap:[0,0],axisLine:{show:"auto"},axisTick:{show:"auto"},splitNumber:5,minorTick:{show:!1,splitNumber:5,length:3,lineStyle:{}},minorSplitLine:{show:!1,lineStyle:{color:"#F4F7FD",width:1}}},DM),LM={category:AM,value:kM,time:C({splitNumber:6,axisLabel:{showMinLabel:!1,showMaxLabel:!1,rich:{primary:{fontWeight:"bold"}}},splitLine:{show:!1}},kM),log:k({logBase:10},kM)},PM={value:1,category:1,time:1,log:1};function OM(t,e,i,r){E(PM,(function(o,a){var s=C(C({},LM[a],!0),r,!0),l=function(t){function i(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e+"Axis."+a,n}return n(i,t),i.prototype.mergeDefaultAndTheme=function(t,e){var n=Dp(this),i=n?kp(t):{};C(t,e.getTheme().get(a+"Axis")),C(t,this.getDefaultOption()),t.type=RM(t),n&&Ap(t,i,n)},i.prototype.optionUpdated=function(){"category"===this.option.type&&(this.__ordinalMeta=dx.createByAxisModel(this))},i.prototype.getCategories=function(t){var e=this.option;if("category"===e.type)return t?e.data:this.__ordinalMeta.categories},i.prototype.getOrdinalMeta=function(){return this.__ordinalMeta},i.type=e+"Axis."+a,i.defaultOption=s,i}(i);t.registerComponentModel(l)})),t.registerSubTypeDefaulter(e+"Axis",RM)}function RM(t){return t.type||(t.data?"category":"value")}var NM=function(){function t(t){this.type="cartesian",this._dimList=[],this._axes={},this.name=t||""}return t.prototype.getAxis=function(t){return this._axes[t]},t.prototype.getAxes=function(){return z(this._dimList,(function(t){return this._axes[t]}),this)},t.prototype.getAxesByScale=function(t){return t=t.toLowerCase(),B(this.getAxes(),(function(e){return e.scale.type===t}))},t.prototype.addAxis=function(t){var e=t.dim;this._axes[e]=t,this._dimList.push(e)},t}(),EM=["x","y"];function zM(t){return"interval"===t.type||"time"===t.type}var VM=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type="cartesian2d",e.dimensions=EM,e}return n(e,t),e.prototype.calcAffineTransform=function(){this._transform=this._invTransform=null;var t=this.getAxis("x").scale,e=this.getAxis("y").scale;if(zM(t)&&zM(e)){var n=t.getExtent(),i=e.getExtent(),r=this.dataToPoint([n[0],i[0]]),o=this.dataToPoint([n[1],i[1]]),a=n[1]-n[0],s=i[1]-i[0];if(a&&s){var l=(o[0]-r[0])/a,u=(o[1]-r[1])/s,h=r[0]-n[0]*l,c=r[1]-i[0]*u,p=this._transform=[l,0,0,u,h,c];this._invTransform=Me([],p)}}},e.prototype.getBaseAxis=function(){return this.getAxesByScale("ordinal")[0]||this.getAxesByScale("time")[0]||this.getAxis("x")},e.prototype.containPoint=function(t){var e=this.getAxis("x"),n=this.getAxis("y");return e.contain(e.toLocalCoord(t[0]))&&n.contain(n.toLocalCoord(t[1]))},e.prototype.containData=function(t){return this.getAxis("x").containData(t[0])&&this.getAxis("y").containData(t[1])},e.prototype.containZone=function(t,e){var n=this.dataToPoint(t),i=this.dataToPoint(e),r=this.getArea(),o=new Ee(n[0],n[1],i[0]-n[0],i[1]-n[1]);return r.intersect(o)},e.prototype.dataToPoint=function(t,e,n){n=n||[];var i=t[0],r=t[1];if(this._transform&&null!=i&&isFinite(i)&&null!=r&&isFinite(r))return Wt(n,t,this._transform);var o=this.getAxis("x"),a=this.getAxis("y");return n[0]=o.toGlobalCoord(o.dataToCoord(i,e)),n[1]=a.toGlobalCoord(a.dataToCoord(r,e)),n},e.prototype.clampData=function(t,e){var n=this.getAxis("x").scale,i=this.getAxis("y").scale,r=n.getExtent(),o=i.getExtent(),a=n.parse(t[0]),s=i.parse(t[1]);return(e=e||[])[0]=Math.min(Math.max(Math.min(r[0],r[1]),a),Math.max(r[0],r[1])),e[1]=Math.min(Math.max(Math.min(o[0],o[1]),s),Math.max(o[0],o[1])),e},e.prototype.pointToData=function(t,e){var n=[];if(this._invTransform)return Wt(n,t,this._invTransform);var i=this.getAxis("x"),r=this.getAxis("y");return n[0]=i.coordToData(i.toLocalCoord(t[0]),e),n[1]=r.coordToData(r.toLocalCoord(t[1]),e),n},e.prototype.getOtherAxis=function(t){return this.getAxis("x"===t.dim?"y":"x")},e.prototype.getArea=function(){var t=this.getAxis("x").getGlobalExtent(),e=this.getAxis("y").getGlobalExtent(),n=Math.min(t[0],t[1]),i=Math.min(e[0],e[1]),r=Math.max(t[0],t[1])-n,o=Math.max(e[0],e[1])-i;return new Ee(n,i,r,o)},e}(NM),BM=function(t){function e(e,n,i,r,o){var a=t.call(this,e,n,i)||this;return a.index=0,a.type=r||"value",a.position=o||"bottom",a}return n(e,t),e.prototype.isHorizontal=function(){var t=this.position;return"top"===t||"bottom"===t},e.prototype.getGlobalExtent=function(t){var e=this.getExtent();return e[0]=this.toGlobalCoord(e[0]),e[1]=this.toGlobalCoord(e[1]),t&&e[0]>e[1]&&e.reverse(),e},e.prototype.pointToData=function(t,e){return this.coordToData(this.toLocalCoord(t["x"===this.dim?0:1]),e)},e.prototype.setCategorySortInfo=function(t){if("category"!==this.type)return!1;this.model.option.categorySortInfo=t,this.scale.setSortInfo(t)},e}(q_);function FM(t,e,n){n=n||{};var i=t.coordinateSystem,r=e.axis,o={},a=r.getAxesOnZeroOf()[0],s=r.position,l=a?"onZero":s,u=r.dim,h=i.getRect(),c=[h.x,h.x+h.width,h.y,h.y+h.height],p={left:0,right:1,top:0,bottom:1,onZero:2},d=e.get("offset")||0,f="x"===u?[c[2]-d,c[3]+d]:[c[0]-d,c[1]+d];if(a){var g=a.toGlobalCoord(a.dataToCoord(0));f[p.onZero]=Math.max(Math.min(g,f[1]),f[0])}o.position=["y"===u?f[p[l]]:c[0],"x"===u?f[p[l]]:c[3]],o.rotation=Math.PI/2*("x"===u?0:1);o.labelDirection=o.tickDirection=o.nameDirection={top:-1,bottom:1,left:-1,right:1}[s],o.labelOffset=a?f[p[s]]-f[p.onZero]:0,e.get(["axisTick","inside"])&&(o.tickDirection=-o.tickDirection),it(n.labelInside,e.get(["axisLabel","inside"]))&&(o.labelDirection=-o.labelDirection);var y=e.get(["axisLabel","rotate"]);return o.labelRotate="top"===l?-y:y,o.z2=1,o}function GM(t){return"cartesian2d"===t.get("coordinateSystem")}function WM(t){var e={xAxisModel:null,yAxisModel:null};return E(e,(function(n,i){var r=i.replace(/Model$/,""),o=t.getReferringComponents(r,Eo).models[0];e[i]=o})),e}var HM=Math.log;function YM(t,e,n){var i=Tx.prototype,r=i.getTicks.call(n),o=i.getTicks.call(n,!0),a=r.length-1,s=i.getInterval.call(n),l=u_(t,e),u=l.extent,h=l.fixMin,c=l.fixMax;if("log"===t.type){var p=HM(t.base);u=[HM(u[0])/p,HM(u[1])/p]}t.setExtent(u[0],u[1]),t.calcNiceExtent({splitNumber:a,fixMin:h,fixMax:c});var d=i.getExtent.call(t);h&&(u[0]=d[0]),c&&(u[1]=d[1]);var f=i.getInterval.call(t),g=u[0],y=u[1];if(h&&c)f=(y-g)/a;else if(h)for(y=u[0]+f*a;yu[0]&&isFinite(g)&&isFinite(u[0]);)f=vx(f),g=u[1]-f*a;else{t.getTicks().length-1>a&&(f=vx(f));var v=f*a;(g=Xr((y=Math.ceil(u[1]/f)*f)-v))<0&&u[0]>=0?(g=0,y=Xr(v)):y>0&&u[1]<=0&&(y=0,g=-Xr(v))}var m=(r[0].value-o[0].value)/s,x=(r[a].value-o[a].value)/s;i.setExtent.call(t,g+f*m,y+f*x),i.setInterval.call(t,f),(m||x)&&i.setNiceExtent.call(t,g+f,y-f)}var UM=function(){function t(t,e,n){this.type="grid",this._coordsMap={},this._coordsList=[],this._axesMap={},this._axesList=[],this.axisPointerEnabled=!0,this.dimensions=EM,this._initCartesian(t,e,n),this.model=t}return t.prototype.getRect=function(){return this._rect},t.prototype.update=function(t,e){var n=this._axesMap;function i(t){var e,n=G(t),i=n.length;if(i){for(var r=[],o=i-1;o>=0;o--){var a=t[+n[o]],s=a.model,l=a.scale;gx(l)&&s.get("alignTicks")&&null==s.get("interval")?r.push(a):(h_(l,s),gx(l)&&(e=a))}r.length&&(e||h_((e=r.pop()).scale,e.model),E(r,(function(t){YM(t.scale,t.model,e.scale)})))}}this._updateScale(t,this.model),i(n.x),i(n.y);var r={};E(n.x,(function(t){ZM(n,"y",t,r)})),E(n.y,(function(t){ZM(n,"x",t,r)})),this.resize(this.model,e)},t.prototype.resize=function(t,e,n){var i=t.getBoxLayoutParams(),r=!n&&t.get("containLabel"),o=Tp(i,{width:e.getWidth(),height:e.getHeight()});this._rect=o;var a=this._axesList;function s(){E(a,(function(t){var e=t.isHorizontal(),n=e?[0,o.width]:[0,o.height],i=t.inverse?1:0;t.setExtent(n[i],n[1-i]),function(t,e){var n=t.getExtent(),i=n[0]+n[1];t.toGlobalCoord="x"===t.dim?function(t){return t+e}:function(t){return i-t+e},t.toLocalCoord="x"===t.dim?function(t){return t-e}:function(t){return i-t+e}}(t,e?o.x:o.y)}))}s(),r&&(E(a,(function(t){if(!t.model.get(["axisLabel","inside"])){var e=function(t){var e=t.model,n=t.scale;if(e.get(["axisLabel","show"])&&!n.isBlank()){var i,r,o=n.getExtent();r=n instanceof Mx?n.count():(i=n.getTicks()).length;var a,s=t.getLabelModel(),l=p_(t),u=1;r>40&&(u=Math.ceil(r/40));for(var h=0;h0&&i>0||n<0&&i<0)}(t)}var qM=Math.PI,KM=function(){function t(t,e){this.group=new Er,this.opt=e,this.axisModel=t,k(e,{labelOffset:0,nameDirection:1,tickDirection:1,labelDirection:1,silent:!0,handleAutoShown:function(){return!0}});var n=new Er({x:e.position[0],y:e.position[1],rotation:e.rotation});n.updateTransform(),this._transformGroup=n}return t.prototype.hasBuilder=function(t){return!!$M[t]},t.prototype.add=function(t){$M[t](this.opt,this.axisModel,this.group,this._transformGroup)},t.prototype.getGroup=function(){return this.group},t.innerTextLayout=function(t,e,n){var i,r,o=to(e-t);return eo(o)?(r=n>0?"top":"bottom",i="center"):eo(o-qM)?(r=n>0?"bottom":"top",i="center"):(r="middle",i=o>0&&o0?"right":"left":n>0?"left":"right"),{rotation:o,textAlign:i,textVerticalAlign:r}},t.makeAxisEventDataBase=function(t){var e={componentType:t.mainType,componentIndex:t.componentIndex};return e[t.mainType+"Index"]=t.componentIndex,e},t.isLabelSilent=function(t){var e=t.get("tooltip");return t.get("silent")||!(t.get("triggerEvent")||e&&e.show)},t}(),$M={axisLine:function(t,e,n,i){var r=e.get(["axisLine","show"]);if("auto"===r&&t.handleAutoShown&&(r=t.handleAutoShown("axisLine")),r){var o=e.axis.getExtent(),a=i.transform,s=[o[0],0],l=[o[1],0],u=s[0]>l[0];a&&(Wt(s,s,a),Wt(l,l,a));var h=A({lineCap:"round"},e.getModel(["axisLine","lineStyle"]).getLineStyle()),c=new Xu({shape:{x1:s[0],y1:s[1],x2:l[0],y2:l[1]},style:h,strokeContainThreshold:t.strokeContainThreshold||5,silent:!0,z2:1});Oh(c.shape,c.style.lineWidth),c.anid="line",n.add(c);var p=e.get(["axisLine","symbol"]);if(null!=p){var d=e.get(["axisLine","symbolSize"]);X(p)&&(p=[p,p]),(X(d)||j(d))&&(d=[d,d]);var f=Fy(e.get(["axisLine","symbolOffset"])||0,d),g=d[0],y=d[1];E([{rotate:t.rotation+Math.PI/2,offset:f[0],r:0},{rotate:t.rotation-Math.PI/2,offset:f[1],r:Math.sqrt((s[0]-l[0])*(s[0]-l[0])+(s[1]-l[1])*(s[1]-l[1]))}],(function(e,i){if("none"!==p[i]&&null!=p[i]){var r=Vy(p[i],-g/2,-y/2,g,y,h.stroke,!0),o=e.r+e.offset,a=u?l:s;r.attr({rotation:e.rotate,x:a[0]+o*Math.cos(t.rotation),y:a[1]-o*Math.sin(t.rotation),silent:!0,z2:11}),n.add(r)}}))}}},axisTickLabel:function(t,e,n,i){var r=function(t,e,n,i){var r=n.axis,o=n.getModel("axisTick"),a=o.get("show");"auto"===a&&i.handleAutoShown&&(a=i.handleAutoShown("axisTick"));if(!a||r.scale.isBlank())return;for(var s=o.getModel("lineStyle"),l=i.tickDirection*o.get("length"),u=eI(r.getTicksCoords(),e.transform,l,k(s.getLineStyle(),{stroke:n.get(["axisLine","lineStyle","color"])}),"ticks"),h=0;hc[1]?-1:1,d=["start"===s?c[0]-p*h:"end"===s?c[1]+p*h:(c[0]+c[1])/2,tI(s)?t.labelOffset+l*h:0],f=e.get("nameRotate");null!=f&&(f=f*qM/180),tI(s)?o=KM.innerTextLayout(t.rotation,null!=f?f:t.rotation,l):(o=function(t,e,n,i){var r,o,a=to(n-t),s=i[0]>i[1],l="start"===e&&!s||"start"!==e&&s;eo(a-qM/2)?(o=l?"bottom":"top",r="center"):eo(a-1.5*qM)?(o=l?"top":"bottom",r="center"):(o="middle",r=a<1.5*qM&&a>qM/2?l?"left":"right":l?"right":"left");return{rotation:a,textAlign:r,textVerticalAlign:o}}(t.rotation,s,f||0,c),null!=(a=t.axisNameAvailableWidth)&&(a=Math.abs(a/Math.sin(o.rotation)),!isFinite(a)&&(a=null)));var g=u.getFont(),y=e.get("nameTruncate",!0)||{},v=y.ellipsis,m=it(t.nameTruncateMaxWidth,y.maxWidth,a),x=new Bs({x:d[0],y:d[1],rotation:o.rotation,silent:KM.isLabelSilent(e),style:ec(u,{text:r,font:g,overflow:"truncate",width:m,ellipsis:v,fill:u.getTextColor()||e.get(["axisLine","lineStyle","color"]),align:u.get("align")||o.textAlign,verticalAlign:u.get("verticalAlign")||o.textVerticalAlign}),z2:1});if(Xh({el:x,componentModel:e,itemName:r}),x.__fullText=r,x.anid="name",e.get("triggerEvent")){var _=KM.makeAxisEventDataBase(e);_.targetType="axisName",_.name=r,Js(x).eventData=_}i.add(x),x.updateTransform(),n.add(x),x.decomposeTransform()}}};function JM(t){t&&(t.ignore=!0)}function QM(t,e){var n=t&&t.getBoundingRect().clone(),i=e&&e.getBoundingRect().clone();if(n&&i){var r=me([]);return we(r,r,-t.rotation),n.applyTransform(_e([],r,t.getLocalTransform())),i.applyTransform(_e([],r,e.getLocalTransform())),n.intersect(i)}}function tI(t){return"middle"===t||"center"===t}function eI(t,e,n,i,r){for(var o=[],a=[],s=[],l=0;l=0||t===e}function rI(t){var e=oI(t);if(e){var n=e.axisPointerModel,i=e.axis.scale,r=n.option,o=n.get("status"),a=n.get("value");null!=a&&(a=i.parse(a));var s=aI(n);null==o&&(r.status=s?"show":"hide");var l=i.getExtent().slice();l[0]>l[1]&&l.reverse(),(null==a||a>l[1])&&(a=l[1]),a0&&!c.min?c.min=0:null!=c.min&&c.min<0&&!c.max&&(c.max=0);var p=a;null!=c.color&&(p=k({color:c.color},a));var d=C(T(c),{boundaryGap:t,splitNumber:e,scale:n,axisLine:i,axisTick:r,axisLabel:o,name:c.text,showName:s,nameLocation:"end",nameGap:u,nameTextStyle:p,triggerEvent:h},!1);if(X(l)){var f=d.name;d.name=l.replace("{value}",null!=f?f:"")}else U(l)&&(d.name=l(d.name,d));var g=new Sc(d,null,this.ecModel);return R(g,m_.prototype),g.mainType="radar",g.componentIndex=this.componentIndex,g}),this);this._indicatorModels=c},e.prototype.getIndicatorModels=function(){return this._indicatorModels},e.type="radar",e.defaultOption={z:0,center:["50%","50%"],radius:"75%",startAngle:90,axisName:{show:!0},boundaryGap:[0,0],splitNumber:5,axisNameGap:15,scale:!1,shape:"polygon",axisLine:C({lineStyle:{color:"#bbb"}},DI.axisLine),axisLabel:AI(DI.axisLabel,!1),axisTick:AI(DI.axisTick,!1),splitLine:AI(DI.splitLine,!0),splitArea:AI(DI.splitArea,!0),indicator:[]},e}(Op),LI=["axisLine","axisTickLabel","axisName"],PI=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this.group.removeAll(),this._buildAxes(t),this._buildSplitLineAndArea(t)},e.prototype._buildAxes=function(t){var e=t.coordinateSystem;E(z(e.getIndicatorAxes(),(function(t){var n=t.model.get("showName")?t.name:"";return new KM(t.model,{axisName:n,position:[e.cx,e.cy],rotation:t.angle,labelDirection:-1,tickDirection:-1,nameDirection:1})})),(function(t){E(LI,t.add,t),this.group.add(t.getGroup())}),this)},e.prototype._buildSplitLineAndArea=function(t){var e=t.coordinateSystem,n=e.getIndicatorAxes();if(n.length){var i=t.get("shape"),r=t.getModel("splitLine"),o=t.getModel("splitArea"),a=r.getModel("lineStyle"),s=o.getModel("areaStyle"),l=r.get("show"),u=o.get("show"),h=a.get("color"),c=s.get("color"),p=Y(h)?h:[h],d=Y(c)?c:[c],f=[],g=[];if("circle"===i)for(var y=n[0].getTicksCoords(),v=e.cx,m=e.cy,x=0;x3?1.4:r>1?1.2:1.1;FI(this,"zoom","zoomOnMouseWheel",t,{scale:i>0?s:1/s,originX:o,originY:a,isAvailableBehavior:null})}if(n){var l=Math.abs(i);FI(this,"scrollMove","moveOnMouseWheel",t,{scrollDelta:(i>0?1:-1)*(l>3?.4:l>1?.15:.05),originX:o,originY:a,isAvailableBehavior:null})}}},e.prototype._pinchHandler=function(t){zI(this._zr,"globalPan")||FI(this,"zoom",null,t,{scale:t.pinchScale>1?1.1:1/1.1,originX:t.pinchX,originY:t.pinchY,isAvailableBehavior:null})},e}(jt);function FI(t,e,n,i,r){t.pointerChecker&&t.pointerChecker(i,r.originX,r.originY)&&(pe(i.event),GI(t,e,n,i,r))}function GI(t,e,n,i,r){r.isAvailableBehavior=W(WI,null,n,i),t.trigger(e,r)}function WI(t,e,n){var i=n[t];return!t||i&&(!X(i)||e.event[i+"Key"])}function HI(t,e,n){var i=t.target;i.x+=e,i.y+=n,i.dirty()}function YI(t,e,n,i){var r=t.target,o=t.zoomLimit,a=t.zoom=t.zoom||1;if(a*=e,o){var s=o.min||0,l=o.max||1/0;a=Math.max(Math.min(l,a),s)}var u=a/t.zoom;t.zoom=a,r.x-=(n-r.x)*(u-1),r.y-=(i-r.y)*(u-1),r.scaleX*=u,r.scaleY*=u,r.dirty()}var UI,XI={axisPointer:1,tooltip:1,brush:1};function ZI(t,e,n){var i=e.getComponentByElement(t.topTarget),r=i&&i.coordinateSystem;return i&&i!==n&&!XI.hasOwnProperty(i.mainType)&&r&&r.model!==n}function jI(t){X(t)&&(t=(new DOMParser).parseFromString(t,"text/xml"));var e=t;for(9===e.nodeType&&(e=e.firstChild);"svg"!==e.nodeName.toLowerCase()||1!==e.nodeType;)e=e.nextSibling;return e}var qI={fill:"fill",stroke:"stroke","stroke-width":"lineWidth",opacity:"opacity","fill-opacity":"fillOpacity","stroke-opacity":"strokeOpacity","stroke-dasharray":"lineDash","stroke-dashoffset":"lineDashOffset","stroke-linecap":"lineCap","stroke-linejoin":"lineJoin","stroke-miterlimit":"miterLimit","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","text-anchor":"textAlign",visibility:"visibility",display:"display"},KI=G(qI),$I={"alignment-baseline":"textBaseline","stop-color":"stopColor"},JI=G($I),QI=function(){function t(){this._defs={},this._root=null}return t.prototype.parse=function(t,e){e=e||{};var n=jI(t);this._defsUsePending=[];var i=new Er;this._root=i;var r=[],o=n.getAttribute("viewBox")||"",a=parseFloat(n.getAttribute("width")||e.width),s=parseFloat(n.getAttribute("height")||e.height);isNaN(a)&&(a=null),isNaN(s)&&(s=null),oT(n,i,null,!0,!1);for(var l,u,h=n.firstChild;h;)this._parseNode(h,i,r,null,!1,!1),h=h.nextSibling;if(function(t,e){for(var n=0;n=4&&(l={x:parseFloat(c[0]||0),y:parseFloat(c[1]||0),width:parseFloat(c[2]),height:parseFloat(c[3])})}if(l&&null!=a&&null!=s&&(u=fT(l,{x:0,y:0,width:a,height:s}),!e.ignoreViewBox)){var p=i;(i=new Er).add(p),p.scaleX=p.scaleY=u.scale,p.x=u.x,p.y=u.y}return e.ignoreRootClip||null==a||null==s||i.setClipPath(new Es({shape:{x:0,y:0,width:a,height:s}})),{root:i,width:a,height:s,viewBoxRect:l,viewBoxTransform:u,named:r}},t.prototype._parseNode=function(t,e,n,i,r,o){var a,s=t.nodeName.toLowerCase(),l=i;if("defs"===s&&(r=!0),"text"===s&&(o=!0),"defs"===s||"switch"===s)a=e;else{if(!r){var u=UI[s];if(u&&_t(UI,s)){a=u.call(this,t,e);var h=t.getAttribute("name");if(h){var c={name:h,namedFrom:null,svgNodeTagLower:s,el:a};n.push(c),"g"===s&&(l=c)}else i&&n.push({name:i.name,namedFrom:i,svgNodeTagLower:s,el:a});e.add(a)}}var p=tT[s];if(p&&_t(tT,s)){var d=p.call(this,t),f=t.getAttribute("id");f&&(this._defs[f]=d)}}if(a&&a.isGroup)for(var g=t.firstChild;g;)1===g.nodeType?this._parseNode(g,a,n,l,r,o):3===g.nodeType&&o&&this._parseText(g,a),g=g.nextSibling},t.prototype._parseText=function(t,e){var n=new Ts({style:{text:t.textContent},silent:!0,x:this._textX||0,y:this._textY||0});iT(e,n),oT(t,n,this._defsUsePending,!1,!1),function(t,e){var n=e.__selfStyle;if(n){var i=n.textBaseline,r=i;i&&"auto"!==i?"baseline"===i?r="alphabetic":"before-edge"===i||"text-before-edge"===i?r="top":"after-edge"===i||"text-after-edge"===i?r="bottom":"central"!==i&&"mathematical"!==i||(r="middle"):r="alphabetic",t.style.textBaseline=r}var o=e.__inheritedStyle;if(o){var a=o.textAlign,s=a;a&&("middle"===a&&(s="center"),t.style.textAlign=s)}}(n,e);var i=n.style,r=i.fontSize;r&&r<9&&(i.fontSize=9,n.scaleX*=r/9,n.scaleY*=r/9);var o=(i.fontSize||i.fontFamily)&&[i.fontStyle,i.fontWeight,(i.fontSize||12)+"px",i.fontFamily||"sans-serif"].join(" ");i.font=o;var a=n.getBoundingRect();return this._textX+=a.width,e.add(n),n},t.internalField=void(UI={g:function(t,e){var n=new Er;return iT(e,n),oT(t,n,this._defsUsePending,!1,!1),n},rect:function(t,e){var n=new Es;return iT(e,n),oT(t,n,this._defsUsePending,!1,!1),n.setShape({x:parseFloat(t.getAttribute("x")||"0"),y:parseFloat(t.getAttribute("y")||"0"),width:parseFloat(t.getAttribute("width")||"0"),height:parseFloat(t.getAttribute("height")||"0")}),n.silent=!0,n},circle:function(t,e){var n=new xu;return iT(e,n),oT(t,n,this._defsUsePending,!1,!1),n.setShape({cx:parseFloat(t.getAttribute("cx")||"0"),cy:parseFloat(t.getAttribute("cy")||"0"),r:parseFloat(t.getAttribute("r")||"0")}),n.silent=!0,n},line:function(t,e){var n=new Xu;return iT(e,n),oT(t,n,this._defsUsePending,!1,!1),n.setShape({x1:parseFloat(t.getAttribute("x1")||"0"),y1:parseFloat(t.getAttribute("y1")||"0"),x2:parseFloat(t.getAttribute("x2")||"0"),y2:parseFloat(t.getAttribute("y2")||"0")}),n.silent=!0,n},ellipse:function(t,e){var n=new bu;return iT(e,n),oT(t,n,this._defsUsePending,!1,!1),n.setShape({cx:parseFloat(t.getAttribute("cx")||"0"),cy:parseFloat(t.getAttribute("cy")||"0"),rx:parseFloat(t.getAttribute("rx")||"0"),ry:parseFloat(t.getAttribute("ry")||"0")}),n.silent=!0,n},polygon:function(t,e){var n,i=t.getAttribute("points");i&&(n=rT(i));var r=new Gu({shape:{points:n||[]},silent:!0});return iT(e,r),oT(t,r,this._defsUsePending,!1,!1),r},polyline:function(t,e){var n,i=t.getAttribute("points");i&&(n=rT(i));var r=new Hu({shape:{points:n||[]},silent:!0});return iT(e,r),oT(t,r,this._defsUsePending,!1,!1),r},image:function(t,e){var n=new As;return iT(e,n),oT(t,n,this._defsUsePending,!1,!1),n.setStyle({image:t.getAttribute("xlink:href")||t.getAttribute("href"),x:+t.getAttribute("x"),y:+t.getAttribute("y"),width:+t.getAttribute("width"),height:+t.getAttribute("height")}),n.silent=!0,n},text:function(t,e){var n=t.getAttribute("x")||"0",i=t.getAttribute("y")||"0",r=t.getAttribute("dx")||"0",o=t.getAttribute("dy")||"0";this._textX=parseFloat(n)+parseFloat(r),this._textY=parseFloat(i)+parseFloat(o);var a=new Er;return iT(e,a),oT(t,a,this._defsUsePending,!1,!0),a},tspan:function(t,e){var n=t.getAttribute("x"),i=t.getAttribute("y");null!=n&&(this._textX=parseFloat(n)),null!=i&&(this._textY=parseFloat(i));var r=t.getAttribute("dx")||"0",o=t.getAttribute("dy")||"0",a=new Er;return iT(e,a),oT(t,a,this._defsUsePending,!1,!0),this._textX+=parseFloat(r),this._textY+=parseFloat(o),a},path:function(t,e){var n=yu(t.getAttribute("d")||"");return iT(e,n),oT(t,n,this._defsUsePending,!1,!1),n.silent=!0,n}}),t}(),tT={lineargradient:function(t){var e=parseInt(t.getAttribute("x1")||"0",10),n=parseInt(t.getAttribute("y1")||"0",10),i=parseInt(t.getAttribute("x2")||"10",10),r=parseInt(t.getAttribute("y2")||"0",10),o=new eh(e,n,i,r);return eT(t,o),nT(t,o),o},radialgradient:function(t){var e=parseInt(t.getAttribute("cx")||"0",10),n=parseInt(t.getAttribute("cy")||"0",10),i=parseInt(t.getAttribute("r")||"0",10),r=new nh(e,n,i);return eT(t,r),nT(t,r),r}};function eT(t,e){"userSpaceOnUse"===t.getAttribute("gradientUnits")&&(e.global=!0)}function nT(t,e){for(var n=t.firstChild;n;){if(1===n.nodeType&&"stop"===n.nodeName.toLocaleLowerCase()){var i=n.getAttribute("offset"),r=void 0;r=i&&i.indexOf("%")>0?parseInt(i,10)/100:i?parseFloat(i):0;var o={};dT(n,o,o);var a=o.stopColor||n.getAttribute("stop-color")||"#000000";e.colorStops.push({offset:r,color:a})}n=n.nextSibling}}function iT(t,e){t&&t.__inheritedStyle&&(e.__inheritedStyle||(e.__inheritedStyle={}),k(e.__inheritedStyle,t.__inheritedStyle))}function rT(t){for(var e=uT(t),n=[],i=0;i0;o-=2){var a=i[o],s=i[o-1],l=uT(a);switch(r=r||[1,0,0,1,0,0],s){case"translate":be(r,r,[parseFloat(l[0]),parseFloat(l[1]||"0")]);break;case"scale":Se(r,r,[parseFloat(l[0]),parseFloat(l[1]||l[0])]);break;case"rotate":we(r,r,-parseFloat(l[0])*cT);break;case"skewX":_e(r,[1,0,Math.tan(parseFloat(l[0])*cT),1,0,0],r);break;case"skewY":_e(r,[1,Math.tan(parseFloat(l[0])*cT),0,1,0,0],r);break;case"matrix":r[0]=parseFloat(l[0]),r[1]=parseFloat(l[1]),r[2]=parseFloat(l[2]),r[3]=parseFloat(l[3]),r[4]=parseFloat(l[4]),r[5]=parseFloat(l[5])}}e.setLocalTransform(r)}}(t,e),dT(t,a,s),i||function(t,e,n){for(var i=0;i0,f={api:n,geo:s,mapOrGeoModel:t,data:a,isVisualEncodedByVisualMap:d,isGeo:o,transformInfoRaw:c};"geoJSON"===s.resourceType?this._buildGeoJSON(f):"geoSVG"===s.resourceType&&this._buildSVG(f),this._updateController(t,e,n),this._updateMapSelectHandler(t,l,n,i)},t.prototype._buildGeoJSON=function(t){var e=this._regionsGroupByName=yt(),n=yt(),i=this._regionsGroup,r=t.transformInfoRaw,o=t.mapOrGeoModel,a=t.data,s=t.geo.projection,l=s&&s.stream;function u(t,e){return e&&(t=e(t)),t&&[t[0]*r.scaleX+r.x,t[1]*r.scaleY+r.y]}function h(t){for(var e=[],n=!l&&s&&s.project,i=0;i=0)&&(p=r);var d=a?{normal:{align:"center",verticalAlign:"middle"}}:null;Qh(e,tc(i),{labelFetcher:p,labelDataIndex:c,defaultText:n},d);var f=e.getTextContent();if(f&&(NT(f).ignore=f.ignore,e.textConfig&&a)){var g=e.getBoundingRect().clone();e.textConfig.layoutRect=g,e.textConfig.position=[(a[0]-g.x)/g.width*100+"%",(a[1]-g.y)/g.height*100+"%"]}e.disableLabelAnimation=!0}else e.removeTextContent(),e.removeTextConfig(),e.disableLabelAnimation=null}function GT(t,e,n,i,r,o){t.data?t.data.setItemGraphicEl(o,e):Js(e).eventData={componentType:"geo",componentIndex:r.componentIndex,geoIndex:r.componentIndex,name:n,region:i&&i.option||{}}}function WT(t,e,n,i,r){t.data||Xh({el:e,componentModel:r,itemName:n,itemTooltipOption:i.get("tooltip")})}function HT(t,e,n,i,r){e.highDownSilentOnTouch=!!r.get("selectedMode");var o=i.getModel("emphasis"),a=o.get("focus");return Hl(e,a,o.get("blurScope"),o.get("disabled")),t.isGeo&&function(t,e,n){var i=Js(t);i.componentMainType=e.mainType,i.componentIndex=e.componentIndex,i.componentHighDownName=n}(e,r,n),a}function YT(t,e,n){var i,r=[];function o(){i=[]}function a(){i.length&&(r.push(i),i=[])}var s=e({polygonStart:o,polygonEnd:a,lineStart:o,lineEnd:a,point:function(t,e){isFinite(t)&&isFinite(e)&&i.push([t,e])},sphere:function(){}});return!n&&s.polygonStart(),E(t,(function(t){s.lineStart();for(var e=0;e-1&&(n.style.stroke=n.style.fill,n.style.fill="#fff",n.style.lineWidth=2),n},e.type="series.map",e.dependencies=["geo"],e.layoutMode="box",e.defaultOption={z:2,coordinateSystem:"geo",map:"",left:"center",top:"center",aspectScale:null,showLegendSymbol:!0,boundingCoords:null,center:null,zoom:1,scaleLimit:null,selectedMode:!0,label:{show:!1,color:"#000"},itemStyle:{borderWidth:.5,borderColor:"#444",areaColor:"#eee"},emphasis:{label:{show:!0,color:"rgb(100,0,0)"},itemStyle:{areaColor:"rgba(255,215,0,0.8)"}},select:{label:{show:!0,color:"rgb(100,0,0)"},itemStyle:{color:"rgba(255,215,0,0.8)"}},nameProperty:"name"},e}(fg);function ZT(t){var e={};t.eachSeriesByType("map",(function(t){var n=t.getHostGeoModel(),i=n?"o"+n.id:"i"+t.getMapType();(e[i]=e[i]||[]).push(t)})),E(e,(function(t,e){for(var n,i,r,o=(n=z(t,(function(t){return t.getData()})),i=t[0].get("mapValueCalculation"),r={},E(n,(function(t){t.each(t.mapDimension("value"),(function(e,n){var i="ec-"+t.getName(n);r[i]=r[i]||[],isNaN(e)||r[i].push(e)}))})),n[0].map(n[0].mapDimension("value"),(function(t,e){for(var o="ec-"+n[0].getName(e),a=0,s=1/0,l=-1/0,u=r[o].length,h=0;h1?(d.width=p,d.height=p/x):(d.height=p,d.width=p*x),d.y=c[1]-d.height/2,d.x=c[0]-d.width/2;else{var b=t.getBoxLayoutParams();b.aspect=x,d=Tp(b,{width:v,height:m})}this.setViewRect(d.x,d.y,d.width,d.height),this.setCenter(t.get("center"),e),this.setZoom(t.get("zoom"))}R(tC,KT);var iC=function(){function t(){this.dimensions=QT}return t.prototype.create=function(t,e){var n=[];function i(t){return{nameProperty:t.get("nameProperty"),aspectScale:t.get("aspectScale"),projection:t.get("projection")}}t.eachComponent("geo",(function(t,r){var o=t.get("map"),a=new tC(o+r,o,A({nameMap:t.get("nameMap")},i(t)));a.zoomLimit=t.get("scaleLimit"),n.push(a),t.coordinateSystem=a,a.model=t,a.resize=nC,a.resize(t,e)})),t.eachSeries((function(t){if("geo"===t.get("coordinateSystem")){var e=t.get("geoIndex")||0;t.coordinateSystem=n[e]}}));var r={};return t.eachSeriesByType("map",(function(t){if(!t.getHostGeoModel()){var e=t.getMapType();r[e]=r[e]||[],r[e].push(t)}})),E(r,(function(t,r){var o=z(t,(function(t){return t.get("nameMap")})),a=new tC(r,r,A({nameMap:D(o)},i(t[0])));a.zoomLimit=it.apply(null,z(t,(function(t){return t.get("scaleLimit")}))),n.push(a),a.resize=nC,a.resize(t[0],e),E(t,(function(t){t.coordinateSystem=a,function(t,e){E(e.get("geoCoord"),(function(e,n){t.addGeoCoord(n,e)}))}(a,t)}))})),n},t.prototype.getFilledRegions=function(t,e,n,i){for(var r=(t||[]).slice(),o=yt(),a=0;a=0;){var o=e[n];o.hierNode.prelim+=i,o.hierNode.modifier+=i,r+=o.hierNode.change,i+=o.hierNode.shift+r}}(t);var o=(n[0].hierNode.prelim+n[n.length-1].hierNode.prelim)/2;r?(t.hierNode.prelim=r.hierNode.prelim+e(t,r),t.hierNode.modifier=t.hierNode.prelim-o):t.hierNode.prelim=o}else r&&(t.hierNode.prelim=r.hierNode.prelim+e(t,r));t.parentNode.hierNode.defaultAncestor=function(t,e,n,i){if(e){for(var r=t,o=t,a=o.parentNode.children[0],s=e,l=r.hierNode.modifier,u=o.hierNode.modifier,h=a.hierNode.modifier,c=s.hierNode.modifier;s=gC(s),o=yC(o),s&&o;){r=gC(r),a=yC(a),r.hierNode.ancestor=t;var p=s.hierNode.prelim+c-o.hierNode.prelim-u+i(s,o);p>0&&(mC(vC(s,t,n),t,p),u+=p,l+=p),c+=s.hierNode.modifier,u+=o.hierNode.modifier,l+=r.hierNode.modifier,h+=a.hierNode.modifier}s&&!gC(r)&&(r.hierNode.thread=s,r.hierNode.modifier+=c-l),o&&!yC(a)&&(a.hierNode.thread=o,a.hierNode.modifier+=u-h,n=t)}return n}(t,r,t.parentNode.hierNode.defaultAncestor||i[0],e)}function pC(t){var e=t.hierNode.prelim+t.parentNode.hierNode.modifier;t.setLayout({x:e},!0),t.hierNode.modifier+=t.parentNode.hierNode.modifier}function dC(t){return arguments.length?t:xC}function fC(t,e){return t-=Math.PI/2,{x:e*Math.cos(t),y:e*Math.sin(t)}}function gC(t){var e=t.children;return e.length&&t.isExpand?e[e.length-1]:t.hierNode.thread}function yC(t){var e=t.children;return e.length&&t.isExpand?e[0]:t.hierNode.thread}function vC(t,e,n){return t.hierNode.ancestor.parentNode===e.parentNode?t.hierNode.ancestor:n}function mC(t,e,n){var i=n/(e.hierNode.i-t.hierNode.i);e.hierNode.change-=i,e.hierNode.shift+=n,e.hierNode.modifier+=n,e.hierNode.prelim+=n,t.hierNode.change+=i}function xC(t,e){return t.parentNode===e.parentNode?1:2}var _C=function(){this.parentPoint=[],this.childPoints=[]},bC=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:"#000",fill:null}},e.prototype.getDefaultShape=function(){return new _C},e.prototype.buildPath=function(t,e){var n=e.childPoints,i=n.length,r=e.parentPoint,o=n[0],a=n[i-1];if(1===i)return t.moveTo(r[0],r[1]),void t.lineTo(o[0],o[1]);var s=e.orient,l="TB"===s||"BT"===s?0:1,u=1-l,h=Ur(e.forkPosition,1),c=[];c[l]=r[l],c[u]=r[u]+(a[u]-r[u])*h,t.moveTo(r[0],r[1]),t.lineTo(c[0],c[1]),t.moveTo(o[0],o[1]),c[l]=o[l],t.lineTo(c[0],c[1]),c[l]=a[l],t.lineTo(c[0],c[1]),t.lineTo(a[0],a[1]);for(var p=1;pm.x)||(_-=Math.PI);var S=b?"left":"right",M=s.getModel("label"),I=M.get("rotate"),T=I*(Math.PI/180),C=y.getTextContent();C&&(y.setTextConfig({position:M.get("position")||S,rotation:null==I?-_:T,origin:"center"}),C.setStyle("verticalAlign","middle"))}var D=s.get(["emphasis","focus"]),A="relative"===D?vt(a.getAncestorsIndices(),a.getDescendantIndices()):"ancestor"===D?a.getAncestorsIndices():"descendant"===D?a.getDescendantIndices():null;A&&(Js(n).focus=A),function(t,e,n,i,r,o,a,s){var l=e.getModel(),u=t.get("edgeShape"),h=t.get("layout"),c=t.getOrient(),p=t.get(["lineStyle","curveness"]),d=t.get("edgeForkPosition"),f=l.getModel("lineStyle").getLineStyle(),g=i.__edge;if("curve"===u)e.parentNode&&e.parentNode!==n&&(g||(g=i.__edge=new Ku({shape:DC(h,c,p,r,r)})),dh(g,{shape:DC(h,c,p,o,a)},t));else if("polyline"===u)if("orthogonal"===h){if(e!==n&&e.children&&0!==e.children.length&&!0===e.isExpand){for(var y=e.children,v=[],m=0;me&&(e=i.height)}this.height=e+1},t.prototype.getNodeById=function(t){if(this.getId()===t)return this;for(var e=0,n=this.children,i=n.length;e=0&&this.hostTree.data.setItemLayout(this.dataIndex,t,e)},t.prototype.getLayout=function(){return this.hostTree.data.getItemLayout(this.dataIndex)},t.prototype.getModel=function(t){if(!(this.dataIndex<0))return this.hostTree.data.getItemModel(this.dataIndex).getModel(t)},t.prototype.getLevelModel=function(){return(this.hostTree.levelModels||[])[this.depth]},t.prototype.setVisual=function(t,e){this.dataIndex>=0&&this.hostTree.data.setItemVisual(this.dataIndex,t,e)},t.prototype.getVisual=function(t){return this.hostTree.data.getItemVisual(this.dataIndex,t)},t.prototype.getRawIndex=function(){return this.hostTree.data.getRawIndex(this.dataIndex)},t.prototype.getId=function(){return this.hostTree.data.getId(this.dataIndex)},t.prototype.getChildIndex=function(){if(this.parentNode){for(var t=this.parentNode.children,e=0;e=0){var i=n.getData().tree.root,r=t.targetNode;if(X(r)&&(r=i.getNodeById(r)),r&&i.contains(r))return{node:r};var o=t.targetNodeId;if(null!=o&&(r=i.getNodeById(o)))return{node:r}}}function GC(t){for(var e=[];t;)(t=t.parentNode)&&e.push(t);return e.reverse()}function WC(t,e){return P(GC(t),e)>=0}function HC(t,e){for(var n=[];t;){var i=t.dataIndex;n.push({name:t.name,dataIndex:i,value:e.getRawValue(i)}),t=t.parentNode}return n.reverse(),n}var YC=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.hasSymbolVisual=!0,e.ignoreStyleOnData=!0,e}return n(e,t),e.prototype.getInitialData=function(t){var e={name:t.name,children:t.data},n=t.leaves||{},i=new Sc(n,this,this.ecModel),r=BC.createTree(e,this,(function(t){t.wrapMethod("getItemModel",(function(t,e){var n=r.getNodeByDataIndex(e);return n&&n.children.length&&n.isExpand||(t.parentModel=i),t}))}));var o=0;r.eachNode("preorder",(function(t){t.depth>o&&(o=t.depth)}));var a=t.expandAndCollapse&&t.initialTreeDepth>=0?t.initialTreeDepth:o;return r.root.eachNode("preorder",(function(t){var e=t.hostTree.data.getRawDataItem(t.dataIndex);t.isExpand=e&&null!=e.collapsed?!e.collapsed:t.depth<=a})),r.data},e.prototype.getOrient=function(){var t=this.get("orient");return"horizontal"===t?t="LR":"vertical"===t&&(t="TB"),t},e.prototype.setZoom=function(t){this.option.zoom=t},e.prototype.setCenter=function(t){this.option.center=t},e.prototype.formatTooltip=function(t,e,n){for(var i=this.getData().tree,r=i.root.children[0],o=i.getNodeByDataIndex(t),a=o.getValue(),s=o.name;o&&o!==r;)s=o.parentNode.name+"."+s,o=o.parentNode;return Qf("nameValue",{name:s,value:a,noValue:isNaN(a)||null==a})},e.prototype.getDataParams=function(e){var n=t.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(e);return n.treeAncestors=HC(i,this),n.collapsed=!i.isExpand,n},e.type="series.tree",e.layoutMode="box",e.defaultOption={z:2,coordinateSystem:"view",left:"12%",top:"12%",right:"12%",bottom:"12%",layout:"orthogonal",edgeShape:"curve",edgeForkPosition:"50%",roam:!1,nodeScaleRatio:.4,center:null,zoom:1,orient:"LR",symbol:"emptyCircle",symbolSize:7,expandAndCollapse:!0,initialTreeDepth:2,lineStyle:{color:"#ccc",width:1.5,curveness:.5},itemStyle:{color:"lightsteelblue",borderWidth:1.5},label:{show:!0},animationEasing:"linear",animationDuration:700,animationDurationUpdate:500},e}(fg);function UC(t,e){for(var n,i=[t];n=i.pop();)if(e(n),n.isExpand){var r=n.children;if(r.length)for(var o=r.length-1;o>=0;o--)i.push(r[o])}}function XC(t,e){t.eachSeriesByType("tree",(function(t){!function(t,e){var n=function(t,e){return Tp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,e);t.layoutInfo=n;var i=t.get("layout"),r=0,o=0,a=null;"radial"===i?(r=2*Math.PI,o=Math.min(n.height,n.width)/2,a=dC((function(t,e){return(t.parentNode===e.parentNode?1:2)/t.depth}))):(r=n.width,o=n.height,a=dC());var s=t.getData().tree.root,l=s.children[0];if(l){!function(t){var e=t;e.hierNode={defaultAncestor:null,ancestor:e,prelim:0,modifier:0,change:0,shift:0,i:0,thread:null};for(var n,i,r=[e];n=r.pop();)if(i=n.children,n.isExpand&&i.length)for(var o=i.length-1;o>=0;o--){var a=i[o];a.hierNode={defaultAncestor:null,ancestor:a,prelim:0,modifier:0,change:0,shift:0,i:o,thread:null},r.push(a)}}(s),function(t,e,n){for(var i,r=[t],o=[];i=r.pop();)if(o.push(i),i.isExpand){var a=i.children;if(a.length)for(var s=0;sh.getLayout().x&&(h=t),t.depth>c.depth&&(c=t)}));var p=u===h?1:a(u,h)/2,d=p-u.getLayout().x,f=0,g=0,y=0,v=0;if("radial"===i)f=r/(h.getLayout().x+p+d),g=o/(c.depth-1||1),UC(l,(function(t){y=(t.getLayout().x+d)*f,v=(t.depth-1)*g;var e=fC(y,v);t.setLayout({x:e.x,y:e.y,rawX:y,rawY:v},!0)}));else{var m=t.getOrient();"RL"===m||"LR"===m?(g=o/(h.getLayout().x+p+d),f=r/(c.depth-1||1),UC(l,(function(t){v=(t.getLayout().x+d)*g,y="LR"===m?(t.depth-1)*f:r-(t.depth-1)*f,t.setLayout({x:y,y:v},!0)}))):"TB"!==m&&"BT"!==m||(f=r/(h.getLayout().x+p+d),g=o/(c.depth-1||1),UC(l,(function(t){y=(t.getLayout().x+d)*f,v="TB"===m?(t.depth-1)*g:o-(t.depth-1)*g,t.setLayout({x:y,y:v},!0)})))}}}(t,e)}))}function ZC(t){t.eachSeriesByType("tree",(function(t){var e=t.getData();e.tree.eachNode((function(t){var n=t.getModel().getModel("itemStyle").getItemStyle();A(e.ensureUniqueItemVisual(t.dataIndex,"style"),n)}))}))}var jC=["treemapZoomToNode","treemapRender","treemapMove"];function qC(t){var e=t.getData().tree,n={};e.eachNode((function(e){for(var i=e;i&&i.depth>1;)i=i.parentNode;var r=ld(t.ecModel,i.name||i.dataIndex+"",n);e.setVisual("decal",r)}))}var KC=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.preventUsingHoverLayer=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){var n={name:t.name,children:t.data};$C(n);var i=t.levels||[],r=this.designatedVisualItemStyle={},o=new Sc({itemStyle:r},this,e);i=t.levels=function(t,e){var n,i,r=_o(e.get("color")),o=_o(e.get(["aria","decal","decals"]));if(!r)return;E(t=t||[],(function(t){var e=new Sc(t),r=e.get("color"),o=e.get("decal");(e.get(["itemStyle","color"])||r&&"none"!==r)&&(n=!0),(e.get(["itemStyle","decal"])||o&&"none"!==o)&&(i=!0)}));var a=t[0]||(t[0]={});n||(a.color=r.slice());!i&&o&&(a.decal=o.slice());return t}(i,e);var a=z(i||[],(function(t){return new Sc(t,o,e)}),this),s=BC.createTree(n,this,(function(t){t.wrapMethod("getItemModel",(function(t,e){var n=s.getNodeByDataIndex(e),i=n?a[n.depth]:null;return t.parentModel=i||o,t}))}));return s.data},e.prototype.optionUpdated=function(){this.resetViewRoot()},e.prototype.formatTooltip=function(t,e,n){var i=this.getData(),r=this.getRawValue(t);return Qf("nameValue",{name:i.getName(t),value:r})},e.prototype.getDataParams=function(e){var n=t.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(e);return n.treeAncestors=HC(i,this),n.treePathInfo=n.treeAncestors,n},e.prototype.setLayoutInfo=function(t){this.layoutInfo=this.layoutInfo||{},A(this.layoutInfo,t)},e.prototype.mapIdToIndex=function(t){var e=this._idIndexMap;e||(e=this._idIndexMap=yt(),this._idIndexMapCount=0);var n=e.get(t);return null==n&&e.set(t,n=this._idIndexMapCount++),n},e.prototype.getViewRoot=function(){return this._viewRoot},e.prototype.resetViewRoot=function(t){t?this._viewRoot=t:t=this._viewRoot;var e=this.getRawData().tree.root;t&&(t===e||e.contains(t))||(this._viewRoot=e)},e.prototype.enableAriaDecal=function(){qC(this)},e.type="series.treemap",e.layoutMode="box",e.defaultOption={progressive:0,left:"center",top:"middle",width:"80%",height:"80%",sort:!0,clipWindow:"origin",squareRatio:.5*(1+Math.sqrt(5)),leafDepth:null,drillDownIcon:"▶",zoomToNodeRatio:.1024,roam:!0,nodeClick:"zoomToNode",animation:!0,animationDurationUpdate:900,animationEasing:"quinticInOut",breadcrumb:{show:!0,height:22,left:"center",top:"bottom",emptyItemWidth:25,itemStyle:{color:"rgba(0,0,0,0.7)",textStyle:{color:"#fff"}},emphasis:{itemStyle:{color:"rgba(0,0,0,0.9)"}}},label:{show:!0,distance:0,padding:5,position:"inside",color:"#fff",overflow:"truncate"},upperLabel:{show:!1,position:[0,"50%"],height:20,overflow:"truncate",verticalAlign:"middle"},itemStyle:{color:null,colorAlpha:null,colorSaturation:null,borderWidth:0,gapWidth:0,borderColor:"#fff",borderColorSaturation:null},emphasis:{upperLabel:{show:!0,position:[0,"50%"],overflow:"truncate",verticalAlign:"middle"}},visualDimension:0,visualMin:null,visualMax:null,color:[],colorAlpha:null,colorSaturation:null,colorMappingBy:"index",visibleMin:10,childrenVisibleMin:null,levels:[]},e}(fg);function $C(t){var e=0;E(t.children,(function(t){$C(t);var n=t.value;Y(n)&&(n=n[0]),e+=n}));var n=t.value;Y(n)&&(n=n[0]),(null==n||isNaN(n))&&(n=e),n<0&&(n=0),Y(t.value)?t.value[0]=n:t.value=n}var JC=function(){function t(t){this.group=new Er,t.add(this.group)}return t.prototype.render=function(t,e,n,i){var r=t.getModel("breadcrumb"),o=this.group;if(o.removeAll(),r.get("show")&&n){var a=r.getModel("itemStyle"),s=r.getModel("emphasis"),l=a.getModel("textStyle"),u=s.getModel(["itemStyle","textStyle"]),h={pos:{left:r.get("left"),right:r.get("right"),top:r.get("top"),bottom:r.get("bottom")},box:{width:e.getWidth(),height:e.getHeight()},emptyItemWidth:r.get("emptyItemWidth"),totalWidth:0,renderList:[]};this._prepare(n,h,l),this._renderContent(t,h,a,s,l,u,i),Cp(o,h.pos,h.box)}},t.prototype._prepare=function(t,e,n){for(var i=t;i;i=i.parentNode){var r=Do(i.getModel().get("name"),""),o=n.getTextRect(r),a=Math.max(o.width+16,e.emptyItemWidth);e.totalWidth+=a+8,e.renderList.push({node:i,text:r,width:a})}},t.prototype._renderContent=function(t,e,n,i,r,o,a){for(var s,l,u,h,c,p,d,f,g,y=0,v=e.emptyItemWidth,m=t.get(["breadcrumb","height"]),x=(s=e.pos,l=e.box,h=l.width,c=l.height,p=Ur(s.left,h),d=Ur(s.top,c),f=Ur(s.right,h),g=Ur(s.bottom,c),(isNaN(p)||isNaN(parseFloat(s.left)))&&(p=0),(isNaN(f)||isNaN(parseFloat(s.right)))&&(f=h),(isNaN(d)||isNaN(parseFloat(s.top)))&&(d=0),(isNaN(g)||isNaN(parseFloat(s.bottom)))&&(g=c),u=dp(u||0),{width:Math.max(f-p-u[1]-u[3],0),height:Math.max(g-d-u[0]-u[2],0)}),_=e.totalWidth,b=e.renderList,w=i.getModel("itemStyle").getItemStyle(),S=b.length-1;S>=0;S--){var M=b[S],I=M.node,T=M.width,C=M.text;_>x.width&&(_-=T-v,T=v,C=null);var D=new Gu({shape:{points:QC(y,0,T,m,S===b.length-1,0===S)},style:k(n.getItemStyle(),{lineJoin:"bevel"}),textContent:new Bs({style:ec(r,{text:C})}),textConfig:{position:"inside"},z2:1e5,onclick:H(a,I)});D.disableLabelAnimation=!0,D.getTextContent().ensureState("emphasis").style=ec(o,{text:C}),D.ensureState("emphasis").style=w,Hl(D,i.get("focus"),i.get("blurScope"),i.get("disabled")),this.group.add(D),tD(D,t,I),y+=T+8}},t.prototype.remove=function(){this.group.removeAll()},t}();function QC(t,e,n,i,r,o){var a=[[r?t:t-5,e],[t+n,e],[t+n,e+i],[r?t:t-5,e+i]];return!o&&a.splice(2,0,[t+n+5,e+i/2]),!r&&a.push([t,e+i/2]),a}function tD(t,e,n){Js(t).eventData={componentType:"series",componentSubType:"treemap",componentIndex:e.componentIndex,seriesIndex:e.seriesIndex,seriesName:e.name,seriesType:"treemap",selfType:"breadcrumb",nodeData:{dataIndex:n&&n.dataIndex,name:n&&n.name},treePathInfo:n&&HC(n,e)}}var eD=function(){function t(){this._storage=[],this._elExistsMap={}}return t.prototype.add=function(t,e,n,i,r){return!this._elExistsMap[t.id]&&(this._elExistsMap[t.id]=!0,this._storage.push({el:t,target:e,duration:n,delay:i,easing:r}),!0)},t.prototype.finished=function(t){return this._finishedCallback=t,this},t.prototype.start=function(){for(var t=this,e=this._storage.length,n=function(){--e<=0&&(t._storage.length=0,t._elExistsMap={},t._finishedCallback&&t._finishedCallback())},i=0,r=this._storage.length;i3||Math.abs(t.dy)>3)){var e=this.seriesModel.getData().tree.root;if(!e)return;var n=e.getLayout();if(!n)return;this.api.dispatchAction({type:"treemapMove",from:this.uid,seriesId:this.seriesModel.id,rootRect:{x:n.x+t.dx,y:n.y+t.dy,width:n.width,height:n.height}})}},e.prototype._onZoom=function(t){var e=t.originX,n=t.originY;if("animating"!==this._state){var i=this.seriesModel.getData().tree.root;if(!i)return;var r=i.getLayout();if(!r)return;var o=new Ee(r.x,r.y,r.width,r.height),a=this.seriesModel.layoutInfo,s=[1,0,0,1,0,0];be(s,s,[-(e-=a.x),-(n-=a.y)]),Se(s,s,[t.scale,t.scale]),be(s,s,[e,n]),o.applyTransform(s),this.api.dispatchAction({type:"treemapRender",from:this.uid,seriesId:this.seriesModel.id,rootRect:{x:o.x,y:o.y,width:o.width,height:o.height}})}},e.prototype._initEvents=function(t){var e=this;t.on("click",(function(t){if("ready"===e._state){var n=e.seriesModel.get("nodeClick",!0);if(n){var i=e.findTarget(t.offsetX,t.offsetY);if(i){var r=i.node;if(r.getLayout().isLeafRoot)e._rootToNode(i);else if("zoomToNode"===n)e._zoomToNode(i);else if("link"===n){var o=r.hostTree.data.getItemModel(r.dataIndex),a=o.get("link",!0),s=o.get("target",!0)||"blank";a&&_p(a,s)}}}}}),this)},e.prototype._renderBreadcrumb=function(t,e,n){var i=this;n||(n=null!=t.get("leafDepth",!0)?{node:t.getViewRoot()}:this.findTarget(e.getWidth()/2,e.getHeight()/2))||(n={node:t.getData().tree.root}),(this._breadcrumb||(this._breadcrumb=new JC(this.group))).render(t,e,n.node,(function(e){"animating"!==i._state&&(WC(t.getViewRoot(),e)?i._rootToNode({node:e}):i._zoomToNode({node:e}))}))},e.prototype.remove=function(){this._clearController(),this._containerGroup&&this._containerGroup.removeAll(),this._storage={nodeGroup:[],background:[],content:[]},this._state="ready",this._breadcrumb&&this._breadcrumb.remove()},e.prototype.dispose=function(){this._clearController()},e.prototype._zoomToNode=function(t){this.api.dispatchAction({type:"treemapZoomToNode",from:this.uid,seriesId:this.seriesModel.id,targetNode:t.node})},e.prototype._rootToNode=function(t){this.api.dispatchAction({type:"treemapRootToNode",from:this.uid,seriesId:this.seriesModel.id,targetNode:t.node})},e.prototype.findTarget=function(t,e){var n;return this.seriesModel.getViewRoot().eachNode({attr:"viewChildren",order:"preorder"},(function(i){var r=this._storage.background[i.getRawIndex()];if(r){var o=r.transformCoordToLocal(t,e),a=r.shape;if(!(a.x<=o[0]&&o[0]<=a.x+a.width&&a.y<=o[1]&&o[1]<=a.y+a.height))return!1;n={node:i,offsetX:o[0],offsetY:o[1]}}}),this),n},e.type="treemap",e}(Tg);var hD=E,cD=q,pD=-1,dD=function(){function t(e){var n=e.mappingMethod,i=e.type,r=this.option=T(e);this.type=i,this.mappingMethod=n,this._normalizeData=SD[n];var o=t.visualHandlers[i];this.applyVisual=o.applyVisual,this.getColorMapper=o.getColorMapper,this._normalizedToVisual=o._normalizedToVisual[n],"piecewise"===n?(fD(r),function(t){var e=t.pieceList;t.hasSpecialVisual=!1,E(e,(function(e,n){e.originIndex=n,null!=e.visual&&(t.hasSpecialVisual=!0)}))}(r)):"category"===n?r.categories?function(t){var e=t.categories,n=t.categoryMap={},i=t.visual;if(hD(e,(function(t,e){n[t]=e})),!Y(i)){var r=[];q(i)?hD(i,(function(t,e){var i=n[e];r[null!=i?i:pD]=t})):r[-1]=i,i=wD(t,r)}for(var o=e.length-1;o>=0;o--)null==i[o]&&(delete n[e[o]],e.pop())}(r):fD(r,!0):(lt("linear"!==n||r.dataExtent),fD(r))}return t.prototype.mapValueToVisual=function(t){var e=this._normalizeData(t);return this._normalizedToVisual(e,t)},t.prototype.getNormalizer=function(){return W(this._normalizeData,this)},t.listVisualTypes=function(){return G(t.visualHandlers)},t.isValidType=function(e){return t.visualHandlers.hasOwnProperty(e)},t.eachVisual=function(t,e,n){q(t)?E(t,e,n):e.call(n,t)},t.mapVisual=function(e,n,i){var r,o=Y(e)?[]:q(e)?{}:(r=!0,null);return t.eachVisual(e,(function(t,e){var a=n.call(i,t,e);r?o=a:o[e]=a})),o},t.retrieveVisuals=function(e){var n,i={};return e&&hD(t.visualHandlers,(function(t,r){e.hasOwnProperty(r)&&(i[r]=e[r],n=!0)})),n?i:null},t.prepareVisualTypes=function(t){if(Y(t))t=t.slice();else{if(!cD(t))return[];var e=[];hD(t,(function(t,n){e.push(n)})),t=e}return t.sort((function(t,e){return"color"===e&&"color"!==t&&0===t.indexOf("color")?1:-1})),t},t.dependsOn=function(t,e){return"color"===e?!(!t||0!==t.indexOf(e)):t===e},t.findPieceIndex=function(t,e,n){for(var i,r=1/0,o=0,a=e.length;ou[1]&&(u[1]=l);var h=e.get("colorMappingBy"),c={type:a.name,dataExtent:u,visual:a.range};"color"!==c.type||"index"!==h&&"id"!==h?c.mappingMethod="linear":(c.mappingMethod="category",c.loop=!0);var p=new dD(c);return ID(p).drColorMappingBy=h,p}(0,r,o,0,u,d);E(d,(function(t,e){if(t.depth>=n.length||t===n[t.depth]){var o=function(t,e,n,i,r,o){var a=A({},e);if(r){var s=r.type,l="color"===s&&ID(r).drColorMappingBy,u="index"===l?i:"id"===l?o.mapIdToIndex(n.getId()):n.getValue(t.get("visualDimension"));a[s]=r.mapValueToVisual(u)}return a}(r,u,t,e,f,i);CD(t,o,n,i)}}))}else s=DD(u),h.fill=s}}function DD(t){var e=AD(t,"color");if(e){var n=AD(t,"colorAlpha"),i=AD(t,"colorSaturation");return i&&(e=ei(e,null,null,i)),n&&(e=ni(e,n)),e}}function AD(t,e){var n=t[e];if(null!=n&&"none"!==n)return n}function kD(t,e){var n=t.get(e);return Y(n)&&n.length?{name:e,range:n}:null}var LD=Math.max,PD=Math.min,OD=it,RD=E,ND=["itemStyle","borderWidth"],ED=["itemStyle","gapWidth"],zD=["upperLabel","show"],VD=["upperLabel","height"],BD={seriesType:"treemap",reset:function(t,e,n,i){var r=n.getWidth(),o=n.getHeight(),a=t.option,s=Tp(t.getBoxLayoutParams(),{width:n.getWidth(),height:n.getHeight()}),l=a.size||[],u=Ur(OD(s.width,l[0]),r),h=Ur(OD(s.height,l[1]),o),c=i&&i.type,p=FC(i,["treemapZoomToNode","treemapRootToNode"],t),d="treemapRender"===c||"treemapMove"===c?i.rootRect:null,f=t.getViewRoot(),g=GC(f);if("treemapMove"!==c){var y="treemapZoomToNode"===c?function(t,e,n,i,r){var o,a=(e||{}).node,s=[i,r];if(!a||a===n)return s;var l=i*r,u=l*t.option.zoomToNodeRatio;for(;o=a.parentNode;){for(var h=0,c=o.children,p=0,d=c.length;pQr&&(u=Qr),a=o}ua[1]&&(a[1]=e)}))):a=[NaN,NaN];return{sum:i,dataExtent:a}}(e,a,s);if(0===u.sum)return t.viewChildren=[];if(u.sum=function(t,e,n,i,r){if(!i)return n;for(var o=t.get("visibleMin"),a=r.length,s=a,l=a-1;l>=0;l--){var u=r["asc"===i?a-l-1:l].getValue();u/n*ei&&(i=a));var l=t.area*t.area,u=e*e*n;return l?LD(u*i/l,l/(u*r)):1/0}function WD(t,e,n,i,r){var o=e===n.width?0:1,a=1-o,s=["x","y"],l=["width","height"],u=n[s[o]],h=e?t.area/e:0;(r||h>n[l[a]])&&(h=n[l[a]]);for(var c=0,p=t.length;ci&&(i=e);var o=i%2?i+2:i+3;r=[];for(var a=0;a0&&(m[0]=-m[0],m[1]=-m[1]);var _=v[0]<0?-1:1;if("start"!==i.__position&&"end"!==i.__position){var b=-Math.atan2(v[1],v[0]);u[0].8?"left":h[0]<-.8?"right":"center",p=h[1]>.8?"top":h[1]<-.8?"bottom":"middle";break;case"start":i.x=-h[0]*f+l[0],i.y=-h[1]*g+l[1],c=h[0]>.8?"right":h[0]<-.8?"left":"center",p=h[1]>.8?"bottom":h[1]<-.8?"top":"middle";break;case"insideStartTop":case"insideStart":case"insideStartBottom":i.x=f*_+l[0],i.y=l[1]+w,c=v[0]<0?"right":"left",i.originX=-f*_,i.originY=-w;break;case"insideMiddleTop":case"insideMiddle":case"insideMiddleBottom":case"middle":i.x=x[0],i.y=x[1]+w,c="center",i.originY=-w;break;case"insideEndTop":case"insideEnd":case"insideEndBottom":i.x=-f*_+u[0],i.y=u[1]+w,c=v[0]>=0?"right":"left",i.originX=f*_,i.originY=-w}i.scaleX=i.scaleY=r,i.setStyle({verticalAlign:i.__verticalAlign||p,align:i.__align||c})}}}function S(t,e){var n=t.__specifiedRotation;if(null==n){var i=a.tangentAt(e);t.attr("rotation",(1===e?-1:1)*Math.PI/2-Math.atan2(i[1],i[0]))}else t.attr("rotation",n)}},e}(Er),TA=function(){function t(t){this.group=new Er,this._LineCtor=t||IA}return t.prototype.updateData=function(t){var e=this;this._progressiveEls=null;var n=this,i=n.group,r=n._lineData;n._lineData=t,r||i.removeAll();var o=CA(t);t.diff(r).add((function(n){e._doAdd(t,n,o)})).update((function(n,i){e._doUpdate(r,t,i,n,o)})).remove((function(t){i.remove(r.getItemGraphicEl(t))})).execute()},t.prototype.updateLayout=function(){var t=this._lineData;t&&t.eachItemGraphicEl((function(e,n){e.updateLayout(t,n)}),this)},t.prototype.incrementalPrepareUpdate=function(t){this._seriesScope=CA(t),this._lineData=null,this.group.removeAll()},t.prototype.incrementalUpdate=function(t,e){function n(t){t.isGroup||function(t){return t.animators&&t.animators.length>0}(t)||(t.incremental=!0,t.ensureState("emphasis").hoverLayer=!0)}this._progressiveEls=[];for(var i=t.start;i=0?i+=u:i-=u:f>=0?i-=u:i+=u}return i}function zA(t,e){var n=[],i=Cn,r=[[],[],[]],o=[[],[]],a=[];e/=2,t.eachEdge((function(t,s){var l=t.getLayout(),u=t.getVisual("fromSymbol"),h=t.getVisual("toSymbol");l.__original||(l.__original=[Tt(l[0]),Tt(l[1])],l[2]&&l.__original.push(Tt(l[2])));var c=l.__original;if(null!=l[2]){if(It(r[0],c[0]),It(r[1],c[2]),It(r[2],c[1]),u&&"none"!==u){var p=aA(t.node1),d=EA(r,c[0],p*e);i(r[0][0],r[1][0],r[2][0],d,n),r[0][0]=n[3],r[1][0]=n[4],i(r[0][1],r[1][1],r[2][1],d,n),r[0][1]=n[3],r[1][1]=n[4]}if(h&&"none"!==h){p=aA(t.node2),d=EA(r,c[1],p*e);i(r[0][0],r[1][0],r[2][0],d,n),r[1][0]=n[1],r[2][0]=n[2],i(r[0][1],r[1][1],r[2][1],d,n),r[1][1]=n[1],r[2][1]=n[2]}It(l[0],r[0]),It(l[1],r[2]),It(l[2],r[1])}else{if(It(o[0],c[0]),It(o[1],c[1]),kt(a,o[1],o[0]),Et(a,a),u&&"none"!==u){p=aA(t.node1);At(o[0],o[0],a,p*e)}if(h&&"none"!==h){p=aA(t.node2);At(o[1],o[1],a,-p*e)}It(l[0],o[0]),It(l[1],o[1])}}))}function VA(t){return"view"===t.type}var BA=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){var n=new iS,i=new TA,r=this.group;this._controller=new BI(e.getZr()),this._controllerHost={target:r},r.add(n.group),r.add(i.group),this._symbolDraw=n,this._lineDraw=i,this._firstRender=!0},e.prototype.render=function(t,e,n){var i=this,r=t.coordinateSystem;this._model=t;var o=this._symbolDraw,a=this._lineDraw,s=this.group;if(VA(r)){var l={x:r.x,y:r.y,scaleX:r.scaleX,scaleY:r.scaleY};this._firstRender?s.attr(l):dh(s,l,t)}zA(t.getGraph(),oA(t));var u=t.getData();o.updateData(u);var h=t.getEdgeData();a.updateData(h),this._updateNodeAndLinkScale(),this._updateController(t,e,n),clearTimeout(this._layoutTimeout);var c=t.forceLayout,p=t.get(["force","layoutAnimation"]);c&&this._startForceLayoutIteration(c,p);var d=t.get("layout");u.graph.eachNode((function(e){var n=e.dataIndex,r=e.getGraphicEl(),o=e.getModel();if(r){r.off("drag").off("dragend");var a=o.get("draggable");a&&r.on("drag",(function(o){switch(d){case"force":c.warmUp(),!i._layouting&&i._startForceLayoutIteration(c,p),c.setFixed(n),u.setItemLayout(n,[r.x,r.y]);break;case"circular":u.setItemLayout(n,[r.x,r.y]),e.setLayout({fixed:!0},!0),uA(t,"symbolSize",e,[o.offsetX,o.offsetY]),i.updateLayout(t);break;default:u.setItemLayout(n,[r.x,r.y]),iA(t.getGraph(),t),i.updateLayout(t)}})).on("dragend",(function(){c&&c.setUnfixed(n)})),r.setDraggable(a,!!o.get("cursor")),"adjacency"===o.get(["emphasis","focus"])&&(Js(r).focus=e.getAdjacentDataIndices())}})),u.graph.eachEdge((function(t){var e=t.getGraphicEl(),n=t.getModel().get(["emphasis","focus"]);e&&"adjacency"===n&&(Js(e).focus={edge:[t.dataIndex],node:[t.node1.dataIndex,t.node2.dataIndex]})}));var f="circular"===t.get("layout")&&t.get(["circular","rotateLabel"]),g=u.getLayout("cx"),y=u.getLayout("cy");u.graph.eachNode((function(t){cA(t,f,g,y)})),this._firstRender=!1},e.prototype.dispose=function(){this._controller&&this._controller.dispose(),this._controllerHost=null},e.prototype._startForceLayoutIteration=function(t,e){var n=this;!function i(){t.step((function(t){n.updateLayout(n._model),(n._layouting=!t)&&(e?n._layoutTimeout=setTimeout(i,16):i())}))}()},e.prototype._updateController=function(t,e,n){var i=this,r=this._controller,o=this._controllerHost,a=this.group;r.setPointerChecker((function(e,i,r){var o=a.getBoundingRect();return o.applyTransform(a.transform),o.contain(i,r)&&!ZI(e,n,t)})),VA(t.coordinateSystem)?(r.enable(t.get("roam")),o.zoomLimit=t.get("scaleLimit"),o.zoom=t.coordinateSystem.getZoom(),r.off("pan").off("zoom").on("pan",(function(e){HI(o,e.dx,e.dy),n.dispatchAction({seriesId:t.id,type:"graphRoam",dx:e.dx,dy:e.dy})})).on("zoom",(function(e){YI(o,e.scale,e.originX,e.originY),n.dispatchAction({seriesId:t.id,type:"graphRoam",zoom:e.scale,originX:e.originX,originY:e.originY}),i._updateNodeAndLinkScale(),zA(t.getGraph(),oA(t)),i._lineDraw.updateLayout(),n.updateLabelLayout()}))):r.disable()},e.prototype._updateNodeAndLinkScale=function(){var t=this._model,e=t.getData(),n=oA(t);e.eachItemGraphicEl((function(t,e){t&&t.setSymbolScale(n)}))},e.prototype.updateLayout=function(t){zA(t.getGraph(),oA(t)),this._symbolDraw.updateLayout(),this._lineDraw.updateLayout()},e.prototype.remove=function(t,e){this._symbolDraw&&this._symbolDraw.remove(),this._lineDraw&&this._lineDraw.remove()},e.type="graph",e}(Tg);function FA(t){return"_EC_"+t}var GA=function(){function t(t){this.type="graph",this.nodes=[],this.edges=[],this._nodesMap={},this._edgesMap={},this._directed=t||!1}return t.prototype.isDirected=function(){return this._directed},t.prototype.addNode=function(t,e){t=null==t?""+e:""+t;var n=this._nodesMap;if(!n[FA(t)]){var i=new WA(t,e);return i.hostGraph=this,this.nodes.push(i),n[FA(t)]=i,i}},t.prototype.getNodeByIndex=function(t){var e=this.data.getRawIndex(t);return this.nodes[e]},t.prototype.getNodeById=function(t){return this._nodesMap[FA(t)]},t.prototype.addEdge=function(t,e,n){var i=this._nodesMap,r=this._edgesMap;if(j(t)&&(t=this.nodes[t]),j(e)&&(e=this.nodes[e]),t instanceof WA||(t=i[FA(t)]),e instanceof WA||(e=i[FA(e)]),t&&e){var o=t.id+"-"+e.id,a=new HA(t,e,n);return a.hostGraph=this,this._directed&&(t.outEdges.push(a),e.inEdges.push(a)),t.edges.push(a),t!==e&&e.edges.push(a),this.edges.push(a),r[o]=a,a}},t.prototype.getEdgeByIndex=function(t){var e=this.edgeData.getRawIndex(t);return this.edges[e]},t.prototype.getEdge=function(t,e){t instanceof WA&&(t=t.id),e instanceof WA&&(e=e.id);var n=this._edgesMap;return this._directed?n[t+"-"+e]:n[t+"-"+e]||n[e+"-"+t]},t.prototype.eachNode=function(t,e){for(var n=this.nodes,i=n.length,r=0;r=0&&t.call(e,n[r],r)},t.prototype.eachEdge=function(t,e){for(var n=this.edges,i=n.length,r=0;r=0&&n[r].node1.dataIndex>=0&&n[r].node2.dataIndex>=0&&t.call(e,n[r],r)},t.prototype.breadthFirstTraverse=function(t,e,n,i){if(e instanceof WA||(e=this._nodesMap[FA(e)]),e){for(var r="out"===n?"outEdges":"in"===n?"inEdges":"edges",o=0;o=0&&n.node2.dataIndex>=0}));for(r=0,o=i.length;r=0&&this[t][e].setItemVisual(this.dataIndex,n,i)},getVisual:function(n){return this[t][e].getItemVisual(this.dataIndex,n)},setLayout:function(n,i){this.dataIndex>=0&&this[t][e].setItemLayout(this.dataIndex,n,i)},getLayout:function(){return this[t][e].getItemLayout(this.dataIndex)},getGraphicEl:function(){return this[t][e].getItemGraphicEl(this.dataIndex)},getRawIndex:function(){return this[t][e].getRawIndex(this.dataIndex)}}}function UA(t,e,n,i,r){for(var o=new GA(i),a=0;a "+p)),u++)}var d,f=n.get("coordinateSystem");if("cartesian2d"===f||"polar"===f)d=hx(t,n);else{var g=vd.get(f),y=g&&g.dimensions||[];P(y,"value")<0&&y.concat(["value"]);var v=nx(t,{coordDimensions:y,encodeDefine:n.getEncode()}).dimensions;(d=new ex(v,n)).initData(t)}var m=new ex(["value"],n);return m.initData(l,s),r&&r(d,m),kC({mainData:d,struct:o,structAttr:"graph",datas:{node:d,edge:m},datasAttr:{node:"data",edge:"edgeData"}}),o.update(),o}R(WA,YA("hostGraph","data")),R(HA,YA("hostGraph","edgeData"));var XA=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments);var n=this;function i(){return n._categoriesData}this.legendVisualProvider=new mM(i,i),this.fillDataTextStyle(e.edges||e.links),this._updateCategoriesData()},e.prototype.mergeOption=function(e){t.prototype.mergeOption.apply(this,arguments),this.fillDataTextStyle(e.edges||e.links),this._updateCategoriesData()},e.prototype.mergeDefaultAndTheme=function(e){t.prototype.mergeDefaultAndTheme.apply(this,arguments),bo(e,"edgeLabel",["show"])},e.prototype.getInitialData=function(t,e){var n,i=t.edges||t.links||[],r=t.data||t.nodes||[],o=this;if(r&&i){KD(n=this)&&(n.__curvenessList=[],n.__edgeMap={},$D(n));var a=UA(r,i,this,!0,(function(t,e){t.wrapMethod("getItemModel",(function(t){var e=o._categoriesModels[t.getShallow("category")];return e&&(e.parentModel=t.parentModel,t.parentModel=e),t}));var n=Sc.prototype.getModel;function i(t,e){var i=n.call(this,t,e);return i.resolveParentPath=r,i}function r(t){if(t&&("label"===t[0]||"label"===t[1])){var e=t.slice();return"label"===t[0]?e[0]="edgeLabel":"label"===t[1]&&(e[1]="edgeLabel"),e}return t}e.wrapMethod("getItemModel",(function(t){return t.resolveParentPath=r,t.getModel=i,t}))}));return E(a.edges,(function(t){!function(t,e,n,i){if(KD(n)){var r=JD(t,e,n),o=n.__edgeMap,a=o[QD(r)];o[r]&&!a?o[r].isForward=!0:a&&o[r]&&(a.isForward=!0,o[r].isForward=!1),o[r]=o[r]||[],o[r].push(i)}}(t.node1,t.node2,this,t.dataIndex)}),this),a.data}},e.prototype.getGraph=function(){return this.getData().graph},e.prototype.getEdgeData=function(){return this.getGraph().edgeData},e.prototype.getCategoriesData=function(){return this._categoriesData},e.prototype.formatTooltip=function(t,e,n){if("edge"===n){var i=this.getData(),r=this.getDataParams(t,n),o=i.graph.getEdgeByIndex(t),a=i.getName(o.node1.dataIndex),s=i.getName(o.node2.dataIndex),l=[];return null!=a&&l.push(a),null!=s&&l.push(s),Qf("nameValue",{name:l.join(" > "),value:r.value,noValue:null==r.value})}return cg({series:this,dataIndex:t,multipleSeries:e})},e.prototype._updateCategoriesData=function(){var t=z(this.option.categories||[],(function(t){return null!=t.value?t:A({value:0},t)})),e=new ex(["value"],this);e.initData(t),this._categoriesData=e,this._categoriesModels=e.mapArray((function(t){return e.getItemModel(t)}))},e.prototype.setZoom=function(t){this.option.zoom=t},e.prototype.setCenter=function(t){this.option.center=t},e.prototype.isAnimationEnabled=function(){return t.prototype.isAnimationEnabled.call(this)&&!("force"===this.get("layout")&&this.get(["force","layoutAnimation"]))},e.type="series.graph",e.dependencies=["grid","polar","geo","singleAxis","calendar"],e.defaultOption={z:2,coordinateSystem:"view",legendHoverLink:!0,layout:null,circular:{rotateLabel:!1},force:{initLayout:null,repulsion:[0,50],gravity:.1,friction:.6,edgeLength:30,layoutAnimation:!0},left:"center",top:"center",symbol:"circle",symbolSize:10,edgeSymbol:["none","none"],edgeSymbolSize:10,edgeLabel:{position:"middle",distance:5},draggable:!1,roam:!1,center:null,zoom:1,nodeScaleRatio:.6,label:{show:!1,formatter:"{b}"},itemStyle:{},lineStyle:{color:"#aaa",width:1,opacity:.5},emphasis:{scale:!0,label:{show:!0}},select:{itemStyle:{borderColor:"#212121"}}},e}(fg),ZA={type:"graphRoam",event:"graphRoam",update:"none"};var jA=function(){this.angle=0,this.width=10,this.r=10,this.x=0,this.y=0},qA=function(t){function e(e){var n=t.call(this,e)||this;return n.type="pointer",n}return n(e,t),e.prototype.getDefaultShape=function(){return new jA},e.prototype.buildPath=function(t,e){var n=Math.cos,i=Math.sin,r=e.r,o=e.width,a=e.angle,s=e.x-n(a)*o*(o>=r/3?1:2),l=e.y-i(a)*o*(o>=r/3?1:2);a=e.angle-Math.PI/2,t.moveTo(s,l),t.lineTo(e.x+n(a)*o,e.y+i(a)*o),t.lineTo(e.x+n(e.angle)*r,e.y+i(e.angle)*r),t.lineTo(e.x-n(a)*o,e.y-i(a)*o),t.lineTo(s,l)},e}(Ms);function KA(t,e){var n=null==t?"":t+"";return e&&(X(e)?n=e.replace("{value}",n):U(e)&&(n=e(t))),n}var $A=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this.group.removeAll();var i=t.get(["axisLine","lineStyle","color"]),r=function(t,e){var n=t.get("center"),i=e.getWidth(),r=e.getHeight(),o=Math.min(i,r);return{cx:Ur(n[0],e.getWidth()),cy:Ur(n[1],e.getHeight()),r:Ur(t.get("radius"),o/2)}}(t,n);this._renderMain(t,e,n,i,r),this._data=t.getData()},e.prototype.dispose=function(){},e.prototype._renderMain=function(t,e,n,i,r){var o=this.group,a=t.get("clockwise"),s=-t.get("startAngle")/180*Math.PI,l=-t.get("endAngle")/180*Math.PI,u=t.getModel("axisLine"),h=u.get("roundCap")?ES:Eu,c=u.get("show"),p=u.getModel("lineStyle"),d=p.get("width"),f=[s,l];is(f,!a);for(var g=(l=f[1])-(s=f[0]),y=s,v=[],m=0;c&&m=t&&(0===e?0:i[e-1][0])Math.PI/2&&(V+=Math.PI):"tangential"===z?V=-M-Math.PI/2:j(z)&&(V=z*Math.PI/180),0===V?c.add(new Bs({style:ec(x,{text:O,x:N,y:E,verticalAlign:h<-.8?"top":h>.8?"bottom":"middle",align:u<-.4?"left":u>.4?"right":"center"},{inheritColor:R}),silent:!0})):c.add(new Bs({style:ec(x,{text:O,x:N,y:E,verticalAlign:"middle",align:"center"},{inheritColor:R}),silent:!0,originX:N,originY:E,rotation:V}))}if(m.get("show")&&k!==_){P=(P=m.get("distance"))?P+l:l;for(var B=0;B<=b;B++){u=Math.cos(M),h=Math.sin(M);var F=new Xu({shape:{x1:u*(f-P)+p,y1:h*(f-P)+d,x2:u*(f-S-P)+p,y2:h*(f-S-P)+d},silent:!0,style:D});"auto"===D.stroke&&F.setStyle({stroke:i((k+B/b)/_)}),c.add(F),M+=T}M-=T}else M+=I}},e.prototype._renderPointer=function(t,e,n,i,r,o,a,s,l){var u=this.group,h=this._data,c=this._progressEls,p=[],d=t.get(["pointer","show"]),f=t.getModel("progress"),g=f.get("show"),y=t.getData(),v=y.mapDimension("value"),m=+t.get("min"),x=+t.get("max"),_=[m,x],b=[o,a];function w(e,n){var i,o=y.getItemModel(e).getModel("pointer"),a=Ur(o.get("width"),r.r),s=Ur(o.get("length"),r.r),l=t.get(["pointer","icon"]),u=o.get("offsetCenter"),h=Ur(u[0],r.r),c=Ur(u[1],r.r),p=o.get("keepAspect");return(i=l?Vy(l,h-a/2,c-s,a,s,null,p):new qA({shape:{angle:-Math.PI/2,width:a,r:s,x:h,y:c}})).rotation=-(n+Math.PI/2),i.x=r.cx,i.y=r.cy,i}function S(t,e){var n=f.get("roundCap")?ES:Eu,i=f.get("overlap"),a=i?f.get("width"):l/y.count(),u=i?r.r-a:r.r-(t+1)*a,h=i?r.r:r.r-t*a,c=new n({shape:{startAngle:o,endAngle:e,cx:r.cx,cy:r.cy,clockwise:s,r0:u,r:h}});return i&&(c.z2=x-y.get(v,t)%x),c}(g||d)&&(y.diff(h).add((function(e){var n=y.get(v,e);if(d){var i=w(e,o);fh(i,{rotation:-((isNaN(+n)?b[0]:Yr(n,_,b,!0))+Math.PI/2)},t),u.add(i),y.setItemGraphicEl(e,i)}if(g){var r=S(e,o),a=f.get("clip");fh(r,{shape:{endAngle:Yr(n,_,b,a)}},t),u.add(r),Qs(t.seriesIndex,y.dataType,e,r),p[e]=r}})).update((function(e,n){var i=y.get(v,e);if(d){var r=h.getItemGraphicEl(n),a=r?r.rotation:o,s=w(e,a);s.rotation=a,dh(s,{rotation:-((isNaN(+i)?b[0]:Yr(i,_,b,!0))+Math.PI/2)},t),u.add(s),y.setItemGraphicEl(e,s)}if(g){var l=c[n],m=S(e,l?l.shape.endAngle:o),x=f.get("clip");dh(m,{shape:{endAngle:Yr(i,_,b,x)}},t),u.add(m),Qs(t.seriesIndex,y.dataType,e,m),p[e]=m}})).execute(),y.each((function(t){var e=y.getItemModel(t),n=e.getModel("emphasis"),r=n.get("focus"),o=n.get("blurScope"),a=n.get("disabled");if(d){var s=y.getItemGraphicEl(t),l=y.getItemVisual(t,"style"),u=l.fill;if(s instanceof As){var h=s.style;s.useStyle(A({image:h.image,x:h.x,y:h.y,width:h.width,height:h.height},l))}else s.useStyle(l),"pointer"!==s.type&&s.setColor(u);s.setStyle(e.getModel(["pointer","itemStyle"]).getItemStyle()),"auto"===s.style.fill&&s.setStyle("fill",i(Yr(y.get(v,t),_,[0,1],!0))),s.z2EmphasisLift=0,Zl(s,e),Hl(s,r,o,a)}if(g){var c=p[t];c.useStyle(y.getItemVisual(t,"style")),c.setStyle(e.getModel(["progress","itemStyle"]).getItemStyle()),c.z2EmphasisLift=0,Zl(c,e),Hl(c,r,o,a)}})),this._progressEls=p)},e.prototype._renderAnchor=function(t,e){var n=t.getModel("anchor");if(n.get("show")){var i=n.get("size"),r=n.get("icon"),o=n.get("offsetCenter"),a=n.get("keepAspect"),s=Vy(r,e.cx-i/2+Ur(o[0],e.r),e.cy-i/2+Ur(o[1],e.r),i,i,null,a);s.z2=n.get("showAbove")?1:0,s.setStyle(n.getModel("itemStyle").getItemStyle()),this.group.add(s)}},e.prototype._renderTitleAndDetail=function(t,e,n,i,r){var o=this,a=t.getData(),s=a.mapDimension("value"),l=+t.get("min"),u=+t.get("max"),h=new Er,c=[],p=[],d=t.isAnimationEnabled(),f=t.get(["pointer","showAbove"]);a.diff(this._data).add((function(t){c[t]=new Bs({silent:!0}),p[t]=new Bs({silent:!0})})).update((function(t,e){c[t]=o._titleEls[e],p[t]=o._detailEls[e]})).execute(),a.each((function(e){var n=a.getItemModel(e),o=a.get(s,e),g=new Er,y=i(Yr(o,[l,u],[0,1],!0)),v=n.getModel("title");if(v.get("show")){var m=v.get("offsetCenter"),x=r.cx+Ur(m[0],r.r),_=r.cy+Ur(m[1],r.r);(D=c[e]).attr({z2:f?0:2,style:ec(v,{x:x,y:_,text:a.getName(e),align:"center",verticalAlign:"middle"},{inheritColor:y})}),g.add(D)}var b=n.getModel("detail");if(b.get("show")){var w=b.get("offsetCenter"),S=r.cx+Ur(w[0],r.r),M=r.cy+Ur(w[1],r.r),I=Ur(b.get("width"),r.r),T=Ur(b.get("height"),r.r),C=t.get(["progress","show"])?a.getItemVisual(e,"style").fill:y,D=p[e],A=b.get("formatter");D.attr({z2:f?0:2,style:ec(b,{x:S,y:M,text:KA(o,A),width:isNaN(I)?null:I,height:isNaN(T)?null:T,align:"center",verticalAlign:"middle"},{inheritColor:C})}),uc(D,{normal:b},o,(function(t){return KA(t,A)})),d&&hc(D,e,a,t,{getFormattedLabel:function(t,e,n,i,r,a){return KA(a?a.interpolatedValue:o,A)}}),g.add(D)}h.add(g)})),this.group.add(h),this._titleEls=c,this._detailEls=p},e.type="gauge",e}(Tg),JA=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.visualStyleAccessPath="itemStyle",n}return n(e,t),e.prototype.getInitialData=function(t,e){return vM(this,["value"])},e.type="series.gauge",e.defaultOption={z:2,colorBy:"data",center:["50%","50%"],legendHoverLink:!0,radius:"75%",startAngle:225,endAngle:-45,clockwise:!0,min:0,max:100,splitNumber:10,axisLine:{show:!0,roundCap:!1,lineStyle:{color:[[1,"#E6EBF8"]],width:10}},progress:{show:!1,overlap:!0,width:10,roundCap:!1,clip:!0},splitLine:{show:!0,length:10,distance:10,lineStyle:{color:"#63677A",width:3,type:"solid"}},axisTick:{show:!0,splitNumber:5,length:6,distance:10,lineStyle:{color:"#63677A",width:1,type:"solid"}},axisLabel:{show:!0,distance:15,color:"#464646",fontSize:12,rotate:0},pointer:{icon:null,offsetCenter:[0,0],show:!0,showAbove:!0,length:"60%",width:6,keepAspect:!1},anchor:{show:!1,showAbove:!1,size:6,icon:"circle",offsetCenter:[0,0],keepAspect:!1,itemStyle:{color:"#fff",borderWidth:0,borderColor:"#5470c6"}},title:{show:!0,offsetCenter:[0,"20%"],color:"#464646",fontSize:16,valueAnimation:!1},detail:{show:!0,backgroundColor:"rgba(0,0,0,0)",borderWidth:0,borderColor:"#ccc",width:100,height:null,padding:[5,10],offsetCenter:[0,"40%"],color:"#464646",fontSize:30,fontWeight:"bold",lineHeight:30,valueAnimation:!1}},e}(fg);var QA=["itemStyle","opacity"],tk=function(t){function e(e,n){var i=t.call(this)||this,r=i,o=new Hu,a=new Bs;return r.setTextContent(a),i.setTextGuideLine(o),i.updateData(e,n,!0),i}return n(e,t),e.prototype.updateData=function(t,e,n){var i=this,r=t.hostModel,o=t.getItemModel(e),a=t.getItemLayout(e),s=o.getModel("emphasis"),l=o.get(QA);l=null==l?1:l,n||xh(i),i.useStyle(t.getItemVisual(e,"style")),i.style.lineJoin="round",n?(i.setShape({points:a.points}),i.style.opacity=0,fh(i,{style:{opacity:l}},r,e)):dh(i,{style:{opacity:l},shape:{points:a.points}},r,e),Zl(i,o),this._updateLabel(t,e),Hl(this,s.get("focus"),s.get("blurScope"),s.get("disabled"))},e.prototype._updateLabel=function(t,e){var n=this,i=this.getTextGuideLine(),r=n.getTextContent(),o=t.hostModel,a=t.getItemModel(e),s=t.getItemLayout(e).label,l=t.getItemVisual(e,"style"),u=l.fill;Qh(r,tc(a),{labelFetcher:t.hostModel,labelDataIndex:e,defaultOpacity:l.opacity,defaultText:t.getName(e)},{normal:{align:s.textAlign,verticalAlign:s.verticalAlign}}),n.setTextConfig({local:!0,inside:!!s.inside,insideStroke:u,outsideFill:u});var h=s.linePoints;i.setShape({points:h}),n.textGuideLineConfig={anchor:h?new Ce(h[0][0],h[0][1]):null},dh(r,{style:{x:s.x,y:s.y}},o,e),r.attr({rotation:s.rotation,originX:s.x,originY:s.y,z2:10}),xb(n,_b(a),{stroke:u})},e}(Gu),ek=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.ignoreLabelLineUpdate=!0,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this._data,o=this.group;i.diff(r).add((function(t){var e=new tk(i,t);i.setItemGraphicEl(t,e),o.add(e)})).update((function(t,e){var n=r.getItemGraphicEl(e);n.updateData(i,t),o.add(n),i.setItemGraphicEl(t,n)})).remove((function(e){mh(r.getItemGraphicEl(e),t,e)})).execute(),this._data=i},e.prototype.remove=function(){this.group.removeAll(),this._data=null},e.prototype.dispose=function(){},e.type="funnel",e}(Tg),nk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments),this.legendVisualProvider=new mM(W(this.getData,this),W(this.getRawData,this)),this._defaultLabelLine(e)},e.prototype.getInitialData=function(t,e){return vM(this,{coordDimensions:["value"],encodeDefaulter:H($p,this)})},e.prototype._defaultLabelLine=function(t){bo(t,"labelLine",["show"]);var e=t.labelLine,n=t.emphasis.labelLine;e.show=e.show&&t.label.show,n.show=n.show&&t.emphasis.label.show},e.prototype.getDataParams=function(e){var n=this.getData(),i=t.prototype.getDataParams.call(this,e),r=n.mapDimension("value"),o=n.getSum(r);return i.percent=o?+(n.get(r,e)/o*100).toFixed(2):0,i.$vars.push("percent"),i},e.type="series.funnel",e.defaultOption={z:2,legendHoverLink:!0,colorBy:"data",left:80,top:60,right:80,bottom:60,minSize:"0%",maxSize:"100%",sort:"descending",orient:"vertical",gap:0,funnelAlign:"center",label:{show:!0,position:"outer"},labelLine:{show:!0,length:20,lineStyle:{width:1}},itemStyle:{borderColor:"#fff",borderWidth:1},emphasis:{label:{show:!0}},select:{itemStyle:{borderColor:"#212121"}}},e}(fg);function ik(t,e){t.eachSeriesByType("funnel",(function(t){var n=t.getData(),i=n.mapDimension("value"),r=t.get("sort"),o=function(t,e){return Tp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,e),a=t.get("orient"),s=o.width,l=o.height,u=function(t,e){for(var n=t.mapDimension("value"),i=t.mapArray(n,(function(t){return t})),r=[],o="ascending"===e,a=0,s=t.count();a5)return;var i=this._model.coordinateSystem.getSlidedAxisExpandWindow([t.offsetX,t.offsetY]);"none"!==i.behavior&&this._dispatchExpand({axisExpandWindow:i.axisExpandWindow})}this._mouseDownPoint=null},mousemove:function(t){if(!this._mouseDownPoint&&yk(this,"mousemove")){var e=this._model,n=e.coordinateSystem.getSlidedAxisExpandWindow([t.offsetX,t.offsetY]),i=n.behavior;"jump"===i&&this._throttledDispatchExpand.debounceNextCall(e.get("axisExpandDebounce")),this._throttledDispatchExpand("none"===i?null:{axisExpandWindow:n.axisExpandWindow,animation:"jump"===i?null:{duration:0}})}}};function yk(t,e){var n=t._model;return n.get("axisExpandable")&&n.get("axisExpandTriggerOn")===e}var vk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(){t.prototype.init.apply(this,arguments),this.mergeOption({})},e.prototype.mergeOption=function(t){var e=this.option;t&&C(e,t,!0),this._initDimensions()},e.prototype.contains=function(t,e){var n=t.get("parallelIndex");return null!=n&&e.getComponent("parallel",n)===this},e.prototype.setAxisExpand=function(t){E(["axisExpandable","axisExpandCenter","axisExpandCount","axisExpandWidth","axisExpandWindow"],(function(e){t.hasOwnProperty(e)&&(this.option[e]=t[e])}),this)},e.prototype._initDimensions=function(){var t=this.dimensions=[],e=this.parallelAxisIndex=[];E(B(this.ecModel.queryComponents({mainType:"parallelAxis"}),(function(t){return(t.get("parallelIndex")||0)===this.componentIndex}),this),(function(n){t.push("dim"+n.get("dim")),e.push(n.componentIndex)}))},e.type="parallel",e.dependencies=["parallelAxis"],e.layoutMode="box",e.defaultOption={z:0,left:80,top:60,right:80,bottom:60,layout:"horizontal",axisExpandable:!1,axisExpandCenter:null,axisExpandCount:0,axisExpandWidth:50,axisExpandRate:17,axisExpandDebounce:50,axisExpandSlideTriggerArea:[-.15,.05,.4],axisExpandTriggerOn:"click",parallelAxisDefault:null},e}(Op),mk=function(t){function e(e,n,i,r,o){var a=t.call(this,e,n,i)||this;return a.type=r||"value",a.axisIndex=o,a}return n(e,t),e.prototype.isHorizontal=function(){return"horizontal"!==this.coordinateSystem.getModel().get("layout")},e}(q_);function xk(t,e,n,i,r,o){t=t||0;var a=n[1]-n[0];if(null!=r&&(r=bk(r,[0,a])),null!=o&&(o=Math.max(o,null!=r?r:0)),"all"===i){var s=Math.abs(e[1]-e[0]);s=bk(s,[0,a]),r=o=bk(s,[r,o]),i=0}e[0]=bk(e[0],n),e[1]=bk(e[1],n);var l=_k(e,i);e[i]+=t;var u,h=r||0,c=n.slice();return l.sign<0?c[0]+=h:c[1]-=h,e[i]=bk(e[i],c),u=_k(e,i),null!=r&&(u.sign!==l.sign||u.spano&&(e[1-i]=e[i]+u.sign*o),e}function _k(t,e){var n=t[e]-t[1-e];return{span:Math.abs(n),sign:n>0?-1:n<0?1:e?-1:1}}function bk(t,e){return Math.min(null!=e[1]?e[1]:1/0,Math.max(null!=e[0]?e[0]:-1/0,t))}var wk=E,Sk=Math.min,Mk=Math.max,Ik=Math.floor,Tk=Math.ceil,Ck=Xr,Dk=Math.PI,Ak=function(){function t(t,e,n){this.type="parallel",this._axesMap=yt(),this._axesLayout={},this.dimensions=t.dimensions,this._model=t,this._init(t,e,n)}return t.prototype._init=function(t,e,n){var i=t.dimensions,r=t.parallelAxisIndex;wk(i,(function(t,n){var i=r[n],o=e.getComponent("parallelAxis",i),a=this._axesMap.set(t,new mk(t,c_(o),[0,0],o.get("type"),i)),s="category"===a.type;a.onBand=s&&o.get("boundaryGap"),a.inverse=o.get("inverse"),o.axis=a,a.model=o,a.coordinateSystem=o.coordinateSystem=this}),this)},t.prototype.update=function(t,e){this._updateAxesFromSeries(this._model,t)},t.prototype.containPoint=function(t){var e=this._makeLayoutInfo(),n=e.axisBase,i=e.layoutBase,r=e.pixelDimIndex,o=t[1-r],a=t[r];return o>=n&&o<=n+e.axisLength&&a>=i&&a<=i+e.layoutLength},t.prototype.getModel=function(){return this._model},t.prototype._updateAxesFromSeries=function(t,e){e.eachSeries((function(n){if(t.contains(n,e)){var i=n.getData();wk(this.dimensions,(function(t){var e=this._axesMap.get(t);e.scale.unionExtentFromData(i,i.mapDimension(t)),h_(e.scale,e.model)}),this)}}),this)},t.prototype.resize=function(t,e){this._rect=Tp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()}),this._layoutAxes()},t.prototype.getRect=function(){return this._rect},t.prototype._makeLayoutInfo=function(){var t,e=this._model,n=this._rect,i=["x","y"],r=["width","height"],o=e.get("layout"),a="horizontal"===o?0:1,s=n[r[a]],l=[0,s],u=this.dimensions.length,h=kk(e.get("axisExpandWidth"),l),c=kk(e.get("axisExpandCount")||0,[0,u]),p=e.get("axisExpandable")&&u>3&&u>c&&c>1&&h>0&&s>0,d=e.get("axisExpandWindow");d?(t=kk(d[1]-d[0],l),d[1]=d[0]+t):(t=kk(h*(c-1),l),(d=[h*(e.get("axisExpandCenter")||Ik(u/2))-t/2])[1]=d[0]+t);var f=(s-t)/(u-c);f<3&&(f=0);var g=[Ik(Ck(d[0]/h,1))+1,Tk(Ck(d[1]/h,1))-1],y=f/h*d[0];return{layout:o,pixelDimIndex:a,layoutBase:n[i[a]],layoutLength:s,axisBase:n[i[1-a]],axisLength:n[r[1-a]],axisExpandable:p,axisExpandWidth:h,axisCollapseWidth:f,axisExpandWindow:d,axisCount:u,winInnerIndices:g,axisExpandWindow0Pos:y}},t.prototype._layoutAxes=function(){var t=this._rect,e=this._axesMap,n=this.dimensions,i=this._makeLayoutInfo(),r=i.layout;e.each((function(t){var e=[0,i.axisLength],n=t.inverse?1:0;t.setExtent(e[n],e[1-n])})),wk(n,(function(e,n){var o=(i.axisExpandable?Pk:Lk)(n,i),a={horizontal:{x:o.position,y:i.axisLength},vertical:{x:0,y:o.position}},s={horizontal:Dk/2,vertical:0},l=[a[r].x+t.x,a[r].y+t.y],u=s[r],h=[1,0,0,1,0,0];we(h,h,u),be(h,h,l),this._axesLayout[e]={position:l,rotation:u,transform:h,axisNameAvailableWidth:o.axisNameAvailableWidth,axisLabelShow:o.axisLabelShow,nameTruncateMaxWidth:o.nameTruncateMaxWidth,tickDirection:1,labelDirection:1}}),this)},t.prototype.getAxis=function(t){return this._axesMap.get(t)},t.prototype.dataToPoint=function(t,e){return this.axisCoordToPoint(this._axesMap.get(e).dataToCoord(t),e)},t.prototype.eachActiveState=function(t,e,n,i){null==n&&(n=0),null==i&&(i=t.count());var r=this._axesMap,o=this.dimensions,a=[],s=[];E(o,(function(e){a.push(t.mapDimension(e)),s.push(r.get(e).model)}));for(var l=this.hasAxisBrushed(),u=n;ur*(1-h[0])?(l="jump",a=s-r*(1-h[2])):(a=s-r*h[1])>=0&&(a=s-r*(1-h[1]))<=0&&(a=0),(a*=e.axisExpandWidth/u)?xk(a,i,o,"all"):l="none";else{var p=i[1]-i[0];(i=[Mk(0,o[1]*s/p-p/2)])[1]=Sk(o[1],i[0]+p),i[0]=i[1]-p}return{axisExpandWindow:i,behavior:l}},t}();function kk(t,e){return Sk(Mk(t,e[0]),e[1])}function Lk(t,e){var n=e.layoutLength/(e.axisCount-1);return{position:n*t,axisNameAvailableWidth:n,axisLabelShow:!0}}function Pk(t,e){var n,i,r=e.layoutLength,o=e.axisExpandWidth,a=e.axisCount,s=e.axisCollapseWidth,l=e.winInnerIndices,u=s,h=!1;return t=0;n--)Zr(e[n])},e.prototype.getActiveState=function(t){var e=this.activeIntervals;if(!e.length)return"normal";if(null==t||isNaN(+t))return"inactive";if(1===e.length){var n=e[0];if(n[0]<=t&&t<=n[1])return"active"}else for(var i=0,r=e.length;i6}(t)||o){if(a&&!o){"single"===s.brushMode&&Qk(t);var l=T(s);l.brushType=yL(l.brushType,a),l.panelId=a===Nk?null:a.panelId,o=t._creatingCover=Uk(t,l),t._covers.push(o)}if(o){var u=xL[yL(t._brushType,a)];o.__brushOption.range=u.getCreatingRange(pL(t,o,t._track)),i&&(Xk(t,o),u.updateCommon(t,o)),Zk(t,o),r={isEnd:i}}}else i&&"single"===s.brushMode&&s.removeOnClick&&$k(t,e,n)&&Qk(t)&&(r={isEnd:i,removeOnClick:!0});return r}function yL(t,e){return"auto"===t?e.defaultBrushType:t}var vL={mousedown:function(t){if(this._dragging)mL(this,t);else if(!t.target||!t.target.draggable){dL(t);var e=this.group.transformCoordToLocal(t.offsetX,t.offsetY);this._creatingCover=null,(this._creatingPanel=$k(this,t,e))&&(this._dragging=!0,this._track=[e.slice()])}},mousemove:function(t){var e=t.offsetX,n=t.offsetY,i=this.group.transformCoordToLocal(e,n);if(function(t,e,n){if(t._brushType&&!function(t,e,n){var i=t._zr;return e<0||e>i.getWidth()||n<0||n>i.getHeight()}(t,e.offsetX,e.offsetY)){var i=t._zr,r=t._covers,o=$k(t,e,n);if(!t._dragging)for(var a=0;a=0&&(o[r[a].depth]=new Sc(r[a],this,e));if(i&&n){var s=UA(i,n,this,!0,(function(t,e){t.wrapMethod("getItemModel",(function(t,e){var n=t.parentModel,i=n.getData().getItemLayout(e);if(i){var r=i.depth,o=n.levelModels[r];o&&(t.parentModel=o)}return t})),e.wrapMethod("getItemModel",(function(t,e){var n=t.parentModel,i=n.getGraph().getEdgeByIndex(e).node1.getLayout();if(i){var r=i.depth,o=n.levelModels[r];o&&(t.parentModel=o)}return t}))}));return s.data}},e.prototype.setNodePosition=function(t,e){var n=(this.option.data||this.option.nodes)[t];n.localX=e[0],n.localY=e[1]},e.prototype.getGraph=function(){return this.getData().graph},e.prototype.getEdgeData=function(){return this.getGraph().edgeData},e.prototype.formatTooltip=function(t,e,n){function i(t){return isNaN(t)||null==t}if("edge"===n){var r=this.getDataParams(t,n),o=r.data,a=r.value;return Qf("nameValue",{name:o.source+" -- "+o.target,value:a,noValue:i(a)})}var s=this.getGraph().getNodeByIndex(t).getLayout().value,l=this.getDataParams(t,n).data.name;return Qf("nameValue",{name:null!=l?l+"":null,value:s,noValue:i(s)})},e.prototype.optionUpdated=function(){},e.prototype.getDataParams=function(e,n){var i=t.prototype.getDataParams.call(this,e,n);if(null==i.value&&"node"===n){var r=this.getGraph().getNodeByIndex(e).getLayout().value;i.value=r}return i},e.type="series.sankey",e.defaultOption={z:2,coordinateSystem:"view",left:"5%",top:"5%",right:"20%",bottom:"5%",orient:"horizontal",nodeWidth:20,nodeGap:8,draggable:!0,layoutIterations:32,label:{show:!0,position:"right",fontSize:12},edgeLabel:{show:!1,fontSize:12},levels:[],nodeAlign:"justify",lineStyle:{color:"#314656",opacity:.2,curveness:.5},emphasis:{label:{show:!0},lineStyle:{opacity:.5}},select:{itemStyle:{borderColor:"#212121"}},animationEasing:"linear",animationDuration:1e3},e}(fg);function RL(t,e){t.eachSeriesByType("sankey",(function(t){var n=t.get("nodeWidth"),i=t.get("nodeGap"),r=function(t,e){return Tp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,e);t.layoutInfo=r;var o=r.width,a=r.height,s=t.getGraph(),l=s.nodes,u=s.edges;!function(t){E(t,(function(t){var e=YL(t.outEdges,HL),n=YL(t.inEdges,HL),i=t.getValue()||0,r=Math.max(e,n,i);t.setLayout({value:r},!0)}))}(l),function(t,e,n,i,r,o,a,s,l){(function(t,e,n,i,r,o,a){for(var s=[],l=[],u=[],h=[],c=0,p=0;p=0;v&&y.depth>d&&(d=y.depth),g.setLayout({depth:v?y.depth:c},!0),"vertical"===o?g.setLayout({dy:n},!0):g.setLayout({dx:n},!0);for(var m=0;mc-1?d:c-1;a&&"left"!==a&&function(t,e,n,i){if("right"===e){for(var r=[],o=t,a=0;o.length;){for(var s=0;s0;o--)zL(s,l*=.99,a),EL(s,r,n,i,a),UL(s,l,a),EL(s,r,n,i,a)}(t,e,o,r,i,a,s),function(t,e){var n="vertical"===e?"x":"y";E(t,(function(t){t.outEdges.sort((function(t,e){return t.node2.getLayout()[n]-e.node2.getLayout()[n]})),t.inEdges.sort((function(t,e){return t.node1.getLayout()[n]-e.node1.getLayout()[n]}))})),E(t,(function(t){var e=0,n=0;E(t.outEdges,(function(t){t.setLayout({sy:e},!0),e+=t.getLayout().dy})),E(t.inEdges,(function(t){t.setLayout({ty:n},!0),n+=t.getLayout().dy}))}))}(t,s)}(l,u,n,i,o,a,0!==B(l,(function(t){return 0===t.getLayout().value})).length?0:t.get("layoutIterations"),t.get("orient"),t.get("nodeAlign"))}))}function NL(t){var e=t.hostGraph.data.getRawDataItem(t.dataIndex);return null!=e.depth&&e.depth>=0}function EL(t,e,n,i,r){var o="vertical"===r?"x":"y";E(t,(function(t){var a,s,l;t.sort((function(t,e){return t.getLayout()[o]-e.getLayout()[o]}));for(var u=0,h=t.length,c="vertical"===r?"dx":"dy",p=0;p0&&(a=s.getLayout()[o]+l,"vertical"===r?s.setLayout({x:a},!0):s.setLayout({y:a},!0)),u=s.getLayout()[o]+s.getLayout()[c]+e;if((l=u-e-("vertical"===r?i:n))>0){a=s.getLayout()[o]-l,"vertical"===r?s.setLayout({x:a},!0):s.setLayout({y:a},!0),u=a;for(p=h-2;p>=0;--p)(l=(s=t[p]).getLayout()[o]+s.getLayout()[c]+e-u)>0&&(a=s.getLayout()[o]-l,"vertical"===r?s.setLayout({x:a},!0):s.setLayout({y:a},!0)),u=s.getLayout()[o]}}))}function zL(t,e,n){E(t.slice().reverse(),(function(t){E(t,(function(t){if(t.outEdges.length){var i=YL(t.outEdges,VL,n)/YL(t.outEdges,HL);if(isNaN(i)){var r=t.outEdges.length;i=r?YL(t.outEdges,BL,n)/r:0}if("vertical"===n){var o=t.getLayout().x+(i-WL(t,n))*e;t.setLayout({x:o},!0)}else{var a=t.getLayout().y+(i-WL(t,n))*e;t.setLayout({y:a},!0)}}}))}))}function VL(t,e){return WL(t.node2,e)*t.getValue()}function BL(t,e){return WL(t.node2,e)}function FL(t,e){return WL(t.node1,e)*t.getValue()}function GL(t,e){return WL(t.node1,e)}function WL(t,e){return"vertical"===e?t.getLayout().x+t.getLayout().dx/2:t.getLayout().y+t.getLayout().dy/2}function HL(t){return t.getValue()}function YL(t,e,n){for(var i=0,r=t.length,o=-1;++oo&&(o=e)})),E(n,(function(e){var n=new dD({type:"color",mappingMethod:"linear",dataExtent:[r,o],visual:t.get("color")}).mapValueToVisual(e.getLayout().value),i=e.getModel().get(["itemStyle","color"]);null!=i?(e.setVisual("color",i),e.setVisual("style",{fill:i})):(e.setVisual("color",n),e.setVisual("style",{fill:n}))}))}i.length&&E(i,(function(t){var e=t.getModel().get("lineStyle");t.setVisual("style",e)}))}))}var ZL=function(){function t(){}return t.prototype.getInitialData=function(t,e){var n,i,r=e.getComponent("xAxis",this.get("xAxisIndex")),o=e.getComponent("yAxis",this.get("yAxisIndex")),a=r.get("type"),s=o.get("type");"category"===a?(t.layout="horizontal",n=r.getOrdinalMeta(),i=!0):"category"===s?(t.layout="vertical",n=o.getOrdinalMeta(),i=!0):t.layout=t.layout||"horizontal";var l=["x","y"],u="horizontal"===t.layout?0:1,h=this._baseAxisDim=l[u],c=l[1-u],p=[r,o],d=p[u].get("type"),f=p[1-u].get("type"),g=t.data;if(g&&i){var y=[];E(g,(function(t,e){var n;Y(t)?(n=t.slice(),t.unshift(e)):Y(t.value)?((n=A({},t)).value=n.value.slice(),t.value.unshift(e)):n=t,y.push(n)})),t.data=y}var v=this.defaultValueDimensions,m=[{name:h,type:Rm(d),ordinalMeta:n,otherDims:{tooltip:!1,itemName:0},dimsDef:["base"]},{name:c,type:Rm(f),dimsDef:v.slice()}];return vM(this,{coordDimensions:m,dimensionsCount:v.length+1,encodeDefaulter:H(Kp,m,this)})},t.prototype.getBaseAxis=function(){var t=this._baseAxisDim;return this.ecModel.getComponent(t+"Axis",this.get(t+"AxisIndex")).axis},t}(),jL=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.defaultValueDimensions=[{name:"min",defaultTooltip:!0},{name:"Q1",defaultTooltip:!0},{name:"median",defaultTooltip:!0},{name:"Q3",defaultTooltip:!0},{name:"max",defaultTooltip:!0}],n.visualDrawType="stroke",n}return n(e,t),e.type="series.boxplot",e.dependencies=["xAxis","yAxis","grid"],e.defaultOption={z:2,coordinateSystem:"cartesian2d",legendHoverLink:!0,layout:null,boxWidth:[7,50],itemStyle:{color:"#fff",borderWidth:1},emphasis:{scale:!0,itemStyle:{borderWidth:2,shadowBlur:5,shadowOffsetX:1,shadowOffsetY:1,shadowColor:"rgba(0,0,0,0.2)"}},animationDuration:800},e}(fg);R(jL,ZL,!0);var qL=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this.group,o=this._data;this._data||r.removeAll();var a="horizontal"===t.get("layout")?1:0;i.diff(o).add((function(t){if(i.hasValue(t)){var e=JL(i.getItemLayout(t),i,t,a,!0);i.setItemGraphicEl(t,e),r.add(e)}})).update((function(t,e){var n=o.getItemGraphicEl(e);if(i.hasValue(t)){var s=i.getItemLayout(t);n?(xh(n),QL(s,n,i,t)):n=JL(s,i,t,a),r.add(n),i.setItemGraphicEl(t,n)}else r.remove(n)})).remove((function(t){var e=o.getItemGraphicEl(t);e&&r.remove(e)})).execute(),this._data=i},e.prototype.remove=function(t){var e=this.group,n=this._data;this._data=null,n&&n.eachItemGraphicEl((function(t){t&&e.remove(t)}))},e.type="boxplot",e}(Tg),KL=function(){},$L=function(t){function e(e){var n=t.call(this,e)||this;return n.type="boxplotBoxPath",n}return n(e,t),e.prototype.getDefaultShape=function(){return new KL},e.prototype.buildPath=function(t,e){var n=e.points,i=0;for(t.moveTo(n[i][0],n[i][1]),i++;i<4;i++)t.lineTo(n[i][0],n[i][1]);for(t.closePath();ig){var _=[v,x];i.push(_)}}}return{boxData:n,outliers:i}}(e.getRawData(),t.config);return[{dimensions:["ItemName","Low","Q1","Q2","Q3","High"],data:i.boxData},{data:i.outliers}]}};var rP=["color","borderColor"],oP=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this.group.removeClipPath(),this._progressiveEls=null,this._updateDrawMode(t),this._isLargeDraw?this._renderLarge(t):this._renderNormal(t)},e.prototype.incrementalPrepareRender=function(t,e,n){this._clear(),this._updateDrawMode(t)},e.prototype.incrementalRender=function(t,e,n,i){this._progressiveEls=[],this._isLargeDraw?this._incrementalRenderLarge(t,e):this._incrementalRenderNormal(t,e)},e.prototype.eachRendered=function(t){jh(this._progressiveEls||this.group,t)},e.prototype._updateDrawMode=function(t){var e=t.pipelineContext.large;null!=this._isLargeDraw&&e===this._isLargeDraw||(this._isLargeDraw=e,this._clear())},e.prototype._renderNormal=function(t){var e=t.getData(),n=this._data,i=this.group,r=e.getLayout("isSimpleBox"),o=t.get("clip",!0),a=t.coordinateSystem,s=a.getArea&&a.getArea();this._data||i.removeAll(),e.diff(n).add((function(n){if(e.hasValue(n)){var a=e.getItemLayout(n);if(o&&uP(s,a))return;var l=lP(a,n,!0);fh(l,{shape:{points:a.ends}},t,n),hP(l,e,n,r),i.add(l),e.setItemGraphicEl(n,l)}})).update((function(a,l){var u=n.getItemGraphicEl(l);if(e.hasValue(a)){var h=e.getItemLayout(a);o&&uP(s,h)?i.remove(u):(u?(dh(u,{shape:{points:h.ends}},t,a),xh(u)):u=lP(h),hP(u,e,a,r),i.add(u),e.setItemGraphicEl(a,u))}else i.remove(u)})).remove((function(t){var e=n.getItemGraphicEl(t);e&&i.remove(e)})).execute(),this._data=e},e.prototype._renderLarge=function(t){this._clear(),fP(t,this.group);var e=t.get("clip",!0)?yS(t.coordinateSystem,!1,t):null;e?this.group.setClipPath(e):this.group.removeClipPath()},e.prototype._incrementalRenderNormal=function(t,e){for(var n,i=e.getData(),r=i.getLayout("isSimpleBox");null!=(n=t.next());){var o=lP(i.getItemLayout(n));hP(o,i,n,r),o.incremental=!0,this.group.add(o),this._progressiveEls.push(o)}},e.prototype._incrementalRenderLarge=function(t,e){fP(e,this.group,this._progressiveEls,!0)},e.prototype.remove=function(t){this._clear()},e.prototype._clear=function(){this.group.removeAll(),this._data=null},e.type="candlestick",e}(Tg),aP=function(){},sP=function(t){function e(e){var n=t.call(this,e)||this;return n.type="normalCandlestickBox",n}return n(e,t),e.prototype.getDefaultShape=function(){return new aP},e.prototype.buildPath=function(t,e){var n=e.points;this.__simpleBox?(t.moveTo(n[4][0],n[4][1]),t.lineTo(n[6][0],n[6][1])):(t.moveTo(n[0][0],n[0][1]),t.lineTo(n[1][0],n[1][1]),t.lineTo(n[2][0],n[2][1]),t.lineTo(n[3][0],n[3][1]),t.closePath(),t.moveTo(n[4][0],n[4][1]),t.lineTo(n[5][0],n[5][1]),t.moveTo(n[6][0],n[6][1]),t.lineTo(n[7][0],n[7][1]))},e}(Ms);function lP(t,e,n){var i=t.ends;return new sP({shape:{points:n?cP(i,t):i},z2:100})}function uP(t,e){for(var n=!0,i=0;i0?"borderColor":"borderColor0"])||n.get(["itemStyle",t>0?"color":"color0"]);0===t&&(r=n.get(["itemStyle","borderColorDoji"]));var o=n.getModel("itemStyle").getItemStyle(rP);e.useStyle(o),e.style.fill=null,e.style.stroke=r}var yP=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.defaultValueDimensions=[{name:"open",defaultTooltip:!0},{name:"close",defaultTooltip:!0},{name:"lowest",defaultTooltip:!0},{name:"highest",defaultTooltip:!0}],n}return n(e,t),e.prototype.getShadowDim=function(){return"open"},e.prototype.brushSelector=function(t,e,n){var i=e.getItemLayout(t);return i&&n.rect(i.brushRect)},e.type="series.candlestick",e.dependencies=["xAxis","yAxis","grid"],e.defaultOption={z:2,coordinateSystem:"cartesian2d",legendHoverLink:!0,layout:null,clip:!0,itemStyle:{color:"#eb5454",color0:"#47b262",borderColor:"#eb5454",borderColor0:"#47b262",borderColorDoji:null,borderWidth:1},emphasis:{scale:!0,itemStyle:{borderWidth:2}},barMaxWidth:null,barMinWidth:null,barWidth:null,large:!0,largeThreshold:600,progressive:3e3,progressiveThreshold:1e4,progressiveChunkMode:"mod",animationEasing:"linear",animationDuration:300},e}(fg);function vP(t){t&&Y(t.series)&&E(t.series,(function(t){q(t)&&"k"===t.type&&(t.type="candlestick")}))}R(yP,ZL,!0);var mP=["itemStyle","borderColor"],xP=["itemStyle","borderColor0"],_P=["itemStyle","borderColorDoji"],bP=["itemStyle","color"],wP=["itemStyle","color0"],SP={seriesType:"candlestick",plan:Sg(),performRawSeries:!0,reset:function(t,e){function n(t,e){return e.get(t>0?bP:wP)}function i(t,e){return e.get(0===t?_P:t>0?mP:xP)}if(!e.isSeriesFiltered(t))return!t.pipelineContext.large&&{progress:function(t,e){for(var r;null!=(r=t.next());){var o=e.getItemModel(r),a=e.getItemLayout(r).sign,s=o.getItemStyle();s.fill=n(a,o),s.stroke=i(a,o)||s.fill,A(e.ensureUniqueItemVisual(r,"style"),s)}}}}},MP={seriesType:"candlestick",plan:Sg(),reset:function(t){var e=t.coordinateSystem,n=t.getData(),i=function(t,e){var n,i=t.getBaseAxis(),r="category"===i.type?i.getBandWidth():(n=i.getExtent(),Math.abs(n[1]-n[0])/e.count()),o=Ur(rt(t.get("barMaxWidth"),r),r),a=Ur(rt(t.get("barMinWidth"),1),r),s=t.get("barWidth");return null!=s?Ur(s,r):Math.max(Math.min(r/2,o),a)}(t,n),r=["x","y"],o=n.getDimensionIndex(n.mapDimension(r[0])),a=z(n.mapDimensionsAll(r[1]),n.getDimensionIndex,n),s=a[0],l=a[1],u=a[2],h=a[3];if(n.setLayout({candleWidth:i,isSimpleBox:i<=1.3}),!(o<0||a.length<4))return{progress:t.pipelineContext.large?function(n,i){var r,a,c=Ax(4*n.count),p=0,d=[],f=[],g=i.getStore(),y=!!t.get(["itemStyle","borderColorDoji"]);for(;null!=(a=n.next());){var v=g.get(o,a),m=g.get(s,a),x=g.get(l,a),_=g.get(u,a),b=g.get(h,a);isNaN(v)||isNaN(_)||isNaN(b)?(c[p++]=NaN,p+=3):(c[p++]=IP(g,a,m,x,l,y),d[0]=v,d[1]=_,r=e.dataToPoint(d,null,f),c[p++]=r?r[0]:NaN,c[p++]=r?r[1]:NaN,d[1]=b,r=e.dataToPoint(d,null,f),c[p++]=r?r[1]:NaN)}i.setLayout("largePoints",c)}:function(t,n){var r,a=n.getStore();for(;null!=(r=t.next());){var c=a.get(o,r),p=a.get(s,r),d=a.get(l,r),f=a.get(u,r),g=a.get(h,r),y=Math.min(p,d),v=Math.max(p,d),m=M(y,c),x=M(v,c),_=M(f,c),b=M(g,c),w=[];I(w,x,0),I(w,m,1),w.push(C(b),C(x),C(_),C(m));var S=!!n.getItemModel(r).get(["itemStyle","borderColorDoji"]);n.setItemLayout(r,{sign:IP(a,r,p,d,l,S),initBaseline:p>d?x[1]:m[1],ends:w,brushRect:T(f,g,c)})}function M(t,n){var i=[];return i[0]=n,i[1]=t,isNaN(n)||isNaN(t)?[NaN,NaN]:e.dataToPoint(i)}function I(t,e,n){var r=e.slice(),o=e.slice();r[0]=Rh(r[0]+i/2,1,!1),o[0]=Rh(o[0]-i/2,1,!0),n?t.push(r,o):t.push(o,r)}function T(t,e,n){var r=M(t,n),o=M(e,n);return r[0]-=i/2,o[0]-=i/2,{x:r[0],y:r[1],width:i,height:o[1]-r[1]}}function C(t){return t[0]=Rh(t[0],1),t}}}}};function IP(t,e,n,i,r,o){return n>i?-1:n0?t.get(r,e-1)<=i?1:-1:1}function TP(t,e){var n=e.rippleEffectColor||e.color;t.eachChild((function(t){t.attr({z:e.z,zlevel:e.zlevel,style:{stroke:"stroke"===e.brushType?n:null,fill:"fill"===e.brushType?n:null}})}))}var CP=function(t){function e(e,n){var i=t.call(this)||this,r=new Jw(e,n),o=new Er;return i.add(r),i.add(o),i.updateData(e,n),i}return n(e,t),e.prototype.stopEffectAnimation=function(){this.childAt(1).removeAll()},e.prototype.startEffectAnimation=function(t){for(var e=t.symbolType,n=t.color,i=t.rippleNumber,r=this.childAt(1),o=0;o0&&(o=this._getLineLength(i)/l*1e3),o!==this._period||a!==this._loop||s!==this._roundTrip){i.stopAnimation();var h=void 0;h=U(u)?u(n):u,i.__t>0&&(h=-o*i.__t),this._animateSymbol(i,o,h,a,s)}this._period=o,this._loop=a,this._roundTrip=s}},e.prototype._animateSymbol=function(t,e,n,i,r){if(e>0){t.__t=0;var o=this,a=t.animate("",i).when(r?2*e:e,{__t:r?2:1}).delay(n).during((function(){o._updateSymbolPosition(t)}));i||a.done((function(){o.remove(t)})),a.start()}},e.prototype._getLineLength=function(t){return Vt(t.__p1,t.__cp1)+Vt(t.__cp1,t.__p2)},e.prototype._updateAnimationPoints=function(t,e){t.__p1=e[0],t.__p2=e[1],t.__cp1=e[2]||[(e[0][0]+e[1][0])/2,(e[0][1]+e[1][1])/2]},e.prototype.updateData=function(t,e,n){this.childAt(0).updateData(t,e,n),this._updateEffectSymbol(t,e)},e.prototype._updateSymbolPosition=function(t){var e=t.__p1,n=t.__p2,i=t.__cp1,r=t.__t<1?t.__t:2-t.__t,o=[t.x,t.y],a=o.slice(),s=Mn,l=In;o[0]=s(e[0],i[0],n[0],r),o[1]=s(e[1],i[1],n[1],r);var u=t.__t<1?l(e[0],i[0],n[0],r):l(n[0],i[0],e[0],1-r),h=t.__t<1?l(e[1],i[1],n[1],r):l(n[1],i[1],e[1],1-r);t.rotation=-Math.atan2(h,u)-Math.PI/2,"line"!==this._symbolType&&"rect"!==this._symbolType&&"roundRect"!==this._symbolType||(void 0!==t.__lastT&&t.__lastT=0&&!(i[o]<=e);o--);o=Math.min(o,r-2)}else{for(o=a;oe);o++);o=Math.min(o-1,r-2)}var s=(e-i[o])/(i[o+1]-i[o]),l=n[o],u=n[o+1];t.x=l[0]*(1-s)+s*u[0],t.y=l[1]*(1-s)+s*u[1];var h=t.__t<1?u[0]-l[0]:l[0]-u[0],c=t.__t<1?u[1]-l[1]:l[1]-u[1];t.rotation=-Math.atan2(c,h)-Math.PI/2,this._lastFrame=o,this._lastFramePercent=e,t.ignore=!1}},e}(kP),OP=function(){this.polyline=!1,this.curveness=0,this.segs=[]},RP=function(t){function e(e){var n=t.call(this,e)||this;return n._off=0,n.hoverDataIdx=-1,n}return n(e,t),e.prototype.reset=function(){this.notClear=!1,this._off=0},e.prototype.getDefaultStyle=function(){return{stroke:"#000",fill:null}},e.prototype.getDefaultShape=function(){return new OP},e.prototype.buildPath=function(t,e){var n,i=e.segs,r=e.curveness;if(e.polyline)for(n=this._off;n0){t.moveTo(i[n++],i[n++]);for(var a=1;a0){var c=(s+u)/2-(l-h)*r,p=(l+h)/2-(u-s)*r;t.quadraticCurveTo(c,p,u,h)}else t.lineTo(u,h)}this.incremental&&(this._off=n,this.notClear=!0)},e.prototype.findDataIndex=function(t,e){var n=this.shape,i=n.segs,r=n.curveness,o=this.style.lineWidth;if(n.polyline)for(var a=0,s=0;s0)for(var u=i[s++],h=i[s++],c=1;c0){if(ss(u,h,(u+p)/2-(h-d)*r,(h+d)/2-(p-u)*r,p,d,o,t,e))return a}else if(os(u,h,p,d,o,t,e))return a;a++}return-1},e.prototype.contain=function(t,e){var n=this.transformCoordToLocal(t,e),i=this.getBoundingRect();return t=n[0],e=n[1],i.contain(t,e)?(this.hoverDataIdx=this.findDataIndex(t,e))>=0:(this.hoverDataIdx=-1,!1)},e.prototype.getBoundingRect=function(){var t=this._rect;if(!t){for(var e=this.shape.segs,n=1/0,i=1/0,r=-1/0,o=-1/0,a=0;a0&&(o.dataIndex=n+t.__startIndex)}))},t.prototype._clear=function(){this._newAdded=[],this.group.removeAll()},t}(),EP={seriesType:"lines",plan:Sg(),reset:function(t){var e=t.coordinateSystem;if(e){var n=t.get("polyline"),i=t.pipelineContext.large;return{progress:function(r,o){var a=[];if(i){var s=void 0,l=r.end-r.start;if(n){for(var u=0,h=r.start;h0&&(l||s.configLayer(o,{motionBlur:!0,lastFrameAlpha:Math.max(Math.min(a/10+.9,1),0)})),r.updateData(i);var u=t.get("clip",!0)&&yS(t.coordinateSystem,!1,t);u?this.group.setClipPath(u):this.group.removeClipPath(),this._lastZlevel=o,this._finished=!0},e.prototype.incrementalPrepareRender=function(t,e,n){var i=t.getData();this._updateLineDraw(i,t).incrementalPrepareUpdate(i),this._clearLayer(n),this._finished=!1},e.prototype.incrementalRender=function(t,e,n){this._lineDraw.incrementalUpdate(t,e.getData()),this._finished=t.end===e.getData().count()},e.prototype.eachRendered=function(t){this._lineDraw&&this._lineDraw.eachRendered(t)},e.prototype.updateTransform=function(t,e,n){var i=t.getData(),r=t.pipelineContext;if(!this._finished||r.large||r.progressiveRender)return{update:!0};var o=EP.reset(t,e,n);o.progress&&o.progress({start:0,end:i.count(),count:i.count()},i),this._lineDraw.updateLayout(),this._clearLayer(n)},e.prototype._updateLineDraw=function(t,e){var n=this._lineDraw,i=this._showEffect(e),r=!!e.get("polyline"),o=e.pipelineContext.large;return n&&i===this._hasEffet&&r===this._isPolyline&&o===this._isLargeDraw||(n&&n.remove(),n=this._lineDraw=o?new NP:new TA(r?i?PP:LP:i?kP:IA),this._hasEffet=i,this._isPolyline=r,this._isLargeDraw=o),this.group.add(n.group),n},e.prototype._showEffect=function(t){return!!t.get(["effect","show"])},e.prototype._clearLayer=function(t){var e=t.getZr();"svg"===e.painter.getType()||null==this._lastZlevel||e.painter.getLayer(this._lastZlevel).clear(!0)},e.prototype.remove=function(t,e){this._lineDraw&&this._lineDraw.remove(),this._lineDraw=null,this._clearLayer(e)},e.prototype.dispose=function(t,e){this.remove(t,e)},e.type="lines",e}(Tg),VP="undefined"==typeof Uint32Array?Array:Uint32Array,BP="undefined"==typeof Float64Array?Array:Float64Array;function FP(t){var e=t.data;e&&e[0]&&e[0][0]&&e[0][0].coord&&(t.data=z(e,(function(t){var e={coords:[t[0].coord,t[1].coord]};return t[0].name&&(e.fromName=t[0].name),t[1].name&&(e.toName=t[1].name),D([e,t[0],t[1]])})))}var GP=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.visualStyleAccessPath="lineStyle",n.visualDrawType="stroke",n}return n(e,t),e.prototype.init=function(e){e.data=e.data||[],FP(e);var n=this._processFlatCoordsArray(e.data);this._flatCoords=n.flatCoords,this._flatCoordsOffset=n.flatCoordsOffset,n.flatCoords&&(e.data=new Float32Array(n.count)),t.prototype.init.apply(this,arguments)},e.prototype.mergeOption=function(e){if(FP(e),e.data){var n=this._processFlatCoordsArray(e.data);this._flatCoords=n.flatCoords,this._flatCoordsOffset=n.flatCoordsOffset,n.flatCoords&&(e.data=new Float32Array(n.count))}t.prototype.mergeOption.apply(this,arguments)},e.prototype.appendData=function(t){var e=this._processFlatCoordsArray(t.data);e.flatCoords&&(this._flatCoords?(this._flatCoords=vt(this._flatCoords,e.flatCoords),this._flatCoordsOffset=vt(this._flatCoordsOffset,e.flatCoordsOffset)):(this._flatCoords=e.flatCoords,this._flatCoordsOffset=e.flatCoordsOffset),t.data=new Float32Array(e.count)),this.getRawData().appendData(t.data)},e.prototype._getCoordsFromItemModel=function(t){var e=this.getData().getItemModel(t),n=e.option instanceof Array?e.option:e.getShallow("coords");return n},e.prototype.getLineCoordsCount=function(t){return this._flatCoordsOffset?this._flatCoordsOffset[2*t+1]:this._getCoordsFromItemModel(t).length},e.prototype.getLineCoords=function(t,e){if(this._flatCoordsOffset){for(var n=this._flatCoordsOffset[2*t],i=this._flatCoordsOffset[2*t+1],r=0;r ")})},e.prototype.preventIncremental=function(){return!!this.get(["effect","show"])},e.prototype.getProgressive=function(){var t=this.option.progressive;return null==t?this.option.large?1e4:this.get("progressive"):t},e.prototype.getProgressiveThreshold=function(){var t=this.option.progressiveThreshold;return null==t?this.option.large?2e4:this.get("progressiveThreshold"):t},e.prototype.getZLevelKey=function(){var t=this.getModel("effect"),e=t.get("trailLength");return this.getData().count()>this.getProgressiveThreshold()?this.id:t.get("show")&&e>0?e+"":""},e.type="series.lines",e.dependencies=["grid","polar","geo","calendar"],e.defaultOption={coordinateSystem:"geo",z:2,legendHoverLink:!0,xAxisIndex:0,yAxisIndex:0,symbol:["none","none"],symbolSize:[10,10],geoIndex:0,effect:{show:!1,period:4,constantSpeed:0,symbol:"circle",symbolSize:3,loop:!0,trailLength:.2},large:!1,largeThreshold:2e3,polyline:!1,clip:!0,label:{show:!1,position:"end"},lineStyle:{opacity:.5}},e}(fg);function WP(t){return t instanceof Array||(t=[t,t]),t}var HP={seriesType:"lines",reset:function(t){var e=WP(t.get("symbol")),n=WP(t.get("symbolSize")),i=t.getData();return i.setVisual("fromSymbol",e&&e[0]),i.setVisual("toSymbol",e&&e[1]),i.setVisual("fromSymbolSize",n&&n[0]),i.setVisual("toSymbolSize",n&&n[1]),{dataEach:i.hasItemOption?function(t,e){var n=t.getItemModel(e),i=WP(n.getShallow("symbol",!0)),r=WP(n.getShallow("symbolSize",!0));i[0]&&t.setItemVisual(e,"fromSymbol",i[0]),i[1]&&t.setItemVisual(e,"toSymbol",i[1]),r[0]&&t.setItemVisual(e,"fromSymbolSize",r[0]),r[1]&&t.setItemVisual(e,"toSymbolSize",r[1])}:null}}};var YP=function(){function t(){this.blurSize=30,this.pointSize=20,this.maxOpacity=1,this.minOpacity=0,this._gradientPixels={inRange:null,outOfRange:null};var t=h.createCanvas();this.canvas=t}return t.prototype.update=function(t,e,n,i,r,o){var a=this._getBrush(),s=this._getGradient(r,"inRange"),l=this._getGradient(r,"outOfRange"),u=this.pointSize+this.blurSize,h=this.canvas,c=h.getContext("2d"),p=t.length;h.width=e,h.height=n;for(var d=0;d0){var I=o(v)?s:l;v>0&&(v=v*S+w),x[_++]=I[M],x[_++]=I[M+1],x[_++]=I[M+2],x[_++]=I[M+3]*v*256}else _+=4}return c.putImageData(m,0,0),h},t.prototype._getBrush=function(){var t=this._brushCanvas||(this._brushCanvas=h.createCanvas()),e=this.pointSize+this.blurSize,n=2*e;t.width=n,t.height=n;var i=t.getContext("2d");return i.clearRect(0,0,n,n),i.shadowOffsetX=n,i.shadowBlur=this.blurSize,i.shadowColor="#000",i.beginPath(),i.arc(-e,e,this.pointSize,0,2*Math.PI,!0),i.closePath(),i.fill(),t},t.prototype._getGradient=function(t,e){for(var n=this._gradientPixels,i=n[e]||(n[e]=new Uint8ClampedArray(1024)),r=[0,0,0,0],o=0,a=0;a<256;a++)t[e](a/255,!0,r),i[o++]=r[0],i[o++]=r[1],i[o++]=r[2],i[o++]=r[3];return i},t}();function UP(t){var e=t.dimensions;return"lng"===e[0]&&"lat"===e[1]}var XP=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i;e.eachComponent("visualMap",(function(e){e.eachTargetSeries((function(n){n===t&&(i=e)}))})),this._progressiveEls=null,this.group.removeAll();var r=t.coordinateSystem;"cartesian2d"===r.type||"calendar"===r.type?this._renderOnCartesianAndCalendar(t,n,0,t.getData().count()):UP(r)&&this._renderOnGeo(r,t,i,n)},e.prototype.incrementalPrepareRender=function(t,e,n){this.group.removeAll()},e.prototype.incrementalRender=function(t,e,n,i){var r=e.coordinateSystem;r&&(UP(r)?this.render(e,n,i):(this._progressiveEls=[],this._renderOnCartesianAndCalendar(e,i,t.start,t.end,!0)))},e.prototype.eachRendered=function(t){jh(this._progressiveEls||this.group,t)},e.prototype._renderOnCartesianAndCalendar=function(t,e,n,i,r){var o,a,s,l,u=t.coordinateSystem,h=vS(u,"cartesian2d");if(h){var c=u.getAxis("x"),p=u.getAxis("y");0,o=c.getBandWidth()+.5,a=p.getBandWidth()+.5,s=c.scale.getExtent(),l=p.scale.getExtent()}for(var d=this.group,f=t.getData(),g=t.getModel(["emphasis","itemStyle"]).getItemStyle(),y=t.getModel(["blur","itemStyle"]).getItemStyle(),v=t.getModel(["select","itemStyle"]).getItemStyle(),m=t.get(["itemStyle","borderRadius"]),x=tc(t),_=t.getModel("emphasis"),b=_.get("focus"),w=_.get("blurScope"),S=_.get("disabled"),M=h?[f.mapDimension("x"),f.mapDimension("y"),f.mapDimension("value")]:[f.mapDimension("time"),f.mapDimension("value")],I=n;Is[1]||Al[1])continue;var k=u.dataToPoint([D,A]);T=new Es({shape:{x:k[0]-o/2,y:k[1]-a/2,width:o,height:a},style:C})}else{if(isNaN(f.get(M[1],I)))continue;T=new Es({z2:1,shape:u.dataToRect([f.get(M[0],I)]).contentShape,style:C})}if(f.hasItemOption){var L=f.getItemModel(I),P=L.getModel("emphasis");g=P.getModel("itemStyle").getItemStyle(),y=L.getModel(["blur","itemStyle"]).getItemStyle(),v=L.getModel(["select","itemStyle"]).getItemStyle(),m=L.get(["itemStyle","borderRadius"]),b=P.get("focus"),w=P.get("blurScope"),S=P.get("disabled"),x=tc(L)}T.shape.r=m;var O=t.getRawValue(I),R="-";O&&null!=O[2]&&(R=O[2]+""),Qh(T,x,{labelFetcher:t,labelDataIndex:I,defaultOpacity:C.opacity,defaultText:R}),T.ensureState("emphasis").style=g,T.ensureState("blur").style=y,T.ensureState("select").style=v,Hl(T,b,w,S),T.incremental=r,r&&(T.states.emphasis.hoverLayer=!0),d.add(T),f.setItemGraphicEl(I,T),this._progressiveEls&&this._progressiveEls.push(T)}},e.prototype._renderOnGeo=function(t,e,n,i){var r=n.targetVisuals.inRange,o=n.targetVisuals.outOfRange,a=e.getData(),s=this._hmLayer||this._hmLayer||new YP;s.blurSize=e.get("blurSize"),s.pointSize=e.get("pointSize"),s.minOpacity=e.get("minOpacity"),s.maxOpacity=e.get("maxOpacity");var l=t.getViewRect().clone(),u=t.getRoamTransform();l.applyTransform(u);var h=Math.max(l.x,0),c=Math.max(l.y,0),p=Math.min(l.width+l.x,i.getWidth()),d=Math.min(l.height+l.y,i.getHeight()),f=p-h,g=d-c,y=[a.mapDimension("lng"),a.mapDimension("lat"),a.mapDimension("value")],v=a.mapArray(y,(function(e,n,i){var r=t.dataToPoint([e,n]);return r[0]-=h,r[1]-=c,r.push(i),r})),m=n.getExtent(),x="visualMap.continuous"===n.type?function(t,e){var n=t[1]-t[0];return e=[(e[0]-t[0])/n,(e[1]-t[0])/n],function(t){return t>=e[0]&&t<=e[1]}}(m,n.option.range):function(t,e,n){var i=t[1]-t[0],r=(e=z(e,(function(e){return{interval:[(e.interval[0]-t[0])/i,(e.interval[1]-t[0])/i]}}))).length,o=0;return function(t){var i;for(i=o;i=0;i--){var a;if((a=e[i].interval)[0]<=t&&t<=a[1]){o=i;break}}return i>=0&&i0?1:-1}(n,o,r,i,c),function(t,e,n,i,r,o,a,s,l,u){var h,c=l.valueDim,p=l.categoryDim,d=Math.abs(n[p.wh]),f=t.getItemVisual(e,"symbolSize");h=Y(f)?f.slice():null==f?["100%","100%"]:[f,f];h[p.index]=Ur(h[p.index],d),h[c.index]=Ur(h[c.index],i?d:Math.abs(o)),u.symbolSize=h,(u.symbolScale=[h[0]/s,h[1]/s])[c.index]*=(l.isHorizontal?-1:1)*a}(t,e,r,o,0,c.boundingLength,c.pxSign,u,i,c),function(t,e,n,i,r){var o=t.get(jP)||0;o&&(KP.attr({scaleX:e[0],scaleY:e[1],rotation:n}),KP.updateTransform(),o/=KP.getLineScale(),o*=e[i.valueDim.index]);r.valueLineWidth=o||0}(n,c.symbolScale,l,i,c);var p=c.symbolSize,d=Fy(n.get("symbolOffset"),p);return function(t,e,n,i,r,o,a,s,l,u,h,c){var p=h.categoryDim,d=h.valueDim,f=c.pxSign,g=Math.max(e[d.index]+s,0),y=g;if(i){var v=Math.abs(l),m=it(t.get("symbolMargin"),"15%")+"",x=!1;m.lastIndexOf("!")===m.length-1&&(x=!0,m=m.slice(0,m.length-1));var _=Ur(m,e[d.index]),b=Math.max(g+2*_,0),w=x?0:2*_,S=ho(i),M=S?i:fO((v+w)/b);b=g+2*(_=(v-M*g)/2/(x?M:Math.max(M-1,1))),w=x?0:2*_,S||"fixed"===i||(M=u?fO((Math.abs(u)+w)/b):0),y=M*b-w,c.repeatTimes=M,c.symbolMargin=_}var I=f*(y/2),T=c.pathPosition=[];T[p.index]=n[p.wh]/2,T[d.index]="start"===a?I:"end"===a?l-I:l/2,o&&(T[0]+=o[0],T[1]+=o[1]);var C=c.bundlePosition=[];C[p.index]=n[p.xy],C[d.index]=n[d.xy];var D=c.barRectShape=A({},n);D[d.wh]=f*Math.max(Math.abs(n[d.wh]),Math.abs(T[d.index]+I)),D[p.wh]=n[p.wh];var k=c.clipShape={};k[p.xy]=-n[p.xy],k[p.wh]=h.ecSize[p.wh],k[d.xy]=0,k[d.wh]=n[d.wh]}(n,p,r,o,0,d,s,c.valueLineWidth,c.boundingLength,c.repeatCutLength,i,c),c}function QP(t,e){return t.toGlobalCoord(t.dataToCoord(t.scale.parse(e)))}function tO(t){var e=t.symbolPatternSize,n=Vy(t.symbolType,-e/2,-e/2,e,e);return n.attr({culling:!0}),"image"!==n.type&&n.setStyle({strokeNoScale:!0}),n}function eO(t,e,n,i){var r=t.__pictorialBundle,o=n.symbolSize,a=n.valueLineWidth,s=n.pathPosition,l=e.valueDim,u=n.repeatTimes||0,h=0,c=o[e.valueDim.index]+a+2*n.symbolMargin;for(cO(t,(function(t){t.__pictorialAnimationIndex=h,t.__pictorialRepeatTimes=u,h0:i<0)&&(r=u-1-t),e[l.index]=c*(r-u/2+.5)+s[l.index],{x:e[0],y:e[1],scaleX:n.symbolScale[0],scaleY:n.symbolScale[1],rotation:n.rotation}}}function nO(t,e,n,i){var r=t.__pictorialBundle,o=t.__pictorialMainPath;o?pO(o,null,{x:n.pathPosition[0],y:n.pathPosition[1],scaleX:n.symbolScale[0],scaleY:n.symbolScale[1],rotation:n.rotation},n,i):(o=t.__pictorialMainPath=tO(n),r.add(o),pO(o,{x:n.pathPosition[0],y:n.pathPosition[1],scaleX:0,scaleY:0,rotation:n.rotation},{scaleX:n.symbolScale[0],scaleY:n.symbolScale[1]},n,i))}function iO(t,e,n){var i=A({},e.barRectShape),r=t.__pictorialBarRect;r?pO(r,null,{shape:i},e,n):((r=t.__pictorialBarRect=new Es({z2:2,shape:i,silent:!0,style:{stroke:"transparent",fill:"transparent",lineWidth:0}})).disableMorphing=!0,t.add(r))}function rO(t,e,n,i){if(n.symbolClip){var r=t.__pictorialClipPath,o=A({},n.clipShape),a=e.valueDim,s=n.animationModel,l=n.dataIndex;if(r)dh(r,{shape:o},s,l);else{o[a.wh]=0,r=new Es({shape:o}),t.__pictorialBundle.setClipPath(r),t.__pictorialClipPath=r;var u={};u[a.wh]=n.clipShape[a.wh],qh[i?"updateProps":"initProps"](r,{shape:u},s,l)}}}function oO(t,e){var n=t.getItemModel(e);return n.getAnimationDelayParams=aO,n.isAnimationEnabled=sO,n}function aO(t){return{index:t.__pictorialAnimationIndex,count:t.__pictorialRepeatTimes}}function sO(){return this.parentModel.isAnimationEnabled()&&!!this.getShallow("animation")}function lO(t,e,n,i){var r=new Er,o=new Er;return r.add(o),r.__pictorialBundle=o,o.x=n.bundlePosition[0],o.y=n.bundlePosition[1],n.symbolRepeat?eO(r,e,n):nO(r,0,n),iO(r,n,i),rO(r,e,n,i),r.__pictorialShapeStr=hO(t,n),r.__pictorialSymbolMeta=n,r}function uO(t,e,n,i){var r=i.__pictorialBarRect;r&&r.removeTextContent();var o=[];cO(i,(function(t){o.push(t)})),i.__pictorialMainPath&&o.push(i.__pictorialMainPath),i.__pictorialClipPath&&(n=null),E(o,(function(t){yh(t,{scaleX:0,scaleY:0},n,e,(function(){i.parent&&i.parent.remove(i)}))})),t.setItemGraphicEl(e,null)}function hO(t,e){return[t.getItemVisual(e.dataIndex,"symbol")||"none",!!e.symbolRepeat,!!e.symbolClip].join(":")}function cO(t,e,n){E(t.__pictorialBundle.children(),(function(i){i!==t.__pictorialBarRect&&e.call(n,i)}))}function pO(t,e,n,i,r,o){e&&t.attr(e),i.symbolClip&&!r?n&&t.attr(n):n&&qh[r?"updateProps":"initProps"](t,n,i.animationModel,i.dataIndex,o)}function dO(t,e,n){var i=n.dataIndex,r=n.itemModel,o=r.getModel("emphasis"),a=o.getModel("itemStyle").getItemStyle(),s=r.getModel(["blur","itemStyle"]).getItemStyle(),l=r.getModel(["select","itemStyle"]).getItemStyle(),u=r.getShallow("cursor"),h=o.get("focus"),c=o.get("blurScope"),p=o.get("scale");cO(t,(function(t){if(t instanceof As){var e=t.style;t.useStyle(A({image:e.image,x:e.x,y:e.y,width:e.width,height:e.height},n.style))}else t.useStyle(n.style);var i=t.ensureState("emphasis");i.style=a,p&&(i.scaleX=1.1*t.scaleX,i.scaleY=1.1*t.scaleY),t.ensureState("blur").style=s,t.ensureState("select").style=l,u&&(t.cursor=u),t.z2=n.z2}));var d=e.valueDim.posDesc[+(n.boundingLength>0)];Qh(t.__pictorialBarRect,tc(r),{labelFetcher:e.seriesModel,labelDataIndex:i,defaultText:Kw(e.seriesModel.getData(),i),inheritColor:n.style.fill,defaultOpacity:n.style.opacity,defaultOutsidePosition:d}),Hl(t,h,c,o.get("disabled"))}function fO(t){var e=Math.round(t);return Math.abs(t-e)<1e-4?e:Math.ceil(t)}var gO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n.defaultSymbol="roundRect",n}return n(e,t),e.prototype.getInitialData=function(e){return e.stack=null,t.prototype.getInitialData.apply(this,arguments)},e.type="series.pictorialBar",e.dependencies=["grid"],e.defaultOption=Tc(OS.defaultOption,{symbol:"circle",symbolSize:null,symbolRotate:null,symbolPosition:null,symbolOffset:null,symbolMargin:null,symbolRepeat:!1,symbolRepeatDirection:"end",symbolClip:!1,symbolBoundingData:null,symbolPatternSize:400,barGap:"-100%",progressive:0,emphasis:{scale:!1},select:{itemStyle:{borderColor:"#212121"}}}),e}(OS);var yO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._layers=[],n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this,o=this.group,a=t.getLayerSeries(),s=i.getLayout("layoutInfo"),l=s.rect,u=s.boundaryGap;function h(t){return t.name}o.x=0,o.y=l.y+u[0];var c=new Lm(this._layersSeries||[],a,h,h),p=[];function d(e,n,s){var l=r._layers;if("remove"!==e){for(var u,h,c=[],d=[],f=a[n].indices,g=0;go&&(o=s),i.push(s)}for(var u=0;uo&&(o=c)}return{y0:r,max:o}}(l),h=u.y0,c=n/u.max,p=o.length,d=o[0].indices.length,f=0;fMath.PI/2?"right":"left"):S&&"center"!==S?"left"===S?(m=r.r0+w,a>Math.PI/2&&(S="right")):"right"===S&&(m=r.r-w,a>Math.PI/2&&(S="left")):(m=o===2*Math.PI&&0===r.r0?0:(r.r+r.r0)/2,S="center"),g.style.align=S,g.style.verticalAlign=f(p,"verticalAlign")||"middle",g.x=m*s+r.cx,g.y=m*l+r.cy;var M=f(p,"rotate"),I=0;"radial"===M?(I=-a)<-Math.PI/2&&(I+=Math.PI):"tangential"===M?(I=Math.PI/2-a)>Math.PI/2?I-=Math.PI:I<-Math.PI/2&&(I+=Math.PI):j(M)&&(I=M*Math.PI/180),g.rotation=I})),h.dirtyStyle()},e}(Eu),bO="sunburstRootToNode",wO="sunburstHighlight";var SO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n,i){var r=this;this.seriesModel=t,this.api=n,this.ecModel=e;var o=t.getData(),a=o.tree.root,s=t.getViewRoot(),l=this.group,u=t.get("renderLabelForZeroData"),h=[];s.eachNode((function(t){h.push(t)}));var c=this._oldChildren||[];!function(i,r){if(0===i.length&&0===r.length)return;function s(t){return t.getId()}function h(s,h){!function(i,r){u||!i||i.getValue()||(i=null);if(i!==a&&r!==a)if(r&&r.piece)i?(r.piece.updateData(!1,i,t,e,n),o.setItemGraphicEl(i.dataIndex,r.piece)):function(t){if(!t)return;t.piece&&(l.remove(t.piece),t.piece=null)}(r);else if(i){var s=new _O(i,t,e,n);l.add(s),o.setItemGraphicEl(i.dataIndex,s)}}(null==s?null:i[s],null==h?null:r[h])}new Lm(r,i,s,s).add(h).update(h).remove(H(h,null)).execute()}(h,c),function(i,o){o.depth>0?(r.virtualPiece?r.virtualPiece.updateData(!1,i,t,e,n):(r.virtualPiece=new _O(i,t,e,n),l.add(r.virtualPiece)),o.piece.off("click"),r.virtualPiece.on("click",(function(t){r._rootToNode(o.parentNode)}))):r.virtualPiece&&(l.remove(r.virtualPiece),r.virtualPiece=null)}(a,s),this._initEvents(),this._oldChildren=h},e.prototype._initEvents=function(){var t=this;this.group.off("click"),this.group.on("click",(function(e){var n=!1;t.seriesModel.getViewRoot().eachNode((function(i){if(!n&&i.piece&&i.piece===e.target){var r=i.getModel().get("nodeClick");if("rootToNode"===r)t._rootToNode(i);else if("link"===r){var o=i.getModel(),a=o.get("link");if(a)_p(a,o.get("target",!0)||"_blank")}n=!0}}))}))},e.prototype._rootToNode=function(t){t!==this.seriesModel.getViewRoot()&&this.api.dispatchAction({type:bO,from:this.uid,seriesId:this.seriesModel.id,targetNode:t})},e.prototype.containPoint=function(t,e){var n=e.getData().getItemLayout(0);if(n){var i=t[0]-n.cx,r=t[1]-n.cy,o=Math.sqrt(i*i+r*r);return o<=n.r&&o>=n.r0}},e.type="sunburst",e}(Tg),MO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.ignoreStyleOnData=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){var n={name:t.name,children:t.data};IO(n);var i=this._levelModels=z(t.levels||[],(function(t){return new Sc(t,this,e)}),this),r=BC.createTree(n,this,(function(t){t.wrapMethod("getItemModel",(function(t,e){var n=r.getNodeByDataIndex(e),o=i[n.depth];return o&&(t.parentModel=o),t}))}));return r.data},e.prototype.optionUpdated=function(){this.resetViewRoot()},e.prototype.getDataParams=function(e){var n=t.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(e);return n.treePathInfo=HC(i,this),n},e.prototype.getLevelModel=function(t){return this._levelModels&&this._levelModels[t.depth]},e.prototype.getViewRoot=function(){return this._viewRoot},e.prototype.resetViewRoot=function(t){t?this._viewRoot=t:t=this._viewRoot;var e=this.getRawData().tree.root;t&&(t===e||e.contains(t))||(this._viewRoot=e)},e.prototype.enableAriaDecal=function(){qC(this)},e.type="series.sunburst",e.defaultOption={z:2,center:["50%","50%"],radius:[0,"75%"],clockwise:!0,startAngle:90,minAngle:0,stillShowZeroSum:!0,nodeClick:"rootToNode",renderLabelForZeroData:!1,label:{rotate:"radial",show:!0,opacity:1,align:"center",position:"inside",distance:5,silent:!0},itemStyle:{borderWidth:1,borderColor:"white",borderType:"solid",shadowBlur:0,shadowColor:"rgba(0, 0, 0, 0.2)",shadowOffsetX:0,shadowOffsetY:0,opacity:1},emphasis:{focus:"descendant"},blur:{itemStyle:{opacity:.2},label:{opacity:.1}},animationType:"expansion",animationDuration:1e3,animationDurationUpdate:500,data:[],sort:"desc"},e}(fg);function IO(t){var e=0;E(t.children,(function(t){IO(t);var n=t.value;Y(n)&&(n=n[0]),e+=n}));var n=t.value;Y(n)&&(n=n[0]),(null==n||isNaN(n))&&(n=e),n<0&&(n=0),Y(t.value)?t.value[0]=n:t.value=n}var TO=Math.PI/180;function CO(t,e,n){e.eachSeriesByType(t,(function(t){var e=t.get("center"),i=t.get("radius");Y(i)||(i=[0,i]),Y(e)||(e=[e,e]);var r=n.getWidth(),o=n.getHeight(),a=Math.min(r,o),s=Ur(e[0],r),l=Ur(e[1],o),u=Ur(i[0],a/2),h=Ur(i[1],a/2),c=-t.get("startAngle")*TO,p=t.get("minAngle")*TO,d=t.getData().tree.root,f=t.getViewRoot(),g=f.depth,y=t.get("sort");null!=y&&DO(f,y);var v=0;E(f.children,(function(t){!isNaN(t.getValue())&&v++}));var m=f.getValue(),x=Math.PI/(m||v)*2,_=f.depth>0,b=f.height-(_?-1:1),w=(h-u)/(b||1),S=t.get("clockwise"),M=t.get("stillShowZeroSum"),I=S?1:-1,T=function(e,n){if(e){var i=n;if(e!==d){var r=e.getValue(),o=0===m&&M?x:r*x;o1;)r=r.parentNode;var o=n.getColorFromPalette(r.name||r.dataIndex+"",e);return t.depth>1&&X(o)&&(o=Kn(o,(t.depth-1)/(i-1)*.5)),o}(r,t,i.root.height)),A(n.ensureUniqueItemVisual(r.dataIndex,"style"),o)}))}))}var kO={color:"fill",borderColor:"stroke"},LO={symbol:1,symbolSize:1,symbolKeepAspect:1,legendIcon:1,visualMeta:1,liftZ:1,decal:1},PO=Po(),OO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(){this.currentZLevel=this.get("zlevel",!0),this.currentZ=this.get("z",!0)},e.prototype.getInitialData=function(t,e){return hx(null,this)},e.prototype.getDataParams=function(e,n,i){var r=t.prototype.getDataParams.call(this,e,n);return i&&(r.info=PO(i).info),r},e.type="series.custom",e.dependencies=["grid","polar","geo","singleAxis","calendar"],e.defaultOption={coordinateSystem:"cartesian2d",z:2,legendHoverLink:!0,clip:!1},e}(fg);function RO(t,e){return e=e||[0,0],z(["x","y"],(function(n,i){var r=this.getAxis(n),o=e[i],a=t[i]/2;return"category"===r.type?r.getBandWidth():Math.abs(r.dataToCoord(o-a)-r.dataToCoord(o+a))}),this)}function NO(t,e){return e=e||[0,0],z([0,1],(function(n){var i=e[n],r=t[n]/2,o=[],a=[];return o[n]=i-r,a[n]=i+r,o[1-n]=a[1-n]=e[1-n],Math.abs(this.dataToPoint(o)[n]-this.dataToPoint(a)[n])}),this)}function EO(t,e){var n=this.getAxis(),i=e instanceof Array?e[0]:e,r=(t instanceof Array?t[0]:t)/2;return"category"===n.type?n.getBandWidth():Math.abs(n.dataToCoord(i-r)-n.dataToCoord(i+r))}function zO(t,e){return e=e||[0,0],z(["Radius","Angle"],(function(n,i){var r=this["get"+n+"Axis"](),o=e[i],a=t[i]/2,s="category"===r.type?r.getBandWidth():Math.abs(r.dataToCoord(o-a)-r.dataToCoord(o+a));return"Angle"===n&&(s=s*Math.PI/180),s}),this)}function VO(t,e,n,i){return t&&(t.legacy||!1!==t.legacy&&!n&&!i&&"tspan"!==e&&("text"===e||_t(t,"text")))}function BO(t,e,n){var i,r,o,a=t;if("text"===e)o=a;else{o={},_t(a,"text")&&(o.text=a.text),_t(a,"rich")&&(o.rich=a.rich),_t(a,"textFill")&&(o.fill=a.textFill),_t(a,"textStroke")&&(o.stroke=a.textStroke),_t(a,"fontFamily")&&(o.fontFamily=a.fontFamily),_t(a,"fontSize")&&(o.fontSize=a.fontSize),_t(a,"fontStyle")&&(o.fontStyle=a.fontStyle),_t(a,"fontWeight")&&(o.fontWeight=a.fontWeight),r={type:"text",style:o,silent:!0},i={};var s=_t(a,"textPosition");n?i.position=s?a.textPosition:"inside":s&&(i.position=a.textPosition),_t(a,"textPosition")&&(i.position=a.textPosition),_t(a,"textOffset")&&(i.offset=a.textOffset),_t(a,"textRotation")&&(i.rotation=a.textRotation),_t(a,"textDistance")&&(i.distance=a.textDistance)}return FO(o,t),E(o.rich,(function(t){FO(t,t)})),{textConfig:i,textContent:r}}function FO(t,e){e&&(e.font=e.textFont||e.font,_t(e,"textStrokeWidth")&&(t.lineWidth=e.textStrokeWidth),_t(e,"textAlign")&&(t.align=e.textAlign),_t(e,"textVerticalAlign")&&(t.verticalAlign=e.textVerticalAlign),_t(e,"textLineHeight")&&(t.lineHeight=e.textLineHeight),_t(e,"textWidth")&&(t.width=e.textWidth),_t(e,"textHeight")&&(t.height=e.textHeight),_t(e,"textBackgroundColor")&&(t.backgroundColor=e.textBackgroundColor),_t(e,"textPadding")&&(t.padding=e.textPadding),_t(e,"textBorderColor")&&(t.borderColor=e.textBorderColor),_t(e,"textBorderWidth")&&(t.borderWidth=e.textBorderWidth),_t(e,"textBorderRadius")&&(t.borderRadius=e.textBorderRadius),_t(e,"textBoxShadowColor")&&(t.shadowColor=e.textBoxShadowColor),_t(e,"textBoxShadowBlur")&&(t.shadowBlur=e.textBoxShadowBlur),_t(e,"textBoxShadowOffsetX")&&(t.shadowOffsetX=e.textBoxShadowOffsetX),_t(e,"textBoxShadowOffsetY")&&(t.shadowOffsetY=e.textBoxShadowOffsetY))}function GO(t,e,n){var i=t;i.textPosition=i.textPosition||n.position||"inside",null!=n.offset&&(i.textOffset=n.offset),null!=n.rotation&&(i.textRotation=n.rotation),null!=n.distance&&(i.textDistance=n.distance);var r=i.textPosition.indexOf("inside")>=0,o=t.fill||"#000";WO(i,e);var a=null==i.textFill;return r?a&&(i.textFill=n.insideFill||"#fff",!i.textStroke&&n.insideStroke&&(i.textStroke=n.insideStroke),!i.textStroke&&(i.textStroke=o),null==i.textStrokeWidth&&(i.textStrokeWidth=2)):(a&&(i.textFill=t.fill||n.outsideFill||"#000"),!i.textStroke&&n.outsideStroke&&(i.textStroke=n.outsideStroke)),i.text=e.text,i.rich=e.rich,E(e.rich,(function(t){WO(t,t)})),i}function WO(t,e){e&&(_t(e,"fill")&&(t.textFill=e.fill),_t(e,"stroke")&&(t.textStroke=e.fill),_t(e,"lineWidth")&&(t.textStrokeWidth=e.lineWidth),_t(e,"font")&&(t.font=e.font),_t(e,"fontStyle")&&(t.fontStyle=e.fontStyle),_t(e,"fontWeight")&&(t.fontWeight=e.fontWeight),_t(e,"fontSize")&&(t.fontSize=e.fontSize),_t(e,"fontFamily")&&(t.fontFamily=e.fontFamily),_t(e,"align")&&(t.textAlign=e.align),_t(e,"verticalAlign")&&(t.textVerticalAlign=e.verticalAlign),_t(e,"lineHeight")&&(t.textLineHeight=e.lineHeight),_t(e,"width")&&(t.textWidth=e.width),_t(e,"height")&&(t.textHeight=e.height),_t(e,"backgroundColor")&&(t.textBackgroundColor=e.backgroundColor),_t(e,"padding")&&(t.textPadding=e.padding),_t(e,"borderColor")&&(t.textBorderColor=e.borderColor),_t(e,"borderWidth")&&(t.textBorderWidth=e.borderWidth),_t(e,"borderRadius")&&(t.textBorderRadius=e.borderRadius),_t(e,"shadowColor")&&(t.textBoxShadowColor=e.shadowColor),_t(e,"shadowBlur")&&(t.textBoxShadowBlur=e.shadowBlur),_t(e,"shadowOffsetX")&&(t.textBoxShadowOffsetX=e.shadowOffsetX),_t(e,"shadowOffsetY")&&(t.textBoxShadowOffsetY=e.shadowOffsetY),_t(e,"textShadowColor")&&(t.textShadowColor=e.textShadowColor),_t(e,"textShadowBlur")&&(t.textShadowBlur=e.textShadowBlur),_t(e,"textShadowOffsetX")&&(t.textShadowOffsetX=e.textShadowOffsetX),_t(e,"textShadowOffsetY")&&(t.textShadowOffsetY=e.textShadowOffsetY))}var HO={position:["x","y"],scale:["scaleX","scaleY"],origin:["originX","originY"]},YO=G(HO),UO=(V(gr,(function(t,e){return t[e]=1,t}),{}),gr.join(", "),["","style","shape","extra"]),XO=Po();function ZO(t,e,n,i,r){var o=t+"Animation",a=ch(t,i,r)||{},s=XO(e).userDuring;return a.duration>0&&(a.during=s?W(tR,{el:e,userDuring:s}):null,a.setToFinal=!0,a.scope=t),A(a,n[o]),a}function jO(t,e,n,i){var r=(i=i||{}).dataIndex,o=i.isInit,a=i.clearStyle,s=n.isAnimationEnabled(),l=XO(t),u=e.style;l.userDuring=e.during;var h={},c={};if(function(t,e,n){for(var i=0;i=0)){var c=t.getAnimationStyleProps(),p=c?c.style:null;if(p){!r&&(r=i.style={});var d=G(n);for(u=0;u0&&t.animateFrom(p,d)}else!function(t,e,n,i,r){if(r){var o=ZO("update",t,e,i,n);o.duration>0&&t.animateFrom(r,o)}}(t,e,r||0,n,h);qO(t,e),u?t.dirty():t.markRedraw()}function qO(t,e){for(var n=XO(t).leaveToProps,i=0;i=0){!o&&(o=i[t]={});var p=G(a);for(h=0;hi[1]&&i.reverse(),{coordSys:{type:"polar",cx:t.cx,cy:t.cy,r:i[1],r0:i[0]},api:{coord:function(i){var r=e.dataToRadius(i[0]),o=n.dataToAngle(i[1]),a=t.coordToPoint([r,o]);return a.push(r,o*Math.PI/180),a},size:W(zO,t)}}},calendar:function(t){var e=t.getRect(),n=t.getRangeInfo();return{coordSys:{type:"calendar",x:e.x,y:e.y,width:e.width,height:e.height,cellWidth:t.getCellWidth(),cellHeight:t.getCellHeight(),rangeInfo:{start:n.start,end:n.end,weeks:n.weeks,dayCount:n.allDay}},api:{coord:function(e,n){return t.dataToPoint(e,n)}}}}};function mR(t){return t instanceof Ms}function xR(t){return t instanceof wa}var _R=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n,i){this._progressiveEls=null;var r=this._data,o=t.getData(),a=this.group,s=IR(t,o,e,n);r||a.removeAll(),o.diff(r).add((function(e){CR(n,null,e,s(e,i),t,a,o)})).remove((function(e){var n=r.getItemGraphicEl(e);n&&KO(n,PO(n).option,t)})).update((function(e,l){var u=r.getItemGraphicEl(l);CR(n,u,e,s(e,i),t,a,o)})).execute();var l=t.get("clip",!0)?yS(t.coordinateSystem,!1,t):null;l?a.setClipPath(l):a.removeClipPath(),this._data=o},e.prototype.incrementalPrepareRender=function(t,e,n){this.group.removeAll(),this._data=null},e.prototype.incrementalRender=function(t,e,n,i,r){var o=e.getData(),a=IR(e,o,n,i),s=this._progressiveEls=[];function l(t){t.isGroup||(t.incremental=!0,t.ensureState("emphasis").hoverLayer=!0)}for(var u=t.start;u=0?e.getStore().get(r,n):void 0}var o=e.get(i.name,n),a=i&&i.ordinalMeta;return a?a.categories[o]:o},styleEmphasis:function(n,i){0;null==i&&(i=s);var r=m(i,lR).getItemStyle(),o=x(i,lR),a=ec(o,null,null,!0,!0);a.text=o.getShallow("show")?ot(t.getFormattedLabel(i,lR),t.getFormattedLabel(i,uR),Kw(e,i)):null;var l=nc(o,null,!0);return b(n,r),r=GO(r,a,l),n&&_(r,n),r.legacy=!0,r},visual:function(t,n){if(null==n&&(n=s),_t(kO,t)){var i=e.getItemVisual(n,"style");return i?i[kO[t]]:null}if(_t(LO,t))return e.getItemVisual(n,t)},barLayout:function(t){if("cartesian2d"===o.type){return function(t){var e=[],n=t.axis,i="axis0";if("category"===n.type){for(var r=n.getBandWidth(),o=0;o=c;f--){var g=e.childAt(f);OR(e,g,r)}}(t,c,n,i,r),a>=0?o.replaceAt(c,a):o.add(c),c}function AR(t,e,n){var i,r=PO(t),o=e.type,a=e.shape,s=e.style;return n.isUniversalTransitionEnabled()||null!=o&&o!==r.customGraphicType||"path"===o&&((i=a)&&(_t(i,"pathData")||_t(i,"d")))&&zR(a)!==r.customPathData||"image"===o&&_t(s,"image")&&s.image!==r.customImagePath}function kR(t,e,n){var i=e?LR(t,e):t,r=e?PR(t,i,lR):t.style,o=t.type,a=i?i.textConfig:null,s=t.textContent,l=s?e?LR(s,e):s:null;if(r&&(n.isLegacy||VO(r,o,!!a,!!l))){n.isLegacy=!0;var u=BO(r,o,!e);!a&&u.textConfig&&(a=u.textConfig),!l&&u.textContent&&(l=u.textContent)}if(!e&&l){var h=l;!h.type&&(h.type="text")}var c=e?n[e]:n.normal;c.cfg=a,c.conOpt=l}function LR(t,e){return e?t?t[e]:null:t}function PR(t,e,n){var i=e&&e.style;return null==i&&n===lR&&t&&(i=t.styleEmphasis),i}function OR(t,e,n){e&&KO(e,PO(t).option,n)}function RR(t,e){var n=t&&t.name;return null!=n?n:"e\0\0"+e}function NR(t,e){var n=this.context,i=null!=t?n.newChildren[t]:null,r=null!=e?n.oldChildren[e]:null;DR(n.api,r,n.dataIndex,i,n.seriesModel,n.group)}function ER(t){var e=this.context,n=e.oldChildren[t];n&&KO(n,PO(n).option,e.seriesModel)}function zR(t){return t&&(t.pathData||t.d)}var VR=Po(),BR=T,FR=W,GR=function(){function t(){this._dragging=!1,this.animationThreshold=15}return t.prototype.render=function(t,e,n,i){var r=e.get("value"),o=e.get("status");if(this._axisModel=t,this._axisPointerModel=e,this._api=n,i||this._lastValue!==r||this._lastStatus!==o){this._lastValue=r,this._lastStatus=o;var a=this._group,s=this._handle;if(!o||"hide"===o)return a&&a.hide(),void(s&&s.hide());a&&a.show(),s&&s.show();var l={};this.makeElOption(l,r,t,e,n);var u=l.graphicKey;u!==this._lastGraphicKey&&this.clear(n),this._lastGraphicKey=u;var h=this._moveAnimation=this.determineAnimation(t,e);if(a){var c=H(WR,e,h);this.updatePointerEl(a,l,c),this.updateLabelEl(a,l,c,e)}else a=this._group=new Er,this.createPointerEl(a,l,t,e),this.createLabelEl(a,l,t,e),n.getZr().add(a);XR(a,e,!0),this._renderHandle(r)}},t.prototype.remove=function(t){this.clear(t)},t.prototype.dispose=function(t){this.clear(t)},t.prototype.determineAnimation=function(t,e){var n=e.get("animation"),i=t.axis,r="category"===i.type,o=e.get("snap");if(!o&&!r)return!1;if("auto"===n||null==n){var a=this.animationThreshold;if(r&&i.getBandWidth()>a)return!0;if(o){var s=oI(t).seriesDataCount,l=i.getExtent();return Math.abs(l[0]-l[1])/s>a}return!1}return!0===n},t.prototype.makeElOption=function(t,e,n,i,r){},t.prototype.createPointerEl=function(t,e,n,i){var r=e.pointer;if(r){var o=VR(t).pointerEl=new qh[r.type](BR(e.pointer));t.add(o)}},t.prototype.createLabelEl=function(t,e,n,i){if(e.label){var r=VR(t).labelEl=new Bs(BR(e.label));t.add(r),YR(r,i)}},t.prototype.updatePointerEl=function(t,e,n){var i=VR(t).pointerEl;i&&e.pointer&&(i.setStyle(e.pointer.style),n(i,{shape:e.pointer.shape}))},t.prototype.updateLabelEl=function(t,e,n,i){var r=VR(t).labelEl;r&&(r.setStyle(e.label.style),n(r,{x:e.label.x,y:e.label.y}),YR(r,i))},t.prototype._renderHandle=function(t){if(!this._dragging&&this.updateHandleTransform){var e,n=this._axisPointerModel,i=this._api.getZr(),r=this._handle,o=n.getModel("handle"),a=n.get("status");if(!o.get("show")||!a||"hide"===a)return r&&i.remove(r),void(this._handle=null);this._handle||(e=!0,r=this._handle=Wh(o.get("icon"),{cursor:"move",draggable:!0,onmousemove:function(t){pe(t.event)},onmousedown:FR(this._onHandleDragMove,this,0,0),drift:FR(this._onHandleDragMove,this),ondragend:FR(this._onHandleDragEnd,this)}),i.add(r)),XR(r,n,!1),r.setStyle(o.getItemStyle(null,["color","borderColor","borderWidth","opacity","shadowColor","shadowBlur","shadowOffsetX","shadowOffsetY"]));var s=o.get("size");Y(s)||(s=[s,s]),r.scaleX=s[0]/2,r.scaleY=s[1]/2,Eg(this,"_doDispatchAxisPointer",o.get("throttle")||0,"fixRate"),this._moveHandleToValue(t,e)}},t.prototype._moveHandleToValue=function(t,e){WR(this._axisPointerModel,!e&&this._moveAnimation,this._handle,UR(this.getHandleTransform(t,this._axisModel,this._axisPointerModel)))},t.prototype._onHandleDragMove=function(t,e){var n=this._handle;if(n){this._dragging=!0;var i=this.updateHandleTransform(UR(n),[t,e],this._axisModel,this._axisPointerModel);this._payloadInfo=i,n.stopAnimation(),n.attr(UR(i)),VR(n).lastProp=null,this._doDispatchAxisPointer()}},t.prototype._doDispatchAxisPointer=function(){if(this._handle){var t=this._payloadInfo,e=this._axisModel;this._api.dispatchAction({type:"updateAxisPointer",x:t.cursorPoint[0],y:t.cursorPoint[1],tooltipOption:t.tooltipOption,axesInfo:[{axisDim:e.axis.dim,axisIndex:e.componentIndex}]})}},t.prototype._onHandleDragEnd=function(){if(this._dragging=!1,this._handle){var t=this._axisPointerModel.get("value");this._moveHandleToValue(t),this._api.dispatchAction({type:"hideTip"})}},t.prototype.clear=function(t){this._lastValue=null,this._lastStatus=null;var e=t.getZr(),n=this._group,i=this._handle;e&&n&&(this._lastGraphicKey=null,n&&e.remove(n),i&&e.remove(i),this._group=null,this._handle=null,this._payloadInfo=null),zg(this,"_doDispatchAxisPointer")},t.prototype.doClear=function(){},t.prototype.buildLabel=function(t,e,n){return{x:t[n=n||0],y:t[1-n],width:e[n],height:e[1-n]}},t}();function WR(t,e,n,i){HR(VR(n).lastProp,i)||(VR(n).lastProp=i,e?dh(n,i,t):(n.stopAnimation(),n.attr(i)))}function HR(t,e){if(q(t)&&q(e)){var n=!0;return E(e,(function(e,i){n=n&&HR(t[i],e)})),!!n}return t===e}function YR(t,e){t[e.get(["label","show"])?"show":"hide"]()}function UR(t){return{x:t.x||0,y:t.y||0,rotation:t.rotation||0}}function XR(t,e,n){var i=e.get("z"),r=e.get("zlevel");t&&t.traverse((function(t){"group"!==t.type&&(null!=i&&(t.z=i),null!=r&&(t.zlevel=r),t.silent=n)}))}function ZR(t){var e,n=t.get("type"),i=t.getModel(n+"Style");return"line"===n?(e=i.getLineStyle()).fill=null:"shadow"===n&&((e=i.getAreaStyle()).stroke=null),e}function jR(t,e,n,i,r){var o=qR(n.get("value"),e.axis,e.ecModel,n.get("seriesDataIndices"),{precision:n.get(["label","precision"]),formatter:n.get(["label","formatter"])}),a=n.getModel("label"),s=dp(a.get("padding")||0),l=a.getFont(),u=_r(o,l),h=r.position,c=u.width+s[1]+s[3],p=u.height+s[0]+s[2],d=r.align;"right"===d&&(h[0]-=c),"center"===d&&(h[0]-=c/2);var f=r.verticalAlign;"bottom"===f&&(h[1]-=p),"middle"===f&&(h[1]-=p/2),function(t,e,n,i){var r=i.getWidth(),o=i.getHeight();t[0]=Math.min(t[0]+e,r)-e,t[1]=Math.min(t[1]+n,o)-n,t[0]=Math.max(t[0],0),t[1]=Math.max(t[1],0)}(h,c,p,i);var g=a.get("backgroundColor");g&&"auto"!==g||(g=e.get(["axisLine","lineStyle","color"])),t.label={x:h[0],y:h[1],style:ec(a,{text:o,font:l,fill:a.getTextColor(),padding:s,backgroundColor:g}),z2:10}}function qR(t,e,n,i,r){t=e.scale.parse(t);var o=e.scale.getLabel({value:t},{precision:r.precision}),a=r.formatter;if(a){var s={value:d_(e,{value:t}),axisDimension:e.dim,axisIndex:e.index,seriesData:[]};E(i,(function(t){var e=n.getSeriesByIndex(t.seriesIndex),i=t.dataIndexInside,r=e&&e.getDataParams(i);r&&s.seriesData.push(r)})),X(a)?o=a.replace("{value}",o):U(a)&&(o=a(s))}return o}function KR(t,e,n){var i=[1,0,0,1,0,0];return we(i,i,n.rotation),be(i,i,n.position),Eh([t.dataToCoord(e),(n.labelOffset||0)+(n.labelDirection||1)*(n.labelMargin||0)],i)}function $R(t,e,n,i,r,o){var a=KM.innerTextLayout(n.rotation,0,n.labelDirection);n.labelMargin=r.get(["label","margin"]),jR(e,i,r,o,{position:KR(i.axis,t,n),align:a.textAlign,verticalAlign:a.textVerticalAlign})}function JR(t,e,n){return{x1:t[n=n||0],y1:t[1-n],x2:e[n],y2:e[1-n]}}function QR(t,e,n){return{x:t[n=n||0],y:t[1-n],width:e[n],height:e[1-n]}}function tN(t,e,n,i,r,o){return{cx:t,cy:e,r0:n,r:i,startAngle:r,endAngle:o,clockwise:!0}}var eN=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.makeElOption=function(t,e,n,i,r){var o=n.axis,a=o.grid,s=i.get("type"),l=nN(a,o).getOtherAxis(o).getGlobalExtent(),u=o.toGlobalCoord(o.dataToCoord(e,!0));if(s&&"none"!==s){var h=ZR(i),c=iN[s](o,u,l);c.style=h,t.graphicKey=c.type,t.pointer=c}$R(e,t,FM(a.model,n),n,i,r)},e.prototype.getHandleTransform=function(t,e,n){var i=FM(e.axis.grid.model,e,{labelInside:!1});i.labelMargin=n.get(["handle","margin"]);var r=KR(e.axis,t,i);return{x:r[0],y:r[1],rotation:i.rotation+(i.labelDirection<0?Math.PI:0)}},e.prototype.updateHandleTransform=function(t,e,n,i){var r=n.axis,o=r.grid,a=r.getGlobalExtent(!0),s=nN(o,r).getOtherAxis(r).getGlobalExtent(),l="x"===r.dim?0:1,u=[t.x,t.y];u[l]+=e[l],u[l]=Math.min(a[1],u[l]),u[l]=Math.max(a[0],u[l]);var h=(s[1]+s[0])/2,c=[h,h];c[l]=u[l];return{x:u[0],y:u[1],rotation:t.rotation,cursorPoint:c,tooltipOption:[{verticalAlign:"middle"},{align:"center"}][l]}},e}(GR);function nN(t,e){var n={};return n[e.dim+"AxisIndex"]=e.index,t.getCartesian(n)}var iN={line:function(t,e,n){return{type:"Line",subPixelOptimize:!0,shape:JR([e,n[0]],[e,n[1]],rN(t))}},shadow:function(t,e,n){var i=Math.max(1,t.getBandWidth()),r=n[1]-n[0];return{type:"Rect",shape:QR([e-i/2,n[0]],[i,r],rN(t))}}};function rN(t){return"x"===t.dim?0:1}var oN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="axisPointer",e.defaultOption={show:"auto",z:50,type:"line",snap:!1,triggerTooltip:!0,value:null,status:null,link:[],animation:null,animationDurationUpdate:200,lineStyle:{color:"#B9BEC9",width:1,type:"dashed"},shadowStyle:{color:"rgba(210,219,238,0.2)"},label:{show:!0,formatter:null,precision:"auto",margin:3,color:"#fff",padding:[5,7,5,7],backgroundColor:"auto",borderColor:null,borderWidth:0,borderRadius:3},handle:{show:!1,icon:"M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7v-1.2h6.6z M13.3,22H6.7v-1.2h6.6z M13.3,19.6H6.7v-1.2h6.6z",size:45,margin:50,color:"#333",shadowBlur:3,shadowColor:"#aaa",shadowOffsetX:0,shadowOffsetY:2,throttle:40}},e}(Op),aN=Po(),sN=E;function lN(t,e,n){if(!r.node){var i=e.getZr();aN(i).records||(aN(i).records={}),function(t,e){if(aN(t).initialized)return;function n(n,i){t.on(n,(function(n){var r=function(t){var e={showTip:[],hideTip:[]},n=function(i){var r=e[i.type];r?r.push(i):(i.dispatchAction=n,t.dispatchAction(i))};return{dispatchAction:n,pendings:e}}(e);sN(aN(t).records,(function(t){t&&i(t,n,r.dispatchAction)})),function(t,e){var n,i=t.showTip.length,r=t.hideTip.length;i?n=t.showTip[i-1]:r&&(n=t.hideTip[r-1]);n&&(n.dispatchAction=null,e.dispatchAction(n))}(r.pendings,e)}))}aN(t).initialized=!0,n("click",H(hN,"click")),n("mousemove",H(hN,"mousemove")),n("globalout",uN)}(i,e),(aN(i).records[t]||(aN(i).records[t]={})).handler=n}}function uN(t,e,n){t.handler("leave",null,n)}function hN(t,e,n,i){e.handler(t,n,i)}function cN(t,e){if(!r.node){var n=e.getZr();(aN(n).records||{})[t]&&(aN(n).records[t]=null)}}var pN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=e.getComponent("tooltip"),r=t.get("triggerOn")||i&&i.get("triggerOn")||"mousemove|click";lN("axisPointer",n,(function(t,e,n){"none"!==r&&("leave"===t||r.indexOf(t)>=0)&&n({type:"updateAxisPointer",currTrigger:t,x:e&&e.offsetX,y:e&&e.offsetY})}))},e.prototype.remove=function(t,e){cN("axisPointer",e)},e.prototype.dispose=function(t,e){cN("axisPointer",e)},e.type="axisPointer",e}(wg);function dN(t,e){var n,i=[],r=t.seriesIndex;if(null==r||!(n=e.getSeriesByIndex(r)))return{point:[]};var o=n.getData(),a=Lo(o,t);if(null==a||a<0||Y(a))return{point:[]};var s=o.getItemGraphicEl(a),l=n.coordinateSystem;if(n.getTooltipPosition)i=n.getTooltipPosition(a)||[];else if(l&&l.dataToPoint)if(t.isStacked){var u=l.getBaseAxis(),h=l.getOtherAxis(u).dim,c=u.dim,p="x"===h||"radius"===h?1:0,d=o.mapDimension(c),f=[];f[p]=o.get(d,a),f[1-p]=o.get(o.getCalculationInfo("stackResultDimension"),a),i=l.dataToPoint(f)||[]}else i=l.dataToPoint(o.getValues(z(l.dimensions,(function(t){return o.mapDimension(t)})),a))||[];else if(s){var g=s.getBoundingRect().clone();g.applyTransform(s.transform),i=[g.x+g.width/2,g.y+g.height/2]}return{point:i,el:s}}var fN=Po();function gN(t,e,n){var i=t.currTrigger,r=[t.x,t.y],o=t,a=t.dispatchAction||W(n.dispatchAction,n),s=e.getComponent("axisPointer").coordSysAxesInfo;if(s){_N(r)&&(r=dN({seriesIndex:o.seriesIndex,dataIndex:o.dataIndex},e).point);var l=_N(r),u=o.axesInfo,h=s.axesInfo,c="leave"===i||_N(r),p={},d={},f={list:[],map:{}},g={showPointer:H(vN,d),showTooltip:H(mN,f)};E(s.coordSysMap,(function(t,e){var n=l||t.containPoint(r);E(s.coordSysAxesInfo[e],(function(t,e){var i=t.axis,o=function(t,e){for(var n=0;n<(t||[]).length;n++){var i=t[n];if(e.axis.dim===i.axisDim&&e.axis.model.componentIndex===i.axisIndex)return i}}(u,t);if(!c&&n&&(!u||o)){var a=o&&o.value;null!=a||l||(a=i.pointToData(r)),null!=a&&yN(t,a,g,!1,p)}}))}));var y={};return E(h,(function(t,e){var n=t.linkGroup;n&&!d[e]&&E(n.axesInfo,(function(e,i){var r=d[i];if(e!==t&&r){var o=r.value;n.mapper&&(o=t.axis.scale.parse(n.mapper(o,xN(e),xN(t)))),y[t.key]=o}}))})),E(y,(function(t,e){yN(h[e],t,g,!0,p)})),function(t,e,n){var i=n.axesInfo=[];E(e,(function(e,n){var r=e.axisPointerModel.option,o=t[n];o?(!e.useHandle&&(r.status="show"),r.value=o.value,r.seriesDataIndices=(o.payloadBatch||[]).slice()):!e.useHandle&&(r.status="hide"),"show"===r.status&&i.push({axisDim:e.axis.dim,axisIndex:e.axis.model.componentIndex,value:r.value})}))}(d,h,p),function(t,e,n,i){if(_N(e)||!t.list.length)return void i({type:"hideTip"});var r=((t.list[0].dataByAxis[0]||{}).seriesDataIndices||[])[0]||{};i({type:"showTip",escapeConnect:!0,x:e[0],y:e[1],tooltipOption:n.tooltipOption,position:n.position,dataIndexInside:r.dataIndexInside,dataIndex:r.dataIndex,seriesIndex:r.seriesIndex,dataByCoordSys:t.list})}(f,r,t,a),function(t,e,n){var i=n.getZr(),r="axisPointerLastHighlights",o=fN(i)[r]||{},a=fN(i)[r]={};E(t,(function(t,e){var n=t.axisPointerModel.option;"show"===n.status&&E(n.seriesDataIndices,(function(t){var e=t.seriesIndex+" | "+t.dataIndex;a[e]=t}))}));var s=[],l=[];E(o,(function(t,e){!a[e]&&l.push(t)})),E(a,(function(t,e){!o[e]&&s.push(t)})),l.length&&n.dispatchAction({type:"downplay",escapeConnect:!0,notBlur:!0,batch:l}),s.length&&n.dispatchAction({type:"highlight",escapeConnect:!0,notBlur:!0,batch:s})}(h,0,n),p}}function yN(t,e,n,i,r){var o=t.axis;if(!o.scale.isBlank()&&o.containData(e))if(t.involveSeries){var a=function(t,e){var n=e.axis,i=n.dim,r=t,o=[],a=Number.MAX_VALUE,s=-1;return E(e.seriesModels,(function(e,l){var u,h,c=e.getData().mapDimensionsAll(i);if(e.getAxisTooltipData){var p=e.getAxisTooltipData(c,t,n);h=p.dataIndices,u=p.nestestValue}else{if(!(h=e.getData().indicesOfNearest(c[0],t,"category"===n.type?.5:null)).length)return;u=e.getData().get(c[0],h[0])}if(null!=u&&isFinite(u)){var d=t-u,f=Math.abs(d);f<=a&&((f=0&&s<0)&&(a=f,s=d,r=u,o.length=0),E(h,(function(t){o.push({seriesIndex:e.seriesIndex,dataIndexInside:t,dataIndex:e.getData().getRawIndex(t)})})))}})),{payloadBatch:o,snapToValue:r}}(e,t),s=a.payloadBatch,l=a.snapToValue;s[0]&&null==r.seriesIndex&&A(r,s[0]),!i&&t.snap&&o.containData(l)&&null!=l&&(e=l),n.showPointer(t,e,s),n.showTooltip(t,a,l)}else n.showPointer(t,e)}function vN(t,e,n,i){t[e.key]={value:n,payloadBatch:i}}function mN(t,e,n,i){var r=n.payloadBatch,o=e.axis,a=o.model,s=e.axisPointerModel;if(e.triggerTooltip&&r.length){var l=e.coordSys.model,u=sI(l),h=t.map[u];h||(h=t.map[u]={coordSysId:l.id,coordSysIndex:l.componentIndex,coordSysType:l.type,coordSysMainType:l.mainType,dataByAxis:[]},t.list.push(h)),h.dataByAxis.push({axisDim:o.dim,axisIndex:a.componentIndex,axisType:a.type,axisId:a.id,value:i,valueLabelOpt:{precision:s.get(["label","precision"]),formatter:s.get(["label","formatter"])},seriesDataIndices:r.slice()})}}function xN(t){var e=t.axis.model,n={},i=n.axisDim=t.axis.dim;return n.axisIndex=n[i+"AxisIndex"]=e.componentIndex,n.axisName=n[i+"AxisName"]=e.name,n.axisId=n[i+"AxisId"]=e.id,n}function _N(t){return!t||null==t[0]||isNaN(t[0])||null==t[1]||isNaN(t[1])}function bN(t){uI.registerAxisPointerClass("CartesianAxisPointer",eN),t.registerComponentModel(oN),t.registerComponentView(pN),t.registerPreprocessor((function(t){if(t){(!t.axisPointer||0===t.axisPointer.length)&&(t.axisPointer={});var e=t.axisPointer.link;e&&!Y(e)&&(t.axisPointer.link=[e])}})),t.registerProcessor(t.PRIORITY.PROCESSOR.STATISTIC,(function(t,e){t.getComponent("axisPointer").coordSysAxesInfo=nI(t,e)})),t.registerAction({type:"updateAxisPointer",event:"updateAxisPointer",update:":updateAxisPointer"},gN)}var wN=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.makeElOption=function(t,e,n,i,r){var o=n.axis;"angle"===o.dim&&(this.animationThreshold=Math.PI/18);var a=o.polar,s=a.getOtherAxis(o).getExtent(),l=o.dataToCoord(e),u=i.get("type");if(u&&"none"!==u){var h=ZR(i),c=SN[u](o,a,l,s);c.style=h,t.graphicKey=c.type,t.pointer=c}var p=function(t,e,n,i,r){var o=e.axis,a=o.dataToCoord(t),s=i.getAngleAxis().getExtent()[0];s=s/180*Math.PI;var l,u,h,c=i.getRadiusAxis().getExtent();if("radius"===o.dim){var p=[1,0,0,1,0,0];we(p,p,s),be(p,p,[i.cx,i.cy]),l=Eh([a,-r],p);var d=e.getModel("axisLabel").get("rotate")||0,f=KM.innerTextLayout(s,d*Math.PI/180,-1);u=f.textAlign,h=f.textVerticalAlign}else{var g=c[1];l=i.coordToPoint([g+r,a]);var y=i.cx,v=i.cy;u=Math.abs(l[0]-y)/g<.3?"center":l[0]>y?"left":"right",h=Math.abs(l[1]-v)/g<.3?"middle":l[1]>v?"top":"bottom"}return{position:l,align:u,verticalAlign:h}}(e,n,0,a,i.get(["label","margin"]));jR(t,n,i,r,p)},e}(GR);var SN={line:function(t,e,n,i){return"angle"===t.dim?{type:"Line",shape:JR(e.coordToPoint([i[0],n]),e.coordToPoint([i[1],n]))}:{type:"Circle",shape:{cx:e.cx,cy:e.cy,r:n}}},shadow:function(t,e,n,i){var r=Math.max(1,t.getBandWidth()),o=Math.PI/180;return"angle"===t.dim?{type:"Sector",shape:tN(e.cx,e.cy,i[0],i[1],(-n-r/2)*o,(r/2-n)*o)}:{type:"Sector",shape:tN(e.cx,e.cy,n-r/2,n+r/2,0,2*Math.PI)}}},MN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.findAxisModel=function(t){var e;return this.ecModel.eachComponent(t,(function(t){t.getCoordSysModel()===this&&(e=t)}),this),e},e.type="polar",e.dependencies=["radiusAxis","angleAxis"],e.defaultOption={z:0,center:["50%","50%"],radius:"80%"},e}(Op),IN=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getCoordSysModel=function(){return this.getReferringComponents("polar",Eo).models[0]},e.type="polarAxis",e}(Op);R(IN,m_);var TN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="angleAxis",e}(IN),CN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="radiusAxis",e}(IN),DN=function(t){function e(e,n){return t.call(this,"radius",e,n)||this}return n(e,t),e.prototype.pointToData=function(t,e){return this.polar.pointToData(t,e)["radius"===this.dim?0:1]},e}(q_);DN.prototype.dataToRadius=q_.prototype.dataToCoord,DN.prototype.radiusToData=q_.prototype.coordToData;var AN=Po(),kN=function(t){function e(e,n){return t.call(this,"angle",e,n||[0,360])||this}return n(e,t),e.prototype.pointToData=function(t,e){return this.polar.pointToData(t,e)["radius"===this.dim?0:1]},e.prototype.calculateCategoryInterval=function(){var t=this,e=t.getLabelModel(),n=t.scale,i=n.getExtent(),r=n.count();if(i[1]-i[0]<1)return 0;var o=i[0],a=t.dataToCoord(o+1)-t.dataToCoord(o),s=Math.abs(a),l=_r(null==o?"":o+"",e.getFont(),"center","top"),u=Math.max(l.height,7)/s;isNaN(u)&&(u=1/0);var h=Math.max(0,Math.floor(u)),c=AN(t.model),p=c.lastAutoInterval,d=c.lastTickCount;return null!=p&&null!=d&&Math.abs(p-h)<=1&&Math.abs(d-r)<=1&&p>h?h=p:(c.lastTickCount=r,c.lastAutoInterval=h),h},e}(q_);kN.prototype.dataToAngle=q_.prototype.dataToCoord,kN.prototype.angleToData=q_.prototype.coordToData;var LN=["radius","angle"],PN=function(){function t(t){this.dimensions=LN,this.type="polar",this.cx=0,this.cy=0,this._radiusAxis=new DN,this._angleAxis=new kN,this.axisPointerEnabled=!0,this.name=t||"",this._radiusAxis.polar=this._angleAxis.polar=this}return t.prototype.containPoint=function(t){var e=this.pointToCoord(t);return this._radiusAxis.contain(e[0])&&this._angleAxis.contain(e[1])},t.prototype.containData=function(t){return this._radiusAxis.containData(t[0])&&this._angleAxis.containData(t[1])},t.prototype.getAxis=function(t){return this["_"+t+"Axis"]},t.prototype.getAxes=function(){return[this._radiusAxis,this._angleAxis]},t.prototype.getAxesByScale=function(t){var e=[],n=this._angleAxis,i=this._radiusAxis;return n.scale.type===t&&e.push(n),i.scale.type===t&&e.push(i),e},t.prototype.getAngleAxis=function(){return this._angleAxis},t.prototype.getRadiusAxis=function(){return this._radiusAxis},t.prototype.getOtherAxis=function(t){var e=this._angleAxis;return t===e?this._radiusAxis:e},t.prototype.getBaseAxis=function(){return this.getAxesByScale("ordinal")[0]||this.getAxesByScale("time")[0]||this.getAngleAxis()},t.prototype.getTooltipAxes=function(t){var e=null!=t&&"auto"!==t?this.getAxis(t):this.getBaseAxis();return{baseAxes:[e],otherAxes:[this.getOtherAxis(e)]}},t.prototype.dataToPoint=function(t,e){return this.coordToPoint([this._radiusAxis.dataToRadius(t[0],e),this._angleAxis.dataToAngle(t[1],e)])},t.prototype.pointToData=function(t,e){var n=this.pointToCoord(t);return[this._radiusAxis.radiusToData(n[0],e),this._angleAxis.angleToData(n[1],e)]},t.prototype.pointToCoord=function(t){var e=t[0]-this.cx,n=t[1]-this.cy,i=this.getAngleAxis(),r=i.getExtent(),o=Math.min(r[0],r[1]),a=Math.max(r[0],r[1]);i.inverse?o=a-360:a=o+360;var s=Math.sqrt(e*e+n*n);e/=s,n/=s;for(var l=Math.atan2(-n,e)/Math.PI*180,u=la;)l+=360*u;return[s,l]},t.prototype.coordToPoint=function(t){var e=t[0],n=t[1]/180*Math.PI;return[Math.cos(n)*e+this.cx,-Math.sin(n)*e+this.cy]},t.prototype.getArea=function(){var t=this.getAngleAxis(),e=this.getRadiusAxis().getExtent().slice();e[0]>e[1]&&e.reverse();var n=t.getExtent(),i=Math.PI/180;return{cx:this.cx,cy:this.cy,r0:e[0],r:e[1],startAngle:-n[0]*i,endAngle:-n[1]*i,clockwise:t.inverse,contain:function(t,e){var n=t-this.cx,i=e-this.cy,r=n*n+i*i-1e-4,o=this.r,a=this.r0;return r<=o*o&&r>=a*a}}},t.prototype.convertToPixel=function(t,e,n){return ON(e)===this?this.dataToPoint(n):null},t.prototype.convertFromPixel=function(t,e,n){return ON(e)===this?this.pointToData(n):null},t}();function ON(t){var e=t.seriesModel,n=t.polarModel;return n&&n.coordinateSystem||e&&e.coordinateSystem}function RN(t,e){var n=this,i=n.getAngleAxis(),r=n.getRadiusAxis();if(i.scale.setExtent(1/0,-1/0),r.scale.setExtent(1/0,-1/0),t.eachSeries((function(t){if(t.coordinateSystem===n){var e=t.getData();E(v_(e,"radius"),(function(t){r.scale.unionExtentFromData(e,t)})),E(v_(e,"angle"),(function(t){i.scale.unionExtentFromData(e,t)}))}})),h_(i.scale,i.model),h_(r.scale,r.model),"category"===i.type&&!i.onBand){var o=i.getExtent(),a=360/i.scale.count();i.inverse?o[1]+=a:o[1]-=a,i.setExtent(o[0],o[1])}}function NN(t,e){if(t.type=e.get("type"),t.scale=c_(e),t.onBand=e.get("boundaryGap")&&"category"===t.type,t.inverse=e.get("inverse"),function(t){return"angleAxis"===t.mainType}(e)){t.inverse=t.inverse!==e.get("clockwise");var n=e.get("startAngle");t.setExtent(n,n+(t.inverse?-360:360))}e.axis=t,t.model=e}var EN={dimensions:LN,create:function(t,e){var n=[];return t.eachComponent("polar",(function(t,i){var r=new PN(i+"");r.update=RN;var o=r.getRadiusAxis(),a=r.getAngleAxis(),s=t.findAxisModel("radiusAxis"),l=t.findAxisModel("angleAxis");NN(o,s),NN(a,l),function(t,e,n){var i=e.get("center"),r=n.getWidth(),o=n.getHeight();t.cx=Ur(i[0],r),t.cy=Ur(i[1],o);var a=t.getRadiusAxis(),s=Math.min(r,o)/2,l=e.get("radius");null==l?l=[0,"100%"]:Y(l)||(l=[0,l]);var u=[Ur(l[0],s),Ur(l[1],s)];a.inverse?a.setExtent(u[1],u[0]):a.setExtent(u[0],u[1])}(r,t,e),n.push(r),t.coordinateSystem=r,r.model=t})),t.eachSeries((function(t){if("polar"===t.get("coordinateSystem")){var e=t.getReferringComponents("polar",Eo).models[0];0,t.coordinateSystem=e.coordinateSystem}})),n}},zN=["axisLine","axisLabel","axisTick","minorTick","splitLine","minorSplitLine","splitArea"];function VN(t,e,n){e[1]>e[0]&&(e=e.slice().reverse());var i=t.coordToPoint([e[0],n]),r=t.coordToPoint([e[1],n]);return{x1:i[0],y1:i[1],x2:r[0],y2:r[1]}}function BN(t){return t.getRadiusAxis().inverse?0:1}function FN(t){var e=t[0],n=t[t.length-1];e&&n&&Math.abs(Math.abs(e.coord-n.coord)-360)<1e-4&&t.pop()}var GN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.axisPointerClass="PolarAxisPointer",n}return n(e,t),e.prototype.render=function(t,e){if(this.group.removeAll(),t.get("show")){var n=t.axis,i=n.polar,r=i.getRadiusAxis().getExtent(),o=n.getTicksCoords(),a=n.getMinorTicksCoords(),s=z(n.getViewLabels(),(function(t){t=T(t);var e=n.scale,i="ordinal"===e.type?e.getRawOrdinalNumber(t.tickValue):t.tickValue;return t.coord=n.dataToCoord(i),t}));FN(s),FN(o),E(zN,(function(e){!t.get([e,"show"])||n.scale.isBlank()&&"axisLine"!==e||WN[e](this.group,t,i,o,a,r,s)}),this)}},e.type="angleAxis",e}(uI),WN={axisLine:function(t,e,n,i,r,o){var a,s=e.getModel(["axisLine","lineStyle"]),l=BN(n),u=l?0:1;(a=0===o[u]?new xu({shape:{cx:n.cx,cy:n.cy,r:o[l]},style:s.getLineStyle(),z2:1,silent:!0}):new Vu({shape:{cx:n.cx,cy:n.cy,r:o[l],r0:o[u]},style:s.getLineStyle(),z2:1,silent:!0})).style.fill=null,t.add(a)},axisTick:function(t,e,n,i,r,o){var a=e.getModel("axisTick"),s=(a.get("inside")?-1:1)*a.get("length"),l=o[BN(n)],u=z(i,(function(t){return new Xu({shape:VN(n,[l,l+s],t.coord)})}));t.add(Lh(u,{style:k(a.getModel("lineStyle").getLineStyle(),{stroke:e.get(["axisLine","lineStyle","color"])})}))},minorTick:function(t,e,n,i,r,o){if(r.length){for(var a=e.getModel("axisTick"),s=e.getModel("minorTick"),l=(a.get("inside")?-1:1)*s.get("length"),u=o[BN(n)],h=[],c=0;cf?"left":"right",v=Math.abs(d[1]-g)/p<.3?"middle":d[1]>g?"top":"bottom";if(s&&s[c]){var m=s[c];q(m)&&m.textStyle&&(a=new Sc(m.textStyle,l,l.ecModel))}var x=new Bs({silent:KM.isLabelSilent(e),style:ec(a,{x:d[0],y:d[1],fill:a.getTextColor()||e.get(["axisLine","lineStyle","color"]),text:i.formattedLabel,align:y,verticalAlign:v})});if(t.add(x),h){var _=KM.makeAxisEventDataBase(e);_.targetType="axisLabel",_.value=i.rawLabel,Js(x).eventData=_}}),this)},splitLine:function(t,e,n,i,r,o){var a=e.getModel("splitLine").getModel("lineStyle"),s=a.get("color"),l=0;s=s instanceof Array?s:[s];for(var u=[],h=0;h=0?"p":"n",T=_;m&&(i[s][M]||(i[s][M]={p:_,n:_}),T=i[s][M][I]);var C=void 0,D=void 0,A=void 0,k=void 0;if("radius"===c.dim){var L=c.dataToCoord(S)-_,P=o.dataToCoord(M);Math.abs(L)=k})}}}))}var KN={startAngle:90,clockwise:!0,splitNumber:12,axisLabel:{rotate:0}},$N={splitNumber:5},JN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="polar",e}(wg);function QN(t,e){e=e||{};var n=t.coordinateSystem,i=t.axis,r={},o=i.position,a=i.orient,s=n.getRect(),l=[s.x,s.x+s.width,s.y,s.y+s.height],u={horizontal:{top:l[2],bottom:l[3]},vertical:{left:l[0],right:l[1]}};r.position=["vertical"===a?u.vertical[o]:l[0],"horizontal"===a?u.horizontal[o]:l[3]];r.rotation=Math.PI/2*{horizontal:0,vertical:1}[a];r.labelDirection=r.tickDirection=r.nameDirection={top:-1,bottom:1,right:1,left:-1}[o],t.get(["axisTick","inside"])&&(r.tickDirection=-r.tickDirection),it(e.labelInside,t.get(["axisLabel","inside"]))&&(r.labelDirection=-r.labelDirection);var h=e.rotate;return null==h&&(h=t.get(["axisLabel","rotate"])),r.labelRotation="top"===o?-h:h,r.z2=1,r}var tE=["axisLine","axisTickLabel","axisName"],eE=["splitArea","splitLine"],nE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.axisPointerClass="SingleAxisPointer",n}return n(e,t),e.prototype.render=function(e,n,i,r){var o=this.group;o.removeAll();var a=this._axisGroup;this._axisGroup=new Er;var s=QN(e),l=new KM(e,s);E(tE,l.add,l),o.add(this._axisGroup),o.add(l.getGroup()),E(eE,(function(t){e.get([t,"show"])&&iE[t](this,this.group,this._axisGroup,e)}),this),Bh(a,this._axisGroup,e),t.prototype.render.call(this,e,n,i,r)},e.prototype.remove=function(){pI(this)},e.type="singleAxis",e}(uI),iE={splitLine:function(t,e,n,i){var r=i.axis;if(!r.scale.isBlank()){var o=i.getModel("splitLine"),a=o.getModel("lineStyle"),s=a.get("color");s=s instanceof Array?s:[s];for(var l=a.get("width"),u=i.coordinateSystem.getRect(),h=r.isHorizontal(),c=[],p=0,d=r.getTicksCoords({tickModel:o}),f=[],g=[],y=0;y=e.y&&t[1]<=e.y+e.height:n.contain(n.toLocalCoord(t[1]))&&t[0]>=e.y&&t[0]<=e.y+e.height},t.prototype.pointToData=function(t){var e=this.getAxis();return[e.coordToData(e.toLocalCoord(t["horizontal"===e.orient?0:1]))]},t.prototype.dataToPoint=function(t){var e=this.getAxis(),n=this.getRect(),i=[],r="horizontal"===e.orient?0:1;return t instanceof Array&&(t=t[0]),i[r]=e.toGlobalCoord(e.dataToCoord(+t)),i[1-r]=0===r?n.y+n.height/2:n.x+n.width/2,i},t.prototype.convertToPixel=function(t,e,n){return lE(e)===this?this.dataToPoint(n):null},t.prototype.convertFromPixel=function(t,e,n){return lE(e)===this?this.pointToData(n):null},t}();function lE(t){var e=t.seriesModel,n=t.singleAxisModel;return n&&n.coordinateSystem||e&&e.coordinateSystem}var uE={create:function(t,e){var n=[];return t.eachComponent("singleAxis",(function(i,r){var o=new sE(i,t,e);o.name="single_"+r,o.resize(i,e),i.coordinateSystem=o,n.push(o)})),t.eachSeries((function(t){if("singleAxis"===t.get("coordinateSystem")){var e=t.getReferringComponents("singleAxis",Eo).models[0];t.coordinateSystem=e&&e.coordinateSystem}})),n},dimensions:aE},hE=["x","y"],cE=["width","height"],pE=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.makeElOption=function(t,e,n,i,r){var o=n.axis,a=o.coordinateSystem,s=gE(a,1-fE(o)),l=a.dataToPoint(e)[0],u=i.get("type");if(u&&"none"!==u){var h=ZR(i),c=dE[u](o,l,s);c.style=h,t.graphicKey=c.type,t.pointer=c}$R(e,t,QN(n),n,i,r)},e.prototype.getHandleTransform=function(t,e,n){var i=QN(e,{labelInside:!1});i.labelMargin=n.get(["handle","margin"]);var r=KR(e.axis,t,i);return{x:r[0],y:r[1],rotation:i.rotation+(i.labelDirection<0?Math.PI:0)}},e.prototype.updateHandleTransform=function(t,e,n,i){var r=n.axis,o=r.coordinateSystem,a=fE(r),s=gE(o,a),l=[t.x,t.y];l[a]+=e[a],l[a]=Math.min(s[1],l[a]),l[a]=Math.max(s[0],l[a]);var u=gE(o,1-a),h=(u[1]+u[0])/2,c=[h,h];return c[a]=l[a],{x:l[0],y:l[1],rotation:t.rotation,cursorPoint:c,tooltipOption:{verticalAlign:"middle"}}},e}(GR),dE={line:function(t,e,n){return{type:"Line",subPixelOptimize:!0,shape:JR([e,n[0]],[e,n[1]],fE(t))}},shadow:function(t,e,n){var i=t.getBandWidth(),r=n[1]-n[0];return{type:"Rect",shape:QR([e-i/2,n[0]],[i,r],fE(t))}}};function fE(t){return t.isHorizontal()?0:1}function gE(t,e){var n=t.getRect();return[n[hE[e]],n[hE[e]]+n[cE[e]]]}var yE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="single",e}(wg);var vE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(e,n,i){var r=kp(e);t.prototype.init.apply(this,arguments),mE(e,r)},e.prototype.mergeOption=function(e){t.prototype.mergeOption.apply(this,arguments),mE(this.option,e)},e.prototype.getCellSize=function(){return this.option.cellSize},e.type="calendar",e.defaultOption={z:2,left:80,top:60,cellSize:20,orient:"horizontal",splitLine:{show:!0,lineStyle:{color:"#000",width:1,type:"solid"}},itemStyle:{color:"#fff",borderWidth:1,borderColor:"#ccc"},dayLabel:{show:!0,firstDay:0,position:"start",margin:"50%",color:"#000"},monthLabel:{show:!0,position:"start",margin:5,align:"center",formatter:null,color:"#000"},yearLabel:{show:!0,position:null,margin:30,formatter:null,color:"#ccc",fontFamily:"sans-serif",fontWeight:"bolder",fontSize:20}},e}(Op);function mE(t,e){var n,i=t.cellSize;1===(n=Y(i)?i:t.cellSize=[i,i]).length&&(n[1]=n[0]);var r=z([0,1],(function(t){return function(t,e){return null!=t[Sp[e][0]]||null!=t[Sp[e][1]]&&null!=t[Sp[e][2]]}(e,t)&&(n[t]="auto"),null!=n[t]&&"auto"!==n[t]}));Ap(t,e,{type:"box",ignoreSize:r})}var xE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=this.group;i.removeAll();var r=t.coordinateSystem,o=r.getRangeInfo(),a=r.getOrient(),s=e.getLocaleModel();this._renderDayRect(t,o,i),this._renderLines(t,o,a,i),this._renderYearText(t,o,a,i),this._renderMonthText(t,s,a,i),this._renderWeekText(t,s,o,a,i)},e.prototype._renderDayRect=function(t,e,n){for(var i=t.coordinateSystem,r=t.getModel("itemStyle").getItemStyle(),o=i.getCellWidth(),a=i.getCellHeight(),s=e.start.time;s<=e.end.time;s=i.getNextNDay(s,1).time){var l=i.dataToRect([s],!1).tl,u=new Es({shape:{x:l[0],y:l[1],width:o,height:a},cursor:"default",style:r});n.add(u)}},e.prototype._renderLines=function(t,e,n,i){var r=this,o=t.coordinateSystem,a=t.getModel(["splitLine","lineStyle"]).getLineStyle(),s=t.get(["splitLine","show"]),l=a.lineWidth;this._tlpoints=[],this._blpoints=[],this._firstDayOfMonth=[],this._firstDayPoints=[];for(var u=e.start,h=0;u.time<=e.end.time;h++){p(u.formatedDate),0===h&&(u=o.getDateInfo(e.start.y+"-"+e.start.m));var c=u.date;c.setMonth(c.getMonth()+1),u=o.getDateInfo(c)}function p(e){r._firstDayOfMonth.push(o.getDateInfo(e)),r._firstDayPoints.push(o.dataToRect([e],!1).tl);var l=r._getLinePointsOfOneWeek(t,e,n);r._tlpoints.push(l[0]),r._blpoints.push(l[l.length-1]),s&&r._drawSplitline(l,a,i)}p(o.getNextNDay(e.end.time,1).formatedDate),s&&this._drawSplitline(r._getEdgesPoints(r._tlpoints,l,n),a,i),s&&this._drawSplitline(r._getEdgesPoints(r._blpoints,l,n),a,i)},e.prototype._getEdgesPoints=function(t,e,n){var i=[t[0].slice(),t[t.length-1].slice()],r="horizontal"===n?0:1;return i[0][r]=i[0][r]-e/2,i[1][r]=i[1][r]+e/2,i},e.prototype._drawSplitline=function(t,e,n){var i=new Hu({z2:20,shape:{points:t},style:e});n.add(i)},e.prototype._getLinePointsOfOneWeek=function(t,e,n){for(var i=t.coordinateSystem,r=i.getDateInfo(e),o=[],a=0;a<7;a++){var s=i.getNextNDay(r.time,a),l=i.dataToRect([s.time],!1);o[2*s.day]=l.tl,o[2*s.day+1]=l["horizontal"===n?"bl":"tr"]}return o},e.prototype._formatterLabel=function(t,e){return X(t)&&t?(n=t,E(e,(function(t,e){n=n.replace("{"+e+"}",i?ie(t):t)})),n):U(t)?t(e):e.nameMap;var n,i},e.prototype._yearTextPositionControl=function(t,e,n,i,r){var o=e[0],a=e[1],s=["center","bottom"];"bottom"===i?(a+=r,s=["center","top"]):"left"===i?o-=r:"right"===i?(o+=r,s=["center","top"]):a-=r;var l=0;return"left"!==i&&"right"!==i||(l=Math.PI/2),{rotation:l,x:o,y:a,style:{align:s[0],verticalAlign:s[1]}}},e.prototype._renderYearText=function(t,e,n,i){var r=t.getModel("yearLabel");if(r.get("show")){var o=r.get("margin"),a=r.get("position");a||(a="horizontal"!==n?"top":"left");var s=[this._tlpoints[this._tlpoints.length-1],this._blpoints[0]],l=(s[0][0]+s[1][0])/2,u=(s[0][1]+s[1][1])/2,h="horizontal"===n?0:1,c={top:[l,s[h][1]],bottom:[l,s[1-h][1]],left:[s[1-h][0],u],right:[s[h][0],u]},p=e.start.y;+e.end.y>+e.start.y&&(p=p+"-"+e.end.y);var d=r.get("formatter"),f={start:e.start.y,end:e.end.y,nameMap:p},g=this._formatterLabel(d,f),y=new Bs({z2:30,style:ec(r,{text:g})});y.attr(this._yearTextPositionControl(y,c[a],n,a,o)),i.add(y)}},e.prototype._monthTextPositionControl=function(t,e,n,i,r){var o="left",a="top",s=t[0],l=t[1];return"horizontal"===n?(l+=r,e&&(o="center"),"start"===i&&(a="bottom")):(s+=r,e&&(a="middle"),"start"===i&&(o="right")),{x:s,y:l,align:o,verticalAlign:a}},e.prototype._renderMonthText=function(t,e,n,i){var r=t.getModel("monthLabel");if(r.get("show")){var o=r.get("nameMap"),a=r.get("margin"),s=r.get("position"),l=r.get("align"),u=[this._tlpoints,this._blpoints];o&&!X(o)||(o&&(e=Rc(o)||e),o=e.get(["time","monthAbbr"])||[]);var h="start"===s?0:1,c="horizontal"===n?0:1;a="start"===s?-a:a;for(var p="center"===l,d=0;d=i.start.time&&n.timea.end.time&&t.reverse(),t},t.prototype._getRangeInfo=function(t){var e,n=[this.getDateInfo(t[0]),this.getDateInfo(t[1])];n[0].time>n[1].time&&(e=!0,n.reverse());var i=Math.floor(n[1].time/_E)-Math.floor(n[0].time/_E)+1,r=new Date(n[0].time),o=r.getDate(),a=n[1].date.getDate();r.setDate(o+i-1);var s=r.getDate();if(s!==a)for(var l=r.getTime()-n[1].time>0?1:-1;(s=r.getDate())!==a&&(r.getTime()-n[1].time)*l>0;)i-=l,r.setDate(s-l);var u=Math.floor((i+n[0].day+6)/7),h=e?1-u:u-1;return e&&n.reverse(),{range:[n[0].formatedDate,n[1].formatedDate],start:n[0],end:n[1],allDay:i,weeks:u,nthWeek:h,fweek:n[0].day,lweek:n[1].day}},t.prototype._getDateByWeeksAndDay=function(t,e,n){var i=this._getRangeInfo(n);if(t>i.weeks||0===t&&ei.lweek)return null;var r=7*(t-1)-i.fweek+e,o=new Date(i.start.time);return o.setDate(+i.start.d+r),this.getDateInfo(o)},t.create=function(e,n){var i=[];return e.eachComponent("calendar",(function(r){var o=new t(r,e,n);i.push(o),r.coordinateSystem=o})),e.eachSeries((function(t){"calendar"===t.get("coordinateSystem")&&(t.coordinateSystem=i[t.get("calendarIndex")||0])})),i},t.dimensions=["time","value"],t}();function wE(t){var e=t.calendarModel,n=t.seriesModel;return e?e.coordinateSystem:n?n.coordinateSystem:null}function SE(t,e){var n;return E(e,(function(e){null!=t[e]&&"auto"!==t[e]&&(n=!0)})),n}var ME=["transition","enterFrom","leaveTo"],IE=ME.concat(["enterAnimation","updateAnimation","leaveAnimation"]);function TE(t,e,n){if(n&&(!t[n]&&e[n]&&(t[n]={}),t=t[n],e=e[n]),t&&e)for(var i=n?ME:IE,r=0;r=0;l--){var p,d,f;if(f=null!=(d=Do((p=n[l]).id,null))?r.get(d):null){var g=f.parent,y=(c=AE(g),{}),v=Cp(f,p,g===i?{width:o,height:a}:{width:c.width,height:c.height},null,{hv:p.hv,boundingMode:p.bounding},y);if(!AE(f).isNew&&v){for(var m=p.transition,x={},_=0;_=0)?x[b]=w:f[b]=w}dh(f,x,t,0)}else f.attr(y)}}},e.prototype._clear=function(){var t=this,e=this._elMap;e.each((function(n){OE(n,AE(n).option,e,t._lastGraphicModel)})),this._elMap=yt()},e.prototype.dispose=function(){this._clear()},e.type="graphic",e}(wg);function LE(t){var e=_t(DE,t)?DE[t]:Ch(t);var n=new e({});return AE(n).type=t,n}function PE(t,e,n,i){var r=LE(n);return e.add(r),i.set(t,r),AE(r).id=t,AE(r).isNew=!0,r}function OE(t,e,n,i){t&&t.parent&&("group"===t.type&&t.traverse((function(t){OE(t,e,n,i)})),KO(t,e,i),n.removeKey(AE(t).id))}function RE(t,e,n,i){t.isGroup||E([["cursor",wa.prototype.cursor],["zlevel",i||0],["z",n||0],["z2",0]],(function(n){var i=n[0];_t(e,i)?t[i]=rt(e[i],n[1]):null==t[i]&&(t[i]=n[1])})),E(G(e),(function(n){if(0===n.indexOf("on")){var i=e[n];t[n]=U(i)?i:null}})),_t(e,"draggable")&&(t.draggable=e.draggable),null!=e.name&&(t.name=e.name),null!=e.id&&(t.id=e.id)}var NE=["x","y","radius","angle","single"],EE=["cartesian2d","polar","singleAxis"];function zE(t){return t+"Axis"}function VE(t,e){var n,i=yt(),r=[],o=yt();t.eachComponent({mainType:"dataZoom",query:e},(function(t){o.get(t.uid)||s(t)}));do{n=!1,t.eachComponent("dataZoom",a)}while(n);function a(t){!o.get(t.uid)&&function(t){var e=!1;return t.eachTargetAxis((function(t,n){var r=i.get(t);r&&r[n]&&(e=!0)})),e}(t)&&(s(t),n=!0)}function s(t){o.set(t.uid,!0),r.push(t),t.eachTargetAxis((function(t,e){(i.get(t)||i.set(t,[]))[e]=!0}))}return r}function BE(t){var e=t.ecModel,n={infoList:[],infoMap:yt()};return t.eachTargetAxis((function(t,i){var r=e.getComponent(zE(t),i);if(r){var o=r.getCoordSysModel();if(o){var a=o.uid,s=n.infoMap.get(a);s||(s={model:o,axisModels:[]},n.infoList.push(s),n.infoMap.set(a,s)),s.axisModels.push(r)}}})),n}var FE=function(){function t(){this.indexList=[],this.indexMap=[]}return t.prototype.add=function(t){this.indexMap[t]||(this.indexList.push(t),this.indexMap[t]=!0)},t}(),GE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._autoThrottle=!0,n._noTarget=!0,n._rangePropMode=["percent","percent"],n}return n(e,t),e.prototype.init=function(t,e,n){var i=WE(t);this.settledOption=i,this.mergeDefaultAndTheme(t,n),this._doInit(i)},e.prototype.mergeOption=function(t){var e=WE(t);C(this.option,t,!0),C(this.settledOption,e,!0),this._doInit(e)},e.prototype._doInit=function(t){var e=this.option;this._setDefaultThrottle(t),this._updateRangeUse(t);var n=this.settledOption;E([["start","startValue"],["end","endValue"]],(function(t,i){"value"===this._rangePropMode[i]&&(e[t[0]]=n[t[0]]=null)}),this),this._resetTarget()},e.prototype._resetTarget=function(){var t=this.get("orient",!0),e=this._targetAxisInfoMap=yt();this._fillSpecifiedTargetAxis(e)?this._orient=t||this._makeAutoOrientByTargetAxis():(this._orient=t||"horizontal",this._fillAutoTargetAxisByOrient(e,this._orient)),this._noTarget=!0,e.each((function(t){t.indexList.length&&(this._noTarget=!1)}),this)},e.prototype._fillSpecifiedTargetAxis=function(t){var e=!1;return E(NE,(function(n){var i=this.getReferringComponents(zE(n),zo);if(i.specified){e=!0;var r=new FE;E(i.models,(function(t){r.add(t.componentIndex)})),t.set(n,r)}}),this),e},e.prototype._fillAutoTargetAxisByOrient=function(t,e){var n=this.ecModel,i=!0;if(i){var r="vertical"===e?"y":"x";o(n.findComponents({mainType:r+"Axis"}),r)}i&&o(n.findComponents({mainType:"singleAxis",filter:function(t){return t.get("orient",!0)===e}}),"single");function o(e,n){var r=e[0];if(r){var o=new FE;if(o.add(r.componentIndex),t.set(n,o),i=!1,"x"===n||"y"===n){var a=r.getReferringComponents("grid",Eo).models[0];a&&E(e,(function(t){r.componentIndex!==t.componentIndex&&a===t.getReferringComponents("grid",Eo).models[0]&&o.add(t.componentIndex)}))}}}i&&E(NE,(function(e){if(i){var r=n.findComponents({mainType:zE(e),filter:function(t){return"category"===t.get("type",!0)}});if(r[0]){var o=new FE;o.add(r[0].componentIndex),t.set(e,o),i=!1}}}),this)},e.prototype._makeAutoOrientByTargetAxis=function(){var t;return this.eachTargetAxis((function(e){!t&&(t=e)}),this),"y"===t?"vertical":"horizontal"},e.prototype._setDefaultThrottle=function(t){if(t.hasOwnProperty("throttle")&&(this._autoThrottle=!1),this._autoThrottle){var e=this.ecModel.option;this.option.throttle=e.animation&&e.animationDurationUpdate>0?100:20}},e.prototype._updateRangeUse=function(t){var e=this._rangePropMode,n=this.get("rangeMode");E([["start","startValue"],["end","endValue"]],(function(i,r){var o=null!=t[i[0]],a=null!=t[i[1]];o&&!a?e[r]="percent":!o&&a?e[r]="value":n?e[r]=n[r]:o&&(e[r]="percent")}))},e.prototype.noTarget=function(){return this._noTarget},e.prototype.getFirstTargetAxisModel=function(){var t;return this.eachTargetAxis((function(e,n){null==t&&(t=this.ecModel.getComponent(zE(e),n))}),this),t},e.prototype.eachTargetAxis=function(t,e){this._targetAxisInfoMap.each((function(n,i){E(n.indexList,(function(n){t.call(e,i,n)}))}))},e.prototype.getAxisProxy=function(t,e){var n=this.getAxisModel(t,e);if(n)return n.__dzAxisProxy},e.prototype.getAxisModel=function(t,e){var n=this._targetAxisInfoMap.get(t);if(n&&n.indexMap[e])return this.ecModel.getComponent(zE(t),e)},e.prototype.setRawRange=function(t){var e=this.option,n=this.settledOption;E([["start","startValue"],["end","endValue"]],(function(i){null==t[i[0]]&&null==t[i[1]]||(e[i[0]]=n[i[0]]=t[i[0]],e[i[1]]=n[i[1]]=t[i[1]])}),this),this._updateRangeUse(t)},e.prototype.setCalculatedRange=function(t){var e=this.option;E(["start","startValue","end","endValue"],(function(n){e[n]=t[n]}))},e.prototype.getPercentRange=function(){var t=this.findRepresentativeAxisProxy();if(t)return t.getDataPercentWindow()},e.prototype.getValueRange=function(t,e){if(null!=t||null!=e)return this.getAxisProxy(t,e).getDataValueWindow();var n=this.findRepresentativeAxisProxy();return n?n.getDataValueWindow():void 0},e.prototype.findRepresentativeAxisProxy=function(t){if(t)return t.__dzAxisProxy;for(var e,n=this._targetAxisInfoMap.keys(),i=0;i=0}(e)){var n=zE(this._dimName),i=e.getReferringComponents(n,Eo).models[0];i&&this._axisIndex===i.componentIndex&&t.push(e)}}),this),t},t.prototype.getAxisModel=function(){return this.ecModel.getComponent(this._dimName+"Axis",this._axisIndex)},t.prototype.getMinMaxSpan=function(){return T(this._minMaxSpan)},t.prototype.calculateDataWindow=function(t){var e,n=this._dataExtent,i=this.getAxisModel().axis.scale,r=this._dataZoomModel.getRangePropMode(),o=[0,100],a=[],s=[];XE(["start","end"],(function(l,u){var h=t[l],c=t[l+"Value"];"percent"===r[u]?(null==h&&(h=o[u]),c=i.parse(Yr(h,o,n))):(e=!0,h=Yr(c=null==c?n[u]:i.parse(c),n,o)),s[u]=null==c||isNaN(c)?n[u]:c,a[u]=null==h||isNaN(h)?o[u]:h})),ZE(s),ZE(a);var l=this._minMaxSpan;function u(t,e,n,r,o){var a=o?"Span":"ValueSpan";xk(0,t,n,"all",l["min"+a],l["max"+a]);for(var s=0;s<2;s++)e[s]=Yr(t[s],n,r,!0),o&&(e[s]=i.parse(e[s]))}return e?u(s,a,n,o,!1):u(a,s,o,n,!0),{valueWindow:s,percentWindow:a}},t.prototype.reset=function(t){if(t===this._dataZoomModel){var e=this.getTargetSeriesModels();this._dataExtent=function(t,e,n){var i=[1/0,-1/0];XE(n,(function(t){!function(t,e,n){e&&E(v_(e,n),(function(n){var i=e.getApproximateExtent(n);i[0]t[1]&&(t[1]=i[1])}))}(i,t.getData(),e)}));var r=t.getAxisModel(),o=s_(r.axis.scale,r,i).calculate();return[o.min,o.max]}(this,this._dimName,e),this._updateMinMaxSpan();var n=this.calculateDataWindow(t.settledOption);this._valueWindow=n.valueWindow,this._percentWindow=n.percentWindow,this._setAxisModel()}},t.prototype.filterData=function(t,e){if(t===this._dataZoomModel){var n=this._dimName,i=this.getTargetSeriesModels(),r=t.get("filterMode"),o=this._valueWindow;"none"!==r&&XE(i,(function(t){var e=t.getData(),i=e.mapDimensionsAll(n);if(i.length){if("weakFilter"===r){var a=e.getStore(),s=z(i,(function(t){return e.getDimensionIndex(t)}),e);e.filterSelf((function(t){for(var e,n,r,l=0;lo[1];if(h&&!c&&!p)return!0;h&&(r=!0),c&&(e=!0),p&&(n=!0)}return r&&e&&n}))}else XE(i,(function(n){if("empty"===r)t.setData(e=e.map(n,(function(t){return function(t){return t>=o[0]&&t<=o[1]}(t)?t:NaN})));else{var i={};i[n]=o,e.selectRange(i)}}));XE(i,(function(t){e.setApproximateExtent(o,t)}))}}))}},t.prototype._updateMinMaxSpan=function(){var t=this._minMaxSpan={},e=this._dataZoomModel,n=this._dataExtent;XE(["min","max"],(function(i){var r=e.get(i+"Span"),o=e.get(i+"ValueSpan");null!=o&&(o=this.getAxisModel().axis.scale.parse(o)),null!=o?r=Yr(n[0]+o,n,[0,100],!0):null!=r&&(o=Yr(r,[0,100],n,!0)-n[0]),t[i+"Span"]=r,t[i+"ValueSpan"]=o}),this)},t.prototype._setAxisModel=function(){var t=this.getAxisModel(),e=this._percentWindow,n=this._valueWindow;if(e){var i=Kr(n,[0,500]);i=Math.min(i,20);var r=t.axis.scale.rawExtentInfo;0!==e[0]&&r.setDeterminedMinMax("min",+n[0].toFixed(i)),100!==e[1]&&r.setDeterminedMinMax("max",+n[1].toFixed(i)),r.freeze()}},t}();var qE={getTargetSeries:function(t){function e(e){t.eachComponent("dataZoom",(function(n){n.eachTargetAxis((function(i,r){var o=t.getComponent(zE(i),r);e(i,r,o,n)}))}))}e((function(t,e,n,i){n.__dzAxisProxy=null}));var n=[];e((function(e,i,r,o){r.__dzAxisProxy||(r.__dzAxisProxy=new jE(e,i,o,t),n.push(r.__dzAxisProxy))}));var i=yt();return E(n,(function(t){E(t.getTargetSeriesModels(),(function(t){i.set(t.uid,t)}))})),i},overallReset:function(t,e){t.eachComponent("dataZoom",(function(t){t.eachTargetAxis((function(e,n){t.getAxisProxy(e,n).reset(t)})),t.eachTargetAxis((function(n,i){t.getAxisProxy(n,i).filterData(t,e)}))})),t.eachComponent("dataZoom",(function(t){var e=t.findRepresentativeAxisProxy();if(e){var n=e.getDataPercentWindow(),i=e.getDataValueWindow();t.setCalculatedRange({start:n[0],end:n[1],startValue:i[0],endValue:i[1]})}}))}};var KE=!1;function $E(t){KE||(KE=!0,t.registerProcessor(t.PRIORITY.PROCESSOR.FILTER,qE),function(t){t.registerAction("dataZoom",(function(t,e){E(VE(e,t),(function(e){e.setRawRange({start:t.start,end:t.end,startValue:t.startValue,endValue:t.endValue})}))}))}(t),t.registerSubTypeDefaulter("dataZoom",(function(){return"slider"})))}function JE(t){t.registerComponentModel(HE),t.registerComponentView(UE),$E(t)}var QE=function(){},tz={};function ez(t,e){tz[t]=e}function nz(t){return tz[t]}var iz=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(){t.prototype.optionUpdated.apply(this,arguments);var e=this.ecModel;E(this.option.feature,(function(t,n){var i=nz(n);i&&(i.getDefaultOption&&(i.defaultOption=i.getDefaultOption(e)),C(t,i.defaultOption))}))},e.type="toolbox",e.layoutMode={type:"box",ignoreSize:!0},e.defaultOption={show:!0,z:6,orient:"horizontal",left:"right",top:"top",backgroundColor:"transparent",borderColor:"#ccc",borderRadius:0,borderWidth:0,padding:5,itemSize:15,itemGap:8,showTitle:!0,iconStyle:{borderColor:"#666",color:"none"},emphasis:{iconStyle:{borderColor:"#3E98C5"}},tooltip:{show:!1,position:"bottom"}},e}(Op);function rz(t,e){var n=dp(e.get("padding")),i=e.getItemStyle(["color","opacity"]);return i.fill=e.get("backgroundColor"),t=new Es({shape:{x:t.x-n[3],y:t.y-n[0],width:t.width+n[1]+n[3],height:t.height+n[0]+n[2],r:e.get("borderRadius")},style:i,silent:!0,z2:-1})}var oz=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t,e,n,i){var r=this.group;if(r.removeAll(),t.get("show")){var o=+t.get("itemSize"),a="vertical"===t.get("orient"),s=t.get("feature")||{},l=this._features||(this._features={}),u=[];E(s,(function(t,e){u.push(e)})),new Lm(this._featureNames||[],u).add(h).update(h).remove(H(h,null)).execute(),this._featureNames=u,function(t,e,n){var i=e.getBoxLayoutParams(),r=e.get("padding"),o={width:n.getWidth(),height:n.getHeight()},a=Tp(i,o,r);Ip(e.get("orient"),t,e.get("itemGap"),a.width,a.height),Cp(t,i,o,r)}(r,t,n),r.add(rz(r.getBoundingRect(),t)),a||r.eachChild((function(t){var e=t.__title,i=t.ensureState("emphasis"),a=i.textConfig||(i.textConfig={}),s=t.getTextContent(),l=s&&s.ensureState("emphasis");if(l&&!U(l)&&e){var u=l.style||(l.style={}),h=_r(e,Bs.makeFont(u)),c=t.x+r.x,p=!1;t.y+r.y+o+h.height>n.getHeight()&&(a.position="top",p=!0);var d=p?-5-h.height:o+10;c+h.width/2>n.getWidth()?(a.position=["100%",d],u.align="right"):c-h.width/2<0&&(a.position=[0,d],u.align="left")}}))}function h(h,c){var p,d=u[h],f=u[c],g=s[d],y=new Sc(g,t,t.ecModel);if(i&&null!=i.newTitle&&i.featureName===d&&(g.title=i.newTitle),d&&!f){if(function(t){return 0===t.indexOf("my")}(d))p={onclick:y.option.onclick,featureName:d};else{var v=nz(d);if(!v)return;p=new v}l[d]=p}else if(!(p=l[f]))return;p.uid=Ic("toolbox-feature"),p.model=y,p.ecModel=e,p.api=n;var m=p instanceof QE;d||!f?!y.get("show")||m&&p.unusable?m&&p.remove&&p.remove(e,n):(!function(i,s,l){var u,h,c=i.getModel("iconStyle"),p=i.getModel(["emphasis","iconStyle"]),d=s instanceof QE&&s.getIcons?s.getIcons():i.get("icon"),f=i.get("title")||{};X(d)?(u={})[l]=d:u=d;X(f)?(h={})[l]=f:h=f;var g=i.iconPaths={};E(u,(function(l,u){var d=Wh(l,{},{x:-o/2,y:-o/2,width:o,height:o});d.setStyle(c.getItemStyle()),d.ensureState("emphasis").style=p.getItemStyle();var f=new Bs({style:{text:h[u],align:p.get("textAlign"),borderRadius:p.get("textBorderRadius"),padding:p.get("textPadding"),fill:null},ignore:!0});d.setTextContent(f),Xh({el:d,componentModel:t,itemName:u,formatterParamsExtra:{title:h[u]}}),d.__title=h[u],d.on("mouseover",(function(){var e=p.getItemStyle(),i=a?null==t.get("right")&&"right"!==t.get("left")?"right":"left":null==t.get("bottom")&&"bottom"!==t.get("top")?"bottom":"top";f.setStyle({fill:p.get("textFill")||e.fill||e.stroke||"#000",backgroundColor:p.get("textBackgroundColor")}),d.setTextConfig({position:p.get("textPosition")||i}),f.ignore=!t.get("showTitle"),n.enterEmphasis(this)})).on("mouseout",(function(){"emphasis"!==i.get(["iconStatus",u])&&n.leaveEmphasis(this),f.hide()})),("emphasis"===i.get(["iconStatus",u])?Al:kl)(d),r.add(d),d.on("click",W(s.onclick,s,e,n,u)),g[u]=d}))}(y,p,d),y.setIconStatus=function(t,e){var n=this.option,i=this.iconPaths;n.iconStatus=n.iconStatus||{},n.iconStatus[t]=e,i[t]&&("emphasis"===e?Al:kl)(i[t])},p instanceof QE&&p.render&&p.render(y,e,n,i)):m&&p.dispose&&p.dispose(e,n)}},e.prototype.updateView=function(t,e,n,i){E(this._features,(function(t){t instanceof QE&&t.updateView&&t.updateView(t.model,e,n,i)}))},e.prototype.remove=function(t,e){E(this._features,(function(n){n instanceof QE&&n.remove&&n.remove(t,e)})),this.group.removeAll()},e.prototype.dispose=function(t,e){E(this._features,(function(n){n instanceof QE&&n.dispose&&n.dispose(t,e)}))},e.type="toolbox",e}(wg);var az=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.onclick=function(t,e){var n=this.model,i=n.get("name")||t.get("title.0.text")||"echarts",o="svg"===e.getZr().painter.getType(),a=o?"svg":n.get("type",!0)||"png",s=e.getConnectedDataURL({type:a,backgroundColor:n.get("backgroundColor",!0)||t.get("backgroundColor")||"#fff",connectedBackgroundColor:n.get("connectedBackgroundColor"),excludeComponents:n.get("excludeComponents"),pixelRatio:n.get("pixelRatio")}),l=r.browser;if(U(MouseEvent)&&(l.newEdge||!l.ie&&!l.edge)){var u=document.createElement("a");u.download=i+"."+a,u.target="_blank",u.href=s;var h=new MouseEvent("click",{view:document.defaultView,bubbles:!0,cancelable:!1});u.dispatchEvent(h)}else if(window.navigator.msSaveOrOpenBlob||o){var c=s.split(","),p=c[0].indexOf("base64")>-1,d=o?decodeURIComponent(c[1]):c[1];p&&(d=window.atob(d));var f=i+"."+a;if(window.navigator.msSaveOrOpenBlob){for(var g=d.length,y=new Uint8Array(g);g--;)y[g]=d.charCodeAt(g);var v=new Blob([y]);window.navigator.msSaveOrOpenBlob(v,f)}else{var m=document.createElement("iframe");document.body.appendChild(m);var x=m.contentWindow,_=x.document;_.open("image/svg+xml","replace"),_.write(d),_.close(),x.focus(),_.execCommand("SaveAs",!0,f),document.body.removeChild(m)}}else{var b=n.get("lang"),w='',S=window.open();S.document.write(w),S.document.title=i}},e.getDefaultOption=function(t){return{show:!0,icon:"M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0",title:t.getLocaleModel().get(["toolbox","saveAsImage","title"]),type:"png",connectedBackgroundColor:"#fff",name:"",excludeComponents:["toolbox"],lang:t.getLocaleModel().get(["toolbox","saveAsImage","lang"])}},e}(QE),sz="__ec_magicType_stack__",lz=[["line","bar"],["stack"]],uz=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getIcons=function(){var t=this.model,e=t.get("icon"),n={};return E(t.get("type"),(function(t){e[t]&&(n[t]=e[t])})),n},e.getDefaultOption=function(t){return{show:!0,type:[],icon:{line:"M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4",bar:"M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7",stack:"M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z"},title:t.getLocaleModel().get(["toolbox","magicType","title"]),option:{},seriesIndex:{}}},e.prototype.onclick=function(t,e,n){var i=this.model,r=i.get(["seriesIndex",n]);if(hz[n]){var o,a={series:[]};E(lz,(function(t){P(t,n)>=0&&E(t,(function(t){i.setIconStatus(t,"normal")}))})),i.setIconStatus(n,"emphasis"),t.eachComponent({mainType:"series",query:null==r?null:{seriesIndex:r}},(function(t){var e=t.subType,r=t.id,o=hz[n](e,r,t,i);o&&(k(o,t.option),a.series.push(o));var s=t.coordinateSystem;if(s&&"cartesian2d"===s.type&&("line"===n||"bar"===n)){var l=s.getAxesByScale("ordinal")[0];if(l){var u=l.dim+"Axis",h=t.getReferringComponents(u,Eo).models[0].componentIndex;a[u]=a[u]||[];for(var c=0;c<=h;c++)a[u][h]=a[u][h]||{};a[u][h].boundaryGap="bar"===n}}}));var s=n;"stack"===n&&(o=C({stack:i.option.title.tiled,tiled:i.option.title.stack},i.option.title),"emphasis"!==i.get(["iconStatus",n])&&(s="tiled")),e.dispatchAction({type:"changeMagicType",currentType:s,newOption:a,newTitle:o,featureName:"magicType"})}},e}(QE),hz={line:function(t,e,n,i){if("bar"===t)return C({id:e,type:"line",data:n.get("data"),stack:n.get("stack"),markPoint:n.get("markPoint"),markLine:n.get("markLine")},i.get(["option","line"])||{},!0)},bar:function(t,e,n,i){if("line"===t)return C({id:e,type:"bar",data:n.get("data"),stack:n.get("stack"),markPoint:n.get("markPoint"),markLine:n.get("markLine")},i.get(["option","bar"])||{},!0)},stack:function(t,e,n,i){var r=n.get("stack")===sz;if("line"===t||"bar"===t)return i.setIconStatus("stack",r?"normal":"emphasis"),C({id:e,stack:r?"":sz},i.get(["option","stack"])||{},!0)}};vm({type:"changeMagicType",event:"magicTypeChanged",update:"prepareAndUpdate"},(function(t,e){e.mergeOption(t.newOption)}));var cz=new Array(60).join("-"),pz="\t";function dz(t){return t.replace(/^\s\s*/,"").replace(/\s\s*$/,"")}var fz=new RegExp("[\t]+","g");function gz(t,e){var n=t.split(new RegExp("\n*"+cz+"\n*","g")),i={series:[]};return E(n,(function(t,n){if(function(t){if(t.slice(0,t.indexOf("\n")).indexOf(pz)>=0)return!0}(t)){var r=function(t){for(var e=t.split(/\n+/g),n=[],i=z(dz(e.shift()).split(fz),(function(t){return{name:t,data:[]}})),r=0;r=0)&&t(r,i._targetInfoList)}))}return t.prototype.setOutputRanges=function(t,e){return this.matchOutputRanges(t,e,(function(t,e,n){if((t.coordRanges||(t.coordRanges=[])).push(e),!t.coordRange){t.coordRange=e;var i=Az[t.brushType](0,n,e);t.__rangeOffset={offset:Lz[t.brushType](i.values,t.range,[1,1]),xyMinMax:i.xyMinMax}}})),t},t.prototype.matchOutputRanges=function(t,e,n){E(t,(function(t){var i=this.findTargetInfo(t,e);i&&!0!==i&&E(i.coordSyses,(function(i){var r=Az[t.brushType](1,i,t.range,!0);n(t,r.values,i,e)}))}),this)},t.prototype.setInputRanges=function(t,e){E(t,(function(t){var n,i,r,o,a,s=this.findTargetInfo(t,e);if(t.range=t.range||[],s&&!0!==s){t.panelId=s.panelId;var l=Az[t.brushType](0,s.coordSys,t.coordRange),u=t.__rangeOffset;t.range=u?Lz[t.brushType](l.values,u.offset,(n=l.xyMinMax,i=u.xyMinMax,r=Oz(n),o=Oz(i),a=[r[0]/o[0],r[1]/o[1]],isNaN(a[0])&&(a[0]=1),isNaN(a[1])&&(a[1]=1),a)):l.values}}),this)},t.prototype.makePanelOpts=function(t,e){return z(this._targetInfoList,(function(n){var i=n.getPanelRect();return{panelId:n.panelId,defaultBrushType:e?e(n):null,clipPath:bL(i),isTargetByCursor:SL(i,t,n.coordSysModel),getLinearBrushOtherExtent:wL(i)}}))},t.prototype.controlSeries=function(t,e,n){var i=this.findTargetInfo(t,n);return!0===i||i&&P(i.coordSyses,e.coordinateSystem)>=0},t.prototype.findTargetInfo=function(t,e){for(var n=this._targetInfoList,i=Iz(e,t),r=0;rt[1]&&t.reverse(),t}function Iz(t,e){return Ro(t,e,{includeMainTypes:wz})}var Tz={grid:function(t,e){var n=t.xAxisModels,i=t.yAxisModels,r=t.gridModels,o=yt(),a={},s={};(n||i||r)&&(E(n,(function(t){var e=t.axis.grid.model;o.set(e.id,e),a[e.id]=!0})),E(i,(function(t){var e=t.axis.grid.model;o.set(e.id,e),s[e.id]=!0})),E(r,(function(t){o.set(t.id,t),a[t.id]=!0,s[t.id]=!0})),o.each((function(t){var r=t.coordinateSystem,o=[];E(r.getCartesians(),(function(t,e){(P(n,t.getAxis("x").model)>=0||P(i,t.getAxis("y").model)>=0)&&o.push(t)})),e.push({panelId:"grid--"+t.id,gridModel:t,coordSysModel:t,coordSys:o[0],coordSyses:o,getPanelRect:Dz.grid,xAxisDeclared:a[t.id],yAxisDeclared:s[t.id]})})))},geo:function(t,e){E(t.geoModels,(function(t){var n=t.coordinateSystem;e.push({panelId:"geo--"+t.id,geoModel:t,coordSysModel:t,coordSys:n,coordSyses:[n],getPanelRect:Dz.geo})}))}},Cz=[function(t,e){var n=t.xAxisModel,i=t.yAxisModel,r=t.gridModel;return!r&&n&&(r=n.axis.grid.model),!r&&i&&(r=i.axis.grid.model),r&&r===e.gridModel},function(t,e){var n=t.geoModel;return n&&n===e.geoModel}],Dz={grid:function(){return this.coordSys.master.getRect().clone()},geo:function(){var t=this.coordSys,e=t.getBoundingRect().clone();return e.applyTransform(Nh(t)),e}},Az={lineX:H(kz,0),lineY:H(kz,1),rect:function(t,e,n,i){var r=t?e.pointToData([n[0][0],n[1][0]],i):e.dataToPoint([n[0][0],n[1][0]],i),o=t?e.pointToData([n[0][1],n[1][1]],i):e.dataToPoint([n[0][1],n[1][1]],i),a=[Mz([r[0],o[0]]),Mz([r[1],o[1]])];return{values:a,xyMinMax:a}},polygon:function(t,e,n,i){var r=[[1/0,-1/0],[1/0,-1/0]];return{values:z(n,(function(n){var o=t?e.pointToData(n,i):e.dataToPoint(n,i);return r[0][0]=Math.min(r[0][0],o[0]),r[1][0]=Math.min(r[1][0],o[1]),r[0][1]=Math.max(r[0][1],o[0]),r[1][1]=Math.max(r[1][1],o[1]),o})),xyMinMax:r}}};function kz(t,e,n,i){var r=n.getAxis(["x","y"][t]),o=Mz(z([0,1],(function(t){return e?r.coordToData(r.toLocalCoord(i[t]),!0):r.toGlobalCoord(r.dataToCoord(i[t]))}))),a=[];return a[t]=o,a[1-t]=[NaN,NaN],{values:o,xyMinMax:a}}var Lz={lineX:H(Pz,0),lineY:H(Pz,1),rect:function(t,e,n){return[[t[0][0]-n[0]*e[0][0],t[0][1]-n[0]*e[0][1]],[t[1][0]-n[1]*e[1][0],t[1][1]-n[1]*e[1][1]]]},polygon:function(t,e,n){return z(t,(function(t,i){return[t[0]-n[0]*e[i][0],t[1]-n[1]*e[i][1]]}))}};function Pz(t,e,n,i){return[e[0]-i[t]*n[0],e[1]-i[t]*n[1]]}function Oz(t){return t?[t[0][1]-t[0][0],t[1][1]-t[1][0]]:[NaN,NaN]}var Rz,Nz,Ez=E,zz=xo+"toolbox-dataZoom_",Vz=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t,e,n,i){this._brushController||(this._brushController=new Yk(n.getZr()),this._brushController.on("brush",W(this._onBrush,this)).mount()),function(t,e,n,i,r){var o=n._isZoomActive;i&&"takeGlobalCursor"===i.type&&(o="dataZoomSelect"===i.key&&i.dataZoomSelectActive);n._isZoomActive=o,t.setIconStatus("zoom",o?"emphasis":"normal");var a=new Sz(Fz(t),e,{include:["grid"]}).makePanelOpts(r,(function(t){return t.xAxisDeclared&&!t.yAxisDeclared?"lineX":!t.xAxisDeclared&&t.yAxisDeclared?"lineY":"rect"}));n._brushController.setPanels(a).enableBrush(!(!o||!a.length)&&{brushType:"auto",brushStyle:t.getModel("brushStyle").getItemStyle()})}(t,e,this,i,n),function(t,e){t.setIconStatus("back",function(t){return _z(t).length}(e)>1?"emphasis":"normal")}(t,e)},e.prototype.onclick=function(t,e,n){Bz[n].call(this)},e.prototype.remove=function(t,e){this._brushController&&this._brushController.unmount()},e.prototype.dispose=function(t,e){this._brushController&&this._brushController.dispose()},e.prototype._onBrush=function(t){var e=t.areas;if(t.isEnd&&e.length){var n={},i=this.ecModel;this._brushController.updateCovers([]),new Sz(Fz(this.model),i,{include:["grid"]}).matchOutputRanges(e,i,(function(t,e,n){if("cartesian2d"===n.type){var i=t.brushType;"rect"===i?(r("x",n,e[0]),r("y",n,e[1])):r({lineX:"x",lineY:"y"}[i],n,e)}})),function(t,e){var n=_z(t);mz(e,(function(e,i){for(var r=n.length-1;r>=0&&!n[r][i];r--);if(r<0){var o=t.queryComponents({mainType:"dataZoom",subType:"select",id:i})[0];if(o){var a=o.getPercentRange();n[0][i]={dataZoomId:i,start:a[0],end:a[1]}}}})),n.push(e)}(i,n),this._dispatchZoomAction(n)}function r(t,e,r){var o=e.getAxis(t),a=o.model,s=function(t,e,n){var i;return n.eachComponent({mainType:"dataZoom",subType:"select"},(function(n){n.getAxisModel(t,e.componentIndex)&&(i=n)})),i}(t,a,i),l=s.findRepresentativeAxisProxy(a).getMinMaxSpan();null==l.minValueSpan&&null==l.maxValueSpan||(r=xk(0,r.slice(),o.scale.getExtent(),0,l.minValueSpan,l.maxValueSpan)),s&&(n[s.id]={dataZoomId:s.id,startValue:r[0],endValue:r[1]})}},e.prototype._dispatchZoomAction=function(t){var e=[];Ez(t,(function(t,n){e.push(T(t))})),e.length&&this.api.dispatchAction({type:"dataZoom",from:this.uid,batch:e})},e.getDefaultOption=function(t){return{show:!0,filterMode:"filter",icon:{zoom:"M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1",back:"M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26"},title:t.getLocaleModel().get(["toolbox","dataZoom","title"]),brushStyle:{borderWidth:0,color:"rgba(210,219,238,0.2)"}}},e}(QE),Bz={zoom:function(){var t=!this._isZoomActive;this.api.dispatchAction({type:"takeGlobalCursor",key:"dataZoomSelect",dataZoomSelectActive:t})},back:function(){this._dispatchZoomAction(function(t){var e=_z(t),n=e[e.length-1];e.length>1&&e.pop();var i={};return mz(n,(function(t,n){for(var r=e.length-1;r>=0;r--)if(t=e[r][n]){i[n]=t;break}})),i}(this.ecModel))}};function Fz(t){var e={xAxisIndex:t.get("xAxisIndex",!0),yAxisIndex:t.get("yAxisIndex",!0),xAxisId:t.get("xAxisId",!0),yAxisId:t.get("yAxisId",!0)};return null==e.xAxisIndex&&null==e.xAxisId&&(e.xAxisIndex="all"),null==e.yAxisIndex&&null==e.yAxisId&&(e.yAxisIndex="all"),e}Rz="dataZoom",Nz=function(t){var e=t.getComponent("toolbox",0),n=["feature","dataZoom"];if(e&&null!=e.get(n)){var i=e.getModel(n),r=[],o=Ro(t,Fz(i));return Ez(o.xAxisModels,(function(t){return a(t,"xAxis","xAxisIndex")})),Ez(o.yAxisModels,(function(t){return a(t,"yAxis","yAxisIndex")})),r}function a(t,e,n){var o=t.componentIndex,a={type:"select",$fromToolbox:!0,filterMode:i.get("filterMode",!0)||"filter",id:zz+e+o};a[n]=o,r.push(a)}},lt(null==ed.get(Rz)&&Nz),ed.set(Rz,Nz);var Gz=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="tooltip",e.dependencies=["axisPointer"],e.defaultOption={z:60,show:!0,showContent:!0,trigger:"item",triggerOn:"mousemove|click",alwaysShowContent:!1,displayMode:"single",renderMode:"auto",confine:null,showDelay:0,hideDelay:100,transitionDuration:.4,enterable:!1,backgroundColor:"#fff",shadowBlur:10,shadowColor:"rgba(0, 0, 0, .2)",shadowOffsetX:1,shadowOffsetY:2,borderRadius:4,borderWidth:1,padding:null,extraCssText:"",axisPointer:{type:"line",axis:"auto",animation:"auto",animationDurationUpdate:200,animationEasingUpdate:"exponentialOut",crossStyle:{color:"#999",width:1,type:"dashed",textStyle:{}}},textStyle:{color:"#666",fontSize:14}},e}(Op);function Wz(t){var e=t.get("confine");return null!=e?!!e:"richText"===t.get("renderMode")}function Hz(t){if(r.domSupported)for(var e=document.documentElement.style,n=0,i=t.length;n-1?(u+="top:50%",h+="translateY(-50%) rotate("+(a="left"===s?-225:-45)+"deg)"):(u+="left:50%",h+="translateX(-50%) rotate("+(a="top"===s?225:45)+"deg)");var c=a*Math.PI/180,p=l+r,d=p*Math.abs(Math.cos(c))+p*Math.abs(Math.sin(c)),f=e+" solid "+r+"px;";return'
                                            '}(n,i,r)),X(t))o.innerHTML=t+a;else if(t){o.innerHTML="",Y(t)||(t=[t]);for(var s=0;s=0?this._tryShow(n,i):"leave"===e&&this._hide(i))}),this))},e.prototype._keepShow=function(){var t=this._tooltipModel,e=this._ecModel,n=this._api,i=t.get("triggerOn");if(null!=this._lastX&&null!=this._lastY&&"none"!==i&&"click"!==i){var r=this;clearTimeout(this._refreshUpdateTimeout),this._refreshUpdateTimeout=setTimeout((function(){!n.isDisposed()&&r.manuallyShowTip(t,e,n,{x:r._lastX,y:r._lastY,dataByCoordSys:r._lastDataByCoordSys})}))}},e.prototype.manuallyShowTip=function(t,e,n,i){if(i.from!==this.uid&&!r.node&&n.getDom()){var o=aV(i,n);this._ticket="";var a=i.dataByCoordSys,s=function(t,e,n){var i=No(t).queryOptionMap,r=i.keys()[0];if(!r||"series"===r)return;var o,a=Vo(e,r,i.get(r),{useDefault:!1,enableAll:!1,enableNone:!1}).models[0];if(!a)return;if(n.getViewOfComponentModel(a).group.traverse((function(e){var n=Js(e).tooltipConfig;if(n&&n.name===t.name)return o=e,!0})),o)return{componentMainType:r,componentIndex:a.componentIndex,el:o}}(i,e,n);if(s){var l=s.el.getBoundingRect().clone();l.applyTransform(s.el.transform),this._tryShow({offsetX:l.x+l.width/2,offsetY:l.y+l.height/2,target:s.el,position:i.position,positionDefault:"bottom"},o)}else if(i.tooltip&&null!=i.x&&null!=i.y){var u=iV;u.x=i.x,u.y=i.y,u.update(),Js(u).tooltipConfig={name:null,option:i.tooltip},this._tryShow({offsetX:i.x,offsetY:i.y,target:u},o)}else if(a)this._tryShow({offsetX:i.x,offsetY:i.y,position:i.position,dataByCoordSys:a,tooltipOption:i.tooltipOption},o);else if(null!=i.seriesIndex){if(this._manuallyAxisShowTip(t,e,n,i))return;var h=dN(i,e),c=h.point[0],p=h.point[1];null!=c&&null!=p&&this._tryShow({offsetX:c,offsetY:p,target:h.el,position:i.position,positionDefault:"bottom"},o)}else null!=i.x&&null!=i.y&&(n.dispatchAction({type:"updateAxisPointer",x:i.x,y:i.y}),this._tryShow({offsetX:i.x,offsetY:i.y,position:i.position,target:n.getZr().findHover(i.x,i.y).target},o))}},e.prototype.manuallyHideTip=function(t,e,n,i){var r=this._tooltipContent;!this._alwaysShowContent&&this._tooltipModel&&r.hideLater(this._tooltipModel.get("hideDelay")),this._lastX=this._lastY=this._lastDataByCoordSys=null,i.from!==this.uid&&this._hide(aV(i,n))},e.prototype._manuallyAxisShowTip=function(t,e,n,i){var r=i.seriesIndex,o=i.dataIndex,a=e.getComponent("axisPointer").coordSysAxesInfo;if(null!=r&&null!=o&&null!=a){var s=e.getSeriesByIndex(r);if(s)if("axis"===oV([s.getData().getItemModel(o),s,(s.coordinateSystem||{}).model],this._tooltipModel).get("trigger"))return n.dispatchAction({type:"updateAxisPointer",seriesIndex:r,dataIndex:o,position:i.position}),!0}},e.prototype._tryShow=function(t,e){var n=t.target;if(this._tooltipModel){this._lastX=t.offsetX,this._lastY=t.offsetY;var i=t.dataByCoordSys;if(i&&i.length)this._showAxisTooltip(i,t);else if(n){var r,o;this._lastDataByCoordSys=null,Ty(n,(function(t){return null!=Js(t).dataIndex?(r=t,!0):null!=Js(t).tooltipConfig?(o=t,!0):void 0}),!0),r?this._showSeriesItemTooltip(t,r,e):o?this._showComponentItemTooltip(t,o,e):this._hide(e)}else this._lastDataByCoordSys=null,this._hide(e)}},e.prototype._showOrMove=function(t,e){var n=t.get("showDelay");e=W(e,this),clearTimeout(this._showTimout),n>0?this._showTimout=setTimeout(e,n):e()},e.prototype._showAxisTooltip=function(t,e){var n=this._ecModel,i=this._tooltipModel,r=[e.offsetX,e.offsetY],o=oV([e.tooltipOption],i),a=this._renderMode,s=[],l=Qf("section",{blocks:[],noHeader:!0}),u=[],h=new hg;E(t,(function(t){E(t.dataByAxis,(function(t){var e=n.getComponent(t.axisDim+"Axis",t.axisIndex),r=t.value;if(e&&null!=r){var o=qR(r,e.axis,n,t.seriesDataIndices,t.valueLabelOpt),c=Qf("section",{header:o,noHeader:!ut(o),sortBlocks:!0,blocks:[]});l.blocks.push(c),E(t.seriesDataIndices,(function(l){var p=n.getSeriesByIndex(l.seriesIndex),d=l.dataIndexInside,f=p.getDataParams(d);if(!(f.dataIndex<0)){f.axisDim=t.axisDim,f.axisIndex=t.axisIndex,f.axisType=t.axisType,f.axisId=t.axisId,f.axisValue=d_(e.axis,{value:r}),f.axisValueLabel=o,f.marker=h.makeTooltipMarker("item",xp(f.color),a);var g=yf(p.formatTooltip(d,!0,null)),y=g.frag;if(y){var v=oV([p],i).get("valueFormatter");c.blocks.push(v?A({valueFormatter:v},y):y)}g.text&&u.push(g.text),s.push(f)}}))}}))})),l.blocks.reverse(),u.reverse();var c=e.position,p=o.get("order"),d=og(l,h,a,p,n.get("useUTC"),o.get("textStyle"));d&&u.unshift(d);var f="richText"===a?"\n\n":"
                                            ",g=u.join(f);this._showOrMove(o,(function(){this._updateContentNotChangedOnAxis(t,s)?this._updatePosition(o,c,r[0],r[1],this._tooltipContent,s):this._showTooltipContent(o,g,s,Math.random()+"",r[0],r[1],c,null,h)}))},e.prototype._showSeriesItemTooltip=function(t,e,n){var i=this._ecModel,r=Js(e),o=r.seriesIndex,a=i.getSeriesByIndex(o),s=r.dataModel||a,l=r.dataIndex,u=r.dataType,h=s.getData(u),c=this._renderMode,p=t.positionDefault,d=oV([h.getItemModel(l),s,a&&(a.coordinateSystem||{}).model],this._tooltipModel,p?{position:p}:null),f=d.get("trigger");if(null==f||"item"===f){var g=s.getDataParams(l,u),y=new hg;g.marker=y.makeTooltipMarker("item",xp(g.color),c);var v=yf(s.formatTooltip(l,!1,u)),m=d.get("order"),x=d.get("valueFormatter"),_=v.frag,b=_?og(x?A({valueFormatter:x},_):_,y,c,m,i.get("useUTC"),d.get("textStyle")):v.text,w="item_"+s.name+"_"+l;this._showOrMove(d,(function(){this._showTooltipContent(d,b,g,w,t.offsetX,t.offsetY,t.position,t.target,y)})),n({type:"showTip",dataIndexInside:l,dataIndex:h.getRawIndex(l),seriesIndex:o,from:this.uid})}},e.prototype._showComponentItemTooltip=function(t,e,n){var i=Js(e),r=i.tooltipConfig.option||{};if(X(r)){r={content:r,formatter:r}}var o=[r],a=this._ecModel.getComponent(i.componentMainType,i.componentIndex);a&&o.push(a),o.push({formatter:r.content});var s=t.positionDefault,l=oV(o,this._tooltipModel,s?{position:s}:null),u=l.get("content"),h=Math.random()+"",c=new hg;this._showOrMove(l,(function(){var n=T(l.get("formatterParams")||{});this._showTooltipContent(l,u,n,h,t.offsetX,t.offsetY,t.position,e,c)})),n({type:"showTip",from:this.uid})},e.prototype._showTooltipContent=function(t,e,n,i,r,o,a,s,l){if(this._ticket="",t.get("showContent")&&t.get("show")){var u=this._tooltipContent;u.setEnterable(t.get("enterable"));var h=t.get("formatter");a=a||t.get("position");var c=e,p=this._getNearestPoint([r,o],n,t.get("trigger"),t.get("borderColor")).color;if(h)if(X(h)){var d=t.ecModel.get("useUTC"),f=Y(n)?n[0]:n;c=h,f&&f.axisType&&f.axisType.indexOf("time")>=0&&(c=jc(f.axisValue,c,d)),c=vp(c,n,!0)}else if(U(h)){var g=W((function(e,i){e===this._ticket&&(u.setContent(i,l,t,p,a),this._updatePosition(t,a,r,o,u,n,s))}),this);this._ticket=i,c=h(n,i,g)}else c=h;u.setContent(c,l,t,p,a),u.show(t,p),this._updatePosition(t,a,r,o,u,n,s)}},e.prototype._getNearestPoint=function(t,e,n,i){return"axis"===n||Y(e)?{color:i||("html"===this._renderMode?"#fff":"none")}:Y(e)?void 0:{color:i||e.color||e.borderColor}},e.prototype._updatePosition=function(t,e,n,i,r,o,a){var s=this._api.getWidth(),l=this._api.getHeight();e=e||t.get("position");var u=r.getSize(),h=t.get("align"),c=t.get("verticalAlign"),p=a&&a.getBoundingRect().clone();if(a&&p.applyTransform(a.transform),U(e)&&(e=e([n,i],o,r.el,p,{viewSize:[s,l],contentSize:u.slice()})),Y(e))n=Ur(e[0],s),i=Ur(e[1],l);else if(q(e)){var d=e;d.width=u[0],d.height=u[1];var f=Tp(d,{width:s,height:l});n=f.x,i=f.y,h=null,c=null}else if(X(e)&&a){var g=function(t,e,n,i){var r=n[0],o=n[1],a=Math.ceil(Math.SQRT2*i)+8,s=0,l=0,u=e.width,h=e.height;switch(t){case"inside":s=e.x+u/2-r/2,l=e.y+h/2-o/2;break;case"top":s=e.x+u/2-r/2,l=e.y-o-a;break;case"bottom":s=e.x+u/2-r/2,l=e.y+h+a;break;case"left":s=e.x-r-a,l=e.y+h/2-o/2;break;case"right":s=e.x+u+a,l=e.y+h/2-o/2}return[s,l]}(e,p,u,t.get("borderWidth"));n=g[0],i=g[1]}else{g=function(t,e,n,i,r,o,a){var s=n.getSize(),l=s[0],u=s[1];null!=o&&(t+l+o+2>i?t-=l+o:t+=o);null!=a&&(e+u+a>r?e-=u+a:e+=a);return[t,e]}(n,i,r,s,l,h?null:20,c?null:20);n=g[0],i=g[1]}if(h&&(n-=sV(h)?u[0]/2:"right"===h?u[0]:0),c&&(i-=sV(c)?u[1]/2:"bottom"===c?u[1]:0),Wz(t)){g=function(t,e,n,i,r){var o=n.getSize(),a=o[0],s=o[1];return t=Math.min(t+a,i)-a,e=Math.min(e+s,r)-s,t=Math.max(t,0),e=Math.max(e,0),[t,e]}(n,i,r,s,l);n=g[0],i=g[1]}r.moveTo(n,i)},e.prototype._updateContentNotChangedOnAxis=function(t,e){var n=this._lastDataByCoordSys,i=this._cbParamsList,r=!!n&&n.length===t.length;return r&&E(n,(function(n,o){var a=n.dataByAxis||[],s=(t[o]||{}).dataByAxis||[];(r=r&&a.length===s.length)&&E(a,(function(t,n){var o=s[n]||{},a=t.seriesDataIndices||[],l=o.seriesDataIndices||[];(r=r&&t.value===o.value&&t.axisType===o.axisType&&t.axisId===o.axisId&&a.length===l.length)&&E(a,(function(t,e){var n=l[e];r=r&&t.seriesIndex===n.seriesIndex&&t.dataIndex===n.dataIndex})),i&&E(t.seriesDataIndices,(function(t){var n=t.seriesIndex,o=e[n],a=i[n];o&&a&&a.data!==o.data&&(r=!1)}))}))})),this._lastDataByCoordSys=t,this._cbParamsList=e,!!r},e.prototype._hide=function(t){this._lastDataByCoordSys=null,t({type:"hideTip",from:this.uid})},e.prototype.dispose=function(t,e){!r.node&&e.getDom()&&(zg(this,"_updatePosition"),this._tooltipContent.dispose(),cN("itemTooltip",e))},e.type="tooltip",e}(wg);function oV(t,e,n){var i,r=e.ecModel;n?(i=new Sc(n,r,r),i=new Sc(e.option,i,r)):i=e;for(var o=t.length-1;o>=0;o--){var a=t[o];a&&(a instanceof Sc&&(a=a.get("tooltip",!0)),X(a)&&(a={formatter:a}),a&&(i=new Sc(a,i,r)))}return i}function aV(t,e){return t.dispatchAction||W(e.dispatchAction,e)}function sV(t){return"center"===t||"middle"===t}var lV=["rect","polygon","keep","clear"];function uV(t,e){var n=_o(t?t.brush:[]);if(n.length){var i=[];E(n,(function(t){var e=t.hasOwnProperty("toolbox")?t.toolbox:[];e instanceof Array&&(i=i.concat(e))}));var r=t&&t.toolbox;Y(r)&&(r=r[0]),r||(r={feature:{}},t.toolbox=[r]);var o=r.feature||(r.feature={}),a=o.brush||(o.brush={}),s=a.type||(a.type=[]);s.push.apply(s,i),function(t){var e={};E(t,(function(t){e[t]=1})),t.length=0,E(e,(function(e,n){t.push(n)}))}(s),e&&!s.length&&s.push.apply(s,lV)}}var hV=E;function cV(t){if(t)for(var e in t)if(t.hasOwnProperty(e))return!0}function pV(t,e,n){var i={};return hV(e,(function(e){var r,o=i[e]=((r=function(){}).prototype.__hidden=r.prototype,new r);hV(t[e],(function(t,i){if(dD.isValidType(i)){var r={type:i,visual:t};n&&n(r,e),o[i]=new dD(r),"opacity"===i&&((r=T(r)).type="colorAlpha",o.__hidden.__alphaForOpacity=new dD(r))}}))})),i}function dV(t,e,n){var i;E(n,(function(t){e.hasOwnProperty(t)&&cV(e[t])&&(i=!0)})),i&&E(n,(function(n){e.hasOwnProperty(n)&&cV(e[n])?t[n]=T(e[n]):delete t[n]}))}var fV={lineX:gV(0),lineY:gV(1),rect:{point:function(t,e,n){return t&&n.boundingRect.contain(t[0],t[1])},rect:function(t,e,n){return t&&n.boundingRect.intersect(t)}},polygon:{point:function(t,e,n){return t&&n.boundingRect.contain(t[0],t[1])&&w_(n.range,t[0],t[1])},rect:function(t,e,n){var i=n.range;if(!t||i.length<=1)return!1;var r=t.x,o=t.y,a=t.width,s=t.height,l=i[0];return!!(w_(i,r,o)||w_(i,r+a,o)||w_(i,r,o+s)||w_(i,r+a,o+s)||Ee.create(t).contain(l[0],l[1])||Hh(r,o,r+a,o,i)||Hh(r,o,r,o+s,i)||Hh(r+a,o,r+a,o+s,i)||Hh(r,o+s,r+a,o+s,i))||void 0}}};function gV(t){var e=["x","y"],n=["width","height"];return{point:function(e,n,i){if(e){var r=i.range;return yV(e[t],r)}},rect:function(i,r,o){if(i){var a=o.range,s=[i[e[t]],i[e[t]]+i[n[t]]];return s[1]e[0][1]&&(e[0][1]=o[0]),o[1]e[1][1]&&(e[1][1]=o[1])}return e&&IV(e)}};function IV(t){return new Ee(t[0][0],t[1][0],t[0][1]-t[0][0],t[1][1]-t[1][0])}var TV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){this.ecModel=t,this.api=e,this.model,(this._brushController=new Yk(e.getZr())).on("brush",W(this._onBrush,this)).mount()},e.prototype.render=function(t,e,n,i){this.model=t,this._updateController(t,e,n,i)},e.prototype.updateTransform=function(t,e,n,i){_V(e),this._updateController(t,e,n,i)},e.prototype.updateVisual=function(t,e,n,i){this.updateTransform(t,e,n,i)},e.prototype.updateView=function(t,e,n,i){this._updateController(t,e,n,i)},e.prototype._updateController=function(t,e,n,i){(!i||i.$from!==t.id)&&this._brushController.setPanels(t.brushTargetManager.makePanelOpts(n)).enableBrush(t.brushOption).updateCovers(t.areas.slice())},e.prototype.dispose=function(){this._brushController.dispose()},e.prototype._onBrush=function(t){var e=this.model.id,n=this.model.brushTargetManager.setOutputRanges(t.areas,this.ecModel);(!t.isEnd||t.removeOnClick)&&this.api.dispatchAction({type:"brush",brushId:e,areas:T(n),$from:e}),t.isEnd&&this.api.dispatchAction({type:"brushEnd",brushId:e,areas:T(n),$from:e})},e.type="brush",e}(wg),CV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.areas=[],n.brushOption={},n}return n(e,t),e.prototype.optionUpdated=function(t,e){var n=this.option;!e&&dV(n,t,["inBrush","outOfBrush"]);var i=n.inBrush=n.inBrush||{};n.outOfBrush=n.outOfBrush||{color:"#ddd"},i.hasOwnProperty("liftZ")||(i.liftZ=5)},e.prototype.setAreas=function(t){t&&(this.areas=z(t,(function(t){return DV(this.option,t)}),this))},e.prototype.setBrushOption=function(t){this.brushOption=DV(this.option,t),this.brushType=this.brushOption.brushType},e.type="brush",e.dependencies=["geo","grid","xAxis","yAxis","parallel","series"],e.defaultOption={seriesIndex:"all",brushType:"rect",brushMode:"single",transformable:!0,brushStyle:{borderWidth:1,color:"rgba(210,219,238,0.3)",borderColor:"#D2DBEE"},throttleType:"fixRate",throttleDelay:0,removeOnClick:!0,z:1e4},e}(Op);function DV(t,e){return C({brushType:t.brushType,brushMode:t.brushMode,transformable:t.transformable,brushStyle:new Sc(t.brushStyle).getItemStyle(),removeOnClick:t.removeOnClick,z:t.z},e,!0)}var AV=["rect","polygon","lineX","lineY","keep","clear"],kV=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t,e,n){var i,r,o;e.eachComponent({mainType:"brush"},(function(t){i=t.brushType,r=t.brushOption.brushMode||"single",o=o||!!t.areas.length})),this._brushType=i,this._brushMode=r,E(t.get("type",!0),(function(e){t.setIconStatus(e,("keep"===e?"multiple"===r:"clear"===e?o:e===i)?"emphasis":"normal")}))},e.prototype.updateView=function(t,e,n){this.render(t,e,n)},e.prototype.getIcons=function(){var t=this.model,e=t.get("icon",!0),n={};return E(t.get("type",!0),(function(t){e[t]&&(n[t]=e[t])})),n},e.prototype.onclick=function(t,e,n){var i=this._brushType,r=this._brushMode;"clear"===n?(e.dispatchAction({type:"axisAreaSelect",intervals:[]}),e.dispatchAction({type:"brush",command:"clear",areas:[]})):e.dispatchAction({type:"takeGlobalCursor",key:"brush",brushOption:{brushType:"keep"===n?i:i!==n&&n,brushMode:"keep"===n?"multiple"===r?"single":"multiple":r}})},e.getDefaultOption=function(t){return{show:!0,type:AV.slice(),icon:{rect:"M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13",polygon:"M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2",lineX:"M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4",lineY:"M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4",keep:"M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z",clear:"M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8 M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1 M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2"},title:t.getLocaleModel().get(["toolbox","brush","title"])}},e}(QE);var LV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.layoutMode={type:"box",ignoreSize:!0},n}return n(e,t),e.type="title",e.defaultOption={z:6,show:!0,text:"",target:"blank",subtext:"",subtarget:"blank",left:0,top:0,backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderWidth:0,padding:5,itemGap:10,textStyle:{fontSize:18,fontWeight:"bold",color:"#464646"},subtextStyle:{fontSize:12,color:"#6E7079"}},e}(Op),PV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){if(this.group.removeAll(),t.get("show")){var i=this.group,r=t.getModel("textStyle"),o=t.getModel("subtextStyle"),a=t.get("textAlign"),s=rt(t.get("textBaseline"),t.get("textVerticalAlign")),l=new Bs({style:ec(r,{text:t.get("text"),fill:r.getTextColor()},{disableBox:!0}),z2:10}),u=l.getBoundingRect(),h=t.get("subtext"),c=new Bs({style:ec(o,{text:h,fill:o.getTextColor(),y:u.height+t.get("itemGap"),verticalAlign:"top"},{disableBox:!0}),z2:10}),p=t.get("link"),d=t.get("sublink"),f=t.get("triggerEvent",!0);l.silent=!p&&!f,c.silent=!d&&!f,p&&l.on("click",(function(){_p(p,"_"+t.get("target"))})),d&&c.on("click",(function(){_p(d,"_"+t.get("subtarget"))})),Js(l).eventData=Js(c).eventData=f?{componentType:"title",componentIndex:t.componentIndex}:null,i.add(l),h&&i.add(c);var g=i.getBoundingRect(),y=t.getBoxLayoutParams();y.width=g.width,y.height=g.height;var v=Tp(y,{width:n.getWidth(),height:n.getHeight()},t.get("padding"));a||("middle"===(a=t.get("left")||t.get("right"))&&(a="center"),"right"===a?v.x+=v.width:"center"===a&&(v.x+=v.width/2)),s||("center"===(s=t.get("top")||t.get("bottom"))&&(s="middle"),"bottom"===s?v.y+=v.height:"middle"===s&&(v.y+=v.height/2),s=s||"top"),i.x=v.x,i.y=v.y,i.markRedraw();var m={align:a,verticalAlign:s};l.setStyle(m),c.setStyle(m),g=i.getBoundingRect();var x=v.margin,_=t.getItemStyle(["color","opacity"]);_.fill=t.get("backgroundColor");var b=new Es({shape:{x:g.x-x[3],y:g.y-x[0],width:g.width+x[1]+x[3],height:g.height+x[0]+x[2],r:t.get("borderRadius")},style:_,subPixelOptimize:!0,silent:!0});i.add(b)}},e.type="title",e}(wg);var OV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.layoutMode="box",n}return n(e,t),e.prototype.init=function(t,e,n){this.mergeDefaultAndTheme(t,n),this._initData()},e.prototype.mergeOption=function(e){t.prototype.mergeOption.apply(this,arguments),this._initData()},e.prototype.setCurrentIndex=function(t){null==t&&(t=this.option.currentIndex);var e=this._data.count();this.option.loop?t=(t%e+e)%e:(t>=e&&(t=e-1),t<0&&(t=0)),this.option.currentIndex=t},e.prototype.getCurrentIndex=function(){return this.option.currentIndex},e.prototype.isIndexMax=function(){return this.getCurrentIndex()>=this._data.count()-1},e.prototype.setPlayState=function(t){this.option.autoPlay=!!t},e.prototype.getPlayState=function(){return!!this.option.autoPlay},e.prototype._initData=function(){var t,e=this.option,n=e.data||[],i=e.axisType,r=this._names=[];"category"===i?(t=[],E(n,(function(e,n){var i,o=Do(So(e),"");q(e)?(i=T(e)).value=n:i=n,t.push(i),r.push(o)}))):t=n;var o={category:"ordinal",time:"time",value:"number"}[i]||"number";(this._data=new ex([{name:"value",type:o}],this)).initData(t,r)},e.prototype.getData=function(){return this._data},e.prototype.getCategories=function(){if("category"===this.get("axisType"))return this._names.slice()},e.type="timeline",e.defaultOption={z:4,show:!0,axisType:"time",realtime:!0,left:"20%",top:null,right:"20%",bottom:0,width:null,height:40,padding:5,controlPosition:"left",autoPlay:!1,rewind:!1,loop:!0,playInterval:2e3,currentIndex:0,itemStyle:{},label:{color:"#000"},data:[]},e}(Op),RV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="timeline.slider",e.defaultOption=Tc(OV.defaultOption,{backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderWidth:0,orient:"horizontal",inverse:!1,tooltip:{trigger:"item"},symbol:"circle",symbolSize:12,lineStyle:{show:!0,width:2,color:"#DAE1F5"},label:{position:"auto",show:!0,interval:"auto",rotate:0,color:"#A4B1D7"},itemStyle:{color:"#A4B1D7",borderWidth:1},checkpointStyle:{symbol:"circle",symbolSize:15,color:"#316bf3",borderColor:"#fff",borderWidth:2,shadowBlur:2,shadowOffsetX:1,shadowOffsetY:1,shadowColor:"rgba(0, 0, 0, 0.3)",animation:!0,animationDuration:300,animationEasing:"quinticInOut"},controlStyle:{show:!0,showPlayBtn:!0,showPrevBtn:!0,showNextBtn:!0,itemSize:24,itemGap:12,position:"left",playIcon:"path://M31.6,53C17.5,53,6,41.5,6,27.4S17.5,1.8,31.6,1.8C45.7,1.8,57.2,13.3,57.2,27.4S45.7,53,31.6,53z M31.6,3.3 C18.4,3.3,7.5,14.1,7.5,27.4c0,13.3,10.8,24.1,24.1,24.1C44.9,51.5,55.7,40.7,55.7,27.4C55.7,14.1,44.9,3.3,31.6,3.3z M24.9,21.3 c0-2.2,1.6-3.1,3.5-2l10.5,6.1c1.899,1.1,1.899,2.9,0,4l-10.5,6.1c-1.9,1.1-3.5,0.2-3.5-2V21.3z",stopIcon:"path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z",nextIcon:"M2,18.5A1.52,1.52,0,0,1,.92,18a1.49,1.49,0,0,1,0-2.12L7.81,9.36,1,3.11A1.5,1.5,0,1,1,3,.89l8,7.34a1.48,1.48,0,0,1,.49,1.09,1.51,1.51,0,0,1-.46,1.1L3,18.08A1.5,1.5,0,0,1,2,18.5Z",prevIcon:"M10,.5A1.52,1.52,0,0,1,11.08,1a1.49,1.49,0,0,1,0,2.12L4.19,9.64,11,15.89a1.5,1.5,0,1,1-2,2.22L1,10.77A1.48,1.48,0,0,1,.5,9.68,1.51,1.51,0,0,1,1,8.58L9,.92A1.5,1.5,0,0,1,10,.5Z",prevBtnSize:18,nextBtnSize:18,color:"#A4B1D7",borderColor:"#A4B1D7",borderWidth:1},emphasis:{label:{show:!0,color:"#6f778d"},itemStyle:{color:"#316BF3"},controlStyle:{color:"#316BF3",borderColor:"#316BF3",borderWidth:2}},progress:{lineStyle:{color:"#316BF3"},itemStyle:{color:"#316BF3"},label:{color:"#6f778d"}},data:[]}),e}(OV);R(RV,gf.prototype);var NV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="timeline",e}(wg),EV=function(t){function e(e,n,i,r){var o=t.call(this,e,n,i)||this;return o.type=r||"value",o}return n(e,t),e.prototype.getLabelModel=function(){return this.model.getModel("label")},e.prototype.isHorizontal=function(){return"horizontal"===this.model.get("orient")},e}(q_),zV=Math.PI,VV=Po(),BV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){this.api=e},e.prototype.render=function(t,e,n){if(this.model=t,this.api=n,this.ecModel=e,this.group.removeAll(),t.get("show",!0)){var i=this._layout(t,n),r=this._createGroup("_mainGroup"),o=this._createGroup("_labelGroup"),a=this._axis=this._createAxis(i,t);t.formatTooltip=function(t){return Qf("nameValue",{noName:!0,value:a.scale.getLabel({value:t})})},E(["AxisLine","AxisTick","Control","CurrentPointer"],(function(e){this["_render"+e](i,r,a,t)}),this),this._renderAxisLabel(i,o,a,t),this._position(i,t)}this._doPlayStop(),this._updateTicksStatus()},e.prototype.remove=function(){this._clearTimer(),this.group.removeAll()},e.prototype.dispose=function(){this._clearTimer()},e.prototype._layout=function(t,e){var n,i,r,o,a=t.get(["label","position"]),s=t.get("orient"),l=function(t,e){return Tp(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()},t.get("padding"))}(t,e),u={horizontal:"center",vertical:(n=null==a||"auto"===a?"horizontal"===s?l.y+l.height/2=0||"+"===n?"left":"right"},h={horizontal:n>=0||"+"===n?"top":"bottom",vertical:"middle"},c={horizontal:0,vertical:zV/2},p="vertical"===s?l.height:l.width,d=t.getModel("controlStyle"),f=d.get("show",!0),g=f?d.get("itemSize"):0,y=f?d.get("itemGap"):0,v=g+y,m=t.get(["label","rotate"])||0;m=m*zV/180;var x=d.get("position",!0),_=f&&d.get("showPlayBtn",!0),b=f&&d.get("showPrevBtn",!0),w=f&&d.get("showNextBtn",!0),S=0,M=p;"left"===x||"bottom"===x?(_&&(i=[0,0],S+=v),b&&(r=[S,0],S+=v),w&&(o=[M-g,0],M-=v)):(_&&(i=[M-g,0],M-=v),b&&(r=[0,0],S+=v),w&&(o=[M-g,0],M-=v));var I=[S,M];return t.get("inverse")&&I.reverse(),{viewRect:l,mainLength:p,orient:s,rotation:c[s],labelRotation:m,labelPosOpt:n,labelAlign:t.get(["label","align"])||u[s],labelBaseline:t.get(["label","verticalAlign"])||t.get(["label","baseline"])||h[s],playPosition:i,prevBtnPosition:r,nextBtnPosition:o,axisExtent:I,controlSize:g,controlGap:y}},e.prototype._position=function(t,e){var n=this._mainGroup,i=this._labelGroup,r=t.viewRect;if("vertical"===t.orient){var o=[1,0,0,1,0,0],a=r.x,s=r.y+r.height;be(o,o,[-a,-s]),we(o,o,-zV/2),be(o,o,[a,s]),(r=r.clone()).applyTransform(o)}var l=y(r),u=y(n.getBoundingRect()),h=y(i.getBoundingRect()),c=[n.x,n.y],p=[i.x,i.y];p[0]=c[0]=l[0][0];var d,f=t.labelPosOpt;null==f||X(f)?(v(c,u,l,1,d="+"===f?0:1),v(p,h,l,1,1-d)):(v(c,u,l,1,d=f>=0?0:1),p[1]=c[1]+f);function g(t){t.originX=l[0][0]-t.x,t.originY=l[1][0]-t.y}function y(t){return[[t.x,t.x+t.width],[t.y,t.y+t.height]]}function v(t,e,n,i,r){t[i]+=n[i][r]-e[i][r]}n.setPosition(c),i.setPosition(p),n.rotation=i.rotation=t.rotation,g(n),g(i)},e.prototype._createAxis=function(t,e){var n=e.getData(),i=e.get("axisType"),r=function(t,e){if(e=e||t.get("type"))switch(e){case"category":return new Mx({ordinalMeta:t.getCategories(),extent:[1/0,-1/0]});case"time":return new Fx({locale:t.ecModel.getLocaleModel(),useUTC:t.ecModel.get("useUTC")});default:return new Tx}}(e,i);r.getTicks=function(){return n.mapArray(["value"],(function(t){return{value:t}}))};var o=n.getDataExtent("value");r.setExtent(o[0],o[1]),r.calcNiceTicks();var a=new EV("value",r,t.axisExtent,i);return a.model=e,a},e.prototype._createGroup=function(t){var e=this[t]=new Er;return this.group.add(e),e},e.prototype._renderAxisLine=function(t,e,n,i){var r=n.getExtent();if(i.get(["lineStyle","show"])){var o=new Xu({shape:{x1:r[0],y1:0,x2:r[1],y2:0},style:A({lineCap:"round"},i.getModel("lineStyle").getLineStyle()),silent:!0,z2:1});e.add(o);var a=this._progressLine=new Xu({shape:{x1:r[0],x2:this._currentPointer?this._currentPointer.x:r[0],y1:0,y2:0},style:k({lineCap:"round",lineWidth:o.style.lineWidth},i.getModel(["progress","lineStyle"]).getLineStyle()),silent:!0,z2:1});e.add(a)}},e.prototype._renderAxisTick=function(t,e,n,i){var r=this,o=i.getData(),a=n.scale.getTicks();this._tickSymbols=[],E(a,(function(t){var a=n.dataToCoord(t.value),s=o.getItemModel(t.value),l=s.getModel("itemStyle"),u=s.getModel(["emphasis","itemStyle"]),h=s.getModel(["progress","itemStyle"]),c={x:a,y:0,onclick:W(r._changeTimeline,r,t.value)},p=FV(s,l,e,c);p.ensureState("emphasis").style=u.getItemStyle(),p.ensureState("progress").style=h.getItemStyle(),Wl(p);var d=Js(p);s.get("tooltip")?(d.dataIndex=t.value,d.dataModel=i):d.dataIndex=d.dataModel=null,r._tickSymbols.push(p)}))},e.prototype._renderAxisLabel=function(t,e,n,i){var r=this;if(n.getLabelModel().get("show")){var o=i.getData(),a=n.getViewLabels();this._tickLabels=[],E(a,(function(i){var a=i.tickValue,s=o.getItemModel(a),l=s.getModel("label"),u=s.getModel(["emphasis","label"]),h=s.getModel(["progress","label"]),c=n.dataToCoord(i.tickValue),p=new Bs({x:c,y:0,rotation:t.labelRotation-t.rotation,onclick:W(r._changeTimeline,r,a),silent:!1,style:ec(l,{text:i.formattedLabel,align:t.labelAlign,verticalAlign:t.labelBaseline})});p.ensureState("emphasis").style=ec(u),p.ensureState("progress").style=ec(h),e.add(p),Wl(p),VV(p).dataIndex=a,r._tickLabels.push(p)}))}},e.prototype._renderControl=function(t,e,n,i){var r=t.controlSize,o=t.rotation,a=i.getModel("controlStyle").getItemStyle(),s=i.getModel(["emphasis","controlStyle"]).getItemStyle(),l=i.getPlayState(),u=i.get("inverse",!0);function h(t,n,l,u){if(t){var h=Mr(rt(i.get(["controlStyle",n+"BtnSize"]),r),r),c=function(t,e,n,i){var r=i.style,o=Wh(t.get(["controlStyle",e]),i||{},new Ee(n[0],n[1],n[2],n[3]));r&&o.setStyle(r);return o}(i,n+"Icon",[0,-h/2,h,h],{x:t[0],y:t[1],originX:r/2,originY:0,rotation:u?-o:0,rectHover:!0,style:a,onclick:l});c.ensureState("emphasis").style=s,e.add(c),Wl(c)}}h(t.nextBtnPosition,"next",W(this._changeTimeline,this,u?"-":"+")),h(t.prevBtnPosition,"prev",W(this._changeTimeline,this,u?"+":"-")),h(t.playPosition,l?"stop":"play",W(this._handlePlayClick,this,!l),!0)},e.prototype._renderCurrentPointer=function(t,e,n,i){var r=i.getData(),o=i.getCurrentIndex(),a=r.getItemModel(o).getModel("checkpointStyle"),s=this,l={onCreate:function(t){t.draggable=!0,t.drift=W(s._handlePointerDrag,s),t.ondragend=W(s._handlePointerDragend,s),GV(t,s._progressLine,o,n,i,!0)},onUpdate:function(t){GV(t,s._progressLine,o,n,i)}};this._currentPointer=FV(a,a,this._mainGroup,{},this._currentPointer,l)},e.prototype._handlePlayClick=function(t){this._clearTimer(),this.api.dispatchAction({type:"timelinePlayChange",playState:t,from:this.uid})},e.prototype._handlePointerDrag=function(t,e,n){this._clearTimer(),this._pointerChangeTimeline([n.offsetX,n.offsetY])},e.prototype._handlePointerDragend=function(t){this._pointerChangeTimeline([t.offsetX,t.offsetY],!0)},e.prototype._pointerChangeTimeline=function(t,e){var n=this._toAxisCoord(t)[0],i=Zr(this._axis.getExtent().slice());n>i[1]&&(n=i[1]),n=0&&(a[o]=+a[o].toFixed(c)),[a,h]}var JV={min:H($V,"min"),max:H($V,"max"),average:H($V,"average"),median:H($V,"median")};function QV(t,e){if(e){var n=t.getData(),i=t.coordinateSystem,r=i.dimensions;if(!function(t){return!isNaN(parseFloat(t.x))&&!isNaN(parseFloat(t.y))}(e)&&!Y(e.coord)&&i){var o=tB(e,n,i,t);if((e=T(e)).type&&JV[e.type]&&o.baseAxis&&o.valueAxis){var a=P(r,o.baseAxis.dim),s=P(r,o.valueAxis.dim),l=JV[e.type](n,o.baseDataDim,o.valueDataDim,a,s);e.coord=l[0],e.value=l[1]}else e.coord=[null!=e.xAxis?e.xAxis:e.radiusAxis,null!=e.yAxis?e.yAxis:e.angleAxis]}if(null==e.coord)e.coord=[];else for(var u=e.coord,h=0;h<2;h++)JV[u[h]]&&(u[h]=iB(n,n.mapDimension(r[h]),u[h]));return e}}function tB(t,e,n,i){var r={};return null!=t.valueIndex||null!=t.valueDim?(r.valueDataDim=null!=t.valueIndex?e.getDimension(t.valueIndex):t.valueDim,r.valueAxis=n.getAxis(function(t,e){var n=t.getData().getDimensionInfo(e);return n&&n.coordDim}(i,r.valueDataDim)),r.baseAxis=n.getOtherAxis(r.valueAxis),r.baseDataDim=e.mapDimension(r.baseAxis.dim)):(r.baseAxis=i.getBaseAxis(),r.valueAxis=n.getOtherAxis(r.baseAxis),r.baseDataDim=e.mapDimension(r.baseAxis.dim),r.valueDataDim=e.mapDimension(r.valueAxis.dim)),r}function eB(t,e){return!(t&&t.containData&&e.coord&&!KV(e))||t.containData(e.coord)}function nB(t,e){return t?function(t,n,i,r){return _f(r<2?t.coord&&t.coord[r]:t.value,e[r])}:function(t,n,i,r){return _f(t.value,e[r])}}function iB(t,e,n){if("average"===n){var i=0,r=0;return t.each(e,(function(t,e){isNaN(t)||(i+=t,r++)})),i/r}return"median"===n?t.getMedian(e):t.getDataExtent(e)["max"===n?1:0]}var rB=Po(),oB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(){this.markerGroupMap=yt()},e.prototype.render=function(t,e,n){var i=this,r=this.markerGroupMap;r.each((function(t){rB(t).keep=!1})),e.eachSeries((function(t){var r=jV.getMarkerModelFromSeries(t,i.type);r&&i.renderSeries(t,r,e,n)})),r.each((function(t){!rB(t).keep&&i.group.remove(t.group)}))},e.prototype.markKeep=function(t){rB(t).keep=!0},e.prototype.toggleBlurSeries=function(t,e){var n=this;E(t,(function(t){var i=jV.getMarkerModelFromSeries(t,n.type);i&&i.getData().eachItemGraphicEl((function(t){t&&(e?Ll(t):Pl(t))}))}))},e.type="marker",e}(wg);function aB(t,e,n){var i=e.coordinateSystem;t.each((function(r){var o,a=t.getItemModel(r),s=Ur(a.get("x"),n.getWidth()),l=Ur(a.get("y"),n.getHeight());if(isNaN(s)||isNaN(l)){if(e.getMarkerPosition)o=e.getMarkerPosition(t.getValues(t.dimensions,r));else if(i){var u=t.get(i.dimensions[0],r),h=t.get(i.dimensions[1],r);o=i.dataToPoint([u,h])}}else o=[s,l];isNaN(s)||(o[0]=s),isNaN(l)||(o[1]=l),t.setItemLayout(r,o)}))}var sB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.updateTransform=function(t,e,n){e.eachSeries((function(t){var e=jV.getMarkerModelFromSeries(t,"markPoint");e&&(aB(e.getData(),t,n),this.markerGroupMap.get(t.id).updateLayout())}),this)},e.prototype.renderSeries=function(t,e,n,i){var r=t.coordinateSystem,o=t.id,a=t.getData(),s=this.markerGroupMap,l=s.get(o)||s.set(o,new iS),u=function(t,e,n){var i;i=t?z(t&&t.dimensions,(function(t){return A(A({},e.getData().getDimensionInfo(e.getData().mapDimension(t))||{}),{name:t,ordinalMeta:null})})):[{name:"value",type:"float"}];var r=new ex(i,n),o=z(n.get("data"),H(QV,e));t&&(o=B(o,H(eB,t)));var a=nB(!!t,i);return r.initData(o,null,a),r}(r,t,e);e.setData(u),aB(e.getData(),t,i),u.each((function(t){var n=u.getItemModel(t),i=n.getShallow("symbol"),r=n.getShallow("symbolSize"),o=n.getShallow("symbolRotate"),s=n.getShallow("symbolOffset"),l=n.getShallow("symbolKeepAspect");if(U(i)||U(r)||U(o)||U(s)){var h=e.getRawValue(t),c=e.getDataParams(t);U(i)&&(i=i(h,c)),U(r)&&(r=r(h,c)),U(o)&&(o=o(h,c)),U(s)&&(s=s(h,c))}var p=n.getModel("itemStyle").getItemStyle(),d=wy(a,"color");p.fill||(p.fill=d),u.setItemVisual(t,{symbol:i,symbolSize:r,symbolRotate:o,symbolOffset:s,symbolKeepAspect:l,style:p})})),l.updateData(u),this.group.add(l.group),u.eachItemGraphicEl((function(t){t.traverse((function(t){Js(t).dataModel=e}))})),this.markKeep(l),l.group.silent=e.get("silent")||t.get("silent")},e.type="markPoint",e}(oB);var lB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.createMarkerModelFromSeries=function(t,n,i){return new e(t,n,i)},e.type="markLine",e.defaultOption={z:5,symbol:["circle","arrow"],symbolSize:[8,16],symbolOffset:0,precision:2,tooltip:{trigger:"item"},label:{show:!0,position:"end",distance:5},lineStyle:{type:"dashed"},emphasis:{label:{show:!0},lineStyle:{width:3}},animationEasing:"linear"},e}(jV),uB=Po(),hB=function(t,e,n,i){var r,o=t.getData();if(Y(i))r=i;else{var a=i.type;if("min"===a||"max"===a||"average"===a||"median"===a||null!=i.xAxis||null!=i.yAxis){var s=void 0,l=void 0;if(null!=i.yAxis||null!=i.xAxis)s=e.getAxis(null!=i.yAxis?"y":"x"),l=it(i.yAxis,i.xAxis);else{var u=tB(i,o,e,t);s=u.valueAxis,l=iB(o,ux(o,u.valueDataDim),a)}var h="x"===s.dim?0:1,c=1-h,p=T(i),d={coord:[]};p.type=null,p.coord=[],p.coord[c]=-1/0,d.coord[c]=1/0;var f=n.get("precision");f>=0&&j(l)&&(l=+l.toFixed(Math.min(f,20))),p.coord[h]=d.coord[h]=l,r=[p,d,{type:a,valueIndex:i.valueIndex,value:l}]}else r=[]}var g=[QV(t,r[0]),QV(t,r[1]),A({},r[2])];return g[2].type=g[2].type||null,C(g[2],g[0]),C(g[2],g[1]),g};function cB(t){return!isNaN(t)&&!isFinite(t)}function pB(t,e,n,i){var r=1-t,o=i.dimensions[t];return cB(e[r])&&cB(n[r])&&e[t]===n[t]&&i.getAxis(o).containData(e[t])}function dB(t,e){if("cartesian2d"===t.type){var n=e[0].coord,i=e[1].coord;if(n&&i&&(pB(1,n,i,t)||pB(0,n,i,t)))return!0}return eB(t,e[0])&&eB(t,e[1])}function fB(t,e,n,i,r){var o,a=i.coordinateSystem,s=t.getItemModel(e),l=Ur(s.get("x"),r.getWidth()),u=Ur(s.get("y"),r.getHeight());if(isNaN(l)||isNaN(u)){if(i.getMarkerPosition)o=i.getMarkerPosition(t.getValues(t.dimensions,e));else{var h=a.dimensions,c=t.get(h[0],e),p=t.get(h[1],e);o=a.dataToPoint([c,p])}if(vS(a,"cartesian2d")){var d=a.getAxis("x"),f=a.getAxis("y");h=a.dimensions;cB(t.get(h[0],e))?o[0]=d.toGlobalCoord(d.getExtent()[n?0:1]):cB(t.get(h[1],e))&&(o[1]=f.toGlobalCoord(f.getExtent()[n?0:1]))}isNaN(l)||(o[0]=l),isNaN(u)||(o[1]=u)}else o=[l,u];t.setItemLayout(e,o)}var gB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.updateTransform=function(t,e,n){e.eachSeries((function(t){var e=jV.getMarkerModelFromSeries(t,"markLine");if(e){var i=e.getData(),r=uB(e).from,o=uB(e).to;r.each((function(e){fB(r,e,!0,t,n),fB(o,e,!1,t,n)})),i.each((function(t){i.setItemLayout(t,[r.getItemLayout(t),o.getItemLayout(t)])})),this.markerGroupMap.get(t.id).updateLayout()}}),this)},e.prototype.renderSeries=function(t,e,n,i){var r=t.coordinateSystem,o=t.id,a=t.getData(),s=this.markerGroupMap,l=s.get(o)||s.set(o,new TA);this.group.add(l.group);var u=function(t,e,n){var i;i=t?z(t&&t.dimensions,(function(t){return A(A({},e.getData().getDimensionInfo(e.getData().mapDimension(t))||{}),{name:t,ordinalMeta:null})})):[{name:"value",type:"float"}];var r=new ex(i,n),o=new ex(i,n),a=new ex([],n),s=z(n.get("data"),H(hB,e,t,n));t&&(s=B(s,H(dB,t)));var l=nB(!!t,i);return r.initData(z(s,(function(t){return t[0]})),null,l),o.initData(z(s,(function(t){return t[1]})),null,l),a.initData(z(s,(function(t){return t[2]}))),a.hasItemOption=!0,{from:r,to:o,line:a}}(r,t,e),h=u.from,c=u.to,p=u.line;uB(e).from=h,uB(e).to=c,e.setData(p);var d=e.get("symbol"),f=e.get("symbolSize"),g=e.get("symbolRotate"),y=e.get("symbolOffset");function v(e,n,r){var o=e.getItemModel(n);fB(e,n,r,t,i);var s=o.getModel("itemStyle").getItemStyle();null==s.fill&&(s.fill=wy(a,"color")),e.setItemVisual(n,{symbolKeepAspect:o.get("symbolKeepAspect"),symbolOffset:rt(o.get("symbolOffset",!0),y[r?0:1]),symbolRotate:rt(o.get("symbolRotate",!0),g[r?0:1]),symbolSize:rt(o.get("symbolSize"),f[r?0:1]),symbol:rt(o.get("symbol",!0),d[r?0:1]),style:s})}Y(d)||(d=[d,d]),Y(f)||(f=[f,f]),Y(g)||(g=[g,g]),Y(y)||(y=[y,y]),u.from.each((function(t){v(h,t,!0),v(c,t,!1)})),p.each((function(t){var e=p.getItemModel(t).getModel("lineStyle").getLineStyle();p.setItemLayout(t,[h.getItemLayout(t),c.getItemLayout(t)]),null==e.stroke&&(e.stroke=h.getItemVisual(t,"style").fill),p.setItemVisual(t,{fromSymbolKeepAspect:h.getItemVisual(t,"symbolKeepAspect"),fromSymbolOffset:h.getItemVisual(t,"symbolOffset"),fromSymbolRotate:h.getItemVisual(t,"symbolRotate"),fromSymbolSize:h.getItemVisual(t,"symbolSize"),fromSymbol:h.getItemVisual(t,"symbol"),toSymbolKeepAspect:c.getItemVisual(t,"symbolKeepAspect"),toSymbolOffset:c.getItemVisual(t,"symbolOffset"),toSymbolRotate:c.getItemVisual(t,"symbolRotate"),toSymbolSize:c.getItemVisual(t,"symbolSize"),toSymbol:c.getItemVisual(t,"symbol"),style:e})})),l.updateData(p),u.line.eachItemGraphicEl((function(t){Js(t).dataModel=e,t.traverse((function(t){Js(t).dataModel=e}))})),this.markKeep(l),l.group.silent=e.get("silent")||t.get("silent")},e.type="markLine",e}(oB);var yB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.createMarkerModelFromSeries=function(t,n,i){return new e(t,n,i)},e.type="markArea",e.defaultOption={z:1,tooltip:{trigger:"item"},animation:!1,label:{show:!0,position:"top"},itemStyle:{borderWidth:0},emphasis:{label:{show:!0,position:"top"}}},e}(jV),vB=Po(),mB=function(t,e,n,i){var r=i[0],o=i[1];if(r&&o){var a=QV(t,r),s=QV(t,o),l=a.coord,u=s.coord;l[0]=it(l[0],-1/0),l[1]=it(l[1],-1/0),u[0]=it(u[0],1/0),u[1]=it(u[1],1/0);var h=D([{},a,s]);return h.coord=[a.coord,s.coord],h.x0=a.x,h.y0=a.y,h.x1=s.x,h.y1=s.y,h}};function xB(t){return!isNaN(t)&&!isFinite(t)}function _B(t,e,n,i){var r=1-t;return xB(e[r])&&xB(n[r])}function bB(t,e){var n=e.coord[0],i=e.coord[1],r={coord:n,x:e.x0,y:e.y0},o={coord:i,x:e.x1,y:e.y1};return vS(t,"cartesian2d")?!(!n||!i||!_B(1,n,i)&&!_B(0,n,i))||function(t,e,n){return!(t&&t.containZone&&e.coord&&n.coord&&!KV(e)&&!KV(n))||t.containZone(e.coord,n.coord)}(t,r,o):eB(t,r)||eB(t,o)}function wB(t,e,n,i,r){var o,a=i.coordinateSystem,s=t.getItemModel(e),l=Ur(s.get(n[0]),r.getWidth()),u=Ur(s.get(n[1]),r.getHeight());if(isNaN(l)||isNaN(u)){if(i.getMarkerPosition){var h=t.getValues(["x0","y0"],e),c=t.getValues(["x1","y1"],e),p=a.clampData(h),d=a.clampData(c),f=[];"x0"===n[0]?f[0]=p[0]>d[0]?c[0]:h[0]:f[0]=p[0]>d[0]?h[0]:c[0],"y0"===n[1]?f[1]=p[1]>d[1]?c[1]:h[1]:f[1]=p[1]>d[1]?h[1]:c[1],o=i.getMarkerPosition(f,n,!0)}else{var g=[m=t.get(n[0],e),x=t.get(n[1],e)];a.clampData&&a.clampData(g,g),o=a.dataToPoint(g,!0)}if(vS(a,"cartesian2d")){var y=a.getAxis("x"),v=a.getAxis("y"),m=t.get(n[0],e),x=t.get(n[1],e);xB(m)?o[0]=y.toGlobalCoord(y.getExtent()["x0"===n[0]?0:1]):xB(x)&&(o[1]=v.toGlobalCoord(v.getExtent()["y0"===n[1]?0:1]))}isNaN(l)||(o[0]=l),isNaN(u)||(o[1]=u)}else o=[l,u];return o}var SB=[["x0","y0"],["x1","y0"],["x1","y1"],["x0","y1"]],MB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.updateTransform=function(t,e,n){e.eachSeries((function(t){var e=jV.getMarkerModelFromSeries(t,"markArea");if(e){var i=e.getData();i.each((function(e){var r=z(SB,(function(r){return wB(i,e,r,t,n)}));i.setItemLayout(e,r),i.getItemGraphicEl(e).setShape("points",r)}))}}),this)},e.prototype.renderSeries=function(t,e,n,i){var r=t.coordinateSystem,o=t.id,a=t.getData(),s=this.markerGroupMap,l=s.get(o)||s.set(o,{group:new Er});this.group.add(l.group),this.markKeep(l);var u=function(t,e,n){var i,r,o=["x0","y0","x1","y1"];if(t){var a=z(t&&t.dimensions,(function(t){var n=e.getData();return A(A({},n.getDimensionInfo(n.mapDimension(t))||{}),{name:t,ordinalMeta:null})}));r=z(o,(function(t,e){return{name:t,type:a[e%2].type}})),i=new ex(r,n)}else i=new ex(r=[{name:"value",type:"float"}],n);var s=z(n.get("data"),H(mB,e,t,n));t&&(s=B(s,H(bB,t)));var l=t?function(t,e,n,i){return _f(t.coord[Math.floor(i/2)][i%2],r[i])}:function(t,e,n,i){return _f(t.value,r[i])};return i.initData(s,null,l),i.hasItemOption=!0,i}(r,t,e);e.setData(u),u.each((function(e){var n=z(SB,(function(n){return wB(u,e,n,t,i)})),o=r.getAxis("x").scale,s=r.getAxis("y").scale,l=o.getExtent(),h=s.getExtent(),c=[o.parse(u.get("x0",e)),o.parse(u.get("x1",e))],p=[s.parse(u.get("y0",e)),s.parse(u.get("y1",e))];Zr(c),Zr(p);var d=!!(l[0]>c[1]||l[1]p[1]||h[1]=0},e.prototype.getOrient=function(){return"vertical"===this.get("orient")?{index:1,name:"vertical"}:{index:0,name:"horizontal"}},e.type="legend.plain",e.dependencies=["series"],e.defaultOption={z:4,show:!0,orient:"horizontal",left:"center",top:0,align:"auto",backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderRadius:0,borderWidth:0,padding:5,itemGap:10,itemWidth:25,itemHeight:14,symbolRotate:"inherit",symbolKeepAspect:!0,inactiveColor:"#ccc",inactiveBorderColor:"#ccc",inactiveBorderWidth:"auto",itemStyle:{color:"inherit",opacity:"inherit",borderColor:"inherit",borderWidth:"auto",borderCap:"inherit",borderJoin:"inherit",borderDashOffset:"inherit",borderMiterLimit:"inherit"},lineStyle:{width:"auto",color:"inherit",inactiveColor:"#ccc",inactiveWidth:2,opacity:"inherit",type:"inherit",cap:"inherit",join:"inherit",dashOffset:"inherit",miterLimit:"inherit"},textStyle:{color:"#333"},selectedMode:!0,selector:!1,selectorLabel:{show:!0,borderRadius:10,padding:[3,5,3,5],fontSize:12,fontFamily:"sans-serif",color:"#666",borderWidth:1,borderColor:"#666"},emphasis:{selectorLabel:{show:!0,color:"#eee",backgroundColor:"#666"}},selectorPosition:"auto",selectorItemGap:7,selectorButtonGap:10,tooltip:{show:!1}},e}(Op),TB=H,CB=E,DB=Er,AB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.newlineDisabled=!1,n}return n(e,t),e.prototype.init=function(){this.group.add(this._contentGroup=new DB),this.group.add(this._selectorGroup=new DB),this._isFirstRender=!0},e.prototype.getContentGroup=function(){return this._contentGroup},e.prototype.getSelectorGroup=function(){return this._selectorGroup},e.prototype.render=function(t,e,n){var i=this._isFirstRender;if(this._isFirstRender=!1,this.resetInner(),t.get("show",!0)){var r=t.get("align"),o=t.get("orient");r&&"auto"!==r||(r="right"===t.get("left")&&"vertical"===o?"right":"left");var a=t.get("selector",!0),s=t.get("selectorPosition",!0);!a||s&&"auto"!==s||(s="horizontal"===o?"end":"start"),this.renderInner(r,t,e,n,a,o,s);var l=t.getBoxLayoutParams(),u={width:n.getWidth(),height:n.getHeight()},h=t.get("padding"),c=Tp(l,u,h),p=this.layoutInner(t,r,c,i,a,s),d=Tp(k({width:p.width,height:p.height},l),u,h);this.group.x=d.x-p.x,this.group.y=d.y-p.y,this.group.markRedraw(),this.group.add(this._backgroundEl=rz(p,t))}},e.prototype.resetInner=function(){this.getContentGroup().removeAll(),this._backgroundEl&&this.group.remove(this._backgroundEl),this.getSelectorGroup().removeAll()},e.prototype.renderInner=function(t,e,n,i,r,o,a){var s=this.getContentGroup(),l=yt(),u=e.get("selectedMode"),h=[];n.eachRawSeries((function(t){!t.get("legendHoverLink")&&h.push(t.id)})),CB(e.getData(),(function(r,o){var a=r.get("name");if(!this.newlineDisabled&&(""===a||"\n"===a)){var c=new DB;return c.newline=!0,void s.add(c)}var p=n.getSeriesByName(a)[0];if(!l.get(a)){if(p){var d=p.getData(),f=d.getVisual("legendLineStyle")||{},g=d.getVisual("legendIcon"),y=d.getVisual("style");this._createItem(p,a,o,r,e,t,f,y,g,u,i).on("click",TB(kB,a,null,i,h)).on("mouseover",TB(PB,p.name,null,i,h)).on("mouseout",TB(OB,p.name,null,i,h)),l.set(a,!0)}else n.eachRawSeries((function(n){if(!l.get(a)&&n.legendVisualProvider){var s=n.legendVisualProvider;if(!s.containName(a))return;var c=s.indexOfName(a),p=s.getItemVisual(c,"style"),d=s.getItemVisual(c,"legendIcon"),f=jn(p.fill);f&&0===f[3]&&(f[3]=.2,p=A(A({},p),{fill:ii(f,"rgba")})),this._createItem(n,a,o,r,e,t,{},p,d,u,i).on("click",TB(kB,null,a,i,h)).on("mouseover",TB(PB,null,a,i,h)).on("mouseout",TB(OB,null,a,i,h)),l.set(a,!0)}}),this);0}}),this),r&&this._createSelector(r,e,i,o,a)},e.prototype._createSelector=function(t,e,n,i,r){var o=this.getSelectorGroup();CB(t,(function(t){var i=t.type,r=new Bs({style:{x:0,y:0,align:"center",verticalAlign:"middle"},onclick:function(){n.dispatchAction({type:"all"===i?"legendAllSelect":"legendInverseSelect"})}});o.add(r),Qh(r,{normal:e.getModel("selectorLabel"),emphasis:e.getModel(["emphasis","selectorLabel"])},{defaultText:t.title}),Wl(r)}))},e.prototype._createItem=function(t,e,n,i,r,o,a,s,l,u,h){var c=t.visualDrawType,p=r.get("itemWidth"),d=r.get("itemHeight"),f=r.isSelected(e),g=i.get("symbolRotate"),y=i.get("symbolKeepAspect"),v=i.get("icon"),m=function(t,e,n,i,r,o,a){function s(t,e){"auto"===t.lineWidth&&(t.lineWidth=e.lineWidth>0?2:0),CB(t,(function(n,i){"inherit"===t[i]&&(t[i]=e[i])}))}var l=e.getModel("itemStyle"),u=l.getItemStyle(),h=0===t.lastIndexOf("empty",0)?"fill":"stroke",c=l.getShallow("decal");u.decal=c&&"inherit"!==c?cv(c,a):i.decal,"inherit"===u.fill&&(u.fill=i[r]);"inherit"===u.stroke&&(u.stroke=i[h]);"inherit"===u.opacity&&(u.opacity=("fill"===r?i:n).opacity);s(u,i);var p=e.getModel("lineStyle"),d=p.getLineStyle();if(s(d,n),"auto"===u.fill&&(u.fill=i.fill),"auto"===u.stroke&&(u.stroke=i.fill),"auto"===d.stroke&&(d.stroke=i.fill),!o){var f=e.get("inactiveBorderWidth"),g=u[h];u.lineWidth="auto"===f?i.lineWidth>0&&g?2:0:u.lineWidth,u.fill=e.get("inactiveColor"),u.stroke=e.get("inactiveBorderColor"),d.stroke=p.get("inactiveColor"),d.lineWidth=p.get("inactiveWidth")}return{itemStyle:u,lineStyle:d}}(l=v||l||"roundRect",i,a,s,c,f,h),x=new DB,_=i.getModel("textStyle");if(!U(t.getLegendIcon)||v&&"inherit"!==v){var b="inherit"===v&&t.getData().getVisual("symbol")?"inherit"===g?t.getData().getVisual("symbolRotate"):g:0;x.add(function(t){var e=t.icon||"roundRect",n=Vy(e,0,0,t.itemWidth,t.itemHeight,t.itemStyle.fill,t.symbolKeepAspect);n.setStyle(t.itemStyle),n.rotation=(t.iconRotate||0)*Math.PI/180,n.setOrigin([t.itemWidth/2,t.itemHeight/2]),e.indexOf("empty")>-1&&(n.style.stroke=n.style.fill,n.style.fill="#fff",n.style.lineWidth=2);return n}({itemWidth:p,itemHeight:d,icon:l,iconRotate:b,itemStyle:m.itemStyle,lineStyle:m.lineStyle,symbolKeepAspect:y}))}else x.add(t.getLegendIcon({itemWidth:p,itemHeight:d,icon:l,iconRotate:g,itemStyle:m.itemStyle,lineStyle:m.lineStyle,symbolKeepAspect:y}));var w="left"===o?p+5:-5,S=o,M=r.get("formatter"),I=e;X(M)&&M?I=M.replace("{name}",null!=e?e:""):U(M)&&(I=M(e));var T=i.get("inactiveColor");x.add(new Bs({style:ec(_,{text:I,x:w,y:d/2,fill:f?_.getTextColor():T,align:S,verticalAlign:"middle"})}));var C=new Es({shape:x.getBoundingRect(),invisible:!0}),D=i.getModel("tooltip");return D.get("show")&&Xh({el:C,componentModel:r,itemName:e,itemTooltipOption:D.option}),x.add(C),x.eachChild((function(t){t.silent=!0})),C.silent=!u,this.getContentGroup().add(x),Wl(x),x.__legendDataIndex=n,x},e.prototype.layoutInner=function(t,e,n,i,r,o){var a=this.getContentGroup(),s=this.getSelectorGroup();Ip(t.get("orient"),a,t.get("itemGap"),n.width,n.height);var l=a.getBoundingRect(),u=[-l.x,-l.y];if(s.markRedraw(),a.markRedraw(),r){Ip("horizontal",s,t.get("selectorItemGap",!0));var h=s.getBoundingRect(),c=[-h.x,-h.y],p=t.get("selectorButtonGap",!0),d=t.getOrient().index,f=0===d?"width":"height",g=0===d?"height":"width",y=0===d?"y":"x";"end"===o?c[d]+=l[f]+p:u[d]+=h[f]+p,c[1-d]+=l[g]/2-h[g]/2,s.x=c[0],s.y=c[1],a.x=u[0],a.y=u[1];var v={x:0,y:0};return v[f]=l[f]+p+h[f],v[g]=Math.max(l[g],h[g]),v[y]=Math.min(0,h[y]+c[1-d]),v}return a.x=u[0],a.y=u[1],this.group.getBoundingRect()},e.prototype.remove=function(){this.getContentGroup().removeAll(),this._isFirstRender=!0},e.type="legend.plain",e}(wg);function kB(t,e,n,i){OB(t,e,n,i),n.dispatchAction({type:"legendToggleSelect",name:null!=t?t:e}),PB(t,e,n,i)}function LB(t){for(var e,n=t.getZr().storage.getDisplayList(),i=0,r=n.length;in[r],f=[-c.x,-c.y];e||(f[i]=l[s]);var g=[0,0],y=[-p.x,-p.y],v=rt(t.get("pageButtonGap",!0),t.get("itemGap",!0));d&&("end"===t.get("pageButtonPosition",!0)?y[i]+=n[r]-p[r]:g[i]+=p[r]+v);y[1-i]+=c[o]/2-p[o]/2,l.setPosition(f),u.setPosition(g),h.setPosition(y);var m={x:0,y:0};if(m[r]=d?n[r]:c[r],m[o]=Math.max(c[o],p[o]),m[a]=Math.min(0,p[a]+y[1-i]),u.__rectSize=n[r],d){var x={x:0,y:0};x[r]=Math.max(n[r]-p[r]-v,0),x[o]=m[o],u.setClipPath(new Es({shape:x})),u.__rectSize=x[r]}else h.eachChild((function(t){t.attr({invisible:!0,silent:!0})}));var _=this._getPageInfo(t);return null!=_.pageIndex&&dh(l,{x:_.contentPosition[0],y:_.contentPosition[1]},d?t:null),this._updatePageInfoView(t,_),m},e.prototype._pageGo=function(t,e,n){var i=this._getPageInfo(e)[t];null!=i&&n.dispatchAction({type:"legendScroll",scrollDataIndex:i,legendId:e.id})},e.prototype._updatePageInfoView=function(t,e){var n=this._controllerGroup;E(["pagePrev","pageNext"],(function(i){var r=null!=e[i+"DataIndex"],o=n.childOfName(i);o&&(o.setStyle("fill",r?t.get("pageIconColor",!0):t.get("pageIconInactiveColor",!0)),o.cursor=r?"pointer":"default")}));var i=n.childOfName("pageText"),r=t.get("pageFormatter"),o=e.pageIndex,a=null!=o?o+1:0,s=e.pageCount;i&&r&&i.setStyle("text",X(r)?r.replace("{current}",null==a?"":a+"").replace("{total}",null==s?"":s+""):r({current:a,total:s}))},e.prototype._getPageInfo=function(t){var e=t.get("scrollDataIndex",!0),n=this.getContentGroup(),i=this._containerGroup.__rectSize,r=t.getOrient().index,o=FB[r],a=GB[r],s=this._findTargetItemIndex(e),l=n.children(),u=l[s],h=l.length,c=h?1:0,p={contentPosition:[n.x,n.y],pageCount:c,pageIndex:c-1,pagePrevDataIndex:null,pageNextDataIndex:null};if(!u)return p;var d=m(u);p.contentPosition[r]=-d.s;for(var f=s+1,g=d,y=d,v=null;f<=h;++f)(!(v=m(l[f]))&&y.e>g.s+i||v&&!x(v,g.s))&&(g=y.i>g.i?y:v)&&(null==p.pageNextDataIndex&&(p.pageNextDataIndex=g.i),++p.pageCount),y=v;for(f=s-1,g=d,y=d,v=null;f>=-1;--f)(v=m(l[f]))&&x(y,v.s)||!(g.i=e&&t.s<=e+i}},e.prototype._findTargetItemIndex=function(t){return this._showController?(this.getContentGroup().eachChild((function(i,r){var o=i.__legendDataIndex;null==n&&null!=o&&(n=r),o===t&&(e=r)})),null!=e?e:n):0;var e,n},e.type="legend.scroll",e}(AB);function HB(t){Dm(EB),t.registerComponentModel(zB),t.registerComponentView(WB),function(t){t.registerAction("legendScroll","legendscroll",(function(t,e){var n=t.scrollDataIndex;null!=n&&e.eachComponent({mainType:"legend",subType:"scroll",query:t},(function(t){t.setScrollDataIndex(n)}))}))}(t)}var YB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="dataZoom.inside",e.defaultOption=Tc(GE.defaultOption,{disabled:!1,zoomLock:!1,zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!1,preventDefaultMouseMove:!0}),e}(GE),UB=Po();function XB(t,e,n){UB(t).coordSysRecordMap.each((function(t){var i=t.dataZoomInfoMap.get(e.uid);i&&(i.getRange=n)}))}function ZB(t,e){if(e){t.removeKey(e.model.uid);var n=e.controller;n&&n.dispose()}}function jB(t,e){t.isDisposed()||t.dispatchAction({type:"dataZoom",animation:{easing:"cubicOut",duration:100},batch:e})}function qB(t,e,n,i){return t.coordinateSystem.containPoint([n,i])}function KB(t){t.registerProcessor(t.PRIORITY.PROCESSOR.FILTER,(function(t,e){var n=UB(e),i=n.coordSysRecordMap||(n.coordSysRecordMap=yt());i.each((function(t){t.dataZoomInfoMap=null})),t.eachComponent({mainType:"dataZoom",subType:"inside"},(function(t){E(BE(t).infoList,(function(n){var r=n.model.uid,o=i.get(r)||i.set(r,function(t,e){var n={model:e,containsPoint:H(qB,e),dispatchAction:H(jB,t),dataZoomInfoMap:null,controller:null},i=n.controller=new BI(t.getZr());return E(["pan","zoom","scrollMove"],(function(t){i.on(t,(function(e){var i=[];n.dataZoomInfoMap.each((function(r){if(e.isAvailableBehavior(r.model.option)){var o=(r.getRange||{})[t],a=o&&o(r.dzReferCoordSysInfo,n.model.mainType,n.controller,e);!r.model.get("disabled",!0)&&a&&i.push({dataZoomId:r.model.id,start:a[0],end:a[1]})}})),i.length&&n.dispatchAction(i)}))})),n}(e,n.model));(o.dataZoomInfoMap||(o.dataZoomInfoMap=yt())).set(t.uid,{dzReferCoordSysInfo:n,model:t,getRange:null})}))})),i.each((function(t){var e,n=t.controller,r=t.dataZoomInfoMap;if(r){var o=r.keys()[0];null!=o&&(e=r.get(o))}if(e){var a=function(t){var e,n="type_",i={type_true:2,type_move:1,type_false:0,type_undefined:-1},r=!0;return t.each((function(t){var o=t.model,a=!o.get("disabled",!0)&&(!o.get("zoomLock",!0)||"move");i[n+a]>i[n+e]&&(e=a),r=r&&o.get("preventDefaultMouseMove",!0)})),{controlType:e,opt:{zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!0,preventDefaultMouseMove:!!r}}}(r);n.enable(a.controlType,a.opt),n.setPointerChecker(t.containsPoint),Eg(t,"dispatchAction",e.model.get("throttle",!0),"fixRate")}else ZB(i,t)}))}))}var $B=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type="dataZoom.inside",e}return n(e,t),e.prototype.render=function(e,n,i){t.prototype.render.apply(this,arguments),e.noTarget()?this._clear():(this.range=e.getPercentRange(),XB(i,e,{pan:W(JB.pan,this),zoom:W(JB.zoom,this),scrollMove:W(JB.scrollMove,this)}))},e.prototype.dispose=function(){this._clear(),t.prototype.dispose.apply(this,arguments)},e.prototype._clear=function(){!function(t,e){for(var n=UB(t).coordSysRecordMap,i=n.keys(),r=0;r0?s.pixelStart+s.pixelLength-s.pixel:s.pixel-s.pixelStart)/s.pixelLength*(o[1]-o[0])+o[0],u=Math.max(1/i.scale,0);o[0]=(o[0]-l)*u+l,o[1]=(o[1]-l)*u+l;var h=this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan();return xk(0,o,[0,100],0,h.minSpan,h.maxSpan),this.range=o,r[0]!==o[0]||r[1]!==o[1]?o:void 0}},pan:QB((function(t,e,n,i,r,o){var a=tF[i]([o.oldX,o.oldY],[o.newX,o.newY],e,r,n);return a.signal*(t[1]-t[0])*a.pixel/a.pixelLength})),scrollMove:QB((function(t,e,n,i,r,o){return tF[i]([0,0],[o.scrollDelta,o.scrollDelta],e,r,n).signal*(t[1]-t[0])*o.scrollDelta}))};function QB(t){return function(e,n,i,r){var o=this.range,a=o.slice(),s=e.axisModels[0];if(s)return xk(t(a,s,e,n,i,r),a,[0,100],"all"),this.range=a,o[0]!==a[0]||o[1]!==a[1]?a:void 0}}var tF={grid:function(t,e,n,i,r){var o=n.axis,a={},s=r.model.coordinateSystem.getRect();return t=t||[0,0],"x"===o.dim?(a.pixel=e[0]-t[0],a.pixelLength=s.width,a.pixelStart=s.x,a.signal=o.inverse?1:-1):(a.pixel=e[1]-t[1],a.pixelLength=s.height,a.pixelStart=s.y,a.signal=o.inverse?-1:1),a},polar:function(t,e,n,i,r){var o=n.axis,a={},s=r.model.coordinateSystem,l=s.getRadiusAxis().getExtent(),u=s.getAngleAxis().getExtent();return t=t?s.pointToCoord(t):[0,0],e=s.pointToCoord(e),"radiusAxis"===n.mainType?(a.pixel=e[0]-t[0],a.pixelLength=l[1]-l[0],a.pixelStart=l[0],a.signal=o.inverse?1:-1):(a.pixel=e[1]-t[1],a.pixelLength=u[1]-u[0],a.pixelStart=u[0],a.signal=o.inverse?-1:1),a},singleAxis:function(t,e,n,i,r){var o=n.axis,a=r.model.coordinateSystem.getRect(),s={};return t=t||[0,0],"horizontal"===o.orient?(s.pixel=e[0]-t[0],s.pixelLength=a.width,s.pixelStart=a.x,s.signal=o.inverse?1:-1):(s.pixel=e[1]-t[1],s.pixelLength=a.height,s.pixelStart=a.y,s.signal=o.inverse?-1:1),s}};function eF(t){$E(t),t.registerComponentModel(YB),t.registerComponentView($B),KB(t)}var nF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type="dataZoom.slider",e.layoutMode="box",e.defaultOption=Tc(GE.defaultOption,{show:!0,right:"ph",top:"ph",width:"ph",height:"ph",left:null,bottom:null,borderColor:"#d2dbee",borderRadius:3,backgroundColor:"rgba(47,69,84,0)",dataBackground:{lineStyle:{color:"#d2dbee",width:.5},areaStyle:{color:"#d2dbee",opacity:.2}},selectedDataBackground:{lineStyle:{color:"#8fb0f7",width:.5},areaStyle:{color:"#8fb0f7",opacity:.2}},fillerColor:"rgba(135,175,274,0.2)",handleIcon:"path://M-9.35,34.56V42m0-40V9.5m-2,0h4a2,2,0,0,1,2,2v21a2,2,0,0,1-2,2h-4a2,2,0,0,1-2-2v-21A2,2,0,0,1-11.35,9.5Z",handleSize:"100%",handleStyle:{color:"#fff",borderColor:"#ACB8D1"},moveHandleSize:7,moveHandleIcon:"path://M-320.9-50L-320.9-50c18.1,0,27.1,9,27.1,27.1V85.7c0,18.1-9,27.1-27.1,27.1l0,0c-18.1,0-27.1-9-27.1-27.1V-22.9C-348-41-339-50-320.9-50z M-212.3-50L-212.3-50c18.1,0,27.1,9,27.1,27.1V85.7c0,18.1-9,27.1-27.1,27.1l0,0c-18.1,0-27.1-9-27.1-27.1V-22.9C-239.4-41-230.4-50-212.3-50z M-103.7-50L-103.7-50c18.1,0,27.1,9,27.1,27.1V85.7c0,18.1-9,27.1-27.1,27.1l0,0c-18.1,0-27.1-9-27.1-27.1V-22.9C-130.9-41-121.8-50-103.7-50z",moveHandleStyle:{color:"#D2DBEE",opacity:.7},showDetail:!0,showDataShadow:"auto",realtime:!0,zoomLock:!1,textStyle:{color:"#6E7079"},brushSelect:!0,brushStyle:{color:"rgba(135,175,274,0.15)"},emphasis:{handleStyle:{borderColor:"#8FB0F7"},moveHandleStyle:{color:"#8FB0F7"}}}),e}(GE),iF=Es,rF="horizontal",oF="vertical",aF=["line","bar","candlestick","scatter"],sF={easing:"cubicOut",duration:100,delay:0},lF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._displayables={},n}return n(e,t),e.prototype.init=function(t,e){this.api=e,this._onBrush=W(this._onBrush,this),this._onBrushEnd=W(this._onBrushEnd,this)},e.prototype.render=function(e,n,i,r){if(t.prototype.render.apply(this,arguments),Eg(this,"_dispatchZoomAction",e.get("throttle"),"fixRate"),this._orient=e.getOrient(),!1!==e.get("show")){if(e.noTarget())return this._clear(),void this.group.removeAll();r&&"dataZoom"===r.type&&r.from===this.uid||this._buildView(),this._updateView()}else this.group.removeAll()},e.prototype.dispose=function(){this._clear(),t.prototype.dispose.apply(this,arguments)},e.prototype._clear=function(){zg(this,"_dispatchZoomAction");var t=this.api.getZr();t.off("mousemove",this._onBrush),t.off("mouseup",this._onBrushEnd)},e.prototype._buildView=function(){var t=this.group;t.removeAll(),this._brushing=!1,this._displayables.brushRect=null,this._resetLocation(),this._resetInterval();var e=this._displayables.sliderGroup=new Er;this._renderBackground(),this._renderHandle(),this._renderDataShadow(),t.add(e),this._positionGroup()},e.prototype._resetLocation=function(){var t=this.dataZoomModel,e=this.api,n=t.get("brushSelect")?7:0,i=this._findCoordRect(),r={width:e.getWidth(),height:e.getHeight()},o=this._orient===rF?{right:r.width-i.x-i.width,top:r.height-30-7-n,width:i.width,height:30}:{right:7,top:i.y,width:30,height:i.height},a=kp(t.option);E(["right","top","width","height"],(function(t){"ph"===a[t]&&(a[t]=o[t])}));var s=Tp(a,r);this._location={x:s.x,y:s.y},this._size=[s.width,s.height],this._orient===oF&&this._size.reverse()},e.prototype._positionGroup=function(){var t=this.group,e=this._location,n=this._orient,i=this.dataZoomModel.getFirstTargetAxisModel(),r=i&&i.get("inverse"),o=this._displayables.sliderGroup,a=(this._dataShadowInfo||{}).otherAxisInverse;o.attr(n!==rF||r?n===rF&&r?{scaleY:a?1:-1,scaleX:-1}:n!==oF||r?{scaleY:a?-1:1,scaleX:-1,rotation:Math.PI/2}:{scaleY:a?-1:1,scaleX:1,rotation:Math.PI/2}:{scaleY:a?1:-1,scaleX:1});var s=t.getBoundingRect([o]);t.x=e.x-s.x,t.y=e.y-s.y,t.markRedraw()},e.prototype._getViewExtent=function(){return[0,this._size[0]]},e.prototype._renderBackground=function(){var t=this.dataZoomModel,e=this._size,n=this._displayables.sliderGroup,i=t.get("brushSelect");n.add(new iF({silent:!0,shape:{x:0,y:0,width:e[0],height:e[1]},style:{fill:t.get("backgroundColor")},z2:-40}));var r=new iF({shape:{x:0,y:0,width:e[0],height:e[1]},style:{fill:"transparent"},z2:0,onclick:W(this._onClickPanel,this)}),o=this.api.getZr();i?(r.on("mousedown",this._onBrushStart,this),r.cursor="crosshair",o.on("mousemove",this._onBrush),o.on("mouseup",this._onBrushEnd)):(o.off("mousemove",this._onBrush),o.off("mouseup",this._onBrushEnd)),n.add(r)},e.prototype._renderDataShadow=function(){var t=this._dataShadowInfo=this._prepareDataShadowInfo();if(this._displayables.dataShadowSegs=[],t){var e=this._size,n=this._shadowSize||[],i=t.series,r=i.getRawData(),o=i.getShadowDim&&i.getShadowDim(),a=o&&r.getDimensionInfo(o)?i.getShadowDim():t.otherDim;if(null!=a){var s=this._shadowPolygonPts,l=this._shadowPolylinePts;if(r!==this._shadowData||a!==this._shadowDim||e[0]!==n[0]||e[1]!==n[1]){var u=r.getDataExtent(a),h=.3*(u[1]-u[0]);u=[u[0]-h,u[1]+h];var c,p=[0,e[1]],d=[0,e[0]],f=[[e[0],0],[0,0]],g=[],y=d[1]/(r.count()-1),v=0,m=Math.round(r.count()/e[0]);r.each([a],(function(t,e){if(m>0&&e%m)v+=y;else{var n=null==t||isNaN(t)||""===t,i=n?0:Yr(t,u,p,!0);n&&!c&&e?(f.push([f[f.length-1][0],0]),g.push([g[g.length-1][0],0])):!n&&c&&(f.push([v,0]),g.push([v,0])),f.push([v,i]),g.push([v,i]),v+=y,c=n}})),s=this._shadowPolygonPts=f,l=this._shadowPolylinePts=g}this._shadowData=r,this._shadowDim=a,this._shadowSize=[e[0],e[1]];for(var x=this.dataZoomModel,_=0;_<3;_++){var b=w(1===_);this._displayables.sliderGroup.add(b),this._displayables.dataShadowSegs.push(b)}}}function w(t){var e=x.getModel(t?"selectedDataBackground":"dataBackground"),n=new Er,i=new Gu({shape:{points:s},segmentIgnoreThreshold:1,style:e.getModel("areaStyle").getAreaStyle(),silent:!0,z2:-20}),r=new Hu({shape:{points:l},segmentIgnoreThreshold:1,style:e.getModel("lineStyle").getLineStyle(),silent:!0,z2:-19});return n.add(i),n.add(r),n}},e.prototype._prepareDataShadowInfo=function(){var t=this.dataZoomModel,e=t.get("showDataShadow");if(!1!==e){var n,i=this.ecModel;return t.eachTargetAxis((function(r,o){E(t.getAxisProxy(r,o).getTargetSeriesModels(),(function(t){if(!(n||!0!==e&&P(aF,t.get("type"))<0)){var a,s=i.getComponent(zE(r),o).axis,l={x:"y",y:"x",radius:"angle",angle:"radius"}[r],u=t.coordinateSystem;null!=l&&u.getOtherAxis&&(a=u.getOtherAxis(s).inverse),l=t.getData().mapDimension(l),n={thisAxis:s,series:t,thisDim:r,otherDim:l,otherAxisInverse:a}}}),this)}),this),n}},e.prototype._renderHandle=function(){var t=this.group,e=this._displayables,n=e.handles=[null,null],i=e.handleLabels=[null,null],r=this._displayables.sliderGroup,o=this._size,a=this.dataZoomModel,s=this.api,l=a.get("borderRadius")||0,u=a.get("brushSelect"),h=e.filler=new iF({silent:u,style:{fill:a.get("fillerColor")},textConfig:{position:"inside"}});r.add(h),r.add(new iF({silent:!0,subPixelOptimize:!0,shape:{x:0,y:0,width:o[0],height:o[1],r:l},style:{stroke:a.get("dataBackgroundColor")||a.get("borderColor"),lineWidth:1,fill:"rgba(0,0,0,0)"}})),E([0,1],(function(e){var o=a.get("handleIcon");!Ny[o]&&o.indexOf("path://")<0&&o.indexOf("image://")<0&&(o="path://"+o);var s=Vy(o,-1,0,2,2,null,!0);s.attr({cursor:uF(this._orient),draggable:!0,drift:W(this._onDragMove,this,e),ondragend:W(this._onDragEnd,this),onmouseover:W(this._showDataInfo,this,!0),onmouseout:W(this._showDataInfo,this,!1),z2:5});var l=s.getBoundingRect(),u=a.get("handleSize");this._handleHeight=Ur(u,this._size[1]),this._handleWidth=l.width/l.height*this._handleHeight,s.setStyle(a.getModel("handleStyle").getItemStyle()),s.style.strokeNoScale=!0,s.rectHover=!0,s.ensureState("emphasis").style=a.getModel(["emphasis","handleStyle"]).getItemStyle(),Wl(s);var h=a.get("handleColor");null!=h&&(s.style.fill=h),r.add(n[e]=s);var c=a.getModel("textStyle");t.add(i[e]=new Bs({silent:!0,invisible:!0,style:ec(c,{x:0,y:0,text:"",verticalAlign:"middle",align:"center",fill:c.getTextColor(),font:c.getFont()}),z2:10}))}),this);var c=h;if(u){var p=Ur(a.get("moveHandleSize"),o[1]),d=e.moveHandle=new Es({style:a.getModel("moveHandleStyle").getItemStyle(),silent:!0,shape:{r:[0,0,2,2],y:o[1]-.5,height:p}}),f=.8*p,g=e.moveHandleIcon=Vy(a.get("moveHandleIcon"),-f/2,-f/2,f,f,"#fff",!0);g.silent=!0,g.y=o[1]+p/2-.5,d.ensureState("emphasis").style=a.getModel(["emphasis","moveHandleStyle"]).getItemStyle();var y=Math.min(o[1]/2,Math.max(p,10));(c=e.moveZone=new Es({invisible:!0,shape:{y:o[1]-y,height:p+y}})).on("mouseover",(function(){s.enterEmphasis(d)})).on("mouseout",(function(){s.leaveEmphasis(d)})),r.add(d),r.add(g),r.add(c)}c.attr({draggable:!0,cursor:uF(this._orient),drift:W(this._onDragMove,this,"all"),ondragstart:W(this._showDataInfo,this,!0),ondragend:W(this._onDragEnd,this),onmouseover:W(this._showDataInfo,this,!0),onmouseout:W(this._showDataInfo,this,!1)})},e.prototype._resetInterval=function(){var t=this._range=this.dataZoomModel.getPercentRange(),e=this._getViewExtent();this._handleEnds=[Yr(t[0],[0,100],e,!0),Yr(t[1],[0,100],e,!0)]},e.prototype._updateInterval=function(t,e){var n=this.dataZoomModel,i=this._handleEnds,r=this._getViewExtent(),o=n.findRepresentativeAxisProxy().getMinMaxSpan(),a=[0,100];xk(e,i,r,n.get("zoomLock")?"all":t,null!=o.minSpan?Yr(o.minSpan,a,r,!0):null,null!=o.maxSpan?Yr(o.maxSpan,a,r,!0):null);var s=this._range,l=this._range=Zr([Yr(i[0],r,a,!0),Yr(i[1],r,a,!0)]);return!s||s[0]!==l[0]||s[1]!==l[1]},e.prototype._updateView=function(t){var e=this._displayables,n=this._handleEnds,i=Zr(n.slice()),r=this._size;E([0,1],(function(t){var i=e.handles[t],o=this._handleHeight;i.attr({scaleX:o/2,scaleY:o/2,x:n[t]+(t?-1:1),y:r[1]/2-o/2})}),this),e.filler.setShape({x:i[0],y:0,width:i[1]-i[0],height:r[1]});var o={x:i[0],width:i[1]-i[0]};e.moveHandle&&(e.moveHandle.setShape(o),e.moveZone.setShape(o),e.moveZone.getBoundingRect(),e.moveHandleIcon&&e.moveHandleIcon.attr("x",o.x+o.width/2));for(var a=e.dataShadowSegs,s=[0,i[0],i[1],r[0]],l=0;le[0]||n[1]<0||n[1]>e[1])){var i=this._handleEnds,r=(i[0]+i[1])/2,o=this._updateInterval("all",n[0]-r);this._updateView(),o&&this._dispatchZoomAction(!1)}},e.prototype._onBrushStart=function(t){var e=t.offsetX,n=t.offsetY;this._brushStart=new Ce(e,n),this._brushing=!0,this._brushStartTime=+new Date},e.prototype._onBrushEnd=function(t){if(this._brushing){var e=this._displayables.brushRect;if(this._brushing=!1,e){e.attr("ignore",!0);var n=e.shape;if(!(+new Date-this._brushStartTime<200&&Math.abs(n.width)<5)){var i=this._getViewExtent(),r=[0,100];this._range=Zr([Yr(n.x,i,r,!0),Yr(n.x+n.width,i,r,!0)]),this._handleEnds=[n.x,n.x+n.width],this._updateView(),this._dispatchZoomAction(!1)}}}},e.prototype._onBrush=function(t){this._brushing&&(pe(t.event),this._updateBrushRect(t.offsetX,t.offsetY))},e.prototype._updateBrushRect=function(t,e){var n=this._displayables,i=this.dataZoomModel,r=n.brushRect;r||(r=n.brushRect=new iF({silent:!0,style:i.getModel("brushStyle").getItemStyle()}),n.sliderGroup.add(r)),r.attr("ignore",!1);var o=this._brushStart,a=this._displayables.sliderGroup,s=a.transformCoordToLocal(t,e),l=a.transformCoordToLocal(o.x,o.y),u=this._size;s[0]=Math.max(Math.min(u[0],s[0]),0),r.setShape({x:l[0],y:0,width:s[0]-l[0],height:u[1]})},e.prototype._dispatchZoomAction=function(t){var e=this._range;this.api.dispatchAction({type:"dataZoom",from:this.uid,dataZoomId:this.dataZoomModel.id,animation:t?sF:null,start:e[0],end:e[1]})},e.prototype._findCoordRect=function(){var t,e=BE(this.dataZoomModel).infoList;if(!t&&e.length){var n=e[0].model.coordinateSystem;t=n.getRect&&n.getRect()}if(!t){var i=this.api.getWidth(),r=this.api.getHeight();t={x:.2*i,y:.2*r,width:.6*i,height:.6*r}}return t},e.type="dataZoom.slider",e}(YE);function uF(t){return"vertical"===t?"ns-resize":"ew-resize"}function hF(t){t.registerComponentModel(nF),t.registerComponentView(lF),$E(t)}var cF=function(t,e,n){var i=T((pF[t]||{})[e]);return n&&Y(i)?i[i.length-1]:i},pF={color:{active:["#006edd","#e0ffff"],inactive:["rgba(0,0,0,0)"]},colorHue:{active:[0,360],inactive:[0,0]},colorSaturation:{active:[.3,1],inactive:[0,0]},colorLightness:{active:[.9,.5],inactive:[0,0]},colorAlpha:{active:[.3,1],inactive:[0,0]},opacity:{active:[.3,1],inactive:[0,0]},symbol:{active:["circle","roundRect","diamond"],inactive:["none"]},symbolSize:{active:[10,50],inactive:[0,0]}},dF=dD.mapVisual,fF=dD.eachVisual,gF=Y,yF=E,vF=Zr,mF=Yr,xF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.stateList=["inRange","outOfRange"],n.replacableOptionKeys=["inRange","outOfRange","target","controller","color"],n.layoutMode={type:"box",ignoreSize:!0},n.dataBound=[-1/0,1/0],n.targetVisuals={},n.controllerVisuals={},n}return n(e,t),e.prototype.init=function(t,e,n){this.mergeDefaultAndTheme(t,n)},e.prototype.optionUpdated=function(t,e){var n=this.option;!e&&dV(n,t,this.replacableOptionKeys),this.textStyleModel=this.getModel("textStyle"),this.resetItemSize(),this.completeVisualOption()},e.prototype.resetVisual=function(t){var e=this.stateList;t=W(t,this),this.controllerVisuals=pV(this.option.controller,e,t),this.targetVisuals=pV(this.option.target,e,t)},e.prototype.getItemSymbol=function(){return null},e.prototype.getTargetSeriesIndices=function(){var t=this.option.seriesIndex,e=[];return null==t||"all"===t?this.ecModel.eachSeries((function(t,n){e.push(n)})):e=_o(t),e},e.prototype.eachTargetSeries=function(t,e){E(this.getTargetSeriesIndices(),(function(n){var i=this.ecModel.getSeriesByIndex(n);i&&t.call(e,i)}),this)},e.prototype.isTargetSeries=function(t){var e=!1;return this.eachTargetSeries((function(n){n===t&&(e=!0)})),e},e.prototype.formatValueText=function(t,e,n){var i,r=this.option,o=r.precision,a=this.dataBound,s=r.formatter;n=n||["<",">"],Y(t)&&(t=t.slice(),i=!0);var l=e?t:i?[u(t[0]),u(t[1])]:u(t);return X(s)?s.replace("{value}",i?l[0]:l).replace("{value2}",i?l[1]:l):U(s)?i?s(t[0],t[1]):s(t):i?t[0]===a[0]?n[0]+" "+l[1]:t[1]===a[1]?n[1]+" "+l[0]:l[0]+" - "+l[1]:l;function u(t){return t===a[0]?"min":t===a[1]?"max":(+t).toFixed(Math.min(o,20))}},e.prototype.resetExtent=function(){var t=this.option,e=vF([t.min,t.max]);this._dataExtent=e},e.prototype.getDataDimensionIndex=function(t){var e=this.option.dimension;if(null!=e)return t.getDimensionIndex(e);for(var n=t.dimensions,i=n.length-1;i>=0;i--){var r=n[i],o=t.getDimensionInfo(r);if(!o.isCalculationCoord)return o.storeDimIndex}},e.prototype.getExtent=function(){return this._dataExtent.slice()},e.prototype.completeVisualOption=function(){var t=this.ecModel,e=this.option,n={inRange:e.inRange,outOfRange:e.outOfRange},i=e.target||(e.target={}),r=e.controller||(e.controller={});C(i,n),C(r,n);var o=this.isCategory();function a(n){gF(e.color)&&!n.inRange&&(n.inRange={color:e.color.slice().reverse()}),n.inRange=n.inRange||{color:t.get("gradientColor")}}a.call(this,i),a.call(this,r),function(t,e,n){var i=t[e],r=t[n];i&&!r&&(r=t[n]={},yF(i,(function(t,e){if(dD.isValidType(e)){var n=cF(e,"inactive",o);null!=n&&(r[e]=n,"color"!==e||r.hasOwnProperty("opacity")||r.hasOwnProperty("colorAlpha")||(r.opacity=[0,0]))}})))}.call(this,i,"inRange","outOfRange"),function(t){var e=(t.inRange||{}).symbol||(t.outOfRange||{}).symbol,n=(t.inRange||{}).symbolSize||(t.outOfRange||{}).symbolSize,i=this.get("inactiveColor"),r=this.getItemSymbol()||"roundRect";yF(this.stateList,(function(a){var s=this.itemSize,l=t[a];l||(l=t[a]={color:o?i:[i]}),null==l.symbol&&(l.symbol=e&&T(e)||(o?r:[r])),null==l.symbolSize&&(l.symbolSize=n&&T(n)||(o?s[0]:[s[0],s[0]])),l.symbol=dF(l.symbol,(function(t){return"none"===t?r:t}));var u=l.symbolSize;if(null!=u){var h=-1/0;fF(u,(function(t){t>h&&(h=t)})),l.symbolSize=dF(u,(function(t){return mF(t,[0,h],[0,s[0]],!0)}))}}),this)}.call(this,r)},e.prototype.resetItemSize=function(){this.itemSize=[parseFloat(this.get("itemWidth")),parseFloat(this.get("itemHeight"))]},e.prototype.isCategory=function(){return!!this.option.categories},e.prototype.setSelected=function(t){},e.prototype.getSelected=function(){return null},e.prototype.getValueState=function(t){return null},e.prototype.getVisualMeta=function(t){return null},e.type="visualMap",e.dependencies=["series"],e.defaultOption={show:!0,z:4,seriesIndex:"all",min:0,max:200,left:0,right:null,top:null,bottom:0,itemWidth:null,itemHeight:null,inverse:!1,orient:"vertical",backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",contentColor:"#5793f3",inactiveColor:"#aaa",borderWidth:0,padding:5,textGap:10,precision:0,textStyle:{color:"#333"}},e}(Op),_F=[20,140],bF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(e,n){t.prototype.optionUpdated.apply(this,arguments),this.resetExtent(),this.resetVisual((function(t){t.mappingMethod="linear",t.dataExtent=this.getExtent()})),this._resetRange()},e.prototype.resetItemSize=function(){t.prototype.resetItemSize.apply(this,arguments);var e=this.itemSize;(null==e[0]||isNaN(e[0]))&&(e[0]=_F[0]),(null==e[1]||isNaN(e[1]))&&(e[1]=_F[1])},e.prototype._resetRange=function(){var t=this.getExtent(),e=this.option.range;!e||e.auto?(t.auto=1,this.option.range=t):Y(e)&&(e[0]>e[1]&&e.reverse(),e[0]=Math.max(e[0],t[0]),e[1]=Math.min(e[1],t[1]))},e.prototype.completeVisualOption=function(){t.prototype.completeVisualOption.apply(this,arguments),E(this.stateList,(function(t){var e=this.option.controller[t].symbolSize;e&&e[0]!==e[1]&&(e[0]=e[1]/3)}),this)},e.prototype.setSelected=function(t){this.option.range=t.slice(),this._resetRange()},e.prototype.getSelected=function(){var t=this.getExtent(),e=Zr((this.get("range")||[]).slice());return e[0]>t[1]&&(e[0]=t[1]),e[1]>t[1]&&(e[1]=t[1]),e[0]=n[1]||t<=e[1])?"inRange":"outOfRange"},e.prototype.findTargetDataIndices=function(t){var e=[];return this.eachTargetSeries((function(n){var i=[],r=n.getData();r.each(this.getDataDimensionIndex(r),(function(e,n){t[0]<=e&&e<=t[1]&&i.push(n)}),this),e.push({seriesId:n.id,dataIndex:i})}),this),e},e.prototype.getVisualMeta=function(t){var e=wF(this,"outOfRange",this.getExtent()),n=wF(this,"inRange",this.option.range.slice()),i=[];function r(e,n){i.push({value:e,color:t(e,n)})}for(var o=0,a=0,s=n.length,l=e.length;at[1])break;n.push({color:this.getControllerVisual(o,"color",e),offset:r/100})}return n.push({color:this.getControllerVisual(t[1],"color",e),offset:1}),n},e.prototype._createBarPoints=function(t,e){var n=this.visualMapModel.itemSize;return[[n[0]-e[0],t[0]],[n[0],t[0]],[n[0],t[1]],[n[0]-e[1],t[1]]]},e.prototype._createBarGroup=function(t){var e=this._orient,n=this.visualMapModel.get("inverse");return new Er("horizontal"!==e||n?"horizontal"===e&&n?{scaleX:"bottom"===t?-1:1,rotation:-Math.PI/2}:"vertical"!==e||n?{scaleX:"left"===t?1:-1}:{scaleX:"left"===t?1:-1,scaleY:-1}:{scaleX:"bottom"===t?1:-1,rotation:Math.PI/2})},e.prototype._updateHandle=function(t,e){if(this._useHandle){var n=this._shapes,i=this.visualMapModel,r=n.handleThumbs,o=n.handleLabels,a=i.itemSize,s=i.getExtent();DF([0,1],(function(l){var u=r[l];u.setStyle("fill",e.handlesColor[l]),u.y=t[l];var h=CF(t[l],[0,a[1]],s,!0),c=this.getControllerVisual(h,"symbolSize");u.scaleX=u.scaleY=c/a[0],u.x=a[0]-c/2;var p=Eh(n.handleLabelPoints[l],Nh(u,this.group));o[l].setStyle({x:p[0],y:p[1],text:i.formatValueText(this._dataInterval[l]),verticalAlign:"middle",align:"vertical"===this._orient?this._applyTransform("left",n.mainGroup):"center"})}),this)}},e.prototype._showIndicator=function(t,e,n,i){var r=this.visualMapModel,o=r.getExtent(),a=r.itemSize,s=[0,a[1]],l=this._shapes,u=l.indicator;if(u){u.attr("invisible",!1);var h=this.getControllerVisual(t,"color",{convertOpacityToAlpha:!0}),c=this.getControllerVisual(t,"symbolSize"),p=CF(t,o,s,!0),d=a[0]-c/2,f={x:u.x,y:u.y};u.y=p,u.x=d;var g=Eh(l.indicatorLabelPoint,Nh(u,this.group)),y=l.indicatorLabel;y.attr("invisible",!1);var v=this._applyTransform("left",l.mainGroup),m="horizontal"===this._orient;y.setStyle({text:(n||"")+r.formatValueText(e),verticalAlign:m?v:"middle",align:m?"center":v});var x={x:d,y:p,style:{fill:h}},_={style:{x:g[0],y:g[1]}};if(r.ecModel.isAnimationEnabled()&&!this._firstShowIndicator){var b={duration:100,easing:"cubicInOut",additive:!0};u.x=f.x,u.y=f.y,u.animateTo(x,b),y.animateTo(_,b)}else u.attr(x),y.attr(_);this._firstShowIndicator=!1;var w=this._shapes.handleLabels;if(w)for(var S=0;Sr[1]&&(u[1]=1/0),e&&(u[0]===-1/0?this._showIndicator(l,u[1],"< ",a):u[1]===1/0?this._showIndicator(l,u[0],"> ",a):this._showIndicator(l,l,"≈ ",a));var h=this._hoverLinkDataIndices,c=[];(e||OF(n))&&(c=this._hoverLinkDataIndices=n.findTargetDataIndices(u));var p=function(t,e){var n={},i={};return r(t||[],n),r(e||[],i,n),[o(n),o(i)];function r(t,e,n){for(var i=0,r=t.length;i=0&&(r.dimension=o,i.push(r))}})),t.getData().setVisual("visualMeta",i)}}];function VF(t,e,n,i){for(var r=e.targetVisuals[i],o=dD.prepareVisualTypes(r),a={color:wy(t.getData(),"color")},s=0,l=o.length;s0:t.splitNumber>0)&&!t.calculable?"piecewise":"continuous"})),t.registerAction(NF,EF),E(zF,(function(e){t.registerVisual(t.PRIORITY.VISUAL.COMPONENT,e)})),t.registerPreprocessor(FF))}function YF(t){t.registerComponentModel(bF),t.registerComponentView(LF),HF(t)}var UF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._pieceList=[],n}return n(e,t),e.prototype.optionUpdated=function(e,n){t.prototype.optionUpdated.apply(this,arguments),this.resetExtent();var i=this._mode=this._determineMode();this._pieceList=[],XF[this._mode].call(this,this._pieceList),this._resetSelected(e,n);var r=this.option.categories;this.resetVisual((function(t,e){"categories"===i?(t.mappingMethod="category",t.categories=T(r)):(t.dataExtent=this.getExtent(),t.mappingMethod="piecewise",t.pieceList=z(this._pieceList,(function(t){return t=T(t),"inRange"!==e&&(t.visual=null),t})))}))},e.prototype.completeVisualOption=function(){var e=this.option,n={},i=dD.listVisualTypes(),r=this.isCategory();function o(t,e,n){return t&&t[e]&&t[e].hasOwnProperty(n)}E(e.pieces,(function(t){E(i,(function(e){t.hasOwnProperty(e)&&(n[e]=1)}))})),E(n,(function(t,n){var i=!1;E(this.stateList,(function(t){i=i||o(e,t,n)||o(e.target,t,n)}),this),!i&&E(this.stateList,(function(t){(e[t]||(e[t]={}))[n]=cF(n,"inRange"===t?"active":"inactive",r)}))}),this),t.prototype.completeVisualOption.apply(this,arguments)},e.prototype._resetSelected=function(t,e){var n=this.option,i=this._pieceList,r=(e?n:t).selected||{};if(n.selected=r,E(i,(function(t,e){var n=this.getSelectedMapKey(t);r.hasOwnProperty(n)||(r[n]=!0)}),this),"single"===n.selectedMode){var o=!1;E(i,(function(t,e){var n=this.getSelectedMapKey(t);r[n]&&(o?r[n]=!1:o=!0)}),this)}},e.prototype.getItemSymbol=function(){return this.get("itemSymbol")},e.prototype.getSelectedMapKey=function(t){return"categories"===this._mode?t.value+"":t.index+""},e.prototype.getPieceList=function(){return this._pieceList},e.prototype._determineMode=function(){var t=this.option;return t.pieces&&t.pieces.length>0?"pieces":this.option.categories?"categories":"splitNumber"},e.prototype.setSelected=function(t){this.option.selected=T(t)},e.prototype.getValueState=function(t){var e=dD.findPieceIndex(t,this._pieceList);return null!=e&&this.option.selected[this.getSelectedMapKey(this._pieceList[e])]?"inRange":"outOfRange"},e.prototype.findTargetDataIndices=function(t){var e=[],n=this._pieceList;return this.eachTargetSeries((function(i){var r=[],o=i.getData();o.each(this.getDataDimensionIndex(o),(function(e,i){dD.findPieceIndex(e,n)===t&&r.push(i)}),this),e.push({seriesId:i.id,dataIndex:r})}),this),e},e.prototype.getRepresentValue=function(t){var e;if(this.isCategory())e=t.value;else if(null!=t.value)e=t.value;else{var n=t.interval||[];e=n[0]===-1/0&&n[1]===1/0?0:(n[0]+n[1])/2}return e},e.prototype.getVisualMeta=function(t){if(!this.isCategory()){var e=[],n=["",""],i=this,r=this._pieceList.slice();if(r.length){var o=r[0].interval[0];o!==-1/0&&r.unshift({interval:[-1/0,o]}),(o=r[r.length-1].interval[1])!==1/0&&r.push({interval:[o,1/0]})}else r.push({interval:[-1/0,1/0]});var a=-1/0;return E(r,(function(t){var e=t.interval;e&&(e[0]>a&&s([a,e[0]],"outOfRange"),s(e.slice()),a=e[1])}),this),{stops:e,outerColors:n}}function s(r,o){var a=i.getRepresentValue({interval:r});o||(o=i.getValueState(a));var s=t(a,o);r[0]===-1/0?n[0]=s:r[1]===1/0?n[1]=s:e.push({value:r[0],color:s},{value:r[1],color:s})}},e.type="visualMap.piecewise",e.defaultOption=Tc(xF.defaultOption,{selected:null,minOpen:!1,maxOpen:!1,align:"auto",itemWidth:20,itemHeight:14,itemSymbol:"roundRect",pieces:null,categories:null,splitNumber:5,selectedMode:"multiple",itemGap:10,hoverLink:!0}),e}(xF),XF={splitNumber:function(t){var e=this.option,n=Math.min(e.precision,20),i=this.getExtent(),r=e.splitNumber;r=Math.max(parseInt(r,10),1),e.splitNumber=r;for(var o=(i[1]-i[0])/r;+o.toFixed(n)!==o&&n<5;)n++;e.precision=n,o=+o.toFixed(n),e.minOpen&&t.push({interval:[-1/0,i[0]],close:[0,0]});for(var a=0,s=i[0];a","≥"][e[0]]];t.text=t.text||this.formatValueText(null!=t.value?t.value:t.interval,!1,n)}),this)}};function ZF(t,e){var n=t.inverse;("vertical"===t.orient?!n:n)&&e.reverse()}var jF=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.doRender=function(){var t=this.group;t.removeAll();var e=this.visualMapModel,n=e.get("textGap"),i=e.textStyleModel,r=i.getFont(),o=i.getTextColor(),a=this._getItemAlign(),s=e.itemSize,l=this._getViewData(),u=l.endsText,h=it(e.get("showLabel",!0),!u);u&&this._renderEndsText(t,u[0],s,h,a),E(l.viewPieceList,(function(i){var l=i.piece,u=new Er;u.onclick=W(this._onItemClick,this,l),this._enableHoverLink(u,i.indexInModelPieceList);var c=e.getRepresentValue(l);if(this._createItemSymbol(u,c,[0,0,s[0],s[1]]),h){var p=this.visualMapModel.getValueState(c);u.add(new Bs({style:{x:"right"===a?-n:s[0]+n,y:s[1]/2,text:l.text,verticalAlign:"middle",align:a,font:r,fill:o,opacity:"outOfRange"===p?.5:1}}))}t.add(u)}),this),u&&this._renderEndsText(t,u[1],s,h,a),Ip(e.get("orient"),t,e.get("itemGap")),this.renderBackground(t),this.positionGroup(t)},e.prototype._enableHoverLink=function(t,e){var n=this;t.on("mouseover",(function(){return i("highlight")})).on("mouseout",(function(){return i("downplay")}));var i=function(t){var i=n.visualMapModel;i.option.hoverLink&&n.api.dispatchAction({type:t,batch:TF(i.findTargetDataIndices(e),i)})}},e.prototype._getItemAlign=function(){var t=this.visualMapModel,e=t.option;if("vertical"===e.orient)return IF(t,this.api,t.itemSize);var n=e.align;return n&&"auto"!==n||(n="left"),n},e.prototype._renderEndsText=function(t,e,n,i,r){if(e){var o=new Er,a=this.visualMapModel.textStyleModel;o.add(new Bs({style:ec(a,{x:i?"right"===r?n[0]:0:n[0]/2,y:n[1]/2,verticalAlign:"middle",align:i?r:"center",text:e})})),t.add(o)}},e.prototype._getViewData=function(){var t=this.visualMapModel,e=z(t.getPieceList(),(function(t,e){return{piece:t,indexInModelPieceList:e}})),n=t.get("text"),i=t.get("orient"),r=t.get("inverse");return("horizontal"===i?r:!r)?e.reverse():n&&(n=n.slice().reverse()),{viewPieceList:e,endsText:n}},e.prototype._createItemSymbol=function(t,e,n){t.add(Vy(this.getControllerVisual(e,"symbol"),n[0],n[1],n[2],n[3],this.getControllerVisual(e,"color")))},e.prototype._onItemClick=function(t){var e=this.visualMapModel,n=e.option,i=n.selectedMode;if(i){var r=T(n.selected),o=e.getSelectedMapKey(t);"single"===i||!0===i?(r[o]=!0,E(r,(function(t,e){r[e]=e===o}))):r[o]=!r[o],this.api.dispatchAction({type:"selectDataRange",from:this.uid,visualMapId:this.visualMapModel.id,selected:r})}},e.type="visualMap.piecewise",e}(SF);function qF(t){t.registerComponentModel(UF),t.registerComponentView(jF),HF(t)}var KF={label:{enabled:!0},decal:{show:!1}},$F=Po(),JF={};function QF(t,e){var n=t.getModel("aria");if(n.get("enabled")){var i=T(KF);C(i.label,t.getLocaleModel().get("aria"),!1),C(n.option,i,!1),function(){if(n.getModel("decal").get("show")){var e=yt();t.eachSeries((function(t){if(!t.isColorBySeries()){var n=e.get(t.type);n||(n={},e.set(t.type,n)),$F(t).scope=n}})),t.eachRawSeries((function(e){if(!t.isSeriesFiltered(e))if(U(e.enableAriaDecal))e.enableAriaDecal();else{var n=e.getData();if(e.isColorBySeries()){var i=ld(e.ecModel,e.name,JF,t.getSeriesCount()),r=n.getVisual("decal");n.setVisual("decal",u(r,i))}else{var o=e.getRawData(),a={},s=$F(e).scope;n.each((function(t){var e=n.getRawIndex(t);a[e]=t}));var l=o.count();o.each((function(t){var i=a[t],r=o.getName(t)||t+"",h=ld(e.ecModel,r,s,l),c=n.getItemVisual(i,"decal");n.setItemVisual(i,"decal",u(c,h))}))}}function u(t,e){var n=t?A(A({},e),t):e;return n.dirty=!0,n}}))}}(),function(){var i=t.getLocaleModel().get("aria"),o=n.getModel("label");if(o.option=k(o.option,i),!o.get("enabled"))return;var a=e.getZr().dom;if(o.get("description"))return void a.setAttribute("aria-label",o.get("description"));var s,l=t.getSeriesCount(),u=o.get(["data","maxCount"])||10,h=o.get(["series","maxCount"])||10,c=Math.min(l,h);if(l<1)return;var p=function(){var e=t.get("title");e&&e.length&&(e=e[0]);return e&&e.text}();if(p){var d=o.get(["general","withTitle"]);s=r(d,{title:p})}else s=o.get(["general","withoutTitle"]);var f=[],g=l>1?o.get(["series","multiple","prefix"]):o.get(["series","single","prefix"]);s+=r(g,{seriesCount:l}),t.eachSeries((function(e,n){if(n1?o.get(["series","multiple",a]):o.get(["series","single",a]),{seriesId:e.seriesIndex,seriesName:e.get("name"),seriesType:(x=e.subType,t.getLocaleModel().get(["series","typeNames"])[x]||"自定义图")});var s=e.getData();if(s.count()>u)i+=r(o.get(["data","partialData"]),{displayCnt:u});else i+=o.get(["data","allData"]);for(var h=o.get(["data","separator","middle"]),p=o.get(["data","separator","end"]),d=[],g=0;g":"gt",">=":"gte","=":"eq","!=":"ne","<>":"ne"},nG=function(){function t(t){if(null==(this._condVal=X(t)?new RegExp(t):et(t)?t:null)){var e="";0,yo(e)}}return t.prototype.evaluate=function(t){var e=typeof t;return X(e)?this._condVal.test(t):!!j(e)&&this._condVal.test(t+"")},t}(),iG=function(){function t(){}return t.prototype.evaluate=function(){return this.value},t}(),rG=function(){function t(){}return t.prototype.evaluate=function(){for(var t=this.children,e=0;e2&&l.push(e),e=[t,n]}function f(t,n,i,r){vG(t,i)&&vG(n,r)||e.push(t,n,i,r,i,r)}function g(t,n,i,r,o,a){var s=Math.abs(n-t),l=4*Math.tan(s/4)/3,u=nM:C2&&l.push(e),l}function xG(t,e,n,i,r,o,a,s,l,u){if(vG(t,n)&&vG(e,i)&&vG(r,a)&&vG(o,s))l.push(a,s);else{var h=2/u,c=h*h,p=a-t,d=s-e,f=Math.sqrt(p*p+d*d);p/=f,d/=f;var g=n-t,y=i-e,v=r-a,m=o-s,x=g*g+y*y,_=v*v+m*m;if(x=0&&_-w*w=0)l.push(a,s);else{var S=[],M=[];bn(t,n,r,a,.5,S),bn(e,i,o,s,.5,M),xG(S[0],M[0],S[1],M[1],S[2],M[2],S[3],M[3],l,u),xG(S[4],M[4],S[5],M[5],S[6],M[6],S[7],M[7],l,u)}}}}function _G(t,e,n){var i=t[e],r=t[1-e],o=Math.abs(i/r),a=Math.ceil(Math.sqrt(o*n)),s=Math.floor(n/a);0===s&&(s=1,a=n);for(var l=[],u=0;u0)for(u=0;uMath.abs(u),c=_G([l,u],h?0:1,e),p=(h?s:u)/c.length,d=0;d1?null:new Ce(d*l+t,d*u+e)}function MG(t,e,n){var i=new Ce;Ce.sub(i,n,e),i.normalize();var r=new Ce;return Ce.sub(r,t,e),r.dot(i)}function IG(t,e){var n=t[t.length-1];n&&n[0]===e[0]&&n[1]===e[1]||t.push(e)}function TG(t){var e=t.points,n=[],i=[];Oa(e,n,i);var r=new Ee(n[0],n[1],i[0]-n[0],i[1]-n[1]),o=r.width,a=r.height,s=r.x,l=r.y,u=new Ce,h=new Ce;return o>a?(u.x=h.x=s+o/2,u.y=l,h.y=l+a):(u.y=h.y=l+a/2,u.x=s,h.x=s+o),function(t,e,n){for(var i=t.length,r=[],o=0;or,a=_G([i,r],o?0:1,e),s=o?"width":"height",l=o?"height":"width",u=o?"x":"y",h=o?"y":"x",c=t[s]/a.length,p=0;p0)for(var b=i/n,w=-i/2;w<=i/2;w+=b){var S=Math.sin(w),M=Math.cos(w),I=0;for(x=0;x0;l/=2){var u=0,h=0;(t&l)>0&&(u=1),(e&l)>0&&(h=1),s+=l*l*(3*u^h),0===h&&(1===u&&(t=l-1-t,e=l-1-e),a=t,t=e,e=a)}return s}function HG(t){var e=1/0,n=1/0,i=-1/0,r=-1/0,o=z(t,(function(t){var o=t.getBoundingRect(),a=t.getComputedTransform(),s=o.x+o.width/2+(a?a[4]:0),l=o.y+o.height/2+(a?a[5]:0);return e=Math.min(s,e),n=Math.min(l,n),i=Math.max(s,i),r=Math.max(l,r),[s,l]}));return z(o,(function(o,a){return{cp:o,z:WG(o[0],o[1],e,n,i,r),path:t[a]}})).sort((function(t,e){return t.z-e.z})).map((function(t){return t.path}))}function YG(t){return AG(t.path,t.count)}function UG(t){return Y(t[0])}function XG(t,e){for(var n=[],i=t.length,r=0;r=0;r--)if(!n[r].many.length){var l=n[s].many;if(l.length<=1){if(!s)return n;s=0}o=l.length;var u=Math.ceil(o/2);n[r].many=l.slice(u,o),n[s].many=l.slice(0,u),s++}return n}var ZG={clone:function(t){for(var e=[],n=1-Math.pow(1-t.path.style.opacity,1/t.count),i=0;i0){var s,l,u=i.getModel("universalTransition").get("delay"),h=Object.assign({setToFinal:!0},a);UG(t)&&(s=t,l=e),UG(e)&&(s=e,l=t);for(var c=s?s===t:t.length>e.length,p=s?XG(l,s):XG(c?e:t,[c?t:e]),d=0,f=0;f1e4))for(var i=n.getIndices(),r=function(t){for(var e=t.dimensions,n=0;n0&&i.group.traverse((function(t){t instanceof Ms&&!t.animators.length&&t.animateFrom({style:{opacity:0}},r)}))}))}function iW(t){var e=t.getModel("universalTransition").get("seriesKey");return e||t.id}function rW(t){return Y(t)?t.sort().join(","):t}function oW(t){if(t.hostModel)return t.hostModel.getModel("universalTransition").get("divideShape")}function aW(t,e){for(var n=0;n=0&&r.push({dataGroupId:e.oldDataGroupIds[n],data:e.oldData[n],divide:oW(e.oldData[n]),dim:t.dimension})})),E(_o(t.to),(function(t){var i=aW(n.updatedSeries,t);if(i>=0){var r=n.updatedSeries[i].getData();o.push({dataGroupId:e.oldDataGroupIds[i],data:r,divide:oW(r),dim:t.dimension})}})),r.length>0&&o.length>0&&nW(r,o,i)}(t,i,n,e)}));else{var o=function(t,e){var n=yt(),i=yt(),r=yt();return E(t.oldSeries,(function(e,n){var o=t.oldDataGroupIds[n],a=t.oldData[n],s=iW(e),l=rW(s);i.set(l,{dataGroupId:o,data:a}),Y(s)&&E(s,(function(t){r.set(t,{key:l,dataGroupId:o,data:a})}))})),E(e.updatedSeries,(function(t){if(t.isUniversalTransitionEnabled()&&t.isAnimationEnabled()){var e=t.get("dataGroupId"),o=t.getData(),a=iW(t),s=rW(a),l=i.get(s);if(l)n.set(s,{oldSeries:[{dataGroupId:l.dataGroupId,divide:oW(l.data),data:l.data}],newSeries:[{dataGroupId:e,divide:oW(o),data:o}]});else if(Y(a)){var u=[];E(a,(function(t){var e=i.get(t);e.data&&u.push({dataGroupId:e.dataGroupId,divide:oW(e.data),data:e.data})})),u.length&&n.set(s,{oldSeries:u,newSeries:[{dataGroupId:e,data:o,divide:oW(o)}]})}else{var h=r.get(a);if(h){var c=n.get(h.key);c||(c={oldSeries:[{dataGroupId:h.dataGroupId,data:h.data,divide:oW(h.data)}],newSeries:[]},n.set(h.key,c)),c.newSeries.push({dataGroupId:e,data:o,divide:oW(o)})}}}})),n}(i,n);E(o.keys(),(function(t){var n=o.get(t);nW(n.oldSeries,n.newSeries,e)}))}E(n.updatedSeries,(function(t){t.__universalTransitionEnabled&&(t.__universalTransitionEnabled=!1)}))}for(var a=t.getSeries(),s=i.oldSeries=[],l=i.oldDataGroupIds=[],u=i.oldData=[],h=0;h)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="
                                            ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
                                            a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
                                            t
                                            ",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
                                            ",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t +}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
                                            ","
                                            "],area:[1,"",""],param:[1,"",""],thead:[1,"","
                                            "],tr:[2,"","
                                            "],col:[2,"","
                                            "],td:[3,"","
                                            "],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
                                            ","
                                            "]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); +u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll("loading");break;case 4:f||(t.content=[t.content,"body"]),t.follow=t.content[1],t.content=t.content[0]+'',delete t.title,t.tips="object"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll("tips")}e.vessel(f,function(n,r,d){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i("body").append(n[1])}():function(){l.parents("."+s[0])[0]||(l.data("display",l.css("display")).show().addClass("layui-layer-wrap").wrap(n[1]),i("#"+s[0]+a).find("."+s[5]).before(r))}()}():c.append(n[1]),i(".layui-layer-move")[0]||c.append(o.moveElem=d),e.layero=i("#"+s[0]+a),t.scrollbar||s.html.css("overflow","hidden").attr("layer-full",a)}).auto(a),2==t.type&&6==r.ie&&e.layero.find("iframe").attr("src",l[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on("resize",function(){e.offset(),(/^\d+%$/.test(t.area[0])||/^\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),s.anim[t.anim]&&e.layero.addClass(s.anim[t.anim]).data("anim",!0)}},l.pt.auto=function(e){function t(e){e=l.find(e),e.height(f[1]-c-d-2*(0|parseFloat(e.css("padding"))))}var a=this,o=a.config,l=i("#"+s[0]+e);""===o.area[0]&&o.maxWidth>0&&(r.ie&&r.ie<8&&o.btn&&l.width(l.innerWidth()),l.outerWidth()>o.maxWidth&&l.width(o.maxWidth));var f=[l.innerWidth(),l.innerHeight()],c=l.find(s[1]).outerHeight()||0,d=l.find("."+s[6]).outerHeight()||0;switch(o.type){case 2:t("iframe");break;default:""===o.area[1]?o.fixed&&f[1]>=n.height()&&(f[1]=n.height(),t("."+s[5])):t("."+s[5])}return a},l.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o="object"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):"auto"!==t.offset&&("t"===t.offset?e.offsetTop=0:"r"===t.offset?e.offsetLeft=n.width()-a[0]:"b"===t.offset?e.offsetTop=n.height()-a[1]:"l"===t.offset?e.offsetLeft=0:"lt"===t.offset?(e.offsetTop=0,e.offsetLeft=0):"lb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):"rt"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):"rb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr("minLeft")&&(e.offsetTop=n.height()-(i.find(s[1]).outerHeight()||0),e.offsetLeft=i.css("left")),i.css({top:e.offsetTop,left:e.offsetLeft})},l.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i("body"));var l={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(".layui-layer-TipsG"),c=t.tips[0];t.tips[1]||f.remove(),l.autoLeft=function(){l.left+o[0]-n.width()>0?(l.tipLeft=l.left+l.width-o[0],f.css({right:12,left:"auto"})):l.tipLeft=l.left},l.where=[function(){l.autoLeft(),l.tipTop=l.top-o[1]-10,f.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",t.tips[1])},function(){l.tipLeft=l.left+l.width+10,l.tipTop=l.top,f.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",t.tips[1])},function(){l.autoLeft(),l.tipTop=l.top+l.height+10,f.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",t.tips[1])},function(){l.tipLeft=l.left-o[0]-10,l.tipTop=l.top,f.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",t.tips[1])}],l.where[c-1](),1===c?l.top-(n.scrollTop()+o[1]+16)<0&&l.where[2]():2===c?n.width()-(l.left+l.width+o[0]+16)>0||l.where[3]():3===c?l.top-n.scrollTop()+l.height+o[1]+16-n.height()>0&&l.where[0]():4===c&&o[0]+16-l.left>0&&l.where[1](),a.find("."+s[5]).css({"background-color":t.tips[1],"padding-right":t.closeBtn?"30px":""}),a.css({left:l.tipLeft-(t.fixed?n.scrollLeft():0),top:l.tipTop-(t.fixed?n.scrollTop():0)})},l.pt.move=function(){var e=this,t=e.config,a=i(document),l=e.layero,s=l.find(t.move),f=l.find(".layui-layer-resize"),c={};return t.move&&s.css("cursor","move"),s.on("mousedown",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(l.css("left")),e.clientY-parseFloat(l.css("top"))],o.moveElem.css("cursor","move").show())}),f.on("mousedown",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[l.outerWidth(),l.outerHeight()],o.moveElem.css("cursor","se-resize").show()}),a.on("mousemove",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],s="fixed"===l.css("position");if(i.preventDefault(),c.stX=s?0:n.scrollLeft(),c.stY=s?0:n.scrollTop(),!t.moveOut){var f=n.width()-l.outerWidth()+c.stX,d=n.height()-l.outerHeight()+c.stY;af&&(a=f),od&&(o=d)}l.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0}}).on("mouseup",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd()),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},l.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find("iframe").on("load",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find("."+s[6]).children("a").on("click",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a["btn"+(e+1)]&&a["btn"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find("."+s[7]).on("click",e),a.shadeClose&&i("#layui-layer-shade"+t.index).on("click",function(){r.close(t.index)}),n.find(".layui-layer-min").on("click",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(".layui-layer-max").on("click",function(){i(this).hasClass("layui-layer-maxmin")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i("select"),function(e,t){var n=i(this);n.parents("."+s[0])[0]||1==n.attr("layer")&&i("."+s[0]).length<1&&n.removeAttr("layer").show(),n=null})},l.pt.IE6=function(e){i("select").each(function(e,t){var n=i(this);n.parents("."+s[0])[0]||"none"===n.css("display")||n.attr({layer:"1"}).hide(),n=null})},l.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css("z-index",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on("mousedown",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css("margin-left"))];e.find(".layui-layer-max").addClass("layui-layer-maxmin"),e.attr({area:t})},o.rescollbar=function(e){s.html.attr("layer-full")==e&&(s.html[0].style.removeProperty?s.html[0].style.removeProperty("overflow"):s.html[0].style.removeAttribute("overflow"),s.html.removeAttr("layer-full"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i("."+s[4]).attr("times"),i("#"+s[0]+t).find("iframe").contents().find(e)},r.getFrameIndex=function(e){return i("#"+e).parents("."+s[4]).attr("times")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame("html",e).outerHeight(),n=i("#"+s[0]+e),a=n.find(s[1]).outerHeight()||0,o=n.find("."+s[6]).outerHeight()||0;n.css({height:t+a+o}),n.find("iframe").css({height:t})}},r.iframeSrc=function(e,t){i("#"+s[0]+e).find("iframe").attr("src",t)},r.style=function(e,t,n){var a=i("#"+s[0]+e),r=a.find(".layui-layer-content"),l=a.attr("type"),f=a.find(s[1]).outerHeight()||0,c=a.find("."+s[6]).outerHeight()||0;a.attr("minLeft");l!==o.type[3]&&l!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find("."+s[6]).outerHeight(),l===o.type[2]?a.find("iframe").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom"))}))},r.min=function(e,t){var a=i("#"+s[0]+e),l=a.find(s[1]).outerHeight()||0,f=a.attr("minLeft")||181*o.minIndex+"px",c=a.css("position");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr("position",c),r.style(e,{width:180,height:l,left:f,top:n.height()-l,position:"fixed",overflow:"hidden"},!0),a.find(".layui-layer-min").hide(),"page"===a.attr("type")&&a.find(s[4]).hide(),o.rescollbar(e),a.attr("minLeft")||o.minIndex++,a.attr("minLeft",f)},r.restore=function(e){var t=i("#"+s[0]+e),n=t.attr("area").split(",");t.attr("type");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr("position"),overflow:"visible"},!0),t.find(".layui-layer-max").removeClass("layui-layer-maxmin"),t.find(".layui-layer-min").show(),"page"===t.attr("type")&&t.find(s[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i("#"+s[0]+e);o.record(a),s.html.attr("layer-full")||s.html.css("overflow","hidden").attr("layer-full",e),clearTimeout(t),t=setTimeout(function(){var t="fixed"===a.css("position");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(".layui-layer-min").hide()},100)},r.title=function(e,t){var n=i("#"+s[0]+(t||r.index)).find(s[1]);n.html(e)},r.close=function(e){var t=i("#"+s[0]+e),n=t.attr("type"),a="layer-anim-close";if(t[0]){var l="layui-layer-wrap",f=function(){if(n===o.type[1]&&"object"===t.attr("conType")){t.children(":not(."+s[5]+")").remove();for(var a=t.find("."+l),r=0;r<2;r++)a.unwrap();a.css("display",a.data("display")).removeClass(l)}else{if(n===o.type[2])try{var f=i("#"+s[4]+e)[0];f.contentWindow.document.write(""),f.contentWindow.close(),t.find("."+s[5])[0].removeChild(f)}catch(c){}t[0].innerHTML="",t.remove()}"function"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data("anim")&&t.addClass(a),i("#layui-layer-moves, #layui-layer-shade"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr("minLeft")&&(o.minIndex--,o.minLeft.push(t.attr("minLeft"))),setTimeout(function(){f()},r.ie&&r.ie<10||!t.data("anim")?0:200)}},r.closeAll=function(e){i.each(i("."+s[0]),function(){var t=i(this),n=e?t.attr("type")===e:1;n&&r.close(t.attr("times")),n=null})};var f=r.cache||{},c=function(e){return f.skin?" "+f.skin+" "+f.skin+"-"+e:""};r.prompt=function(e,t){var a="";if(e=e||{},"function"==typeof e&&(t=e),e.area){var o=e.area;a='style="width: '+o[0]+"; height: "+o[1]+';"',delete e.area}var l,s=2==e.formType?'":function(){return''}();return r.open(i.extend({type:1,btn:["确定","取消"],content:s,skin:"layui-layer-prompt"+c("prompt"),maxWidth:n.width(),success:function(e){l=e.find(".layui-layer-input"),l.focus()},resize:!1,yes:function(i){var n=l.val();""===n?l.focus():n.length>(e.maxlength||500)?r.tips("最多输入"+(e.maxlength||500)+"个字数",l,{tips:1}):t&&t(n,i,l)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{};return r.open(i.extend({type:1,skin:"layui-layer-tab"+c("tab"),resize:!1,title:function(){var e=t.length,i=1,n="";if(e>0)for(n=''+t[0].title+"";i"+t[i].title+"";return n}(),content:'
                                              '+function(){var e=t.length,i=1,n="";if(e>0)for(n='
                                            • '+(t[0].content||"no content")+"
                                            • ";i'+(t[i].content||"no content")+"";return n}()+"
                                            ",success:function(t){var n=t.find(".layui-layer-title").children(),a=t.find(".layui-layer-tabmain").children();n.on("mousedown",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var n=i(this),o=n.index();n.addClass("layui-layer-tabnow").siblings().removeClass("layui-layer-tabnow"),a.eq(o).show().siblings().hide(),"function"==typeof e.change&&e.change(o)})}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var l={};if(t=t||{},t.photos){var s=t.photos.constructor===Object,f=s?t.photos:{},d=f.data||[],u=f.start||0;if(l.imgIndex=(0|u)+1,t.img=t.img||"img",s){if(0===d.length)return r.msg("没有图片")}else{var y=i(t.photos),p=function(){d=[],y.find(t.img).each(function(e){var t=i(this);t.attr("layer-index",e),d.push({alt:t.attr("alt"),pid:t.attr("layer-pid"),src:t.attr("layer-src")||t.attr("src"),thumb:t.attr("src")})})};if(p(),0===d.length)return;if(n||y.on("click",t.img,function(){var e=i(this),n=e.attr("layer-index");r.photos(i.extend(t,{photos:{start:n,data:d,tab:t.tab},full:t.full}),!0),p()}),!n)return}l.imgprev=function(e){l.imgIndex--,l.imgIndex<1&&(l.imgIndex=d.length),l.tabimg(e)},l.imgnext=function(e,t){l.imgIndex++,l.imgIndex>d.length&&(l.imgIndex=1,t)||l.tabimg(e)},l.keyup=function(e){if(!l.end){var t=e.keyCode;e.preventDefault(),37===t?l.imgprev(!0):39===t?l.imgnext(!0):27===t&&r.close(l.index)}},l.tabimg=function(e){d.length<=1||(f.start=l.imgIndex-1,r.close(l.index),r.photos(t,!0,e))},l.event=function(){l.bigimg.hover(function(){l.imgsee.show()},function(){l.imgsee.hide()}),l.bigimg.find(".layui-layer-imgprev").on("click",function(e){e.preventDefault(),l.imgprev()}),l.bigimg.find(".layui-layer-imgnext").on("click",function(e){e.preventDefault(),l.imgnext()}),i(document).on("keyup",l.keyup)},l.loadi=r.load(1,{shade:!("shade"in t)&&.9,scrollbar:!1}),o(d[u].src,function(n){r.close(l.loadi),l.index=r.open(i.extend({type:1,area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]'+(d[u].alt||
                                            '+(d.length>1?'':"")+'
                                            '+(d[u].alt||"")+""+l.imgIndex+"/"+d.length+"
                                            ",success:function(e,i){l.bigimg=e.find(".layui-layer-phimg"),l.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),l.event(e),t.tab&&t.tab(d[u],e)},end:function(){l.end=!0,i(document).off("keyup",l.keyup)}},t))},function(){r.close(l.loadi),r.msg("当前图片地址异常
                                            是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){d.length>1&&l.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),s.html=i("html"),r.open=function(e){var t=new l(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.jquery),e.layer=r,t("layer",r)})):"function"==typeof define?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window); \ No newline at end of file diff --git a/web/static/layer/mobile/index.html b/web/static/layer/mobile/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/layer/mobile/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/layer/mobile/layer.js b/web/static/layer/mobile/layer.js new file mode 100755 index 000000000..f9cf69313 --- /dev/null +++ b/web/static/layer/mobile/layer.js @@ -0,0 +1,2 @@ +/*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */ + ;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'

                                            '+(e?n.title[0]:n.title)+"

                                            ":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e=''+n.btn[0]+"",2===t&&(e=''+n.btn[1]+""+e),'
                                            '+e+"
                                            "):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='

                                            '+(n.content||"")+"

                                            "),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"
                                            ':"")+'
                                            "+l+'
                                            '+n.content+"
                                            "+c+"
                                            ",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;o +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            + \ No newline at end of file diff --git a/web/static/layer/mobile/need/layer.css b/web/static/layer/mobile/need/layer.css new file mode 100755 index 000000000..b9dbf2010 --- /dev/null +++ b/web/static/layer/mobile/need/layer.css @@ -0,0 +1 @@ +.layui-m-layer{position:relative;z-index:19891014}.layui-m-layer *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.layui-m-layermain,.layui-m-layershade{position:fixed;left:0;top:0;width:100%;height:100%}.layui-m-layershade{background-color:rgba(0,0,0,.7);pointer-events:auto}.layui-m-layermain{display:table;font-family:Helvetica,arial,sans-serif;pointer-events:none}.layui-m-layermain .layui-m-layersection{display:table-cell;vertical-align:middle;text-align:center}.layui-m-layerchild{position:relative;display:inline-block;text-align:left;background-color:#fff;font-size:14px;border-radius:5px;box-shadow:0 0 8px rgba(0,0,0,.1);pointer-events:auto;-webkit-overflow-scrolling:touch;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@-webkit-keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}.layui-m-anim-scale{animation-name:layui-m-anim-scale;-webkit-animation-name:layui-m-anim-scale}@-webkit-keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}.layui-m-anim-up{-webkit-animation-name:layui-m-anim-up;animation-name:layui-m-anim-up}.layui-m-layer0 .layui-m-layerchild{width:90%;max-width:640px}.layui-m-layer1 .layui-m-layerchild{border:none;border-radius:0}.layui-m-layer2 .layui-m-layerchild{width:auto;max-width:260px;min-width:40px;border:none;background:0 0;box-shadow:none;color:#fff}.layui-m-layerchild h3{padding:0 10px;height:60px;line-height:60px;font-size:16px;font-weight:400;border-radius:5px 5px 0 0;text-align:center}.layui-m-layerbtn span,.layui-m-layerchild h3{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-m-layercont{padding:50px 30px;line-height:22px;text-align:center}.layui-m-layer1 .layui-m-layercont{padding:0;text-align:left}.layui-m-layer2 .layui-m-layercont{text-align:center;padding:0;line-height:0}.layui-m-layer2 .layui-m-layercont i{width:25px;height:25px;margin-left:8px;display:inline-block;background-color:#fff;border-radius:100%;-webkit-animation:layui-m-anim-loading 1.4s infinite ease-in-out;animation:layui-m-anim-loading 1.4s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-m-layerbtn,.layui-m-layerbtn span{position:relative;text-align:center;border-radius:0 0 5px 5px}.layui-m-layer2 .layui-m-layercont p{margin-top:20px}@-webkit-keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}@keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}.layui-m-layer2 .layui-m-layercont i:first-child{margin-left:0;-webkit-animation-delay:-.32s;animation-delay:-.32s}.layui-m-layer2 .layui-m-layercont i.layui-m-layerload{-webkit-animation-delay:-.16s;animation-delay:-.16s}.layui-m-layer2 .layui-m-layercont>div{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} \ No newline at end of file diff --git a/web/static/layer/skin/default/close.png b/web/static/layer/skin/default/close.png new file mode 100755 index 000000000..c1162cba9 Binary files /dev/null and b/web/static/layer/skin/default/close.png differ diff --git a/web/static/layer/skin/default/close_hover.png b/web/static/layer/skin/default/close_hover.png new file mode 100755 index 000000000..5b9151dbd Binary files /dev/null and b/web/static/layer/skin/default/close_hover.png differ diff --git a/web/static/layer/skin/default/icon-ext.png b/web/static/layer/skin/default/icon-ext.png new file mode 100755 index 000000000..15bc30025 Binary files /dev/null and b/web/static/layer/skin/default/icon-ext.png differ diff --git a/web/static/layer/skin/default/icon.png b/web/static/layer/skin/default/icon.png new file mode 100755 index 000000000..9afc7fda8 Binary files /dev/null and b/web/static/layer/skin/default/icon.png differ diff --git a/web/static/layer/skin/default/index.html b/web/static/layer/skin/default/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/layer/skin/default/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/layer/skin/default/layer.css b/web/static/layer/skin/default/layer.css new file mode 100755 index 000000000..08f3a0c9a --- /dev/null +++ b/web/static/layer/skin/default/layer.css @@ -0,0 +1 @@ +.layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}*html{background-image:url(about:blank);background-attachment:fixed}html #layuicss-skinlayercss{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;box-shadow:1px 1px 50px rgba(0,0,0,.3)}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #b2b2b2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-load{background:url(loading-1.gif) center center no-repeat #eee}.layui-layer-ico{background:url(icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position:absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;display:none;cursor:se-resize}.layui-layer{border-radius:2px;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:rollIn;animation-name:rollIn}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:bounceOut;animation-name:bounceOut;-webkit-animation-duration:.2s;animation-duration:.2s}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#f8f8f8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2e2d3c;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2d93ca}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:0 -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{background-image:url(close.png);position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;transition:All .4s ease-in-out;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-image:url(close_hover.png);transform:rotate(180deg)}.layui-layer-btn{background-color:#f6f8f8;border-top:1px solid #edf1f2;float:left;padding:9px 18px 10px;pointer-events:auto;text-align:right;width:100%;user-select:none;-webkit-user-select:none}.layui-layer-btn a{background-color:#20a53a;border-radius:3px;color:#fff;cursor:pointer;float:right;font-size:12px;font-weight:700;height:30px;line-height:28px;margin-left:8px;padding:0 12px;text-decoration:none}.layui-layer-btn a:hover{background-color:#10952a;border-color:#398439;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn1{border-color:#cbcbcb;background-color:#cbcbcb;color:#fff}.layui-layer-btn .layui-layer-btn1:hover{background-color:#c9302c}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:350px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8d8d8d;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #d3d4d3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:0}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0;box-shadow:none;border:0}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(loading-2.gif) no-repeat}.layui-layer-tips{background:0;box-shadow:none;border:0}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:5px 10px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px rgba(0,0,0,.2);background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:1px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476a7;color:#fff;border:0}.layui-layer-lan .layui-layer-btn{padding:10px;text-align:right;border-top:1px solid #e9e7e7}.layui-layer-lan .layui-layer-btn a{background:#bbb5b5;border:0}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#c9c5c5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:0}.layui-layer-molv .layui-layer-btn a{background:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92b8b1}.layui-layer-iconext{background:url(icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:220px;height:30px;margin:0 auto;line-height:30px;padding:0 5px;border:1px solid #ccc;box-shadow:1px 1px 5px rgba(0,0,0,.1) inset;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;border-bottom:1px solid #ccc;background-color:#eee;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;cursor:default;overflow:hidden}.layui-layer-tab .layui-layer-title span.layui-layer-tabnow{height:43px;border-left:1px solid #ccc;border-right:1px solid #ccc;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.xubox_tab_layer{display:block}.xubox_tabclose{position:absolute;right:10px;top:5px;cursor:pointer}.layui-layer-photos{-webkit-animation-duration:1s;animation-duration:1s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}} \ No newline at end of file diff --git a/web/static/layer/skin/default/loading-0.gif b/web/static/layer/skin/default/loading-0.gif new file mode 100755 index 000000000..fe1097f72 Binary files /dev/null and b/web/static/layer/skin/default/loading-0.gif differ diff --git a/web/static/layer/skin/default/loading-1.gif b/web/static/layer/skin/default/loading-1.gif new file mode 100755 index 000000000..a6abc9e57 Binary files /dev/null and b/web/static/layer/skin/default/loading-1.gif differ diff --git a/web/static/layer/skin/default/loading-2.gif b/web/static/layer/skin/default/loading-2.gif new file mode 100755 index 000000000..e1691ac98 Binary files /dev/null and b/web/static/layer/skin/default/loading-2.gif differ diff --git a/web/static/layer/skin/index.html b/web/static/layer/skin/index.html new file mode 100755 index 000000000..35c63e8b1 --- /dev/null +++ b/web/static/layer/skin/index.html @@ -0,0 +1,13 @@ +
                                            +

                                            目标URL

                                            +

                                            发送域名

                                            +

                                            内容替换

                                            +
                                            +
                                              +
                                            • 目标Url必需是可以访问的,否则将直接502
                                            • +
                                            • 默认本站点所有域名访问将被传递到目标服务器,请确保目标服务器已绑定域名
                                            • +
                                            • 若您是被动代理,请在发送域名处填写上目标站点的域名
                                            • +
                                            • 若您不需要内容替换功能,请直接留空
                                            • +
                                            • 可通过purge清理指定URL的缓存,示例:http://test.com/purge/test.png
                                            • +
                                            +
                                            \ No newline at end of file diff --git a/web/static/layer/skin/layer.css b/web/static/layer/skin/layer.css new file mode 100755 index 000000000..6985801d3 --- /dev/null +++ b/web/static/layer/skin/layer.css @@ -0,0 +1,7 @@ +/*! + + @Name: layer's style + @Author: 贤心 + @Blog: sentsin.com + + */html{background-image:url(about:blank);background-attachment:fixed}html #layui_layer_skinlayercss{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{top:150px;left:50%;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;box-shadow:1px 1px 50px rgba(0,0,0,.3);-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #b2b2b2;border:1px solid rgba(0,0,0,.3);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-moves{position:absolute;border:3px solid #666;border:3px solid rgba(0,0,0,.5);cursor:move;background-color:#fff;background-color:rgba(255,255,255,.3);filter:alpha(opacity=50)}.layui-layer-load{background:url(default/loading-0.gif) center center no-repeat #fff}.layui-layer-ico{background:url(default/icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}@-webkit-keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layui-anim{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.03);transform:scale(1.03)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.03);-ms-transform:scale(1.03);transform:scale(1.03)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layui-anim-close{-webkit-animation-name:bounceOut;animation-name:bounceOut;-webkit-animation-duration:.2s;animation-duration:.2s}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layui-anim-01{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layui-anim-02{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layui-anim-03{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layui-anim-04{-webkit-animation-name:rollIn;animation-name:rollIn}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-anim-05{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layui-anim-06{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 10px;height:40px;line-height:40px;border-bottom:1px solid #edf1f2;font-size:14px;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background-color:#f6f8f8}.layui-layer-setwin{position:absolute;right:10px;*right:0;top:10px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:14px;height:14px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#919191;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2d93ca}.layui-layer-setwin .layui-layer-max{background-position:-45px -55px}.layui-layer-setwin .layui-layer-max:hover{background-position:-95px -55px}.layui-layer-setwin .layui-layer-maxmin{background-position:-68px -55px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-118px -55px}.layui-layer-setwin .layui-layer-close1{width:14px;height:14px;background-position:-5px -55px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{background-position:-25px -55px}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-23px;top:-23px;width:30px;height:30px;margin-left:0;background-position:-149px -44px;*right:-18px;_right:-15px;_top:-23px;_width:14px;_height:14px;_background-position:-5px -55px}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -44px;_background-position:-25px -55px}.layui-layer-btn{background-color:#f6f8f8;border-top:1px solid #edf1f2;float:left;padding:9px 20px 10px;pointer-events:auto;text-align:right;width:100%}.layui-layer-btn a{height:30px;line-height:30px;margin:0 8px;padding:0 20px;background:#5fbfe7;color:#fff;font-size:14px;font-weight:700;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.7}.layui-layer-btn .layui-layer-btn1{background:#a6bbce}.layui-layer-dialog{min-width:240px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;font-size:14px;overflow:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:15px;left:15px;_left:-50px;width:39px;height:39px}.layui-layer-ico1{background-position:-46px 0}.layui-layer-ico2{background-position:-93px 0}.layui-layer-ico3{background-position:-145px 0}.layui-layer-ico4{background-position:-191px 0}.layui-layer-ico5{background-position:-239px 0}.layui-layer-ico6{background-position:-287px 0}.layui-layer-rim{border:6px solid #8d8d8d;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:170px;border-radius:3px;border:5px solid #8d8d8d;border:5px solid rgba(0,0,0,.4)}.layui-layer-msg .layui-layer-content .layui-layer-ico{top:10px}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:0}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:22px 20px 22px 65px;text-align:left}.layui-layer-msg .layui-layer-padding{padding:17px 20px 17px 65px}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0}.layui-layer-iframe .layui-layer-content{overflow:hidden}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0;box-shadow:none;border:0}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(default/loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(default/loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(default/loading-2.gif) no-repeat}.layui-layer-tips{background:0;box-shadow:none;border:0}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:5px 10px;font-size:12px;_float:left;border-radius:3px;box-shadow:1px 1px 3px rgba(0,0,0,.3);background-color:#F90;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#F90}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:1px;border-bottom-style:solid;border-bottom-color:#F90}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476a7;color:#fff;border:0}.layui-layer-lan .layui-layer-btn{padding:10px;text-align:right;border-top:1px solid #e9e7e7}.layui-layer-lan .layui-layer-btn a{background:#bbb5b5}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#c9c5c5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:0}.layui-layer-molv .layui-layer-btn a{background:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92b8b1} \ No newline at end of file diff --git a/web/static/layer/skin/layer.ext.css b/web/static/layer/skin/layer.ext.css new file mode 100755 index 000000000..45a32a80e --- /dev/null +++ b/web/static/layer/skin/layer.ext.css @@ -0,0 +1,8 @@ +/*! + + @Name: layer拓展样式 + @Date: 2012.12.13 + @Author: 贤心 + @blog: sentsin.com + + */.layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span{text-overflow:ellipsis;white-space:nowrap}.layui-layer-iconext{background:url(default/icon-ext.png) no-repeat}html #layui_layer_skinlayerextcss{display:none;position:absolute;width:1989px}.layui-layer-prompt .layui-layer-input{display:block;width:220px;height:30px;margin:0 auto;line-height:30px;padding:0 5px;border:1px solid #ccc;box-shadow:1px 1px 5px rgba(0,0,0,.1) inset;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;border-bottom:1px solid #ccc;background-color:#eee;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;cursor:default;overflow:hidden}.layui-layer-tab .layui-layer-title span.layui-layer-tabnow{height:36px;border-left:1px solid #ccc;border-right:1px solid #ccc;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.xubox_tab_layer{display:block}.xubox_tabclose{position:absolute;right:10px;top:5px;cursor:pointer}.layui-layer-photos{-webkit-animation-duration:1s;animation-duration:1s;background:url(default/xubox_loading1.gif) center center no-repeat #000}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal} \ No newline at end of file diff --git a/web/static/logo-original.png b/web/static/logo-original.png new file mode 100644 index 000000000..34548a078 Binary files /dev/null and b/web/static/logo-original.png differ diff --git a/web/static/logo.png b/web/static/logo.png new file mode 100644 index 000000000..75c43d250 Binary files /dev/null and b/web/static/logo.png differ diff --git a/web/static/mdw.jpg b/web/static/mdw.jpg new file mode 100644 index 000000000..f21a10aca Binary files /dev/null and b/web/static/mdw.jpg differ diff --git a/web/static/vendor/mdui/mdui.css b/web/static/vendor/mdui/mdui.css new file mode 100644 index 000000000..b3d4313ec --- /dev/null +++ b/web/static/vendor/mdui/mdui.css @@ -0,0 +1 @@ +:root{--mdui-breakpoint-xs:0px;--mdui-breakpoint-sm:600px;--mdui-breakpoint-md:840px;--mdui-breakpoint-lg:1080px;--mdui-breakpoint-xl:1440px;--mdui-breakpoint-xxl:1920px}:root{--mdui-color-primary-light:103,80,164;--mdui-color-primary-container-light:234,221,255;--mdui-color-on-primary-light:255,255,255;--mdui-color-on-primary-container-light:33,0,94;--mdui-color-inverse-primary-light:208,188,255;--mdui-color-secondary-light:98,91,113;--mdui-color-secondary-container-light:232,222,248;--mdui-color-on-secondary-light:255,255,255;--mdui-color-on-secondary-container-light:30,25,43;--mdui-color-tertiary-light:125,82,96;--mdui-color-tertiary-container-light:255,216,228;--mdui-color-on-tertiary-light:255,255,255;--mdui-color-on-tertiary-container-light:55,11,30;--mdui-color-surface-light:254,247,255;--mdui-color-surface-dim-light:222,216,225;--mdui-color-surface-bright-light:254,247,255;--mdui-color-surface-container-lowest-light:255,255,255;--mdui-color-surface-container-low-light:247,242,250;--mdui-color-surface-container-light:243,237,247;--mdui-color-surface-container-high-light:236,230,240;--mdui-color-surface-container-highest-light:230,224,233;--mdui-color-surface-variant-light:231,224,236;--mdui-color-on-surface-light:28,27,31;--mdui-color-on-surface-variant-light:73,69,78;--mdui-color-inverse-surface-light:49,48,51;--mdui-color-inverse-on-surface-light:244,239,244;--mdui-color-background-light:254,247,255;--mdui-color-on-background-light:28,27,31;--mdui-color-error-light:179,38,30;--mdui-color-error-container-light:249,222,220;--mdui-color-on-error-light:255,255,255;--mdui-color-on-error-container-light:65,14,11;--mdui-color-outline-light:121,116,126;--mdui-color-outline-variant-light:196,199,197;--mdui-color-shadow-light:0,0,0;--mdui-color-surface-tint-color-light:103,80,164;--mdui-color-scrim-light:0,0,0;--mdui-color-primary-dark:208,188,255;--mdui-color-primary-container-dark:79,55,139;--mdui-color-on-primary-dark:55,30,115;--mdui-color-on-primary-container-dark:234,221,255;--mdui-color-inverse-primary-dark:103,80,164;--mdui-color-secondary-dark:204,194,220;--mdui-color-secondary-container-dark:74,68,88;--mdui-color-on-secondary-dark:51,45,65;--mdui-color-on-secondary-container-dark:232,222,248;--mdui-color-tertiary-dark:239,184,200;--mdui-color-tertiary-container-dark:99,59,72;--mdui-color-on-tertiary-dark:73,37,50;--mdui-color-on-tertiary-container-dark:255,216,228;--mdui-color-surface-dark:20,18,24;--mdui-color-surface-dim-dark:20,18,24;--mdui-color-surface-bright-dark:59,56,62;--mdui-color-surface-container-lowest-dark:15,13,19;--mdui-color-surface-container-low-dark:29,27,32;--mdui-color-surface-container-dark:33,31,38;--mdui-color-surface-container-high-dark:43,41,48;--mdui-color-surface-container-highest-dark:54,52,59;--mdui-color-surface-variant-dark:73,69,79;--mdui-color-on-surface-dark:230,225,229;--mdui-color-on-surface-variant-dark:202,196,208;--mdui-color-inverse-surface-dark:230,225,229;--mdui-color-inverse-on-surface-dark:49,48,51;--mdui-color-background-dark:20,18,24;--mdui-color-on-background-dark:230,225,229;--mdui-color-error-dark:242,184,181;--mdui-color-error-container-dark:140,29,24;--mdui-color-on-error-dark:96,20,16;--mdui-color-on-error-container-dark:249,222,220;--mdui-color-outline-dark:147,143,153;--mdui-color-outline-variant-dark:68,71,70;--mdui-color-shadow-dark:0,0,0;--mdui-color-surface-tint-color-dark:208,188,255;--mdui-color-scrim-dark:0,0,0;font-size:16px}.mdui-theme-light,:root{color-scheme:light;--mdui-color-primary:var(--mdui-color-primary-light);--mdui-color-primary-container:var(--mdui-color-primary-container-light);--mdui-color-on-primary:var(--mdui-color-on-primary-light);--mdui-color-on-primary-container:var(--mdui-color-on-primary-container-light);--mdui-color-inverse-primary:var(--mdui-color-inverse-primary-light);--mdui-color-secondary:var(--mdui-color-secondary-light);--mdui-color-secondary-container:var(--mdui-color-secondary-container-light);--mdui-color-on-secondary:var(--mdui-color-on-secondary-light);--mdui-color-on-secondary-container:var(--mdui-color-on-secondary-container-light);--mdui-color-tertiary:var(--mdui-color-tertiary-light);--mdui-color-tertiary-container:var(--mdui-color-tertiary-container-light);--mdui-color-on-tertiary:var(--mdui-color-on-tertiary-light);--mdui-color-on-tertiary-container:var(--mdui-color-on-tertiary-container-light);--mdui-color-surface:var(--mdui-color-surface-light);--mdui-color-surface-dim:var(--mdui-color-surface-dim-light);--mdui-color-surface-bright:var(--mdui-color-surface-bright-light);--mdui-color-surface-container-lowest:var(--mdui-color-surface-container-lowest-light);--mdui-color-surface-container-low:var(--mdui-color-surface-container-low-light);--mdui-color-surface-container:var(--mdui-color-surface-container-light);--mdui-color-surface-container-high:var(--mdui-color-surface-container-high-light);--mdui-color-surface-container-highest:var(--mdui-color-surface-container-highest-light);--mdui-color-surface-variant:var(--mdui-color-surface-variant-light);--mdui-color-on-surface:var(--mdui-color-on-surface-light);--mdui-color-on-surface-variant:var(--mdui-color-on-surface-variant-light);--mdui-color-inverse-surface:var(--mdui-color-inverse-surface-light);--mdui-color-inverse-on-surface:var(--mdui-color-inverse-on-surface-light);--mdui-color-background:var(--mdui-color-background-light);--mdui-color-on-background:var(--mdui-color-on-background-light);--mdui-color-error:var(--mdui-color-error-light);--mdui-color-error-container:var(--mdui-color-error-container-light);--mdui-color-on-error:var(--mdui-color-on-error-light);--mdui-color-on-error-container:var(--mdui-color-on-error-container-light);--mdui-color-outline:var(--mdui-color-outline-light);--mdui-color-outline-variant:var(--mdui-color-outline-variant-light);--mdui-color-shadow:var(--mdui-color-shadow-light);--mdui-color-surface-tint-color:var(--mdui-color-surface-tint-color-light);--mdui-color-scrim:var(--mdui-color-scrim-light);color:rgb(var(--mdui-color-on-background));background-color:rgb(var(--mdui-color-background))}.mdui-theme-dark{color-scheme:dark;--mdui-color-primary:var(--mdui-color-primary-dark);--mdui-color-primary-container:var(--mdui-color-primary-container-dark);--mdui-color-on-primary:var(--mdui-color-on-primary-dark);--mdui-color-on-primary-container:var(--mdui-color-on-primary-container-dark);--mdui-color-inverse-primary:var(--mdui-color-inverse-primary-dark);--mdui-color-secondary:var(--mdui-color-secondary-dark);--mdui-color-secondary-container:var(--mdui-color-secondary-container-dark);--mdui-color-on-secondary:var(--mdui-color-on-secondary-dark);--mdui-color-on-secondary-container:var(--mdui-color-on-secondary-container-dark);--mdui-color-tertiary:var(--mdui-color-tertiary-dark);--mdui-color-tertiary-container:var(--mdui-color-tertiary-container-dark);--mdui-color-on-tertiary:var(--mdui-color-on-tertiary-dark);--mdui-color-on-tertiary-container:var(--mdui-color-on-tertiary-container-dark);--mdui-color-surface:var(--mdui-color-surface-dark);--mdui-color-surface-dim:var(--mdui-color-surface-dim-dark);--mdui-color-surface-bright:var(--mdui-color-surface-bright-dark);--mdui-color-surface-container-lowest:var(--mdui-color-surface-container-lowest-dark);--mdui-color-surface-container-low:var(--mdui-color-surface-container-low-dark);--mdui-color-surface-container:var(--mdui-color-surface-container-dark);--mdui-color-surface-container-high:var(--mdui-color-surface-container-high-dark);--mdui-color-surface-container-highest:var(--mdui-color-surface-container-highest-dark);--mdui-color-surface-variant:var(--mdui-color-surface-variant-dark);--mdui-color-on-surface:var(--mdui-color-on-surface-dark);--mdui-color-on-surface-variant:var(--mdui-color-on-surface-variant-dark);--mdui-color-inverse-surface:var(--mdui-color-inverse-surface-dark);--mdui-color-inverse-on-surface:var(--mdui-color-inverse-on-surface-dark);--mdui-color-background:var(--mdui-color-background-dark);--mdui-color-on-background:var(--mdui-color-on-background-dark);--mdui-color-error:var(--mdui-color-error-dark);--mdui-color-error-container:var(--mdui-color-error-container-dark);--mdui-color-on-error:var(--mdui-color-on-error-dark);--mdui-color-on-error-container:var(--mdui-color-on-error-container-dark);--mdui-color-outline:var(--mdui-color-outline-dark);--mdui-color-outline-variant:var(--mdui-color-outline-variant-dark);--mdui-color-shadow:var(--mdui-color-shadow-dark);--mdui-color-surface-tint-color:var(--mdui-color-surface-tint-color-dark);--mdui-color-scrim:var(--mdui-color-scrim-dark);color:rgb(var(--mdui-color-on-background));background-color:rgb(var(--mdui-color-background))}@media (prefers-color-scheme:dark){.mdui-theme-auto{color-scheme:dark;--mdui-color-primary:var(--mdui-color-primary-dark);--mdui-color-primary-container:var(--mdui-color-primary-container-dark);--mdui-color-on-primary:var(--mdui-color-on-primary-dark);--mdui-color-on-primary-container:var(--mdui-color-on-primary-container-dark);--mdui-color-inverse-primary:var(--mdui-color-inverse-primary-dark);--mdui-color-secondary:var(--mdui-color-secondary-dark);--mdui-color-secondary-container:var(--mdui-color-secondary-container-dark);--mdui-color-on-secondary:var(--mdui-color-on-secondary-dark);--mdui-color-on-secondary-container:var(--mdui-color-on-secondary-container-dark);--mdui-color-tertiary:var(--mdui-color-tertiary-dark);--mdui-color-tertiary-container:var(--mdui-color-tertiary-container-dark);--mdui-color-on-tertiary:var(--mdui-color-on-tertiary-dark);--mdui-color-on-tertiary-container:var(--mdui-color-on-tertiary-container-dark);--mdui-color-surface:var(--mdui-color-surface-dark);--mdui-color-surface-dim:var(--mdui-color-surface-dim-dark);--mdui-color-surface-bright:var(--mdui-color-surface-bright-dark);--mdui-color-surface-container-lowest:var(--mdui-color-surface-container-lowest-dark);--mdui-color-surface-container-low:var(--mdui-color-surface-container-low-dark);--mdui-color-surface-container:var(--mdui-color-surface-container-dark);--mdui-color-surface-container-high:var(--mdui-color-surface-container-high-dark);--mdui-color-surface-container-highest:var(--mdui-color-surface-container-highest-dark);--mdui-color-surface-variant:var(--mdui-color-surface-variant-dark);--mdui-color-on-surface:var(--mdui-color-on-surface-dark);--mdui-color-on-surface-variant:var(--mdui-color-on-surface-variant-dark);--mdui-color-inverse-surface:var(--mdui-color-inverse-surface-dark);--mdui-color-inverse-on-surface:var(--mdui-color-inverse-on-surface-dark);--mdui-color-background:var(--mdui-color-background-dark);--mdui-color-on-background:var(--mdui-color-on-background-dark);--mdui-color-error:var(--mdui-color-error-dark);--mdui-color-error-container:var(--mdui-color-error-container-dark);--mdui-color-on-error:var(--mdui-color-on-error-dark);--mdui-color-on-error-container:var(--mdui-color-on-error-container-dark);--mdui-color-outline:var(--mdui-color-outline-dark);--mdui-color-outline-variant:var(--mdui-color-outline-variant-dark);--mdui-color-shadow:var(--mdui-color-shadow-dark);--mdui-color-surface-tint-color:var(--mdui-color-surface-tint-color-dark);--mdui-color-scrim:var(--mdui-color-scrim-dark);color:rgb(var(--mdui-color-on-background));background-color:rgb(var(--mdui-color-background))}}:root{--mdui-elevation-level0:none;--mdui-elevation-level1:0 0.5px 1.5px 0 rgba(var(--mdui-color-shadow), 19%),0 0 1px 0 rgba(var(--mdui-color-shadow), 3.9%);--mdui-elevation-level2:0 0.85px 3px 0 rgba(var(--mdui-color-shadow), 19%),0 0.25px 1px 0 rgba(var(--mdui-color-shadow), 3.9%);--mdui-elevation-level3:0 1.25px 5px 0 rgba(var(--mdui-color-shadow), 19%),0 0.3333px 1.5px 0 rgba(var(--mdui-color-shadow), 3.9%);--mdui-elevation-level4:0 1.85px 6.25px 0 rgba(var(--mdui-color-shadow), 19%),0 0.5px 1.75px 0 rgba(var(--mdui-color-shadow), 3.9%);--mdui-elevation-level5:0 2.75px 9px 0 rgba(var(--mdui-color-shadow), 19%),0 0.25px 3px 0 rgba(var(--mdui-color-shadow), 3.9%)}:root{--mdui-motion-easing-linear:cubic-bezier(0, 0, 1, 1);--mdui-motion-easing-standard:cubic-bezier(0.2, 0, 0, 1);--mdui-motion-easing-standard-accelerate:cubic-bezier(0.3, 0, 1, 1);--mdui-motion-easing-standard-decelerate:cubic-bezier(0, 0, 0, 1);--mdui-motion-easing-emphasized:var(--mdui-motion-easing-standard);--mdui-motion-easing-emphasized-accelerate:cubic-bezier(0.3, 0, 0.8, 0.15);--mdui-motion-easing-emphasized-decelerate:cubic-bezier(0.05, 0.7, 0.1, 1);--mdui-motion-duration-short1:50ms;--mdui-motion-duration-short2:100ms;--mdui-motion-duration-short3:150ms;--mdui-motion-duration-short4:200ms;--mdui-motion-duration-medium1:250ms;--mdui-motion-duration-medium2:300ms;--mdui-motion-duration-medium3:350ms;--mdui-motion-duration-medium4:400ms;--mdui-motion-duration-long1:450ms;--mdui-motion-duration-long2:500ms;--mdui-motion-duration-long3:550ms;--mdui-motion-duration-long4:600ms;--mdui-motion-duration-extra-long1:700ms;--mdui-motion-duration-extra-long2:800ms;--mdui-motion-duration-extra-long3:900ms;--mdui-motion-duration-extra-long4:1000ms}.mdui-prose{line-height:1.75;word-wrap:break-word}.mdui-prose :first-child{margin-top:0}.mdui-prose :last-child{margin-bottom:0}.mdui-prose code,.mdui-prose kbd,.mdui-prose pre,.mdui-prose pre tt,.mdui-prose samp{font-family:Consolas,Courier,"Courier New",monospace}.mdui-prose caption{text-align:left}.mdui-prose [draggable=true],.mdui-prose [draggable]{cursor:move}.mdui-prose [draggable=false]{cursor:inherit}.mdui-prose dl,.mdui-prose form,.mdui-prose ol,.mdui-prose p,.mdui-prose ul{margin-top:1.25em;margin-bottom:1.25em}.mdui-prose a{text-decoration:none;outline:0;color:rgb(var(--mdui-color-primary))}.mdui-prose a:focus,.mdui-prose a:hover{border-bottom:.0625rem solid rgb(var(--mdui-color-primary))}.mdui-prose small{font-size:.875em}.mdui-prose strong{font-weight:600}.mdui-prose blockquote{margin:1.6em 2em;padding-left:1em;border-left:.25rem solid rgb(var(--mdui-color-surface-variant))}@media only screen and (max-width:599.98px){.mdui-prose blockquote{margin:1.6em 0}}.mdui-prose blockquote footer{font-size:86%;color:rgb(var(--mdui-color-on-surface-variant))}.mdui-prose mark{color:inherit;background-color:rgb(var(--mdui-color-secondary-container));border-bottom:.0625rem solid rgb(var(--mdui-color-secondary));margin:0 .375rem;padding:.125rem}.mdui-prose h1,.mdui-prose h2,.mdui-prose h3,.mdui-prose h4,.mdui-prose h5,.mdui-prose h6{font-weight:400}.mdui-prose h1 small,.mdui-prose h2 small,.mdui-prose h3 small,.mdui-prose h4 small,.mdui-prose h5 small,.mdui-prose h6 small{font-weight:inherit;font-size:65%;color:rgb(var(--mdui-color-on-surface-variant))}.mdui-prose h1 strong,.mdui-prose h2 strong,.mdui-prose h3 strong,.mdui-prose h4 strong,.mdui-prose h5 strong,.mdui-prose h6 strong{font-weight:600}.mdui-prose h1{font-size:2.5em;margin-top:0;margin-bottom:1.25em;line-height:1.1111}.mdui-prose h2{font-size:1.875em;margin-top:2.25em;margin-bottom:1.125em;line-height:1.3333}.mdui-prose h3{font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.6}.mdui-prose h4{font-size:1.25em;margin-top:1.875em;margin-bottom:.875em;line-height:1.5}.mdui-prose h2+*,.mdui-prose h3+*,.mdui-prose h4+*,.mdui-prose hr+*{margin-top:0}.mdui-prose code,.mdui-prose kbd{font-size:.875em;color:rgb(var(--mdui-color-on-surface-container));background-color:rgba(var(--mdui-color-surface-variant),.28);padding:.125rem .375rem;border-radius:var(--mdui-shape-corner-extra-small)}.mdui-prose kbd{font-size:.9em}.mdui-prose abbr[title]{text-decoration:none;cursor:help;border-bottom:.0625rem dotted rgb(var(--mdui-color-on-surface-variant))}.mdui-prose ins,.mdui-prose u{text-decoration:none;border-bottom:.0625rem solid rgb(var(--mdui-color-on-surface-variant))}.mdui-prose del{text-decoration:line-through}.mdui-prose hr{margin-top:3em;margin-bottom:3em;border:none;border-bottom:.0625rem solid rgb(var(--mdui-color-surface-variant))}.mdui-prose pre{margin-top:1.7143em;margin-bottom:1.7143em}.mdui-prose pre code{padding:.8571em 1.1429em;overflow-x:auto;-webkit-overflow-scrolling:touch;background-color:rgb(var(--mdui-color-surface-container));color:rgb(var(--mdui-color-on-surface-container));border-radius:var(--mdui-shape-corner-extra-small)}.mdui-prose ol,.mdui-prose ul{padding-left:1.625em}.mdui-prose ul{list-style-type:disc}.mdui-prose ol{list-style-type:decimal}.mdui-prose ol[type="A"]{list-style-type:upper-alpha}.mdui-prose ol[type="a"]{list-style-type:lower-alpha}.mdui-prose ol[type="I"]{list-style-type:upper-roman}.mdui-prose ol[type="i"]{list-style-type:lower-roman}.mdui-prose ol[type="1"]{list-style-type:decimal}.mdui-prose li{margin-top:.5em;margin-bottom:.5em}.mdui-prose ol>li,.mdui-prose ul>li{padding-left:.375em}.mdui-prose ol>li>p,.mdui-prose ul>li>p{margin-top:.75em;margin-bottom:.75em}.mdui-prose ol>li>:first-child,.mdui-prose ul>li>:first-child{margin-top:1.25em}.mdui-prose ol>li>:last-child,.mdui-prose ul>li>:last-child{margin-bottom:1.25em}.mdui-prose ol>li::marker{font-weight:400;color:rgb(var(--mdui-color-on-surface-variant))}.mdui-prose ul>li::marker{color:rgb(var(--mdui-color-on-surface-variant))}.mdui-prose ol ol,.mdui-prose ol ul,.mdui-prose ul ol,.mdui-prose ul ul{margin-top:.75em;margin-bottom:.75em}.mdui-prose fieldset,.mdui-prose img{border:none}.mdui-prose figure,.mdui-prose img,.mdui-prose video{margin-top:2em;margin-bottom:2em;max-width:100%}.mdui-prose figure>*{margin-top:0;margin-bottom:0}.mdui-prose figcaption{font-size:.875em;line-height:1.4286;margin-top:.8571em;color:rgb(var(--mdui-color-on-surface-variant))}.mdui-prose figcaption:empty::before{z-index:-1;cursor:text;content:attr(placeholder);color:rgb(var(--mdui-color-on-surface-variant))}.mdui-prose table{margin-top:2em;margin-bottom:2em;border:.0625rem solid rgb(var(--mdui-color-surface-variant));border-radius:var(--mdui-shape-corner-large)}.mdui-table{width:100%;overflow-x:auto;margin-top:2em;margin-bottom:2em;border:.0625rem solid rgb(var(--mdui-color-surface-variant));border-radius:var(--mdui-shape-corner-large)}.mdui-table table{margin-top:0;margin-bottom:0;border:none;border-radius:0}.mdui-prose table,.mdui-table table{width:100%;text-align:left;border-collapse:collapse;border-spacing:0}.mdui-prose td,.mdui-prose th,.mdui-table td,.mdui-table th{border-top:.0625rem solid rgb(var(--mdui-color-surface-variant))}.mdui-prose td:not(:first-child),.mdui-prose th:not(:first-child),.mdui-table td:not(:first-child),.mdui-table th:not(:first-child){border-left:.0625rem solid rgb(var(--mdui-color-surface-variant))}.mdui-prose td:not(:last-child),.mdui-prose th:not(:last-child),.mdui-table td:not(:last-child),.mdui-table th:not(:last-child){border-right:.0625rem solid rgb(var(--mdui-color-surface-variant))}.mdui-prose tbody:first-child tr:first-child td,.mdui-prose thead:first-child tr:first-child th,.mdui-table tbody:first-child tr:first-child td,.mdui-table thead:first-child tr:first-child th{border-top:0}.mdui-prose tfoot td,.mdui-prose tfoot th,.mdui-prose thead td,.mdui-prose thead th,.mdui-table tfoot td,.mdui-table tfoot th,.mdui-table thead td,.mdui-table thead th{position:relative;vertical-align:middle;padding:1.125rem 1rem;font-weight:var(--mdui-typescale-title-medium-weight);letter-spacing:var(--mdui-typescale-title-medium-tracking);line-height:var(--mdui-typescale-title-medium-line-height);color:rgb(var(--mdui-color-on-surface-variant));box-shadow:var(--mdui-elevation-level1)}.mdui-prose tbody td,.mdui-prose tbody th,.mdui-table tbody td,.mdui-table tbody th{padding:.875rem 1rem}.mdui-prose tbody th,.mdui-table tbody th{vertical-align:middle;font-weight:inherit}.mdui-prose tbody td,.mdui-table tbody td{vertical-align:baseline}:root{--mdui-shape-corner-none:0;--mdui-shape-corner-extra-small:0.25rem;--mdui-shape-corner-small:0.5rem;--mdui-shape-corner-medium:0.75rem;--mdui-shape-corner-large:1rem;--mdui-shape-corner-extra-large:1.75rem;--mdui-shape-corner-full:1000rem}:root{--mdui-state-layer-hover:0.08;--mdui-state-layer-focus:0.12;--mdui-state-layer-pressed:0.12;--mdui-state-layer-dragged:0.16}:root{--mdui-typescale-display-large-weight:400;--mdui-typescale-display-medium-weight:400;--mdui-typescale-display-small-weight:400;--mdui-typescale-display-large-line-height:4rem;--mdui-typescale-display-medium-line-height:3.25rem;--mdui-typescale-display-small-line-height:2.75rem;--mdui-typescale-display-large-size:3.5625rem;--mdui-typescale-display-medium-size:2.8125rem;--mdui-typescale-display-small-size:2.25rem;--mdui-typescale-display-large-tracking:0rem;--mdui-typescale-display-medium-tracking:0rem;--mdui-typescale-display-small-tracking:0rem;--mdui-typescale-headline-large-weight:400;--mdui-typescale-headline-medium-weight:400;--mdui-typescale-headline-small-weight:400;--mdui-typescale-headline-large-line-height:2.5rem;--mdui-typescale-headline-medium-line-height:2.25rem;--mdui-typescale-headline-small-line-height:2rem;--mdui-typescale-headline-large-size:2rem;--mdui-typescale-headline-medium-size:1.75rem;--mdui-typescale-headline-small-size:1.5rem;--mdui-typescale-headline-large-tracking:0rem;--mdui-typescale-headline-medium-tracking:0rem;--mdui-typescale-headline-small-tracking:0rem;--mdui-typescale-title-large-weight:400;--mdui-typescale-title-medium-weight:500;--mdui-typescale-title-small-weight:500;--mdui-typescale-title-large-line-height:1.75rem;--mdui-typescale-title-medium-line-height:1.5rem;--mdui-typescale-title-small-line-height:1.25rem;--mdui-typescale-title-large-size:1.375rem;--mdui-typescale-title-medium-size:1rem;--mdui-typescale-title-small-size:0.875rem;--mdui-typescale-title-large-tracking:0rem;--mdui-typescale-title-medium-tracking:0.009375rem;--mdui-typescale-title-small-tracking:0.00625rem;--mdui-typescale-label-large-weight:500;--mdui-typescale-label-medium-weight:500;--mdui-typescale-label-small-weight:500;--mdui-typescale-label-large-line-height:1.25rem;--mdui-typescale-label-medium-line-height:1rem;--mdui-typescale-label-small-line-height:0.375rem;--mdui-typescale-label-large-size:0.875rem;--mdui-typescale-label-medium-size:0.75rem;--mdui-typescale-label-small-size:0.6875rem;--mdui-typescale-label-large-tracking:0.00625rem;--mdui-typescale-label-medium-tracking:0.03125rem;--mdui-typescale-label-small-tracking:0.03125rem;--mdui-typescale-body-large-weight:400;--mdui-typescale-body-medium-weight:400;--mdui-typescale-body-small-weight:400;--mdui-typescale-body-large-line-height:1.5rem;--mdui-typescale-body-medium-line-height:1.25rem;--mdui-typescale-body-small-line-height:1rem;--mdui-typescale-body-large-size:1rem;--mdui-typescale-body-medium-size:0.875rem;--mdui-typescale-body-small-size:0.75rem;--mdui-typescale-body-large-tracking:0.009375rem;--mdui-typescale-body-medium-tracking:0.015625rem;--mdui-typescale-body-small-tracking:0.025rem}.mdui-lock-screen{overflow:hidden!important} \ No newline at end of file diff --git a/web/static/vendor/mdui/mdui.global.js b/web/static/vendor/mdui/mdui.global.js new file mode 100644 index 000000000..d52b650ce --- /dev/null +++ b/web/static/vendor/mdui/mdui.global.js @@ -0,0 +1,22 @@ +/*! + * mdui 2.0.0 (https://www.mdui.org) + * Copyright 2016-2023 zdhxiong + * Licensed under MIT + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).mdui={})}(this,(function(e){"use strict";function t(e){return null!==e&&"object"==typeof e&&"constructor"in e&&e.constructor===Object}function i(e={},o={}){Object.keys(o).forEach((r=>{void 0===e[r]?e[r]=o[r]:t(o[r])&&t(e[r])&&Object.keys(o[r]).length>0&&i(e[r],o[r])}))}const o={body:{},addEventListener(){},removeEventListener(){},activeElement:{blur(){},nodeName:""},querySelector:()=>null,querySelectorAll:()=>[],getElementById:()=>null,createEvent:()=>({initEvent(){}}),createElement:()=>({children:[],childNodes:[],style:{},setAttribute(){},getElementsByTagName:()=>[]}),createElementNS:()=>({}),importNode:()=>null,location:{hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",protocol:"",search:""}};function r(){const e="undefined"!=typeof document?document:{};return i(e,o),e}const n={document:o,navigator:{userAgent:""},location:{hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",protocol:"",search:""},history:{replaceState(){},pushState(){},go(){},back(){}},CustomEvent:function(){return this},addEventListener(){},removeEventListener(){},getComputedStyle:()=>({getPropertyValue:()=>""}),Image(){},Date(){},screen:{},setTimeout(){},clearTimeout(){},matchMedia:()=>({}),requestAnimationFrame:e=>"undefined"==typeof setTimeout?(e(),null):setTimeout(e,0),cancelAnimationFrame(e){"undefined"!=typeof setTimeout&&clearTimeout(e)}};function s(){const e="undefined"!=typeof window?window:{};return i(e,n),e}const a=(e,t)=>(null==e?void 0:e.nodeName.toLowerCase())===t.toLowerCase(),l=e=>"function"==typeof e,c=e=>"string"==typeof e,d=e=>"number"==typeof e,h=e=>"boolean"==typeof e,u=e=>void 0===e,p=e=>null===e,m=e=>"undefined"!=typeof Window&&e instanceof Window,v=e=>"undefined"!=typeof Document&&e instanceof Document,f=e=>"undefined"!=typeof Element&&e instanceof Element,g=e=>!l(e)&&!m(e)&&d(e.length),b=e=>"object"==typeof e&&null!==e,y=e=>v(e)?e.documentElement:e,w=e=>e.replace(/-([a-z])/g,((e,t)=>t.toUpperCase())),k=e=>e?e.replace(/^./,e[0].toLowerCase()).replace(/[A-Z]/g,(e=>"-"+e.toLowerCase())):e,C=()=>!1,x=()=>!0,$=(e,t)=>{for(let i=0;i{const i=Object.keys(e);for(let o=0;o{this[t]=e})),this.length=e.length,this):this}}const T=e=>r().createElement(e),E=(e,t)=>e.appendChild(t),I=e=>e.parentNode?e.parentNode.removeChild(e):e,A=(e,t)=>{const i=T(t);return i.innerHTML=e,[].slice.call(i.childNodes)},P=(()=>{const e=function(t){const i=r();if(!t)return new S;if(t instanceof S)return t;if(l(t))return/complete|loaded|interactive/.test(i.readyState)&&i.body?t.call(i,e):i.addEventListener("DOMContentLoaded",(()=>t.call(i,e)),!1),new S([i]);if(c(t)){const e=t.trim();if(e.startsWith("<")&&e.endsWith(">")){let t="div";return R({li:"ul",tr:"tbody",td:"tr",th:"tr",tbody:"table",option:"select"},((i,o)=>{if(e.startsWith(`<${i}`))return t=o,!1})),new S(A(e,t))}return new S(i.querySelectorAll(t))}return!g(t)||(o=t,"undefined"!=typeof Node&&o instanceof Node)?new S([t]):new S(t);var o};return e.fn=S.prototype,e})(),M=(e,t)=>($(t,(t=>{e.push(t)})),e),D=e=>[...new Set(e)];P.fn.get=function(e){return void 0===e?[].slice.call(this):this[e>=0?e:e+this.length]},P.fn.add=function(e){return new S(D(M(this.get(),P(e).get())))};const B=(e,t,i)=>{const o=e.getAttribute(t);return p(o)?i:o},L=(e,t)=>{e.removeAttribute(t)},_=(e,t,i)=>{p(i)?L(e,t):e.setAttribute(t,i)};P.fn.each=function(e){return $(this,((t,i)=>e.call(t,i,t)))},$(["add","remove","toggle"],(e=>{P.fn[`${e}Class`]=function(t){return"remove"!==e||arguments.length?this.each(((i,o)=>{if(!f(o))return;const r=(l(t)?t.call(o,i,B(o,"class","")):t).split(" ").filter((e=>e));$(r,(t=>{o.classList[e](t)}))})):this.each(((e,t)=>{_(t,"class","")}))}})),$(["insertBefore","insertAfter"],((e,t)=>{P.fn[e]=function(e){const i=t?P(this.get().reverse()):this,o=P(e),r=[];return o.each(((e,o)=>{o.parentNode&&i.each(((i,n)=>{const s=e?n.cloneNode(!0):n,a=t?o.nextSibling:o;r.push(s),o.parentNode.insertBefore(s,a)}))})),P(t?r.reverse():r)}}));function O(e,t){return g(e)?$(e,((e,i)=>t.call(e,i,e))):R(e,t)}function z(e,t){const i=s();let o;const r=[];return O(e,((e,n)=>{o=t.call(i,n,e),null!=o&&r.push(o)})),[].concat(...r)}$(["before","after"],((e,t)=>{P.fn[e]=function(...e){return 1===t&&(e=e.reverse()),this.each(((i,o)=>{const r=l(e[0])?[e[0].call(o,i,o.innerHTML)]:e;$(r,(e=>{let r;r=(e=>c(e)&&!(e.startsWith("<")&&e.endsWith(">")))(e)?P(A(e,"div")):i&&f(e)?P(e.cloneNode(!0)):P(e),r[t?"insertAfter":"insertBefore"](o)}))}))}})),P.fn.map=function(e){return new S(z(this,((t,i)=>e.call(t,i,t))))},P.fn.clone=function(){return this.map((function(){return this.cloneNode(!0)}))},P.fn.is=function(e){let t=!1;if(l(e))return this.each(((i,o)=>{e.call(o,i,o)&&(t=!0)})),t;if(c(e))return this.each(((i,o)=>{v(o)||m(o)||o.matches.call(o,e)&&(t=!0)})),t;const i=P(e);return this.each(((e,o)=>{i.each(((e,i)=>{o===i&&(t=!0)}))})),t},P.fn.remove=function(e){return this.each(((t,i)=>{e&&!P(i).is(e)||I(i)}))},$(["prepend","append"],((e,t)=>{P.fn[e]=function(...e){return this.each(((i,o)=>{const r=o.childNodes,n=r.length,s=n?r[t?n-1:0]:T("div");n||E(o,s);let a=l(e[0])?[e[0].call(o,i,o.innerHTML)]:e;i&&(a=a.map((e=>c(e)?e:P(e).clone()))),P(s)[t?"after":"before"](...a),n||I(s)}))}})),$(["appendTo","prependTo"],((e,t)=>{P.fn[e]=function(e){const i=[],o=P(e).map(((e,o)=>{const r=o.childNodes,n=r.length;if(n)return r[t?0:n-1];const s=T("div");return E(o,s),i.push(s),s})),r=this[t?"insertBefore":"insertAfter"](o);return P(i).remove(),r}}));const F=(e,t)=>s().getComputedStyle(e).getPropertyValue(k(t)),N=e=>"border-box"===F(e,"box-sizing"),V=(e,t,i)=>{const o="width"===t?["Left","Right"]:["Top","Bottom"];return[0,1].reduce(((t,r,n)=>{let s=i+o[n];return"border"===i&&(s+="Width"),t+parseFloat(F(e,s)||"0")}),0)},H=(e,t)=>{if("width"===t||"height"===t){const i=e.getBoundingClientRect()[t];return N(e)?`${i}px`:i-V(e,t,"border")-V(e,t,"padding")+"px"}return F(e,t)},K=["animation-iteration-count","column-count","fill-opacity","flex-grow","flex-shrink","font-weight","grid-area","grid-column","grid-column-end","grid-column-start","grid-row","grid-row-end","grid-row-start","line-height","opacity","order","orphans","widows","z-index","zoom"];$(["attr","prop","css"],((e,t)=>{const i=(e,i)=>0===t?B(e,i):1===t?e[i]:H(e,i);P.fn[e]=function(o,r){if(b(o))return R(o,((t,i)=>{this[e](t,i)})),this;if(1===arguments.length){const e=this[0];return f(e)?i(e,o):void 0}return this.each(((e,n)=>{((e,i,o)=>{if(u(o))return;if(0===t)return _(e,i,o);if(1===t)return void(e[i]=o);i=k(i),e.style.setProperty(i,d(o)?`${o}${i.startsWith("--")||K.includes(i)?"":"px"}`:o)})(n,o,l(r)?r.call(n,e,i(n,o)):r)}))}})),P.fn.children=function(e){const t=[];return this.each(((i,o)=>{$(o.childNodes,(i=>{f(i)&&(e&&!P(i).is(e)||t.push(i))}))})),new S(D(t))},P.fn.slice=function(...e){return new S([].slice.apply(this,e))},P.fn.eq=function(e){const t=-1===e?this.slice(e):this.slice(e,+e+1);return new S(t)};const U=(e,t,i,o,r)=>{const n=[];let s;return e.each(((e,a)=>{for(s=a[i];s&&f(s);){if(2===t){if(o&&P(s).is(o))break;r&&!P(s).is(r)||n.push(s)}else{if(0===t){o&&!P(s).is(o)||n.push(s);break}o&&!P(s).is(o)||n.push(s)}s=s[i]}})),new S(D(n))};$(["","s","sUntil"],((e,t)=>{P.fn[`parent${e}`]=function(e,i){const o=t?P(this.get().reverse()):this;return U(o,t,"parentNode",e,i)}})),P.fn.closest=function(e){if(this.is(e))return this;const t=[];return this.parents().each(((i,o)=>{if(P(o).is(e))return t.push(o),!1})),new S(t)};const q=new WeakMap,j=e=>{var t;return null!==(t=q.get(e))&&void 0!==t?t:{}},G=(e,t)=>{const i=j(e),o=w(t);return o in i?i[o]:void 0},W=(e,t)=>{const i=j(e);R(t,((e,t)=>{i[w(e)]=t})),q.set(e,i)},Y=(e,t,i)=>{W(e,{[t]:i})},X=/^(?:{[\w\W]*\}|\[[\w\W]*\])$/,J=(e,t,i)=>{if(u(i)&&1===e.nodeType&&(i=e.dataset[t],c(i)))try{i=(e=>"true"===e||"false"!==e&&("null"===e?null:e===+e+""?+e:X.test(e)?JSON.parse(e):e))(i)}catch(e){}return i};P.fn.data=function(e,t){if(u(e)){if(!this.length)return;const e=this[0],t=j(e);return 1!==e.nodeType||R(e.dataset,(i=>{t[i]=J(e,i,t[i])})),t}return b(e)?this.each((function(){W(this,e)})):2===arguments.length&&u(t)?this:u(t)?this.length?J(this[0],w(e),G(this[0],e)):void 0:this.each((function(){Y(this,e,t)}))},P.fn.empty=function(){return this.each(((e,t)=>{t.innerHTML=""}))},P.fn.extend=function(e){return R(e,((e,t)=>{P.fn[e]=t})),this},P.fn.filter=function(e){if(l(e))return this.map(((t,i)=>e.call(i,t,i)?i:void 0));if(c(e))return this.map(((t,i)=>P(i).is(e)?i:void 0));const t=P(e);return this.map(((e,i)=>t.get().includes(i)?i:void 0))},P.fn.find=function(e){const t=[];return this.each(((i,o)=>{M(t,P(o.querySelectorAll(e)).get())})),new S(t)},P.fn.first=function(){return this.eq(0)};const Z=(e,t)=>e!==t&&y(e).contains(t);P.fn.has=function(e){const t=c(e)?this.find(e):P(e),{length:i}=t;return this.map((function(){for(let e=0;e{const s=i=>V(e,t.toLowerCase(),i)*n;return 2===o&&r&&(i+=s("margin")),N(e)?(0===o&&(i-=s("border")),1===o&&(i-=s("border"),i-=s("padding"))):(0===o&&(i+=s("padding")),2===o&&(i+=s("border"),i+=s("padding"))),i},ee=(e,t,i,o)=>{const n=r(),s=`client${t}`,a=`scroll${t}`,l=`offset${t}`,c=`inner${t}`;if(m(e))return 2===i?e[c]:y(n)[s];if(v(e)){const t=y(e);return Math.max(e.body[a],t[a],e.body[l],t[l],t[s])}const d=parseFloat(F(e,t.toLowerCase())||"0");return Q(e,t,d,i,o,1)};$(["Width","Height"],(e=>{$([`inner${e}`,e.toLowerCase(),`outer${e}`],((t,i)=>{P.fn[t]=function(t,o){const r=arguments.length&&(i<2||!h(t)),n=!0===t||!0===o;return r?this.each(((o,r)=>((e,t,i,o,r,n)=>{let s=l(n)?n.call(e,t,ee(e,i,o,r)):n;if(null==s)return;const a=P(e),d=i.toLowerCase();if(c(s)&&["auto","inherit",""].includes(s))return void a.css(d,s);const h=s.toString().replace(/\b[0-9.]*/,""),u=parseFloat(s);s=Q(e,i,u,o,r,-1)+(h||"px"),a.css(d,s)})(r,o,e,i,n,t))):this.length?ee(this[0],e,i,n):void 0}}))})),P.fn.hide=function(){return this.each(((e,t)=>{t.style.display="none"}))},$(["val","html","text"],((e,t)=>{const i=["value","innerHTML","textContent"][t],o=e=>{if(2===t)return z(e,(e=>y(e)[i])).join("");if(!e.length)return;const o=e[0],r=P(o);return 0===t&&r.is("select[multiple]")?z(r.find("option:checked"),(e=>e.value)):o[i]};P.fn[e]=function(e){return arguments.length?this.each(((r,n)=>{const s=P(n),a=l(e)?e.call(n,r,o(s)):e;0===t&&Array.isArray(a)?s.is("select[multiple]")?z(s.find("option"),(e=>e.selected=a.includes(e.value))):n.checked=a.includes(n.value):((e,o)=>{if(u(o)){if(0!==t)return;o=""}1===t&&f(o)&&(o=o.outerHTML),e[i]=o})(n,a)})):o(this)}})),P.fn.index=function(e){return arguments.length?c(e)?P(e).get().indexOf(this[0]):this.get().indexOf(P(e)[0]):this.eq(0).parent().children().get().indexOf(this[0])},P.fn.last=function(){return this.eq(-1)},$(["","All","Until"],((e,t)=>{P.fn[`next${e}`]=function(e,i){return U(this,t,"nextElementSibling",e,i)}})),P.fn.not=function(e){const t=this.filter(e);return this.map(((e,i)=>t.index(i)>-1?void 0:i))};const te=s().CustomEvent;class ie extends te{constructor(e,t){super(e,t),this.data=t.data,this.namespace=t.namespace}}const oe=new WeakMap;let re=1;const ne=e=>(oe.has(e)||oe.set(e,++re),oe.get(e)),se=new Map,ae=e=>{const t=ne(e);return se.get(t)||se.set(t,[]).get(t)},le=e=>{const t=e.split(".");return{type:t[0],namespace:t.slice(1).sort().join(" ")}},ce=e=>new RegExp("(?:^| )"+e.replace(" "," .* ?")+"(?: |$)"),de=(e,t,i,o)=>{const r=ae(e),n=t=>{delete r[t.id],e.removeEventListener(t.type,t.proxy,!1)};t?t.split(" ").forEach((t=>{t&&((e,t,i,o)=>{const r=le(t);return ae(e).filter((e=>e&&(!r.type||e.type===r.type)&&(!r.namespace||ce(r.namespace).test(e.namespace))&&(!i||ne(e.func)===ne(i))&&(!o||e.selector===o)))})(e,t,i,o).forEach((e=>{n(e)}))})):r.forEach((e=>{n(e)}))};function he(e,...t){return $(t,(t=>{R(t,((t,i)=>{u(i)||(e[t]=i)}))})),e}P.fn.off=function(e,t,i){return b(e)?(R(e,((e,i)=>{this.off(e,t,i)})),this):((!1===t||l(t))&&(i=t,t=void 0),!1===i&&(i=C),this.each((function(){de(this,e,i,t)})))},P.fn.offsetParent=function(){const e=r();return this.map((function(){let t=this.offsetParent;for(;t&&"static"===P(t).css("position");)t=t.offsetParent;return t||e.documentElement}))};const ue=(e,t)=>parseFloat(e.css(t));P.fn.position=function(){if(!this.length)return;const e=this.eq(0);let t,i={left:0,top:0};if("fixed"===e.css("position"))t=e[0].getBoundingClientRect();else{t=e.offset();const o=e.offsetParent();i=o.offset(),i.top+=ue(o,"border-top-width"),i.left+=ue(o,"border-left-width")}return{top:t.top-i.top-ue(e,"margin-top"),left:t.left-i.left-ue(e,"margin-left")}};const pe=e=>{if(!e.getClientRects().length)return{top:0,left:0};const{top:t,left:i}=e.getBoundingClientRect(),{pageYOffset:o,pageXOffset:r}=e.ownerDocument.defaultView;return{top:t+o,left:i+r}};P.fn.offset=function(e){if(!arguments.length){if(!this.length)return;return pe(this[0])}return this.each((function(t){((e,t,i)=>{const o=P(e),r=o.css("position");"static"===r&&o.css("position","relative");const n=pe(e),s=o.css("top"),a=o.css("left");let c,d;if("absolute"!==r&&"fixed"!==r||!(s+a).includes("auto"))c=parseFloat(s),d=parseFloat(a);else{const e=o.position();c=e.top,d=e.left}const h=l(t)?t.call(e,i,he({},n)):t;o.css({top:null!=h.top?h.top-n.top+c:void 0,left:null!=h.left?h.left-n.left+d:void 0})})(this,e,t)}))},P.fn.on=function(e,t,i,o,r){if(b(e))return c(t)||(i=i||t,t=void 0),R(e,((e,o)=>{this.on(e,t,i,o,r)})),this;if(null==i&&null==o?(o=t,i=t=void 0):null==o&&(c(t)?(o=i,i=void 0):(o=i,i=t,t=void 0)),!1===o)o=C;else if(!o)return this;if(r){const e=this,i=o;o=function(r,...n){return e.off(r.type,t,o),i.call(this,r,...n)}}return this.each((function(){((e,t,i,o,r)=>{let n=!1;b(o)&&o.useCapture&&(n=!0),t.split(" ").forEach((t=>{if(!t)return;const s=le(t),a=(e,t)=>{!1===i.apply(t,null===e.detail?[e]:[e].concat(e.detail))&&(e.preventDefault(),e.stopPropagation())},l=t=>{t.namespace&&!ce(t.namespace).test(s.namespace)||(t.data=o,r?P(e).find(r).get().reverse().forEach((e=>{(e===t.target||Z(e,t.target))&&a(t,e)})):a(t,e))},c={type:s.type,namespace:s.namespace,func:i,selector:r,id:ae(e).length,proxy:l};ae(e).push(c),e.addEventListener(c.type,l,n)}))})(this,e,o,i,t)}))},P.fn.one=function(e,t,i,o){return this.on(e,t,i,o,!0)},$(["","All","Until"],((e,t)=>{P.fn[`prev${e}`]=function(e,i){const o=t?P(this.get().reverse()):this;return U(o,t,"previousElementSibling",e,i)}})),P.fn.removeAttr=function(e){const t=e.split(" ").filter((e=>e));return this.each((function(){$(t,(e=>{L(this,e)}))}))};const me=(e,t)=>{if(u(t))return(e=>{q.delete(e)})(e);((e,t)=>{const i=j(e);$(t,(e=>{const t=w(e);delete i[t]})),q.set(e,i)})(e,c(t)?t.split(" ").filter((e=>e)):t)};P.fn.removeData=function(e){return this.each(((t,i)=>{me(i,e)}))},P.fn.removeProp=function(e){return this.each(((t,i)=>{try{delete i[e]}catch(e){}}))},P.fn.replaceWith=function(e){return this.each(((t,i)=>{let o=e;l(o)?o=o.call(i,t,i.innerHTML):t&&!c(o)&&(o=P(o).clone()),P(i).before(o)})),this.remove()},P.fn.replaceAll=function(e){return P(e).map(((e,t)=>(P(t).replaceWith(e?this.clone():this),this.get())))};const ve=e=>{if(!b(e)&&!Array.isArray(e))return"";const t=[],i=(e,o)=>{let r;b(o)?R(o,((t,n)=>{r=Array.isArray(o)&&!b(n)?"":t,i(`${e}[${r}]`,n)})):(r=null==o||""===o?"=":`=${encodeURIComponent(o)}`,t.push(encodeURIComponent(e)+r))};return Array.isArray(e)?$(e,(({name:e,value:t})=>i(e,t))):R(e,i),t.join("&")},fe=new WeakMap,ge=e=>{const t=[];return e.each(((e,i)=>{const o=i instanceof HTMLFormElement?(e=>{const t=[...e.getRootNode().querySelectorAll("*")],i=[...e.elements],o=fe.get(e);return[...i,...o?Array.from(o):[]].sort(((e,i)=>t.indexOf(e)t.indexOf(i)?1:0))})(i):[i];P(o).each(((e,i)=>{const o=P(i),r=i.type,n=i.nodeName.toLowerCase();"fieldset"===n||!i.name||i.disabled||!["input","select","textarea","keygen","mdui-checkbox","mdui-radio-group","mdui-switch","mdui-text-field","mdui-select","mdui-slider","mdui-range-slider","mdui-segmented-button-group"].includes(n)||["submit","button","image","reset","file"].includes(r)||["radio","checkbox"].includes(r)&&!i.checked||["mdui-checkbox","mdui-switch"].includes(n)&&!i.checked||t.push({name:i.name,value:o.val()})}))})),t};P.fn.serializeArray=function(){return ge(this).map((e=>Array.isArray(e.value)?e.value.map((t=>({name:e.name,value:t}))):e)).flat()},P.fn.serialize=function(){return ve(this.serializeArray())},P.fn.serializeObject=function(){const e={};return ge(this).forEach((t=>{const{name:i,value:o}=t;if(e.hasOwnProperty(i)){const t=e[i];Array.isArray(t)||(e[i]=[t]),Array.isArray(o)?e[i].push(...o):e[i].push(o)}else e[i]=o})),e};const be={};P.fn.show=function(){return this.each(((e,t)=>{"none"===t.style.display&&(t.style.display=""),"none"===H(t,"display")&&(t.style.display=(e=>{const t=r();let i,o;return be[e]||(i=T(e),E(t.body,i),o=H(i,"display"),I(i),"none"===o&&(o="block"),be[e]=o),be[e]})(t.nodeName))}))},P.fn.siblings=function(e){return this.prevAll(e).add(this.nextAll(e))},P.fn.toggle=function(){return this.each(((e,t)=>{"none"===H(t,"display")?P(t).show():P(t).hide()}))},P.fn.trigger=function(e,t=null,i){const{type:o,namespace:r}=le(e),n=new ie(o,{detail:t,data:null,namespace:r,bubbles:!0,cancelable:!1,composed:!0,...i});return this.each(((e,t)=>{t.dispatchEvent(n)}))};const ye="ajaxSuccess",we="ajaxError",ke="ajaxComplete",Ce={},xe=(e,t)=>`${e}&${t}`.replace(/[&?]{1,2}/,"?"),$e=e=>{const t=r(),i=s();let o=!1;const n={},a={},l=(e=>{const t={url:"",method:"GET",data:"",processData:!0,async:!0,cache:!0,username:"",password:"",headers:{},xhrFields:{},statusCode:{},dataType:"",contentType:"application/x-www-form-urlencoded",timeout:0,global:!0};return R(Ce,((e,i)=>{["beforeSend","success","error","complete","statusCode"].includes(e)||u(i)||(t[e]=i)})),he({},t,e)})(e),d=l.method.toUpperCase();let{data:h,url:p}=l;p=p||i.location.toString();const{processData:m,async:v,cache:f,username:g,password:b,headers:y,xhrFields:w,statusCode:k,dataType:C,contentType:x,timeout:S,global:T}=l,E=(e=>["GET","HEAD"].includes(e))(d);!h||!E&&!m||c(h)||h instanceof ArrayBuffer||h instanceof Blob||h instanceof Document||h instanceof FormData||(h=ve(h)),h&&E&&(p=xe(p,h),h=null);const I=(e,i,...r)=>{let s,c;T&&P(t).trigger(e,"success"===i?a:n),i in Ce&&(s=Ce[i](...r)),l[i]&&(c=l[i](...r)),"beforeSend"===i&&[s,c].includes(!1)&&(o=!0)};return(()=>{let e;return new Promise(((t,r)=>{const c=e=>r(new Error(e));E&&!f&&(p=xe(p,`_=${Date.now()}`));const m=new XMLHttpRequest;let T;if(m.open(d,p,v,g,b),(x||h&&!E&&!1!==x)&&m.setRequestHeader("Content-Type",x),"json"===C&&m.setRequestHeader("Accept","application/json, text/javascript"),R(y,((e,t)=>{u(t)||m.setRequestHeader(e,t+"")})),(e=>{const t=s();return/^([\w-]+:)?\/\/([^/]+)/.test(e)&&RegExp.$2!==t.location.host})(p)||m.setRequestHeader("X-Requested-With","XMLHttpRequest"),R(w,((e,t)=>{m[e]=t})),n.xhr=a.xhr=m,n.options=a.options=l,m.onload=()=>{var i;T&&clearTimeout(T);const o=(r=m.status)>=200&&r<300||[0,304].includes(r);var r;let n;if(o)if(e=204===m.status||"HEAD"===d?"nocontent":304===m.status?"notmodified":"success","json"===C||!C&&(m.getResponseHeader("content-type")||"").includes("json")){try{n="HEAD"===d?void 0:JSON.parse(m.responseText),a.response=n}catch(t){e="parsererror",I(we,"error",m,e),c(e)}"parsererror"!==e&&(I(ye,"success",n,e,m),t(n))}else n="HEAD"===d?void 0:"text"===m.responseType||""===m.responseType?m.responseText:m.response,a.response=n,I(ye,"success",n,e,m),t(n);else e="error",I(we,"error",m,e),c(e);$([null!==(i=Ce.statusCode)&&void 0!==i?i:{},k],(t=>{t[m.status]&&(o?t[m.status](n,e,m):t[m.status](m,e))})),I(ke,"complete",m,e)},m.onerror=()=>{T&&clearTimeout(T),I(we,"error",m,m.statusText),I(ke,"complete",m,"error"),c(m.statusText)},m.onabort=()=>{let e="abort";T&&(e="timeout",clearTimeout(T)),I(we,"error",m,e),I(ke,"complete",m,e),c(e)},I("ajaxStart","beforeSend",m,l),o)return c("cancel");S>0&&(T=i.setTimeout((()=>m.abort()),S)),m.send(h)}))})()};P.ajax=$e;function Re(e,t,i,o){var r,n=arguments.length,s=n<3?t:null===o?o=Object.getOwnPropertyDescriptor(t,i):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,i,o);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(s=(n<3?r(s):n>3?r(t,i,s):r(t,i))||s);return n>3&&s&&Object.defineProperty(t,i,s),s}P.ajaxSetup=e=>he(Ce,e),P.contains=Z,P.data=function(e,t,i){return b(t)?(W(e,t),t):u(i)?u(t)?j(e):G(e,t):(Y(e,t,i),i)},P.each=O,P.extend=function(e,...t){return t.length?he(e,...t):(R(e,((e,t)=>{this[e]=t})),this)},P.map=z,P.merge=M,P.param=ve,P.removeData=me,P.unique=D,"function"==typeof SuppressedError&&SuppressedError;const Se=window,Te=Se.ShadowRoot&&(void 0===Se.ShadyCSS||Se.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Ee=Symbol(),Ie=new WeakMap;let Ae=class{constructor(e,t,i){if(this._$cssResult$=!0,i!==Ee)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(Te&&void 0===e){const i=void 0!==t&&1===t.length;i&&(e=Ie.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),i&&Ie.set(t,e))}return e}toString(){return this.cssText}};const Pe=(e,...t)=>{const i=1===e.length?e[0]:t.reduce(((t,i,o)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+e[o+1]),e[0]);return new Ae(i,e,Ee)},Me=Te?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const i of e.cssRules)t+=i.cssText;return(e=>new Ae("string"==typeof e?e:e+"",void 0,Ee))(t)})(e):e;var De;const Be=window,Le=Be.trustedTypes,_e=Le?Le.emptyScript:"",Oe=Be.reactiveElementPolyfillSupport,ze={toAttribute(e,t){switch(t){case Boolean:e=e?_e:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let i=e;switch(t){case Boolean:i=null!==e;break;case Number:i=null===e?null:Number(e);break;case Object:case Array:try{i=JSON.parse(e)}catch(e){i=null}}return i}},Fe=(e,t)=>t!==e&&(t==t||e==e),Ne={attribute:!0,type:String,converter:ze,reflect:!1,hasChanged:Fe},Ve="finalized";let He=class extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u()}static addInitializer(e){var t;this.finalize(),(null!==(t=this.h)&&void 0!==t?t:this.h=[]).push(e)}static get observedAttributes(){this.finalize();const e=[];return this.elementProperties.forEach(((t,i)=>{const o=this._$Ep(i,t);void 0!==o&&(this._$Ev.set(o,i),e.push(o))})),e}static createProperty(e,t=Ne){if(t.state&&(t.attribute=!1),this.finalize(),this.elementProperties.set(e,t),!t.noAccessor&&!this.prototype.hasOwnProperty(e)){const i="symbol"==typeof e?Symbol():"__"+e,o=this.getPropertyDescriptor(e,i,t);void 0!==o&&Object.defineProperty(this.prototype,e,o)}}static getPropertyDescriptor(e,t,i){return{get(){return this[t]},set(o){const r=this[e];this[t]=o,this.requestUpdate(e,r,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)||Ne}static finalize(){if(this.hasOwnProperty(Ve))return!1;this[Ve]=!0;const e=Object.getPrototypeOf(this);if(e.finalize(),void 0!==e.h&&(this.h=[...e.h]),this.elementProperties=new Map(e.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const e=this.properties,t=[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)];for(const i of t)this.createProperty(i,e[i])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const i=new Set(e.flat(1/0).reverse());for(const e of i)t.unshift(Me(e))}else void 0!==e&&t.push(Me(e));return t}static _$Ep(e,t){const i=t.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof e?e.toLowerCase():void 0}u(){var e;this._$E_=new Promise((e=>this.enableUpdating=e)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(e=this.constructor.h)||void 0===e||e.forEach((e=>e(this)))}addController(e){var t,i;(null!==(t=this._$ES)&&void 0!==t?t:this._$ES=[]).push(e),void 0!==this.renderRoot&&this.isConnected&&(null===(i=e.hostConnected)||void 0===i||i.call(e))}removeController(e){var t;null===(t=this._$ES)||void 0===t||t.splice(this._$ES.indexOf(e)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((e,t)=>{this.hasOwnProperty(t)&&(this._$Ei.set(t,this[t]),delete this[t])}))}createRenderRoot(){var e;const t=null!==(e=this.shadowRoot)&&void 0!==e?e:this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{Te?e.adoptedStyleSheets=t.map((e=>e instanceof CSSStyleSheet?e:e.styleSheet)):t.forEach((t=>{const i=document.createElement("style"),o=Se.litNonce;void 0!==o&&i.setAttribute("nonce",o),i.textContent=t.cssText,e.appendChild(i)}))})(t,this.constructor.elementStyles),t}connectedCallback(){var e;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(e=this._$ES)||void 0===e||e.forEach((e=>{var t;return null===(t=e.hostConnected)||void 0===t?void 0:t.call(e)}))}enableUpdating(e){}disconnectedCallback(){var e;null===(e=this._$ES)||void 0===e||e.forEach((e=>{var t;return null===(t=e.hostDisconnected)||void 0===t?void 0:t.call(e)}))}attributeChangedCallback(e,t,i){this._$AK(e,i)}_$EO(e,t,i=Ne){var o;const r=this.constructor._$Ep(e,i);if(void 0!==r&&!0===i.reflect){const n=(void 0!==(null===(o=i.converter)||void 0===o?void 0:o.toAttribute)?i.converter:ze).toAttribute(t,i.type);this._$El=e,null==n?this.removeAttribute(r):this.setAttribute(r,n),this._$El=null}}_$AK(e,t){var i;const o=this.constructor,r=o._$Ev.get(e);if(void 0!==r&&this._$El!==r){const e=o.getPropertyOptions(r),n="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==(null===(i=e.converter)||void 0===i?void 0:i.fromAttribute)?e.converter:ze;this._$El=r,this[r]=n.fromAttribute(t,e.type),this._$El=null}}requestUpdate(e,t,i){let o=!0;void 0!==e&&(((i=i||this.constructor.getPropertyOptions(e)).hasChanged||Fe)(this[e],t)?(this._$AL.has(e)||this._$AL.set(e,t),!0===i.reflect&&this._$El!==e&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(e,i))):o=!1),!this.isUpdatePending&&o&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var e;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((e,t)=>this[t]=e)),this._$Ei=void 0);let t=!1;const i=this._$AL;try{t=this.shouldUpdate(i),t?(this.willUpdate(i),null===(e=this._$ES)||void 0===e||e.forEach((e=>{var t;return null===(t=e.hostUpdate)||void 0===t?void 0:t.call(e)})),this.update(i)):this._$Ek()}catch(e){throw t=!1,this._$Ek(),e}t&&this._$AE(i)}willUpdate(e){}_$AE(e){var t;null===(t=this._$ES)||void 0===t||t.forEach((e=>{var t;return null===(t=e.hostUpdated)||void 0===t?void 0:t.call(e)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(e){return!0}update(e){void 0!==this._$EC&&(this._$EC.forEach(((e,t)=>this._$EO(t,this[t],e))),this._$EC=void 0),this._$Ek()}updated(e){}firstUpdated(e){}};var Ke;He[Ve]=!0,He.elementProperties=new Map,He.elementStyles=[],He.shadowRootOptions={mode:"open"},null==Oe||Oe({ReactiveElement:He}),(null!==(De=Be.reactiveElementVersions)&&void 0!==De?De:Be.reactiveElementVersions=[]).push("1.6.2");const Ue=window,qe=Ue.trustedTypes,je=qe?qe.createPolicy("lit-html",{createHTML:e=>e}):void 0,Ge="$lit$",We=`lit$${(Math.random()+"").slice(9)}$`,Ye="?"+We,Xe=`<${Ye}>`,Je=document,Ze=()=>Je.createComment(""),Qe=e=>null===e||"object"!=typeof e&&"function"!=typeof e,et=Array.isArray,tt="[ \t\n\f\r]",it=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ot=/-->/g,rt=/>/g,nt=RegExp(`>|${tt}(?:([^\\s"'>=/]+)(${tt}*=${tt}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),st=/'/g,at=/"/g,lt=/^(?:script|style|textarea|title)$/i,ct=(e=>(t,...i)=>({_$litType$:e,strings:t,values:i}))(1),dt=Symbol.for("lit-noChange"),ht=Symbol.for("lit-nothing"),ut=new WeakMap,pt=Je.createTreeWalker(Je,129,null,!1);class mt{constructor({strings:e,_$litType$:t},i){let o;this.parts=[];let r=0,n=0;const s=e.length-1,a=this.parts,[l,c]=((e,t)=>{const i=e.length-1,o=[];let r,n=2===t?"":"",s=it;for(let t=0;t"===l[0]?(s=null!=r?r:it,c=-1):void 0===l[1]?c=-2:(c=s.lastIndex-l[2].length,a=l[1],s=void 0===l[3]?nt:'"'===l[3]?at:st):s===at||s===st?s=nt:s===ot||s===rt?s=it:(s=nt,r=void 0);const h=s===nt&&e[t+1].startsWith("/>")?" ":"";n+=s===it?i+Xe:c>=0?(o.push(a),i.slice(0,c)+Ge+i.slice(c)+We+h):i+We+(-2===c?(o.push(void 0),t):h)}const a=n+(e[i]||"")+(2===t?"":"");if(!Array.isArray(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==je?je.createHTML(a):a,o]})(e,t);if(this.el=mt.createElement(l,i),pt.currentNode=this.el.content,2===t){const e=this.el.content,t=e.firstChild;t.remove(),e.append(...t.childNodes)}for(;null!==(o=pt.nextNode())&&a.length0){o.textContent=qe?qe.emptyScript:"";for(let i=0;iet(e)||"function"==typeof(null==e?void 0:e[Symbol.iterator]))(e)?this.T(e):this._(e)}k(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}$(e){this._$AH!==e&&(this._$AR(),this._$AH=this.k(e))}_(e){this._$AH!==ht&&Qe(this._$AH)?this._$AA.nextSibling.data=e:this.$(Je.createTextNode(e)),this._$AH=e}g(e){var t;const{values:i,_$litType$:o}=e,r="number"==typeof o?this._$AC(e):(void 0===o.el&&(o.el=mt.createElement(o.h,this.options)),o);if((null===(t=this._$AH)||void 0===t?void 0:t._$AD)===r)this._$AH.v(i);else{const e=new class{constructor(e,t){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=t}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){var t;const{el:{content:i},parts:o}=this._$AD,r=(null!==(t=null==e?void 0:e.creationScope)&&void 0!==t?t:Je).importNode(i,!0);pt.currentNode=r;let n=pt.nextNode(),s=0,a=0,l=o[0];for(;void 0!==l;){if(s===l.index){let t;2===l.type?t=new ft(n,n.nextSibling,this,e):1===l.type?t=new l.ctor(n,l.name,l.strings,this,e):6===l.type&&(t=new Ct(n,this,e)),this._$AV.push(t),l=o[++a]}s!==(null==l?void 0:l.index)&&(n=pt.nextNode(),s++)}return pt.currentNode=Je,r}v(e){let t=0;for(const i of this._$AV)void 0!==i&&(void 0!==i.strings?(i._$AI(e,i,t),t+=i.strings.length-2):i._$AI(e[t])),t++}}(r,this),t=e.u(this.options);e.v(i),this.$(t),this._$AH=e}}_$AC(e){let t=ut.get(e.strings);return void 0===t&&ut.set(e.strings,t=new mt(e)),t}T(e){et(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let i,o=0;for(const r of e)o===t.length?t.push(i=new ft(this.k(Ze()),this.k(Ze()),this,this.options)):i=t[o],i._$AI(r),o++;o2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=ht}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(e,t=this,i,o){const r=this.strings;let n=!1;if(void 0===r)e=vt(this,e,t,0),n=!Qe(e)||e!==this._$AH&&e!==dt,n&&(this._$AH=e);else{const o=e;let s,a;for(e=r[0],s=0;s{var o,r;const n=null!==(o=null==i?void 0:i.renderBefore)&&void 0!==o?o:t;let s=n._$litPart$;if(void 0===s){const e=null!==(r=null==i?void 0:i.renderBefore)&&void 0!==r?r:null;n._$litPart$=s=new ft(t.insertBefore(Ze(),e),e,void 0,null!=i?i:{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){var e;super.connectedCallback(),null===(e=this._$Do)||void 0===e||e.setConnected(!0)}disconnectedCallback(){var e;super.disconnectedCallback(),null===(e=this._$Do)||void 0===e||e.setConnected(!1)}render(){return dt}};St.finalized=!0,St._$litElement$=!0,null===($t=globalThis.litElementHydrateSupport)||void 0===$t||$t.call(globalThis,{LitElement:St});const Tt=globalThis.litElementPolyfillSupport;null==Tt||Tt({LitElement:St}),(null!==(Rt=globalThis.litElementVersions)&&void 0!==Rt?Rt:globalThis.litElementVersions=[]).push("3.3.2");const Et=e=>t=>"function"==typeof t?((e,t)=>(customElements.define(e,t),t))(e,t):((e,t)=>{const{kind:i,elements:o}=t;return{kind:i,elements:o,finisher(t){customElements.define(e,t)}}})(e,t),It=(e,t)=>"method"===t.kind&&t.descriptor&&!("value"in t.descriptor)?{...t,finisher(i){i.createProperty(t.key,e)}}:{kind:"field",key:Symbol(),placement:"own",descriptor:{},originalKey:t.key,initializer(){"function"==typeof t.initializer&&(this[t.key]=t.initializer.call(this))},finisher(i){i.createProperty(t.key,e)}},At=(e,t,i)=>{t.constructor.createProperty(i,e)};function Pt(e){return(t,i)=>void 0!==i?At(e,t,i):It(e,t)}function Mt(e){return Pt({...e,state:!0})}var Dt;const Bt=null!=(null===(Dt=window.HTMLSlotElement)||void 0===Dt?void 0:Dt.prototype.assignedElements)?(e,t)=>e.assignedElements(t):(e,t)=>e.assignedNodes(t).filter((e=>e.nodeType===Node.ELEMENT_NODE));function Lt(e){const{slot:t,selector:i}=null!=e?e:{};return(({finisher:e,descriptor:t})=>(i,o)=>{var r;if(void 0===o){const o=null!==(r=i.originalKey)&&void 0!==r?r:i.key,n=null!=t?{kind:"method",placement:"prototype",key:o,descriptor:t(i.key)}:{...i,key:o};return null!=e&&(n.finisher=function(t){e(t,o)}),n}{const r=i.constructor;void 0!==t&&Object.defineProperty(i,o,t(o)),null==e||e(r,o)}})({descriptor:o=>({get(){var o;const r="slot"+(t?`[name=${t}]`:":not([name])"),n=null===(o=this.renderRoot)||void 0===o?void 0:o.querySelector(r),s=null!=n?Bt(n,e):[];return i?s.filter((e=>e.matches(i))):s},enumerable:!0,configurable:!0})})}const _t=e=>null!=e?e:ht,Ot=1,zt=2,Ft=3,Nt=4,Vt=e=>(...t)=>({_$litDirective$:e,values:t});let Ht=class{constructor(e){}get _$AU(){return this._$AM._$AU}_$AT(e,t,i){this._$Ct=e,this._$AM=t,this._$Ci=i}_$AS(e,t){return this.update(e,t)}update(e,t){return this.render(...t)}};const Kt="important",Ut=" !"+Kt,qt=Vt(class extends Ht{constructor(e){var t;if(super(e),e.type!==Ot||"style"!==e.name||(null===(t=e.strings)||void 0===t?void 0:t.length)>2)throw Error("The `styleMap` directive must be used in the `style` attribute and must be the only part in the attribute.")}render(e){return Object.keys(e).reduce(((t,i)=>{const o=e[i];return null==o?t:t+`${i=i.includes("-")?i:i.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g,"-$&").toLowerCase()}:${o};`}),"")}update(e,[t]){const{style:i}=e.element;if(void 0===this.ut){this.ut=new Set;for(const e in t)this.ut.add(e);return this.render(t)}this.ut.forEach((e=>{null==t[e]&&(this.ut.delete(e),e.includes("-")?i.removeProperty(e):i[e]="")}));for(const e in t){const o=t[e];if(null!=o){this.ut.add(e);const t="string"==typeof o&&o.endsWith(Ut);e.includes("-")||t?i.setProperty(e,t?o.slice(0,-11):o,t?Kt:""):i[e]=o}}return dt}});class jt{constructor(e,...t){this.slotNames=[],(this.host=e).addController(this),this.slotNames=t,this.onSlotChange=this.onSlotChange.bind(this)}hostConnected(){this.host.shadowRoot.addEventListener("slotchange",this.onSlotChange)}hostDisconnected(){this.host.shadowRoot.removeEventListener("slotchange",this.onSlotChange)}test(e){return"[default]"===e?this.hasDefaultSlot():this.hasNamedSlot(e)}hasDefaultSlot(){return[...this.host.childNodes].some((e=>{if(e.nodeType===e.TEXT_NODE&&""!==e.textContent.trim())return!0;if(e.nodeType===e.ELEMENT_NODE){if(!e.hasAttribute("slot"))return!0}return!1}))}hasNamedSlot(e){return null!==this.host.querySelector(`:scope > [slot="${e}"]`)}onSlotChange(e){const t=e.target;(this.slotNames.includes("[default]")&&!t.name||t.name&&this.slotNames.includes(t.name))&&this.host.requestUpdate()}}const Gt=ct`${ht}`,Wt=Pe`:host{box-sizing:border-box}:host *,:host ::after,:host ::before{box-sizing:inherit}:host :focus,:host :focus-visible,:host(:focus),:host(:focus-visible){outline:0}[hidden]{display:none!important}`;let Yt=class extends Ht{constructor(e){if(super(e),this.et=ht,e.type!==zt)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(e){if(e===ht||null==e)return this.ft=void 0,this.et=e;if(e===dt)return e;if("string"!=typeof e)throw Error(this.constructor.directiveName+"() called with a non-string value");if(e===this.et)return this.ft;this.et=e;const t=[e];return t.raw=t,this.ft={_$litType$:this.constructor.resultType,strings:t,values:[]}}};Yt.directiveName="unsafeHTML",Yt.resultType=1;let Xt=class extends Yt{};Xt.directiveName="unsafeSVG",Xt.resultType=2;const Jt=Vt(Xt),Zt=e=>void 0===e.strings,Qt={},ei=(e,t)=>{var i,o;const r=e._$AN;if(void 0===r)return!1;for(const e of r)null===(o=(i=e)._$AO)||void 0===o||o.call(i,t,!1),ei(e,t);return!0},ti=e=>{let t,i;do{if(void 0===(t=e._$AM))break;i=t._$AN,i.delete(e),e=t}while(0===(null==i?void 0:i.size))},ii=e=>{for(let t;t=e._$AM;e=t){let i=t._$AN;if(void 0===i)t._$AN=i=new Set;else if(i.has(e))break;i.add(e),ni(t)}};function oi(e){void 0!==this._$AN?(ti(this),this._$AM=e,ii(this)):this._$AM=e}function ri(e,t=!1,i=0){const o=this._$AH,r=this._$AN;if(void 0!==r&&0!==r.size)if(t)if(Array.isArray(o))for(let e=i;e{var t,i,o,r;e.type==zt&&(null!==(t=(o=e)._$AP)&&void 0!==t||(o._$AP=ri),null!==(i=(r=e)._$AQ)&&void 0!==i||(r._$AQ=oi))};let si=class extends Ht{constructor(){super(...arguments),this._$AN=void 0}_$AT(e,t,i){super._$AT(e,t,i),ii(this),this.isConnected=e._$AU}_$AO(e,t=!0){var i,o;e!==this.isConnected&&(this.isConnected=e,e?null===(i=this.reconnected)||void 0===i||i.call(this):null===(o=this.disconnected)||void 0===o||o.call(this)),t&&(ei(this,e),ti(this))}setValue(e){if(Zt(this._$Ct))this._$Ct._$AI(e,this);else{const t=[...this._$Ct._$AH];t[this._$Ci]=e,this._$Ct._$AI(t,this,0)}}disconnected(){}reconnected(){}};class ai{constructor(e){this.G=e}disconnect(){this.G=void 0}reconnect(e){this.G=e}deref(){return this.G}}const li=e=>!(e=>null===e||"object"!=typeof e&&"function"!=typeof e)(e)&&"function"==typeof e.then,ci=1073741823;const di=Vt(class extends si{constructor(){super(...arguments),this._$C_t=ci,this._$Cwt=[],this._$Cq=new ai(this),this._$CK=new class{constructor(){this.Y=void 0,this.Z=void 0}get(){return this.Y}pause(){var e;null!==(e=this.Y)&&void 0!==e||(this.Y=new Promise((e=>this.Z=e)))}resume(){var e;null===(e=this.Z)||void 0===e||e.call(this),this.Y=this.Z=void 0}}}render(...e){var t;return null!==(t=e.find((e=>!li(e))))&&void 0!==t?t:dt}update(e,t){const i=this._$Cwt;let o=i.length;this._$Cwt=t;const r=this._$Cq,n=this._$CK;this.isConnected||this.disconnected();for(let e=0;ethis._$C_t);e++){const s=t[e];if(!li(s))return this._$C_t=e,s;e{for(;n.get();)await n.get();const t=r.deref();if(void 0!==t){const i=t._$Cwt.indexOf(s);i>-1&&i`:(()=>{if(this.name){const[e,t]=this.name.split("--"),i=new Map([["outlined","Material Icons Outlined"],["filled","Material Icons"],["rounded","Material Icons Round"],["sharp","Material Icons Sharp"],["two-tone","Material Icons Two Tone"]]);return ct`${e}`}return this.src?ct`${di($e({url:this.src}).then(Jt))}`:ct``})()}},e.Icon.styles=[Wt,hi],Re([Pt({reflect:!0})],e.Icon.prototype,"name",void 0),Re([Pt({reflect:!0})],e.Icon.prototype,"src",void 0),e.Icon=Re([Et("mdui-icon")],e.Icon);const ui=Pe`:host{--shape-corner:var(--mdui-shape-corner-full);position:relative;display:inline-flex;align-items:center;justify-content:center;overflow:hidden;white-space:nowrap;vertical-align:middle;border-radius:var(--shape-corner);-webkit-user-select:none;user-select:none;width:2.5rem;height:2.5rem;background-color:rgb(var(--mdui-color-primary-container));color:rgb(var(--mdui-color-on-primary-container));font-size:var(--mdui-typescale-title-medium-size);font-weight:var(--mdui-typescale-title-medium-weight);letter-spacing:var(--mdui-typescale-title-medium-tracking);line-height:var(--mdui-typescale-title-medium-line-height)}img{width:100%;height:100%}::slotted(mdui-icon),mdui-icon{font-size:1.5em}`;e.Avatar=class extends St{constructor(){super(...arguments),this.hasSlotController=new jt(this,"[default]")}render(){return this.hasSlotController.test("[default]")?ct``:this.src?ct`${_t(this.label)}`:this.icon?ct``:Gt}},e.Avatar.styles=[Wt,ui],Re([Pt({reflect:!0})],e.Avatar.prototype,"src",void 0),Re([Pt({reflect:!0})],e.Avatar.prototype,"fit",void 0),Re([Pt({reflect:!0})],e.Avatar.prototype,"icon",void 0),Re([Pt({reflect:!0})],e.Avatar.prototype,"label",void 0),e.Avatar=Re([Et("mdui-avatar")],e.Avatar);const pi=Pe`:host{--shape-corner:var(--mdui-shape-corner-full);display:inline-flex;align-items:center;justify-content:center;border-radius:var(--shape-corner);padding-left:.25rem;padding-right:.25rem;color:rgb(var(--mdui-color-on-error));background-color:rgb(var(--mdui-color-error));height:1rem;min-width:1rem;font-size:var(--mdui-typescale-label-small-size);font-weight:var(--mdui-typescale-label-small-weight);letter-spacing:var(--mdui-typescale-label-small-tracking);line-height:var(--mdui-typescale-label-small-line-height)}:host([variant=small]){min-width:0;padding:0;width:.375rem;height:.375rem}`;e.Badge=class extends St{constructor(){super(...arguments),this.variant="large"}render(){return"small"===this.variant?Gt:ct``}},e.Badge.styles=[Wt,pi],Re([Pt({reflect:!0})],e.Badge.prototype,"variant",void 0),e.Badge=Re([Et("mdui-badge")],e.Badge);const mi=e=>null!==e&&"false"!==e,vi=(e,t,i)=>{const o=new CustomEvent(t,{bubbles:!0,cancelable:!1,composed:!0,detail:{},...i});return e.dispatchEvent(o),o};function fi(e,t=!1){return(i,o)=>{const{update:r}=i;e in i&&(i.update=function(i){if(i.has(e)){const r=i.get(e),n=this[e];r!==n&&(t&&!this.hasUpdated||this[o](r,n))}r.call(this,i)})}}const gi=e=>{class t extends e{constructor(...e){super(...e),this.lastScrollTopThreshold=0,this.lastScrollTopNoThreshold=0,this.isParentLayout=!1,this.onListeningScroll=this.onListeningScroll.bind(this)}get scrollPaddingPosition(){throw new Error("Must implement scrollPaddingPosition getter")}onScrollTargetChange(e,t){if((e&&!t||!e&&t)&&this.updateContainerPadding(),!this.scrollBehavior)return;const i=this.getListening(e);i&&i.removeEventListener("scroll",this.onListeningScroll);const o=this.getListening(t);o&&(this.updateScrollTop(o),o.addEventListener("scroll",this.onListeningScroll))}onScrollBehaviorChange(e,t){(e&&!t||!e&&t)&&this.updateContainerPadding();const i=this.getListening(this.scrollTarget);i&&(this.scrollBehavior?(this.updateScrollTop(i),i.addEventListener("scroll",this.onListeningScroll)):i.removeEventListener("scroll",this.onListeningScroll))}connectedCallback(){super.connectedCallback(),this.isParentLayout=a(this.parentElement,"mdui-layout"),this.updateContainerPadding()}disconnectedCallback(){super.disconnectedCallback(),this.updateContainerPadding(!1)}hasScrollBehavior(e){var t,i;const o=null!==(i=null===(t=this.scrollBehavior)||void 0===t?void 0:t.split(" "))&&void 0!==i?i:[];return Array.isArray(e)?!!o.filter((t=>e.includes(t))).length:o.includes(e)}runScrollThreshold(e,t){}runScrollNoThreshold(e,t){}updateContainerPadding(e=!0){const t=this.getContainer(this.scrollTarget);if(!t||this.isParentLayout)return;const i="top"===this.scrollPaddingPosition?"paddingTop":"paddingBottom";if(e){const e=this.getListening(this.scrollTarget)&&["fixed","absolute"].includes(P(this).css("position"))?this.offsetHeight:null;P(t).css({[i]:e})}else P(t).css({[i]:null})}onListeningScroll(){const e=this.getListening(this.scrollTarget);window.requestAnimationFrame((()=>this.onScroll(e)))}onScroll(e){var t;const i=null!==(t=e.scrollY)&&void 0!==t?t:e.scrollTop;this.lastScrollTopNoThreshold!==i&&(this.runScrollNoThreshold(i(this.scrollThreshold||0)&&(this.runScrollThreshold(i++bi;let wi,ki;const Ci=(e,t)=>{const i=P(e),o=yi(),r={unobserve:()=>{i.each(((e,t)=>{var i;const r=null!==(i=wi.get(t))&&void 0!==i?i:[],n=r.findIndex((e=>e.key===o));-1!==n&&r.splice(n,1),r.length?wi.set(t,r):(ki.unobserve(t),wi.delete(t))}))}};return wi||(wi=new WeakMap,ki=new ResizeObserver((e=>{e.forEach((e=>{const t=e.target;wi.get(t).forEach((t=>{t.callback.call(r,e,r)}))}))}))),i.each(((e,i)=>{var r;ki.observe(i);const n=null!==(r=wi.get(i))&&void 0!==r?r:[];n.push({callback:t,key:o}),wi.set(i,n)})),r};class xi{constructor(e){this.states=[],this.$layout=P(e)}registerMain(e){this.$main=P(e)}unregisterMain(){this.$main=void 0}registerItem(e){const t={element:e};this.states.push(t),t.observeResize=Ci(t.element,(()=>{a(t.element,"mdui-navigation-drawer")&&t.element.isModal?this.updateLayout(t.element,{width:0}):this.updateLayout(t.element)})),this.items=void 0,this.resort(),this.updateLayout()}unregisterItem(e){var t;const i=this.states.findIndex((t=>t.element===e));if(i<0)return;null===(t=this.states[i].observeResize)||void 0===t||t.unobserve(),this.items=void 0,this.states.splice(i,1),this.states[i]&&this.updateLayout(this.states[i].element)}getItems(){return this.items||(this.items=this.$layout.children(["mdui-navigation-bar","mdui-navigation-drawer","mdui-navigation-rail","mdui-bottom-app-bar","mdui-top-app-bar","mdui-layout-item"].join(",")).get()),this.items}getMain(){return this.$main?this.$main[0]:void 0}getItemsAndMain(){return[...this.getItems(),this.getMain()].filter((e=>e))}hasItem(e){return this.getItems().includes(e)}updateOrder(){this.resort(),this.updateLayout()}updateLayout(e,t){const i=e?{element:e,width:null==t?void 0:t.width,height:null==t?void 0:t.height}:void 0,o=i?this.states.findIndex((e=>e.element===i.element)):0;if(o<0)return;Object.assign(this.states[o],i),this.states.forEach(((e,t)=>{var i,r,n,s,a,l,c,d;if(t0?this.states[t-1]:void 0,p=null!==(i=null==u?void 0:u.top)&&void 0!==i?i:0,m=null!==(r=null==u?void 0:u.right)&&void 0!==r?r:0,v=null!==(n=null==u?void 0:u.bottom)&&void 0!==n?n:0,f=null!==(s=null==u?void 0:u.left)&&void 0!==s?s:0;switch(Object.assign(e,{top:p,right:m,bottom:v,left:f}),h){case"top":e.top+=null!==(a=e.height)&&void 0!==a?a:e.element.offsetHeight;break;case"right":e.right+=null!==(l=e.width)&&void 0!==l?l:e.element.offsetWidth;break;case"bottom":e.bottom+=null!==(c=e.height)&&void 0!==c?c:e.element.offsetHeight;break;case"left":e.left+=null!==(d=e.width)&&void 0!==d?d:e.element.offsetWidth}e.height=e.width=void 0,P(e.element).css({position:"absolute",top:"bottom"===h?null:p,right:"left"===h?null:m,bottom:"top"===h?null:v,left:"right"===h?null:f})}));const r=this.states[this.states.length-1];this.$main&&this.$main.css({paddingTop:r.top,paddingRight:r.right,paddingBottom:r.bottom,paddingLeft:r.left})}resort(){const e=this.getItems();this.states.sort(((t,i)=>{var o,r;const n=null!==(o=t.element.order)&&void 0!==o?o:0,s=null!==(r=i.element.order)&&void 0!==r?r:0;return n>s?1:ne.indexOf(i.element)?1:e.indexOf(t.element)($i.has(e)||$i.set(e,new xi(e)),$i.get(e));class Si extends St{constructor(){super(...arguments),this.isParentLayout=!1}get layoutPlacement(){throw new Error("Must implement placement getter!")}onOrderChange(){var e;null===(e=this.layoutManager)||void 0===e||e.updateOrder()}connectedCallback(){super.connectedCallback();const e=this.parentElement;this.isParentLayout=a(e,"mdui-layout"),this.isParentLayout&&(this.layoutManager=Ri(e),this.layoutManager.registerItem(this))}disconnectedCallback(){super.disconnectedCallback(),this.layoutManager&&this.layoutManager.unregisterItem(this)}}Re([Pt({type:Number,reflect:!0})],Si.prototype,"order",void 0),Re([fi("order",!0)],Si.prototype,"onOrderChange",null);const Ti=Pe`:host{--shape-corner:var(--mdui-shape-corner-none);--z-index:2000;position:fixed;right:0;bottom:0;left:0;display:flex;flex:0 0 auto;align-items:center;justify-content:flex-start;border-radius:var(--shape-corner) var(--shape-corner) 0 0;z-index:var(--z-index);transition:bottom var(--mdui-motion-duration-long2) var(--mdui-motion-easing-emphasized);padding:0 1rem;height:5rem;background-color:rgb(var(--mdui-color-surface-container));box-shadow:var(--mdui-elevation-level2)}:host([scroll-target]:not([scroll-target=''])){position:absolute}:host([hide]){transition-duration:var(--mdui-motion-duration-short4);bottom:-5.625rem}::slotted(:not(:first-child)){margin-left:.5rem}::slotted(mdui-fab){box-shadow:var(--mdui-elevation-level0)}:host([fab-detach]) ::slotted(mdui-fab){position:absolute;transition:bottom var(--mdui-motion-duration-long2) var(--mdui-motion-easing-standard);right:1rem;bottom:.75rem}:host([fab-detach][hide][scroll-behavior~=hide]) ::slotted(mdui-fab){transition-duration:var(--mdui-motion-duration-short4);bottom:1rem;box-shadow:var(--mdui-elevation-level2)}:host([fab-detach][hide][scroll-behavior~=hide][scroll-target]:not([scroll-target=''])) ::slotted(mdui-fab){bottom:6.625rem}:host([hide]) ::slotted(:not(mdui-fab)),:host([hide]:not([fab-detach])) ::slotted(mdui-fab){transform:translateY(8.75rem);transition:transform var(--mdui-motion-duration-0) var(--mdui-motion-easing-emphasized-accelerate) var(--mdui-motion-duration-short4)}::slotted(:first-child){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-short1)}::slotted(:nth-child(2)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-short3)}::slotted(:nth-child(3)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-short4)}::slotted(:nth-child(4)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-medium1)}::slotted(:nth-child(5)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-medium2)}::slotted(:nth-child(6)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-medium3)}`;e.BottomAppBar=class extends(gi(Si)){constructor(){super(...arguments),this.hide=!1,this.fabDetach=!1}get scrollPaddingPosition(){return"bottom"}get layoutPlacement(){return"bottom"}connectedCallback(){super.connectedCallback(),this.addEventListener("transitionend",(e=>{e.target===this&&vi(this,this.hide?"hidden":"shown")}))}render(){return ct``}runScrollThreshold(e){if(!e&&!this.hide){vi(this,"hide").defaultPrevented||(this.hide=!0)}if(e&&this.hide){vi(this,"show").defaultPrevented||(this.hide=!1)}}},e.BottomAppBar.styles=[Wt,Ti],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.BottomAppBar.prototype,"hide",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"fab-detach"})],e.BottomAppBar.prototype,"fabDetach",void 0),Re([Pt({reflect:!0,attribute:"scroll-behavior"})],e.BottomAppBar.prototype,"scrollBehavior",void 0),e.BottomAppBar=Re([Et("mdui-bottom-app-bar")],e.BottomAppBar);const Ei=()=>new Ii;let Ii=class{};const Ai=new WeakMap,Pi=Vt(class extends si{render(e){return ht}update(e,[t]){var i;const o=t!==this.G;return o&&void 0!==this.G&&this.ot(void 0),(o||this.rt!==this.lt)&&(this.G=t,this.ct=null===(i=e.options)||void 0===i?void 0:i.host,this.ot(this.lt=e.element)),ht}ot(e){var t;if("function"==typeof this.G){const i=null!==(t=this.ct)&&void 0!==t?t:globalThis;let o=Ai.get(i);void 0===o&&(o=new WeakMap,Ai.set(i,o)),void 0!==o.get(this.G)&&this.G.call(this.ct,void 0),o.set(this.G,e),void 0!==e&&this.G.call(this.ct,e)}else this.G.value=e}get rt(){var e,t,i;return"function"==typeof this.G?null===(t=Ai.get(null!==(e=this.ct)&&void 0!==e?e:globalThis))||void 0===t?void 0:t.get(this.G):null===(i=this.G)||void 0===i?void 0:i.value}disconnected(){this.rt===this.lt&&this.ot(void 0)}reconnected(){this.ot(this.lt)}});function Mi(e){if("string"==typeof e||"number"==typeof e)return""+e;let t="";if(Array.isArray(e))for(let i,o=0;o{const t=P(e).attr("form");if(t){return e.getRootNode().getElementById(t)}return e.closest("form")},name:e=>e.name,value:e=>e.value,defaultValue:e=>e.defaultValue,setValue:(e,t)=>e.value=t,disabled:e=>e.disabled,reportValidity:e=>!l(e.reportValidity)||e.reportValidity(),...t},this.onFormData=this.onFormData.bind(this),this.onFormSubmit=this.onFormSubmit.bind(this),this.onFormReset=this.onFormReset.bind(this),this.reportFormValidity=this.reportFormValidity.bind(this)}hostConnected(){this.form=this.options.form(this.host),this.form&&this.attachForm(this.form)}hostDisconnected(){this.detachForm()}hostUpdated(){const e=this.options.form(this.host);e||this.detachForm(),e&&this.form!==e&&(this.detachForm(),this.attachForm(e))}getForm(){var e;return null!==(e=this.form)&&void 0!==e?e:null}reset(e){this.doAction("reset",e)}submit(e){this.doAction("submit",e)}attachForm(e){e?(this.form=e,fe.has(this.form)?fe.get(this.form).add(this.host):fe.set(this.form,new Set([this.host])),this.form.addEventListener("formdata",this.onFormData),this.form.addEventListener("submit",this.onFormSubmit),this.form.addEventListener("reset",this.onFormReset),Di.has(this.form)||(Di.set(this.form,this.form.reportValidity),this.form.reportValidity=()=>this.reportFormValidity())):this.form=void 0}detachForm(){this.form&&(fe.get(this.form).delete(this.host),this.form.removeEventListener("formdata",this.onFormData),this.form.removeEventListener("submit",this.onFormSubmit),this.form.removeEventListener("reset",this.onFormReset),Di.has(this.form)&&!fe.get(this.form).size&&(this.form.reportValidity=Di.get(this.form),Di.delete(this.form)))}doAction(e,t){if(!this.form)return;const i=P(``}isButton(){return!this.href}}qi.styles=[Wt,Ui],Re([Pt({type:Boolean,reflect:!0,converter:mi})],qi.prototype,"disabled",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],qi.prototype,"loading",void 0),Re([Pt({reflect:!0})],qi.prototype,"name",void 0),Re([Pt({reflect:!0})],qi.prototype,"value",void 0),Re([Pt({reflect:!0})],qi.prototype,"type",void 0),Re([Pt({reflect:!0})],qi.prototype,"form",void 0),Re([Pt({reflect:!0,attribute:"formaction"})],qi.prototype,"formAction",void 0),Re([Pt({reflect:!0,attribute:"formenctype"})],qi.prototype,"formEnctype",void 0),Re([Pt({reflect:!0,attribute:"formmethod"})],qi.prototype,"formMethod",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"formnovalidate"})],qi.prototype,"formNoValidate",void 0),Re([Pt({reflect:!0,attribute:"formtarget"})],qi.prototype,"formTarget",void 0);const ji=Pe`:host{--shape-corner:var(--mdui-shape-corner-full);position:relative;display:inline-block;overflow:hidden;text-align:center;border-radius:var(--shape-corner);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:box-shadow var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);min-width:3rem;height:2.5rem;color:rgb(var(--mdui-color-primary));font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height)}.button{width:100%;padding:0 1rem}:host([full-width]){display:block}:host([variant=elevated]){box-shadow:var(--mdui-elevation-level1);background-color:rgb(var(--mdui-color-surface-container-low));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=filled]){color:rgb(var(--mdui-color-on-primary));background-color:rgb(var(--mdui-color-primary));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-primary)}:host([variant=tonal]){color:rgb(var(--mdui-color-on-secondary-container));background-color:rgb(var(--mdui-color-secondary-container));--mdui-comp-ripple-state-layer-color:var( + --mdui-color-on-secondary-container + )}:host([variant=outlined]){border:.0625rem solid rgb(var(--mdui-color-outline));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=text]){--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=outlined][focus-visible]){border-color:rgb(var(--mdui-color-primary))}:host([variant=elevated][hover]){box-shadow:var(--mdui-elevation-level2)}:host([variant=filled][hover]),:host([variant=tonal][hover]){box-shadow:var(--mdui-elevation-level1)}:host([disabled]),:host([loading]){cursor:default;pointer-events:none}:host([disabled]){color:rgba(var(--mdui-color-on-surface),38%);box-shadow:var(--mdui-elevation-level0)}:host([variant=elevated][disabled]),:host([variant=filled][disabled]),:host([variant=tonal][disabled]){background-color:rgba(var(--mdui-color-on-surface),12%)}:host([variant=outlined][disabled]){border-color:rgba(var(--mdui-color-on-surface),12%)}.label{display:inline-flex;padding-right:.5rem;padding-left:.5rem}.end-icon,.icon{display:inline-flex;font-size:1.28571429em}.end-icon mdui-icon,.icon mdui-icon,::slotted([slot=end-icon]),::slotted([slot=icon]){font-size:inherit}mdui-circular-progress{display:inline-flex;width:1.125rem;height:1.125rem}:host([variant=filled]) mdui-circular-progress{stroke:rgb(var(--mdui-color-on-primary))}:host([variant=tonal]) mdui-circular-progress{stroke:rgb(var(--mdui-color-on-secondary-container))}:host([disabled]) mdui-circular-progress{stroke:rgba(var(--mdui-color-on-surface),38%)}`;e.Button=class extends qi{constructor(){super(...arguments),this.variant="filled",this.fullWidth=!1,this.rippleRef=Ei()}get rippleElement(){return this.rippleRef.value}render(){return ct`${this.isButton()?this.renderButton({className:"button",part:"button",content:this.renderInner()}):this.disabled||this.loading?ct`${this.renderInner()}`:this.renderAnchor({className:"button",part:"button",content:this.renderInner()})}`}renderIcon(){return this.loading?this.renderLoading():ct`${this.icon?ct``:Gt}`}renderLabel(){return ct``}renderEndIcon(){return ct`${this.endIcon?ct``:Gt}`}renderInner(){return[this.renderIcon(),this.renderLabel(),this.renderEndIcon()]}},e.Button.styles=[qi.styles,ji],Re([Pt({reflect:!0})],e.Button.prototype,"variant",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"full-width"})],e.Button.prototype,"fullWidth",void 0),Re([Pt({reflect:!0})],e.Button.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"end-icon"})],e.Button.prototype,"endIcon",void 0),e.Button=Re([Et("mdui-button")],e.Button);const Gi=Pe`:host{--shape-corner:var(--mdui-shape-corner-full);position:relative;display:inline-block;overflow:hidden;text-align:center;border-radius:var(--shape-corner);cursor:pointer;-webkit-tap-highlight-color:transparent;font-size:1.5rem;width:2.5rem;min-width:2.5rem;height:2.5rem}:host([variant=standard]){color:rgb(var(--mdui-color-on-surface-variant));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}:host([variant=filled]){color:rgb(var(--mdui-color-primary));background-color:rgb(var(--mdui-color-surface-container-highest));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=tonal]){color:rgb(var(--mdui-color-on-surface-variant));background-color:rgb(var(--mdui-color-surface-container-highest));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}:host([variant=outlined]){border:.0625rem solid rgb(var(--mdui-color-outline));color:rgb(var(--mdui-color-on-surface-variant));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}:host([variant=outlined][pressed]){color:rgb(var(--mdui-color-on-surface));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([variant=standard][selected]){color:rgb(var(--mdui-color-primary));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=filled]:not([selectable])),:host([variant=filled][selected]){color:rgb(var(--mdui-color-on-primary));background-color:rgb(var(--mdui-color-primary));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-primary)}:host([variant=tonal]:not([selectable])),:host([variant=tonal][selected]){color:rgb(var(--mdui-color-on-secondary-container));background-color:rgb(var(--mdui-color-secondary-container));--mdui-comp-ripple-state-layer-color:var( + --mdui-color-on-secondary-container + )}:host([variant=outlined][selected]){border:none;color:rgb(var(--mdui-color-inverse-on-surface));background-color:rgb(var(--mdui-color-inverse-surface));--mdui-comp-ripple-state-layer-color:var(--mdui-color-inverse-on-surface)}:host([variant=filled][disabled]),:host([variant=outlined][disabled]),:host([variant=tonal][disabled]){background-color:rgba(var(--mdui-color-on-surface),.12);border-color:rgba(var(--mdui-color-on-surface),.12)}:host([disabled]),:host([loading]){cursor:default;pointer-events:none}:host([disabled]){color:rgba(var(--mdui-color-on-surface),.38)!important}:host([loading]) .button,:host([loading]) mdui-ripple{opacity:0}.button{float:left;width:100%}.icon,.selected-icon mdui-icon,::slotted(*){font-size:inherit}mdui-circular-progress{display:flex;position:absolute;top:calc(50% - 1.5rem / 2);left:calc(50% - 1.5rem / 2);width:1.5rem;height:1.5rem}:host([variant=filled]:not([disabled])) mdui-circular-progress{stroke:rgb(var(--mdui-color-on-primary))}:host([disabled]) mdui-circular-progress{stroke:rgba(var(--mdui-color-on-surface),38%)}`;e.ButtonIcon=class extends qi{constructor(){super(...arguments),this.variant="standard",this.selectable=!1,this.selected=!1,this.rippleRef=Ei(),this.hasSlotController=new jt(this,"[default]","selected-icon")}get rippleElement(){return this.rippleRef.value}onSelectedChange(){vi(this,"change")}firstUpdated(e){super.firstUpdated(e),this.addEventListener("click",(()=>{this.selectable&&!this.disabled&&(this.selected=!this.selected)}))}render(){return ct`${this.isButton()?this.renderButton({className:"button",part:"button",content:this.renderIcon()}):this.disabled||this.loading?ct`${this.renderIcon()}`:this.renderAnchor({className:"button",part:"button",content:this.renderIcon()})} ${this.renderLoading()}`}renderIcon(){const e=()=>this.hasSlotController.test("[default]")?ct``:this.icon?ct``:Gt;return this.selected?(()=>this.hasSlotController.test("selected-icon")||this.selectedIcon?ct``:e())():e()}},e.ButtonIcon.styles=[qi.styles,Gi],Re([Pt({reflect:!0})],e.ButtonIcon.prototype,"variant",void 0),Re([Pt({reflect:!0})],e.ButtonIcon.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"selected-icon"})],e.ButtonIcon.prototype,"selectedIcon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.ButtonIcon.prototype,"selectable",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.ButtonIcon.prototype,"selected",void 0),Re([fi("selected",!0)],e.ButtonIcon.prototype,"onSelectedChange",null),e.ButtonIcon=Re([Et("mdui-button-icon")],e.ButtonIcon);const Wi=Pe`:host{--shape-corner:var(--mdui-shape-corner-medium);position:relative;display:inline-block;overflow:hidden;border-radius:var(--shape-corner);-webkit-tap-highlight-color:transparent;transition:box-shadow var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([clickable]){cursor:pointer}:host([variant=elevated]){background-color:rgb(var(--mdui-color-surface-container-low));box-shadow:var(--mdui-elevation-level1)}:host([variant=filled]){background-color:rgb(var(--mdui-color-surface-container-highest))}:host([variant=outlined]){background-color:rgb(var(--mdui-color-surface));border:.0625rem solid rgb(var(--mdui-color-outline))}:host([variant=elevated][hover]){box-shadow:var(--mdui-elevation-level2)}:host([variant=filled][hover]),:host([variant=outlined][hover]){box-shadow:var(--mdui-elevation-level1)}:host([variant=elevated][dragged]),:host([variant=filled][dragged]),:host([variant=outlined][dragged]){box-shadow:var(--mdui-elevation-level3)}:host([disabled]){opacity:.38;cursor:default;-webkit-user-select:none;user-select:none}:host([variant=elevated][disabled]){background-color:rgb(var(--mdui-color-surface-variant));box-shadow:var(--mdui-elevation-level0)}:host([variant=filled][disabled]){background-color:rgb(var(--mdui-color-surface));box-shadow:var(--mdui-elevation-level1)}:host([variant=outlined][disabled]){box-shadow:var(--mdui-elevation-level0);border-color:rgba(var(--mdui-color-outline),.32)}.link{position:relative;display:inline-block;width:100%;height:100%;color:inherit;font-size:inherit;letter-spacing:inherit;text-decoration:none;touch-action:manipulation;-webkit-user-drag:none}`;e.Card=class extends(_i(Ki(Fi(St)))){constructor(){super(...arguments),this.variant="elevated",this.clickable=!1,this.disabled=!1,this.rippleRef=Ei()}get rippleElement(){return this.rippleRef.value}get rippleDisabled(){return this.disabled||!this.href&&!this.clickable}get focusElement(){return this}get focusDisabled(){return this.rippleDisabled}render(){return ct`${this.href&&!this.disabled?this.renderAnchor({className:"link",content:ct``}):ct``}`}},e.Card.styles=[Wt,Wi],Re([Pt({reflect:!0})],e.Card.prototype,"variant",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Card.prototype,"clickable",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Card.prototype,"disabled",void 0),e.Card=Re([Et("mdui-card")],e.Card);const Yi=Vt(class extends Ht{constructor(e){if(super(e),e.type!==Ft&&e.type!==Ot&&e.type!==Nt)throw Error("The `live` directive is not allowed on child or event bindings");if(!Zt(e))throw Error("`live` bindings can only contain a single expression")}render(e){return e}update(e,[t]){if(t===dt||t===ht)return t;const i=e.element,o=e.name;if(e.type===Ft){if(t===i[o])return dt}else if(e.type===Nt){if(!!t===i.hasAttribute(o))return dt}else if(e.type===Ot&&i.getAttribute(o)===t+"")return dt;return((e,t=Qt)=>{e._$AH=t})(e),t}});function Xi(e="value"){return(t,i)=>{const o=t.constructor,r=o.prototype.attributeChangedCallback;o.prototype.attributeChangedCallback=function(t,n,s){var a;const d=o.getPropertyOptions(e);if(t===(c(d.attribute)?d.attribute:e)){const t=d.converter||ze,o=(l(t)?t:null!==(a=null==t?void 0:t.fromAttribute)&&void 0!==a?a:ze.fromAttribute)(s,d.type);this[e]!==o&&(this[i]=o)}r.call(this,t,n,s)}}}const Ji=Pe`:host{display:inline-block;width:1em;height:1em;line-height:1;font-size:1.5rem}`,Zi=e=>ct`${Jt(e)}`;let Qi=class extends St{render(){return Zi('')}};Qi.styles=Ji,Qi=Re([Et("mdui-icon-check-box-outline-blank")],Qi);let eo=class extends St{render(){return Zi('')}};eo.styles=Ji,eo=Re([Et("mdui-icon-check-box")],eo);let to=class extends St{render(){return Zi('')}};to.styles=Ji,to=Re([Et("mdui-icon-indeterminate-check-box")],to);const io=Pe`:host{position:relative;display:inline-flex;cursor:pointer;-webkit-tap-highlight-color:transparent;border-radius:.125rem;font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height)}label{display:inline-flex;align-items:center;width:100%;cursor:inherit;-webkit-user-select:none;user-select:none;touch-action:manipulation;zoom:1;-webkit-user-drag:none}input{position:absolute;padding:0;opacity:0;pointer-events:none;width:1.125rem;height:1.125rem;margin:0 0 0 .6875rem}.icon{display:flex;position:absolute;opacity:1;transform:scale(1);color:rgb(var(--mdui-color-on-surface));font-size:1.5rem;transition:color var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard)}.checked-icon,.indeterminate-icon{opacity:0;transform:scale(.5);transition-property:color,opacity,transform;transition-duration:var(--mdui-motion-duration-short4);transition-timing-function:var(--mdui-motion-easing-standard)}.icon .i,::slotted([slot=checked-icon]),::slotted([slot=indeterminate-icon]),::slotted([slot=unchecked-icon]){color:inherit;font-size:inherit}i{position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;border-radius:50%;width:2.5rem;min-width:2.5rem;height:2.5rem;--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}.label{display:flex;width:100%;padding-top:.625rem;padding-bottom:.625rem;color:rgb(var(--mdui-color-on-surface));transition:color var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard)}:host([checked]) i{--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([checked]) .icon{color:rgb(var(--mdui-color-primary))}:host([checked]) .indeterminate-icon{opacity:0;transform:scale(.5)}:host([checked]) .checked-icon{opacity:1;transform:scale(1)}:host([indeterminate]) i{--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([indeterminate]) .icon{color:rgb(var(--mdui-color-primary))}:host([indeterminate]) .checked-icon{opacity:0;transform:scale(.5)}:host([indeterminate]) .indeterminate-icon{opacity:1;transform:scale(1)}:host([invalid]) i{--mdui-comp-ripple-state-layer-color:var(--mdui-color-error)}:host([invalid]) .icon{color:rgb(var(--mdui-color-error))}:host([invalid]) .label{color:rgb(var(--mdui-color-error))}:host([disabled]){cursor:default;pointer-events:none}:host([disabled]) .icon{color:rgba(var(--mdui-color-on-surface),38%)}:host([disabled]) .label{color:rgba(var(--mdui-color-on-surface),38%)}:host([disabled][checked]) .unchecked-icon,:host([disabled][indeterminate]) .unchecked-icon{opacity:0}`;e.Checkbox=class extends(Ki(Fi(St))){constructor(){super(...arguments),this.disabled=!1,this.checked=!1,this.defaultChecked=!1,this.indeterminate=!1,this.required=!1,this.name="",this.value="on",this.invalid=!1,this.inputRef=Ei(),this.rippleRef=Ei(),this.formController=new Li(this,{value:e=>e.checked?e.value:void 0,defaultValue:e=>e.defaultChecked,setValue:(e,t)=>e.checked=t})}get validity(){return this.inputRef.value.validity}get validationMessage(){return this.inputRef.value.validationMessage}get rippleElement(){return this.rippleRef.value}get rippleDisabled(){return this.disabled}get focusElement(){return this.inputRef.value}get focusDisabled(){return this.disabled}async onDisabledChange(){await this.updateComplete,this.invalid=!this.inputRef.value.checkValidity()}async onCheckedChange(){var e;await this.updateComplete;const t=this.formController.getForm();t&&(null===(e=Bi.get(t))||void 0===e?void 0:e.has(this))?(this.invalid=!1,Bi.get(t).delete(this)):this.invalid=!this.inputRef.value.checkValidity()}checkValidity(){const e=this.inputRef.value.checkValidity();return e||vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}),e}reportValidity(){if(this.invalid=!this.inputRef.value.reportValidity(),this.invalid){vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}).defaultPrevented&&(this.blur(),this.focus())}return!this.invalid}setCustomValidity(e){this.inputRef.value.setCustomValidity(e),this.invalid=!this.inputRef.value.checkValidity()}render(){return ct``}onChange(){this.checked=this.inputRef.value.checked,this.indeterminate=!1,vi(this,"change")}},e.Checkbox.styles=[Wt,io],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Checkbox.prototype,"disabled",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Checkbox.prototype,"checked",void 0),Re([Xi("checked")],e.Checkbox.prototype,"defaultChecked",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Checkbox.prototype,"indeterminate",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Checkbox.prototype,"required",void 0),Re([Pt({reflect:!0})],e.Checkbox.prototype,"form",void 0),Re([Pt({reflect:!0})],e.Checkbox.prototype,"name",void 0),Re([Pt({reflect:!0})],e.Checkbox.prototype,"value",void 0),Re([Pt({reflect:!0,attribute:"unchecked-icon"})],e.Checkbox.prototype,"uncheckedIcon",void 0),Re([Pt({reflect:!0,attribute:"checked-icon"})],e.Checkbox.prototype,"checkedIcon",void 0),Re([Pt({reflect:!0,attribute:"indeterminate-icon"})],e.Checkbox.prototype,"indeterminateIcon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Checkbox.prototype,"invalid",void 0),Re([fi("disabled",!0),fi("indeterminate",!0),fi("required",!0)],e.Checkbox.prototype,"onDisabledChange",null),Re([fi("checked",!0)],e.Checkbox.prototype,"onCheckedChange",null),e.Checkbox=Re([Et("mdui-checkbox")],e.Checkbox);let oo=class extends St{render(){return Zi('')}};oo.styles=Ji,oo=Re([Et("mdui-icon-check")],oo);let ro=class extends St{render(){return Zi('')}};ro.styles=Ji,ro=Re([Et("mdui-icon-clear")],ro);const no=Pe`:host{--shape-corner:var(--mdui-shape-corner-small);position:relative;display:inline-block;overflow:hidden;border-radius:var(--shape-corner);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:box-shadow var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);height:2rem;background-color:rgb(var(--mdui-color-surface));border:.0625rem solid rgb(var(--mdui-color-outline));color:rgb(var(--mdui-color-on-surface-variant));font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height);--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}.button{padding-right:.4375rem;padding-left:.4375rem}:host([variant=input]) .button{padding-right:.1875rem;padding-left:.1875rem}:host([selected]) .button{padding-right:.5rem;padding-left:.5rem}:host([selected][variant=input]) .button{padding-right:.25rem;padding-left:.25rem}:host([elevated]) .button{padding-right:.5rem;padding-left:.5rem}:host([variant=assist]){color:rgb(var(--mdui-color-on-surface));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([elevated]){border-width:0;background-color:rgb(var(--mdui-color-surface-container-low));box-shadow:var(--mdui-elevation-level1)}:host([selected]){color:rgb(var(--mdui-color-on-secondary-container));background-color:rgb(var(--mdui-color-secondary-container));border-width:0;--mdui-comp-ripple-state-layer-color:var( + --mdui-color-on-secondary-container + )}:host([disabled]),:host([loading]){cursor:default;pointer-events:none}:host([disabled]){border-color:rgba(var(--mdui-color-on-surface),12%);color:rgba(var(--mdui-color-on-surface),38%);box-shadow:var(--mdui-elevation-level0)}:host([disabled][elevated]),:host([disabled][selected]){background-color:rgba(var(--mdui-color-on-surface),12%)}:host([selected][hover]){box-shadow:var(--mdui-elevation-level1)}:host([elevated][hover]){color:rgb(var(--mdui-color-on-secondary-container));box-shadow:var(--mdui-elevation-level2)}:host([variant=filter][hover]),:host([variant=input][hover]),:host([variant=suggestion][hover]){color:rgb(var(--mdui-color-on-surface-variant))}:host([variant=filter][focus-visible]),:host([variant=input][focus-visible]),:host([variant=suggestion][focus-visible]){border-color:rgb(var(--mdui-color-on-surface-variant))}:host([dragged]),:host([dragged][hover]){box-shadow:var(--mdui-elevation-level4)}.button{overflow:visible}.label{display:inline-flex;padding-right:.5rem;padding-left:.5rem}.end-icon,.icon,.selected-icon{display:inline-flex;font-size:1.28571429em;color:rgb(var(--mdui-color-on-surface-variant))}:host([variant=assist]) .end-icon,:host([variant=assist]) .icon,:host([variant=assist]) .selected-icon{color:rgb(var(--mdui-color-primary))}:host([selected]) .end-icon,:host([selected]) .icon,:host([selected]) .selected-icon{color:rgb(var(--mdui-color-on-secondary-container))}:host([disabled]) .end-icon,:host([disabled]) .icon,:host([disabled]) .selected-icon{opacity:.38;color:rgb(var(--mdui-color-on-surface))}.end-icon .i,.icon .i,.selected-icon .i,::slotted([slot=end-icon]),::slotted([slot=icon]),::slotted([slot=selected-icon]){font-size:inherit}:host([variant=input]) .has-icon .icon,:host([variant=input]) .has-icon .selected-icon,:host([variant=input]) .has-icon mdui-circular-progress{margin-left:.25rem}:host([variant=input]) .has-end-icon .end-icon{margin-right:.25rem}mdui-circular-progress{display:inline-flex;width:1.125rem;height:1.125rem}:host([disabled]) mdui-circular-progress{stroke:rgba(var(--mdui-color-on-surface),38%)}::slotted(mdui-avatar[slot=end-icon]),::slotted(mdui-avatar[slot=icon]),::slotted(mdui-avatar[slot=selected-icon]){width:1.5rem;height:1.5rem}:host([disabled]) ::slotted(mdui-avatar[slot=end-icon]),:host([disabled]) ::slotted(mdui-avatar[slot=icon]),:host([disabled]) ::slotted(mdui-avatar[slot=selected-icon]){opacity:.38}::slotted(mdui-avatar[slot=icon]),::slotted(mdui-avatar[slot=selected-icon]){margin-left:-.25rem;margin-right:-.125rem}::slotted(mdui-avatar[slot=end-icon]){margin-right:-.25rem;margin-left:-.125rem}.delete-icon{display:inline-flex;font-size:1.28571429em;transition:background-color var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);border-radius:var(--mdui-shape-corner-full);margin-right:-.25rem;margin-left:-.25rem;padding:.25rem;color:rgb(var(--mdui-color-on-surface-variant))}.delete-icon:hover{background-color:rgba(var(--mdui-color-on-surface-variant),12%)}.has-end-icon .delete-icon{margin-left:.25rem}:host([variant=assiat]) .delete-icon{color:rgb(var(--mdui-color-primary))}:host([variant=input]) .delete-icon{margin-right:.0625rem}:host([disabled]) .delete-icon{color:rgba(var(--mdui-color-on-surface),38%)}.delete-icon .i,::slotted([slot=delete-icon]){font-size:inherit}::slotted(mdui-avatar[slot=delete-icon]){width:1.125rem;height:1.125rem}`;e.Chip=class extends qi{constructor(){super(),this.variant="assist",this.elevated=!1,this.selectable=!1,this.selected=!1,this.deletable=!1,this.rippleRef=Ei(),this.hasSlotController=new jt(this,"icon","selected-icon","end-icon"),this.onClick=this.onClick.bind(this),this.onKeyDown=this.onKeyDown.bind(this)}get rippleElement(){return this.rippleRef.value}onSelectedChange(){vi(this,"change")}firstUpdated(e){super.firstUpdated(e),this.addEventListener("click",this.onClick),this.addEventListener("keydown",this.onKeyDown)}render(){const e=this.icon||this.hasSlotController.test("icon"),t=this.endIcon||this.hasSlotController.test("end-icon"),i=this.selectedIcon||["assist","filter"].includes(this.variant)||e||this.hasSlotController.test("selected-icon"),o=Mi({button:!0,"has-icon":this.loading||!this.selected&&e||this.selected&&i,"has-end-icon":t});return ct`${this.isButton()?this.renderButton({className:o,part:"button",content:this.renderInner()}):this.disabled||this.loading?ct`${this.renderInner()}`:this.renderAnchor({className:o,part:"button",content:this.renderInner()})}`}onClick(){this.disabled||this.loading||this.selectable&&(this.selected=!this.selected)}onKeyDown(e){this.disabled||this.loading||(this.selectable&&" "===e.key&&(e.preventDefault(),this.selected=!this.selected),this.deletable&&["Delete","Backspace"].includes(e.key)&&vi(this,"delete"))}onDelete(e){e.stopPropagation(),vi(this,"delete")}renderIcon(){if(this.loading)return this.renderLoading();const e=()=>this.icon?ct``:Gt;return this.selected?ct`${(()=>this.selectedIcon?ct``:"assist"===this.variant||"filter"===this.variant?ct``:e())()}`:ct`${e()}`}renderLabel(){return ct``}renderEndIcon(){return ct`${this.endIcon?ct``:Gt}`}renderDeleteIcon(){return this.deletable?ct`${this.deleteIcon?ct``:ct``}`:Gt}renderInner(){return[this.renderIcon(),this.renderLabel(),this.renderEndIcon(),this.renderDeleteIcon()]}},e.Chip.styles=[qi.styles,no],Re([Pt({reflect:!0})],e.Chip.prototype,"variant",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Chip.prototype,"elevated",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Chip.prototype,"selectable",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Chip.prototype,"selected",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Chip.prototype,"deletable",void 0),Re([Pt({reflect:!0})],e.Chip.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"selected-icon"})],e.Chip.prototype,"selectedIcon",void 0),Re([Pt({reflect:!0,attribute:"end-icon"})],e.Chip.prototype,"endIcon",void 0),Re([Pt({reflect:!0,attribute:"delete-icon"})],e.Chip.prototype,"deleteIcon",void 0),Re([fi("selected",!0)],e.Chip.prototype,"onSelectedChange",null),e.Chip=Re([Et("mdui-chip")],e.Chip);const so=Pe`:host{display:block}`;e.Collapse=class extends St{constructor(){super(...arguments),this.accordion=!1,this.disabled=!1,this.activeKeys=[],this.items=[]}onActiveKeysChange(){var e;this.value=this.accordion?null===(e=this.items.find((e=>this.activeKeys.includes(e.key))))||void 0===e?void 0:e.value:this.items.filter((e=>this.activeKeys.includes(e.key))).map((e=>e.value)),vi(this,"change")}onValueChange(){if(this.accordion){const e=this.value;if(e){const t=this.items.find((t=>t.value===e));this.activeKeys=t?[t.key]:[]}else this.activeKeys=[]}else{const e=this.value;e.length?this.activeKeys=this.items.filter((t=>e.includes(t.value))).map((e=>e.key)):this.activeKeys=[]}this.updateActive()}connectedCallback(){super.connectedCallback(),this.syncItems()}render(){return ct``}syncItems(){this.items=P(this).find("mdui-collapse-item").get()}onClick(e){if(this.disabled)return;if(e.button)return;const t=e.target.closest("mdui-collapse-item");if(t.disabled)return;const i=e.composedPath();if((!t.trigger||i.find((e=>f(e)&&P(e).is(t.trigger))))&&i.find((e=>f(e)&&e.part.contains("header")))){if(this.accordion)this.activeKeys.includes(t.key)?this.activeKeys=[]:this.activeKeys=[t.key];else{const e=[...this.activeKeys];e.includes(t.key)?e.splice(e.indexOf(t.key),1):e.push(t.key),this.activeKeys=e}this.updateActive()}}onSlotChange(){this.syncItems(),this.updateActive()}updateActive(){this.items.forEach((e=>e.active=this.activeKeys.includes(e.key)))}},e.Collapse.styles=[Wt,so],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Collapse.prototype,"accordion",void 0),Re([Pt()],e.Collapse.prototype,"value",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Collapse.prototype,"disabled",void 0),Re([Mt()],e.Collapse.prototype,"activeKeys",void 0),Re([fi("activeKeys",!0)],e.Collapse.prototype,"onActiveKeysChange",null),Re([fi("value")],e.Collapse.prototype,"onValueChange",null),e.Collapse=Re([Et("mdui-collapse")],e.Collapse);const ao=Pe`:host{display:flex;flex-direction:column}.header{display:block}.body{display:block;overflow:hidden;transition:height var(--mdui-motion-duration-short4) var(--mdui-motion-easing-emphasized)}.body.opened{overflow:visible}:host([active]) .body{transition-duration:var(--mdui-motion-duration-medium4)}`;function lo(e,t,i){return e?t():null==i?void 0:i()}function co(e,t,i){return e?new Promise((o=>{if(i.duration===1/0)throw new Error("Promise-based animations must be finite.");d(i.duration)&&isNaN(i.duration)&&(i.duration=0),""===i.easing&&(i.easing="linear");const r=e.animate(t,i);r.addEventListener("cancel",o,{once:!0}),r.addEventListener("finish",o,{once:!0})})):Promise.resolve()}function ho(e){return e?Promise.all(e.getAnimations().map((e=>new Promise((t=>{const i=requestAnimationFrame(t);e.addEventListener("cancel",(()=>i),{once:!0}),e.addEventListener("finish",(()=>i),{once:!0}),e.cancel()}))))):Promise.resolve()}function uo(e){const t=s(),i=e.tagName.toLowerCase();return"-1"!==e.getAttribute("tabindex")&&(!e.hasAttribute("disabled")&&((!e.hasAttribute("aria-disabled")||"false"===e.getAttribute("aria-disabled"))&&(!("input"===i&&"radio"===e.getAttribute("type")&&!e.hasAttribute("checked"))&&(null!==e.offsetParent&&("hidden"!==t.getComputedStyle(e).visibility&&(!("audio"!==i&&"video"!==i||!e.hasAttribute("controls"))||(!!e.hasAttribute("tabindex")||(!(!e.hasAttribute("contenteditable")||"false"===e.getAttribute("contenteditable"))||["button","input","select","textarea","a","audio","video","summary"].includes(i)))))))))}e.CollapseItem=class extends St{constructor(){super(...arguments),this.disabled=!1,this.active=!1,this.state="closed",this.key=yi(),this.bodyRef=Ei()}onActiveChange(){this.hasUpdated?(this.state=this.active?"open":"close",vi(this,this.state),this.updateBodyHeight()):this.state=this.active?"opened":"closed"}firstUpdated(e){super.firstUpdated(e),P(this.bodyRef.value).on("transitionend",(e=>{e.target===this.bodyRef.value&&(this.state=this.active?"opened":"closed",vi(this,this.state),this.updateBodyHeight())})),this.updateBodyHeight()}render(){return ct`${this.header}`}updateBodyHeight(){const e=this.bodyRef.value.scrollHeight;"close"===this.state&&(P(this.bodyRef.value).height(e),this.bodyRef.value.clientLeft),P(this.bodyRef.value).height("opened"===this.state?"auto":"open"===this.state?e:0)}},e.CollapseItem.styles=[Wt,ao],Re([Pt({reflect:!0})],e.CollapseItem.prototype,"value",void 0),Re([Pt({reflect:!0})],e.CollapseItem.prototype,"header",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.CollapseItem.prototype,"disabled",void 0),Re([Pt()],e.CollapseItem.prototype,"trigger",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.CollapseItem.prototype,"active",void 0),Re([Mt()],e.CollapseItem.prototype,"state",void 0),Re([fi("active")],e.CollapseItem.prototype,"onActiveChange",null),e.CollapseItem=Re([Et("mdui-collapse-item")],e.CollapseItem);let po=[];class mo{constructor(e){this.tabDirection="forward",this.element=e,this.handleFocusIn=this.handleFocusIn.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleKeyUp=this.handleKeyUp.bind(this)}activate(){po.push(this.element),document.addEventListener("focusin",this.handleFocusIn),document.addEventListener("keydown",this.handleKeyDown),document.addEventListener("keyup",this.handleKeyUp)}deactivate(){po=po.filter((e=>e!==this.element)),document.removeEventListener("focusin",this.handleFocusIn),document.removeEventListener("keydown",this.handleKeyDown),document.removeEventListener("keyup",this.handleKeyUp)}isActive(){return po[po.length-1]===this.element}checkFocus(){if(this.isActive()&&!this.element.matches(":focus-within")){const{start:e,end:t}=function(e){var t,i;const o=[];return function e(t){t instanceof HTMLElement&&(o.push(t),null!==t.shadowRoot&&"open"===t.shadowRoot.mode&&e(t.shadowRoot)),[...t.children].forEach((t=>e(t)))}(e),{start:null!==(t=o.find((e=>uo(e))))&&void 0!==t?t:null,end:null!==(i=o.reverse().find((e=>uo(e))))&&void 0!==i?i:null}}(this.element),i="forward"===this.tabDirection?e:t;"function"==typeof(null==i?void 0:i.focus)&&i.focus({preventScroll:!0})}}handleFocusIn(){this.checkFocus()}handleKeyDown(e){"Tab"===e.key&&e.shiftKey&&(this.tabDirection="backward"),requestAnimationFrame((()=>this.checkFocus()))}handleKeyUp(){this.tabDirection="forward"}}const vo=(e,t)=>{const i=`--mdui-motion-easing-${t}`;return P(e).css(i).trim()},fo=(e,t)=>{const i=`--mdui-motion-duration-${t}`;return parseFloat(P(e).css(i).trim())},go=new Set,bo="mdui-lock-screen",yo=(e,t)=>{const i=r();go.add(e),P(t||i.body).addClass(bo)},wo=(e,t)=>{const i=r();go.delete(e),0===go.size&&P(t||i.body).removeClass(bo)},ko=Pe`:host{--shape-corner:var(--mdui-shape-corner-extra-large);--z-index:2300;position:fixed;z-index:var(--z-index);display:none;align-items:center;justify-content:center;inset:0;padding:3rem}::slotted(mdui-top-app-bar[slot=header]){position:absolute;border-top-left-radius:var(--mdui-shape-corner-extra-large);border-top-right-radius:var(--mdui-shape-corner-extra-large);background-color:rgb(var(--mdui-color-surface-container-high))}:host([fullscreen]){--shape-corner:var(--mdui-shape-corner-none);padding:0}:host([fullscreen]) ::slotted(mdui-top-app-bar[slot=header]){border-top-left-radius:var(--mdui-shape-corner-none);border-top-right-radius:var(--mdui-shape-corner-none)}.overlay{position:fixed;inset:0;background-color:rgba(var(--mdui-color-scrim),.4)}.panel{position:relative;display:flex;flex-direction:column;max-height:100%;border-radius:var(--shape-corner);outline:0;transform-origin:top;min-width:17.5rem;max-width:35rem;padding:1.5rem;background-color:rgb(var(--mdui-color-surface-container-high));box-shadow:var(--mdui-elevation-level3)}:host([fullscreen]) .panel{width:100%;max-width:100%;height:100%;max-height:100%;box-shadow:var(--mdui-elevation-level0)}.header{display:flex;flex-direction:column}.has-icon .header{align-items:center}.icon{display:flex;color:rgb(var(--mdui-color-secondary));font-size:1.5rem}.icon mdui-icon,::slotted([slot=icon]){font-size:inherit}.headline{display:flex;color:rgb(var(--mdui-color-on-surface));font-size:var(--mdui-typescale-headline-small-size);font-weight:var(--mdui-typescale-headline-small-weight);letter-spacing:var(--mdui-typescale-headline-small-tracking);line-height:var(--mdui-typescale-headline-small-line-height)}.icon+.headline{padding-top:1rem}.body{overflow:auto}.header+.body{margin-top:1rem}.description{display:flex;color:rgb(var(--mdui-color-on-surface-variant));font-size:var(--mdui-typescale-body-medium-size);font-weight:var(--mdui-typescale-body-medium-weight);letter-spacing:var(--mdui-typescale-body-medium-tracking);line-height:var(--mdui-typescale-body-medium-line-height)}:host([fullscreen]) .description{color:rgb(var(--mdui-color-on-surface))}.has-description.has-default .description{margin-bottom:1rem}.action{display:flex;justify-content:flex-end;padding-top:1.5rem}.action::slotted(:not(:first-child)){margin-left:.5rem}:host([stacked-actions]) .action{flex-direction:column;align-items:end}:host([stacked-actions]) .action::slotted(:not(:first-child)){margin-left:0;margin-top:.5rem}`;e.Dialog=class extends St{constructor(){super(...arguments),this.open=!1,this.fullscreen=!1,this.closeOnEsc=!1,this.closeOnOverlayClick=!1,this.stackedActions=!1,this.overlayRef=Ei(),this.panelRef=Ei(),this.bodyRef=Ei(),this.hasSlotController=new jt(this,"header","icon","headline","description","action","[default]")}async onOpenChange(){var e;const t=this.hasUpdated;t||await this.updateComplete;const i=Array.from(this.panelRef.value.querySelectorAll(".header, .body, .actions")),o=vo(this,"linear"),r=vo(this,"emphasized-decelerate"),n=vo(this,"emphasized-accelerate");if(this.open){if(t){if(vi(this,"open",{cancelable:!0}).defaultPrevented)return}this.style.display="flex";const n=null!==(e=this.topAppBarElements)&&void 0!==e?e:[];if(n.length){const e=n[0];e.scrollTarget||(e.scrollTarget=this.bodyRef.value),this.bodyRef.value.style.marginTop="0"}this.originalTrigger=document.activeElement,this.modalHelper.activate(),yo(this),await Promise.all([ho(this.overlayRef.value),ho(this.panelRef.value),...i.map((e=>ho(e)))]),requestAnimationFrame((()=>{const e=this.querySelector("[autofocus]");e?e.focus({preventScroll:!0}):this.panelRef.value.focus({preventScroll:!0})}));const s=fo(this,"medium4");return await Promise.all([co(this.overlayRef.value,[{opacity:0},{opacity:1,offset:.3},{opacity:1}],{duration:t?s:0,easing:o}),co(this.panelRef.value,[{transform:"translateY(-1.875rem) scaleY(0)"},{transform:"translateY(0) scaleY(1)"}],{duration:t?s:0,easing:r}),co(this.panelRef.value,[{opacity:0},{opacity:1,offset:.1},{opacity:1}],{duration:t?s:0,easing:o}),...i.map((e=>co(e,[{opacity:0},{opacity:0,offset:.2},{opacity:1,offset:.8},{opacity:1}],{duration:t?s:0,easing:o})))]),void(t&&vi(this,"opened"))}if(!this.open&&t){if(vi(this,"close",{cancelable:!0}).defaultPrevented)return;this.modalHelper.deactivate(),await Promise.all([ho(this.overlayRef.value),ho(this.panelRef.value),...i.map((e=>ho(e)))]);const e=fo(this,"short4");await Promise.all([co(this.overlayRef.value,[{opacity:1},{opacity:0}],{duration:e,easing:o}),co(this.panelRef.value,[{transform:"translateY(0) scaleY(1)"},{transform:"translateY(-1.875rem) scaleY(0.6)"}],{duration:e,easing:n}),co(this.panelRef.value,[{opacity:1},{opacity:1,offset:.75},{opacity:0}],{duration:e,easing:o}),...i.map((t=>co(t,[{opacity:1},{opacity:0,offset:.75},{opacity:0}],{duration:e,easing:o})))]),this.style.display="none",wo(this);const t=this.originalTrigger;return"function"==typeof(null==t?void 0:t.focus)&&setTimeout((()=>t.focus())),void vi(this,"closed")}}connectedCallback(){super.connectedCallback(),this.modalHelper=new mo(this),this.addEventListener("keydown",(e=>{this.open&&this.closeOnEsc&&"Escape"===e.key&&(e.stopPropagation(),this.open=!1)}))}disconnectedCallback(){super.disconnectedCallback(),wo(this)}render(){const e=this.hasSlotController.test("action"),t=this.hasSlotController.test("[default]"),i=!!this.icon||this.hasSlotController.test("icon"),o=!!this.headline||this.hasSlotController.test("headline"),r=!!this.description||this.hasSlotController.test("description"),n=i||o||this.hasSlotController.test("header"),s=r||t;return ct`
                                            ${lo(n,(()=>ct`${lo(i,(()=>this.renderIcon()))} ${lo(o,(()=>this.renderHeadline()))}`))} ${lo(s,(()=>ct`
                                            ${lo(r,(()=>this.renderDescription()))}
                                            `))} ${lo(e,(()=>ct``))}
                                            `}onOverlayClick(){vi(this,"overlay-click"),this.closeOnOverlayClick&&(this.open=!1)}renderIcon(){return ct`${this.icon?ct``:Gt}`}renderHeadline(){return ct`${this.headline}`}renderDescription(){return ct`${this.description}`}},e.Dialog.styles=[Wt,ko],Re([Pt({reflect:!0})],e.Dialog.prototype,"icon",void 0),Re([Pt({reflect:!0})],e.Dialog.prototype,"headline",void 0),Re([Pt({reflect:!0})],e.Dialog.prototype,"description",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Dialog.prototype,"open",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Dialog.prototype,"fullscreen",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"close-on-esc"})],e.Dialog.prototype,"closeOnEsc",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"close-on-overlay-click"})],e.Dialog.prototype,"closeOnOverlayClick",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"stacked-actions"})],e.Dialog.prototype,"stackedActions",void 0),Re([Lt({slot:"header",selector:"mdui-top-app-bar",flatten:!0})],e.Dialog.prototype,"topAppBarElements",void 0),Re([fi("open")],e.Dialog.prototype,"onOpenChange",null),e.Dialog=Re([Et("mdui-dialog")],e.Dialog);const Co=Pe`:host{display:block;height:.0625rem;background-color:rgb(var(--mdui-color-surface-variant))}:host([inset]){margin-left:1rem}:host([middle]){margin-left:1rem;margin-right:1rem}:host([vertical]){height:100%;width:.0625rem}`;e.Divider=class extends St{constructor(){super(...arguments),this.vertical=!1,this.inset=!1,this.middle=!1}render(){return ct``}},e.Divider.styles=[Wt,Co],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Divider.prototype,"vertical",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Divider.prototype,"inset",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Divider.prototype,"middle",void 0),e.Divider=Re([Et("mdui-divider")],e.Divider);const xo=Pe`:host{--z-index:2100;display:contents}.panel{display:block;position:fixed;z-index:var(--z-index)}`;e.Dropdown=class extends St{constructor(){super(),this.open=!1,this.disabled=!1,this.trigger="click",this.placement="auto",this.stayOpenOnClick=!1,this.openDelay=150,this.closeDelay=150,this.openOnPointer=!1,this.panelRef=Ei(),this.onDocumentClick=this.onDocumentClick.bind(this),this.onDocumentKeydown=this.onDocumentKeydown.bind(this),this.onWindowScroll=this.onWindowScroll.bind(this),this.onMouseLeave=this.onMouseLeave.bind(this),this.onFocus=this.onFocus.bind(this),this.onClick=this.onClick.bind(this),this.onContextMenu=this.onContextMenu.bind(this),this.onMouseEnter=this.onMouseEnter.bind(this),this.onPanelClick=this.onPanelClick.bind(this)}get triggerSlot(){return this.triggerSlots[0]}get panelSlot(){return this.panelSlots[0]}async onPositionChange(){this.open&&this.updatePositioner()}async onOpenChange(){var e,t,i,o;const r=this.hasUpdated,n=vo(this,"linear"),s=vo(this,"emphasized-decelerate"),a=vo(this,"emphasized-accelerate");if(this.open){if(r||await this.updateComplete,r){if(vi(this,"open",{cancelable:!0}).defaultPrevented)return}"function"==typeof(null===(e=this.panelSlot)||void 0===e?void 0:e.focus)&&this.panelSlot.focus();const t=fo(this,"medium4");return await ho(this.panelRef.value),this.panelRef.value.hidden=!1,this.updatePositioner(),await Promise.all([co(this.panelRef.value,[{transform:`${this.getCssScaleName()}(0.45)`},{transform:`${this.getCssScaleName()}(1)`}],{duration:r?t:0,easing:s}),co(this.panelRef.value,[{opacity:0},{opacity:1,offset:.125},{opacity:1}],{duration:r?t:0,easing:n})]),void(r&&vi(this,"opened"))}if(!this.open&&r){if(vi(this,"close",{cancelable:!0}).defaultPrevented)return;this.hasTrigger("focus")||"function"!=typeof(null===(t=this.triggerSlot)||void 0===t?void 0:t.focus)||!this.contains(document.activeElement)&&!this.contains(null!==(o=null===(i=document.activeElement)||void 0===i?void 0:i.assignedSlot)&&void 0!==o?o:null)||this.triggerSlot.focus();const e=fo(this,"short4");await ho(this.panelRef.value),await Promise.all([co(this.panelRef.value,[{transform:`${this.getCssScaleName()}(1)`},{transform:`${this.getCssScaleName()}(0.45)`}],{duration:e,easing:a}),co(this.panelRef.value,[{opacity:1},{opacity:1,offset:.875},{opacity:0}],{duration:e,easing:n})]),this.panelRef.value&&(this.panelRef.value.hidden=!0),vi(this,"closed")}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this.onDocumentClick),document.addEventListener("keydown",this.onDocumentKeydown),window.addEventListener("scroll",this.onWindowScroll),this.updateComplete.then((()=>{this.observeResize=Ci(this.triggerSlot,(()=>{this.updatePositioner()}))}))}disconnectedCallback(){var e;super.disconnectedCallback(),document.removeEventListener("pointerdown",this.onDocumentClick),document.removeEventListener("keydown",this.onDocumentKeydown),window.removeEventListener("scroll",this.onWindowScroll),null===(e=this.observeResize)||void 0===e||e.unobserve()}firstUpdated(e){super.firstUpdated(e),this.addEventListener("mouseleave",this.onMouseLeave),this.triggerSlot.addEventListener("focus",this.onFocus),this.triggerSlot.addEventListener("click",this.onClick),this.triggerSlot.addEventListener("contextmenu",this.onContextMenu),this.triggerSlot.addEventListener("mouseenter",this.onMouseEnter),P(this.panelRef.value).on("click",this.onPanelClick)}render(){return ct``}getCssScaleName(){return"horizontal"===this.animateDirection?"scaleX":"scaleY"}onDocumentClick(e){if(this.disabled||!this.open)return;const t=e.composedPath();t.includes(this)||(this.open=!1),this.hasTrigger("contextmenu")&&!this.hasTrigger("click")&&t.includes(this.triggerSlot)&&(this.open=!1)}onDocumentKeydown(e){var t;!this.disabled&&this.open&&("Escape"!==e.key?"Tab"===e.key&&(this.hasTrigger("focus")||"function"!=typeof(null===(t=this.triggerSlot)||void 0===t?void 0:t.focus)||e.preventDefault(),this.open=!1):this.open=!1)}onWindowScroll(){window.requestAnimationFrame((()=>this.onPositionChange()))}hasTrigger(e){return this.trigger.split(" ").includes(e)}onFocus(){this.disabled||this.open||!this.hasTrigger("focus")||(this.open=!0)}onClick(e){this.disabled||e.button||!this.hasTrigger("click")||this.open&&(this.hasTrigger("hover")||this.hasTrigger("focus"))||(this.pointerOffsetX=e.offsetX,this.pointerOffsetY=e.offsetY,this.open=!this.open)}onPanelClick(e){const t=e;this.disabled||this.stayOpenOnClick||!P(t.target).is("mdui-menu-item")||(this.open=!1)}onContextMenu(e){!this.disabled&&this.hasTrigger("contextmenu")&&(e.preventDefault(),this.pointerOffsetX=e.offsetX,this.pointerOffsetY=e.offsetY,this.open=!0)}onMouseEnter(){!this.disabled&&this.hasTrigger("hover")&&(window.clearTimeout(this.closeTimeout),this.openDelay?this.openTimeout=window.setTimeout((()=>{this.open=!0}),this.openDelay):this.open=!0)}onMouseLeave(){!this.disabled&&this.hasTrigger("hover")&&(window.clearTimeout(this.openTimeout),this.closeTimeout=window.setTimeout((()=>{this.open=!1}),this.closeDelay||50))}updatePositioner(){var e;const t=P(this.panelRef.value),i=P(window),o=this.panelSlots,r=Math.max(...null!==(e=null==o?void 0:o.map((e=>e.offsetWidth)))&&void 0!==e?e:[]),n=null==o?void 0:o.map((e=>e.offsetHeight)).reduce(((e,t)=>e+t)),s=this.triggerSlot.getBoundingClientRect(),a=this.openOnPointer?{top:this.pointerOffsetY+s.top,left:this.pointerOffsetX+s.left,width:0,height:0}:s;let l,c,d,h,u=this.placement;if("auto"===u){const e=i.width(),t=i.height();let o,s;o=t-a.top-a.height>n+8?"bottom":a.top>n+8?"top":e-a.left-a.width>r+8?"right":a.left>r+8?"left":"bottom",s=["top","bottom"].includes(o)?e-a.left>r+8?"start":a.left+a.width/2>r/2+8&&e-a.left-a.width/2>r/2+8?void 0:a.left+a.width>r+8?"end":"start":t-a.top>n+8?"start":a.top+a.height/2>n/2+8&&t-a.top-a.height/2>n/2+8?void 0:a.top+a.height>n+8?"end":"start",u=s?[o,s].join("-"):o}const[p,m]=u.split("-");switch(this.animateDirection=["top","bottom"].includes(p)?"vertical":"horizontal",p){case"top":c="bottom",d=a.top-n;break;case"bottom":c="top",d=a.top+a.height;break;default:switch(c="center",m){case"start":d=a.top;break;case"end":d=a.top+a.height-n;break;default:d=a.top+a.height/2-n/2}}switch(p){case"left":l="right",h=a.left-r;break;case"right":l="left",h=a.left+a.width;break;default:switch(l="center",m){case"start":h=a.left;break;case"end":h=a.left+a.width-r;break;default:h=a.left+a.width/2-r/2}}t.css({top:d,left:h,transformOrigin:[l,c].join(" ")})}},e.Dropdown.styles=[Wt,xo],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Dropdown.prototype,"open",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Dropdown.prototype,"disabled",void 0),Re([Pt({reflect:!0})],e.Dropdown.prototype,"trigger",void 0),Re([Pt({reflect:!0})],e.Dropdown.prototype,"placement",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"stay-open-on-click"})],e.Dropdown.prototype,"stayOpenOnClick",void 0),Re([Pt({type:Number,reflect:!0,attribute:"open-delay"})],e.Dropdown.prototype,"openDelay",void 0),Re([Pt({type:Number,reflect:!0,attribute:"close-delay"})],e.Dropdown.prototype,"closeDelay",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"open-on-pointer"})],e.Dropdown.prototype,"openOnPointer",void 0),Re([Lt({slot:"trigger",flatten:!0})],e.Dropdown.prototype,"triggerSlots",void 0),Re([Lt({flatten:!0})],e.Dropdown.prototype,"panelSlots",void 0),Re([fi("placement",!0),fi("openOnPointer",!0)],e.Dropdown.prototype,"onPositionChange",null),Re([fi("open")],e.Dropdown.prototype,"onOpenChange",null),e.Dropdown=Re([Et("mdui-dropdown")],e.Dropdown);const $o=(e=0)=>new Promise((t=>setTimeout(t,e))),Ro=Pe`:host{--shape-corner-small:var(--mdui-shape-corner-small);--shape-corner-normal:var(--mdui-shape-corner-large);--shape-corner-large:var(--mdui-shape-corner-extra-large);position:relative;display:inline-block;overflow:hidden;text-align:center;border-radius:var(--shape-corner-normal);cursor:pointer;-webkit-tap-highlight-color:transparent;transition-property:box-shadow;transition-timing-function:var(--mdui-motion-easing-emphasized);transition-duration:var(--mdui-motion-duration-medium4);width:3.5rem;height:3.5rem;box-shadow:var(--mdui-elevation-level3);font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height)}.button{padding:0 1rem}:host([size=small]) .button{padding:0 .5rem}:host([size=large]) .button{padding:0 1.875rem}:host([lowered]){box-shadow:var(--mdui-elevation-level1)}:host([focus-visible]){box-shadow:var(--mdui-elevation-level3)}:host([lowered][focus-visible]){box-shadow:var(--mdui-elevation-level1)}:host([pressed]){box-shadow:var(--mdui-elevation-level3)}:host([lowered][pressed]){box-shadow:var(--mdui-elevation-level1)}:host([hover]){box-shadow:var(--mdui-elevation-level4)}:host([lowered][hover]){box-shadow:var(--mdui-elevation-level2)}:host([variant=primary]){color:rgb(var(--mdui-color-on-primary-container));background-color:rgb(var(--mdui-color-primary-container));--mdui-comp-ripple-state-layer-color:var( + --mdui-color-on-primary-container + )}:host([variant=surface]){color:rgb(var(--mdui-color-primary));background-color:rgb(var(--mdui-color-surface-container-high));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=surface][lowered]){background-color:rgb(var(--mdui-color-surface-container-low))}:host([variant=secondary]){color:rgb(var(--mdui-color-on-secondary-container));background-color:rgb(var(--mdui-color-secondary-container));--mdui-comp-ripple-state-layer-color:var( + --mdui-color-on-secondary-container + )}:host([variant=tertiary]){color:rgb(var(--mdui-color-on-tertiary-container));background-color:rgb(var(--mdui-color-tertiary-container));--mdui-comp-ripple-state-layer-color:var( + --mdui-color-on-tertiary-container + )}:host([size=small]){border-radius:var(--shape-corner-small);width:2.5rem;height:2.5rem}:host([size=large]){border-radius:var(--shape-corner-large);width:6rem;height:6rem}:host([disabled]),:host([loading]){cursor:default;pointer-events:none}:host([disabled]){color:rgba(var(--mdui-color-on-surface),38%);background-color:rgba(var(--mdui-color-on-surface),12%);box-shadow:var(--mdui-elevation-level0)}:host([extended]){width:auto}.label{display:inline-flex;transition:opacity var(--mdui-motion-duration-short2) var(--mdui-motion-easing-linear) var(--mdui-motion-duration-short2);padding-left:.25rem;padding-right:.25rem}.has-icon .label{margin-left:.5rem}:host([size=small]) .has-icon .label{margin-left:.25rem}:host([size=large]) .has-icon .label{margin-left:1rem}:host(:not([extended])) .label{opacity:0;transition-delay:0s;transition-duration:var(--mdui-motion-duration-short1)}:host([size=large]) .label{font-size:1.5em}.icon{display:inline-flex;font-size:1.71428571em}:host([size=large]) .icon{font-size:2.57142857em}.icon mdui-icon,::slotted([slot=icon]){font-size:inherit}mdui-circular-progress{display:inline-flex;width:1.5rem;height:1.5rem}:host([size=large]) mdui-circular-progress{width:2.25rem;height:2.25rem}:host([disabled]) mdui-circular-progress{stroke:rgba(var(--mdui-color-on-surface),38%)}`;e.Fab=class extends qi{constructor(){super(...arguments),this.variant="primary",this.size="normal",this.extended=!1,this.rippleRef=Ei(),this.hasSlotController=new jt(this,"icon")}get rippleElement(){return this.rippleRef.value}async onExtendedChange(){const e=this.hasUpdated;this.extended?this.style.width=`${this.scrollWidth}px`:this.style.width="",await this.updateComplete,this.extended&&!e&&(await $o(),this.style.width=`${this.scrollWidth}px`),e||(await $o(),this.style.transitionProperty="box-shadow, width, bottom, transform")}render(){const e=Mi({button:!0,"has-icon":this.icon||this.hasSlotController.test("icon")});return ct`${this.isButton()?this.renderButton({className:e,part:"button",content:this.renderInner()}):this.disabled||this.loading?ct`${this.renderInner()}`:this.renderAnchor({className:e,part:"button",content:this.renderInner()})}`}renderLabel(){return ct``}renderIcon(){return this.loading?this.renderLoading():ct`${this.icon?ct``:Gt}`}renderInner(){return[this.renderIcon(),this.renderLabel()]}},e.Fab.styles=[qi.styles,Ro],Re([Pt({reflect:!0})],e.Fab.prototype,"variant",void 0),Re([Pt({reflect:!0})],e.Fab.prototype,"size",void 0),Re([Pt({reflect:!0})],e.Fab.prototype,"icon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Fab.prototype,"extended",void 0),Re([fi("extended")],e.Fab.prototype,"onExtendedChange",null),e.Fab=Re([Et("mdui-fab")],e.Fab);const So=Pe`:host{position:relative;display:flex;flex:1 1 auto;overflow:hidden}:host([full-height]){height:100%}`;e.Layout=class extends St{constructor(){super(...arguments),this.fullHeight=!1}render(){return ct``}},e.Layout.styles=[Wt,So],Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"full-height"})],e.Layout.prototype,"fullHeight",void 0),e.Layout=Re([Et("mdui-layout")],e.Layout);const To=Pe`:host{display:flex;z-index:1}`;e.LayoutItem=class extends Si{constructor(){super(...arguments),this.placement="top"}get layoutPlacement(){return this.placement}onPlacementChange(){var e;null===(e=this.layoutManager)||void 0===e||e.updateLayout(this)}render(){return ct``}},e.LayoutItem.styles=[Wt,To],Re([Pt({reflect:!0})],e.LayoutItem.prototype,"placement",void 0),Re([fi("placement",!0)],e.LayoutItem.prototype,"onPlacementChange",null),e.LayoutItem=Re([Et("mdui-layout-item")],e.LayoutItem);const Eo=Pe`:host{flex:1 0 auto;max-width:100%;overflow:auto}`;e.LayoutMain=class extends St{connectedCallback(){super.connectedCallback();const e=this.parentElement;a(e,"mdui-layout")&&(this.layoutManager=Ri(e),this.layoutManager.registerMain(this))}disconnectedCallback(){super.disconnectedCallback(),this.layoutManager&&this.layoutManager.unregisterMain()}render(){return ct``}},e.LayoutMain.styles=[Wt,Eo],e.LayoutMain=Re([Et("mdui-layout-main")],e.LayoutMain);const Io=Pe`:host{--shape-corner:var(--mdui-shape-corner-none);position:relative;display:inline-block;width:100%;overflow:hidden;border-radius:var(--shape-corner);background-color:rgb(var(--mdui-color-surface-container-highest));height:.25rem}.determinate,.indeterminate{background-color:rgb(var(--mdui-color-primary))}.determinate{height:100%;transition:width var(--mdui-motion-duration-long2) var(--mdui-motion-easing-standard)}.indeterminate::before{position:absolute;top:0;bottom:0;left:0;background-color:inherit;animation:mdui-comp-progress-indeterminate 2s var(--mdui-motion-easing-linear) infinite;content:' '}.indeterminate::after{position:absolute;top:0;bottom:0;left:0;background-color:inherit;animation:mdui-comp-progress-indeterminate-short 2s var(--mdui-motion-easing-linear) infinite;content:' '}@keyframes mdui-comp-progress-indeterminate{0%{left:0;width:0}50%{left:30%;width:70%}75%{left:100%;width:0}}@keyframes mdui-comp-progress-indeterminate-short{0%{left:0;width:0}50%{left:0;width:0}75%{left:0;width:25%}100%{left:100%;width:0}}`;e.LinearProgress=class extends St{constructor(){super(...arguments),this.max=1}render(){var e;if(!u(this.value)){const t=this.value;return ct`
                                            `}return ct`
                                            `}},e.LinearProgress.styles=[Wt,Io],Re([Pt({type:Number,reflect:!0})],e.LinearProgress.prototype,"max",void 0),Re([Pt({type:Number})],e.LinearProgress.prototype,"value",void 0),e.LinearProgress=Re([Et("mdui-linear-progress")],e.LinearProgress);const Ao=Pe`:host{--shape-corner:var(--mdui-shape-corner-none);--shape-corner-rounded:var(--mdui-shape-corner-extra-large);position:relative;display:block;border-radius:var(--shape-corner);--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([rounded]),:host([rounded]) mdui-ripple{border-radius:var(--shape-corner-rounded)}:host([active]){background-color:rgb(var(--mdui-color-secondary-container));--mdui-comp-ripple-state-layer-color:var( + --mdui-color-on-secondary-container + )}:host([disabled]){pointer-events:none}.container{cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}:host([disabled]) .container{cursor:default;opacity:.38}:host([nonclickable]:not([href])) .container{cursor:auto;-webkit-user-select:auto;user-select:auto}.preset{display:flex;align-items:center;text-decoration:none;padding:.5rem 1.5rem .5rem 1rem;min-height:3.5rem}:host([alignment=start]) .preset{align-items:flex-start}:host([alignment=end]) .preset{align-items:flex-end}.body{display:flex;flex:1 1 100%;flex-direction:column;justify-content:center;min-width:0}.headline{display:block;color:rgb(var(--mdui-color-on-surface));font-size:var(--mdui-typescale-body-large-size);font-weight:var(--mdui-typescale-body-large-weight);letter-spacing:var(--mdui-typescale-body-large-tracking);line-height:var(--mdui-typescale-body-large-line-height)}:host([active]) .headline{color:rgb(var(--mdui-color-on-secondary-container))}.description{display:none;color:rgb(var(--mdui-color-on-surface-variant));font-size:var(--mdui-typescale-body-medium-size);font-weight:var(--mdui-typescale-body-medium-weight);letter-spacing:var(--mdui-typescale-body-medium-tracking);line-height:var(--mdui-typescale-body-medium-line-height)}:host([disabled]) .description,:host([focused]) .description,:host([hover]) .description,:host([pressed]) .description{color:rgb(var(--mdui-color-on-surface))}.has-description .description{display:block}:host([description-line='1']) .description,:host([headline-line='1']) .headline{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host([description-line='2']) .description,:host([description-line='3']) .description,:host([headline-line='2']) .headline,:host([headline-line='3']) .headline{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;-webkit-box-orient:vertical}:host([description-line='2']) .description,:host([headline-line='2']) .headline{-webkit-line-clamp:2}:host([description-line='3']) .description,:host([headline-line='3']) .headline{-webkit-line-clamp:3}.end-icon,.icon{display:flex;flex:0 0 auto;font-size:var(--mdui-typescale-label-small-size);font-weight:var(--mdui-typescale-label-small-weight);letter-spacing:var(--mdui-typescale-label-small-tracking);line-height:var(--mdui-typescale-label-small-line-height);color:rgb(var(--mdui-color-on-surface-variant))}:host([disabled]) .end-icon,:host([disabled]) .icon,:host([focused]) .end-icon,:host([focused]) .icon,:host([hover]) .end-icon,:host([hover]) .icon,:host([pressed]) .end-icon,:host([pressed]) .icon{color:rgb(var(--mdui-color-on-surface))}:host([active]) .end-icon,:host([active]) .icon{color:rgb(var(--mdui-color-on-secondary-container))}.end-icon mdui-icon,.icon mdui-icon,.is-end-icon ::slotted([slot=end-icon]),.is-icon ::slotted([slot=icon]){font-size:1.5rem}.has-icon .icon{margin-right:1rem}.has-icon ::slotted(mdui-checkbox[slot=icon]),.has-icon ::slotted(mdui-radio[slot=icon]){margin-left:-.5rem}.has-end-icon .end-icon{margin-left:1rem}.has-end-icon ::slotted(mdui-checkbox[slot=end-icon]),.has-end-icon ::slotted(mdui-radio[slot=end-icon]){margin-right:-.5rem}`;e.ListItem=class extends(_i(Ki(Fi(St)))){constructor(){super(...arguments),this.disabled=!1,this.active=!1,this.nonclickable=!1,this.rounded=!1,this.alignment="center",this.rippleRef=Ei(),this.itemRef=Ei(),this.hasSlotController=new jt(this,"[default]","description","icon","end-icon","custom")}get rippleElement(){return this.rippleRef.value}get rippleDisabled(){return this.focusDisabled}get focusElement(){return this.href?this.itemRef.value:this}get focusDisabled(){return this.href?this.disabled:this.disabled||this.nonclickable}render(){const e=Mi({container:!0,preset:!this.hasSlotController.test("custom"),"has-icon":this.icon||this.hasSlotController.test("icon"),"has-end-icon":this.endIcon||this.hasSlotController.test("end-icon"),"has-description":this.description||this.hasSlotController.test("description"),"is-icon":a(this.iconElements[0],"mdui-icon"),"is-end-icon":(t=this.endIconElements[0],null!==(i=null==t?void 0:t.nodeName.toLowerCase())&&void 0!==i?i:"").startsWith("mdui-icon-")});var t,i;return ct`${this.href&&!this.disabled?this.renderAnchor({className:e,content:this.renderInner(),part:"container",refDirective:Pi(this.itemRef)}):ct`
                                            ${this.renderInner()}
                                            `}`}renderInner(){const e=this.hasSlotController.test("[default]");return ct`${this.icon?ct``:Gt}
                                            ${e?ct``:ct`
                                            ${this.headline}
                                            `}${this.description}
                                            ${this.endIcon?ct``:Gt}
                                            `}},e.ListItem.styles=[Wt,Ao],Re([Pt({reflect:!0})],e.ListItem.prototype,"headline",void 0),Re([Pt({type:Number,reflect:!0,attribute:"headline-line"})],e.ListItem.prototype,"headlineLine",void 0),Re([Pt({reflect:!0})],e.ListItem.prototype,"description",void 0),Re([Pt({type:Number,reflect:!0,attribute:"description-line"})],e.ListItem.prototype,"descriptionLine",void 0),Re([Pt({reflect:!0})],e.ListItem.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"end-icon"})],e.ListItem.prototype,"endIcon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.ListItem.prototype,"disabled",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.ListItem.prototype,"active",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.ListItem.prototype,"nonclickable",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.ListItem.prototype,"rounded",void 0),Re([Pt({reflect:!0})],e.ListItem.prototype,"alignment",void 0),Re([Lt({slot:"icon",flatten:!0})],e.ListItem.prototype,"iconElements",void 0),Re([Lt({slot:"end-icon",flatten:!0})],e.ListItem.prototype,"endIconElements",void 0),e.ListItem=Re([Et("mdui-list-item")],e.ListItem);const Po=Pe`:host{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;cursor:default;color:rgb(var(--mdui-color-on-surface-variant));font-size:var(--mdui-typescale-label-small-size);font-weight:var(--mdui-typescale-label-small-weight);letter-spacing:var(--mdui-typescale-label-small-tracking);line-height:var(--mdui-typescale-label-small-line-height);padding-left:1rem;padding-right:1.5rem;height:3.5rem;line-height:3.5rem}`;e.ListSubheader=class extends St{render(){return ct``}},e.ListSubheader.styles=[Wt,Po],e.ListSubheader=Re([Et("mdui-list-subheader")],e.ListSubheader);const Mo=Pe`:host{display:block;padding:.5rem 0}::slotted(mdui-divider[middle]){margin-left:1rem;margin-right:1.5rem}`;e.List=class extends St{render(){return ct``}},e.List.styles=[Wt,Mo],e.List=Re([Et("mdui-list")],e.List);let Do=class extends St{render(){return Zi('')}};Do.styles=Ji,Do=Re([Et("mdui-icon-arrow-right")],Do);const Bo=Pe`:host{position:relative;display:block}:host([selected]){background-color:rgba(var(--mdui-color-primary),12%)}:host([disabled]){pointer-events:none}.container{cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}:host([disabled]) .container{cursor:default;opacity:.38}.preset{display:flex;align-items:center;text-decoration:none;height:3rem;padding:0 .75rem}.preset.dense{height:2rem}.label-container{flex:1 1 100%;min-width:0}.label{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:rgb(var(--mdui-color-on-surface));font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking)}.end-icon,.end-text,.icon,.selected-icon{display:none;flex:0 0 auto;color:rgb(var(--mdui-color-on-surface-variant))}.has-end-icon .end-icon,.has-end-text .end-text,.has-icon .icon,.has-icon .selected-icon{display:flex}.end-icon,.icon,.selected-icon{font-size:1.5rem}.end-icon::slotted(mdui-avatar),.icon::slotted(mdui-avatar),.selected-icon::slotted(mdui-avatar){width:1.5rem;height:1.5rem}.dense .end-icon,.dense .icon,.dense .selected-icon{font-size:1.125rem}.dense .end-icon::slotted(mdui-avatar),.dense .icon::slotted(mdui-avatar),.dense .selected-icon::slotted(mdui-avatar){width:1.125rem;height:1.125rem}.end-icon .i,.icon .i,.selected-icon .i,::slotted([slot=end-icon]),::slotted([slot=icon]),::slotted([slot=selected-icon]){font-size:inherit}.end-text{font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height)}.icon,.selected-icon{margin-right:.75rem}.end-icon,.end-text{margin-left:.75rem}.arrow-right{color:rgb(var(--mdui-color-on-surface))}.submenu{--shape-corner:var(--mdui-shape-corner-extra-small);display:block;position:absolute;z-index:1;border-radius:var(--shape-corner);background-color:rgb(var(--mdui-color-surface-container));box-shadow:var(--mdui-elevation-level2);min-width:7rem;max-width:17.5rem;padding-top:.5rem;padding-bottom:.5rem;--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}.submenu::slotted(mdui-divider){margin-top:.5rem;margin-bottom:.5rem}`;e.MenuItem=class extends(_i(Ki(Fi(St)))){constructor(){super(),this.disabled=!1,this.submenuOpen=!1,this.selected=!1,this.dense=!1,this.focusable=!1,this.key=yi(),this.rippleRef=Ei(),this.containerRef=Ei(),this.submenuRef=Ei(),this.hasSlotController=new jt(this,"[default]","icon","end-icon","end-text","submenu","custom"),this.onOuterClick=this.onOuterClick.bind(this),this.onFocus=this.onFocus.bind(this),this.onBlur=this.onBlur.bind(this),this.onClick=this.onClick.bind(this),this.onKeydown=this.onKeydown.bind(this),this.onMouseEnter=this.onMouseEnter.bind(this),this.onMouseLeave=this.onMouseLeave.bind(this)}get focusDisabled(){return this.disabled||!this.focusable}get focusElement(){return this.href?this.containerRef.value:this}get rippleDisabled(){return this.disabled}get rippleElement(){return this.rippleRef.value}get hasSubmenu(){return this.hasSlotController.test("submenu")}async onOpenChange(){const e=this.hasUpdated,t=vo(this,"linear"),i=vo(this,"emphasized-decelerate"),o=vo(this,"emphasized-accelerate");if(this.submenuOpen){if(e||await this.updateComplete,e){if(vi(this,"submenu-open",{cancelable:!0}).defaultPrevented)return}const o=fo(this,"medium4");return await ho(this.submenuRef.value),this.submenuRef.value.hidden=!1,this.updateSubmenuPositioner(),await Promise.all([co(this.submenuRef.value,[{transform:"scaleY(0.45)"},{transform:"scaleY(1)"}],{duration:e?o:0,easing:i}),co(this.submenuRef.value,[{opacity:0},{opacity:1,offset:.125},{opacity:1}],{duration:e?o:0,easing:t})]),void(e&&vi(this,"submenu-opened"))}if(!this.submenuOpen&&e){if(vi(this,"submenu-close",{cancelable:!0}).defaultPrevented)return;const e=fo(this,"short4");await ho(this.submenuRef.value),await Promise.all([co(this.submenuRef.value,[{transform:"scaleY(1)"},{transform:"scaleY(0.45)"}],{duration:e,easing:o}),co(this.submenuRef.value,[{opacity:1},{opacity:1,offset:.875},{opacity:0}],{duration:e,easing:t})]),this.submenuRef.value.hidden=!0,vi(this,"submenu-closed")}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this.onOuterClick)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("pointerdown",this.onOuterClick)}firstUpdated(e){super.firstUpdated(e),this.addEventListener("focus",this.onFocus),this.addEventListener("blur",this.onBlur),this.addEventListener("click",this.onClick),this.addEventListener("keydown",this.onKeydown),this.addEventListener("mouseenter",this.onMouseEnter),this.addEventListener("mouseleave",this.onMouseLeave)}render(){const e=this.hasSubmenu,t=this.hasSlotController.test("custom"),i=this.hasSlotController.test("end-icon"),o=!this.endIcon&&e&&!i,r=this.endIcon||e||i,n=!u(this.icon)||"single"===this.selects||"multiple"===this.selects||this.hasSlotController.test("icon"),s=!!this.endText||this.hasSlotController.test("end-text"),a=Mi({container:!0,dense:this.dense,preset:!t,"has-icon":n,"has-end-text":s,"has-end-icon":r});return ct`${this.href&&!this.disabled?this.renderAnchor({part:"container",className:a,content:this.renderInner(o,n),refDirective:Pi(this.containerRef)}):ct`
                                            ${this.renderInner(o,n)}
                                            `} ${lo(e,(()=>ct``))}`}onOuterClick(e){this.disabled||!this.submenuOpen||this===e.target||P.contains(this,e.target)||(this.submenuOpen=!1)}hasTrigger(e){return!!this.submenuTrigger&&this.submenuTrigger.split(" ").includes(e)}onFocus(){!this.disabled&&!this.submenuOpen&&this.hasTrigger("focus")&&this.hasSubmenu&&(this.submenuOpen=!0)}onBlur(){!this.disabled&&this.submenuOpen&&this.hasTrigger("focus")&&this.hasSubmenu&&(this.submenuOpen=!1)}onClick(e){this.disabled||e.button||this.hasTrigger("click")&&e.target===this&&this.hasSubmenu&&(this.submenuOpen&&(this.hasTrigger("hover")||this.hasTrigger("focus"))||(this.submenuOpen=!this.submenuOpen))}onKeydown(e){!this.disabled&&this.hasSubmenu&&(this.submenuOpen||"Enter"!==e.key||(e.stopPropagation(),this.submenuOpen=!0),this.submenuOpen&&"Escape"===e.key&&(e.stopPropagation(),this.submenuOpen=!1))}onMouseEnter(){!this.disabled&&this.hasTrigger("hover")&&this.hasSubmenu&&(window.clearTimeout(this.submenuCloseTimeout),this.submenuOpenDelay?this.submenuOpenTimeout=window.setTimeout((()=>{this.submenuOpen=!0}),this.submenuOpenDelay):this.submenuOpen=!0)}onMouseLeave(){!this.disabled&&this.hasTrigger("hover")&&this.hasSubmenu&&(window.clearTimeout(this.submenuOpenTimeout),this.submenuCloseTimeout=window.setTimeout((()=>{this.submenuOpen=!1}),this.submenuCloseDelay||50))}updateSubmenuPositioner(){const e=P(window),t=P(this.submenuRef.value),i=this.getBoundingClientRect(),o=t.innerWidth(),r=t.innerHeight();let n="bottom",s="right";e.height()-i.top>r+8?n="bottom":i.top+i.height>r+8&&(n="top"),e.width()-i.left-i.width>o+8?s="right":i.left>o+8&&(s="left"),P(this.submenuRef.value).css({top:"bottom"===n?0:i.height-r,left:"right"===s?i.width:-o,transformOrigin:["right"===s?0:"100%","bottom"===n?0:"100%"].join(" ")})}renderInner(e,t){return ct`${this.selected?ct`${this.selectedIcon?ct``:ct``}`:ct`${t?ct``:Gt}`}
                                            ${this.endText}${e?ct``:ct`${this.endIcon?ct``:Gt}`}
                                            `}},e.MenuItem.styles=[Wt,Bo],Re([Pt({reflect:!0})],e.MenuItem.prototype,"value",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.MenuItem.prototype,"disabled",void 0),Re([Pt({reflect:!0})],e.MenuItem.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"end-icon"})],e.MenuItem.prototype,"endIcon",void 0),Re([Pt({reflect:!0,attribute:"end-text"})],e.MenuItem.prototype,"endText",void 0),Re([Pt({reflect:!0,attribute:"selected-icon"})],e.MenuItem.prototype,"selectedIcon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"submenu-open"})],e.MenuItem.prototype,"submenuOpen",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.MenuItem.prototype,"selected",void 0),Re([Mt()],e.MenuItem.prototype,"dense",void 0),Re([Mt()],e.MenuItem.prototype,"selects",void 0),Re([Mt()],e.MenuItem.prototype,"submenuTrigger",void 0),Re([Mt()],e.MenuItem.prototype,"submenuOpenDelay",void 0),Re([Mt()],e.MenuItem.prototype,"submenuCloseDelay",void 0),Re([Mt()],e.MenuItem.prototype,"focusable",void 0),Re([fi("submenuOpen")],e.MenuItem.prototype,"onOpenChange",null),e.MenuItem=Re([Et("mdui-menu-item")],e.MenuItem);const Lo=Pe`:host{--shape-corner:var(--mdui-shape-corner-extra-small);position:relative;display:block;border-radius:var(--shape-corner);background-color:rgb(var(--mdui-color-surface-container));box-shadow:var(--mdui-elevation-level2);min-width:7rem;max-width:17.5rem;padding-top:.5rem;padding-bottom:.5rem;--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}::slotted(mdui-divider){margin-top:.5rem;margin-bottom:.5rem}`;e.Menu=class extends St{constructor(){super(...arguments),this.dense=!1,this.submenuTrigger="click hover",this.submenuOpenDelay=200,this.submenuCloseDelay=200,this.selectedKeys=[],this.hasSetDefaultValue=!1,this.lastActiveItems=[]}get items(){return P(this.childrenItems).find("mdui-menu-item").add(this.childrenItems).get()}get itemsEnabled(){return this.items.filter((e=>!e.disabled))}get isSingle(){return"single"===this.selects}get isMultiple(){return"multiple"===this.selects}get isSelectable(){return this.isSingle||this.isMultiple}get isSubmenu(){return!P(this).parent().length}get lastActiveItem(){const e=this.lastActiveItems.length?this.lastActiveItems.length-1:0;return this.lastActiveItems[e]}set lastActiveItem(e){const t=this.lastActiveItems.length?this.lastActiveItems.length-1:0;this.lastActiveItems[t]=e}onSlotChange(){this.items.forEach((e=>{e.dense=this.dense,e.selects=this.selects,e.submenuTrigger=this.submenuTrigger,e.submenuOpenDelay=this.submenuOpenDelay,e.submenuCloseDelay=this.submenuCloseDelay}))}onSelectsChange(){!this.isSelectable&&this.selectedKeys.length&&(this.selectedKeys=[],this.onSelectedKeysChange(),this.onValueChange()),this.isSingle&&this.selectedKeys.length>1&&(this.selectedKeys=this.selectedKeys.slice(0,1),this.onSelectedKeysChange(),this.onValueChange())}onSelectedKeysChange(e){const t=this.itemsEnabled.filter((e=>this.selectedKeys.includes(e.key))).map((e=>e.value));this.isMultiple&&e&&e.length===this.selectedKeys.length&&e.every(((e,t)=>e===this.selectedKeys[t]))?this.hasSetDefaultValue=!0:(this.value=this.isMultiple?t:t[0]||void 0,this.hasSetDefaultValue?vi(this,"change"):this.hasSetDefaultValue=!0)}async onValueChange(){if(this.hasUpdated||await this.updateComplete,!this.isSelectable)return void this.updateSelected();const e=(this.isSingle||c(this.value)?[this.value]:this.value).filter((e=>e));if(e.length)if(this.isSingle){const t=this.itemsEnabled.find((t=>t.value===e[0]));this.selectedKeys=t?[t.key]:[]}else this.isMultiple&&(this.selectedKeys=this.itemsEnabled.filter((t=>e.includes(t.value))).map((e=>e.key)));else this.selectedKeys=[];this.updateSelected(),this.updateFocusable()}focus(e){this.lastActiveItem&&this.focusOne(this.lastActiveItem,e)}blur(){this.lastActiveItem&&this.lastActiveItem.blur()}firstUpdated(e){super.firstUpdated(e),this.updateFocusable(),this.lastActiveItem=this.items.find((e=>e.focusable)),this.addEventListener("submenu-open",(e=>{const t=P(e.target),i=t.children("mdui-menu-item:not([disabled])").get(),o=t.parents("mdui-menu-item").length+1;i.length&&(this.lastActiveItems[o]=i[0],this.updateFocusable(),this.focusOne(this.lastActiveItems[o]))})),this.addEventListener("submenu-close",(e=>{const t=P(e.target).parents("mdui-menu-item").length+1;this.lastActiveItems.length-1===t&&(this.lastActiveItems.pop(),this.updateFocusable(),this.lastActiveItems[t-1]&&this.focusOne(this.lastActiveItems[t-1]))}))}render(){return ct``}getSiblingsItems(e,t=!1){return P(e).parent().children("mdui-menu-item"+(t?":not([disabled])":"")).get()}updateFocusable(){if(this.lastActiveItem)this.items.forEach((e=>e.focusable=e.key===this.lastActiveItem.key));else if(this.selectedKeys.length){if(this.isSingle)this.items.forEach((e=>e.focusable=this.selectedKeys.includes(e.key)));else if(this.isMultiple){const e=this.items.find((e=>e.focusable));(null==e?void 0:e.key)&&this.selectedKeys.includes(e.key)||this.itemsEnabled.filter((e=>this.selectedKeys.includes(e.key))).forEach(((e,t)=>e.focusable=!t))}}else this.itemsEnabled.forEach(((e,t)=>e.focusable=!t))}updateSelected(){this.items.forEach((e=>e.selected=this.selectedKeys.includes(e.key)))}selectOne(e){if(this.isMultiple){const t=[...this.selectedKeys];t.includes(e.key)?t.splice(t.indexOf(e.key),1):t.push(e.key),this.selectedKeys=t}this.isSingle&&(this.selectedKeys.includes(e.key)?this.selectedKeys=[]:this.selectedKeys=[e.key]),this.updateSelected()}focusableOne(e){this.items.forEach((t=>t.focusable=t.key===e.key))}async focusOne(e,t){await $o(),e.focus(t)}onClick(e){if(this.isSubmenu)return;if(e.button)return;const t=e.target.closest("mdui-menu-item");(null==t?void 0:t.disabled)||(this.lastActiveItem=t,this.isSelectable&&t.value&&this.selectOne(t),this.focusableOne(t),this.focusOne(t))}onKeyDown(e){if(this.isSubmenu)return;const t=e.target;if("Enter"===e.key&&(e.preventDefault(),t.click())," "===e.key&&(e.preventDefault(),this.isSelectable&&t.value&&(this.selectOne(t),this.focusableOne(t),this.focusOne(t))),["ArrowUp","ArrowDown","Home","End"].includes(e.key)){const i=this.getSiblingsItems(t,!0),o=i.find((e=>e.focusable));let r=o?i.indexOf(o):0;if(i.length>0)return e.preventDefault(),"ArrowDown"===e.key?r++:"ArrowUp"===e.key?r--:"Home"===e.key?r=0:"End"===e.key&&(r=i.length-1),r<0&&(r=i.length-1),r>i.length-1&&(r=0),this.lastActiveItem=i[r],this.focusableOne(i[r]),void this.focusOne(i[r])}}},e.Menu.styles=[Wt,Lo],Re([Pt({reflect:!0})],e.Menu.prototype,"selects",void 0),Re([Pt()],e.Menu.prototype,"value",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Menu.prototype,"dense",void 0),Re([Pt({reflect:!0,attribute:"submenu-trigger"})],e.Menu.prototype,"submenuTrigger",void 0),Re([Pt({type:Number,reflect:!0,attribute:"submenu-open-delay"})],e.Menu.prototype,"submenuOpenDelay",void 0),Re([Pt({type:Number,reflect:!0,attribute:"submenu-close-delay"})],e.Menu.prototype,"submenuCloseDelay",void 0),Re([Mt()],e.Menu.prototype,"selectedKeys",void 0),Re([Lt({flatten:!0,selector:"mdui-menu-item"})],e.Menu.prototype,"childrenItems",void 0),Re([fi("dense"),fi("selects"),fi("submenuTrigger"),fi("submenuOpenDelay"),fi("submenuCloseDelay")],e.Menu.prototype,"onSlotChange",null),Re([fi("selects")],e.Menu.prototype,"onSelectsChange",null),Re([fi("selectedKeys",!0)],e.Menu.prototype,"onSelectedKeysChange",null),Re([fi("value")],e.Menu.prototype,"onValueChange",null),e.Menu=Re([Et("mdui-menu")],e.Menu);const _o=Pe`:host{--shape-corner-indicator:var(--mdui-shape-corner-full);position:relative;z-index:0;flex:1;overflow:hidden;min-width:3rem;--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}.container{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;text-decoration:none;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;transition:padding var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard);padding-top:.75rem;padding-bottom:.75rem}mdui-ripple{z-index:1;left:50%;transform:translateX(-50%);transition:margin-top var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard);width:4rem;height:2rem;margin-top:.75rem;border-radius:var(--mdui-shape-corner-full)}.indicator{position:relative;display:flex;align-items:center;justify-content:center;background-color:transparent;border-radius:var(--shape-corner-indicator);transition:background-color var(--mdui-motion-duration-short1) var(--mdui-motion-easing-standard),width var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard);height:2rem;width:2rem}::slotted([slot=badge]){position:absolute;transform:translate(50%,-50%)}::slotted([slot=badge][variant=small]){transform:translate(.5625rem,-.5625rem)}.active-icon,.icon{color:rgb(var(--mdui-color-on-surface-variant));font-size:1.5rem}.active-icon mdui-icon,.icon mdui-icon,::slotted([slot=active]),::slotted([slot=icon]){font-size:inherit}.icon{display:flex}.active-icon{display:none}.label{display:flex;align-items:center;transition:opacity var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);height:1rem;color:rgb(var(--mdui-color-on-surface-variant));margin-top:.25rem;margin-bottom:.25rem;font-size:var(--mdui-typescale-label-medium-size);font-weight:var(--mdui-typescale-label-medium-weight);letter-spacing:var(--mdui-typescale-label-medium-tracking);line-height:var(--mdui-typescale-label-medium-line-height)}:host([label-visibility=selected]:not([active])) mdui-ripple,:host([label-visibility=unlabeled]) mdui-ripple{margin-top:1.5rem}:host([label-visibility=selected]:not([active])) .container,:host([label-visibility=unlabeled]) .container{padding-top:1.5rem;padding-bottom:0}:host([label-visibility=selected]:not([active])) .label,:host([label-visibility=unlabeled]) .label{opacity:0}:host([active]){--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([active]) .indicator{width:4rem;background-color:rgb(var(--mdui-color-secondary-container))}:host([active]) .active-icon,:host([active]) .icon{color:rgb(var(--mdui-color-on-secondary-container))}:host([active]) .has-active-icon .active-icon{display:flex}:host([active]) .has-active-icon .icon{display:none}:host([active]) .label{color:rgb(var(--mdui-color-on-surface))}`;e.NavigationBarItem=class extends(_i(Ki(Fi(St)))){constructor(){super(...arguments),this.active=!1,this.disabled=!1,this.key=yi(),this.rippleRef=Ei(),this.hasSlotController=new jt(this,"active-icon")}get rippleElement(){return this.rippleRef.value}get rippleDisabled(){return this.disabled}get focusElement(){var e;return this.href?null===(e=this.renderRoot)||void 0===e?void 0:e.querySelector("._a"):this}get focusDisabled(){return this.disabled}render(){const e=Mi({container:!0,"has-active-icon":this.activeIcon||this.hasSlotController.test("active-icon")});return ct`${this.href?this.renderAnchor({part:"container",className:e,content:this.renderInner()}):ct`
                                            ${this.renderInner()}
                                            `}`}renderInner(){return ct`
                                            ${this.activeIcon?ct``:Gt}${this.icon?ct``:Gt}
                                            `}},e.NavigationBarItem.styles=[Wt,_o],Re([Pt({reflect:!0})],e.NavigationBarItem.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"active-icon"})],e.NavigationBarItem.prototype,"activeIcon",void 0),Re([Pt({reflect:!0})],e.NavigationBarItem.prototype,"value",void 0),Re([Pt({reflect:!0,attribute:"label-visibility"})],e.NavigationBarItem.prototype,"labelVisibility",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.NavigationBarItem.prototype,"active",void 0),Re([Mt()],e.NavigationBarItem.prototype,"disabled",void 0),e.NavigationBarItem=Re([Et("mdui-navigation-bar-item")],e.NavigationBarItem);const Oo=Pe`:host{--shape-corner:var(--mdui-shape-corner-none);--z-index:2000;position:fixed;right:0;bottom:0;left:0;display:flex;flex:0 0 auto;overflow:hidden;border-radius:var(--shape-corner) var(--shape-corner) 0 0;z-index:var(--z-index);transition-property:transform;transition-duration:var(--mdui-motion-duration-long2);transition-timing-function:var(--mdui-motion-easing-emphasized);height:5rem;background-color:rgb(var(--mdui-color-surface));box-shadow:var(--mdui-elevation-level2)}:host([scroll-target]:not([scroll-target=''])){position:absolute}:host([hide]){transform:translateY(5.625rem);transition-duration:var(--mdui-motion-duration-short4)}`;e.NavigationBar=class extends(gi(Si)){constructor(){super(...arguments),this.hide=!1,this.labelVisibility="auto",this.activeKey=0,this.hasSetDefaultValue=!1}get scrollPaddingPosition(){return"bottom"}get layoutPlacement(){return"bottom"}get items(){return P(this).find("mdui-navigation-bar-item").get()}onActiveKeyChange(){const e=this.items.find((e=>e.key===this.activeKey));this.value=null==e?void 0:e.value,this.hasSetDefaultValue?vi(this,"change"):this.hasSetDefaultValue=!0}onValueChange(){var e;const t=this.items.find((e=>e.value===this.value));this.activeKey=null!==(e=null==t?void 0:t.key)&&void 0!==e?e:0,this.updateActive()}onLabelVisibilityChange(){const e=this.items,t="auto"===this.labelVisibility?e.length<=3?"labeled":"selected":this.labelVisibility;e.forEach((e=>{e.labelVisibility=t}))}connectedCallback(){super.connectedCallback(),this.addEventListener("transitionend",(e=>{e.target===this&&vi(this,this.hide?"hidden":"shown")}))}render(){return ct``}runScrollThreshold(e){if(!e&&!this.hide){vi(this,"hide").defaultPrevented||(this.hide=!0)}if(e&&this.hide){vi(this,"show").defaultPrevented||(this.hide=!1)}}onClick(e){if(e.button)return;const t=e.target.closest("mdui-navigation-bar-item");this.activeKey=t.key,this.updateActive()}updateActive(){this.items.forEach((e=>e.active=this.activeKey===e.key))}onSlotChange(){this.onLabelVisibilityChange()}},e.NavigationBar.styles=[Wt,Oo],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.NavigationBar.prototype,"hide",void 0),Re([Pt({reflect:!0,attribute:"label-visibility"})],e.NavigationBar.prototype,"labelVisibility",void 0),Re([Pt({reflect:!0})],e.NavigationBar.prototype,"value",void 0),Re([Pt({reflect:!0,attribute:"scroll-behavior"})],e.NavigationBar.prototype,"scrollBehavior",void 0),Re([Mt()],e.NavigationBar.prototype,"activeKey",void 0),Re([fi("activeKey")],e.NavigationBar.prototype,"onActiveKeyChange",null),Re([fi("value")],e.NavigationBar.prototype,"onValueChange",null),Re([fi("labelVisibility",!0)],e.NavigationBar.prototype,"onLabelVisibilityChange",null),e.NavigationBar=Re([Et("mdui-navigation-bar")],e.NavigationBar);const zo=e=>{const t=s(),i=r(),o=t.getComputedStyle(i.documentElement),n=f(e)?P(e).innerWidth():d(e)?e:P(t).innerWidth(),a=e=>{const t=o.getPropertyValue(`--mdui-breakpoint-${e}`).toLowerCase();return parseFloat(t)};return{up:e=>n>=a(e),down:e=>n{switch(e){case"xs":return"sm";case"sm":return"md";case"md":return"lg";case"lg":return"xl";case"xl":return"xxl"}})(e))},not(e){return!this.only(e)},between(e,t){return this.up(e)&&this.down(t)}}},Fo=Pe`:host{--shape-corner:var(--mdui-shape-corner-large);--z-index:2200;display:none;position:fixed;top:0;bottom:0;left:0;z-index:1;width:22.5rem}:host([placement=right]){left:initial;right:0}:host([mobile]),:host([modal]){top:0!important;right:0;bottom:0!important;width:initial;z-index:var(--z-index)}:host([placement=right][mobile]),:host([placement=right][modal]){left:0}:host([contained]){position:absolute}.overlay{position:absolute;inset:0;z-index:inherit;background-color:rgba(var(--mdui-color-scrim),.4)}.panel{display:block;position:absolute;top:0;bottom:0;left:0;width:100%;overflow:auto;z-index:inherit;background-color:rgb(var(--mdui-color-surface));box-shadow:var(--mdui-elevation-level0)}:host([mobile]) .panel,:host([modal]) .panel{border-radius:0 var(--shape-corner) var(--shape-corner) 0;width:22.5rem;background-color:rgb(var(--mdui-color-surface-container-low));box-shadow:var(--mdui-elevation-level1)}:host([placement=right]) .panel{left:initial;right:0}:host([placement=right][mobile]) .panel,:host([placement=right][modal]) .panel{border-radius:var(--shape-corner) 0 0 var(--shape-corner)}`;e.NavigationDrawer=class extends Si{constructor(){super(...arguments),this.open=!1,this.modal=!1,this.closeOnEsc=!1,this.closeOnOverlayClick=!1,this.placement="left",this.contained=!1,this.mobile=!1,this.overlayRef=Ei(),this.panelRef=Ei()}get layoutPlacement(){return this.placement}get lockTarget(){return this.contained||this.isParentLayout?this.parentElement:document.body}get isModal(){return this.mobile||this.modal}onContainedChange(){var e;this.hasUpdated&&(null===(e=this.observeResize)||void 0===e||e.unobserve()),this.observeResize=Ci(this.contained?this.parentElement:document.body,(()=>{const e=this.contained?this.parentElement:void 0;this.mobile=zo(e).down("md"),this.isParentLayout&&this.layoutManager.updateLayout(this,{width:this.isModal?0:void 0})}))}onPlacementChange(){this.isParentLayout&&this.layoutManager.updateLayout(this)}async onOpenChange(){let e=this.panelRef.value,t=this.overlayRef.value;const i="right"===this.placement,o=i?"paddingRight":"paddingLeft",r=vo(this,"linear"),n=vo(this,"emphasized"),s=(e,t)=>{P(this.layoutManager.getItemsAndMain()).css("transition",p(e)?null:`all ${e}ms ${t}`)},a=async()=>{await Promise.all([this.isModal?ho(t):this.isParentLayout?Promise.resolve():ho(this.lockTarget),this.isModal?ho(e):ho(this)])};if(this.open){const l=this.hasUpdated;if(l||(await this.updateComplete,e=this.panelRef.value,t=this.overlayRef.value),l){if(vi(this,"open",{cancelable:!0}).defaultPrevented)return}this.style.display="block",this.originalTrigger=document.activeElement,this.isModal&&(this.modalHelper.activate(),yo(this,this.lockTarget)),await a(),requestAnimationFrame((()=>{const t=this.querySelector("[autofocus]");t?t.focus({preventScroll:!0}):e.focus({preventScroll:!0})}));const c=fo(this,"long2"),d=[];return this.isModal?d.push(co(t,[{opacity:0},{opacity:1,offset:.3},{opacity:1}],{duration:l?c:0,easing:r})):this.isParentLayout||d.push(co(this.lockTarget,[{[o]:0},{[o]:P(e).innerWidth()+"px"}],{duration:l?c:0,easing:n,fill:"forwards"})),this.isParentLayout&&l&&s(c,n),d.push(co(this.isModal?e:this,[{transform:`translateX(${i?"":"-"}100%)`},{transform:"translateX(0)"}],{duration:l?c:0,easing:n})),await Promise.all(d),this.isParentLayout&&l&&s(null),void(l&&vi(this,"opened"))}if(!this.open&&this.hasUpdated){if(vi(this,"close",{cancelable:!0}).defaultPrevented)return;this.isModal&&this.modalHelper.deactivate(),await a();const l=fo(this,"short4"),c=[];this.isModal?c.push(co(t,[{opacity:1},{opacity:0}],{duration:l,easing:r})):this.isParentLayout||c.push(co(this.lockTarget,[{[o]:P(e).innerWidth()+"px"},{[o]:0}],{duration:l,easing:n,fill:"forwards"})),this.isParentLayout&&(s(l,n),this.layoutManager.updateLayout(this,{width:0})),c.push(co(this.isModal?e:this,[{transform:"translateX(0)"},{transform:`translateX(${i?"":"-"}100%)`}],{duration:l,easing:n})),await Promise.all(c),this.isParentLayout&&s(null),this.style.display="none",this.isModal&&wo(this,this.lockTarget);const d=this.originalTrigger;return"function"==typeof(null==d?void 0:d.focus)&&setTimeout((()=>d.focus())),void vi(this,"closed")}}connectedCallback(){super.connectedCallback(),this.modalHelper=new mo(this),this.addEventListener("keydown",(e=>{this.open&&this.closeOnEsc&&"Escape"===e.key&&this.isModal&&(e.stopPropagation(),this.open=!1)}))}disconnectedCallback(){var e;super.disconnectedCallback(),wo(this,this.lockTarget),null===(e=this.observeResize)||void 0===e||e.unobserve()}render(){return ct`${lo(this.isModal,(()=>ct`
                                            `))}`}onOverlayClick(){vi(this,"overlay-click"),this.closeOnOverlayClick&&(this.open=!1)}},e.NavigationDrawer.styles=[Wt,Fo],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.NavigationDrawer.prototype,"open",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.NavigationDrawer.prototype,"modal",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"close-on-esc"})],e.NavigationDrawer.prototype,"closeOnEsc",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"close-on-overlay-click"})],e.NavigationDrawer.prototype,"closeOnOverlayClick",void 0),Re([Pt({reflect:!0})],e.NavigationDrawer.prototype,"placement",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.NavigationDrawer.prototype,"contained",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.NavigationDrawer.prototype,"mobile",void 0),Re([fi("contained")],e.NavigationDrawer.prototype,"onContainedChange",null),Re([fi("placement")],e.NavigationDrawer.prototype,"onPlacementChange",null),Re([fi("open")],e.NavigationDrawer.prototype,"onOpenChange",null),e.NavigationDrawer=Re([Et("mdui-navigation-drawer")],e.NavigationDrawer);const No=Pe`:host{--shape-corner:var(--mdui-shape-corner-none);--z-index:2000;position:fixed;top:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;border-radius:0 var(--shape-corner) var(--shape-corner) 0;z-index:var(--z-index);width:5rem;background-color:rgb(var(--mdui-color-surface));padding:.375rem .75rem}:host([contained]){position:absolute}:host([divider]){border-right:.0625rem solid rgb(var(--mdui-color-surface-variant));width:5.0625rem}:host([placement=right]){left:initial;right:0;border-radius:var(--shape-corner) 0 0 var(--shape-corner)}:host([placement=right][divider]){border-right:none;border-left:.0625rem solid rgb(var(--mdui-color-surface-variant))}.bottom,.items,.top{display:flex;flex-direction:column;align-items:center;width:100%}.top{margin-bottom:1.75rem}.bottom{margin-top:1.75rem}::slotted([slot=bottom]),::slotted([slot=top]),::slotted(mdui-navigation-rail-item){margin-top:.375rem;margin-bottom:.375rem}:host([alignment=start]) .top-spacer{flex-grow:0}:host([alignment=start]) .bottom-spacer{flex-grow:1}:host([alignment=end]) .top-spacer{flex-grow:1}:host([alignment=end]) .bottom-spacer{flex-grow:0}:host([alignment=center]){justify-content:center}:host([alignment=center]) .bottom,:host([alignment=center]) .top{position:absolute}:host([alignment=center]) .top{top:.375rem}:host([alignment=center]) .bottom{bottom:.375rem}`;e.NavigationRail=class extends Si{constructor(){super(...arguments),this.placement="left",this.alignment="start",this.contained=!1,this.divider=!1,this.activeKey=0,this.hasSlotController=new jt(this,"top","bottom"),this.hasSetDefaultValue=!1}get layoutPlacement(){return this.placement}get items(){return P(this).find("mdui-navigation-rail-item").get()}get parentTarget(){return this.contained||this.isParentLayout?this.parentElement:document.body}get isRight(){return"right"===this.placement}get paddingValue(){return["fixed","absolute"].includes(P(this).css("position"))?this.offsetWidth:void 0}onActiveKeyChange(){const e=this.items.find((e=>e.key===this.activeKey));this.value=null==e?void 0:e.value,this.hasSetDefaultValue?vi(this,"change"):this.hasSetDefaultValue=!0}onValueChange(){var e;const t=this.items.find((e=>e.value===this.value));this.activeKey=null!==(e=null==t?void 0:t.key)&&void 0!==e?e:0,this.updateActive()}onContainedChange(){this.isParentLayout||(P(document.body).css({paddingLeft:this.contained||this.isRight?null:this.paddingValue,paddingRight:this.contained||!this.isRight?null:this.paddingValue}),P(this.parentElement).css({paddingLeft:this.contained&&!this.isRight?this.paddingValue:null,paddingRight:this.contained&&this.isRight?this.paddingValue:null}))}onPlacementChange(){var e;null===(e=this.layoutManager)||void 0===e||e.updateLayout(this),this.items.forEach((e=>{e.placement=this.placement})),this.isParentLayout||P(this.parentTarget).css({paddingLeft:this.isRight?null:this.paddingValue,paddingRight:this.isRight?this.paddingValue:null})}connectedCallback(){super.connectedCallback(),this.isParentLayout||P(this.parentTarget).css({paddingLeft:this.isRight?null:this.paddingValue,paddingRight:this.isRight?this.paddingValue:null})}disconnectedCallback(){super.disconnectedCallback(),this.isParentLayout||P(this.parentTarget).css({paddingLeft:this.isRight?void 0:null,paddingRight:this.isRight?null:void 0})}render(){const e=this.hasSlotController.test("top"),t=this.hasSlotController.test("bottom");return ct`${lo(e,(()=>ct``))} ${lo(t,(()=>ct``))}`}onClick(e){if(e.button)return;const t=e.target.closest("mdui-navigation-rail-item");this.activeKey=t.key,this.updateActive()}updateActive(){this.items.forEach((e=>e.active=this.activeKey===e.key))}onSlotChange(){this.items.forEach((e=>{e.placement=this.placement}))}},e.NavigationRail.styles=[Wt,No],Re([Pt({reflect:!0})],e.NavigationRail.prototype,"value",void 0),Re([Pt({reflect:!0})],e.NavigationRail.prototype,"placement",void 0),Re([Pt({reflect:!0})],e.NavigationRail.prototype,"alignment",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.NavigationRail.prototype,"contained",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.NavigationRail.prototype,"divider",void 0),Re([Mt()],e.NavigationRail.prototype,"activeKey",void 0),Re([fi("activeKey")],e.NavigationRail.prototype,"onActiveKeyChange",null),Re([fi("value")],e.NavigationRail.prototype,"onValueChange",null),Re([fi("contained",!0)],e.NavigationRail.prototype,"onContainedChange",null),Re([fi("placement")],e.NavigationRail.prototype,"onPlacementChange",null),e.NavigationRail=Re([Et("mdui-navigation-rail")],e.NavigationRail);const Vo=Pe`:host{--shape-corner-indicator:var(--mdui-shape-corner-full);position:relative;z-index:0;width:100%;--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}.container{display:flex;flex-direction:column;align-items:center;justify-content:center;text-decoration:none;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;transition:padding var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard);height:3.5rem}mdui-ripple{z-index:1;width:3.5rem;height:2rem;border-radius:var(--mdui-shape-corner-full)}.container:not(.has-label)+mdui-ripple{height:3.5rem}.indicator{position:relative;display:flex;align-items:center;justify-content:center;background-color:transparent;border-radius:var(--shape-corner-indicator);transition:background-color var(--mdui-motion-duration-short1) var(--mdui-motion-easing-standard),width var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard),height var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard);height:2rem;width:2rem}::slotted([slot=badge]){position:absolute;transform:translate(50%,-50%)}:host([placement=right]) ::slotted([slot=badge]){transform:translate(-50%,-50%)}::slotted([slot=badge][variant=small]){transform:translate(.5625rem,-.5625rem)}:host([placement=right]) ::slotted([slot=badge][variant=small]){transform:translate(-.5625rem,-.5625rem)}.active-icon,.icon{color:rgb(var(--mdui-color-on-surface-variant));font-size:1.5rem}.active-icon mdui-icon,.icon mdui-icon,::slotted([slot=active-icon]),::slotted([slot=icon]){font-size:inherit}.icon{display:flex}.active-icon{display:none}.label{display:flex;align-items:center;transition:opacity var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);height:1rem;color:rgb(var(--mdui-color-on-surface-variant));margin-top:.25rem;margin-bottom:.25rem;font-size:var(--mdui-typescale-label-medium-size);font-weight:var(--mdui-typescale-label-medium-weight);letter-spacing:var(--mdui-typescale-label-medium-tracking);line-height:var(--mdui-typescale-label-medium-line-height)}:host([active]){--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([active]) .indicator{width:3.5rem;background-color:rgb(var(--mdui-color-secondary-container))}:host([active]) :not(.has-label) .indicator{height:3.5rem}:host([active]) .active-icon,:host([active]) .icon{color:rgb(var(--mdui-color-on-secondary-container))}:host([active]) .has-active-icon .active-icon{display:flex}:host([active]) .has-active-icon .icon{display:none}:host([active]) .label{color:rgb(var(--mdui-color-on-surface))}`;e.NavigationRailItem=class extends(_i(Ki(Fi(St)))){constructor(){super(...arguments),this.active=!1,this.placement="left",this.disabled=!1,this.key=yi(),this.rippleRef=Ei(),this.hasSlotController=new jt(this,"[default]","active-icon")}get rippleElement(){return this.rippleRef.value}get rippleDisabled(){return this.disabled}get focusElement(){var e;return this.href?null===(e=this.renderRoot)||void 0===e?void 0:e.querySelector("._a"):this}get focusDisabled(){return this.disabled}render(){const e=this.hasSlotController.test("[default]"),t=Mi({container:!0,"has-label":e,"has-active-icon":this.activeIcon||this.hasSlotController.test("active-icon")});return ct`${this.href?this.renderAnchor({part:"container",className:t,content:this.renderInner(e)}):ct`
                                            ${this.renderInner(e)}
                                            `}`}renderInner(e){return ct`
                                            ${this.activeIcon?ct``:Gt}${this.icon?ct``:Gt}
                                            ${e?ct``:ht}`}},e.NavigationRailItem.styles=[Wt,Vo],Re([Pt({reflect:!0})],e.NavigationRailItem.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"active-icon"})],e.NavigationRailItem.prototype,"activeIcon",void 0),Re([Pt({reflect:!0})],e.NavigationRailItem.prototype,"value",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.NavigationRailItem.prototype,"active",void 0),Re([Pt({reflect:!0})],e.NavigationRailItem.prototype,"placement",void 0),Re([Mt()],e.NavigationRailItem.prototype,"disabled",void 0),e.NavigationRailItem=Re([Et("mdui-navigation-rail-item")],e.NavigationRailItem);let Ho=class extends St{render(){return Zi('')}};Ho.styles=Ji,Ho=Re([Et("mdui-icon-circle")],Ho);let Ko=class extends St{render(){return Zi('')}};Ko.styles=Ji,Ko=Re([Et("mdui-icon-radio-button-unchecked")],Ko);const Uo=Pe`:host{position:relative;display:inline-flex;align-items:center;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;touch-action:manipulation;zoom:1;-webkit-user-drag:none;border-radius:.125rem;font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height)}.icon{display:flex;position:absolute;font-size:1.5rem;transition-duration:var(--mdui-motion-duration-short4);transition-timing-function:var(--mdui-motion-easing-standard)}.unchecked-icon{transition-property:color;color:rgb(var(--mdui-color-on-surface-variant))}:host([focused]) .unchecked-icon,:host([hover]) .unchecked-icon,:host([pressed]) .unchecked-icon{color:rgb(var(--mdui-color-on-surface))}.checked-icon{opacity:0;transform:scale(.2);transition-property:color,opacity,transform;color:rgb(var(--mdui-color-primary))}.icon .i,::slotted([slot=checked-icon]),::slotted([slot=unchecked-icon]){color:inherit;font-size:inherit}i{position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;border-radius:50%;width:2.5rem;min-width:2.5rem;height:2.5rem;--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}.label{display:flex;width:100%;padding-top:.625rem;padding-bottom:.625rem;color:rgb(var(--mdui-color-on-surface));transition:color var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard)}:host([checked]) i{--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([checked]) .icon{color:rgb(var(--mdui-color-primary))}:host([checked]) .checked-icon{opacity:1;transform:scale(.5)}:host([invalid]) i{--mdui-comp-ripple-state-layer-color:var(--mdui-color-error)}:host([invalid]) .icon{color:rgb(var(--mdui-color-error))}:host([invalid]) .label{color:rgb(var(--mdui-color-error))}:host([disabled]),:host([group-disabled]){cursor:default;pointer-events:none}:host([disabled]) .icon,:host([group-disabled]) .icon{color:rgba(var(--mdui-color-on-surface),38%)}:host([disabled]) .label,:host([group-disabled]) .label{color:rgba(var(--mdui-color-on-surface),38%)}`;e.Radio=class extends(Ki(Fi(St))){constructor(){super(...arguments),this.value="",this.disabled=!1,this.checked=!1,this.invalid=!1,this.groupDisabled=!1,this.focusable=!1,this.rippleRef=Ei()}get rippleElement(){return this.rippleRef.value}get rippleDisabled(){return this.isDisabled()}get focusElement(){return this}get focusDisabled(){return this.isDisabled()||!this.focusable}onCheckedChange(){this.checked&&vi(this,"change")}connectedCallback(){super.connectedCallback(),this.addEventListener("click",(()=>{this.isDisabled()||(this.checked=!0)}))}render(){return ct`${this.uncheckedIcon?ct``:ct``}${this.checkedIcon?ct``:ct``}`}isDisabled(){return this.disabled||this.groupDisabled}},e.Radio.styles=[Wt,Uo],Re([Pt({reflect:!0})],e.Radio.prototype,"value",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Radio.prototype,"disabled",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Radio.prototype,"checked",void 0),Re([Pt({reflect:!0,attribute:"unchecked-icon"})],e.Radio.prototype,"uncheckedIcon",void 0),Re([Pt({reflect:!0,attribute:"checked-icon"})],e.Radio.prototype,"checkedIcon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Radio.prototype,"invalid",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"group-disabled"})],e.Radio.prototype,"groupDisabled",void 0),Re([Mt()],e.Radio.prototype,"focusable",void 0),Re([fi("checked",!0)],e.Radio.prototype,"onCheckedChange",null),e.Radio=Re([Et("mdui-radio")],e.Radio);const qo=Pe`:host{display:inline-block}fieldset{border:none;padding:0;margin:0;min-width:0}input{position:absolute;padding:0;opacity:0;pointer-events:none;width:1.25rem;height:1.25rem;margin:0 0 0 .625rem}`;function*jo(e,t){if(void 0!==e){let i=0;for(const o of e)yield t(o,i++)}}e.RadioGroup=class extends St{constructor(){super(...arguments),this.disabled=!1,this.name="",this.value="",this.defaultValue="",this.required=!1,this.invalid=!1,this.inputRef=Ei(),this.formController=new Li(this)}get validity(){return this.inputRef.value.validity}get validationMessage(){return this.inputRef.value.validationMessage}get radios(){return P(this).find("mdui-radio").get()}get radiosEnabled(){return P(this).find("mdui-radio:not([disabled])").get()}async onValueChange(){var e;vi(this,"input"),vi(this,"change"),this.radios.forEach((e=>e.checked=e.value===this.value)),this.updateRadioFocusable(),await this.updateComplete;const t=this.formController.getForm();t&&(null===(e=Bi.get(t))||void 0===e?void 0:e.has(this))?(this.invalid=!1,Bi.get(t).delete(this)):this.invalid=!this.inputRef.value.checkValidity()}onInvalidChange(){this.radiosEnabled.forEach((e=>e.invalid=this.invalid))}onDisabledChange(){this.radios.forEach((e=>e.groupDisabled=this.disabled))}checkValidity(){const e=this.inputRef.value.checkValidity();return e||vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}),e}reportValidity(){if(this.invalid=!this.inputRef.value.reportValidity(),this.invalid){vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}).defaultPrevented&&(this.inputRef.value.blur(),this.inputRef.value.focus())}return!this.invalid}setCustomValidity(e){this.inputRef.value.setCustomValidity(e),this.invalid=!this.inputRef.value.checkValidity()}render(){return ct`
                                            `}updateRadioFocusable(){const e=this.radios,t=e.find((e=>e.checked));t?e.forEach((e=>e.focusable=e===t)):this.radiosEnabled.forEach(((e,t)=>e.focusable=!t))}async onRadioClick(e){const t=e.target,i=a(t,"mdui-radio")?t:P(t).closest("mdui-radio")[0];i.disabled||(this.value=i.value,await this.updateComplete,i.focus())}onKeyDown(e){var t;if(!["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"," "].includes(e.key))return;const i=this.radiosEnabled,o=null!==(t=i.find((e=>e.checked)))&&void 0!==t?t:i[0],r=" "===e.key?0:["ArrowUp","ArrowLeft"].includes(e.key)?-1:1;let n=i.indexOf(o)+r;n<0&&(n=i.length-1),n>i.length-1&&(n=0),this.value=i[n].value,this.updateComplete.then((()=>{i[n].focus()})),e.preventDefault()}onSlotChange(){this.radios.forEach((e=>e.checked=e.value===this.value)),this.updateRadioFocusable()}onCheckedChange(e){e.stopPropagation();const t=e.target;this.value=t.value}},e.RadioGroup.styles=[Wt,qo],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.RadioGroup.prototype,"disabled",void 0),Re([Pt({reflect:!0})],e.RadioGroup.prototype,"form",void 0),Re([Pt({reflect:!0})],e.RadioGroup.prototype,"name",void 0),Re([Pt({reflect:!0})],e.RadioGroup.prototype,"value",void 0),Re([Xi()],e.RadioGroup.prototype,"defaultValue",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.RadioGroup.prototype,"required",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.RadioGroup.prototype,"invalid",void 0),Re([fi("value",!0)],e.RadioGroup.prototype,"onValueChange",null),Re([fi("invalid")],e.RadioGroup.prototype,"onInvalidChange",null),Re([fi("disabled")],e.RadioGroup.prototype,"onDisabledChange",null),e.RadioGroup=Re([Et("mdui-radio-group")],e.RadioGroup);const Go=Pe`:host{position:relative;display:block;width:100%;-webkit-tap-highlight-color:transparent;height:2.5rem;padding:0 1.25rem}label{position:relative;display:block;width:100%;height:100%}input[type=range]{position:absolute;inset:0;z-index:4;height:100%;cursor:pointer;opacity:0;-webkit-appearance:none;appearance:none;margin:0 -1.25rem;padding:0 .75rem}:host([disabled]) input[type=range]{cursor:not-allowed}.track-active,.track-inactive{position:absolute;top:50%;height:.25rem;margin-top:-.125rem}.track-inactive{left:-.125rem;right:-.125rem;border-radius:var(--mdui-shape-corner-full);background-color:rgb(var(--mdui-color-surface-container-highest))}:host([invalid]) .track-inactive{background-color:rgba(var(--mdui-color-error),.12)}:host([disabled]) .track-inactive{background-color:rgba(var(--mdui-color-on-surface),.12)}.track-active{background-color:rgb(var(--mdui-color-primary))}:host([invalid]) .track-active{background-color:rgb(var(--mdui-color-error))}:host([disabled]) .track-active{background-color:rgba(var(--mdui-color-on-surface),.38)}.handle{position:absolute;top:50%;transform:translate(-50%);cursor:pointer;z-index:2;width:2.5rem;height:2.5rem;margin-top:-1.25rem;--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([invalid]) .handle{--mdui-comp-ripple-state-layer-color:var(--mdui-color-error)}.handle .elevation,.handle::before{position:absolute;display:block;content:' ';left:.625rem;top:.625rem;width:1.25rem;height:1.25rem;border-radius:var(--mdui-shape-corner-full)}.handle .elevation{background-color:rgb(var(--mdui-color-primary));box-shadow:var(--mdui-elevation-level1)}:host([invalid]) .handle .elevation{background-color:rgb(var(--mdui-color-error))}:host([disabled]) .handle .elevation{background-color:rgba(var(--mdui-color-on-surface),.38);box-shadow:var(--mdui-elevation-level0)}.handle::before{background-color:rgb(var(--mdui-color-background))}.handle mdui-ripple{border-radius:var(--mdui-shape-corner-full)}.label{position:absolute;left:50%;transform:translateX(-50%) scale(0);transform-origin:center bottom;display:flex;align-items:center;justify-content:center;cursor:default;white-space:nowrap;-webkit-user-select:none;user-select:none;pointer-events:none;transition:transform var(--mdui-motion-duration-short2) var(--mdui-motion-easing-standard);bottom:2.5rem;min-width:1.75rem;height:1.75rem;padding:.375rem .5rem;border-radius:var(--mdui-shape-corner-full);color:rgb(var(--mdui-color-on-primary));font-size:var(--mdui-typescale-label-medium-size);font-weight:var(--mdui-typescale-label-medium-weight);letter-spacing:var(--mdui-typescale-label-medium-tracking);line-height:var(--mdui-typescale-label-medium-line-height);background-color:rgb(var(--mdui-color-primary))}:host([invalid]) .label{color:rgb(var(--mdui-color-on-error));background-color:rgb(var(--mdui-color-error))}.label::after{content:' ';position:absolute;z-index:-1;transform:rotate(45deg);width:.875rem;height:.875rem;bottom:-.125rem;background-color:rgb(var(--mdui-color-primary))}:host([invalid]) .label::after{background-color:rgb(var(--mdui-color-error))}.label-visible{transform:translateX(-50%) scale(1);transition:transform var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard)}.tickmark{position:absolute;top:50%;transform:translate(-50%);width:.125rem;height:.125rem;margin-top:-.0625rem;border-radius:var(--mdui-shape-corner-full);background-color:rgba(var(--mdui-color-on-surface-variant),.38)}:host([invalid]) .tickmark{background-color:rgba(var(--mdui-color-error),.38)}.tickmark.active{background-color:rgba(var(--mdui-color-on-primary),.38)}:host([invalid]) .tickmark.active{background-color:rgba(var(--mdui-color-on-error),.38)}:host([disabled]) .tickmark{background-color:rgba(var(--mdui-color-on-surface),.38)}`;class Wo extends(Ki(Fi(St))){constructor(){super(...arguments),this.min=0,this.max=100,this.step=1,this.tickmarks=!1,this.nolabel=!1,this.disabled=!1,this.name="",this.invalid=!1,this.labelVisible=!1,this.inputRef=Ei(),this.trackActiveRef=Ei(),this.labelFormatter=e=>e.toString()}get validity(){return this.inputRef.value.validity}get validationMessage(){return this.inputRef.value.validationMessage}get rippleDisabled(){return this.disabled}get focusElement(){return this.inputRef.value}get focusDisabled(){return this.disabled}onDisabledChange(){this.invalid=!this.inputRef.value.checkValidity()}checkValidity(){const e=this.inputRef.value.checkValidity();return e||vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}),e}reportValidity(){if(this.invalid=!this.inputRef.value.reportValidity(),this.invalid){vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}).defaultPrevented&&(this.blur(),this.focus())}return!this.invalid}setCustomValidity(e){this.inputRef.value.setCustomValidity(e),this.invalid=!this.inputRef.value.checkValidity()}getCandidateValues(){return Array.from({length:this.max-this.min+1},((e,t)=>t+this.min)).filter((e=>!((e-this.min)%this.step)))}renderLabel(e){return lo(!this.nolabel,(()=>ct`
                                            ${this.labelFormatter(e)}
                                            `))}onChange(){vi(this,"change")}}Wo.styles=[Wt,Go],Re([Pt({type:Number,reflect:!0})],Wo.prototype,"min",void 0),Re([Pt({type:Number,reflect:!0})],Wo.prototype,"max",void 0),Re([Pt({type:Number,reflect:!0})],Wo.prototype,"step",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],Wo.prototype,"tickmarks",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],Wo.prototype,"nolabel",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],Wo.prototype,"disabled",void 0),Re([Pt({reflect:!0})],Wo.prototype,"form",void 0),Re([Pt({reflect:!0})],Wo.prototype,"name",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],Wo.prototype,"invalid",void 0),Re([Mt()],Wo.prototype,"labelVisible",void 0),Re([Pt({attribute:!1})],Wo.prototype,"labelFormatter",void 0),Re([fi("disabled",!0)],Wo.prototype,"onDisabledChange",null),e.RangeSlider=class extends Wo{constructor(){super(...arguments),this.defaultValue=[],this.currentHandle="start",this.rippleStartRef=Ei(),this.rippleEndRef=Ei(),this.handleStartRef=Ei(),this.handleEndRef=Ei(),this.formController=new Li(this),this._value=[],this.getRippleIndex=()=>this.hoverHandle?"start"===this.hoverHandle?0:1:"start"===this.currentHandle?0:1}get value(){return this._value}set value(e){const t=[...this._value];this._value=e,this.requestUpdate("value",t),this.updateComplete.then((()=>{var e;this.updateStyle();const t=this.formController.getForm();t&&(null===(e=Bi.get(t))||void 0===e?void 0:e.has(this))?(this.invalid=!1,Bi.get(t).delete(this)):this.invalid=!this.inputRef.value.checkValidity()}))}get rippleElement(){return[this.rippleStartRef.value,this.rippleEndRef.value]}connectedCallback(){super.connectedCallback(),this.value=[this.min,this.max],this.defaultValue.length||(this.defaultValue=[...this.value]);const e=e=>{const t=P(this),i=parseFloat(t.css("padding-left")),o=parseFloat(t.css("padding-right")),r=(e.offsetX-i)/(this.clientWidth-i-o);return(this.max-this.min)*r+this.min>(this.value[1]-this.value[0])/2+this.value[0]?"end":"start"},t=()=>{this.disabled||(this.labelVisible=!0)},i=()=>{this.disabled||(this.labelVisible=!1)};this.addEventListener("touchstart",t),this.addEventListener("mousedown",t),this.addEventListener("touchend",i),this.addEventListener("mouseup",i),this.addEventListener("pointerdown",(t=>{this.currentHandle=e(t)})),this.addEventListener("pointermove",(t=>{const i=e(t);this.hoverHandle!==i&&(this.endHover(t),this.hoverHandle=i,this.startHover(t))}))}firstUpdated(e){super.firstUpdated(e),this.updateStyle()}render(){return ct``}updateStyle(){const e=e=>(e-this.min)/(this.max-this.min)*100,t=e(this.value[0]),i=e(this.value[1]);this.trackActiveRef.value.style.width=i-t+"%",this.trackActiveRef.value.style.left=`${t}%`,this.handleStartRef.value.style.left=`${t}%`,this.handleEndRef.value.style.left=`${i}%`}onInput(){const e="start"===this.currentHandle,t=parseFloat(this.inputRef.value.value),i=this.value[0],o=this.value[1],r=()=>{vi(this,"input"),this.updateStyle()};e?t<=o?(this.value=[t,o],r()):i!==o&&(this.value=[o,o],r()):t>=i?(this.value=[i,t],r()):i!==o&&(this.value=[i,i],r())}},e.RangeSlider.styles=[Wo.styles],Re([Xi()],e.RangeSlider.prototype,"defaultValue",void 0),Re([Mt()],e.RangeSlider.prototype,"currentHandle",void 0),Re([Pt({type:Array,attribute:!1})],e.RangeSlider.prototype,"value",null),e.RangeSlider=Re([Et("mdui-range-slider")],e.RangeSlider);const Yo=Pe`:host{position:relative;display:inline-flex;flex-grow:1;flex-shrink:0;float:left;height:100%;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:transparent;border:.0625rem solid rgb(var(--mdui-color-outline))}.button{width:100%;padding:0 .75rem}:host([invalid]){color:rgb(var(--mdui-color-error));border-color:rgb(var(--mdui-color-error))}:host([invalid]) .button{background-color:rgb(var(--mdui-color-error-container))}:host([selected]){color:rgb(var(--mdui-color-on-secondary-container));background-color:rgb(var(--mdui-color-secondary-container));--mdui-comp-ripple-state-layer-color:var( + --mdui-color-on-secondary-container + )}:host([disabled]),:host([group-disabled]){cursor:default;pointer-events:none;color:rgba(var(--mdui-color-on-surface),38%);border-color:rgba(var(--mdui-color-on-surface),12%)}:host([loading]){cursor:default;pointer-events:none}:host(:not(.mdui-segmented-button-first)){margin-left:-.0625rem}:host(.mdui-segmented-button-first){border-radius:var(--shape-corner) 0 0 var(--shape-corner)}:host(.mdui-segmented-button-last){border-radius:0 var(--shape-corner) var(--shape-corner) 0}.end-icon,.icon,.selected-icon{display:inline-flex;font-size:1.28571429em}.end-icon .i,.icon .i,.selected-icon .i,::slotted([slot=end-icon]),::slotted([slot=icon]),::slotted([slot=selected-icon]){font-size:inherit}mdui-circular-progress{width:1.125rem;height:1.125rem}:host([disabled]) mdui-circular-progress{opacity:.38}.label{display:inline-flex}.has-icon .label{padding-left:.5rem}.has-end-icon .label{padding-right:.5rem}`;e.SegmentedButton=class extends qi{constructor(){super(...arguments),this.selected=!1,this.invalid=!1,this.groupDisabled=!1,this.key=yi(),this.rippleRef=Ei(),this.hasSlotController=new jt(this,"[default]","icon","end-icon")}get rippleElement(){return this.rippleRef.value}get rippleDisabled(){return this.isDisabled()||this.loading}get focusDisabled(){return this.isDisabled()||this.loading}render(){const e=Mi({button:!0,"has-icon":this.icon||this.selected||this.loading||this.hasSlotController.test("icon"),"has-end-icon":this.endIcon||this.hasSlotController.test("end-icon")});return ct`${this.isButton()?this.renderButton({className:e,part:"button",content:this.renderInner()}):this.isDisabled()||this.loading?ct`${this.renderInner()}`:this.renderAnchor({className:e,part:"button",content:this.renderInner()})}`}isDisabled(){return this.disabled||this.groupDisabled}renderIcon(){return this.loading?this.renderLoading():this.selected?ct`${this.selectedIcon?ct``:ct``}`:ct`${this.icon?ct``:Gt}`}renderLabel(){return this.hasSlotController.test("[default]")?ct``:Gt}renderEndIcon(){return ct`${this.endIcon?ct``:Gt}`}renderInner(){return[this.renderIcon(),this.renderLabel(),this.renderEndIcon()]}},e.SegmentedButton.styles=[qi.styles,Yo],Re([Pt({reflect:!0})],e.SegmentedButton.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"end-icon"})],e.SegmentedButton.prototype,"endIcon",void 0),Re([Pt({reflect:!0,attribute:"selected-icon"})],e.SegmentedButton.prototype,"selectedIcon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.SegmentedButton.prototype,"selected",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.SegmentedButton.prototype,"invalid",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"group-disabled"})],e.SegmentedButton.prototype,"groupDisabled",void 0),e.SegmentedButton=Re([Et("mdui-segmented-button")],e.SegmentedButton);const Xo=Pe`:host{--shape-corner:var(--mdui-shape-corner-full);position:relative;display:inline-flex;height:2.5rem;font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height);color:rgb(var(--mdui-color-on-surface));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([full-width]){display:flex;flex-wrap:nowrap}input,select{position:absolute;width:100%;height:100%;padding:0;opacity:0;pointer-events:none}`;e.SegmentedButtonGroup=class extends St{constructor(){super(...arguments),this.fullWidth=!1,this.disabled=!1,this.required=!1,this.name="",this.value="",this.defaultValue="",this.selectedKeys=[],this.invalid=!1,this.hasSetDefaultValue=!1,this.inputRef=Ei(),this.formController=new Li(this)}get validity(){return this.inputRef.value.validity}get validationMessage(){return this.inputRef.value.validationMessage}get items(){return P(this).find("mdui-segmented-button").get()}get itemsEnabled(){return P(this).find("mdui-segmented-button:not([disabled])").get()}get isSingle(){return"single"===this.selects}get isMultiple(){return"multiple"===this.selects}get isSelectable(){return this.isSingle||this.isMultiple}onSelectsChange(){!this.isSelectable&&this.selectedKeys.length&&(this.selectedKeys=[],this.onSelectedKeysChange(),this.onValueChange()),this.isSingle&&this.selectedKeys.length>1&&(this.selectedKeys=this.selectedKeys.slice(0,1),this.onSelectedKeysChange(),this.onValueChange())}onSelectedKeysChange(){const e=this.itemsEnabled.filter((e=>this.selectedKeys.includes(e.key))).map((e=>e.value));this.value=this.isMultiple?e:e[0]||"",this.hasSetDefaultValue?vi(this,"change"):this.hasSetDefaultValue=!0}async onValueChange(){const e=this.hasUpdated;if(e||await this.updateComplete,!this.isSelectable)return void this.updateSelected(e);const t=(this.isSingle||c(this.value)?[this.value]:this.value).filter((e=>e));if(t.length)if(this.isSingle){const e=this.itemsEnabled.find((e=>e.value===t[0]));this.selectedKeys=e?[e.key]:[]}else this.isMultiple&&(this.selectedKeys=this.itemsEnabled.filter((e=>t.includes(e.value))).map((e=>e.key)));else this.selectedKeys=[];this.updateSelected(e)}onInvalidChange(){this.itemsEnabled.forEach((e=>e.invalid=this.invalid))}onDisabledChange(){this.items.forEach((e=>e.groupDisabled=this.disabled))}connectedCallback(){super.connectedCallback(),this.value=this.isMultiple&&c(this.value)?this.value?[this.value]:[]:this.value,this.defaultValue="multiple"===this.selects?[]:""}checkValidity(){const e=this.inputRef.value.checkValidity();return e||vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}),e}reportValidity(){if(this.invalid=!this.inputRef.value.reportValidity(),this.invalid){vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}).defaultPrevented&&(this.inputRef.value.blur(),this.inputRef.value.focus())}return!this.invalid}setCustomValidity(e){this.inputRef.value.setCustomValidity(e),this.invalid=!this.inputRef.value.checkValidity()}render(){return ct`${lo(this.isSelectable&&this.isSingle,(()=>ct``))}${lo(this.isSelectable&&this.isMultiple,(()=>ct``))}`}async updateSelected(e){var t;if(this.items.forEach((e=>e.selected=this.selectedKeys.includes(e.key))),e){await this.updateComplete;const e=this.formController.getForm();e&&(null===(t=Bi.get(e))||void 0===t?void 0:t.has(this))?(this.invalid=!1,Bi.get(e).delete(this)):this.invalid=!this.inputRef.value.checkValidity()}}selectOne(e){if(this.isMultiple){const t=[...this.selectedKeys];t.includes(e.key)?t.splice(t.indexOf(e.key),1):t.push(e.key),this.selectedKeys=t}this.isSingle&&(this.selectedKeys.includes(e.key)?this.selectedKeys=[]:this.selectedKeys=[e.key]),this.updateSelected(this.hasUpdated)}onClick(e){if(e.button)return;const t=e.target.closest("mdui-segmented-button");t.disabled||this.isSelectable&&t.value&&this.selectOne(t)}onInputKeyDown(e){if(["Enter"," "].includes(e.key)){if(e.preventDefault(),this.isSingle){const t=e.target;t.checked=!t.checked,this.selectOne(this.itemsEnabled[0]),this.itemsEnabled[0].focus()}this.isMultiple&&(this.selectOne(this.itemsEnabled[0]),this.itemsEnabled[0].focus())}}onSlotChange(){const e=this.items;e.forEach(((t,i)=>{t.classList.toggle("mdui-segmented-button-first",0===i),t.classList.toggle("mdui-segmented-button-last",i===e.length-1)}))}},e.SegmentedButtonGroup.styles=[Wt,Xo],Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"full-width"})],e.SegmentedButtonGroup.prototype,"fullWidth",void 0),Re([Pt({reflect:!0})],e.SegmentedButtonGroup.prototype,"selects",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.SegmentedButtonGroup.prototype,"disabled",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.SegmentedButtonGroup.prototype,"required",void 0),Re([Pt({reflect:!0})],e.SegmentedButtonGroup.prototype,"form",void 0),Re([Pt({reflect:!0})],e.SegmentedButtonGroup.prototype,"name",void 0),Re([Pt()],e.SegmentedButtonGroup.prototype,"value",void 0),Re([Xi()],e.SegmentedButtonGroup.prototype,"defaultValue",void 0),Re([Mt()],e.SegmentedButtonGroup.prototype,"selectedKeys",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.SegmentedButtonGroup.prototype,"invalid",void 0),Re([fi("selects")],e.SegmentedButtonGroup.prototype,"onSelectsChange",null),Re([fi("selectedKeys",!0)],e.SegmentedButtonGroup.prototype,"onSelectedKeysChange",null),Re([fi("value")],e.SegmentedButtonGroup.prototype,"onValueChange",null),Re([fi("invalid")],e.SegmentedButtonGroup.prototype,"onInvalidChange",null),Re([fi("disabled")],e.SegmentedButtonGroup.prototype,"onDisabledChange",null),e.SegmentedButtonGroup=Re([Et("mdui-segmented-button-group")],e.SegmentedButtonGroup);const Jo=new WeakMap;let Zo=0;const Qo=new Map,er=new WeakSet,tr=()=>new Promise((e=>requestAnimationFrame(e))),ir=(e,t)=>{const i=e-t;return 0===i?void 0:i},or=(e,t)=>{const i=e/t;return 1===i?void 0:i},rr={left:(e,t)=>{const i=ir(e,t);return{value:i,transform:i&&`translateX(${i}px)`}},top:(e,t)=>{const i=ir(e,t);return{value:i,transform:i&&`translateY(${i}px)`}},width:(e,t)=>{const i=or(e,t);return{value:i,transform:i&&`scaleX(${i})`}},height:(e,t)=>{const i=or(e,t);return{value:i,transform:i&&`scaleY(${i})`}}},nr={duration:333,easing:"ease-in-out"},sr=["left","top","width","height","opacity","color","background"],ar=new WeakMap;const lr=Vt(class extends si{constructor(e){if(super(e),this.t=null,this.i=null,this.o=!0,this.shouldLog=!1,e.type===zt)throw Error("The `animate` directive must be used in attribute position.");this.createFinished()}createFinished(){var e;null===(e=this.resolveFinished)||void 0===e||e.call(this),this.finished=new Promise((e=>{this.h=e}))}async resolveFinished(){var e;null===(e=this.h)||void 0===e||e.call(this),this.h=void 0}render(e){return ht}getController(){return Jo.get(this.l)}isDisabled(){var e;return this.options.disabled||(null===(e=this.getController())||void 0===e?void 0:e.disabled)}update(e,[t]){var i;const o=void 0===this.l;return o&&(this.l=null===(i=e.options)||void 0===i?void 0:i.host,this.l.addController(this),this.element=e.element,ar.set(this.element,this)),this.optionsOrCallback=t,(o||"function"!=typeof t)&&this.u(t),this.render(t)}u(e){var t,i;e=null!=e?e:{};const o=this.getController();void 0!==o&&((e={...o.defaultOptions,...e}).keyframeOptions={...o.defaultOptions.keyframeOptions,...e.keyframeOptions}),null!==(t=(i=e).properties)&&void 0!==t||(i.properties=sr),this.options=e}v(){const e={},t=this.element.getBoundingClientRect(),i=getComputedStyle(this.element);return this.options.properties.forEach((o=>{var r;const n=null!==(r=t[o])&&void 0!==r?r:rr[o]?void 0:i[o],s=Number(n);e[o]=isNaN(s)?n+"":s})),e}p(){let e,t=!0;return this.options.guard&&(e=this.options.guard(),t=((e,t)=>{if(Array.isArray(e)){if(Array.isArray(t)&&t.length===e.length&&e.every(((e,i)=>e===t[i])))return!1}else if(t===e)return!1;return!0})(e,this.m)),this.o=this.l.hasUpdated&&!this.isDisabled()&&!this.isAnimating()&&t&&this.element.isConnected,this.o&&(this.m=Array.isArray(e)?Array.from(e):e),this.o}hostUpdate(){var e;"function"==typeof this.optionsOrCallback&&this.u(this.optionsOrCallback()),this.p()&&(this.g=this.v(),this.t=null!==(e=this.t)&&void 0!==e?e:this.element.parentNode,this.i=this.element.nextSibling)}async hostUpdated(){if(!this.o||!this.element.isConnected||this.options.skipInitial&&!this.isHostRendered)return;let e;this.prepare(),await tr;const t=this._(),i=this.A(this.options.keyframeOptions,t),o=this.v();if(void 0!==this.g){const{from:i,to:r}=this.O(this.g,o,t);this.log("measured",[this.g,o,i,r]),e=this.calculateKeyframes(i,r)}else{const i=Qo.get(this.options.inId);if(i){Qo.delete(this.options.inId);const{from:r,to:n}=this.O(i,o,t);e=this.calculateKeyframes(r,n),e=this.options.in?[{...this.options.in[0],...e[0]},...this.options.in.slice(1),e[1]]:e,Zo++,e.forEach((e=>e.zIndex=Zo))}else this.options.in&&(e=[...this.options.in,{}])}this.animate(e,i)}resetStyles(){var e;void 0!==this.P&&(this.element.setAttribute("style",null!==(e=this.P)&&void 0!==e?e:""),this.P=void 0)}commitStyles(){var e,t;this.P=this.element.getAttribute("style"),null===(e=this.webAnimation)||void 0===e||e.commitStyles(),null===(t=this.webAnimation)||void 0===t||t.cancel()}reconnected(){}async disconnected(){var e;if(!this.o)return;if(void 0!==this.options.id&&Qo.set(this.options.id,this.g),void 0===this.options.out)return;if(this.prepare(),await tr(),null===(e=this.t)||void 0===e?void 0:e.isConnected){const e=this.i&&this.i.parentNode===this.t?this.i:null;if(this.t.insertBefore(this.element,e),this.options.stabilizeOut){const e=this.v();this.log("stabilizing out");const t=this.g.left-e.left,i=this.g.top-e.top;!("static"===getComputedStyle(this.element).position)||0===t&&0===i||(this.element.style.position="relative"),0!==t&&(this.element.style.left=t+"px"),0!==i&&(this.element.style.top=i+"px")}}const t=this.A(this.options.keyframeOptions);await this.animate(this.options.out,t),this.element.remove()}prepare(){this.createFinished()}start(){var e,t;null===(t=(e=this.options).onStart)||void 0===t||t.call(e,this)}didFinish(e){var t,i;e&&(null===(i=(t=this.options).onComplete)||void 0===i||i.call(t,this)),this.g=void 0,this.animatingProperties=void 0,this.frames=void 0,this.resolveFinished()}_(){const e=[];for(let t=this.element.parentNode;t;t=null==t?void 0:t.parentNode){const i=ar.get(t);i&&!i.isDisabled()&&i&&e.push(i)}return e}get isHostRendered(){const e=er.has(this.l);return e||this.l.updateComplete.then((()=>{er.add(this.l)})),e}A(e,t=this._()){const i={...nr};return t.forEach((e=>Object.assign(i,e.options.keyframeOptions))),Object.assign(i,e),i}O(e,t,i){e={...e},t={...t};const o=i.map((e=>e.animatingProperties)).filter((e=>void 0!==e));let r=1,n=1;return void 0!==o&&(o.forEach((e=>{e.width&&(r/=e.width),e.height&&(n/=e.height)})),void 0!==e.left&&void 0!==t.left&&(e.left=r*e.left,t.left=r*t.left),void 0!==e.top&&void 0!==t.top&&(e.top=n*e.top,t.top=n*t.top)),{from:e,to:t}}calculateKeyframes(e,t,i=!1){var o;const r={},n={};let s=!1;const a={};for(const i in t){const l=e[i],c=t[i];if(i in rr){const e=rr[i];if(void 0===l||void 0===c)continue;const t=e(l,c);void 0!==t.transform&&(a[i]=t.value,s=!0,r.transform=`${null!==(o=r.transform)&&void 0!==o?o:""} ${t.transform}`)}else l!==c&&void 0!==l&&void 0!==c&&(s=!0,r[i]=l,n[i]=c)}return r.transformOrigin=n.transformOrigin=i?"center center":"top left",this.animatingProperties=a,s?[r,n]:void 0}async animate(e,t=this.options.keyframeOptions){this.start(),this.frames=e;let i=!1;if(!this.isAnimating()&&!this.isDisabled()&&(this.options.onFrames&&(this.frames=e=this.options.onFrames(this),this.log("modified frames",e)),void 0!==e)){this.log("animate",[e,t]),i=!0,this.webAnimation=this.element.animate(e,t);const o=this.getController();null==o||o.add(this);try{await this.webAnimation.finished}catch(e){}null==o||o.remove(this)}return this.didFinish(i),i}isAnimating(){var e,t;return"running"===(null===(e=this.webAnimation)||void 0===e?void 0:e.playState)||(null===(t=this.webAnimation)||void 0===t?void 0:t.pending)}log(e,t){this.shouldLog&&!this.isDisabled()&&console.log(e,this.options.id,t)}});let cr=class extends St{render(){return Zi('')}};cr.styles=Ji,cr=Re([Et("mdui-icon-cancel--outlined")],cr);let dr=class extends St{render(){return Zi('')}};dr.styles=Ji,dr=Re([Et("mdui-icon-error")],dr);let hr=class extends St{render(){return Zi('')}};hr.styles=Ji,hr=Re([Et("mdui-icon-visibility-off")],hr);let ur=class extends St{render(){return Zi('')}};ur.styles=Ji,ur=Re([Et("mdui-icon-visibility")],ur);const pr=Pe`:host{display:inline-block;width:100%}:host([disabled]){pointer-events:none}:host([type=hidden]){display:none}.container{position:relative;display:flex;align-items:center;transition:box-shadow var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard);padding:0 1rem}.container.has-icon{padding-left:.75rem}.container.has-end-icon,.container.has-error-icon{padding-right:.75rem}:host([variant=filled]) .container{box-shadow:inset 0 -.0625rem 0 0 rgb(var(--mdui-color-on-surface-variant));background-color:rgb(var(--mdui-color-surface-container-highest));border-radius:var(--mdui-shape-corner-extra-small) var(--mdui-shape-corner-extra-small) 0 0}:host([variant=filled][invalid-style]) .container,:host([variant=filled][invalid]) .container{box-shadow:inset 0 -.0625rem 0 0 rgb(var(--mdui-color-error))}:host([variant=filled]:hover) .container{box-shadow:inset 0 -.0625rem 0 0 rgb(var(--mdui-color-on-surface))}:host([variant=filled][invalid-style]:hover) .container,:host([variant=filled][invalid]:hover) .container{box-shadow:inset 0 -.0625rem 0 0 rgb(var(--mdui-color-on-error-container))}:host([variant=filled][focused-style]) .container,:host([variant=filled][focused]) .container{box-shadow:inset 0 -.125rem 0 0 rgb(var(--mdui-color-primary))}:host([variant=filled][focused-style][invalid-style]) .container,:host([variant=filled][focused-style][invalid]) .container,:host([variant=filled][focused][invalid-style]) .container,:host([variant=filled][focused][invalid]) .container{box-shadow:inset 0 -.125rem 0 0 rgb(var(--mdui-color-error))}:host([variant=filled][disabled]) .container{box-shadow:inset 0 -.0625rem 0 0 rgba(var(--mdui-color-on-surface),38%);background-color:rgba(var(--mdui-color-on-surface),4%)}:host([variant=outlined]) .container{box-shadow:inset 0 0 0 .0625rem rgb(var(--mdui-color-outline));border-radius:var(--mdui-shape-corner-extra-small)}:host([variant=outlined][invalid-style]) .container,:host([variant=outlined][invalid]) .container{box-shadow:inset 0 0 0 .0625rem rgb(var(--mdui-color-error))}:host([variant=outlined]:hover) .container{box-shadow:inset 0 0 0 .0625rem rgb(var(--mdui-color-on-surface))}:host([variant=outlined][invalid-style]:hover) .container,:host([variant=outlined][invalid]:hover) .container{box-shadow:inset 0 0 0 .0625rem rgb(var(--mdui-color-on-error-container))}:host([variant=outlined][focused-style]) .container,:host([variant=outlined][focused]) .container{box-shadow:inset 0 0 0 .125rem rgb(var(--mdui-color-primary))}:host([variant=outlined][focused-style][invalid-style]) .container,:host([variant=outlined][focused-style][invalid]) .container,:host([variant=outlined][focused][invalid-style]) .container,:host([variant=outlined][focused][invalid]) .container{box-shadow:inset 0 0 0 .125rem rgb(var(--mdui-color-error))}:host([variant=outlined][disabled]) .container{box-shadow:inset 0 0 0 .125rem rgba(var(--mdui-color-on-surface),12%)}.icon,.prefix,.right-icon,.suffix{display:flex;-webkit-user-select:none;user-select:none;color:rgb(var(--mdui-color-on-surface-variant))}:host([disabled]) .icon,:host([disabled]) .prefix,:host([disabled]) .right-icon,:host([disabled]) .suffix{color:rgba(var(--mdui-color-on-surface),38%)}:host([invalid-style]) .right-icon,:host([invalid-style]) .suffix,:host([invalid]) .right-icon,:host([invalid]) .suffix{color:rgb(var(--mdui-color-error))}:host([invalid-style]:hover) .right-icon,:host([invalid-style]:hover) .suffix,:host([invalid]:hover) .right-icon,:host([invalid]:hover) .suffix{color:rgb(var(--mdui-color-on-error-container))}:host([focused-style][invalid-style]) .right-icon,:host([focused-style][invalid-style]) .suffix,:host([focused-style][invalid]) .right-icon,:host([focused-style][invalid]) .suffix,:host([focused][invalid-style]) .right-icon,:host([focused][invalid-style]) .suffix,:host([focused][invalid]) .right-icon,:host([focused][invalid]) .suffix{color:rgb(var(--mdui-color-error))}.icon,.right-icon{font-size:1.5rem}.icon mdui-button-icon,.right-icon mdui-button-icon,::slotted(mdui-button-icon[slot]){margin-left:-.5rem;margin-right:-.5rem}.icon .i,.right-icon .i,::slotted([slot$=icon]){font-size:inherit}.has-icon .icon{margin-right:1rem}.has-end-icon .end-icon,.right-icon:not(.end-icon){margin-left:1rem}.prefix,.suffix{display:none;font-size:var(--mdui-typescale-body-large-size);font-weight:var(--mdui-typescale-body-large-weight);letter-spacing:var(--mdui-typescale-body-large-tracking);line-height:var(--mdui-typescale-body-large-line-height)}:host([variant=filled][label]) .prefix,:host([variant=filled][label]) .suffix{padding-top:1rem}.has-value .prefix,.has-value .suffix,:host([focused-style]) .prefix,:host([focused-style]) .suffix,:host([focused]) .prefix,:host([focused]) .suffix{display:flex}.prefix{padding-right:.125rem}.suffix{padding-left:.125rem}.input-container{width:100%}.label{position:absolute;pointer-events:none;max-width:calc(100% - 1rem);display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:1;top:1rem;color:rgb(var(--mdui-color-on-surface-variant));font-size:var(--mdui-typescale-body-large-size);font-weight:var(--mdui-typescale-body-large-weight);letter-spacing:var(--mdui-typescale-body-large-tracking);line-height:var(--mdui-typescale-body-large-line-height)}:host([invalid-style]) .label,:host([invalid]) .label{color:rgb(var(--mdui-color-error))}:host([variant=outlined]) .label{padding:0 .25rem;margin:0 -.25rem}:host([variant=outlined]:hover) .label{color:rgb(var(--mdui-color-on-surface))}:host([variant=filled][invalid-style]:hover) .label,:host([variant=filled][invalid]:hover) .label,:host([variant=outlined][invalid-style]:hover) .label,:host([variant=outlined][invalid]:hover) .label{color:rgb(var(--mdui-color-on-error-container))}:host([variant=filled][focused-style]) .label,:host([variant=filled][focused]) .label,:host([variant=outlined][focused-style]) .label,:host([variant=outlined][focused]) .label{color:rgb(var(--mdui-color-primary))}:host([variant=filled]) .has-value .label,:host([variant=filled][focused-style]) .label,:host([variant=filled][focused]) .label,:host([variant=filled][type=date]) .label,:host([variant=filled][type=datetime-local]) .label,:host([variant=filled][type=month]) .label,:host([variant=filled][type=time]) .label,:host([variant=filled][type=week]) .label{font-size:var(--mdui-typescale-body-small-size);font-weight:var(--mdui-typescale-body-small-weight);letter-spacing:var(--mdui-typescale-body-small-tracking);line-height:var(--mdui-typescale-body-small-line-height);top:.25rem}:host([variant=outlined]) .has-value .label,:host([variant=outlined][focused-style]) .label,:host([variant=outlined][focused]) .label,:host([variant=outlined][type=date]) .label,:host([variant=outlined][type=datetime-local]) .label,:host([variant=outlined][type=month]) .label,:host([variant=outlined][type=time]) .label,:host([variant=outlined][type=week]) .label{font-size:var(--mdui-typescale-body-small-size);font-weight:var(--mdui-typescale-body-small-weight);letter-spacing:var(--mdui-typescale-body-small-tracking);line-height:var(--mdui-typescale-body-small-line-height);top:-.5rem;left:.75rem;background-color:rgb(var(--mdui-color-background))}:host([variant=filled][focused-style][invalid-style]) .label,:host([variant=filled][focused-style][invalid]) .label,:host([variant=filled][focused][invalid-style]) .label,:host([variant=filled][focused][invalid]) .label,:host([variant=outlined][focused-style][invalid-style]) .label,:host([variant=outlined][focused-style][invalid]) .label,:host([variant=outlined][focused][invalid-style]) .label,:host([variant=outlined][focused][invalid]) .label{color:rgb(var(--mdui-color-error))}:host([variant=filled][disabled]) .label,:host([variant=outlined][disabled]) .label{color:rgba(var(--mdui-color-on-surface),38%)}.input{display:flex;flex-wrap:wrap;width:100%;border:none;outline:0;background:0 0;-webkit-appearance:none;appearance:none;resize:none;cursor:inherit;font-family:inherit;padding:1rem 0;font-size:var(--mdui-typescale-body-large-size);font-weight:var(--mdui-typescale-body-large-weight);letter-spacing:var(--mdui-typescale-body-large-tracking);line-height:var(--mdui-typescale-body-large-line-height);color:rgb(var(--mdui-color-on-surface));caret-color:rgb(var(--mdui-color-primary))}.input.hide-input{opacity:0;height:0;padding:0!important;overflow:hidden}.input::placeholder{color:rgb(var(--mdui-color-on-surface-variant))}:host([invalid-style]) .input,:host([invalid]) .input{caret-color:rgb(var(--mdui-color-error))}:host([disabled]) .input{color:rgba(var(--mdui-color-on-surface),38%)}:host([end-aligned]) .input{text-align:right}:host([variant=filled]) .label+.input{padding:1.5rem 0 .5rem 0}.supporting{display:flex;justify-content:space-between;padding:.25rem 1rem;color:rgb(var(--mdui-color-on-surface-variant))}:host([invalid-style]) .supporting,:host([invalid]) .supporting{color:rgb(var(--mdui-color-error))}.helper{display:block;opacity:1;transition:opacity var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);font-size:var(--mdui-typescale-body-small-size);font-weight:var(--mdui-typescale-body-small-weight);letter-spacing:var(--mdui-typescale-body-small-tracking);line-height:var(--mdui-typescale-body-small-line-height)}:host([disabled]) .helper{color:rgba(var(--mdui-color-on-surface),38%)}:host([helper-on-focus]) .helper{opacity:0}:host([helper-on-focus][focused-style]) .helper,:host([helper-on-focus][focused]) .helper{opacity:1}.error{font-size:var(--mdui-typescale-body-small-size);font-weight:var(--mdui-typescale-body-small-weight);letter-spacing:var(--mdui-typescale-body-small-tracking);line-height:var(--mdui-typescale-body-small-line-height)}.counter{flex-wrap:nowrap;padding-left:1rem;font-size:var(--mdui-typescale-body-small-size);font-weight:var(--mdui-typescale-body-small-weight);letter-spacing:var(--mdui-typescale-body-small-tracking);line-height:var(--mdui-typescale-body-small-line-height)}::-ms-reveal{display:none}.is-firefox .input[type=date],.is-firefox .input[type=datetime-local],.is-firefox .input[type=time]{-webkit-clip-path:inset(0 2em 0 0);clip-path:inset(0 2em 0 0)}.input[type=number]::-webkit-inner-spin-button,.input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;display:none}.input[type=number]{-moz-appearance:textfield}.input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none}`;e.TextField=class extends(Fi(St)){constructor(){super(...arguments),this.variant="filled",this.type="text",this.name="",this.value="",this.defaultValue="",this.helperOnFocus=!1,this.clearable=!1,this.endAligned=!1,this.readonly=!1,this.disabled=!1,this.required=!1,this.autosize=!1,this.counter=!1,this.togglePassword=!1,this.spellcheck=!1,this.invalid=!1,this.invalidStyle=!1,this.focusedStyle=!1,this.isPasswordVisible=!1,this.hasValue=!1,this.error="",this.inputRef=Ei(),this.formController=new Li(this),this.hasSlotController=new jt(this,"icon","end-icon","helper","input"),this.readonlyButClearable=!1}get validity(){return this.inputRef.value.validity}get validationMessage(){return this.inputRef.value.validationMessage}get valueAsNumber(){var e,t;return null!==(t=null===(e=this.inputRef.value)||void 0===e?void 0:e.valueAsNumber)&&void 0!==t?t:parseFloat(this.value)}set valueAsNumber(e){const t=document.createElement("input");t.type="number",t.valueAsNumber=e,this.value=t.value}get focusElement(){return this.inputRef.value}get focusDisabled(){return this.disabled}get isFocusedStyle(){return this.focused||this.focusedStyle}get isTextarea(){return this.rows&&this.rows>1||this.autosize}onDisabledChange(){this.inputRef.value.disabled=this.disabled,this.invalid=!this.inputRef.value.checkValidity()}async onValueChange(){var e;if(this.hasValue=!!this.value,this.hasUpdated){await this.updateComplete;const t=this.formController.getForm();t&&(null===(e=Bi.get(t))||void 0===e?void 0:e.has(this))?(this.invalid=!1,Bi.get(t).delete(this)):this.invalid=!this.inputRef.value.checkValidity()}}onRowsChange(){this.setTextareaHeight()}async onMaxRowsChange(){if(!this.autosize)return;const e=()=>{var e;const t=P(this.inputRef.value);t.css("max-height",parseFloat(t.css("line-height"))*(null!==(e=this.maxRows)&&void 0!==e?e:1)+parseFloat(t.css("padding-top"))+parseFloat(t.css("padding-bottom")))};this.hasUpdated||await this.updateComplete,e()}async onMinRowsChange(){if(!this.autosize)return;const e=()=>{var e;const t=P(this.inputRef.value);t.css("min-height",parseFloat(t.css("line-height"))*(null!==(e=this.minRows)&&void 0!==e?e:1)+parseFloat(t.css("padding-top"))+parseFloat(t.css("padding-bottom")))};this.hasUpdated||await this.updateComplete,e()}connectedCallback(){super.connectedCallback(),this.updateComplete.then((()=>{this.setTextareaHeight(),this.observeResize=Ci(this.inputRef.value,(()=>this.setTextareaHeight()))}))}disconnectedCallback(){var e;super.disconnectedCallback(),null===(e=this.observeResize)||void 0===e||e.unobserve()}select(){this.inputRef.value.select()}setSelectionRange(e,t,i="none"){this.inputRef.value.setSelectionRange(e,t,i)}setRangeText(e,t,i,o="preserve"){this.inputRef.value.setRangeText(e,t,i,o),this.value!==this.inputRef.value.value&&(this.value=this.inputRef.value.value,this.setTextareaHeight(),vi(this,"input"),vi(this,"change"))}checkValidity(){const e=this.inputRef.value.checkValidity();return e||vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}),e}reportValidity(){return this.invalid=!this.inputRef.value.reportValidity(),this.invalid&&(vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}),this.focus()),!this.invalid}setCustomValidity(e){this.inputRef.value.setCustomValidity(e),this.invalid=!this.inputRef.value.checkValidity()}render(){const e=!!this.icon||this.hasSlotController.test("icon"),t=!!this.endIcon||this.hasSlotController.test("end-icon"),i=this.invalid||this.invalidStyle,o=!!this.helper||this.hasSlotController.test("helper"),r=i&&!(!this.error&&!this.inputRef.value.validationMessage),n=this.counter&&!!this.maxlength,s=this.hasSlotController.test("input"),a=Ni({container:!0,"has-value":this.hasValue,"has-icon":e,"has-end-icon":t,"has-error-icon":i,"is-firefox":navigator.userAgent.includes("Firefox")});return ct`
                                            ${this.renderPrefix()}
                                            ${this.renderLabel()} ${this.isTextarea?this.renderTextArea(s):this.renderInput(s)} ${lo(s,(()=>ct``))}
                                            ${this.renderClearButton()}${this.renderTogglePasswordButton()} ${this.renderSuffix(i)}
                                            ${lo(r||o||n,(()=>ct`
                                            ${this.renderHelper(r,o)} ${this.renderCounter(n)}
                                            `))}`}onChange(){this.value=this.inputRef.value.value,this.isTextarea&&this.setTextareaHeight(),vi(this,"change")}onClear(e){this.value="",vi(this,"clear"),vi(this,"input"),vi(this,"change"),this.focus(),e.stopPropagation()}onInput(){this.value=this.inputRef.value.value,this.isTextarea&&this.setTextareaHeight(),vi(this,"input")}onInvalid(e){e.preventDefault()}onKeyDown(e){const t=e.metaKey||e.ctrlKey||e.shiftKey||e.altKey;"Enter"!==e.key||t||setTimeout((()=>{e.defaultPrevented||this.formController.submit()}))}onTextAreaKeyUp(){if(this.pattern){const e=new RegExp(this.pattern),t=this.value&&!this.value.match(e);this.setCustomValidity(t?"请与请求的格式匹配。":"")}}onTogglePassword(){this.isPasswordVisible=!this.isPasswordVisible}setTextareaHeight(){this.autosize?(this.inputRef.value.style.height="auto",this.inputRef.value.style.height=`${this.inputRef.value.scrollHeight}px`):this.inputRef.value.style.height=void 0}renderLabel(){return this.label?ct``:Gt}renderPrefix(){return ct`${this.icon?ct``:Gt}${this.prefix}`}renderSuffix(e){return ct`${this.suffix}${e?ct`${this.errorIcon?ct``:ct``}`:ct`${this.endIcon?ct``:Gt}`}`}renderClearButton(){return lo(this.clearable&&!this.disabled&&(!this.readonly||this.readonlyButClearable)&&("number"==typeof this.value||this.value.length>0),(()=>ct`${this.clearIcon?ct``:ct``}`))}renderTogglePasswordButton(){return lo("password"===this.type&&this.togglePassword&&!this.disabled,(()=>ct`${this.isPasswordVisible?ct`${this.showPasswordIcon?ct``:ct``}`:ct`${this.hidePasswordIcon?ct``:ct``}`}`))}renderInput(e){return ct``}renderTextArea(e){var t;return ct``}renderHelper(e,t){return e?ct`
                                            ${this.error||this.inputRef.value.validationMessage}
                                            `:t?ct`${this.helper}`:ct``}renderCounter(e){return e?ct`
                                            ${this.value.length}/${this.maxlength}
                                            `:Gt}},e.TextField.styles=[Wt,pr],Re([Pt({reflect:!0})],e.TextField.prototype,"variant",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"type",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"name",void 0),Re([Pt()],e.TextField.prototype,"value",void 0),Re([Xi()],e.TextField.prototype,"defaultValue",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"label",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"placeholder",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"helper",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"helper-on-focus"})],e.TextField.prototype,"helperOnFocus",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TextField.prototype,"clearable",void 0),Re([Pt({reflect:!0,attribute:"clear-icon"})],e.TextField.prototype,"clearIcon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"end-aligned"})],e.TextField.prototype,"endAligned",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"prefix",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"suffix",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"end-icon"})],e.TextField.prototype,"endIcon",void 0),Re([Pt({reflect:!0,attribute:"error-icon"})],e.TextField.prototype,"errorIcon",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"form",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TextField.prototype,"readonly",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TextField.prototype,"disabled",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TextField.prototype,"required",void 0),Re([Pt({type:Number,reflect:!0})],e.TextField.prototype,"rows",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TextField.prototype,"autosize",void 0),Re([Pt({type:Number,reflect:!0,attribute:"min-rows"})],e.TextField.prototype,"minRows",void 0),Re([Pt({type:Number,reflect:!0,attribute:"max-rows"})],e.TextField.prototype,"maxRows",void 0),Re([Pt({type:Number,reflect:!0})],e.TextField.prototype,"minlength",void 0),Re([Pt({type:Number,reflect:!0})],e.TextField.prototype,"maxlength",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TextField.prototype,"counter",void 0),Re([Pt({type:Number,reflect:!0})],e.TextField.prototype,"min",void 0),Re([Pt({type:Number,reflect:!0})],e.TextField.prototype,"max",void 0),Re([Pt({type:Number,reflect:!0})],e.TextField.prototype,"step",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"pattern",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"toggle-password"})],e.TextField.prototype,"togglePassword",void 0),Re([Pt({reflect:!0,attribute:"show-password-icon"})],e.TextField.prototype,"showPasswordIcon",void 0),Re([Pt({reflect:!0,attribute:"hide-password-icon"})],e.TextField.prototype,"hidePasswordIcon",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"autocapitalize",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"autocorrect",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"autocomplete",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"enterkeyhint",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TextField.prototype,"spellcheck",void 0),Re([Pt({reflect:!0})],e.TextField.prototype,"inputmode",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TextField.prototype,"invalid",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"invalid-style"})],e.TextField.prototype,"invalidStyle",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"focused-style"})],e.TextField.prototype,"focusedStyle",void 0),Re([Mt()],e.TextField.prototype,"isPasswordVisible",void 0),Re([Mt()],e.TextField.prototype,"hasValue",void 0),Re([Mt()],e.TextField.prototype,"error",void 0),Re([fi("disabled",!0)],e.TextField.prototype,"onDisabledChange",null),Re([fi("value")],e.TextField.prototype,"onValueChange",null),Re([fi("rows",!0)],e.TextField.prototype,"onRowsChange",null),Re([fi("maxRows")],e.TextField.prototype,"onMaxRowsChange",null),Re([fi("minRows")],e.TextField.prototype,"onMinRowsChange",null),e.TextField=Re([Et("mdui-text-field")],e.TextField);const mr=Pe`:host{display:inline-block;width:100%}.hidden-input{display:none}.text-field{cursor:pointer}.chips{display:flex;flex-wrap:wrap;margin:-.5rem -.25rem;min-height:2.5rem}:host([variant=filled][label]) .chips{margin:0 -.25rem -1rem -.25rem}.chip{margin:.25rem}mdui-menu{max-width:none}`;e.Select=class extends(Fi(St)){constructor(){super(...arguments),this.variant="filled",this.multiple=!1,this.name="",this.value="",this.defaultValue="",this.clearable=!1,this.placement="auto",this.endAligned=!1,this.readonly=!1,this.disabled=!1,this.required=!1,this.invalid=!1,this.menuRef=Ei(),this.textFieldRef=Ei(),this.hiddenInputRef=Ei(),this.formController=new Li(this),this.hasSlotController=new jt(this,"icon","end-icon","error-icon","prefix","suffix","clear-button","clear-icon","helper")}get validity(){return this.hiddenInputRef.value.validity}get validationMessage(){return this.hiddenInputRef.value.validationMessage}get focusElement(){return this.textFieldRef.value}get focusDisabled(){return this.disabled}connectedCallback(){super.connectedCallback(),this.value=this.multiple&&c(this.value)?this.value?[this.value]:[]:this.value,this.defaultValue=this.multiple?[]:"",this.updateComplete.then((()=>{this.observeResize=Ci(this.textFieldRef.value,(()=>this.resizeMenu()))}))}disconnectedCallback(){var e;super.disconnectedCallback(),null===(e=this.observeResize)||void 0===e||e.unobserve()}checkValidity(){const e=this.hiddenInputRef.value.checkValidity();return e||vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}),e}reportValidity(){return this.invalid=!this.hiddenInputRef.value.reportValidity(),this.invalid&&(vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}),this.focus()),!this.invalid}setCustomValidity(e){this.hiddenInputRef.value.setCustomValidity(e),this.invalid=!this.hiddenInputRef.value.checkValidity()}firstUpdated(e){super.firstUpdated(e),this.requestUpdate()}render(){var e;const t=this.multiple?!!this.value.length:!!this.value;return ct`${this.multiple?ct``:ct``}${jo(["icon","end-icon","error-icon","prefix","suffix","clear-button","clear-icon","helper"],(e=>this.hasSlotController.test(e)?ct``:ht))} ${lo(this.multiple&&this.value.length,(()=>ct`
                                            ${jo(this.value,(e=>ct`${this.getMenuItemLabelByValue(e)}`))}
                                            `))}
                                            `}getMenuItemLabelByValue(e){var t;return this.menuItems.length&&(null===(t=this.menuItems.find((t=>t.value===e)))||void 0===t?void 0:t.textContent)||e}resizeMenu(){this.menuRef.value.style.width=`${this.textFieldRef.value.clientWidth}px`}async onDropdownOpen(){this.textFieldRef.value.focusedStyle=!0}onDropdownClose(){this.textFieldRef.value.focusedStyle=!1}async onValueChange(e){var t,i;const o=e.target;this.value=this.multiple?o.value.map((e=>null!=e?e:"")):null!==(t=o.value)&&void 0!==t?t:"",await this.updateComplete;const r=this.formController.getForm();r&&(null===(i=Bi.get(r))||void 0===i?void 0:i.has(this))?(this.invalid=!1,Bi.get(r).delete(this)):this.invalid=!this.hiddenInputRef.value.checkValidity()}onDeleteOneValue(e){const t=[...this.value];t.includes(e)&&t.splice(t.indexOf(e),1),this.value=t}onClear(){this.value=this.multiple?[]:""}onTextFieldKeyDown(e){"Enter"===e.key&&(e.preventDefault(),this.textFieldRef.value.click())}},e.Select.enabledWarnings=["migration"],e.Select.styles=[Wt,mr],Re([Pt({reflect:!0})],e.Select.prototype,"variant",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Select.prototype,"multiple",void 0),Re([Pt({reflect:!0})],e.Select.prototype,"name",void 0),Re([Pt()],e.Select.prototype,"value",void 0),Re([Xi()],e.Select.prototype,"defaultValue",void 0),Re([Pt({reflect:!0})],e.Select.prototype,"label",void 0),Re([Pt({reflect:!0})],e.Select.prototype,"placeholder",void 0),Re([Pt({reflect:!0})],e.Select.prototype,"helper",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Select.prototype,"clearable",void 0),Re([Pt({reflect:!0,attribute:"clear-icon"})],e.Select.prototype,"clearIcon",void 0),Re([Pt({reflect:!0})],e.Select.prototype,"placement",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"end-aligned"})],e.Select.prototype,"endAligned",void 0),Re([Pt({reflect:!0})],e.Select.prototype,"prefix",void 0),Re([Pt({reflect:!0})],e.Select.prototype,"suffix",void 0),Re([Pt({reflect:!0})],e.Select.prototype,"icon",void 0),Re([Pt({reflect:!0,attribute:"end-icon"})],e.Select.prototype,"endIcon",void 0),Re([Pt({reflect:!0,attribute:"error-icon"})],e.Select.prototype,"errorIcon",void 0),Re([Pt({reflect:!0})],e.Select.prototype,"form",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Select.prototype,"readonly",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Select.prototype,"disabled",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Select.prototype,"required",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Select.prototype,"invalid",void 0),Re([Lt({flatten:!0,selector:"mdui-menu-item"})],e.Select.prototype,"menuItems",void 0),e.Select=Re([Et("mdui-select")],e.Select);const vr=Pe`.track-active{left:-.125rem;border-radius:var(--mdui-shape-corner-full) 0 0 var(--mdui-shape-corner-full)}`;e.Slider=class extends Wo{constructor(){super(...arguments),this.value=0,this.defaultValue=0,this.rippleRef=Ei(),this.handleRef=Ei(),this.formController=new Li(this)}get rippleElement(){return this.rippleRef.value}onValueChange(){var e;const t=this.formController.getForm();t&&(null===(e=Bi.get(t))||void 0===e?void 0:e.has(this))?(this.invalid=!1,Bi.get(t).delete(this)):this.invalid=!this.inputRef.value.checkValidity(),this.inputRef.value.value=this.value.toString(),this.value=parseFloat(this.inputRef.value.value),this.updateStyle()}connectedCallback(){super.connectedCallback(),this.valuethis.max&&(this.value=this.max);const e=()=>{this.disabled||(this.labelVisible=!0)},t=()=>{this.disabled||(this.labelVisible=!1)};this.addEventListener("touchstart",e),this.addEventListener("mousedown",e),this.addEventListener("touchend",t),this.addEventListener("mouseup",t)}firstUpdated(e){super.firstUpdated(e),this.updateStyle()}render(){return ct``}updateStyle(){const e=(this.value-this.min)/(this.max-this.min)*100;this.trackActiveRef.value.style.width=`${e}%`,this.handleRef.value.style.left=`${e}%`}onInput(){this.value=parseFloat(this.inputRef.value.value),vi(this,"input"),this.updateStyle()}},e.Slider.styles=[Wo.styles,vr],Re([Pt({type:Number})],e.Slider.prototype,"value",void 0),Re([Xi()],e.Slider.prototype,"defaultValue",void 0),Re([fi("value",!0)],e.Slider.prototype,"onValueChange",null),e.Slider=Re([Et("mdui-slider")],e.Slider);const fr=Pe`:host{--shape-corner:var(--mdui-shape-corner-extra-small);--z-index:2400;position:fixed;z-index:var(--z-index);display:none;align-items:center;flex-wrap:wrap;border-radius:var(--shape-corner);min-width:20rem;max-width:36rem;padding:.25rem 0;box-shadow:var(--mdui-elevation-level3);background-color:rgb(var(--mdui-color-inverse-surface));color:rgb(var(--mdui-color-inverse-on-surface));font-size:var(--mdui-typescale-body-medium-size);font-weight:var(--mdui-typescale-body-medium-weight);letter-spacing:var(--mdui-typescale-body-medium-tracking);line-height:var(--mdui-typescale-body-medium-line-height)}:host([placement^=top]){transform-origin:top;top:1rem}:host([placement^=bottom]){transform-origin:bottom;bottom:1rem}:host([placement=bottom-start]),:host([placement=top-start]){left:1rem}:host([placement=bottom-end]),:host([placement=top-end]){right:1rem}.message{display:block;margin:.625rem 1rem}:host([message-line='1']) .message{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host([message-line='2']) .message{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:2}.action-group{display:flex;align-items:center;margin-left:auto;padding-right:.5rem}.action,.close-button{display:inline-flex;align-items:center;justify-content:center}.action{color:rgb(var(--mdui-color-inverse-primary));font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking)}.action mdui-button,::slotted(mdui-button[slot=action][variant=outlined]),::slotted(mdui-button[slot=action][variant=text]){color:inherit;font-size:inherit;font-weight:inherit;letter-spacing:inherit;--mdui-comp-ripple-state-layer-color:var(--mdui-color-inverse-primary)}.action mdui-button::part(button){padding:0 .5rem}.close-button{margin:0 -.25rem 0 .25rem;font-size:1.5rem;color:rgb(var(--mdui-color-inverse-on-surface))}.close-button mdui-button-icon,::slotted(mdui-button-icon[slot=close-button][variant=outlined]),::slotted(mdui-button-icon[slot=close-button][variant=standard]){font-size:inherit;color:inherit;--mdui-comp-ripple-state-layer-color:var(--mdui-color-inverse-on-surface)}.close-button .i,::slotted([slot=close-icon]){font-size:inherit}`;e.Snackbar=class extends St{constructor(){super(),this.open=!1,this.placement="bottom",this.actionLoading=!1,this.closeable=!1,this.autoCloseDelay=5e3,this.closeOnOutsideClick=!1,this.onDocumentClick=this.onDocumentClick.bind(this)}async onOpenChange(){const e=zo().down("sm"),t=["top","bottom"].includes(this.placement),i=vo(this,"linear"),o=vo(this,"emphasized-decelerate"),r=Array.from(this.renderRoot.querySelectorAll(".message, .action-group")),n=e?{left:"1rem",right:"1rem",minWidth:0}:t?{left:"50%"}:{};if(this.open){const s=this.hasUpdated;if(s||await this.updateComplete,s){if(vi(this,"open",{cancelable:!0}).defaultPrevented)return}window.clearTimeout(this.closeTimeout),this.autoCloseDelay&&(this.closeTimeout=window.setTimeout((()=>{this.open=!1}),this.autoCloseDelay)),this.style.display="flex",await Promise.all([ho(this),...r.map((e=>ho(e)))]);const a=fo(this,"medium4"),l=i=>{const o=`scaleY(${"start"===i?0:1})`;return e?{transform:o}:{transform:[o,t?"translateX(-50%)":""].filter((e=>e)).join(" ")}};return await Promise.all([co(this,[{...l("start"),...n},{...l("end"),...n}],{duration:s?a:0,easing:o,fill:"forwards"}),co(this,[{opacity:0},{opacity:1,offset:.5},{opacity:1}],{duration:s?a:0,easing:i,fill:"forwards"}),...r.map((e=>co(e,[{opacity:0},{opacity:0,offset:.2},{opacity:1,offset:.8},{opacity:1}],{duration:s?a:0,easing:i})))]),void(s&&vi(this,"opened"))}if(!this.open&&this.hasUpdated){if(vi(this,"close",{cancelable:!0}).defaultPrevented)return;window.clearTimeout(this.closeTimeout),await Promise.all([ho(this),...r.map((e=>ho(e)))]);const o=fo(this,"short4"),s=i=>{const o={opacity:"start"===i?1:0};return!e&&t&&Object.assign(o,{transform:"translateX(-50%)"}),o};return await Promise.all([co(this,[{...s("start"),...n},{...s("end"),...n}],{duration:o,easing:i,fill:"forwards"}),...r.map((e=>co(e,[{opacity:1},{opacity:0,offset:.75},{opacity:0}],{duration:o,easing:i})))]),this.style.display="none",void vi(this,"closed")}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this.onDocumentClick)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("pointerdown",this.onDocumentClick)}render(){return ct`
                                            ${this.action?ct`${this.action}`:Gt}${lo(this.closeable,(()=>ct`${this.closeIcon?ct``:ct``}`))}
                                            `}onDocumentClick(e){if(!this.open||!this.closeOnOutsideClick)return;const t=e.target;this.contains(t)||this===t||(this.open=!1)}onActionClick(e){e.stopPropagation(),vi(this,"action-click")}onCloseClick(){this.open=!1}},e.Snackbar.styles=[Wt,fr],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Snackbar.prototype,"open",void 0),Re([Pt({reflect:!0})],e.Snackbar.prototype,"placement",void 0),Re([Pt({reflect:!0,attribute:"action"})],e.Snackbar.prototype,"action",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"action-loading"})],e.Snackbar.prototype,"actionLoading",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Snackbar.prototype,"closeable",void 0),Re([Pt({reflect:!0,attribute:"close-icon"})],e.Snackbar.prototype,"closeIcon",void 0),Re([Pt({type:Number,reflect:!0,attribute:"message-line"})],e.Snackbar.prototype,"messageLine",void 0),Re([Pt({type:Number,reflect:!0,attribute:"auto-close-delay"})],e.Snackbar.prototype,"autoCloseDelay",void 0),Re([Pt({type:Boolean,reflect:!0,attribute:"close-on-outside-click",converter:mi})],e.Snackbar.prototype,"closeOnOutsideClick",void 0),Re([fi("open")],e.Snackbar.prototype,"onOpenChange",null),e.Snackbar=Re([Et("mdui-snackbar")],e.Snackbar);const gr=Pe`:host{--shape-corner:var(--mdui-shape-corner-full);--shape-corner-thumb:var(--mdui-shape-corner-full);position:relative;display:inline-block;cursor:pointer;-webkit-tap-highlight-color:transparent;height:2.5rem}:host([disabled]){cursor:default;pointer-events:none}label{display:inline-flex;align-items:center;width:100%;height:100%;white-space:nowrap;cursor:inherit;-webkit-user-select:none;user-select:none;touch-action:manipulation;zoom:1;-webkit-user-drag:none}.track{position:relative;display:flex;align-items:center;border-radius:var(--shape-corner);transition-property:background-color,border-width;transition-duration:var(--mdui-motion-duration-short4);transition-timing-function:var(--mdui-motion-easing-standard);height:2rem;width:3.25rem;border:.125rem solid rgb(var(--mdui-color-outline));background-color:rgb(var(--mdui-color-surface-container-highest))}:host([checked]) .track{background-color:rgb(var(--mdui-color-primary));border-width:0}:host([invalid]) .track{background-color:rgb(var(--mdui-color-error-container));border-color:rgb(var(--mdui-color-error))}:host([disabled]) .track{background-color:rgba(var(--mdui-color-surface-container-highest),.12);border-color:rgba(var(--mdui-color-on-surface),.12)}:host([disabled][checked]) .track{background-color:rgba(var(--mdui-color-on-surface),.12)}input{position:absolute;padding:0;opacity:0;pointer-events:none;width:1.25rem;height:1.25rem;margin:0 0 0 .625rem}mdui-ripple{border-radius:50%;transition-property:left,top;transition-duration:var(--mdui-motion-duration-short4);transition-timing-function:var(--mdui-motion-easing-standard);width:2.5rem;height:2.5rem}.thumb{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:var(--shape-corner-thumb);transition-property:width,height,left,background-color;transition-duration:var(--mdui-motion-duration-short4);transition-timing-function:var(--mdui-motion-easing-standard);height:1rem;width:1rem;left:.375rem;background-color:rgb(var(--mdui-color-outline));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}.thumb mdui-ripple{left:-.75rem;top:-.75rem}.has-unchecked-icon .thumb{height:1.5rem;width:1.5rem;left:.125rem}.has-unchecked-icon .thumb mdui-ripple{left:-.5rem;top:-.5rem}:host([focus-visible]) .thumb,:host([hover]) .thumb,:host([pressed]) .thumb{background-color:rgb(var(--mdui-color-on-surface-variant))}:host([checked]) .thumb{height:1.5rem;width:1.5rem;left:1.5rem;background-color:rgb(var(--mdui-color-on-primary));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([checked]) .thumb mdui-ripple{left:-.5rem;top:-.5rem}:host([pressed]) .thumb{height:1.75rem;width:1.75rem;left:0}:host([pressed]) .thumb mdui-ripple{left:-.375rem;top:-.375rem}:host([pressed][checked]) .thumb{left:1.375rem}:host([focus-visible][checked]) .thumb,:host([hover][checked]) .thumb,:host([pressed][checked]) .thumb{background-color:rgb(var(--mdui-color-primary-container))}:host([invalid]) .thumb{background-color:rgb(var(--mdui-color-error));--mdui-comp-ripple-state-layer-color:var(--mdui-color-error)}:host([focus-visible][invalid]) .thumb,:host([hover][invalid]) .thumb,:host([pressed][invalid]) .thumb{background-color:rgb(var(--mdui-color-error))}:host([disabled]) .thumb{background-color:rgba(var(--mdui-color-on-surface),.38)}:host([disabled][checked]) .thumb{background-color:rgb(var(--mdui-color-surface))}.checked-icon,.unchecked-icon{display:flex;position:absolute;transition-property:opacity,transform;font-size:1rem}.unchecked-icon{opacity:1;transform:scale(1);transition-delay:var(--mdui-motion-duration-short1);transition-duration:var(--mdui-motion-duration-short3);transition-timing-function:var(--mdui-motion-easing-linear);color:rgb(var(--mdui-color-surface-container-highest))}:host([checked]) .unchecked-icon{opacity:0;transform:scale(.92);transition-delay:0s;transition-duration:var(--mdui-motion-duration-short1)}:host([disabled]) .unchecked-icon{color:rgba(var(--mdui-color-surface-container-highest),.38)}.checked-icon{opacity:0;transform:scale(.92);transition-delay:0s;transition-duration:var(--mdui-motion-duration-short1);transition-timing-function:var(--mdui-motion-easing-linear);color:rgb(var(--mdui-color-on-primary-container))}:host([checked]) .checked-icon{opacity:1;transform:scale(1);transition-delay:var(--mdui-motion-duration-short1);transition-duration:var(--mdui-motion-duration-short3)}:host([invalid]) .checked-icon{color:rgb(var(--mdui-color-error-container))}:host([disabled]) .checked-icon{color:rgba(var(--mdui-color-on-surface),.38)}.checked-icon .i,.unchecked-icon .i,::slotted([slot=checked-icon]),::slotted([slot=unchecked-icon]){font-size:inherit;color:inherit}`;e.Switch=class extends(Ki(Fi(St))){constructor(){super(...arguments),this.disabled=!1,this.checked=!1,this.defaultChecked=!1,this.required=!1,this.name="",this.value="on",this.invalid=!1,this.rippleRef=Ei(),this.inputRef=Ei(),this.formController=new Li(this,{value:e=>e.checked?e.value:void 0,defaultValue:e=>e.defaultChecked,setValue:(e,t)=>e.checked=t}),this.hasSlotController=new jt(this,"unchecked-icon")}get validity(){return this.inputRef.value.validity}get validationMessage(){return this.inputRef.value.validationMessage}get rippleElement(){return this.rippleRef.value}get rippleDisabled(){return this.disabled}get focusElement(){return this.inputRef.value}get focusDisabled(){return this.disabled}async onDisabledChange(){await this.updateComplete,this.invalid=!this.inputRef.value.checkValidity()}async onCheckedChange(){var e;await this.updateComplete;const t=this.formController.getForm();t&&(null===(e=Bi.get(t))||void 0===e?void 0:e.has(this))?(this.invalid=!1,Bi.get(t).delete(this)):this.invalid=!this.inputRef.value.checkValidity()}checkValidity(){const e=this.inputRef.value.checkValidity();return e||vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}),e}reportValidity(){if(this.invalid=!this.inputRef.value.reportValidity(),this.invalid){vi(this,"invalid",{bubbles:!1,cancelable:!0,composed:!1}).defaultPrevented&&(this.blur(),this.focus())}return!this.invalid}setCustomValidity(e){this.inputRef.value.setCustomValidity(e),this.invalid=!this.inputRef.value.checkValidity()}render(){return ct``}onChange(){this.checked=this.inputRef.value.checked,vi(this,"change")}},e.Switch.styles=[Wt,gr],Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Switch.prototype,"disabled",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Switch.prototype,"checked",void 0),Re([Xi("checked")],e.Switch.prototype,"defaultChecked",void 0),Re([Pt({reflect:!0,attribute:"unchecked-icon"})],e.Switch.prototype,"uncheckedIcon",void 0),Re([Pt({reflect:!0,attribute:"checked-icon"})],e.Switch.prototype,"checkedIcon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Switch.prototype,"required",void 0),Re([Pt({reflect:!0})],e.Switch.prototype,"form",void 0),Re([Pt({reflect:!0})],e.Switch.prototype,"name",void 0),Re([Pt({reflect:!0})],e.Switch.prototype,"value",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Switch.prototype,"invalid",void 0),Re([fi("disabled",!0),fi("required",!0)],e.Switch.prototype,"onDisabledChange",null),Re([fi("checked",!0)],e.Switch.prototype,"onCheckedChange",null),e.Switch=Re([Et("mdui-switch")],e.Switch);const br=Pe`:host{position:relative;--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([active]){--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}.container{display:flex;justify-content:center;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;height:100%}.preset{flex-direction:column;min-height:3rem;padding:.625rem 1rem}:host([inline]) .preset{flex-direction:row}.icon-container,.label-container{position:relative;display:flex;align-items:center;justify-content:center}.icon-container ::slotted([slot=badge]){position:absolute;transform:translate(50%,-50%)}.icon-container ::slotted([slot=badge][variant=small]){transform:translate(.5625rem,-.5625rem)}.label-container ::slotted([slot=badge]){position:absolute;left:100%;bottom:100%;transform:translate(-.75rem,.625rem)}.label-container ::slotted([slot=badge][variant=small]){transform:translate(-.375rem,.375rem)}.icon,.label{display:flex;color:rgb(var(--mdui-color-on-surface-variant))}:host([focused]) .icon,:host([focused]) .label,:host([hover]) .icon,:host([hover]) .label,:host([pressed]) .icon,:host([pressed]) .label{color:rgb(var(--mdui-color-on-surface))}:host([active]) .icon,:host([active]) .label{color:rgb(var(--mdui-color-primary))}:host([active][variant=secondary]) .icon,:host([active][variant=secondary]) .label{color:rgb(var(--mdui-color-on-surface))}.icon{font-size:1.5rem}.label{font-size:var(--mdui-typescale-title-small-size);font-weight:var(--mdui-typescale-title-small-weight);letter-spacing:var(--mdui-typescale-title-small-tracking);line-height:var(--mdui-typescale-title-small-line-height)}.icon mdui-icon,::slotted([slot=icon]){font-size:inherit;color:inherit}`;e.Tab=class extends(Ki(Fi(St))){constructor(){super(...arguments),this.inline=!1,this.active=!1,this.variant="primary",this.key=yi(),this.rippleRef=Ei(),this.hasSlotController=new jt(this,"icon","custom")}get rippleElement(){return this.rippleRef.value}get rippleDisabled(){return!1}get focusElement(){return this}get focusDisabled(){return!1}render(){const e=this.icon||this.hasSlotController.test("icon"),t=this.hasSlotController.test("custom"),i=()=>ct``;return ct`
                                            ${lo(e||this.icon,i)}${this.icon?ct``:Gt}
                                            ${lo(!e,i)}
                                            `}},e.Tab.styles=[Wt,br],Re([Pt({reflect:!0})],e.Tab.prototype,"value",void 0),Re([Pt({reflect:!0})],e.Tab.prototype,"icon",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Tab.prototype,"inline",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Tab.prototype,"active",void 0),Re([Pt({reflect:!0})],e.Tab.prototype,"variant",void 0),e.Tab=Re([Et("mdui-tab")],e.Tab);const yr=Pe`:host{display:block;overflow-y:auto;flex:1 1 auto}:host(:not([active])){display:none}`;e.TabPanel=class extends St{constructor(){super(...arguments),this.active=!1}render(){return ct``}},e.TabPanel.styles=[Wt,yr],Re([Pt({reflect:!0})],e.TabPanel.prototype,"value",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TabPanel.prototype,"active",void 0),e.TabPanel=Re([Et("mdui-tab-panel")],e.TabPanel);const wr=Pe`:host{position:relative;display:flex}:host([placement^=top]){flex-direction:column}:host([placement^=bottom]){flex-direction:column-reverse}:host([placement^=left]){flex-direction:row}:host([placement^=right]){flex-direction:row-reverse}.container{position:relative;display:flex;flex:0 0 auto;overflow-x:auto;background-color:rgb(var(--mdui-color-surface))}:host([placement^=bottom]) .container,:host([placement^=top]) .container{flex-direction:row}:host([placement^=left]) .container,:host([placement^=right]) .container{flex-direction:column}:host([placement$='-start']) .container{justify-content:flex-start}:host([placement=bottom]) .container,:host([placement=left]) .container,:host([placement=right]) .container,:host([placement=top]) .container{justify-content:center}:host([placement$='-end']) .container{justify-content:flex-end}.container::after{content:' ';position:absolute;background-color:rgb(var(--mdui-color-surface-variant))}:host([placement^=bottom]) .container::after,:host([placement^=top]) .container::after{left:0;width:100%;height:.0625rem}:host([placement^=top]) .container::after{bottom:0}:host([placement^=bottom]) .container::after{top:0}:host([placement^=left]) .container::after,:host([placement^=right]) .container::after{top:0;height:100%;width:.0625rem}:host([placement^=left]) .container::after{right:0}:host([placement^=right]) .container::after{left:0}.indicator{position:absolute;z-index:1;transition-duration:var(--mdui-motion-duration-medium2);transition-timing-function:var(--mdui-motion-easing-standard-decelerate);background-color:rgb(var(--mdui-color-primary))}:host([placement^=bottom]) .indicator,:host([placement^=top]) .indicator{transition-property:transform,left,width}:host([placement^=left]) .indicator,:host([placement^=right]) .indicator{transition-property:transform,top,height}:host([placement^=top]) .indicator{bottom:0}:host([placement^=bottom]) .indicator{top:0}:host([placement^=left]) .indicator{right:0}:host([placement^=right]) .indicator{left:0}:host([placement^=bottom][variant=primary]) .indicator,:host([placement^=top][variant=primary]) .indicator{height:.1875rem}:host([placement^=bottom][variant=secondary]) .indicator,:host([placement^=top][variant=secondary]) .indicator{height:.125rem}:host([placement^=left][variant=primary]) .indicator,:host([placement^=right][variant=primary]) .indicator{width:.1875rem}:host([placement^=left][variant=secondary]) .indicator,:host([placement^=right][variant=secondary]) .indicator{width:.125rem}:host([placement^=top][variant=primary]) .indicator{border-top-left-radius:.1875rem;border-top-right-radius:.1875rem}:host([placement^=bottom][variant=primary]) .indicator{border-bottom-right-radius:.1875rem;border-bottom-left-radius:.1875rem}:host([placement^=left][variant=primary]) .indicator{border-top-left-radius:.1875rem;border-bottom-left-radius:.1875rem}:host([placement^=right][variant=primary]) .indicator{border-top-right-radius:.1875rem;border-bottom-right-radius:.1875rem}:host([full-width]) ::slotted(mdui-tab){flex:1}`;e.Tabs=class extends St{constructor(){super(...arguments),this.variant="primary",this.placement="top-start",this.fullWidth=!1,this.activeKey=0,this.tabs=[],this.panels=[],this.containerRef=Ei(),this.indicatorRef=Ei()}onActiveKeyChange(){var e;this.value=null===(e=this.tabs.find((e=>e.key===this.activeKey)))||void 0===e?void 0:e.value,vi(this,"change")}onValueChange(){var e;const t=this.tabs.find((e=>e.value===this.value));this.activeKey=null!==(e=null==t?void 0:t.key)&&void 0!==e?e:0,this.updateActive()}onIndicatorChange(){this.updateIndicator()}connectedCallback(){super.connectedCallback(),this.syncTabsAndPanels(),this.updateComplete.then((()=>{this.observeResize=Ci(this.containerRef.value,(()=>this.updateIndicator()))}))}disconnectedCallback(){var e;super.disconnectedCallback(),null===(e=this.observeResize)||void 0===e||e.unobserve()}render(){return ct`
                                            `}syncTabsAndPanels(){this.tabs=P(this).find("mdui-tab").get(),this.panels=P(this).find('mdui-tab-panel[slot="panel"]').get()}onSlotChange(){this.syncTabsAndPanels(),this.updateActive()}onClick(e){if(e.button)return;const t=e.target.closest("mdui-tab");this.activeKey=t.key,this.updateActive()}updateActive(){this.activeTab=this.tabs.map((e=>(e.active=this.activeKey===e.key,e))).find((e=>e.active)),this.panels.forEach((e=>{var t;return e.active=e.value===(null===(t=this.activeTab)||void 0===t?void 0:t.value)})),this.updateIndicator()}async updateIndicator(){await this.updateComplete;const e=this.activeTab,t=P(this.indicatorRef.value),i=this.placement.startsWith("left")||this.placement.startsWith("right");if(!e)return void t.css({transform:i?"scaleY(0)":"scaleX(0)"});const o=P(e),r=e.offsetTop,n=e.offsetLeft,s=i?{transform:"scaleY(1)",width:"",left:""}:{transform:"scaleX(1)",height:"",top:""};let a={};if("primary"===this.variant){const t=o.find(':scope > [slot="custom"]').get(),s=t.length?t:P(e.renderRoot).find('slot[name="custom"]').children().get();if(i){const e=Math.min(...s.map((e=>e.offsetTop)))+r;a={top:e,height:Math.max(...s.map((e=>e.offsetTop+e.offsetHeight)))+r-e}}else{const e=Math.min(...s.map((e=>e.offsetLeft)))+n;a={left:e,width:Math.max(...s.map((e=>e.offsetLeft+e.offsetWidth)))+n-e}}}"secondary"===this.variant&&(a=i?{top:r,height:e.offsetHeight}:{left:n,width:e.offsetWidth}),t.css({...s,...a})}},e.Tabs.styles=[Wt,wr],Re([Pt({reflect:!0})],e.Tabs.prototype,"variant",void 0),Re([Pt({reflect:!0})],e.Tabs.prototype,"value",void 0),Re([Pt({reflect:!0})],e.Tabs.prototype,"placement",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi,attribute:"full-width"})],e.Tabs.prototype,"fullWidth",void 0),Re([Mt()],e.Tabs.prototype,"activeKey",void 0),Re([fi("activeKey",!0)],e.Tabs.prototype,"onActiveKeyChange",null),Re([fi("value")],e.Tabs.prototype,"onValueChange",null),Re([fi("variant",!0),fi("placement",!0),fi("fullWidth",!0)],e.Tabs.prototype,"onIndicatorChange",null),e.Tabs=Re([Et("mdui-tabs")],e.Tabs);class kr{constructor(e,t){this.isHover=!1,this.uniqueID=yi(),this.enterEventName=`mouseenter.${this.uniqueID}.hoverController`,this.leaveEventName=`mouseleave.${this.uniqueID}.hoverController`,this.mouseEnterItems=[],this.mouseLeaveItems=[],(this.host=e).addController(this),this.elementRef=t,this.host.updateComplete.then((()=>{P(this.elementRef.value).on(this.enterEventName,(()=>{this.isHover=!0,this.mouseEnterItems.forEach(((e,t,i)=>{e.callback(),e.one&&i.splice(t,1)}))})).on(this.leaveEventName,(()=>{this.isHover=!1,this.mouseLeaveItems.forEach(((e,t,i)=>{e.callback(),e.one&&i.splice(t,1)}))}))}))}hostDisconnected(){P(this.elementRef.value).off(this.enterEventName).off(this.leaveEventName)}onMouseEnter(e,t=!1){this.mouseEnterItems.push({callback:e,one:t})}onMouseLeave(e,t=!1){this.mouseLeaveItems.push({callback:e,one:t})}}const Cr=Pe`:host{--shape-corner-plain:var(--mdui-shape-corner-extra-small);--shape-corner-rich:var(--mdui-shape-corner-medium);--z-index:2500;display:contents}.popup{position:fixed;display:flex;flex-direction:column;z-index:var(--z-index);border-radius:var(--shape-corner-plain);background-color:rgb(var(--mdui-color-inverse-surface));padding:0 .5rem;min-width:1.75rem;max-width:20rem}:host([variant=rich]) .popup{border-radius:var(--shape-corner-rich);background-color:rgb(var(--mdui-color-surface-container));box-shadow:var(--mdui-elevation-level2);padding:.75rem 1rem .5rem 1rem}.headline{display:flex;color:rgb(var(--mdui-color-on-surface-variant));font-size:var(--mdui-typescale-title-small-size);font-weight:var(--mdui-typescale-title-small-weight);letter-spacing:var(--mdui-typescale-title-small-tracking);line-height:var(--mdui-typescale-title-small-line-height)}.content{display:flex;padding:.25rem 0;color:rgb(var(--mdui-color-inverse-on-surface));font-size:var(--mdui-typescale-body-small-size);font-weight:var(--mdui-typescale-body-small-weight);letter-spacing:var(--mdui-typescale-body-small-tracking);line-height:var(--mdui-typescale-body-small-line-height)}:host([variant=rich]) .content{color:rgb(var(--mdui-color-on-surface-variant));font-size:var(--mdui-typescale-body-medium-size);font-weight:var(--mdui-typescale-body-medium-weight);letter-spacing:var(--mdui-typescale-body-medium-tracking);line-height:var(--mdui-typescale-body-medium-line-height)}.action{display:flex;justify-content:flex-start;padding-top:.5rem}.action ::slotted(:not(:last-child)){margin-right:.5rem}`;e.Tooltip=class extends St{constructor(){super(),this.variant="plain",this.placement="auto",this.openDelay=150,this.closeDelay=150,this.trigger="hover focus",this.disabled=!1,this.open=!1,this.popupRef=Ei(),this.hasSlotController=new jt(this,"headline","action"),this.hoverController=new kr(this,this.popupRef),this.onDocumentClick=this.onDocumentClick.bind(this),this.onWindowScroll=this.onWindowScroll.bind(this),this.onFocus=this.onFocus.bind(this),this.onBlur=this.onBlur.bind(this),this.onClick=this.onClick.bind(this),this.onKeydown=this.onKeydown.bind(this),this.onMouseEnter=this.onMouseEnter.bind(this),this.onMouseLeave=this.onMouseLeave.bind(this)}async onPositionChange(){this.open&&this.updatePositioner()}async onOpenChange(){const e=this.hasUpdated,t=fo(this,"short4"),i=vo(this,"standard");if(this.open){if(P(`mdui-tooltip[variant="${this.variant}"]`).filter(((e,t)=>t!==this)).prop("open",!1),e||await this.updateComplete,e){if(vi(this,"open",{cancelable:!0}).defaultPrevented)return}return await ho(this.popupRef.value),this.popupRef.value.hidden=!1,this.updatePositioner(),await co(this.popupRef.value,[{transform:"scale(0)"},{transform:"scale(1)"}],{duration:e?t:0,easing:i}),void(e&&vi(this,"opened"))}if(!this.open&&e){if(vi(this,"close",{cancelable:!0}).defaultPrevented)return;return await ho(this.popupRef.value),await co(this.popupRef.value,[{transform:"scale(1)"},{transform:"scale(0)"}],{duration:t,easing:i}),this.popupRef.value.hidden=!0,void vi(this,"closed")}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this.onDocumentClick),window.addEventListener("scroll",this.onWindowScroll),this.updateComplete.then((()=>{this.observeResize=Ci(this.target,(()=>{this.updatePositioner()}))}))}disconnectedCallback(){var e;super.disconnectedCallback(),document.removeEventListener("pointerdown",this.onDocumentClick),window.removeEventListener("scroll",this.onWindowScroll),null===(e=this.observeResize)||void 0===e||e.unobserve()}firstUpdated(e){super.firstUpdated(e),this.target=this.getTarget(),this.target.addEventListener("focus",this.onFocus),this.target.addEventListener("blur",this.onBlur),this.target.addEventListener("pointerdown",this.onClick),this.target.addEventListener("keydown",this.onKeydown),this.target.addEventListener("mouseenter",this.onMouseEnter),this.target.addEventListener("mouseleave",this.onMouseLeave)}render(){const e=this.isRich()&&(this.headline||this.hasSlotController.test("headline")),t=this.isRich()&&this.hasSlotController.test("action");return ct``}isRich(){return"rich"===this.variant}async requestClose(){this.hoverController.isHover?this.hoverController.onMouseLeave((()=>{this.hasTrigger("hover")?this.hoverTimeout=window.setTimeout((()=>{this.open=!1}),this.closeDelay||50):this.open=!1}),!0):this.open=!1}getTarget(){return[...this.children].find((e=>"style"!==e.tagName.toLowerCase()&&"content"!==e.getAttribute("slot")))}hasTrigger(e){return this.trigger.split(" ").includes(e)}onFocus(){this.disabled||this.open||!this.hasTrigger("focus")||(this.open=!0)}onBlur(){!this.disabled&&this.open&&this.hasTrigger("focus")&&this.requestClose()}onClick(e){this.disabled||e.button||!this.hasTrigger("click")||this.open&&(this.hasTrigger("hover")||this.hasTrigger("focus"))||(this.open=!this.open)}onKeydown(e){!this.disabled&&this.open&&"Escape"===e.key&&(e.stopPropagation(),this.requestClose())}onMouseEnter(){this.disabled||this.open||!this.hasTrigger("hover")||(this.openDelay?(window.clearTimeout(this.hoverTimeout),this.hoverTimeout=window.setTimeout((()=>{this.open=!0}),this.openDelay)):this.open=!0)}onMouseLeave(){window.clearTimeout(this.hoverTimeout),!this.disabled&&this.open&&this.hasTrigger("hover")&&(this.hoverTimeout=window.setTimeout((()=>{this.requestClose()}),this.closeDelay||50))}onDocumentClick(e){if(this.disabled||!this.open)return;e.composedPath().includes(this)||this.requestClose()}onWindowScroll(){window.requestAnimationFrame((()=>this.updatePositioner()))}updatePositioner(){const e=P(this.popupRef.value),t=this.isRich()?0:4,i=this.target.getBoundingClientRect(),o=i.top,r=i.left,n=i.height,s=i.width,a=this.popupRef.value.offsetHeight,l=this.popupRef.value.offsetWidth,c=l+t+4,d=a+t+4;let h,u,p,m,v=this.placement;if("auto"===v){const e=P(window),t=o>d,i=e.height()-o-n>d,a=r>c,l=e.width()-r-s>c;this.isRich()?(v="bottom-right",i&&l?v="bottom-right":i&&a?v="bottom-left":t&&l?v="top-right":t&&a?v="top-left":i?v="bottom":t?v="top":l?v="right":a&&(v="left")):(v="top",t?v="top":i?v="bottom":a?v="left":l&&(v="right"))}const[f,g]=v.split("-");switch(f){case"top":u="bottom",p=o-a-t;break;case"bottom":u="top",p=o+n+t;break;default:switch(u="center",g){case"start":p=o;break;case"end":p=o+n-a;break;default:p=o+n/2-a/2}}switch(f){case"left":h="right",m=r-l-t;break;case"right":h="left",m=r+s+t;break;default:switch(h="center",g){case"start":m=r;break;case"end":m=r+s-l;break;case"left":h="right",m=r-l-t;break;case"right":h="left",m=r+s+t;break;default:m=r+s/2-l/2}}e.css({top:p,left:m,transformOrigin:[h,u].join(" ")})}},e.Tooltip.styles=[Wt,Cr],Re([Pt({reflect:!0})],e.Tooltip.prototype,"variant",void 0),Re([Pt({reflect:!0})],e.Tooltip.prototype,"placement",void 0),Re([Pt({type:Number,reflect:!0,attribute:"open-delay"})],e.Tooltip.prototype,"openDelay",void 0),Re([Pt({type:Number,reflect:!0,attribute:"close-delay"})],e.Tooltip.prototype,"closeDelay",void 0),Re([Pt({reflect:!0})],e.Tooltip.prototype,"headline",void 0),Re([Pt({reflect:!0})],e.Tooltip.prototype,"content",void 0),Re([Pt({reflect:!0})],e.Tooltip.prototype,"trigger",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Tooltip.prototype,"disabled",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.Tooltip.prototype,"open",void 0),Re([fi("placement",!0),fi("content",!0)],e.Tooltip.prototype,"onPositionChange",null),Re([fi("open")],e.Tooltip.prototype,"onOpenChange",null),e.Tooltip=Re([Et("mdui-tooltip")],e.Tooltip);const xr=Pe`:host{display:block;width:100%;flex-shrink:initial!important;overflow:hidden;color:rgb(var(--mdui-color-on-surface));font-size:var(--mdui-typescale-title-large-size);font-weight:var(--mdui-typescale-title-large-weight);letter-spacing:var(--mdui-typescale-title-large-tracking);line-height:var(--mdui-typescale-title-large-line-height);line-height:2.5rem}.label{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;opacity:1;transition:opacity var(--mdui-motion-duration-short2) var(--mdui-motion-easing-linear)}:host([variant=center-aligned]) .label{text-align:center}:host([variant=large]:not([shrink])) .label,:host([variant=medium]:not([shrink])) .label{opacity:0}:host([variant=large][shrink]) .label,:host([variant=medium][shrink]) .label{transition-delay:var(--mdui-motion-duration-short2)}.label-large{display:none;position:absolute;width:100%;left:0;margin-right:0;padding:0 1rem;transition:opacity var(--mdui-motion-duration-short2) var(--mdui-motion-easing-linear)}:host([variant=large]) .label-large,:host([variant=medium]) .label-large{display:block}:host([variant=medium]) .label-large{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;bottom:.75rem;font-size:var(--mdui-typescale-headline-small-size);font-weight:var(--mdui-typescale-headline-small-weight);letter-spacing:var(--mdui-typescale-headline-small-tracking);line-height:var(--mdui-typescale-headline-small-line-height)}:host([variant=large]) .label-large{display:-webkit-box;overflow:hidden;white-space:normal;-webkit-box-orient:vertical;-webkit-line-clamp:2;bottom:1.25rem;font-size:var(--mdui-typescale-headline-medium-size);font-weight:var(--mdui-typescale-headline-medium-weight);letter-spacing:var(--mdui-typescale-headline-medium-tracking);line-height:var(--mdui-typescale-headline-medium-line-height)}:host([variant=large]:not([shrink])) .label-large,:host([variant=medium]:not([shrink])) .label-large{opacity:1;transition-delay:var(--mdui-motion-duration-short2)}:host([variant=large][shrink]) .label-large,:host([variant=medium][shrink]) .label-large{opacity:0;z-index:-1}`;e.TopAppBarTitle=class extends St{constructor(){super(...arguments),this.variant="small",this.shrink=!1,this.hasSlotController=new jt(this,"label-large"),this.labelLargeRef=Ei(),this.defaultSlotRef=Ei()}render(){const e=this.hasSlotController.test("label-large");return ct`${e?ct``:ct`
                                            `}`}onSlotChange(e){e||(this.labelLargeRef.value.innerHTML=(e=>{const t=e.assignedNodes({flatten:!0});let i="";return[...t].forEach((e=>{e.nodeType===Node.ELEMENT_NODE&&(i+=e.outerHTML),e.nodeType===Node.TEXT_NODE&&(i+=e.textContent)})),i})(this.defaultSlotRef.value))}},e.TopAppBarTitle.styles=[Wt,xr],Re([Pt({reflect:!0})],e.TopAppBarTitle.prototype,"variant",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TopAppBarTitle.prototype,"shrink",void 0),e.TopAppBarTitle=Re([Et("mdui-top-app-bar-title")],e.TopAppBarTitle);const $r=Pe`:host{--shape-corner:var(--mdui-shape-corner-none);--z-index:2000;position:fixed;top:0;right:0;left:0;display:flex;flex:0 0 auto;align-items:flex-start;justify-content:flex-start;border-bottom-left-radius:var(--shape-corner);border-bottom-right-radius:var(--shape-corner);z-index:var(--z-index);transition:top var(--mdui-motion-duration-long2) var(--mdui-motion-easing-standard),height var(--mdui-motion-duration-long2) var(--mdui-motion-easing-standard),box-shadow var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear),background-color var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);padding:.75rem .5rem;height:4rem;background-color:rgb(var(--mdui-color-surface))}:host([scroll-target]:not([scroll-target=''])){position:absolute}:host([scroll-behavior~=shrink]){transition-duration:var(--mdui-motion-duration-short4)}:host([scrolling]){background-color:rgb(var(--mdui-color-surface-container));box-shadow:var(--mdui-elevation-level2)}::slotted(mdui-button-icon){color:rgb(var(--mdui-color-on-surface-variant));font-size:1.5rem}::slotted(mdui-button-icon:first-child){color:rgb(var(--mdui-color-on-surface))}::slotted(mdui-avatar){width:1.875rem;height:1.875rem;margin-top:.3125rem;margin-bottom:.3125rem}::slotted(*){flex-shrink:0}::slotted(:not(:last-child)){margin-right:.5rem}:host([variant=medium]){height:7rem}:host([variant=large]){height:9.5rem}:host([hide]){transition-duration:var(--mdui-motion-duration-short4);top:-4.625rem}:host([hide][variant=medium]){top:-7.625rem}:host([hide][variant=large]){top:-10.125rem}:host([shrink][variant=large]),:host([shrink][variant=medium]){transition-duration:var(--mdui-motion-duration-short4);height:4rem}`;function Rr(e){return!!e&&("object"==typeof e||"function"==typeof e)&&"function"==typeof e.then}e.TopAppBar=class extends(gi(Si)){constructor(){super(...arguments),this.variant="small",this.hide=!1,this.shrink=!1,this.scrolling=!1}get scrollPaddingPosition(){return"top"}get layoutPlacement(){return"top"}async onVariantChange(){this.hasUpdated?this.addEventListener("transitionend",(()=>{this.updateContainerPadding()}),{once:!0}):await this.updateComplete,this.titleElements.forEach((e=>{e.variant=this.variant}))}async onShrinkChange(){this.hasUpdated||await this.updateComplete,this.titleElements.forEach((e=>{e.shrink=this.shrink}))}connectedCallback(){super.connectedCallback(),this.addEventListener("transitionend",(e=>{e.target===this&&vi(this,this.hide?"hidden":"shown")}))}render(){return ct``}runScrollNoThreshold(e,t){this.hasScrollBehavior("shrink")&&e&&t<8&&(this.shrink=!1)}runScrollThreshold(e,t){if(this.hasScrollBehavior("elevate")&&(this.scrolling=!!t),this.hasScrollBehavior("shrink")&&(e||(this.shrink=!0)),this.hasScrollBehavior("hide")){if(!e&&!this.hide){vi(this,"hide").defaultPrevented||(this.hide=!0)}if(e&&this.hide){vi(this,"show").defaultPrevented||(this.hide=!1)}}}},e.TopAppBar.styles=[Wt,$r],Re([Pt({reflect:!0})],e.TopAppBar.prototype,"variant",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TopAppBar.prototype,"hide",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TopAppBar.prototype,"shrink",void 0),Re([Pt({reflect:!0,attribute:"scroll-behavior"})],e.TopAppBar.prototype,"scrollBehavior",void 0),Re([Pt({type:Boolean,reflect:!0,converter:mi})],e.TopAppBar.prototype,"scrolling",void 0),Re([Lt({selector:"mdui-top-app-bar-title",flatten:!0})],e.TopAppBar.prototype,"titleElements",void 0),Re([fi("variant")],e.TopAppBar.prototype,"onVariantChange",null),Re([fi("shrink")],e.TopAppBar.prototype,"onShrinkChange",null),e.TopAppBar=Re([Et("mdui-top-app-bar")],e.TopAppBar);const Sr={};function Tr(e,t){if(u(Sr[e])&&(Sr[e]=[]),u(t))return Sr[e];Sr[e].push(t)}function Er(e){if(u(Sr[e]))return;if(!Sr[e].length)return;Sr[e].shift()()}const Ir={onClick:x},Ar="mdui.functions.dialog.";let Pr;const Mr=t=>{const i=new e.Dialog,o=P(i),r=["headline","description","icon","closeOnEsc","closeOnOverlayClick","stackedActions"],n=["onOpen","onOpened","onClose","onClosed","onOverlayClick"];return Object.entries(t).forEach((([e,t])=>{if(r.includes(e))i[e]=t;else if(n.includes(e)){const r=k(e.slice(2));o.on(r,(()=>{t.call(i,i)}))}})),t.body&&o.append(t.body),t.actions&&t.actions.forEach((e=>{const t=Object.assign({},Ir,e);P(`${t.text}`).appendTo(o).on("click",(function(){const e=t.onClick.call(i,i);Rr(e)?(this.loading=!0,e.then((()=>{i.open=!1})).finally((()=>{this.loading=!1}))):!1!==e&&(i.open=!1)}))})),o.appendTo("body").on("closed",(()=>{o.remove(),t.queue&&(Pr=void 0,Er(Ar+t.queue))})),t.queue?Pr?Tr(Ar+t.queue,(()=>{i.open=!0,Pr=i})):(setTimeout((()=>{i.open=!0})),Pr=i):setTimeout((()=>{i.open=!0})),i},Dr={confirmText:"确定",onConfirm:x},Br={confirmText:"确定",cancelText:"取消",onConfirm:x,onCancel:x};function Lr(e){return e<0?-1:0===e?0:1}function _r(e,t,i){return(1-i)*e+i*t}function Or(e,t,i){return it?t:i}function zr(e){return(e%=360)<0&&(e+=360),e}function Fr(e){return(e%=360)<0&&(e+=360),e}function Nr(e,t){return 180-Math.abs(Math.abs(e-t)-180)}function Vr(e,t){return[e[0]*t[0][0]+e[1]*t[0][1]+e[2]*t[0][2],e[0]*t[1][0]+e[1]*t[1][1]+e[2]*t[1][2],e[0]*t[2][0]+e[1]*t[2][1]+e[2]*t[2][2]]}const Hr=[[.41233895,.35762064,.18051042],[.2126,.7152,.0722],[.01932141,.11916382,.95034478]],Kr=[[3.2413774792388685,-1.5376652402851851,-.49885366846268053],[-.9691452513005321,1.8758853451067872,.04156585616912061],[.05562093689691305,-.20395524564742123,1.0571799111220335]],Ur=[95.047,100,108.883];function qr(e,t,i){return(255<<24|(255&e)<<16|(255&t)<<8|255&i)>>>0}function jr(e){return qr(tn(e[0]),tn(e[1]),tn(e[2]))}function Gr(e){return e>>16&255}function Wr(e){return e>>8&255}function Yr(e){return 255&e}function Xr(e,t,i){const o=Kr,r=o[0][0]*e+o[0][1]*t+o[0][2]*i,n=o[1][0]*e+o[1][1]*t+o[1][2]*i,s=o[2][0]*e+o[2][1]*t+o[2][2]*i;return qr(tn(r),tn(n),tn(s))}function Jr(e){const t=function(e){return Vr([en(Gr(e)),en(Wr(e)),en(Yr(e))],Hr)}(e)[1];return 116*on(t/100)-16}function Zr(e){return 100*rn((e+16)/116)}function Qr(e){return 116*on(e/100)-16}function en(e){const t=e/255;return t<=.040449936?t/12.92*100:100*Math.pow((t+.055)/1.055,2.4)}function tn(e){const t=e/100;let i=0;return i=t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,o=0,r=255,(n=Math.round(255*i))r?r:n;var o,r,n}function on(e){return e>216/24389?Math.pow(e,1/3):(903.2962962962963*e+16)/116}function rn(e){const t=e*e*e;return t>216/24389?t:(116*e-16)/903.2962962962963}class nn{static make(e=function(){return Ur}(),t=200/Math.PI*Zr(50)/100,i=50,o=2,r=!1){const n=e,s=.401288*n[0]+.650173*n[1]+-.051461*n[2],a=-.250268*n[0]+1.204414*n[1]+.045854*n[2],l=-.002079*n[0]+.048952*n[1]+.953127*n[2],c=.8+o/10,d=c>=.9?_r(.59,.69,10*(c-.9)):_r(.525,.59,10*(c-.8));let h=r?1:c*(1-1/3.6*Math.exp((-t-42)/92));h=h>1?1:h<0?0:h;const u=c,p=[h*(100/s)+1-h,h*(100/a)+1-h,h*(100/l)+1-h],m=1/(5*t+1),v=m*m*m*m,f=1-v,g=v*t+.1*f*f*Math.cbrt(5*t),b=Zr(i)/e[1],y=1.48+Math.sqrt(b),w=.725/Math.pow(b,.2),k=w,C=[Math.pow(g*p[0]*s/100,.42),Math.pow(g*p[1]*a/100,.42),Math.pow(g*p[2]*l/100,.42)],x=[400*C[0]/(C[0]+27.13),400*C[1]/(C[1]+27.13),400*C[2]/(C[2]+27.13)];return new nn(b,(2*x[0]+x[1]+.05*x[2])*w,w,k,d,u,p,g,Math.pow(g,.25),y)}constructor(e,t,i,o,r,n,s,a,l,c){this.n=e,this.aw=t,this.nbb=i,this.ncb=o,this.c=r,this.nc=n,this.rgbD=s,this.fl=a,this.fLRoot=l,this.z=c}}nn.DEFAULT=nn.make();class sn{constructor(e,t,i,o,r,n,s,a,l){this.hue=e,this.chroma=t,this.j=i,this.q=o,this.m=r,this.s=n,this.jstar=s,this.astar=a,this.bstar=l}distance(e){const t=this.jstar-e.jstar,i=this.astar-e.astar,o=this.bstar-e.bstar,r=Math.sqrt(t*t+i*i+o*o);return 1.41*Math.pow(r,.63)}static fromInt(e){return sn.fromIntInViewingConditions(e,nn.DEFAULT)}static fromIntInViewingConditions(e,t){const i=(65280&e)>>8,o=255&e,r=en((16711680&e)>>16),n=en(i),s=en(o),a=.41233895*r+.35762064*n+.18051042*s,l=.2126*r+.7152*n+.0722*s,c=.01932141*r+.11916382*n+.95034478*s,d=.401288*a+.650173*l-.051461*c,h=-.250268*a+1.204414*l+.045854*c,u=-.002079*a+.048952*l+.953127*c,p=t.rgbD[0]*d,m=t.rgbD[1]*h,v=t.rgbD[2]*u,f=Math.pow(t.fl*Math.abs(p)/100,.42),g=Math.pow(t.fl*Math.abs(m)/100,.42),b=Math.pow(t.fl*Math.abs(v)/100,.42),y=400*Lr(p)*f/(f+27.13),w=400*Lr(m)*g/(g+27.13),k=400*Lr(v)*b/(b+27.13),C=(11*y+-12*w+k)/11,x=(y+w-2*k)/9,$=(20*y+20*w+21*k)/20,R=(40*y+20*w+k)/20,S=180*Math.atan2(x,C)/Math.PI,T=S<0?S+360:S>=360?S-360:S,E=T*Math.PI/180,I=R*t.nbb,A=100*Math.pow(I/t.aw,t.c*t.z),P=4/t.c*Math.sqrt(A/100)*(t.aw+4)*t.fLRoot,M=T<20.14?T+360:T,D=5e4/13*(.25*(Math.cos(M*Math.PI/180+2)+3.8))*t.nc*t.ncb*Math.sqrt(C*C+x*x)/($+.305),B=Math.pow(D,.9)*Math.pow(1.64-Math.pow(.29,t.n),.73),L=B*Math.sqrt(A/100),_=L*t.fLRoot,O=50*Math.sqrt(B*t.c/(t.aw+4)),z=(1+100*.007)*A/(1+.007*A),F=1/.0228*Math.log(1+.0228*_),N=F*Math.cos(E),V=F*Math.sin(E);return new sn(T,L,A,P,_,O,z,N,V)}static fromJch(e,t,i){return sn.fromJchInViewingConditions(e,t,i,nn.DEFAULT)}static fromJchInViewingConditions(e,t,i,o){const r=4/o.c*Math.sqrt(e/100)*(o.aw+4)*o.fLRoot,n=t*o.fLRoot,s=t/Math.sqrt(e/100),a=50*Math.sqrt(s*o.c/(o.aw+4)),l=i*Math.PI/180,c=(1+100*.007)*e/(1+.007*e),d=1/.0228*Math.log(1+.0228*n),h=d*Math.cos(l),u=d*Math.sin(l);return new sn(i,t,e,r,n,a,c,h,u)}static fromUcs(e,t,i){return sn.fromUcsInViewingConditions(e,t,i,nn.DEFAULT)}static fromUcsInViewingConditions(e,t,i,o){const r=t,n=i,s=Math.sqrt(r*r+n*n),a=(Math.exp(.0228*s)-1)/.0228/o.fLRoot;let l=Math.atan2(n,r)*(180/Math.PI);l<0&&(l+=360);const c=e/(1-.007*(e-100));return sn.fromJchInViewingConditions(c,a,l,o)}toInt(){return this.viewed(nn.DEFAULT)}viewed(e){const t=0===this.chroma||0===this.j?0:this.chroma/Math.sqrt(this.j/100),i=Math.pow(t/Math.pow(1.64-Math.pow(.29,e.n),.73),1/.9),o=this.hue*Math.PI/180,r=.25*(Math.cos(o+2)+3.8),n=e.aw*Math.pow(this.j/100,1/e.c/e.z),s=r*(5e4/13)*e.nc*e.ncb,a=n/e.nbb,l=Math.sin(o),c=Math.cos(o),d=23*(a+.305)*i/(23*s+11*i*c+108*i*l),h=d*c,u=d*l,p=(460*a+451*h+288*u)/1403,m=(460*a-891*h-261*u)/1403,v=(460*a-220*h-6300*u)/1403,f=Math.max(0,27.13*Math.abs(p)/(400-Math.abs(p))),g=Lr(p)*(100/e.fl)*Math.pow(f,1/.42),b=Math.max(0,27.13*Math.abs(m)/(400-Math.abs(m))),y=Lr(m)*(100/e.fl)*Math.pow(b,1/.42),w=Math.max(0,27.13*Math.abs(v)/(400-Math.abs(v))),k=Lr(v)*(100/e.fl)*Math.pow(w,1/.42),C=g/e.rgbD[0],x=y/e.rgbD[1],$=k/e.rgbD[2];return Xr(1.86206786*C-1.01125463*x+.14918677*$,.38752654*C+.62144744*x-.00897398*$,-.0158415*C-.03412294*x+1.04996444*$)}static fromXyzInViewingConditions(e,t,i,o){const r=.401288*e+.650173*t-.051461*i,n=-.250268*e+1.204414*t+.045854*i,s=-.002079*e+.048952*t+.953127*i,a=o.rgbD[0]*r,l=o.rgbD[1]*n,c=o.rgbD[2]*s,d=Math.pow(o.fl*Math.abs(a)/100,.42),h=Math.pow(o.fl*Math.abs(l)/100,.42),u=Math.pow(o.fl*Math.abs(c)/100,.42),p=400*Lr(a)*d/(d+27.13),m=400*Lr(l)*h/(h+27.13),v=400*Lr(c)*u/(u+27.13),f=(11*p+-12*m+v)/11,g=(p+m-2*v)/9,b=(20*p+20*m+21*v)/20,y=(40*p+20*m+v)/20,w=180*Math.atan2(g,f)/Math.PI,k=w<0?w+360:w>=360?w-360:w,C=k*Math.PI/180,x=y*o.nbb,$=100*Math.pow(x/o.aw,o.c*o.z),R=4/o.c*Math.sqrt($/100)*(o.aw+4)*o.fLRoot,S=k<20.14?k+360:k,T=5e4/13*(1/4*(Math.cos(S*Math.PI/180+2)+3.8))*o.nc*o.ncb*Math.sqrt(f*f+g*g)/(b+.305),E=Math.pow(T,.9)*Math.pow(1.64-Math.pow(.29,o.n),.73),I=E*Math.sqrt($/100),A=I*o.fLRoot,P=50*Math.sqrt(E*o.c/(o.aw+4)),M=(1+100*.007)*$/(1+.007*$),D=Math.log(1+.0228*A)/.0228,B=D*Math.cos(C),L=D*Math.sin(C);return new sn(k,I,$,R,A,P,M,B,L)}xyzInViewingConditions(e){const t=0===this.chroma||0===this.j?0:this.chroma/Math.sqrt(this.j/100),i=Math.pow(t/Math.pow(1.64-Math.pow(.29,e.n),.73),1/.9),o=this.hue*Math.PI/180,r=.25*(Math.cos(o+2)+3.8),n=e.aw*Math.pow(this.j/100,1/e.c/e.z),s=r*(5e4/13)*e.nc*e.ncb,a=n/e.nbb,l=Math.sin(o),c=Math.cos(o),d=23*(a+.305)*i/(23*s+11*i*c+108*i*l),h=d*c,u=d*l,p=(460*a+451*h+288*u)/1403,m=(460*a-891*h-261*u)/1403,v=(460*a-220*h-6300*u)/1403,f=Math.max(0,27.13*Math.abs(p)/(400-Math.abs(p))),g=Lr(p)*(100/e.fl)*Math.pow(f,1/.42),b=Math.max(0,27.13*Math.abs(m)/(400-Math.abs(m))),y=Lr(m)*(100/e.fl)*Math.pow(b,1/.42),w=Math.max(0,27.13*Math.abs(v)/(400-Math.abs(v))),k=Lr(v)*(100/e.fl)*Math.pow(w,1/.42),C=g/e.rgbD[0],x=y/e.rgbD[1],$=k/e.rgbD[2];return[1.86206786*C-1.01125463*x+.14918677*$,.38752654*C+.62144744*x-.00897398*$,-.0158415*C-.03412294*x+1.04996444*$]}}class an{static sanitizeRadians(e){return(e+8*Math.PI)%(2*Math.PI)}static trueDelinearized(e){const t=e/100;let i=0;return i=t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,255*i}static chromaticAdaptation(e){const t=Math.pow(Math.abs(e),.42);return 400*Lr(e)*t/(t+27.13)}static hueOf(e){const t=Vr(e,an.SCALED_DISCOUNT_FROM_LINRGB),i=an.chromaticAdaptation(t[0]),o=an.chromaticAdaptation(t[1]),r=an.chromaticAdaptation(t[2]),n=(11*i+-12*o+r)/11,s=(i+o-2*r)/9;return Math.atan2(s,n)}static areInCyclicOrder(e,t,i){return an.sanitizeRadians(t-e)100.01||y[1]>100.01||y[2]>100.01?0:jr(y);o-=(x-i)*o/(2*x)}return 0}static solveToInt(e,t,i){if(t<1e-4||i<1e-4||i>99.9999)return function(e){const t=tn(Zr(e));return qr(t,t,t)}(i);const o=(e=Fr(e))/180*Math.PI,r=Zr(i),n=an.findResultByJ(o,t,r);if(0!==n)return n;return jr(an.bisectToLimit(r,o))}static solveToCam(e,t,i){return sn.fromInt(an.solveToInt(e,t,i))}}an.SCALED_DISCOUNT_FROM_LINRGB=[[.001200833568784504,.002389694492170889,.0002795742885861124],[.0005891086651375999,.0029785502573438758,.0003270666104008398],[.00010146692491640572,.0005364214359186694,.0032979401770712076]],an.LINRGB_FROM_SCALED_DISCOUNT=[[1373.2198709594231,-1100.4251190754821,-7.278681089101213],[-271.815969077903,559.6580465940733,-32.46047482791194],[1.9622899599665666,-57.173814538844006,308.7233197812385]],an.Y_FROM_LINRGB=[.2126,.7152,.0722],an.CRITICAL_PLANES=[.015176349177441876,.045529047532325624,.07588174588720938,.10623444424209313,.13658714259697685,.16693984095186062,.19729253930674434,.2276452376616281,.2579979360165119,.28835063437139563,.3188300904430532,.350925934958123,.3848314933096426,.42057480301049466,.458183274052838,.4976837250274023,.5391024159806381,.5824650784040898,.6277969426914107,.6751227633498623,.7244668422128921,.775853049866786,.829304845476233,.8848452951698498,.942497089126609,1.0022825574869039,1.0642236851973577,1.1283421258858297,1.1946592148522128,1.2631959812511864,1.3339731595349034,1.407011200216447,1.4823302800086415,1.5599503113873272,1.6398909516233677,1.7221716113234105,1.8068114625156377,1.8938294463134073,1.9832442801866852,2.075074464868551,2.1693382909216234,2.2660538449872063,2.36523901573795,2.4669114995532007,2.5710888059345764,2.6777882626779785,2.7870270208169257,2.898822059350997,3.0131901897720907,3.1301480604002863,3.2497121605402226,3.3718988244681087,3.4967242352587946,3.624204428461639,3.754355295633311,3.887192587735158,4.022731918402185,4.160988767090289,4.301978482107941,4.445716283538092,4.592217266055746,4.741496401646282,4.893568542229298,5.048448422192488,5.20615066083972,5.3666897647573375,5.5300801301023865,5.696336044816294,5.865471690767354,6.037501145825082,6.212438385869475,6.390297286737924,6.571091626112461,6.7548350853498045,6.941541251256611,7.131223617812143,7.323895587840543,7.5195704746346665,7.7182615035334345,7.919981813454504,8.124744458384042,8.332562408825165,8.543448553206703,8.757415699253682,8.974476575321063,9.194643831691977,9.417930041841839,9.644347703669503,9.873909240696694,10.106627003236781,10.342513269534024,10.58158024687427,10.8238400726681,11.069304815507364,11.317986476196008,11.569896988756009,11.825048221409341,12.083451977536606,12.345119996613247,12.610063955123938,12.878295467455942,13.149826086772048,13.42466730586372,13.702830557985108,13.984327217668513,14.269168601521828,14.55736596900856,14.848930523210871,15.143873411576273,15.44220572664832,15.743938506781891,16.04908273684337,16.35764934889634,16.66964922287304,16.985093187232053,17.30399201960269,17.62635644741625,17.95219714852476,18.281524751807332,18.614349837764564,18.95068293910138,19.290534541298456,19.633915083172692,19.98083495742689,20.331304511189067,20.685334046541502,21.042933821039977,21.404114048223256,21.76888489811322,22.137256497705877,22.50923893145328,22.884842241736916,23.264076429332462,23.6469514538663,24.033477234264016,24.42366364919083,24.817520537484558,25.21505769858089,25.61628489293138,26.021211842414342,26.429848230738664,26.842203703840827,27.258287870275353,27.678110301598522,28.10168053274597,28.529008062403893,28.96010235337422,29.39497283293396,29.83362889318845,30.276079891419332,30.722335150426627,31.172403958865512,31.62629557157785,32.08401920991837,32.54558406207592,33.010999283389665,33.4802739966603,33.953417292456834,34.430438229418264,34.911345834551085,35.39614910352207,35.88485700094671,36.37747846067349,36.87402238606382,37.37449765026789,37.87891309649659,38.38727753828926,38.89959975977785,39.41588851594697,39.93615253289054,40.460400508064545,40.98864111053629,41.520882981230194,42.05713473317016,42.597404951718396,43.141702194811224,43.6900349931913,44.24241185063697,44.798841244188324,45.35933162437017,45.92389141541209,46.49252901546552,47.065252796817916,47.64207110610409,48.22299226451468,48.808024568002054,49.3971762874833,49.9904556690408,50.587870934119984,51.189430279724725,51.79514187861014,52.40501387947288,53.0190544071392,53.637271562750364,54.259673423945976,54.88626804504493,55.517063457223934,56.15206766869424,56.79128866487574,57.43473440856916,58.08241284012621,58.734331877617365,59.39049941699807,60.05092333227251,60.715611475655585,61.38457167773311,62.057811747619894,62.7353394731159,63.417162620860914,64.10328893648692,64.79372614476921,65.48848194977529,66.18756403501224,66.89098006357258,67.59873767827808,68.31084450182222,69.02730813691093,69.74813616640164,70.47333615344107,71.20291564160104,71.93688215501312,72.67524319850172,73.41800625771542,74.16517879925733,74.9167682708136,75.67278210128072,76.43322770089146,77.1981124613393,77.96744375590167,78.74122893956174,79.51947534912904,80.30219030335869,81.08938110306934,81.88105503125999,82.67721935322541,83.4778813166706,84.28304815182372,85.09272707154808,85.90692527145302,86.72564993000343,87.54890820862819,88.3767072518277,89.2090541872801,90.04595612594655,90.88742016217518,91.73345337380438,92.58406282226491,93.43925555268066,94.29903859396902,95.16341895893969,96.03240364439274,96.9059996312159,97.78421388448044,98.6670533535366,99.55452497210776];class ln{static from(e,t,i){return new ln(an.solveToInt(e,t,i))}static fromInt(e){return new ln(e)}toInt(){return this.argb}get hue(){return this.internalHue}set hue(e){this.setInternalState(an.solveToInt(e,this.internalChroma,this.internalTone))}get chroma(){return this.internalChroma}set chroma(e){this.setInternalState(an.solveToInt(this.internalHue,e,this.internalTone))}get tone(){return this.internalTone}set tone(e){this.setInternalState(an.solveToInt(this.internalHue,this.internalChroma,e))}constructor(e){this.argb=e;const t=sn.fromInt(e);this.internalHue=t.hue,this.internalChroma=t.chroma,this.internalTone=Jr(e),this.argb=e}setInternalState(e){const t=sn.fromInt(e);this.internalHue=t.hue,this.internalChroma=t.chroma,this.internalTone=Jr(e),this.argb=e}inViewingConditions(e){const t=sn.fromInt(this.toInt()).xyzInViewingConditions(e),i=sn.fromXyzInViewingConditions(t[0],t[1],t[2],nn.make());return ln.from(i.hue,i.chroma,Qr(t[1]))}}class cn{static harmonize(e,t){const i=ln.fromInt(e),o=ln.fromInt(t),r=Nr(i.hue,o.hue),n=Math.min(.5*r,15),s=Fr(i.hue+n*(a=i.hue,Fr(o.hue-a)<=180?1:-1));var a;return ln.from(s,i.chroma,i.tone).toInt()}static hctHue(e,t,i){const o=cn.cam16Ucs(e,t,i),r=sn.fromInt(o),n=sn.fromInt(e);return ln.from(r.hue,n.chroma,Jr(e)).toInt()}static cam16Ucs(e,t,i){const o=sn.fromInt(e),r=sn.fromInt(t),n=o.jstar,s=o.astar,a=o.bstar,l=n+(r.jstar-n)*i,c=s+(r.astar-s)*i,d=a+(r.bstar-a)*i;return sn.fromUcs(l,c,d).toInt()}}class dn{static ratioOfTones(e,t){return e=Or(0,100,e),t=Or(0,100,t),dn.ratioOfYs(Zr(e),Zr(t))}static ratioOfYs(e,t){const i=e>t?e:t;return(i+5)/((i===t?e:t)+5)}static lighter(e,t){if(e<0||e>100)return-1;const i=Zr(e),o=t*(i+5)-5,r=dn.ratioOfYs(o,i),n=Math.abs(r-t);if(r.04)return-1;const s=Qr(o)+.4;return s<0||s>100?-1:s}static darker(e,t){if(e<0||e>100)return-1;const i=Zr(e),o=(i+5)/t-5,r=dn.ratioOfYs(i,o),n=Math.abs(r-t);if(r.04)return-1;const s=Qr(o)-.4;return s<0||s>100?-1:s}static lighterUnsafe(e,t){const i=dn.lighter(e,t);return i<0?100:i}static darkerUnsafe(e,t){const i=dn.darker(e,t);return i<0?0:i}}class hn{static isDisliked(e){const t=Math.round(e.hue)>=90&&Math.round(e.hue)<=111,i=Math.round(e.chroma)>16,o=Math.round(e.tone)<65;return t&&i&&o}static fixIfDisliked(e){return hn.isDisliked(e)?ln.from(e.hue,e.chroma,70):e}}class un{static fromPalette(e){return new un(e.name??"",e.palette,e.tone,e.isBackground??!1,e.background,e.secondBackground,e.contrastCurve,e.toneDeltaPair)}constructor(e,t,i,o,r,n,s,a){if(this.name=e,this.palette=t,this.tone=i,this.isBackground=o,this.background=r,this.secondBackground=n,this.contrastCurve=s,this.toneDeltaPair=a,this.hctCache=new Map,!r&&n)throw new Error(`Color ${e} has secondBackgrounddefined, but background is not defined.`);if(!r&&s)throw new Error(`Color ${e} has contrastCurvedefined, but background is not defined.`);if(r&&!s)throw new Error(`Color ${e} has backgrounddefined, but contrastCurve is not defined.`)}getArgb(e){return this.getHct(e).toInt()}getHct(e){const t=this.hctCache.get(e);if(null!=t)return t;const i=this.getTone(e),o=this.palette(e).getHct(i);return this.hctCache.size>4&&this.hctCache.clear(),this.hctCache.set(e,o),o}getTone(e){const t=e.contrastLevel<0;if(this.toneDeltaPair){const i=this.toneDeltaPair(e),o=i.roleA,r=i.roleB,n=i.delta,s=i.polarity,a=i.stayTogether,l=this.background(e).getTone(e),c="nearer"===s||"lighter"===s&&!e.isDark||"darker"===s&&e.isDark,d=c?o:r,h=c?r:o,u=this.name===d.name,p=e.isDark?1:-1,m=d.contrastCurve.getContrast(e.contrastLevel),v=h.contrastCurve.getContrast(e.contrastLevel),f=d.tone(e);let g=dn.ratioOfTones(l,f)>=m?f:un.foregroundTone(l,m);const b=h.tone(e);let y=dn.ratioOfTones(l,b)>=v?b:un.foregroundTone(l,v);return t&&(g=un.foregroundTone(l,m),y=un.foregroundTone(l,v)),(y-g)*p>=n||(y=Or(0,100,g+n*p),(y-g)*p>=n||(g=Or(0,100,y-n*p))),50<=g&&g<60?p>0?(g=60,y=Math.max(y,g+n*p)):(g=49,y=Math.min(y,g+n*p)):50<=y&&y<60&&(a?p>0?(g=60,y=Math.max(y,g+n*p)):(g=49,y=Math.min(y,g+n*p)):y=p>0?60:49),u?g:y}{let i=this.tone(e);if(null==this.background)return i;const o=this.background(e).getTone(e),r=this.contrastCurve.getContrast(e.contrastLevel);if(dn.ratioOfTones(o,i)>=r||(i=un.foregroundTone(o,r)),t&&(i=un.foregroundTone(o,r)),this.isBackground&&50<=i&&i<60&&(i=dn.ratioOfTones(49,o)>=r?49:60),this.secondBackground){const[t,o]=[this.background,this.secondBackground],[n,s]=[t(e).getTone(e),o(e).getTone(e)],[a,l]=[Math.max(n,s),Math.min(n,s)];if(dn.ratioOfTones(a,i)>=r&&dn.ratioOfTones(l,i)>=r)return i;const c=dn.lighter(a,r),d=dn.darker(l,r),h=[];-1!==c&&h.push(c),-1!==d&&h.push(d);return un.tonePrefersLightForeground(n)||un.tonePrefersLightForeground(s)?c<0?100:c:1===h.length?h[0]:d<0?0:d}return i}}static foregroundTone(e,t){const i=dn.lighterUnsafe(e,t),o=dn.darkerUnsafe(e,t),r=dn.ratioOfTones(i,e),n=dn.ratioOfTones(o,e);if(un.tonePrefersLightForeground(e)){const e=Math.abs(r-n)<.1&&r=t||r>=n||e?i:o}return n>=t||n>=r?o:i}static tonePrefersLightForeground(e){return Math.round(e)<60}static toneAllowsLightForeground(e){return Math.round(e)<=49}static enableLightForeground(e){return un.tonePrefersLightForeground(e)&&!un.toneAllowsLightForeground(e)?49:e}}var pn;!function(e){e[e.MONOCHROME=0]="MONOCHROME",e[e.NEUTRAL=1]="NEUTRAL",e[e.TONAL_SPOT=2]="TONAL_SPOT",e[e.VIBRANT=3]="VIBRANT",e[e.EXPRESSIVE=4]="EXPRESSIVE",e[e.FIDELITY=5]="FIDELITY",e[e.CONTENT=6]="CONTENT",e[e.RAINBOW=7]="RAINBOW",e[e.FRUIT_SALAD=8]="FRUIT_SALAD"}(pn||(pn={}));class mn{constructor(e,t,i,o){this.low=e,this.normal=t,this.medium=i,this.high=o}getContrast(e){return e<=-1?this.low:e<0?_r(this.low,this.normal,(e- -1)/1):e<.5?_r(this.normal,this.medium,(e-0)/.5):e<1?_r(this.medium,this.high,(e-.5)/.5):this.high}}class vn{constructor(e,t,i,o,r){this.roleA=e,this.roleB=t,this.delta=i,this.polarity=o,this.stayTogether=r}}function fn(e){return e.variant===pn.FIDELITY||e.variant===pn.CONTENT}function gn(e){return e.variant===pn.MONOCHROME}function bn(e,t){const i=e.inViewingConditions(function(e){return nn.make(void 0,void 0,e.isDark?30:80,void 0,void 0)}(t));return un.tonePrefersLightForeground(e.tone)&&!un.toneAllowsLightForeground(i.tone)?un.enableLightForeground(e.tone):un.enableLightForeground(i.tone)}class yn{static highestSurface(e){return e.isDark?yn.surfaceBright:yn.surfaceDim}}yn.contentAccentToneDelta=15,yn.primaryPaletteKeyColor=un.fromPalette({name:"primary_palette_key_color",palette:e=>e.primaryPalette,tone:e=>e.primaryPalette.keyColor.tone}),yn.secondaryPaletteKeyColor=un.fromPalette({name:"secondary_palette_key_color",palette:e=>e.secondaryPalette,tone:e=>e.secondaryPalette.keyColor.tone}),yn.tertiaryPaletteKeyColor=un.fromPalette({name:"tertiary_palette_key_color",palette:e=>e.tertiaryPalette,tone:e=>e.tertiaryPalette.keyColor.tone}),yn.neutralPaletteKeyColor=un.fromPalette({name:"neutral_palette_key_color",palette:e=>e.neutralPalette,tone:e=>e.neutralPalette.keyColor.tone}),yn.neutralVariantPaletteKeyColor=un.fromPalette({name:"neutral_variant_palette_key_color",palette:e=>e.neutralVariantPalette,tone:e=>e.neutralVariantPalette.keyColor.tone}),yn.background=un.fromPalette({name:"background",palette:e=>e.neutralPalette,tone:e=>e.isDark?6:98,isBackground:!0}),yn.onBackground=un.fromPalette({name:"on_background",palette:e=>e.neutralPalette,tone:e=>e.isDark?90:10,background:e=>yn.background,contrastCurve:new mn(3,3,4.5,7)}),yn.surface=un.fromPalette({name:"surface",palette:e=>e.neutralPalette,tone:e=>e.isDark?6:98,isBackground:!0}),yn.surfaceDim=un.fromPalette({name:"surface_dim",palette:e=>e.neutralPalette,tone:e=>e.isDark?6:87,isBackground:!0}),yn.surfaceBright=un.fromPalette({name:"surface_bright",palette:e=>e.neutralPalette,tone:e=>e.isDark?24:98,isBackground:!0}),yn.surfaceContainerLowest=un.fromPalette({name:"surface_container_lowest",palette:e=>e.neutralPalette,tone:e=>e.isDark?4:100,isBackground:!0}),yn.surfaceContainerLow=un.fromPalette({name:"surface_container_low",palette:e=>e.neutralPalette,tone:e=>e.isDark?10:96,isBackground:!0}),yn.surfaceContainer=un.fromPalette({name:"surface_container",palette:e=>e.neutralPalette,tone:e=>e.isDark?12:94,isBackground:!0}),yn.surfaceContainerHigh=un.fromPalette({name:"surface_container_high",palette:e=>e.neutralPalette,tone:e=>e.isDark?17:92,isBackground:!0}),yn.surfaceContainerHighest=un.fromPalette({name:"surface_container_highest",palette:e=>e.neutralPalette,tone:e=>e.isDark?22:90,isBackground:!0}),yn.onSurface=un.fromPalette({name:"on_surface",palette:e=>e.neutralPalette,tone:e=>e.isDark?90:10,background:e=>yn.highestSurface(e),contrastCurve:new mn(4.5,7,11,21)}),yn.surfaceVariant=un.fromPalette({name:"surface_variant",palette:e=>e.neutralVariantPalette,tone:e=>e.isDark?30:90,isBackground:!0}),yn.onSurfaceVariant=un.fromPalette({name:"on_surface_variant",palette:e=>e.neutralVariantPalette,tone:e=>e.isDark?80:30,background:e=>yn.highestSurface(e),contrastCurve:new mn(3,4.5,7,11)}),yn.inverseSurface=un.fromPalette({name:"inverse_surface",palette:e=>e.neutralPalette,tone:e=>e.isDark?90:20}),yn.inverseOnSurface=un.fromPalette({name:"inverse_on_surface",palette:e=>e.neutralPalette,tone:e=>e.isDark?20:95,background:e=>yn.inverseSurface,contrastCurve:new mn(4.5,7,11,21)}),yn.outline=un.fromPalette({name:"outline",palette:e=>e.neutralVariantPalette,tone:e=>e.isDark?60:50,background:e=>yn.highestSurface(e),contrastCurve:new mn(1.5,3,4.5,7)}),yn.outlineVariant=un.fromPalette({name:"outline_variant",palette:e=>e.neutralVariantPalette,tone:e=>e.isDark?30:80,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7)}),yn.shadow=un.fromPalette({name:"shadow",palette:e=>e.neutralPalette,tone:e=>0}),yn.scrim=un.fromPalette({name:"scrim",palette:e=>e.neutralPalette,tone:e=>0}),yn.surfaceTint=un.fromPalette({name:"surface_tint",palette:e=>e.primaryPalette,tone:e=>e.isDark?80:40,isBackground:!0}),yn.primary=un.fromPalette({name:"primary",palette:e=>e.primaryPalette,tone:e=>gn(e)?e.isDark?100:0:e.isDark?80:40,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(3,4.5,7,11),toneDeltaPair:e=>new vn(yn.primaryContainer,yn.primary,15,"nearer",!1)}),yn.onPrimary=un.fromPalette({name:"on_primary",palette:e=>e.primaryPalette,tone:e=>gn(e)?e.isDark?10:90:e.isDark?20:100,background:e=>yn.primary,contrastCurve:new mn(4.5,7,11,21)}),yn.primaryContainer=un.fromPalette({name:"primary_container",palette:e=>e.primaryPalette,tone:e=>fn(e)?bn(e.sourceColorHct,e):gn(e)?e.isDark?85:25:e.isDark?30:90,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.primaryContainer,yn.primary,15,"nearer",!1)}),yn.onPrimaryContainer=un.fromPalette({name:"on_primary_container",palette:e=>e.primaryPalette,tone:e=>fn(e)?un.foregroundTone(yn.primaryContainer.tone(e),4.5):gn(e)?e.isDark?0:100:e.isDark?90:10,background:e=>yn.primaryContainer,contrastCurve:new mn(4.5,7,11,21)}),yn.inversePrimary=un.fromPalette({name:"inverse_primary",palette:e=>e.primaryPalette,tone:e=>e.isDark?40:80,background:e=>yn.inverseSurface,contrastCurve:new mn(3,4.5,7,11)}),yn.secondary=un.fromPalette({name:"secondary",palette:e=>e.secondaryPalette,tone:e=>e.isDark?80:40,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(3,4.5,7,11),toneDeltaPair:e=>new vn(yn.secondaryContainer,yn.secondary,15,"nearer",!1)}),yn.onSecondary=un.fromPalette({name:"on_secondary",palette:e=>e.secondaryPalette,tone:e=>gn(e)?e.isDark?10:100:e.isDark?20:100,background:e=>yn.secondary,contrastCurve:new mn(4.5,7,11,21)}),yn.secondaryContainer=un.fromPalette({name:"secondary_container",palette:e=>e.secondaryPalette,tone:e=>{const t=e.isDark?30:90;if(gn(e))return e.isDark?30:85;if(!fn(e))return t;let i=function(e,t,i,o){let r=i,n=ln.from(e,t,i);if(n.chromas.chroma)break;if(Math.abs(s.chroma-t)<.4)break;Math.abs(s.chroma-t)yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.secondaryContainer,yn.secondary,15,"nearer",!1)}),yn.onSecondaryContainer=un.fromPalette({name:"on_secondary_container",palette:e=>e.secondaryPalette,tone:e=>fn(e)?un.foregroundTone(yn.secondaryContainer.tone(e),4.5):e.isDark?90:10,background:e=>yn.secondaryContainer,contrastCurve:new mn(4.5,7,11,21)}),yn.tertiary=un.fromPalette({name:"tertiary",palette:e=>e.tertiaryPalette,tone:e=>gn(e)?e.isDark?90:25:e.isDark?80:40,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(3,4.5,7,11),toneDeltaPair:e=>new vn(yn.tertiaryContainer,yn.tertiary,15,"nearer",!1)}),yn.onTertiary=un.fromPalette({name:"on_tertiary",palette:e=>e.tertiaryPalette,tone:e=>gn(e)?e.isDark?10:90:e.isDark?20:100,background:e=>yn.tertiary,contrastCurve:new mn(4.5,7,11,21)}),yn.tertiaryContainer=un.fromPalette({name:"tertiary_container",palette:e=>e.tertiaryPalette,tone:e=>{if(gn(e))return e.isDark?60:49;if(!fn(e))return e.isDark?30:90;const t=bn(e.tertiaryPalette.getHct(e.sourceColorHct.tone),e),i=e.tertiaryPalette.getHct(t);return hn.fixIfDisliked(i).tone},isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.tertiaryContainer,yn.tertiary,15,"nearer",!1)}),yn.onTertiaryContainer=un.fromPalette({name:"on_tertiary_container",palette:e=>e.tertiaryPalette,tone:e=>gn(e)?e.isDark?0:100:fn(e)?un.foregroundTone(yn.tertiaryContainer.tone(e),4.5):e.isDark?90:10,background:e=>yn.tertiaryContainer,contrastCurve:new mn(4.5,7,11,21)}),yn.error=un.fromPalette({name:"error",palette:e=>e.errorPalette,tone:e=>e.isDark?80:40,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(3,4.5,7,11),toneDeltaPair:e=>new vn(yn.errorContainer,yn.error,15,"nearer",!1)}),yn.onError=un.fromPalette({name:"on_error",palette:e=>e.errorPalette,tone:e=>e.isDark?20:100,background:e=>yn.error,contrastCurve:new mn(4.5,7,11,21)}),yn.errorContainer=un.fromPalette({name:"error_container",palette:e=>e.errorPalette,tone:e=>e.isDark?30:90,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.errorContainer,yn.error,15,"nearer",!1)}),yn.onErrorContainer=un.fromPalette({name:"on_error_container",palette:e=>e.errorPalette,tone:e=>e.isDark?90:10,background:e=>yn.errorContainer,contrastCurve:new mn(4.5,7,11,21)}),yn.primaryFixed=un.fromPalette({name:"primary_fixed",palette:e=>e.primaryPalette,tone:e=>gn(e)?40:90,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.primaryFixed,yn.primaryFixedDim,10,"lighter",!0)}),yn.primaryFixedDim=un.fromPalette({name:"primary_fixed_dim",palette:e=>e.primaryPalette,tone:e=>gn(e)?30:80,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.primaryFixed,yn.primaryFixedDim,10,"lighter",!0)}),yn.onPrimaryFixed=un.fromPalette({name:"on_primary_fixed",palette:e=>e.primaryPalette,tone:e=>gn(e)?100:10,background:e=>yn.primaryFixedDim,secondBackground:e=>yn.primaryFixed,contrastCurve:new mn(4.5,7,11,21)}),yn.onPrimaryFixedVariant=un.fromPalette({name:"on_primary_fixed_variant",palette:e=>e.primaryPalette,tone:e=>gn(e)?90:30,background:e=>yn.primaryFixedDim,secondBackground:e=>yn.primaryFixed,contrastCurve:new mn(3,4.5,7,11)}),yn.secondaryFixed=un.fromPalette({name:"secondary_fixed",palette:e=>e.secondaryPalette,tone:e=>gn(e)?80:90,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.secondaryFixed,yn.secondaryFixedDim,10,"lighter",!0)}),yn.secondaryFixedDim=un.fromPalette({name:"secondary_fixed_dim",palette:e=>e.secondaryPalette,tone:e=>gn(e)?70:80,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.secondaryFixed,yn.secondaryFixedDim,10,"lighter",!0)}),yn.onSecondaryFixed=un.fromPalette({name:"on_secondary_fixed",palette:e=>e.secondaryPalette,tone:e=>10,background:e=>yn.secondaryFixedDim,secondBackground:e=>yn.secondaryFixed,contrastCurve:new mn(4.5,7,11,21)}),yn.onSecondaryFixedVariant=un.fromPalette({name:"on_secondary_fixed_variant",palette:e=>e.secondaryPalette,tone:e=>gn(e)?25:30,background:e=>yn.secondaryFixedDim,secondBackground:e=>yn.secondaryFixed,contrastCurve:new mn(3,4.5,7,11)}),yn.tertiaryFixed=un.fromPalette({name:"tertiary_fixed",palette:e=>e.tertiaryPalette,tone:e=>gn(e)?40:90,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.tertiaryFixed,yn.tertiaryFixedDim,10,"lighter",!0)}),yn.tertiaryFixedDim=un.fromPalette({name:"tertiary_fixed_dim",palette:e=>e.tertiaryPalette,tone:e=>gn(e)?30:80,isBackground:!0,background:e=>yn.highestSurface(e),contrastCurve:new mn(1,1,3,7),toneDeltaPair:e=>new vn(yn.tertiaryFixed,yn.tertiaryFixedDim,10,"lighter",!0)}),yn.onTertiaryFixed=un.fromPalette({name:"on_tertiary_fixed",palette:e=>e.tertiaryPalette,tone:e=>gn(e)?100:10,background:e=>yn.tertiaryFixedDim,secondBackground:e=>yn.tertiaryFixed,contrastCurve:new mn(4.5,7,11,21)}),yn.onTertiaryFixedVariant=un.fromPalette({name:"on_tertiary_fixed_variant",palette:e=>e.tertiaryPalette,tone:e=>gn(e)?90:30,background:e=>yn.tertiaryFixedDim,secondBackground:e=>yn.tertiaryFixed,contrastCurve:new mn(3,4.5,7,11)});class wn{static fromInt(e){const t=ln.fromInt(e);return wn.fromHct(t)}static fromHct(e){return new wn(e.hue,e.chroma,e)}static fromHueAndChroma(e,t){return new wn(e,t,wn.createKeyColor(e,t))}constructor(e,t,i){this.hue=e,this.chroma=t,this.keyColor=i,this.cache=new Map}static createKeyColor(e,t){let i=ln.from(e,t,50),o=Math.abs(i.chroma-t);for(let r=1;r<50;r+=1){if(Math.round(t)===Math.round(i.chroma))return i;const n=ln.from(e,t,50+r),s=Math.abs(n.chroma-t);s0&&(c=Math.min(c,t.length));const d=new Array;for(let e=0;e0)for(let e=0;e=4*a)continue;const t=s.distance(i,d[e]);t3&&(t++,u[e]=h)}}if(0===t&&0!==e)break;const i=new Array(c).fill(0),o=new Array(c).fill(0),n=new Array(c).fill(0);for(let e=0;e>24&255)<255||t.set(o,(t.get(o)??0)+1)}return t}}const Sn=33,Tn=35937,En="red",In="green",An="blue";class Pn{constructor(e=[],t=[],i=[],o=[],r=[],n=[]){this.weights=e,this.momentsR=t,this.momentsG=i,this.momentsB=o,this.moments=r,this.cubes=n}quantize(e,t){this.constructHistogram(e),this.computeMoments();const i=this.createBoxes(t);return this.createResult(i.resultCount)}constructHistogram(e){this.weights=Array.from({length:Tn}).fill(0),this.momentsR=Array.from({length:Tn}).fill(0),this.momentsG=Array.from({length:Tn}).fill(0),this.momentsB=Array.from({length:Tn}).fill(0),this.moments=Array.from({length:Tn}).fill(0);const t=Rn.quantize(e);for(const[e,i]of t.entries()){const t=Gr(e),o=Wr(e),r=Yr(e),n=3,s=1+(t>>n),a=1+(o>>n),l=1+(r>>n),c=this.getIndex(s,a,l);this.weights[c]=(this.weights[c]??0)+i,this.momentsR[c]+=i*t,this.momentsG[c]+=i*o,this.momentsB[c]+=i*r,this.moments[c]+=i*(t*t+o*o+r*r)}}computeMoments(){for(let e=1;enew Mn));const t=Array.from({length:e}).fill(0);this.cubes[0].r0=0,this.cubes[0].g0=0,this.cubes[0].b0=0,this.cubes[0].r1=32,this.cubes[0].g1=32,this.cubes[0].b1=32;let i=e,o=0;for(let r=1;r1?this.variance(this.cubes[o]):0,t[r]=this.cubes[r].vol>1?this.variance(this.cubes[r]):0):(t[o]=0,r--),o=0;let e=t[0];for(let i=1;i<=r;i++)t[i]>e&&(e=t[i],o=i);if(e<=0){i=r+1;break}}return new Dn(e,i)}createResult(e){const t=[];for(let i=0;i0){const i=255<<24|(255&Math.round(this.volume(e,this.momentsR)/o))<<16|(255&Math.round(this.volume(e,this.momentsG)/o))<<8|255&Math.round(this.volume(e,this.momentsB)/o);t.push(i)}}return t}variance(e){const t=this.volume(e,this.momentsR),i=this.volume(e,this.momentsG),o=this.volume(e,this.momentsB);return this.moments[this.getIndex(e.r1,e.g1,e.b1)]-this.moments[this.getIndex(e.r1,e.g1,e.b0)]-this.moments[this.getIndex(e.r1,e.g0,e.b1)]+this.moments[this.getIndex(e.r1,e.g0,e.b0)]-this.moments[this.getIndex(e.r0,e.g1,e.b1)]+this.moments[this.getIndex(e.r0,e.g1,e.b0)]+this.moments[this.getIndex(e.r0,e.g0,e.b1)]-this.moments[this.getIndex(e.r0,e.g0,e.b0)]-(t*t+i*i+o*o)/this.volume(e,this.weights)}cut(e,t){const i=this.volume(e,this.momentsR),o=this.volume(e,this.momentsG),r=this.volume(e,this.momentsB),n=this.volume(e,this.weights),s=this.maximize(e,En,e.r0+1,e.r1,i,o,r,n),a=this.maximize(e,In,e.g0+1,e.g1,i,o,r,n),l=this.maximize(e,An,e.b0+1,e.b1,i,o,r,n);let c;const d=s.maximum,h=a.maximum,u=l.maximum;if(d>=h&&d>=u){if(s.cutLocation<0)return!1;c=En}else c=h>=d&&h>=u?In:An;switch(t.r1=e.r1,t.g1=e.g1,t.b1=e.b1,c){case En:e.r1=s.cutLocation,t.r0=e.r1,t.g0=e.g0,t.b0=e.b0;break;case In:e.g1=a.cutLocation,t.r0=e.r0,t.g0=e.g1,t.b0=e.b0;break;case An:e.b1=l.cutLocation,t.r0=e.r0,t.g0=e.g0,t.b0=e.b1;break;default:throw new Error("unexpected direction "+c)}return e.vol=(e.r1-e.r0)*(e.g1-e.g0)*(e.b1-e.b0),t.vol=(t.r1-t.r0)*(t.g1-t.g0)*(t.b1-t.b0),!0}maximize(e,t,i,o,r,n,s,a){const l=this.bottom(e,t,this.momentsR),c=this.bottom(e,t,this.momentsG),d=this.bottom(e,t,this.momentsB),h=this.bottom(e,t,this.weights);let u=0,p=-1,m=0,v=0,f=0,g=0;for(let b=i;bu&&(u=y,p=b))}return new Bn(p,u)}volume(e,t){return t[this.getIndex(e.r1,e.g1,e.b1)]-t[this.getIndex(e.r1,e.g1,e.b0)]-t[this.getIndex(e.r1,e.g0,e.b1)]+t[this.getIndex(e.r1,e.g0,e.b0)]-t[this.getIndex(e.r0,e.g1,e.b1)]+t[this.getIndex(e.r0,e.g1,e.b0)]+t[this.getIndex(e.r0,e.g0,e.b1)]-t[this.getIndex(e.r0,e.g0,e.b0)]}bottom(e,t,i){switch(t){case En:return-i[this.getIndex(e.r0,e.g1,e.b1)]+i[this.getIndex(e.r0,e.g1,e.b0)]+i[this.getIndex(e.r0,e.g0,e.b1)]-i[this.getIndex(e.r0,e.g0,e.b0)];case In:return-i[this.getIndex(e.r1,e.g0,e.b1)]+i[this.getIndex(e.r1,e.g0,e.b0)]+i[this.getIndex(e.r0,e.g0,e.b1)]-i[this.getIndex(e.r0,e.g0,e.b0)];case An:return-i[this.getIndex(e.r1,e.g1,e.b0)]+i[this.getIndex(e.r1,e.g0,e.b0)]+i[this.getIndex(e.r0,e.g1,e.b0)]-i[this.getIndex(e.r0,e.g0,e.b0)];default:throw new Error("unexpected direction $direction")}}top(e,t,i,o){switch(t){case En:return o[this.getIndex(i,e.g1,e.b1)]-o[this.getIndex(i,e.g1,e.b0)]-o[this.getIndex(i,e.g0,e.b1)]+o[this.getIndex(i,e.g0,e.b0)];case In:return o[this.getIndex(e.r1,i,e.b1)]-o[this.getIndex(e.r1,i,e.b0)]-o[this.getIndex(e.r0,i,e.b1)]+o[this.getIndex(e.r0,i,e.b0)];case An:return o[this.getIndex(e.r1,e.g1,i)]-o[this.getIndex(e.r1,e.g0,i)]-o[this.getIndex(e.r0,e.g1,i)]+o[this.getIndex(e.r0,e.g0,i)];default:throw new Error("unexpected direction $direction")}}getIndex(e,t,i){return(e<<10)+(e<<6)+e+(t<<5)+t+i}}class Mn{constructor(e=0,t=0,i=0,o=0,r=0,n=0,s=0){this.r0=e,this.r1=t,this.g0=i,this.g1=o,this.b0=r,this.b1=n,this.vol=s}}class Dn{constructor(e,t){this.requestedCount=e,this.resultCount=t}}class Bn{constructor(e,t){this.cutLocation=e,this.maximum=t}}class Ln{static quantize(e,t){const i=(new Pn).quantize(e,t);return xn.quantize(e,i,t)}}class _n{get primary(){return this.props.primary}get onPrimary(){return this.props.onPrimary}get primaryContainer(){return this.props.primaryContainer}get onPrimaryContainer(){return this.props.onPrimaryContainer}get secondary(){return this.props.secondary}get onSecondary(){return this.props.onSecondary}get secondaryContainer(){return this.props.secondaryContainer}get onSecondaryContainer(){return this.props.onSecondaryContainer}get tertiary(){return this.props.tertiary}get onTertiary(){return this.props.onTertiary}get tertiaryContainer(){return this.props.tertiaryContainer}get onTertiaryContainer(){return this.props.onTertiaryContainer}get error(){return this.props.error}get onError(){return this.props.onError}get errorContainer(){return this.props.errorContainer}get onErrorContainer(){return this.props.onErrorContainer}get background(){return this.props.background}get onBackground(){return this.props.onBackground}get surface(){return this.props.surface}get onSurface(){return this.props.onSurface}get surfaceVariant(){return this.props.surfaceVariant}get onSurfaceVariant(){return this.props.onSurfaceVariant}get outline(){return this.props.outline}get outlineVariant(){return this.props.outlineVariant}get shadow(){return this.props.shadow}get scrim(){return this.props.scrim}get inverseSurface(){return this.props.inverseSurface}get inverseOnSurface(){return this.props.inverseOnSurface}get inversePrimary(){return this.props.inversePrimary}static light(e){return _n.lightFromCorePalette(kn.of(e))}static dark(e){return _n.darkFromCorePalette(kn.of(e))}static lightContent(e){return _n.lightFromCorePalette(kn.contentOf(e))}static darkContent(e){return _n.darkFromCorePalette(kn.contentOf(e))}static lightFromCorePalette(e){return new _n({primary:e.a1.tone(40),onPrimary:e.a1.tone(100),primaryContainer:e.a1.tone(90),onPrimaryContainer:e.a1.tone(10),secondary:e.a2.tone(40),onSecondary:e.a2.tone(100),secondaryContainer:e.a2.tone(90),onSecondaryContainer:e.a2.tone(10),tertiary:e.a3.tone(40),onTertiary:e.a3.tone(100),tertiaryContainer:e.a3.tone(90),onTertiaryContainer:e.a3.tone(10),error:e.error.tone(40),onError:e.error.tone(100),errorContainer:e.error.tone(90),onErrorContainer:e.error.tone(10),background:e.n1.tone(99),onBackground:e.n1.tone(10),surface:e.n1.tone(99),onSurface:e.n1.tone(10),surfaceVariant:e.n2.tone(90),onSurfaceVariant:e.n2.tone(30),outline:e.n2.tone(50),outlineVariant:e.n2.tone(80),shadow:e.n1.tone(0),scrim:e.n1.tone(0),inverseSurface:e.n1.tone(20),inverseOnSurface:e.n1.tone(95),inversePrimary:e.a1.tone(80)})}static darkFromCorePalette(e){return new _n({primary:e.a1.tone(80),onPrimary:e.a1.tone(20),primaryContainer:e.a1.tone(30),onPrimaryContainer:e.a1.tone(90),secondary:e.a2.tone(80),onSecondary:e.a2.tone(20),secondaryContainer:e.a2.tone(30),onSecondaryContainer:e.a2.tone(90),tertiary:e.a3.tone(80),onTertiary:e.a3.tone(20),tertiaryContainer:e.a3.tone(30),onTertiaryContainer:e.a3.tone(90),error:e.error.tone(80),onError:e.error.tone(20),errorContainer:e.error.tone(30),onErrorContainer:e.error.tone(80),background:e.n1.tone(10),onBackground:e.n1.tone(90),surface:e.n1.tone(10),onSurface:e.n1.tone(90),surfaceVariant:e.n2.tone(30),onSurfaceVariant:e.n2.tone(80),outline:e.n2.tone(60),outlineVariant:e.n2.tone(30),shadow:e.n1.tone(0),scrim:e.n1.tone(0),inverseSurface:e.n1.tone(90),inverseOnSurface:e.n1.tone(20),inversePrimary:e.a1.tone(40)})}constructor(e){this.props=e}toJSON(){return{...this.props}}}const On={desired:4,fallbackColorARGB:4282549748,filter:!0};function zn(e,t){return e.score>t.score?-1:e.score=15;e--){d.length=0;for(const{hct:t}of c){if(d.find((i=>Nr(t.hue,i.hue)=i)break}if(d.length>=i)break}const h=[];0===d.length&&h.push(o);for(const e of d)h.push(e.toInt());return h}}function Nn(e){const t=3===(e=e.replace("#","")).length,i=6===e.length,o=8===e.length;if(!t&&!i&&!o)throw new Error("unexpected hex "+e);let r=0,n=0,s=0;return t?(r=Vn(e.slice(0,1).repeat(2)),n=Vn(e.slice(1,2).repeat(2)),s=Vn(e.slice(2,3).repeat(2))):i?(r=Vn(e.slice(0,2)),n=Vn(e.slice(2,4)),s=Vn(e.slice(4,6))):o&&(r=Vn(e.slice(2,4)),n=Vn(e.slice(4,6)),s=Vn(e.slice(6,8))),(255<<24|(255&r)<<16|(255&n)<<8|255&s)>>>0}function Vn(e){return parseInt(e,16)}async function Hn(e){const t=await new Promise(((t,i)=>{const o=document.createElement("canvas"),r=o.getContext("2d");if(!r)return void i(new Error("Could not get canvas context"));const n=()=>{o.width=e.width,o.height=e.height,r.drawImage(e,0,0);let i=[0,0,e.width,e.height];const n=e.dataset.area;n&&/^\d+(\s*,\s*\d+){3}$/.test(n)&&(i=n.split(/\s*,\s*/).map((e=>parseInt(e,10))));const[s,a,l,c]=i;t(r.getImageData(s,a,l,c).data)};e.complete?n():e.onload=n})),i=[];for(let e=0;e{const t=P(e);let i=t.get().map((e=>Array.from(e.classList))).flat();i=D(i).filter((e=>e.startsWith(qn))),t.removeClass(i.join(" "));const o=i.filter((e=>0===P(`.${e}`).length));P(o.map((e=>`#${e}`)).join(",")).remove()},Wn=(e,t)=>{const i=r(),o=P((null==t?void 0:t.target)||i.documentElement),n={light:_n.light(e).toJSON(),dark:_n.dark(e).toJSON()},s=kn.of(e);Object.assign(n.light,{"surface-dim":s.n1.tone(87),"surface-bright":s.n1.tone(98),"surface-container-lowest":s.n1.tone(100),"surface-container-low":s.n1.tone(96),"surface-container":s.n1.tone(94),"surface-container-high":s.n1.tone(92),"surface-container-highest":s.n1.tone(90),"surface-tint-color":n.light.primary}),Object.assign(n.dark,{"surface-dim":s.n1.tone(6),"surface-bright":s.n1.tone(24),"surface-container-lowest":s.n1.tone(4),"surface-container-low":s.n1.tone(10),"surface-container":s.n1.tone(12),"surface-container-high":s.n1.tone(17),"surface-container-highest":s.n1.tone(22),"surface-tint-color":n.dark.primary}),((null==t?void 0:t.customColors)||[]).map((t=>{const i=k(t.name),o=function(e,t){let i=t.value;const o=i,r=e;t.blend&&(i=cn.harmonize(o,r));const n=kn.of(i).a1;return{color:t,value:i,light:{color:n.tone(40),onColor:n.tone(100),colorContainer:n.tone(90),onColorContainer:n.tone(10)},dark:{color:n.tone(80),onColor:n.tone(20),colorContainer:n.tone(30),onColorContainer:n.tone(90)}}}(e,{name:i,value:Nn(t.value),blend:!0});Un.forEach((e=>{n[e][i]=o[e].color,n[e][`on-${i}`]=o[e].onColor,n[e][`${i}-container`]=o[e].colorContainer,n[e][`on-${i}-container`]=o[e].onColorContainer}))}));const a=(e,t)=>Object.entries(n[e]).map((([e,i])=>t(k(e),(e=>[Gr(e),Wr(e),Yr(e)].join(", "))(i)))).join(""),l=qn+`${e}-${jn++}`,c=`.${l} {\n ${a("light",((e,t)=>`--mdui-color-${e}-light: ${t};`))}\n ${a("dark",((e,t)=>`--mdui-color-${e}-dark: ${t};`))}\n ${a("light",(e=>`--mdui-color-${e}: var(--mdui-color-${e}-light);`))}\n\n color: rgb(var(--mdui-color-on-background));\n background-color: rgb(var(--mdui-color-background));\n}\n\n.mdui-theme-dark .${l},\n.mdui-theme-dark.${l} {\n ${a("dark",(e=>`--mdui-color-${e}: var(--mdui-color-${e}-dark);`))}\n}\n\n@media (prefers-color-scheme: dark) {\n .mdui-theme-auto .${l},\n .mdui-theme-auto.${l} {\n ${a("dark",(e=>`--mdui-color-${e}: var(--mdui-color-${e}-dark);`))}\n }\n}`;Gn(o),P(i.head).append(``),o.addClass(l)},Yn="mdui.functions.snackbar.";let Xn;e.$=P,e.alert=e=>{const t=Object.assign({},Dr,e),i=["headline","description","icon","closeOnEsc","closeOnOverlayClick","queue","onOpen","onOpened","onClose","onClosed","onOverlayClick"];return new Promise(((e,o)=>{let r=!1;const n=Mr({...Object.fromEntries(i.filter((e=>!u(t[e]))).map((e=>[e,t[e]]))),actions:[{text:t.confirmText,onClick:e=>{const i=t.onConfirm.call(e,e);return Rr(i)?i.then((()=>{r=!0})):!1!==i&&(r=!0),i}}]});P(n).on("close",(()=>{r?e():o()}))}))},e.breakpoint=zo,e.confirm=e=>{const t=Object.assign({},Br,e),i=["headline","description","icon","closeOnEsc","closeOnOverlayClick","stackedActions","queue","onOpen","onOpened","onClose","onClosed","onOverlayClick"];return new Promise(((e,o)=>{let r=!1;const n=Mr({...Object.fromEntries(i.filter((e=>!u(t[e]))).map((e=>[e,t[e]]))),actions:[{text:t.cancelText,onClick:e=>t.onCancel.call(e,e)},{text:t.confirmText,onClick:e=>{const i=t.onConfirm.call(e,e);return Rr(i)?i.then((()=>{r=!0})):!1!==i&&(r=!0),i}}]});P(n).on("close",(()=>{r?e():o()}))}))},e.dialog=Mr,e.getColorFromImage=async e=>{const t=P(e);return function(e){const t=Gr(e),i=Wr(e),o=Yr(e),r=[t.toString(16),i.toString(16),o.toString(16)];for(const[e,t]of r.entries())1===t.length&&(r[e]="0"+t);return"#"+r.join("")}(await Hn(t[0]))},e.getTheme=(e=document.documentElement)=>{var t,i;const o=P(e)[0],r=["light","dark","auto"],n="mdui-theme-";return null!==(i=null===(t=Array.from(o.classList).find((e=>r.map((e=>n+e)).includes(e))))||void 0===t?void 0:t.slice(11))&&void 0!==i?i:"light"},e.observeResize=Ci,e.prompt=t=>{const i=Object.assign({},Kn,t),o=["headline","description","icon","closeOnEsc","closeOnOverlayClick","stackedActions","queue","onOpen","onOpened","onClose","onClosed","onOverlayClick"],r=new e.TextField;return Object.entries(i.textFieldOptions).forEach((([e,t])=>{r[e]=t})),new Promise(((e,t)=>{let n=!1;const s=Mr({...Object.fromEntries(o.filter((e=>!u(i[e]))).map((e=>[e,i[e]]))),body:r,actions:[{text:i.cancelText,onClick:e=>i.onCancel.call(e,r.value,e)},{text:i.confirmText,onClick:e=>{if(r.setCustomValidity(""),!r.reportValidity())return!1;const t=i.validator.call(r,r.value);return h(t)&&!t?(r.setCustomValidity(" "),!1):c(t)?(r.setCustomValidity(t),!1):Rr(t)?new Promise(((e,i)=>{t.then(e).catch((e=>{r.setCustomValidity(e),i(e)}))})):(()=>{const t=i.onConfirm.call(e,r.value,e);return Rr(t)?t.then((()=>{n=!0})):!1!==t&&(n=!0),t})()}}]});P(s).on("close",(()=>{n?e(r.value):t()}))}))},e.removeColorScheme=(e=document.documentElement)=>{Gn(e)},e.setColorScheme=(e,t)=>{const i=Nn(e);Wn(i,t)},e.setTheme=(e,t=document.documentElement)=>{const i=P(t),o="mdui-theme-";i.removeClass(["light","dark","auto"].map((e=>o+e)).join(" ")).addClass(o+e)},e.snackbar=t=>{const i=new e.Snackbar,o=P(i);return Object.entries(t).forEach((([e,r])=>{if("message"===e)i.innerHTML=r;else if(["onClick","onActionClick","onOpen","onOpened","onClose","onClosed"].includes(e)){const n=k(e.slice(2));o.on(n,(()=>{var o;if("onActionClick"===e){const e=(null!==(o=t.onActionClick)&&void 0!==o?o:x).call(i,i);Rr(e)?(i.actionLoading=!0,e.then((()=>{i.open=!1})).finally((()=>{i.actionLoading=!1}))):!1!==e&&(i.open=!1)}else r.call(i,i)}))}else i[e]=r})),o.appendTo("body").on("closed",(()=>{o.remove(),t.queue&&(Xn=void 0,Er(Yn+t.queue))})),t.queue?Xn?Tr(Yn+t.queue,(()=>{i.open=!0,Xn=i})):(setTimeout((()=>{i.open=!0})),Xn=i):setTimeout((()=>{i.open=!0})),i},e.throttle=(e,t=0)=>{const i=s();let o,r;return function(...n){return void 0===o&&(o=i.setTimeout((()=>{r=e.apply(this,n),o=void 0}),t)),r}}})); diff --git a/web/templates/default/close.html b/web/templates/default/close.html new file mode 100755 index 000000000..542ad5e26 --- /dev/null +++ b/web/templates/default/close.html @@ -0,0 +1,98 @@ + + + + + + + 抱歉,管理员已关闭面板 + + + + + + +
                                            +
                                            +
                                            +
                                            + power_settings_new +
                                            +
                                            +

                                            管理员已关闭面板

                                            +

                                            当前面板处于关闭状态,只有在执行开启命令后才能继续访问。这通常用于维护、迁移或临时下线。

                                            +
                                            + 面板关闭中 + 等待管理员开启 +
                                            +
                                            +
                                            +
                                            +
                                            + 开启命令 + 在 SSH 终端中输入下面命令即可重新开启面板。 +
                                            +
                                            mw open
                                            +
                                            如果你不是管理员,请联系面板所有者确认当前维护状态。
                                            +
                                            +
                                            +
                                            + + diff --git a/web/templates/default/crontab.html b/web/templates/default/crontab.html new file mode 100755 index 000000000..39941066d --- /dev/null +++ b/web/templates/default/crontab.html @@ -0,0 +1,176 @@ +{% extends "layout.html" %} + +{% block content %} + +
                                            + +
                                            +
                                            计划任务
                                            +
                                            按天、按小时或按星期安排脚本、备份和维护任务,把自动化集中到一个清晰的工作台。
                                            +
                                            +
                                            + Shell / 备份 / 日志切割 / URL +
                                            +
                                            +
                                            +
                                            +
                                            + 首页/计划任务 +
                                            +
                                            +
                                            +
                                            +

                                            添加计划任务

                                            +
                                            +
                                            +
                                            + 任务类型 + + *任务类型包含以下部分:Shell脚本、备份网站、备份数据库、备份目录、日志切割、释放内存、访问URL +
                                            +
                                            + 任务名称 +
                                            +
                                            +
                                            + 执行周期 + +
                                            + +
                                            + + 小时 +
                                            +
                                            + + 分钟 +
                                            +
                                            +
                                            +
                                            + 脚本内容 +
                                            + +
                                            +
                                            + +
                                            +
                                            添加任务
                                            +
                                            +
                                              +
                                            • 当添加完备份任务,应该手动运行一次,并检查备份包是否完整
                                            • +
                                            • 磁盘容量不够、数据库密码错误、网络不稳定等原因,可能导致数据备份不完整
                                            • +
                                            +
                                            + + + + + + + + + + + + + + + + + +
                                            +
                                            +
                                            +

                                            任务列表

                                            + +
                                            +
                                            +
                                            + + + + + + + + + + + + + + +
                                            任务名称状态周期执行时机保存数量备份到添加时间操作
                                            + + + + + + + +{% endblock %} diff --git a/web/templates/default/files.html b/web/templates/default/files.html new file mode 100755 index 000000000..c5c104c90 --- /dev/null +++ b/web/templates/default/files.html @@ -0,0 +1,132 @@ +{% extends "layout.html" %} + +{% block content %} +
                                            + +
                                            +
                                            文件管理
                                            +
                                            +
                                            +
                                            + 返回首页 +
                                            +
                                            + + +
                                            +
                                            +
                                            +
                                            + + +
                                            + 当前位置 + +
                                            + +
                                            + +
                                            +
                                            +
                                            + 上传 + 远程下载 + + + +
                                            +
                                            + + +
                                            +
                                            +
                                            + +
                                            + +
                                            +
                                            +
                                            + + + + + + +{% endblock %} diff --git a/web/templates/default/firewall.html b/web/templates/default/firewall.html new file mode 100755 index 000000000..a787422c7 --- /dev/null +++ b/web/templates/default/firewall.html @@ -0,0 +1,103 @@ +{% extends "layout.html" %} + +{% block content %} +
                                            + +
                                            +
                                            系统安全
                                            +
                                            SSH、禁 ping、防火墙与端口放行集中管理,适合快速收紧或排查访问问题。
                                            +
                                            +
                                            + 安全入口 / 防火墙 / SSH / 日志搜索 +
                                            +
                                            +
                                            +
                                            +
                                            + 首页/系统安全 +
                                            + +
                                            +
                                            +
                                            + +
                                            +
                                            + SSH管理 +
                                            +
                                            +
                                            +
                                            + +
                                            + SSH端口: + +
                                            +
                                            + +
                                            +
                                            +
                                            +
                                            + 启用禁ping +
                                            + +
                                            +
                                            +
                                            + +
                                            +
                                            + 启用防火墙 +
                                            + + +
                                            +
                                            +
                                            + +
                                            +
                                            + +
                                            +
                                            +
                                            +
                                            +

                                            防火墙

                                            +
                                            + +
                                            +
                                            + + + + + + 说明: 支持放行端口范围,如: 3000:3500 +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            + +
                                            +
                                            + +{% endblock %} diff --git a/web/templates/default/index.html b/web/templates/default/index.html new file mode 100755 index 000000000..6c41484ea --- /dev/null +++ b/web/templates/default/index.html @@ -0,0 +1,134 @@ +{% extends "layout.html" %} {% block content %} +
                                            + +
                                            +
                                            系统
                                            +
                                            正在获取中...
                                            +
                                            +
                                            +
                                            + {{config.version}} + 更新 + 重启 + 关机 + PowerLinux 3 会员 +
                                            +
                                            + +
                                            +
                                            + +
                                            + + +
                                            +
                                            状态
                                            +
                                            +
                                            +
                                              +
                                            • +

                                              负载状态?

                                              +
                                              + +
                                              +
                                              0%
                                              +
                                              获取中...
                                              +
                                            • +
                                            • +

                                              CPU使用率

                                              +
                                              + +
                                              +
                                              0%
                                              +
                                              获取中...
                                              +
                                            • +
                                            • +

                                              内存使用率

                                              +
                                              + +
                                              +
                                              0%
                                              +
                                              获取中...
                                              + 释放内存 +
                                            • +
                                            +
                                            +
                                            + + +
                                            +
                                            概览
                                            +
                                            +
                                            + +
                                            +
                                            + +
                                            + +
                                            +
                                            软件
                                            +
                                            +
                                            +
                                            +
                                            + +
                                            +
                                            +
                                            +
                                            +
                                            + + +
                                            +
                                            便签
                                            +
                                            +
                                            +
                                            +
                                            + +
                                            + 保存 + 取消 +
                                            +
                                            +
                                            + +
                                            +
                                            +
                                            +
                                            + + + + + +{% endblock %} diff --git a/web/templates/default/layout.html b/web/templates/default/layout.html new file mode 100755 index 000000000..a04f2aa8a --- /dev/null +++ b/web/templates/default/layout.html @@ -0,0 +1,316 @@ + + + + + + + + +{{data['title']}} + + + + + + + + + + + + +{% for menu in data['hook_menu'] %} +{% if menu['css_path'] and data['hook_tag'] == menu['name'] %} + + +{% endif %} +{% endfor %} + + +{% for menu in data['hook_global_static'] %} +{% if menu['css_path'] %} + +{% endif %} +{% endfor %} + + + +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            {{data['title']}}
                                            +
                                            {{data['ip']}}
                                            +
                                            +
                                            +
                                            + +
                                            +
                                            +
                                            + + +
                                            +
                                            +
                                            +
                                            +
                                            + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% block content %}{% endblock %} +
                                            +
                                            + + + +
                                            +
                                            +
                                            + + + + + + + + + + + + + + +
                                            商家优惠码
                                            + + + + PowerLinux
                                            +
                                            + + + + + + + + + + + + + + +{% for menu in data['hook_menu'] %} +{% if menu['js_path'] and data['hook_tag'] == menu['name'] %} + +{% endif %} +{% endfor %} + + +{% for menu in data['hook_global_static'] %} +{% if menu['js_path'] %} + +{% endif %} +{% endfor %} + + + + + + + + + diff --git a/web/templates/default/login.html b/web/templates/default/login.html new file mode 100755 index 000000000..be2947630 --- /dev/null +++ b/web/templates/default/login.html @@ -0,0 +1,200 @@ + + + + + + + + + +{{data['title']}} + + + + + + + + + +
                                            + +
                                            + + + + + + + + + + + diff --git a/web/templates/default/logs.html b/web/templates/default/logs.html new file mode 100644 index 000000000..7f80a6422 --- /dev/null +++ b/web/templates/default/logs.html @@ -0,0 +1,117 @@ +{% extends "layout.html" %} +{% block content %} + +
                                            + +
                                            +
                                            日志中心
                                            +
                                            面板日志与日志审计分开查看,便于回溯操作和检查系统行为。
                                            +
                                            +
                                            + 操作日志 / 登录审计 / 事件回放 +
                                            +
                                            + +
                                            + +
                                            +
                                            +
                                            面板日志
                                            +
                                            日志审计
                                            +
                                            +
                                            +
                                            + + +
                                            + +
                                            +
                                            +
                                            +
                                            + + +
                                            +
                                            +
                                            + + + + + + + + +
                                            编号操作类型详情操作时间
                                            +
                                            +
                                            +
                                            +
                                            1共1条
                                            +
                                            +
                                            + + +
                                            +
                                            +
                                            + +
                                            +
                                            +
                                            +
                                            +
                                            + +
                                            +
                                            + +
                                            +
                                            +
                                            + + + + + + + + + + + + +
                                            用户来源端口时间
                                            +
                                            +
                                            +
                                            +
                                            +
                                            第 1 页
                                            +
                                            +
                                            +
                                            +
                                            + +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            + + +{% endblock %} diff --git a/web/templates/default/monitor.html b/web/templates/default/monitor.html new file mode 100755 index 000000000..227796e16 --- /dev/null +++ b/web/templates/default/monitor.html @@ -0,0 +1,186 @@ +{% extends "layout.html" %} + +{% block content %} +
                                            + +
                                            +
                                            服务器监控中枢
                                            +
                                            从趋势、事件与容量三条线观察系统运行状态
                                            +
                                            +
                                            + 刷新视图 + 返回首页 +
                                            +
                                            + +
                                            + + +
                                            +
                                            +
                                            +
                                            趋势图表
                                            +
                                            展示资源随时间变化的详细曲线
                                            +
                                            +
                                            +
                                            + +
                                            +
                                            +
                                            系统负载趋势
                                            +
                                            1 / 5 / 15 分钟负载随时间变化
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            + + +
                                            +
                                            +
                                            CPU 使用率
                                            +
                                            用户态 / 内核态占比趋势
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            + + +
                                            +
                                            +
                                            内存占用分布
                                            +
                                            应用 / 缓冲 / 缓存走势
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            + + +
                                            +
                                            +
                                            磁盘 IO 速率
                                            +
                                            读取 / 写入吞吐与延迟变化
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            + + +
                                            +
                                            +
                                            网络 IO 变化
                                            +
                                            流入 / 流出带宽随时间变化
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            + +{% endblock %} diff --git a/web/templates/default/path.html b/web/templates/default/path.html new file mode 100755 index 000000000..c37c651e9 --- /dev/null +++ b/web/templates/default/path.html @@ -0,0 +1,156 @@ + + + + + + + + 安全入口校验失败 + + + + + + +
                                            +
                                            +
                                            +
                                            + lock +
                                            +
                                            +

                                            请使用正确的入口登录面板

                                            +

                                            当前面板启用了安全入口,必须通过正确的路径访问。这个保护层会在入口不正确时拦住请求,避免面板特征直接暴露。

                                            +
                                            + 安全入口校验 + 登录前保护 + 需要 SSH 处理 +
                                            +
                                            +
                                            +
                                            +
                                            + 错误原因 + 新安装的面板会随机生成一个 8 位左右的安全入口,也可能是管理员在面板设置里手动修改过入口。 +
                                            +
                                            + 解决方法 + 在 SSH 终端里执行下面任意一条命令,先查看当前入口,或者直接关闭安全入口。 +
                                            +
                                            mw default
                                            +mw close_admin_path
                                            +
                                            关闭安全入口会直接暴露登录地址,请确认这是你想要的行为后再执行。
                                            +
                                            +
                                            +
                                            + + diff --git a/web/templates/default/plugin_menu.html b/web/templates/default/plugin_menu.html new file mode 100644 index 000000000..647c28616 --- /dev/null +++ b/web/templates/default/plugin_menu.html @@ -0,0 +1,4 @@ +{% extends "layout.html" %} +{% block content %} +{{data['plugin_content']|safe }} +{% endblock %} \ No newline at end of file diff --git a/web/templates/default/setting.html b/web/templates/default/setting.html new file mode 100755 index 000000000..9923abe2d --- /dev/null +++ b/web/templates/default/setting.html @@ -0,0 +1,268 @@ +{% extends "layout.html" %} +{% block content %} +
                                            + +
                                            +
                                            面板设置
                                            +
                                            主题、背景、外观、安全和通知都可以在这里统一调整。
                                            +
                                            +
                                            + 外观 / 安全 / 通知 / 基础配置 +
                                            +
                                            +
                                            +
                                            +
                                            首页/面板设置
                                            +
                                            +
                                            +
                                            +
                                            + 关闭面板 +
                                            + + +
                                            +
                                            +
                                            + 开发模式 +
                                            + + +
                                            +
                                            + +
                                            + 监听IPv6 +
                                            + + +
                                            +
                                            +
                                            +
                                            +
                                            +

                                            外观

                                            +
                                            +

                                            + 主题配色 + + + 恢复默认 + + 选择主色后自动生成全局配色,MDUI 动态主题将应用到所有页面。 +

                                            +

                                            + 主题模式 + + + + + + + + 支持浅色、深色和自动跟随系统切换。 +

                                            +

                                            + 背景图 + + + + + + + + + 支持本地上传和外链图片,背景会自动叠加柔化层保证内容可读。 + 当前:默认背景 +

                                            +

                                            + 背景图透明度 + + + 72% + + 数值越高,背景越淡,页面内容越容易看清。 +

                                            +
                                            + 推荐配色 +
                                            + 紫罗兰 + 清澈蓝 + 青绿 + 琥珀橙 + 石榴红 + 深空灰 +
                                            +
                                            +

                                            + 底栏文字 + + + 显示在页面底部的文字,可自定义。 +

                                            +
                                            +
                                            +
                                            +

                                            设置

                                            +
                                            +

                                            为了提高安全,修改面板密码!

                                            +
                                            +
                                            +
                                            +

                                            + 别名 + + + 面板名称 +

                                            + +

                                            + 服务器IP + + + 默认为外网IP,若您在本地虚拟机测试,请填写虚拟机内网IP! +

                                            + +

                                            + 面板端口 + + + 建议端口范围7200 - 65535 +

                                            + +

                                            + 安全入口 + + + 面板管理入口,设置后只能通过指定安全入口登录面板,如: /abc +

                                            + +

                                            + 默认建站目录 + + + 新创建的站点,默认将保存到该目录的下级目录! +

                                            + +

                                            + 默认备份目录 + + + 网站和数据库的备份目录! +

                                            + +

                                            + 服务器时间 + + + 设置/同步当前服务器时间 +

                                            +

                                            + 面板用户 + + + 设置面板账号 +

                                            +

                                            + 面板密码 + + + 设置面板密码 +

                                            +
                                            +
                                            + +

                                            安全

                                            +
                                            + +

                                            + 绑定域名 + + + 为面板绑定一个访问域名,注意:一旦绑定域名,只能通过域名访问面板 +

                                            + +

                                            + 面板SSL + + + + 为面板设置https协议访问,提升面板访问安全性 +

                                            + +

                                            + BasicAuth认证 + + + 为面板增加一道基于BasicAuth的认证服务,有效防止面板被扫描 +

                                            + +

                                            + API接口 + + + + 提供面板API接口访问的支持 +

                                            + +

                                            + 未认证响应状态 + + + 用于在未登录且未正确输入安全入口时的响应,可用于隐藏面板特征 +

                                            + +

                                            + 临时访问授权 + + 为非管理员临时提供面板访问权限 +

                                            + +

                                            + 二步验证 + + + + 二步验证,加强安全登录 +

                                            +
                                            + + +

                                            通知

                                            + +
                                            + +

                                            + TG机器人通知 + + + + Telegram Bot机器人通知【国内可能无法使用 +

                                            + +

                                            + 邮件通知 + + + + 邮件通知 +

                                            + +
                                            + + +
                                            +
                                            +
                                            + +{% endblock %} diff --git a/web/templates/default/site.html b/web/templates/default/site.html new file mode 100755 index 000000000..319ab4c64 --- /dev/null +++ b/web/templates/default/site.html @@ -0,0 +1,133 @@ +{% extends "layout.html" %} + +{% block content %} +
                                            + +
                                            +
                                            网站管理
                                            +
                                            统一查看、搜索和批量操作站点,默认页、分类和 PHP 命令行版本都集中在这里。
                                            +
                                            +
                                            + 站点列表 / 默认页 / 分类 / PHP + 添加站点 +
                                            +
                                            + +
                                            + +
                                            +
                                            +
                                            站点筛选
                                            +
                                            快速搜索站点并切换分类,便于批量处理和定位目标站点。
                                            +
                                            +
                                            + 搜索 / 分类 / 批量 +
                                            +
                                            +
                                            +
                                            +
                                            + 首页/网站 +
                                            + +
                                            +
                                            +
                                            + + +
                                            +
                                            +
                                            +

                                            创建站点时会自动创建权限配置,统一使用www用户。

                                            +
                                            + + + + + + + +
                                            + +
                                            +
                                            + +
                                            +
                                            + + + + + + + + + + + + + + +
                                            域名网站状态备份网站目录 + 到期日期 + 备注操作
                                            +
                                            + + +
                                            + 站点分类: + +
                                            +
                                            +
                                            +
                                            +
                                            +
                                            + + + + +{% endblock %} diff --git a/web/templates/default/soft.html b/web/templates/default/soft.html new file mode 100755 index 000000000..1c5846894 --- /dev/null +++ b/web/templates/default/soft.html @@ -0,0 +1,87 @@ +{% extends "layout.html" %} + +{% block content %} + +
                                            + +
                                            +
                                            软件管理
                                            +
                                            浏览插件、安装/卸载、切换首页展示状态,软件中心与面板功能保持统一。
                                            +
                                            +
                                            + 支持分类切换和关键词搜索 + 添加插件 +
                                            +
                                            +
                                            + +
                                            +
                                            +
                                            筛选与搜索
                                            +
                                            按分类浏览插件,支持关键词搜索和安装包导入。
                                            +
                                            +
                                            + 插件分类 / 搜索 / 导入 +
                                            +
                                            +
                                            +
                                            +
                                            + 首页/软件管理 +
                                            + +
                                            + +
                                            + +
                                            +
                                            +
                                            +
                                            + + +
                                            +
                                            +
                                            软件列表
                                            +
                                            软件状态、首页展示和安装操作都在这里统一管理。
                                            +
                                            +
                                            + 安装 / 设置 / 卸载 +
                                            +
                                            +
                                            +
                                            + + + + + + + + + + + + +
                                            软件名称说明位置状态首页显示操作
                                            + +
                                            +
                                            +
                                            +
                                            +
                                            + + + + + +{% endblock %} diff --git a/web/templates/index.html b/web/templates/index.html new file mode 100644 index 000000000..b2d525b29 --- /dev/null +++ b/web/templates/index.html @@ -0,0 +1 @@ +index \ No newline at end of file diff --git a/web/thisdb/__init__.py b/web/thisdb/__init__.py new file mode 100644 index 000000000..41674e2d2 --- /dev/null +++ b/web/thisdb/__init__.py @@ -0,0 +1,28 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +from .init import * +from .option import * + +from .sites import * +from .site_types import * +from .backup import * + +from .domain import * +from .binding import * + +from .tasks import * +from .logs import * +from .crontab import * +from .firewall import * + +from .temp_login import * +from .user import * +from .app import * diff --git a/web/thisdb/app.py b/web/thisdb/app.py new file mode 100644 index 000000000..27fbd0e4e --- /dev/null +++ b/web/thisdb/app.py @@ -0,0 +1,64 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import core.mw as mw + +__FIELD = 'id,app_id,app_secret,white_list,status,add_time,update_time' + +def addApp(app_id,app_secret,white_list): + now_time = mw.getDateFromNow() + add_data = { + 'app_id': app_id, + 'app_secret': app_secret, + 'status': 1, + 'white_list':white_list, + 'add_time': now_time, + 'update_time': now_time + } + return mw.M('app').insert(add_data) + +def getAppList(page=1,size=10): + m = mw.M('app').field(__FIELD) + + start = (int(page) - 1) * (int(size)) + limit = str(start) + ',' +str(size) + app_list = m.limit(limit).order('id desc').select() + count = m.count() + + data = {} + data['list'] = app_list + data['count'] = count + return data + +def getAppById(aid): + return mw.M('app').field(__FIELD).where("id=?", (aid,)).find() + +def getAppByAppId(app_id): + return mw.M('app').field(__FIELD).where("app_id=?", (app_id,)).find() + +def deleteAppById(aid): + return mw.M('app').where("id=?", (aid,)).delete() + +def toggleAppStatus(aid): + info = mw.M('app').field(__FIELD).where("id=?", (aid,)).find() + if info['status'] == 1: + return mw.M('app').where('id=?',(aid,)).update({'status':0}) + return mw.M('app').where('id=?',(aid,)).update({'status':1}) + + +def setAppData(aid, status=None, app_id=None, app_secret=None): + up_data = {} + if status is not None: + up_data['status'] = status + if app_id is not None: + up_data['app_id'] = app_id + if app_secret is not None: + up_data['app_secret'] = app_secret + return mw.M('app').where('id=?',(aid,)).update(up_data) diff --git a/web/thisdb/backup.py b/web/thisdb/backup.py new file mode 100644 index 000000000..961498b82 --- /dev/null +++ b/web/thisdb/backup.py @@ -0,0 +1,44 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import core.mw as mw + +__FIELD = 'id,pid,type,name,filename,size,add_time' + +def addBackup(pid,name,filename,size,type=0): + + add_time = mw.formatDate() + insert_data = { + 'type':type, + 'name':name, + 'pid':pid, + 'filename':filename, + 'size':size, + 'add_time':add_time, + } + mw.M('backup').insert(insert_data) + return True + +def getBackupById(bp_id): + return mw.M('backup').field(__FIELD).where("id=?", (bp_id,)).find() + +def getBackupPage(site_id,page = 1,size = 10): + start = (int(page) - 1) * int(size) + limit = str(start) + ',' + str(size) + bk_list = mw.M('backup').where('pid=?', (site_id,)).field(__FIELD).limit(limit).order('id desc').select() + count = mw.M('backup').where('pid=?', (site_id,)).count() + + rdata = {} + rdata['list'] = bk_list + rdata['count'] = count + return rdata + +def deleteBackupById(bp_id): + return mw.M('backup').where("id=?", (bp_id,)).delete() diff --git a/web/thisdb/binding.py b/web/thisdb/binding.py new file mode 100644 index 000000000..689989530 --- /dev/null +++ b/web/thisdb/binding.py @@ -0,0 +1,45 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import core.mw as mw + +# 站点-子目录绑定 + +__FIELD = 'id,pid,domain,port,path,add_time' + +def getBindingCountByDomain(name): + # .debug(True) + return mw.M('binding').where("domain=?", (name,)).count() + +def addBinding(pid, domain, port, path): + now_time = mw.getDateFromNow() + insert_data = { + 'pid': pid, + 'domain': domain, + 'port':port, + 'path':path, + 'add_time': now_time, + } + return mw.M('binding').insert(insert_data) + +def getBindingListBySiteId(site_id): + # .debug(True) + binding_list = mw.M('binding').field(__FIELD).where('pid=?', (site_id,)).select() + return binding_list + +def getBindingById(site_id): + return mw.M('binding').where("id=?", (site_id,)).field(__FIELD).find() + + +def deleteBindingById(binding_id): + return mw.M('binding').where("id=?", (binding_id,)).delete() + +def deleteBindingBySiteId(site_id): + return mw.M('binding').where("pid=?", (site_id,)).delete() diff --git a/web/thisdb/crontab.py b/web/thisdb/crontab.py new file mode 100644 index 000000000..023b57e00 --- /dev/null +++ b/web/thisdb/crontab.py @@ -0,0 +1,55 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os + +import core.mw as mw + +__field = 'id,name,type,where1,where_hour,where_minute,echo,status,save,backup_to,stype,sname,sbody,url_address,attr,add_time,update_time' + +def addCrontab(data): + now_time = mw.formatDate() + data['add_time'] = now_time + data['update_time'] = now_time + return mw.M('crontab').insert(data) + +def getCronByName(name): + return mw.M('crontab').where("name=?", (name,)).find() + +def setCrontabData(cron_id, data): + mw.M('crontab').where('id=?', (cron_id,)).update(data) + return True + +def setCrontabStatus(cron_id, status): + mw.M('crontab').where('id=?', (cron_id,)).update({'status':status}) + return True + +def getCrond(id): + return mw.M('crontab').where('id=?', (id,)).field(__field).find() + +def deleteCronById(cron_id): + mw.M('crontab').where("id=?", (cron_id,)).delete() + return True + +def getCrontabList( + page = 1, + size = 10, +): + start = (int(page) - 1) * size + limit = str(start) + ',' + str(size) + + cron_list = mw.M('crontab').field(__field).limit(limit).order('id desc').select() + count = mw.M('crontab').count() + + data = {} + data['count'] = count + data['list'] = cron_list + return data + diff --git a/web/thisdb/domain.py b/web/thisdb/domain.py new file mode 100644 index 000000000..799fc9202 --- /dev/null +++ b/web/thisdb/domain.py @@ -0,0 +1,42 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import core.mw as mw + +__FIELD = 'id,pid,name,port,add_time' + +def getDomainCountByName(name): + # .debug(True) + return mw.M('domain').where("name=?", (name,)).count() + +def getDomainCountBySiteId(site_id): + # .debug(True) + return mw.M('domain').where("pid=?", (site_id,)).count() + +def addDomain(site_id, name, port): + now_time = mw.getDateFromNow() + insert_data = { + 'pid': site_id, + 'name': name, + 'port':port, + 'add_time': now_time, + } + return mw.M('domain').insert(insert_data) + +def getDomainBySiteId(site_id): + # .debug(True) + return mw.M('domain').field(__FIELD).where("pid=?", (site_id,)).select() + + +def deleteDomainId(domain_id): + return mw.M('domain').where("id=?", (domain_id,)).delete() + +def deleteDomainBySiteId(site_id): + return mw.M('domain').where("pid=?", (site_id,)).delete() diff --git a/web/thisdb/firewall.py b/web/thisdb/firewall.py new file mode 100644 index 000000000..bff32e6f2 --- /dev/null +++ b/web/thisdb/firewall.py @@ -0,0 +1,49 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import core.mw as mw + +__FIELD = 'id,port,protocol,ps,add_time,update_time' + +def getFirewallList(page=1,size=10): + start = (int(page) - 1) * (int(size)) + limit = str(start) + ',' +str(size) + + firewall_list = mw.M('firewall').field(__FIELD).limit(limit).order('id desc').select() + count = mw.M('firewall').count() + + data = {} + data['count'] = count + data['list'] = firewall_list + return data + +def addFirewall(port, protocol='tcp',ps='备注') -> bool: + ''' + 设置配置的值 + :port -> str 端口 (必填) + :protocol -> str 协议 (可选|tcp,udp,tcp/udp) + :ps -> str 备注 (可选) + ''' + now_time = mw.formatDate() + insert_data = { + 'port':port, + 'protocol':protocol, + 'ps':ps, + 'add_time':now_time, + 'update_time':now_time, + } + mw.M('firewall').insert(insert_data) + return True + + +def getFirewallCountByPort(port): + return mw.M('firewall').where('port=?',(port,)).count() + + diff --git a/web/thisdb/init.py b/web/thisdb/init.py new file mode 100644 index 000000000..b3d12d93b --- /dev/null +++ b/web/thisdb/init.py @@ -0,0 +1,55 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os + +import core.mw as mw + + +def initPanelData(): + is_reload = False + sql_file = mw.getPanelDir() + '/web/admin/setup/sql/default.sql' + sql_file_md5 = mw.getPanelDir() + '/web/admin/setup/sql/default.md5' + content = mw.readFile(sql_file) + content_md5 = mw.md5(content) + if not os.path.exists(sql_file_md5): + mw.writeFile(sql_file_md5, content_md5) + + sql = mw.M().dbPos(mw.getPanelDataDir(),'panel') + csql_data = content.split(';') + for i in range(len(csql_data)): + sql.execute(csql_data[i], ()) + return True + +def reinstallPanelData(): + is_reload = False + sql_file = mw.getPanelDir() + '/web/admin/setup/sql/default.sql' + sql_file_md5 = mw.getPanelDir() + '/web/admin/setup/sql/default.md5' + content = mw.readFile(sql_file) + content_md5 = mw.md5(content) + if not os.path.exists(sql_file_md5): + mw.writeFile(sql_file_md5, content_md5) + + content_src_md5 = mw.readFile(sql_file) + if content_md5 != content_src_md5: + is_reload = True + + __dbfile = mw.getPanelDataDir() + '/panel.db' + if os.path.exists(__dbfile) and not is_reload: + return True + sql = mw.M().dbPos(mw.getPanelDataDir(),'panel') + csql_data = content.split(';') + for i in range(len(csql_data)): + # print(csql_data[i]) + # print(sql.execute(csql_data[i], ())) + sql.execute(csql_data[i], ()) + + mw.writeFile(sql_file_md5, content_md5) + return True \ No newline at end of file diff --git a/web/thisdb/logs.py b/web/thisdb/logs.py new file mode 100644 index 000000000..d93fd85f3 --- /dev/null +++ b/web/thisdb/logs.py @@ -0,0 +1,61 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import json + + +import core.mw as mw + +def clearLog(): + mw.M('logs').where('id>?', (0,)).delete() + mw.M('logs').execute("update sqlite_sequence set seq=0 where name='logs'") + return True + +def addLog(type, log, uid = 1) -> bool: + ''' + 添加日志 + :type -> str 类型 (必填) + :log -> str 日志内容 (必填) + :uid -> int 用户ID + ''' + + # if log.find("eval") > -1: + # return False + + add_time = mw.formatDate() + insert_data = { + 'type':type, + 'log':log, + 'add_time':add_time, + } + mw.M('logs').insert(insert_data) + return True + +def getLogsList(page = 1,size = 10,search = ''): + sql_where = '' + if search != '' : + sql_where = " type like '%" + search + "%' or log like '%" + search + "%' " + + field = 'id,type,log,uid,add_time' + dbM = dbC = mw.M('logs').field(field) + + if sql_where != '': + count = mw.M('logs').field(field).where(sql_where).count() + else: + count = mw.M('logs').field(field).count() + + start = (int(page) - 1) * (int(size)) + limit = str(start) + ',' +str(size) + logs_list = mw.M('logs').field(field).limit(limit).order('id desc').select() + + data = {} + data['list'] = logs_list + data['count'] = count + return data \ No newline at end of file diff --git a/web/thisdb/option.py b/web/thisdb/option.py new file mode 100644 index 000000000..35a32f5cc --- /dev/null +++ b/web/thisdb/option.py @@ -0,0 +1,58 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import json +import core.mw as mw + +def getOption(name,type='common',default=None) -> str: + ''' + 获取配置的值 + :name -> str 名称 (必填) + :type -> str 类型 (可选|默认common) + :default -> str 默认值 (可选) + ''' + data = mw.M('option').field('name').where('name=? and type=?',(name, type,)).getField('value') + if data is None: + return default + return data + + +def getOptionByJson(name,type='common',default=None) -> object: + ''' + 获取配置的值,返回对象类型 + :name -> str 名称 (必填) + :type -> str 类型 (可选|默认common) + :default -> str 默认值 (可选) + ''' + data = mw.M('option').field('name').where('name=? and type=?',(name, type,)).getField('value') + if data is None: + return default + if data is not None: + return json.loads(data) + +def setOption(name, value, type = 'common') -> bool: + ''' + 设置配置的值 + :name -> str 名称 (必填) + :value -> object值 (必填) + :type -> str 类型 (可选|默认common) + ''' + + data = mw.M('option').field('name,type,value').where('name=? and type=?',(name, type,)).find() + if data is not None: + mw.M('option').field('name').where('name=? and type=?',(name, type,)).setField('value', value) + return True + add_option = { + 'name':name, + 'type':type, + 'value':value + } + return mw.M('option').insert(add_option) \ No newline at end of file diff --git a/web/thisdb/site_types.py b/web/thisdb/site_types.py new file mode 100644 index 000000000..54751f64d --- /dev/null +++ b/web/thisdb/site_types.py @@ -0,0 +1,28 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +__FIELD = 'id,name' + +import core.mw as mw + +def addSiteTypes(name): + return mw.M('site_types').add("name", (name,)) + +def getSiteTypesCount(): + return mw.M('site_types').count() + +def getSiteTypesCountByName(name): + return mw.M('site_types').where('name=?', (name,)).count() + +def getSiteTypesList(): + # .debug(True) + return mw.M('site_types').field(__FIELD).order("id asc").select() + + diff --git a/web/thisdb/sites.py b/web/thisdb/sites.py new file mode 100644 index 000000000..6dfcaf77e --- /dev/null +++ b/web/thisdb/sites.py @@ -0,0 +1,129 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import core.mw as mw + +from .option import getOption + +__FIELD = 'id,name,path,status,ps,edate,type_id,add_time,update_time' + +def checkSitesDomainIsExist(domain, port): + nums = mw.M('domain').where("name=? AND port=?", (domain, port,)).count() + if nums>0: + return True + + nums = mw.M('binding').where("name=? AND port=?", (domain, port,)).count() + if nums>0: + return True + return False + +def getSitesCount(): + return mw.M('sites').count() + +def getSitesById(site_id): + return mw.M('sites').field(__FIELD).where("id=?", (site_id,)).find() + +def getSitesByName(site_name): + return mw.M('sites').field(__FIELD).where("name=?", (site_name,)).find() + +def getSitesDomainById(site_id): + data = {} + domains = mw.M('domain').where('pid=?', (site_id,)).field('name,id').select() + binding = mw.M('binding').where('pid=?', (site_id,)).field('domain,id').select() + for b in binding: + t = {} + t['name'] = b['domain'] + t['id'] = b['id'] + domains.append(t) + data['domains'] = domains + data['email'] = getOption('ssl_email', default='') + return data + +def addSites(name, path): + now_time = mw.getDateFromNow() + insert_data = { + 'name': name, + 'path': path, + 'status': 1, + 'ps':name, + 'type_id':0, + 'edate':'0000-00-00', + 'add_time': now_time, + 'update_time': now_time + } + return mw.M('sites').insert(insert_data) + + +def isSitesExist(name): + if mw.M('sites').where("name=?", (name,)).count() > 0: + return True + return False + +def getSitesEdateList(edate): + elist = mw.M('sites').field(__FIELD).where('edate>? AND edate= 0 and search != '' : + sql_where = sql_where + " and type_id=" + str(type_id) + "" + if type_id != '' and int(type_id) >= 0: + sql_where = " type_id=" + str(type_id) + + dbM = dbC = mw.M('sites').field(__FIELD) + + if sql_where != '': + count = dbC.where(sql_where).count() + dbM.where(sql_where) + else: + count = dbC.count() + + start = (int(page) - 1) * (int(size)) + limit = str(start) + ',' +str(size) + + if order.find("none") > -1: + site_list = dbM.limit(limit).order('').select() + elif order is not None: + site_list = dbM.limit(limit).order(order).select() + else: + site_list = dbM.limit(limit).order('id desc').select() + + data = {} + data['list'] = site_list + data['count'] = count + return data + + +def deleteSitesById(site_id): + return mw.M('sites').where("id=?", (site_id,)).delete() + +def setSitesData(site_id, edate = None, ps = None, path = None,status = None): + update_data = {} + if edate is not None: + update_data['edate'] = edate + if ps is not None: + update_data['ps'] = ps + + if path is not None: + update_data['path'] = path + + if status is not None: + update_data['status'] = status + + return mw.M('sites').where('id=?',(site_id,)).update(update_data) + diff --git a/web/thisdb/tasks.py b/web/thisdb/tasks.py new file mode 100644 index 000000000..7ee8ab8c5 --- /dev/null +++ b/web/thisdb/tasks.py @@ -0,0 +1,138 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import core.mw as mw + +__FIELD = 'id,name,type,start,end,cmd,status,add_time' + +def getTaskCount(status = -1) -> int: + return mw.M('tasks').where('status=?',(1,)).count() + + +# 未执行任务总数 +def getTaskUnexecutedCount() -> int: + return mw.M('tasks').where('status!=?',(1,)).count() + +def addTaskByDownload(name = '下载文件',cmd = None,type = 'download',status = 0): + ''' + 添加后台任务 + :name -> str 类型 + :cmd -> str 日志内容 (必填) + :type -> str 用户ID + ''' + if cmd is None: + return False + + add_time = mw.formatDate() + insert_data = { + 'name':name, + 'type':type, + 'cmd':cmd, + 'start':0, + 'end':0, + 'status':status, + 'add_time':add_time, + } + mw.M('tasks').insert(insert_data) + return True + +def addTask( + name = '常用任务', + cmd = None, + type = 'execshell', + status = 0, +): + ''' + 添加后台任务 + :name -> str 类型 + :cmd -> str 日志内容 (必填) + :type -> str 用户ID + ''' + if cmd is None: + return False + + add_time = mw.formatDate() + insert_data = { + 'name':name, + 'type':type, + 'cmd':cmd, + 'start':0, + 'end':0, + 'status':status, + 'add_time':add_time, + } + mw.M('tasks').insert(insert_data) + return True + + +def getTaskList( + status = 1, + page = 1, + size = 10, +): + start = (page - 1) * size + limit = str(start) + ',' + str(size) + task_list = mw.M('tasks').where('status=?', (status,)).field(__FIELD).limit(limit).order('id asc').select() + return task_list + +def getTaskPage( + status = 1, + page = 1, + size = 10, +): + start = (page - 1) * size + limit = str(start) + ',' + str(size) + task_list = mw.M('tasks').where('', ()).field(__FIELD).limit(limit).order('id desc').select() + count = mw.M('tasks').where('', ()).count() + + rdata = {} + rdata['list'] = task_list + rdata['count'] = count + return rdata + +def getTaskFirstByRun() -> None: + data = mw.M('tasks').where('status!=?', (1,)).field(__FIELD).order('id asc').find() + if data is None: + return None + return data + +def getTaskRunList(page = 1,size = 10): + start = (page - 1) * size + limit = str(start) + ',' + str(size) + task_list = mw.M('tasks').where('status!=?', (1,)).field(__FIELD).limit(limit).order('id asc').select() + return task_list + +def getTaskRunAll(): + task_list = mw.M('tasks').where('status!=?', (1,)).field(__FIELD).order('id asc').select() + return task_list + +def getTaskRunPage(page = 1, size = 10): + start = (page - 1) * size + limit = str(start) + ',' + str(size) + + task_list = mw.M('tasks').where('status!=?', (1,)).field(__FIELD).limit(limit).order('id asc').select() + count = mw.M('tasks').where('status!=?', (1,)).count() + + rdata = {} + rdata['list'] = task_list + rdata['count'] = count + return rdata + + +def setTaskStatus(task_id,status = 0): + mw.M('tasks').where('id=?',(task_id,)).update({'status':status}) + return True + +def setTaskData(id, start = None,end = None): + if start is not None: + mw.M('tasks').where('id=?',(id,)).update({'start':start}) + if end is not None: + mw.M('tasks').where('id=?',(id,)).update({'end':end}) + return True \ No newline at end of file diff --git a/web/thisdb/temp_login.py b/web/thisdb/temp_login.py new file mode 100644 index 000000000..558f2526f --- /dev/null +++ b/web/thisdb/temp_login.py @@ -0,0 +1,69 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import time + +import core.mw as mw + +__FIELD = 'id,token,salt,state,login_time,login_addr,logout_time,expire,add_time' + +def addTempLogin(token = None,expire = None): + if expire is None: + start_time = int(time.time()) + expire=start_time+3600 + + if token is None: + salt = mw.getRandomString(12) + r = mw.getRandomString(48) + token = mw.md5(r + salt) + + now_time = mw.formatDate() + insert_data = { + 'token':token, + 'salt':'0', + 'state':0, + 'login_time':0, + 'login_addr':'', + 'expire':expire, + 'add_time':now_time, + } + + return mw.M('temp_login').insert(insert_data) + +def getTempLoginPage(page = 1,size = 10): + start = (page - 1) * size + limit = str(start) + ',' + str(size) + tl_list = mw.M('temp_login').where('', ()).field(__FIELD).limit(limit).order('id desc').select() + count = mw.M('temp_login').where('', ()).count() + + rdata = {} + rdata['list'] = tl_list + rdata['count'] = count + return rdata + +def getTempLoginByToken(token, +) -> None: + ''' + 获取用户信息通过用户名 + ''' + data = mw.M('temp_login').where('token=?', (token,)).field(__FIELD).order('id asc').find() + return data + +def deleteTempLoginById(id) : + return mw.M('temp_login').where('id=?', (id,)).delete() + +def clearTempLogin()->bool: + ''' + 清空过期数据 + ''' + + now_time = int(time.time()) + mw.M('temp_login').where('expire +# --------------------------------------------------------------------------------- + +import core.mw as mw + +__field = 'id,name,password,login_ip,login_time,phone,email,add_time,update_time' + +# 初始化用户信息 +def initAdminUser(): + data = mw.M('users').field(__field).where('id=?', (1,)).find() + if data is None: + name = mw.getRandomString(8).lower() + password = mw.getRandomString(8).lower() + now_time = mw.formatDate() + login_ip = '127.0.0.1' + add_user = { + 'name':name, + 'password':mw.md5(password), + 'login_ip':login_ip, + 'login_time':now_time, + 'phone':'', + 'email':'', + 'add_time':now_time, + 'update_time':now_time + } + file_pass_pl = mw.getPanelDataDir() + '/default.pl' + mw.writeFile(file_pass_pl, password) + mw.M('users').insert(add_user) + return True + + +def getUserByName(name) -> None: + ''' + 获取用户信息通过用户名 + ''' + data = mw.M('users').field(__field).where('name=?', (name,)).find() + if data is None: + return None + row = {} + row['id'] = data['id'] + row['name'] = data['name'] + row['password'] = data['password'] + row['login_ip'] = data['login_ip'] + row['login_time'] = data['login_time'] + row['phone'] = data['phone'] + row['email'] = data['email'] + row['add_time'] = data['add_time'] + row['update_time'] = data['update_time'] + return row + +def getUserById(id) -> None: + ''' + 获取用户信息通过用户名 + ''' + data = mw.M('users').field(__field).where('id=?', (1,)).find() + if data is None: + return None + row = {} + row['id'] = data['id'] + row['name'] = data['name'] + row['password'] = data['password'] + row['login_ip'] = data['login_ip'] + row['login_time'] = data['login_time'] + row['phone'] = data['phone'] + row['email'] = data['email'] + row['add_time'] = data['add_time'] + row['update_time'] = data['update_time'] + return row + + +def getUserByRoot() -> None: + ''' + 获取用户信息通过用户名 + ''' + return getUserById(1) + +def updateUserLoginTime(): + now_time = mw.formatDate() + mw.M('users').field(__field).where('id=?', (1,)).update({'login_time':now_time}) + return True + +def setUserByName(name, new_name): + return mw.M('users').where("name=?", (name,)).setField('name', new_name.strip()) + +def setUserPwdByName(name, password): + pwd = mw.md5(password) + return mw.M('users').where("name=?", (name,)).setField('password', pwd) + +def setUserByRoot(name = None,password = None) -> bool: + ''' + 设置配置的值 + :name -> str 名称 (必填) + :value -> object值 (必填) + :type -> str 类型 (可选|默认common) + ''' + data = {} + if name is not None: + mw.M('users').where('id=?', (1,)).setField('name', name) + + if password is not None: + pwd = mw.md5(password) + mw.M('users').where('id=?', (1,)).setField('password', pwd) + + if not data: + return False + return True \ No newline at end of file diff --git a/web/utils/__init__.py b/web/utils/__init__.py new file mode 100644 index 000000000..dfba09b52 --- /dev/null +++ b/web/utils/__init__.py @@ -0,0 +1,15 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os + +IS_WIN = (os.name == 'nt') + + diff --git a/web/utils/adult_log.py b/web/utils/adult_log.py new file mode 100644 index 000000000..d69d1ccc5 --- /dev/null +++ b/web/utils/adult_log.py @@ -0,0 +1,356 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +from datetime import datetime + +import core.mw as mw + +__months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', + 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Sept': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'} + +def getAuditLogsFiles(): + log_dir = '/var/log' + log_files = [] + for log_file in os.listdir(log_dir): + log_suffix = log_file.split('.')[-1:] + if log_suffix[0] in ['gz', 'xz', 'bz2', 'asl']: + continue + + if log_file in ['.', '..']: + continue + + filename = os.path.join(log_dir, log_file) + if not os.path.exists(filename): + continue + + if os.path.isfile(filename): + file_size = os.path.getsize(filename) + if not file_size: + continue + + tmp = { + 'name': log_file, + 'size': file_size, + 'log_file': filename, + 'title': getLogsTitle(log_file), + 'uptime': os.path.getmtime(filename) + } + + log_files.append(tmp) + else: + for next_name in os.listdir(filename): + if next_name[-3:] in ['.gz', '.xz']: + continue + next_file = os.path.join(filename, next_name) + if not os.path.isfile(next_file): + continue + file_size = os.path.getsize(next_file) + if not file_size: + continue + log_name = '{}/{}'.format(log_file, next_name) + tmp = { + 'name': log_name, + 'size': file_size, + 'log_file': next_file, + 'title': getLogsTitle(log_name), + 'uptime': os.path.getmtime(next_file) + } + log_files.append(tmp) + log_files = sorted(log_files, key=lambda x: x['name'], reverse=True) + return log_files + +def __to_date2(date_str): + tmp = date_str.split() + s_date = str(tmp[-1]) + '-' + __months.get(tmp[1], tmp[1]) + '-' + tmp[2] + ' ' + tmp[3] + return s_date + +def __to_date3(date_str): + tmp = date_str.split() + s_date = str(datetime.now().year) + '-' + __months.get(tmp[1], tmp[1]) + '-' + tmp[2] + ' ' + tmp[3] + return s_date + +def __to_date4(date_str): + tmp = date_str.split() + s_date = str(datetime.now().year) + '-' + __months.get(tmp[0], tmp[0]) + '-' + tmp[1] + ' ' + tmp[2] + return s_date + +def parseAuditFile(log_name, result): + log_list = [] + is_string = True + for _line in result.split("\n"): + if not _line.strip(): + continue + if log_name.find('sa/sa') == -1: + if _line[:3] in __months: + _msg = _line[16:] + _tmp = _msg.split(": ") + _act = '' + if len(_tmp) > 1: + _act = _tmp[0] + _msg = _tmp[1] + else: + _msg = _tmp[0] + _line = { + "时间": __to_date4(_line[:16].strip()), + "角色": _act, + "事件": _msg + } + is_string = False + elif _line[:2] in ['19', '20', '21', '22', '23', '24']: + _msg = _line[19:] + _tmp = _msg.split(" ") + _act = _tmp[1] + _msg = ' '.join(_tmp[2:]) + _line = { + "时间": _line[:19].strip(), + "角色": _act, + "事件": _msg + } + is_string = False + elif log_name.find('alternatives') == 0: + _tmp = _line.split(": ") + _last = _tmp[0].split(" ") + _act = _last[0] + _msg = ' '.join(_tmp[1:]) + _line = { + "时间": ' '.join(_last[1:]).strip(), + "角色": _act, + "事件": _msg + } + is_string = False + else: + if not is_string: + if type(_line) != dict: + continue + + log_list.append(_line) + return log_list + +def getAuditLast(log_name): + # 获取日志 + cmd = '''LANG=en_US.UTF-8 last -n 200 -x -f {} |grep -v 127.0.0.1|grep -v " begins"'''.format( + '/var/log/' + log_name) + result = mw.execShell(cmd) + lastlog_list = [] + for _line in result[0].split("\n"): + if not _line: + continue + tmp = {} + sp_arr = _line.split() + tmp['用户'] = sp_arr[0] + if sp_arr[0] == 'runlevel': + tmp['来源'] = sp_arr[4] + tmp['端口'] = ' '.join(sp_arr[1:4]) + tmp['时间'] = __to_date3(' '.join(sp_arr[5:])) + ' ' + ' '.join(sp_arr[-2:]) + elif sp_arr[0] in ['reboot', 'shutdown']: + tmp['来源'] = sp_arr[3] + tmp['端口'] = ' '.join(sp_arr[1:3]) + if sp_arr[-3] == '-': + tmp['时间'] = __to_date3( + ' '.join(sp_arr[4:])) + ' ' + ' '.join(sp_arr[-3:]) + else: + tmp['时间'] = __to_date3( + ' '.join(sp_arr[4:])) + ' ' + ' '.join(sp_arr[-2:]) + elif sp_arr[1] in ['tty1', 'tty', 'tty2', 'tty3', 'hvc0', 'hvc1', 'hvc2'] or len(sp_arr) == 9: + tmp['来源'] = '' + tmp['端口'] = sp_arr[1] + tmp['时间'] = __to_date3(' '.join(sp_arr[2:])) + ' ' + ' '.join(sp_arr[-3:]) + else: + tmp['来源'] = sp_arr[2] + tmp['端口'] = sp_arr[1] + tmp['时间'] = __to_date3(' '.join(sp_arr[3:])) + ' ' + ' '.join(sp_arr[-3:]) + + # tmp['_line'] = _line + lastlog_list.append(tmp) + # lastlog_list = sorted(lastlog_list,key=lambda x:x['时间'],reverse=True) + return mw.returnData(True, 'ok!', lastlog_list) + +def getAuditLastLog(): + cmd = '''LANG=en_US.UTF-8 lastlog|grep -v Username''' + result = mw.execShell(cmd) + lastlog_list = [] + for _line in result[0].split("\n"): + if not _line: + continue + tmp = {} + sp_arr = _line.split() + tmp['用户'] = sp_arr[0] + # tmp['_line'] = _line + if _line.find('Never logged in') != -1: + tmp['最后登录时间'] = '0' + tmp['最后登录来源'] = '-' + tmp['最后登录端口'] = '-' + lastlog_list.append(tmp) + continue + tmp['最后登录来源'] = sp_arr[2] + tmp['最后登录端口'] = sp_arr[1] + tmp['最后登录时间'] = __to_date2(' '.join(sp_arr[3:])) + + lastlog_list.append(tmp) + lastlog_list = sorted(lastlog_list, key=lambda x: x['最后登录时间'], reverse=True) + for i in range(len(lastlog_list)): + if lastlog_list[i]['最后登录时间'] == '0': + lastlog_list[i]['最后登录时间'] = '从未登录过' + return mw.returnData(True, 'ok!', lastlog_list) + +def parseAuditFileLine(log_name, _line): + is_string = True + if log_name.find('sa/sa') == -1: + if _line[:3] in __months: + _msg = _line[16:] + _tmp = _msg.split(": ") + # print('tmp: ',_tmp) + _act = '' + if len(_tmp) > 1: + _act = _tmp[0] + _msg = _tmp[1] + else: + _msg = _tmp[0] + _line = { + "时间": __to_date4(_line[:16].strip()), + "角色": _act, + "事件": _msg + } + is_string = False + elif _line[:2] in ['19', '20', '21', '22', '23', '24']: + # print(_line) + _msg = _line[19:] + _tmp = _msg.split(" ") + _act = _tmp[1] + _msg = ' '.join(_tmp[2:]) + _line = { + "时间": _line[:19].strip(), + "角色": _act, + "事件": _msg + } + is_string = False + elif log_name.find('alternatives') == 0: + _tmp = _line.split(": ") + _last = _tmp[0].split(" ") + _act = _last[0] + _msg = ' '.join(_tmp[1:]) + _line = { + "时间": ' '.join(_last[1:]).strip(), + "角色": _act, + "事件": _msg + } + is_string = False + else: + if not is_string: + if type(_line) != dict: + return _line + return _line + +def parseAuditFile(log_name, result): + log_list = [] + for _line in result.split("\n"): + if not _line.strip(): + continue + try: + _line = parseAuditFileLine(log_name, _line) + except Exception as e: + print(str(e)) + + log_list.append(_line) + return log_list + +def getAuditLogsName(log_name): + if log_name in ['wtmp', 'btmp', 'utmp'] or log_name.find('wtmp') == 0 or log_name.find('btmp') == 0 or log_name.find('utmp') == 0: + return getAuditLast(log_name) + + if log_name.find('lastlog') == 0: + return getAuditLastLog() + + if log_name.find('sa/sa') == 0: + if log_name.find('sa/sar') == -1: + return mw.execShell("sar -f /var/log/{}".format(log_name))[0] + log_dir = '/var/log' + log_file = log_dir + '/' + log_name + if not os.path.exists(log_file): + return mw.returnData(False, '日志文件不存在!') + result = mw.getLastLine(log_file, 100) + try: + log_list = parseAuditFile(log_name, result) + _string = [] + _dict = [] + _list = [] + for _line in log_list: + if isinstance(_line, str): + _string.append(_line.strip()) + elif isinstance(_line, dict): + _dict.append(_line) + elif isinstance(_line, list): + _list.append(_line) + else: + continue + _str_len = len(_string) + _dict_len = len(_dict) + _list_len = len(_list) + if _str_len > _dict_len + _list_len: + return "\n".join(_string) + elif _dict_len > _str_len + _list_len: + return mw.returnData(True, 'ok!', _dict) + else: + return mw.returnData(True, 'ok!', _list) + + except Exception as e: + # print(mw.getTracebackInfo()) + return mw.returnData(True, 'ok!', result) + +def getLogsTitle(log_name): + log_name = log_name.replace('.1', '') + if log_name in ['mw-update.log']: + return '面板更新日志' + if log_name in ['mw-install.log']: + return '面板安装日志' + if log_name in ['auth.log', 'secure'] or log_name.find('auth.') == 0: + return '授权日志' + if log_name in ['dmesg'] or log_name.find('dmesg') == 0: + return '内核缓冲区日志' + if log_name in ['syslog'] or log_name.find('syslog') == 0: + return '系统日志' + if log_name in ['rsyncd.log']: + return '远程同步日志' + if log_name in ['btmp']: + return '失败的登录记录' + if log_name in ['utmp', 'wtmp']: + return '登录和重启记录' + if log_name in ['lastlog']: + return '用户最后登录' + if log_name in ['yum.log']: + return 'yum包管理器日志' + if log_name in ['anaconda.log']: + return 'Anaconda日志' + if log_name in ['dpkg.log']: + return 'dpkg日志' + if log_name in ['daemon.log']: + return '系统后台守护进程日志' + if log_name in ['boot.log']: + return '启动日志' + if log_name in ['kern.log']: + return '内核日志' + if log_name in ['maillog', 'mail.log']: + return '邮件日志' + if log_name.find('Xorg') == 0: + return 'Xorg日志' + if log_name in ['cron.log']: + return '定时任务日志' + if log_name in ['alternatives.log']: + return '更新替代信息' + if log_name in ['debug']: + return '调试信息' + if log_name.find('apt') == 0: + return 'apt-get相关日志' + if log_name.find('installer') == 0: + return '系统安装相关日志' + if log_name in ['messages']: + return '综合日志' + return '{}日志'.format(log_name.split('.')[0]) \ No newline at end of file diff --git a/web/utils/cert/cert.py b/web/utils/cert/cert.py new file mode 100644 index 000000000..b69881868 --- /dev/null +++ b/web/utils/cert/cert.py @@ -0,0 +1,10 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + diff --git a/web/utils/config.py b/web/utils/config.py new file mode 100644 index 000000000..f118a9285 --- /dev/null +++ b/web/utils/config.py @@ -0,0 +1,128 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import re + +import core.mw as mw +import thisdb + + +def _normalize_title(title): + title = str(title or '').strip() + if title in ('PowerLinux Pro Max', 'Powerlinux Pro Max', ''): + return 'PowerLinux 3' + return title + + +def _sanitize_server_ip(value): + if not value: + return '' + value = str(value).strip() + lower_value = value.lower() + if ' +# --------------------------------------------------------------------------------- + +import os +import sys +import re +import time +import json +import threading +import multiprocessing + +import core.mw as mw +import thisdb + + +class crontab(object): + # lock + _instance_lock = threading.Lock() + + @classmethod + def instance(cls, *args, **kwargs): + if not hasattr(crontab, "_instance"): + with crontab._instance_lock: + if not hasattr(crontab, "_instance"): + crontab._instance = crontab(*args, **kwargs) + return crontab._instance + + def modifyCrond(self,cron_id,data): + if len(data['name']) < 1: + return mw.returnData(False, '任务名称不能为空!') + + is_check_pass, msg = self.cronCheck(data) + if not is_check_pass: + return mw.returnData(is_check_pass, msg) + + info = thisdb.getCrond(cron_id) + + dbdata = {} + dbdata['name'] = data['name'] + dbdata['type'] = data['type'] + dbdata['where1'] = data['where1'] + dbdata['where_hour'] = data['hour'] + dbdata['where_minute'] = data['minute'] + dbdata['stype'] = data['stype'] + + dbdata['sname'] = mw.getDefault(data, 'sname', '') + dbdata['backup_to'] = mw.getDefault(data, 'backup_to', '') + dbdata['save'] = mw.getDefault(data, 'save', '') + dbdata['sbody'] = mw.getDefault(data, 'sbody', '') + dbdata['url_address'] = mw.getDefault(data, 'url_address', '') + dbdata['attr'] = mw.getDefault(data, 'attr', '') + + if not self.removeForCrond(info['echo']): + return mw.returnData(False, '无法写入文件,是否开启了系统加固功能!') + + thisdb.setCrontabData(cron_id, dbdata) + self.syncToCrond(cron_id) + msg = '修改计划任务[' + data['name'] + ']成功' + mw.writeLog('计划任务', msg) + return mw.returnData(True, msg) + + # 取数据列表 + def getDataList(self,stype=''): + + bak_data = [] + if stype == 'site' or stype == 'sites' or stype == 'database' or stype.find('database_') > -1 or stype == 'path': + bak_data = thisdb.getOptionByJson('hook_backup',type='hook', default=[]) + + if stype == 'database' or stype.find('database_') > -1: + sqlite3_name = 'mysql' + path = mw.getServerDir() + '/mysql' + if stype != 'database': + soft_name = stype.replace('database_', '') + path = mw.getServerDir() + '/' + soft_name + + if soft_name == 'postgresql': + sqlite3_name = 'pgsql' + + if soft_name == 'mongodb': + sqlite3_name = 'mongodb' + + db_list = {} + db_list['orderOpt'] = bak_data + + if not os.path.exists(path + '/' + sqlite3_name + '.db'): + db_list['data'] = [] + else: + db_list['data'] = mw.M('databases').dbPos(path, sqlite3_name).field('name,ps').select() + return db_list + + if stype == 'path': + db_list = {} + db_list['data'] = [{"name": mw.getWwwDir(), "ps": "www"}] + db_list['orderOpt'] = bak_data + return db_list + + data = {} + data['orderOpt'] = bak_data + + default_db = 'sites' + data['data'] = mw.M(default_db).field('name,ps').select() + return data + + def setCronStatus(self,cron_id): + data = thisdb.getCrond(cron_id) + + status = 1 + status_msg = '开启' + if data['status'] == status: + status = 0 + status_msg = '关闭' + thisdb.setCrontabStatus(cron_id, status) + self.removeForCrond(data['echo']) + else: + data['status'] = 1 + thisdb.setCrontabData(cron_id, data) + self.syncToCrond(cron_id) + + msg = '修改计划任务[' + data['name'] + ']状态为[' + str(status_msg) + ']' + mw.writeLog('计划任务', msg) + return mw.returnJson(True, msg) + + + def cronLog(self, cron_id): + data = thisdb.getCrond(cron_id) + log_file = mw.getServerDir() + '/cron/' + data['echo'] + '.log' + if not os.path.exists(log_file): + return mw.returnData(False, '当前日志为空!') + content = mw.getLastLine(log_file, 500) + return mw.returnData(True, content) + + def startTask(self, cron_id): + data = thisdb.getCrond(cron_id) + cmd_file = mw.getServerDir() + '/cron/' + data['echo'] + os.system('chmod +x ' + cmd_file) + os.system('nohup ' + cmd_file + ' >> ' + cmd_file + '.log 2>&1 &') + return mw.returnData(True, '计划任务【%s】已执行!' % data['name']) + + + # 获取指定任务数据 + def getCrondFind(self, cron_id): + return thisdb.getCrond(cron_id) + + def add(self, data): + if len(data['name']) < 1: + # 任务名称不能为空 + return -1 + + is_check_pass, msg = self.cronCheck(data) + if not is_check_pass: + return mw.returnData(is_check_pass, msg) + + cmd, title = self.getCrondCycle(data) + cron_path = mw.getServerDir() + '/cron' + cron_shell = self.getShell(data) + + cmd += ' ' + cron_path + '/' + cron_shell + ' >> ' + cron_path + '/' + cron_shell + '.log 2>&1' + + if not mw.isAppleSystem(): + sh_data = self.writeShell(cmd) + if not sh_data['status']: + return sh_data + self.crondReload() + + add_dbdata = {} + add_dbdata['name'] = data['name'] + add_dbdata['type'] = data['type'] + add_dbdata['where1'] = data['where1'] + add_dbdata['where_hour'] = data['hour'] + add_dbdata['where_minute'] = data['minute'] + add_dbdata['stype'] = data['stype'] + add_dbdata['echo'] = cron_shell + + add_dbdata['sname'] = mw.getDefault(data, 'sname', '') + add_dbdata['backup_to'] = mw.getDefault(data, 'backup_to', '') + add_dbdata['save'] = mw.getDefault(data, 'save', '') + add_dbdata['sbody'] = mw.getDefault(data, 'sbody', '') + add_dbdata['url_address'] = mw.getDefault(data, 'url_address', '') + add_dbdata['attr'] = mw.getDefault(data, 'attr', '') + + tid = thisdb.addCrontab(add_dbdata) + return tid + + def delete(self, tid): + data = thisdb.getCrond(tid) + if not self.removeForCrond(data['echo']): + return mw.returnData(False, '无法写入文件,是否开启了系统加固功能!') + + cron_path = mw.getServerDir() + '/cron' + cron_file = cron_path + '/' + data['echo'] + + if os.path.exists(cron_file): + os.remove(cron_file) + cron_file = cron_path + '/' + data['echo'] + '.log' + if os.path.exists(cron_file): + os.remove(cron_file) + + thisdb.deleteCronById(tid) + msg = mw.getInfo('删除计划任务[{1}]成功!', (data['name'],)) + mw.writeLog('计划任务', msg) + return mw.returnData(True, msg) + + + def delLogs(self,cron_id): + try: + data = thisdb.getCrond(cron_id) + log_file = mw.getServerDir() + '/cron/' + data['echo'] + '.log' + if os.path.exists(log_file): + os.remove(log_file) + return mw.returnData(True, '任务日志已清空!') + except: + return mw.returnData(False, '任务日志清空失败!') + + def getCrontabHuman(self, data): + rdata = [] + for i in range(len(data)): + t = data[i] + if t['type'] == "day": + t['type'] = '每天' + t['cycle'] = mw.getInfo('每天, {1}点{2}分 执行', (str(t['where_hour']), str(t['where_minute']))) + elif t['type'] == "day-n": + t['type'] = mw.getInfo('每{1}天', (str(t['where1']),)) + t['cycle'] = mw.getInfo('每隔{1}天, {2}点{3}分 执行', (str(t['where1']), str(t['where_hour']), str(t['where_minute']))) + elif t['type'] == "hour": + t['type'] = '每小时' + t['cycle'] = mw.getInfo('每小时, 第{1}分钟 执行', (str(t['where_minute']),)) + elif t['type'] == "hour-n": + t['type'] = mw.getInfo('每{1}小时', (str(t['where1']),)) + t['cycle'] = mw.getInfo('每{1}小时, 第{2}分钟 执行', (str(t['where1']), str(t['where_minute']))) + elif t['type'] == "minute-n": + t['type'] = mw.getInfo('每{1}分钟', (str(t['where1']),)) + t['cycle'] = mw.getInfo('每隔{1}分钟执行', (str(t['where1']),)) + elif t['type'] == "week": + t['type'] = '每周' + if not t['where1']: + t['where1'] = '0' + t['cycle'] = mw.getInfo('每周{1}, {2}点{3}分执行', (self.toWeek(int(t['where1'])), str(t['where_hour']), str(t['where_minute']))) + elif t['type'] == "month": + t['type'] = '每月' + t['cycle'] = mw.getInfo('每月, {1}日 {2}点{3}分执行', (str(t['where1']), str(t['where_hour']), str(t['where_minute']))) + rdata.append(t) + return rdata + + # 从crond删除 + def removeForCrond(self, echo): + if mw.isAppleSystem(): + return True + + cron_file = [ + '/var/spool/cron/crontabs/root', + '/var/spool/cron/root', + ] + + file = '' + for i in cron_file: + if os.path.exists(i): + file = i + + + if file == '': + return False + + content = mw.readFile(file) + rep = ".+" + str(echo) + ".+\n" + content = re.sub(rep, "", content) + if not mw.writeFile(file, content): + return False + self.crondReload() + return True + + def getCrontabList(self, + page = 1, + size = 10 + ): + info = thisdb.getCrontabList(page=int(page),size=int(size)) + + rdata = {} + rdata['data'] = self.getCrontabHuman(info['list']) + rdata['page'] = mw.getPage({'count':info['count'],'tojs':'getCronData','p':page,'row':size}) + + + # backup hook + rdata['backup_hook'] = thisdb.getOptionByJson('hook_backup', type='hook', default=[]) + return rdata + + def getCrondCycle(self, params): + cron_cmd = '' + title = '' + if params['type'] == "day": + cron_cmd = self.getDay(params) + title = '每天' + elif params['type'] == "day-n": + cron_cmd = self.getDay_N(params) + title = mw.getInfo('每{1}天', (params['where1'],)) + elif params['type'] == "hour": + cron_cmd = self.getHour(params) + title = '每小时' + elif params['type'] == "hour-n": + cron_cmd = self.getHour_N(params) + title = '每小时' + elif params['type'] == "minute-n": + cron_cmd = self.minute_N(params) + elif params['type'] == "week": + params['where1'] = params['week'] + cron_cmd = self.week(params) + elif params['type'] == "month": + cron_cmd = self.month(params) + return cron_cmd, title + + # 转换大写星期 + def toWeek(self, num): + wheres = { + 0: '日', + 1: '一', + 2: '二', + 3: '三', + 4: '四', + 5: '五', + 6: '六' + } + try: + return wheres[num] + except: + return '' + + # 取任务构造Day + def getDay(self, param): + cmd = "{0} {1} * * * ".format(param['minute'], param['hour']) + return cmd + + # 取任务构造Day_n + def getDay_N(self, param): + cmd = "{0} {1} */{2} * * ".format(param['minute'], param['hour'], param['where1']) + return cmd + + # 取任务构造Hour + def getHour(self, param): + cmd = "{0} * * * * ".format(param['minute']) + return cmd + + # 取任务构造Hour-N + def getHour_N(self, param): + cmd = "{0} */{1} * * * ".format(param['minute'], param['where1']) + return cmd + + # 取任务构造Minute-N + def minute_N(self, param): + cmd = "*/{0} * * * * ".format(param['where1']) + return cmd + + # 取任务构造week + def week(self, param): + cmd = "{0} {1} * * {2}".format(param['minute'], param['hour'], param['week']) + return cmd + + # 取任务构造Month + def month(self, param): + cmd = "{0} {1} {2} * * ".format(param['minute'], param['hour'], param['where1']) + return cmd + + # 参数校验 + def cronCheck(self, params): + if params['stype'] == 'site' or params['stype'] == 'database' or params['stype'].find('database_') > -1 or params['stype'] == 'logs' or params['stype'] == 'path': + if params['save'] == '': + return False, '保留份数不能为空!' + + if params['type'] == 'day': + if params['hour'] == '': + return False, '小时不能为空!' + if params['minute'] == '': + return False, '分钟不能为空!' + + if params['type'] == 'day-n': + if params['where1'] == '': + return False, '天不能为空!' + if params['hour'] == '': + return False, '小时不能为空!' + if params['minute'] == '': + return False, '分钟不能为空!' + if params['type'] == 'hour': + if params['minute'] == '': + return False, '分钟不能为空!' + + if params['type'] == 'hour-n': + if params['where1'] == '': + return False, '小时不能为空!' + if params['minute'] == '': + return False, '分钟不能为空!' + + if params['type'] == 'minute-n': + if params['where1'] == '': + return False, '分钟不能为空!' + + if params['type'] == 'week': + if params['hour'] == '': + return False, '小时不能为空!' + if params['minute'] == '': + return False, '分钟不能为空!' + + if params['type'] == 'month': + if params['where1'] == '': + return False, '日不能为空!' + if params['hour'] == '': + return False, '小时不能为空!' + if params['minute'] == '': + return False, '分钟不能为空!' + return True, 'OK' + + + # 取执行脚本 + def getShell(self, param): + if not 'echo' in param: + cron_name = mw.md5(mw.md5(str(time.time()) + '_mw')) + else: + cron_name = param['echo'] + param['echo'] = cron_name + + # try: + stype = param['stype'] + if stype == 'toFile': + shell = param.sFile + else: + head = "#!/bin/bash\nPATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin\nexport PATH\n" + start_head = ''' +SCRIPT_RUN_TIME="0s" +MW_ToSeconds() +{ + SEC=$1 + if [ $SEC -lt 60 ]; then + SCRIPT_RUN_TIME="${SEC}s" + elif [ $SEC -ge 60 ] && [ $SEC -lt 3600 ];then + SCRIPT_RUN_TIME="$(( SEC / 60 ))m$(( SEC % 60 ))s" + elif [ $SEC -ge 3600 ]; then + SCRIPT_RUN_TIME="$(( SEC / 3600 ))h$(( (SEC % 3600) / 60 ))m$(( (SEC % 3600) % 60 ))s" + fi +} +START_MW_SHELL_TIME=`date +%s` +''' + + source_bin_activate = ''' +export LANG=en_US.UTF-8 +MW_PATH=%s/bin/activate +if [ -f $MW_PATH ];then + source $MW_PATH +fi''' % (mw.getPanelDir(),) + + head = head + start_head + source_bin_activate + "\n" + log = '.log' + + #所有 + if param['sname'] == 'ALL': + log = '' + + script_dir = mw.getPanelDir() + "/scripts" + source_stype = 'database' + if stype.find('database_') > -1: + plugin_name = stype.replace('database_', '') + script_dir = mw.getPanelDir() + "/plugins/" + plugin_name + "/scripts" + + source_stype = stype + stype = 'database' + + if stype == 'path' and param['echo'] == '': + param['echo'] == "1" + + wheres = { + 'path': head + "python3 " + script_dir + "/backup.py path " + param['sname'] + " " + str(param['save']) + " " + str(param['echo']), + 'site': head + "python3 " + script_dir + "/backup.py site " + param['sname'] + " " + str(param['save']) + " " + str(param['echo']), + 'database': head + "python3 " + script_dir + "/backup.py database " + param['sname'] + " " + str(param['save']), + 'logs': head + "python3 " + script_dir + "/logs_backup.py " + param['sname'] + log + " " + str(param['save']), + 'rememory': head + "/bin/bash " + script_dir + '/rememory.sh' + } + if param['backup_to'] != 'localhost': + cfile = mw.getPluginDir() + "/" + param['backup_to'] + "/index.py" + wheres['path'] = head + "python3 " + cfile + " path " + param['sname'] + " " + str(param['save']) + " " + str(param['echo']) + wheres['site'] = head + "python3 " + cfile + " site " + param['sname'] + " " + str(param['save']) + " " + str(param['echo']) + wheres['database'] = head + "python3 " + cfile + " " + source_stype + " " + param['sname'] + " " + str(param['save']) + try: + shell = wheres[stype] + except: + if stype == 'toUrl': + shell = head + "curl -sS --connect-timeout 10 -m 60 '" + param['url_address'] + "'" + else: + shell = head + param['sbody'].replace("\r\n", "\n") + + shell += ''' +echo "----------------------------------------------------------------------------" +endDate=`date +"%Y-%m-%d %H:%M:%S"` +END_MW_SHELL_TIME=`date +"%s"` +((SHELL_COS_TIME=($END_MW_SHELL_TIME-$START_MW_SHELL_TIME))) +MW_ToSeconds $SHELL_COS_TIME +echo "★[$endDate] Successful | Script Run [$SCRIPT_RUN_TIME] " +echo "----------------------------------------------------------------------------" +''' + cron_path = mw.getServerDir() + '/cron' + if not os.path.exists(cron_path): + mw.execShell('mkdir -p ' + cron_path) + + + file = cron_path + '/' + cron_name + # print(shell) + mw.writeFile(file, self.checkScript(shell)) + mw.execShell('chmod 750 ' + file) + return cron_name + + # 检查脚本 + def checkScript(self, shell): + keys = ['shutdown', 'init 0', 'mkfs', 'passwd', + 'chpasswd', '--stdin', 'mkfs.ext', 'mke2fs'] + for k in keys: + shell = shell.replace(k, '[***]') + return shell + + # 将Shell脚本写到文件 + def writeShell(self, bash_script): + if mw.isAppleSystem(): + return mw.returnData(True, 'ok') + + if not os.path.exists("/var/spool/cron/crontabs"): + mw.execShell("mkdir -p /var/spool/cron/crontabs") + + file = '/var/spool/cron/crontabs/root' + sys_os = mw.getOs() + sys_name = mw.getOsName() + if sys_os == 'darwin': + file = '/etc/crontab' + elif sys_name.startswith("freebsd"): + file = '/var/cron/tabs/root' + # elif sys_name.startswith("ubuntu"): + # file = '/var/spool/cron/root' + + if not os.path.exists(file): + mw.writeFile(file, '') + content = mw.readFile(file) + if not content: + content = '' + # if not content: + # return mw.returnData(False, '计划任务配置文件不存在?') + content += str(bash_script) + "\n" + if mw.writeFile(file, content): + if not os.path.exists(file): + mw.execShell("chmod 600 '" + file +"' && chown root.root " + file) + else: + mw.execShell("chmod 600 '" + file +"' && chown root.crontab " + file) + return mw.returnData(True, 'ok') + return mw.returnData(False, '文件写入失败,是否开启系统加固功能!') + + # 重载配置 + def crondReload(self): + if mw.isAppleSystem(): + if os.path.exists('/etc/crontab'): + pass + else: + if os.path.exists('/etc/init.d/crond'): + mw.execShell('/etc/init.d/crond reload') + elif os.path.exists('/etc/init.d/cron'): + mw.execShell('service cron restart') + else: + mw.execShell("systemctl reload crond") + + def syncToCrond(self, cron_id): + info = thisdb.getCrond(cron_id) + if 'status' in info: + if info['status'] == 0: + return False + + if 'where_hour' in info: + info['hour'] = info['where_hour'] + info['minute'] = info['where_minute'] + info['week'] = info['where1'] + + cmd, _ = self.getCrondCycle(info) + cron_path = mw.getServerDir() + '/cron' + cron_name = self.getShell(info) + cmd += ' ' + cron_path + '/' + cron_name + ' >> ' + cron_path + '/' + cron_name + '.log 2>&1' + self.writeShell(cmd) + self.crondReload() + return True + diff --git a/web/utils/email.py b/web/utils/email.py new file mode 100644 index 000000000..9112622cb --- /dev/null +++ b/web/utils/email.py @@ -0,0 +1,52 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +import smtplib + +from email import encoders +from email.header import Header +from email.mime.text import MIMEText +from email.utils import parseaddr, formataddr + + +def _format_addr(s): + name, addr = parseaddr(s) + return formataddr((Header(name, 'utf-8').encode(), addr)) + + +def send(smtp_host, smtp_port, username, password, to_mail, subject, content): + + smtp = smtplib.SMTP() + smtp.connect(smtp_host, port=smtp_port) + smtp.login(user=username, password=password) + + msg = MIMEText(content, 'plain', 'utf-8') + msg['From'] = _format_addr(username) + msg['To'] = _format_addr(to_mail) + msg['Subject'] = Header(subject, 'utf-8').encode() + + smtp.sendmail(from_addr=username, to_addrs=to_mail, msg=msg.as_string()) + return True + + +def sendSSL(smtp_host, smtp_port, username, password, to_mail, subject, content): + + smtp = smtplib.SMTP_SSL(smtp_host, port=smtp_port) + smtp.login(user=username, password=password) + + msg = MIMEText(content, 'plain', 'utf-8') + msg['From'] = _format_addr(username) + msg['To'] = _format_addr(to_mail) + msg['Subject'] = Header(subject, 'utf-8').encode() + + smtp.sendmail(from_addr=username, to_addrs=to_mail, msg=msg.as_string()) + smtp.quit() + return True diff --git a/web/utils/enhanced_log_rotation.py b/web/utils/enhanced_log_rotation.py new file mode 100644 index 000000000..1dfa449c0 --- /dev/null +++ b/web/utils/enhanced_log_rotation.py @@ -0,0 +1,61 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + + +import re +from logging import handlers + + +class EnhancedRotatingFileHandler(handlers.TimedRotatingFileHandler, + handlers.RotatingFileHandler): + """ + Handler for logging to a set of files, which switches from one file + to the next when the current file reaches a certain size, or at certain + timed intervals + @filename - log file name + @max_bytes - file size in bytes to rotate file + @interval - Duration to rotate file + @backup_count - Maximum number of files to retain + @encoding - file encoding + @when - 'when' events supported: + # S - Seconds + # M - Minutes + # H - Hours + # D - Days + # midnight - roll over at midnight + # W{0-6} - roll over on a certain day; 0 - Monday + Here we are defaulting rotation with minutes interval + """ + def __init__(self, filename, max_bytes=1, interval=60, backup_count=0, + encoding=None, when='M'): + max_bytes = max_bytes * 1024 * 1024 + handlers.TimedRotatingFileHandler.__init__(self, filename=filename, + when=when, + interval=interval, + backupCount=backup_count, + encoding=encoding) + + handlers.RotatingFileHandler.__init__(self, filename=filename, + mode='a', + maxBytes=max_bytes, + backupCount=backup_count, + encoding=encoding) + + # Time & Size combined rollover + def shouldRollover(self, record): + return handlers.TimedRotatingFileHandler.shouldRollover(self, record) \ + or handlers.RotatingFileHandler.shouldRollover(self, record) + + # Roll overs current file + def doRollover(self): + self.suffix = "%Y-%m-%d_%H-%M-%S" + self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$" + self.extMatch = re.compile(self.extMatch, re.ASCII) + handlers.TimedRotatingFileHandler.doRollover(self) \ No newline at end of file diff --git a/web/utils/file.py b/web/utils/file.py new file mode 100644 index 000000000..f124b84ba --- /dev/null +++ b/web/utils/file.py @@ -0,0 +1,934 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import pwd +import time +import shutil +import json +import base64 + +import core.mw as mw +import thisdb + +def uploadSegment(path,name,size,start,dir_mode,file_mode,b64_data,upload_files): + if not mw.fileNameCheck(name): + return mw.returnData(False, '文件名中不能包含特殊字符!') + + if path == '/': + return mw.returnData(False, '不能直接上传文件到系统根目录!') + + if name.find('./') != -1 or path.find('./') != -1: + return mw.returnData(False, '错误的参数') + + if not os.path.exists(path): + os.makedirs(path, 493) + if not dir_mode != '' or not file_mode != '': + mw.setMode(path) + + save_path = os.path.join(path, name + '.' + str(int(size)) + '.upload.tmp') + d_size = 0 + if os.path.exists(save_path): + d_size = os.path.getsize(save_path) + + if d_size != int(start): + return mw.returnData(True, 'size', d_size) + + f = open(save_path, 'ab') + if b64_data == '1': + b64_data = base64.b64decode(b64_data) + f.write(b64_data) + else: + for tmp_f in upload_files: + f.write(tmp_f.read()) + + f.close() + f_size = os.path.getsize(save_path) + if f_size != int(size): + return mw.returnData(True, 'size', f_size) + + new_name = os.path.join(path, name) + if os.path.exists(new_name): + if new_name.find('.user.ini') != -1: + mw.execShell("chattr -i " + new_name) + try: + os.remove(new_name) + except: + mw.execShell("rm -f %s" % new_name) + + os.renames(save_path, new_name) + + if dir_mode != '' and dir_mode != '': + mode_tmp1 = dir_mode.split(',') + mw.setMode(path, mode_tmp1[0]) + mw.setOwn(path, mode_tmp1[1]) + mode_tmp2 = file_mode.split(',') + mw.setMode(new_name, mode_tmp2[0]) + mw.setOwn(new_name, mode_tmp2[1]) + else: + setMode(new_name) + + msg = mw.getInfo('上传文件[{1}] 到 [{2}]成功!', (new_name, path)) + mw.writeLog('文件管理', msg) + return mw.returnData(True, '上传成功!', f_size) + + +def mvFile(sfile, dfile): + if not checkFileName(dfile): + return mw.returnData(False, '文件名中不能包含特殊字符!') + if not os.path.exists(sfile): + return mw.returnData(False, '指定文件不存在!') + + if not checkDir(sfile): + return mw.returnData(False, 'FILE_DANGER') + + + try: + pass + except Exception as e: + raise e + + try: + shutil.move(sfile, dfile) + msg = mw.getInfo('移动或重名命文件[{1}]到[{2}]成功!', (sfile, dfile,)) + mw.writeLog('文件管理', msg) + return mw.returnData(True, '移动或重名命文件成功!') + except Exception as e: + return mw.returnData(False, '移动或重名命文件失败!'+str(e)) + +def unzip(sfile, dfile, stype, path): + if dfile == '' or dfile == '/': + return mw.returnData(False, '不能在根目录解压!') + + if not os.path.exists(sfile): + return mw.returnData(False, '指定文件不存在!') + + try: + tmps = mw.getPanelDir() + '/logs/panel_exec.log' + if stype == 'zip': + mw.execShell("cd " + path + " && unzip -o -d '" + dfile +"' '" + sfile + "' > " + tmps + " 2>&1 &") + else: + sfiles = '' + for sfile in sfile.split(','): + if not sfile: + continue + sfiles += " '" + sfile + "'" + mw.execShell("cd " + path + " && tar -zxvf " + sfiles +" -C " + dfile + " > " + tmps + " 2>&1 &") + + if os.path.exists(dfile): + if dfile.startswith("/www/wwwroot"): + setFileAccept(dfile) + mw.writeLog("文件管理", '文件[{1}]解压[{2}]成功!', (sfile, dfile)) + return mw.returnData(True, '文件解压成功!') + except: + return mw.returnData(False, '文件解压失败!') + +def uncompress(sfile, dfile, path): + if dfile == '' or dfile == '/': + return mw.returnData(False, '不能在根目录解压!') + + if not os.path.exists(sfile): + return mw.returnData(False, '指定文件不存在!') + + filename = os.path.basename(sfile) + extension = os.path.splitext(filename)[-1] + extension = extension.strip('.') + + tar_gz = 'tar.gz' + tar_gz_len = len(tar_gz) + suffix_gz = sfile[-tar_gz_len:] + if suffix_gz == tar_gz: + extension = suffix_gz + + if not extension in ['tar.gz', 'gz', 'zip', 'rar', '7z', 'xz','bz2']: + return mw.returnData(False, '现在仅支持gz,zip,rar,7z,xz,bz2格式解压!') + + if extension == 'rar' and not mw.checkBinExist('rar'): + return mw.returnData(False, 'rar解压命令不存在,请安装!') + if extension == '7z' and not mw.checkBinExist('7z'): + return mw.returnData(False, '7z解压命令不存在,请安装!') + + + cmd = "cd " + path + " " + try: + tmps = mw.getPanelDir() + '/logs/panel_exec.log' + if extension == 'zip': + cmd += "&& unzip -o -d '" + dfile + "' '" + sfile + "' > " + tmps + " 2>&1 &" + mw.execShell(cmd) + elif extension == 'tar.gz': + cmd += "&& tar -zxvf " + sfile + " -C " + dfile + " > " + tmps + " 2>&1 &" + mw.execShell(cmd) + elif extension == 'gz': + cmd += "&& gunzip -k " + sfile + " > " + tmps + " 2>&1 &" + mw.execShell(cmd) + elif extension == 'rar': + cmd += "&& unrar x " + sfile + " " + dfile + " > " + tmps + " 2>&1 &" + mw.execShell(cmd) + elif extension == '7z': + cmd += "&& 7z x " + sfile + " -r -o" + dfile + " > " + tmps + " 2>&1 &" + mw.execShell(cmd) + elif extension == 'xz': + cmd += "&& tar -Jxvf " + sfile + " -C " + dfile + " > " + tmps + " 2>&1 &" + mw.execShell(cmd) + elif extension == 'bz2': + cmd += "&& tar -xjvf " + sfile + " -C " + dfile + " > " + tmps + " 2>&1 &" + mw.execShell(cmd) + + if os.path.exists(dfile): + if dfile.startswith("/www/wwwroot"): + setFileAccept(dfile) + mw.writeLog("文件管理", '文件[{1}]解压[{2}]成功!', (sfile, dfile,)) + return mw.returnData(True, '文件解压成功!') + except Exception as e: + return mw.returnData(False, '文件解压失败!:' + str(e)) + +def setBatchData(path, stype, access, user, data): + from admin import session + if stype == '1' or stype == '2': + session['selected'] = { + 'path': path, + 'type': stype, + 'access': access, + 'user': user, + 'data': data + } + return mw.returnData(True, '标记成功,请在目标目录点击粘贴所有按钮!') + elif stype == '3': + for key in json.loads(data): + try: + filename = path + '/' + key + if not checkDir(filename): + return mw.returnData(False, 'FILE_DANGER') + os.system('chmod -R ' + access + " '" + filename + "'") + os.system('chown -R ' + user + ':' + user + " '" + filename + "'") + except: + continue + mw.writeLog('文件管理', '批量设置权限成功!') + return mw.returnData(True, '批量设置权限成功!') + else: + recycle_bin = thisdb.getOption('recycle_bin') + is_recycle = False + if recycle_bin == 'open': + is_recycle = True + data = json.loads(data) + l = len(data) + i = 0 + for key in data: + try: + filename = path + '/' + key + topath = filename + if not os.path.exists(filename): + continue + + i += 1 + mw.writeSpeed(key, i, l) + if os.path.isdir(filename): + if not checkDir(filename): + return mw.returnData(False, '请不要花样作死!') + if is_recycle: + mvRecycleBin(topath) + else: + shutil.rmtree(filename) + else: + if key == '.user.ini': + os.system('which chattr && chattr -i ' + filename) + if is_recycle: + mvRecycleBin(topath) + else: + os.remove(filename) + except: + continue + mw.writeSpeed(None, 0, 0) + mw.writeLog('文件管理', '批量删除成功!') + return mw.returnData(True, '批量删除成功!') + +def batchPaste(path, stype): + from admin import session + if not checkDir(path): + return mw.returnData(False, '请不要花样作死!') + i = 0 + myfiles = json.loads(session['selected']['data']) + l = len(myfiles) + if stype == '1': + for key in myfiles: + i += 1 + mw.writeSpeed(key, i, l) + try: + + sfile = session['selected'][ + 'path'] + '/' + key + dfile = path + '/' + key + + if os.path.isdir(sfile): + shutil.copytree(sfile, dfile) + else: + shutil.copyfile(sfile, dfile) + stat = os.stat(sfile) + os.chown(dfile, stat.st_uid, stat.st_gid) + except: + continue + msg = mw.getInfo('从[{1}]批量复制到[{2}]成功',(session['selected']['path'], path,)) + mw.writeLog('文件管理', msg) + else: + for key in myfiles: + try: + i += 1 + mw.writeSpeed(key, i, l) + + sfile = session['selected'][ + 'path'] + '/' + key + dfile = path + '/' + key + + shutil.move(sfile, dfile) + except: + continue + msg = mw.getInfo('从[{1}]批量移动到[{2}]成功',(session['selected']['path'], path,)) + mw.writeLog('文件管理', msg) + mw.writeSpeed(None, 0, 0) + errorCount = len(myfiles) - i + del(session['selected']) + msg = mw.getInfo('批量操作成功[{1}],失败[{2}]', (str(i), str(errorCount))) + return mw.returnData(True, msg) + + +def zip(sfile, dfile, stype, path): + tmps = mw.getPanelDir() + '/logs/panel_exec.log' + if sfile.find(',') == -1: + if stype == 'zip': + mw.execShell("cd '" + path + "' && zip '" + dfile + "' -r '" + sfile + "' > " + tmps + " 2>&1") + elif stype == '7z': + if not mw.checkBinExist('7z'): + return mw.returnData(False, '7z压缩命令不存在,请安装!') + mw.execShell("cd '" + path + "' && 7z a '" + dfile + "' -r '" + sfile + "' > " + tmps + " 2>&1") + elif stype == 'tar_gz': + mw.execShell("cd '" + path + "' && tar -zcvf '" + dfile + "' " + sfile + " > " + tmps + " 2>&1") + elif stype == 'xz': + # if not mw.checkBinExist('xz'): + # return mw.returnData(False, 'xz压缩命令不存在,请安装!') + # dfile = dfile.strip(".xz") + # cmd = "cd '" + path + "' && tar -cvf '" + dfile + ".tar' " + sfile + " && xz -z '" + dfile + ".tar' > " + tmps + " 2>&1 &" + cmd = "cd '" + path + "' && tar -cJf '" + dfile + "' " + sfile + " > " + tmps + " 2>&1" + # print(cmd) + mw.execShell(cmd) + elif stype == 'rar': + if not mw.checkBinExist('rar'): + return mw.returnData(False, 'rar压缩命令不存在,请安装!') + mw.execShell("cd '" + path + "' && rar a '" + dfile + "' '" + sfile + "' > " + tmps + " 2>&1") + elif stype == 'bz2': + mw.execShell("cd '" + path + "' && tar -cjvf '" + dfile + "' " + sfile + " > " + tmps + " 2>&1") + else: + return mw.returnData(False, '未知压缩格式') + mw.writeLog("文件管理", '文件[{1}]压缩[{2}]成功!', (sfile, dfile)) + else: + sfiles = '' + for sfile in sfile.split(','): + if not sfile: + continue + if not os.path.exists(sfile): + return mw.returnData(False, '指定文件不存在!') + + sfiles += " '" + sfile.replace(path+'/','') + "'" + + if stype == 'zip': + mw.execShell("cd '" + path + "' && zip '" + dfile + "' -r " + sfiles + " > " + tmps + " 2>&1") + elif stype == '7z': + if not mw.checkBinExist('7z'): + return mw.returnData(False, '7z压缩命令不存在,请安装!') + mw.execShell("cd '" + path + "' && 7z a '" + dfile + "' -r " + sfiles + " > " + tmps + " 2>&1") + elif stype == 'tar_gz': + mw.execShell("cd '" + path + "' && tar -zcvf '" + dfile + "' " + sfiles + " > " + tmps + " 2>&1") + elif stype == 'rar': + if not mw.checkBinExist('rar'): + return mw.returnData(False, 'rar压缩命令不存在,请安装!') + mw.execShell("cd '" + path + "' && rar a '" + dfile + "' " + sfiles + " > " + tmps + " 2>&1") + elif stype == 'bz2': + mw.execShell("cd '" + path + "' && tar -cjvf '" + dfile + "' " + sfiles + " > " + tmps + " 2>&1") + else: + return mw.returnData(False, '未知压缩格式') + + mw.writeLog("文件管理", '文件[{1}]压缩[{2}]成功!', (sfiles, dfile)) + setFileAccept(dfile) + return mw.returnData(True, '文件压缩成功!') + +def getAccess(filename): + data = {} + try: + stat = os.stat(filename) + data['chmod'] = str(oct(stat.st_mode)[-3:]) + data['chown'] = pwd.getpwuid(stat.st_uid).pw_name + except: + data['chmod'] = 755 + data['chown'] = 'www' + return data + +def copyDir(src_file, dst_file): + if not os.path.exists(src_file): + return mw.returnData(False, '指定目录不存在!') + + if os.path.exists(dst_file): + return mw.returnData(False, '指定目录已存在!') + + try: + shutil.copytree(src_file, dst_file) + stat = os.stat(src_file) + os.chown(dst_file, stat.st_uid, stat.st_gid) + msg = mw.getInfo('复制目录[{1}]到[{2}]成功!', (src_file, dst_file)) + mw.writeLog('文件管理', msg) + return mw.returnData(True, '目录复制成功!') + except: + return mw.returnData(False, '目录复制失败!') + +def copyFile(src_file, dst_file): + if src_file == dst_file: + return mw.returnJson(False, '源与目的一致!') + + if not os.path.exists(src_file): + return mw.returnJson(False, '指定文件不存在!') + + if os.path.isdir(src_file): + return copyDir(src_file, dst_file) + + try: + shutil.copyfile(src_file, dst_file) + msg = mw.getInfo('复制文件[{1}]到[{2}]成功!', (src_file, dst_file,)) + mw.writeLog('文件管理', msg) + stat = os.stat(src_file) + os.chown(dst_file, stat.st_uid, stat.st_gid) + return mw.returnData(True, '文件复制成功!') + except: + return mw.returnData(False, '文件复制失败!') + +def setFileAccept(filename): + auth = 'www:www' + if mw.getOs() == 'darwin': + user = mw.execShell("who | sed -n '2, 1p' |awk '{print $1}'")[0].strip() + auth = user + ':staff' + os.system('chown -R ' + auth + ' ' + filename) + os.system('chmod -R 755 ' + filename) + +def createFile(file_path): + try: + if not checkFileName(file_path): + return mw.returnData(False, '文件名中不能包含特殊字符!') + if os.path.exists(file_path): + return mw.returnData(False, '指定文件已存在!') + _path = os.path.dirname(file_path) + if not os.path.exists(_path): + os.makedirs(_path) + open(file_path, 'w+').close() + setFileAccept(file) + msg = mw.getInfo('创建文件[{1}]成功!', (file_path,)) + mw.writeLog('文件管理', msg) + return mw.returnData(True, '文件创建成功!') + except Exception as e: + return mw.returnData(True, '文件创建失败:'+str(e)) + +def createDir(path): + try: + if not checkFileName(path): + return mw.returnData(False, '目录名中不能包含特殊字符!') + if os.path.exists(path): + return mw.returnData(False, '指定目录已存在!') + os.makedirs(path) + setFileAccept(path) + msg = mw.getInfo('创建目录[{1}]成功!', (path,)) + mw.writeLog('文件管理', msg) + return mw.returnData(True, '目录创建成功!') + except Exception as e: + print(e) + return mw.returnData(False, '目录创建失败!') + +# 检查敏感目录 +def checkDir(path): + path = path.replace('//', '/') + if path[-1:] == '/': + path = path[:-1] + + sense_dir = ('', + '/', + '/*', + '/www', + '/root', + '/boot', + '/bin', + '/etc', + '/home', + '/dev', + '/sbin', + '/var', + '/usr', + '/tmp', + '/sys', + '/proc', + '/media', + '/mnt', + '/opt', + '/lib', + '/srv', + '/selinux', + '/www/server', + mw.getRootDir()) + return not path in sense_dir + +def getFileBody(path): + if not os.path.exists(path): + return mw.returnData(False, '文件不存在', (path,)) + + if os.path.getsize(path) > 2097152: + return mw.returnData(False, '不能在线编辑大于2MB的文件!') + + if os.path.isdir(path): + return mw.returnData(False, '这不是一个文件!') + + fp = open(path, 'rb') + data = {} + data['status'] = True + if fp: + srcBody = fp.read() + fp.close() + + encoding_list = ['utf-8', 'GBK', 'BIG5'] + for el in encoding_list: + try: + data['encoding'] = el + data['data'] = srcBody.decode(data['encoding']) + break + except Exception as ex: + if el == 'BIG5': + return mw.returnData(False, '文件编码不被兼容,无法正确读取文件!' + str(ex)) + else: + return mw.returnData(False, '文件未正常打开!') + return mw.returnData(True, 'OK', data) + +def saveBody(path, data, encoding): + if not os.path.exists(path): + return mw.returnData(False, '文件不存在') + try: + if encoding == 'ascii': + encoding = 'utf-8' + + data = data.encode(encoding, errors='ignore').decode(encoding) + fp = open(path, 'w+', encoding=encoding) + fp.write(data) + fp.close() + + if path.find("web_conf") > 0: + mw.restartWeb() + mw.writeLog('文件管理', '文件[{1}]保存成功', (path,)) + return mw.returnData(True, '文件保存成功') + except Exception as ex: + return mw.returnData(False, '文件保存错误:' + str(ex)) + + +def sortFileList(path, ftype = 'mtime', sort = 'desc'): + flist = os.listdir(path) + if ftype == 'mtime': + try: + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.getmtime(os.path.join(path,f)), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.getmtime(os.path.join(path,f)), reverse=False) + except Exception as e: + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.getctime(os.path.join(path,f)), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.getctime(os.path.join(path,f)), reverse=False) + + + if ftype == 'size': + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.getsize(os.path.join(path,f)), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.getsize(os.path.join(path,f)), reverse=False) + + if ftype == 'fname': + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.join(path,f), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.join(path,f), reverse=False) + return flist + +def sortAllFileList(path, ftype = 'mtime', sort = 'desc', search = '',limit = 3000): + count = 0 + flist = [] + for d_list in os.walk(path): + if count >= limit: + break + + for d in d_list[1]: + if count >= limit: + break + if d.lower().find(search) != -1: + filename = d_list[0] + '/' + d + if not os.path.exists(filename): + continue + count += 1 + flist.append(filename) + + for f in d_list[2]: + if count >= limit: + break + + if f.lower().find(search) != -1: + filename = d_list[0] + '/' + f + if not os.path.exists(filename): + continue + count += 1 + flist.append(filename) + + if ftype == 'mtime': + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.getmtime(f), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.getmtime(f), reverse=False) + + if ftype == 'size': + if sort == 'desc': + flist = sorted(flist, key=lambda f: os.path.getsize(f), reverse=True) + if sort == 'asc': + flist = sorted(flist, key=lambda f: os.path.getsize(f), reverse=False) + return flist + +def getAllDirList(path, page=1, size=10, order = '', search=None): + if page < 1: + page == 1 + + data = {} + dirnames = [] + filenames = [] + + max_limit = 3000 + order_split = order.split(' ') + if len(order_split) < 2: + flist = sortAllFileList(path, order_split[0],'',search, max_limit) + else: + flist = sortAllFileList(path, order_split[0], order_split[1], search, max_limit) + + count = len(flist) + start = (page - 1) * size + end = start + size + if end > count: + end = count + + plist = flist[start:end] + for dst_file in plist: + if not os.path.exists(dst_file): + continue + stat = mw.getFileStatsDesc(dst_file, path) + if os.path.isdir(dst_file): + dirnames.append(stat) + else: + filenames.append(stat) + + data['count'] = count + data['dir'] = dirnames + data['files'] = filenames + data['path'] = path.replace('//', '/') + return data + +def getDirList(path, page=1, size=10, order = '', search=None): + if page < 1: + page == 1 + + data = {} + dirnames = [] + filenames = [] + + count = getCount(path, search) + order_split = order.strip().split(' ') + if len(order_split) < 2: + flist = sortFileList(path, order_split[0], '') + else: + flist = sortFileList(path, order_split[0], order_split[1]) + + start = (page - 1) * size + end = start + size + if end > count: + end = count + + + if search or search != '': + nlist = [] + for f in flist: + if f.lower().find(search) == -1: + continue + nlist.append(f) + plist = nlist[start:end] + else: + plist = flist[start:end] + + for filename in plist: + abs_file = path + '/' + filename + if not os.path.exists(abs_file): + continue + + stats = mw.getFileStatsDesc(abs_file, path) + if os.path.isdir(abs_file): + dirnames.append(stats) + else: + filenames.append(stats) + + data['count'] = count + data['dir'] = dirnames + data['files'] = filenames + data['path'] = path.replace('//', '/') + return data + +# 检测文件名 +def checkFileName(filename): + nots = ['\\', '&', '*', '|', ';'] + if filename.find('/') != -1: + filename = filename.split('/')[-1] + for n in nots: + if n in filename: + return False + return True + + +# 获取目录大小 +def getDirSize(filePath, size=0): + for root, dirs, files in os.walk(filePath): + for f in files: + try: + size += os.path.getsize(os.path.join(root, f)) + except Exception as e: + pass + # print(f) + return size + +# 获取目录大小(bash) +def getDirSizeByBash(path): + tmp = mw.execShell('du -sh ' + path) + return tmp[0].split()[0].lower() + +# 计算文件数量 +def getCount(path, search = None): + i = 0 + for name in os.listdir(path): + if name == '.' or name == '..': + continue + if search: + if name.lower().find(search) == -1: + continue + i += 1 + return i + +# 获取文件权限 +def getAccess(fname): + data = {} + try: + stat = os.stat(fname) + data['chmod'] = str(oct(stat.st_mode)[-3:]) + data['chown'] = pwd.getpwuid(stat.st_uid).pw_name + except Exception as e: + # print(e) + data['chmod'] = 755 + data['chown'] = 'www' + return data + +def setFileAccess(filename,user,access): + sall = '-R' + try: + if not checkDir(filename): + return mw.returnData(False, '请不要花样作死') + + if not os.path.exists(filename): + return mw.returnData(False, '指定文件不存在!') + + os.system('chmod ' + sall + ' ' + access + " '" + filename + "'") + os.system('chown ' + sall + ' ' + user + ':' + user + " '" + filename + "'") + msg = mw.getInfo('设置[{1}]权限为[{2}]所有者为[{3}]', (filename, access, user,)) + mw.writeLog('文件管理', msg) + return mw.returnData(True, '设置成功!') + except Exception as e: + return mw.returnData(False, '设置失败!'+str(e)) + +def getSysUserList(): + pwd_file = '/etc/passwd' + if os.path.exists(pwd_file): + content = mw.readFile(pwd_file) + clist = content.split('\n') + sys_users = [] + for line in clist: + if line.find(":")<0: + continue + lines = line.split(":",1) + sys_users.append(lines[0]) + return sys_users + return ['root','mysql','www'] + +def fileDelete(path): + if not os.path.exists(path): + return mw.returnData(False, '指定文件不存在!') + + # 检查是否为.user.ini + if path.find('.user.ini') >= 0: + cmd = "which chattr && chattr -i {0}".format(path) + mw.execShell(cmd) + + try: + recycle_bin = thisdb.getOption('recycle_bin') + if recycle_bin == 'open': + if mvRecycleBin(path): + return mw.returnData(True, '已将文件移动到回收站!') + return mw.returnData(False, '移动到回收站失败!') + os.remove(path) + mw.writeLog('文件管理', mw.getInfo('删除文件[{1}]成功!', (path,))) + return mw.returnData(True, '删除文件成功!') + except Exception as e: + return mw.returnData(False, '删除文件失败!') + +def dirDelete(path): + if not os.path.exists(path): + return mw.returnData(False, '指定文件不存在!') + + # 检查是否为.user.ini + if path.find('.user.ini'): + os.system("which chattr && chattr -i '" + path + "'") + try: + recycle_bin = thisdb.getOption('recycle_bin') + if recycle_bin == 'open': + if mvRecycleBin(path): + return mw.returnData(True, '已将文件移动到回收站!') + mw.execShell('rm -rf ' + path) + mw.writeLog('文件管理', '删除{1}成功!', (path,)) + return mw.returnData(True, '删除文件成功!') + except: + return mw.returnData(False, '删除文件失败!') + +# 关闭 +def toggleRecycleBin(): + recycle_bin = thisdb.getOption('recycle_bin') + if recycle_bin == 'open': + thisdb.setOption('recycle_bin','close') + mw.writeLog('文件管理', '已关闭回收站功能!') + return mw.returnData(True, '已关闭回收站功能!') + else: + thisdb.setOption('recycle_bin','open') + mw.writeLog('文件管理', '已开启回收站功能!') + return mw.returnData(True, '已开启回收站功能!') + +def getRecycleBin(): + rb_dir = mw.getRecycleBinDir() + recycle_bin = thisdb.getOption('recycle_bin') + + data = {} + data['dirs'] = [] + data['files'] = [] + data['status'] = False + if recycle_bin == 'open': + data['status'] = True + + for file in os.listdir(rb_dir): + try: + tmp = {} + fname = rb_dir+'/'+ file + tmp1 = file.split('_mw_') + tmp2 = tmp1[len(tmp1) - 1].split('_t_') + tmp['rname'] = file + tmp['dname'] = file.replace('_mw_', '/').split('_t_')[0] + tmp['name'] = tmp2[0] + tmp['time'] = int(float(tmp2[1])) + if os.path.islink(fname): + filePath = os.readlink(fname) + link = ' -> ' + filePath + if os.path.exists(filePath): + tmp['size'] = os.path.getsize(filePath) + else: + tmp['size'] = 0 + else: + tmp['size'] = os.path.getsize(fname) + if os.path.isdir(fname): + data['dirs'].append(tmp) + else: + data['files'].append(tmp) + except Exception as e: + continue + + return mw.returnJson(True, 'OK', data) + +def delRecycleBin(path): + rb_dir = mw.getRecycleBinDir() + rb_file = rb_dir + '/' + path + if os.path.isdir(rb_file): + import shutil + shutil.rmtree(rb_file) + else: + os.remove(rb_file) + + tfile = path.replace('_mw_', '/').split('_t_')[0] + msg = mw.getInfo('已彻底从回收站删除[{1}]!', (tfile,)) + mw.writeLog('文件管理', msg) + return mw.returnJson(True, msg) + +# 移动到回收站 +def mvRecycleBin(path): + rb_dir = mw.getRecycleBinDir() + rb_file = rb_dir + '/' + path.replace('/', '_mw_') + '_t_' + str(time.time()) + try: + import shutil + shutil.move(path, rb_file) + mw.writeLog('文件管理', mw.getInfo('移动[{1}]到回收站成功!', (path,))) + return True + except Exception as e: + mw.writeLog('文件管理', mw.getInfo('移动[{1}]到回收站失败!', (path,))) + return False + +# 回收站文件恢复 +def reRecycleBin(path): + rb_dir = mw.getRecycleBinDir() + dst_file = path.replace('_mw_', '/').split('_t_')[0] + try: + import shutil + shutil.move(rb_dir + '/' + path, dst_file) + msg = mw.getInfo('移动文件[{1}]到回收站成功!', (dst_file,)) + mw.writeLog('文件管理', msg) + return mw.returnData(True, '恢复成功!') + except Exception as e: + msg = mw.getInfo('从回收站恢复[{1}]失败!', (dst_file,)) + mw.writeLog('文件管理', msg) + return mw.returnData(False, '恢复失败!') + + +def closeRecycleBin(): + rb_dir = mw.getRecycleBinDir() + mw.execShell('which chattr && chattr -R -i ' + rb_dir) + rlist = os.listdir(rb_dir) + i = 0 + l = len(rlist) + for name in rlist: + i += 1 + path = rb_dir + '/' + name + mw.writeSpeed(name, i, l) + if os.path.isdir(path): + shutil.rmtree(path) + else: + os.remove(path) + mw.writeSpeed(None, 0, 0) + mw.writeLog('文件管理', '已清空回收站!') + return mw.returnJson(True, '已清空回收站!') + + +# 设置文件和目录权限 +def setMode(path): + s_path = os.path.dirname(path) + p_stat = os.stat(s_path) + os.chown(path, p_stat.st_uid, p_stat.st_gid) + os.chmod(path, p_stat.st_mode) + + +def closeLogs(): + log_file = mw.getLogsDir() + os.system('rm -rf ' + log_file + '/*') + mw.opWeb('reload') + # os.system('kill -USR1 `cat ' + mw.getServerDir() +'/openresty/nginx/logs/nginx.pid`') + mw.writeLog('文件管理', '网站日志已被清空!') + tmp = getDirSizeByBash(log_file) + return mw.returnData(True, tmp) diff --git a/web/utils/firewall.py b/web/utils/firewall.py new file mode 100644 index 000000000..74f45020a --- /dev/null +++ b/web/utils/firewall.py @@ -0,0 +1,543 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import re +import threading +import re +import time + + +import core.mw as mw +import thisdb + +class Firewall(object): + + __isFirewalld = False + __isIptables = False + __isUfw = False + __isMac = False + + # lock + _instance_lock = threading.Lock() + + @classmethod + def instance(cls, *args, **kwargs): + if not hasattr(Firewall, "_instance"): + with Firewall._instance_lock: + if not hasattr(Firewall, "_instance"): + Firewall._instance = Firewall(*args, **kwargs) + return Firewall._instance + + def __init__(self): + iptables_file = mw.systemdCfgDir() + '/iptables.service' + if os.path.exists('/usr/sbin/firewalld'): + self.__isFirewalld = True + elif os.path.exists('/usr/sbin/ufw'): + self.__isUfw = True + elif os.path.exists(iptables_file): + self.__isIptables = True + elif mw.isAppleSystem(): + self.__isMac = True + + # 自动识别防火墙配置 | Automatically identify firewall + def aIF(self): + if self.__isFirewalld: + self.AIF_Firewalld() + if self.__isUfw: + self.aIF_Ufw() + + + def aIF_Ufw(self): + t = mw.execShell("ufw status|awk '{print $1}' | grep -v 'Status'|grep -v 'To'|grep -v '-'") + if t[1] != '': + return True + + all_port = t[0].strip() + ports_list = all_port.split('\n') + + + ports_all = [] + for pinfo in ports_list: + if pinfo.strip() == "": + continue + + info = pinfo.split('/') + if len(info) != 2: + continue + + is_same = False + for i in range(len(ports_all)): + if ports_all[i]['port'] == info[0] and ports_all[i]['protocol'] != info[1]: + ports_all[i]['protocol'] = ports_all[i]['protocol']+'/'+info[1] + is_same = True + + if not is_same: + t = {} + t['port'] = info[0].replace('-',':') + t['protocol'] = info[1] + ports_all.append(t) + for add_info in ports_all: + if thisdb.getFirewallCountByPort(add_info['port']) == 0: + thisdb.addFirewall(add_info['port'], ps='自动识别',protocol=add_info['protocol']) + + def AIF_Firewalld(self): + t = mw.execShell("firewall-cmd --list-all | grep ' ports'") + if t[1] != '': + return True + + all_port = t[0].strip() + data = all_port.split(":") + if len(data) < 2: + return True + ports_str = data[1] + if not ports_str.strip(): + return True + ports_list = ports_str.strip().split(' ') + + ports_all = [] + for pinfo in ports_list: + info = pinfo.split('/') + if len(info) < 2: + continue + + is_same = False + for i in range(len(ports_all)): + if ports_all[i]['port'] == info[0] and ports_all[i]['protocol'] != info[1]: + ports_all[i]['protocol'] = ports_all[i]['protocol']+'/'+info[1] + is_same = True + + if not is_same: + t = {} + t['port'] = info[0].replace('-',':') + t['protocol'] = info[1] + ports_all.append(t) + + for add_info in ports_all: + if thisdb.getFirewallCountByPort(add_info['port']) == 0: + thisdb.addFirewall(add_info['port'], ps='自动识别',protocol=add_info['protocol']) + + def getList(self, page=1,size=10): + info = thisdb.getFirewallList(page=page, size=size) + + rdata = {} + rdata['data'] = info['list'] + rdata['page'] = mw.getPage({'count':info['count'],'tojs':'showAccept','p':page,'row':size}) + return rdata + + def reload(self): + if self.__isUfw: + mw.execShell('/usr/sbin/ufw reload') + return + elif self.__isIptables: + mw.execShell('service iptables save') + mw.execShell('service iptables restart') + elif self.__isFirewalld: + mw.execShell('firewall-cmd --reload') + else: + pass + + def reloadSshd(self): + if self.__isUfw: + mw.execShell("service ssh restart") + elif self.__isIptables: + mw.execShell("/etc/init.d/sshd restart") + elif self.__isFirewalld: + mw.execShell("systemctl restart sshd.service") + else: + return False + return True + + def getFwStatus(self): + if self.__isUfw: + cmd = "/usr/sbin/ufw status| grep Status | awk -F ':' '{print $2}'" + data = mw.execShell(cmd) + if data[0].strip() == 'inactive': + return False + return True + elif self.__isIptables: + cmd = "systemctl status iptables | grep 'inactive'" + data = mw.execShell(cmd) + if data[0] != '': + return False + return True + elif self.__isFirewalld: + cmd = "ps -ef|grep firewalld |grep -v grep | awk '{print $2}'" + data = mw.execShell(cmd) + if data[0] == '': + return False + return True + else: + return False + + + def getSshInfo(self): + data = {} + + isPing = True + try: + if mw.isAppleSystem(): + isPing = True + else: + file = '/etc/sysctl.conf' + sys_conf = mw.readFile(file) + rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)" + tmp = re.search(rep, sys_conf).groups(0)[0] + if tmp == '1': + isPing = False + except: + isPing = True + + # sshd 检测 + status = True + cmd = "service sshd status | grep -P '(dead|stop)'|grep -v grep" + ssh_status = mw.execShell(cmd) + if ssh_status[0] != '': + status = False + + cmd = "systemctl status sshd | grep 'dead'|grep -v grep" + ssh_status = mw.execShell(cmd) + if ssh_status[0] != '': + status = False + + data['pubkey_prohibit_status'] = False + data['pass_prohibit_status'] = False + data['root_prohibit_status'] = False + port = '22' + sshd_file = '/etc/ssh/sshd_config' + if os.path.exists(sshd_file): + conf = mw.readFile(sshd_file) + rep = r"#*Port\s+([0-9]+)\s*\n" + port = re.search(rep, conf).groups(0)[0] + + # 密码登陆配置检查 + pass_rep = r"PasswordAuthentication\s+(\w*)\s*\n" + pass_status = re.search(pass_rep, conf) + if pass_status: + if pass_status and pass_status.groups(0)[0].strip() == 'no': + data['pass_prohibit_status'] = True + else: + data['pass_prohibit_status'] = True + + # 密钥登陆配置检查 + pass_rep = r"PubkeyAuthentication\s+(\w*)\s*\n" + pass_status = re.search(pass_rep, conf) + if pass_status: + if pass_status and pass_status.groups(0)[0].strip() == 'no': + data['pubkey_prohibit_status'] = True + else: + data['pubkey_prohibit_status'] = True + + # root登陆配置检查 + root_rep = r"PermitRootLogin\s+(\w*)\s*\n" + root_status = re.search(root_rep, conf) + if root_status: + if root_status and root_status.groups(0)[0].strip() == 'no': + data['root_prohibit_status'] = True + else: + data['root_prohibit_status'] = True + + data['port'] = port + data['status'] = status + data['ping'] = isPing + if mw.isAppleSystem(): + data['firewall_status'] = False + else: + data['firewall_status'] = self.getFwStatus() + return data + + def setPing(self, status): + if mw.isAppleSystem(): + return mw.returnData(True, '开发机不能操作!') + + filename = '/etc/sysctl.conf' + conf = mw.readFile(filename) + if conf.find('net.ipv4.icmp_echo') != -1: + rep = r"net\.ipv4\.icmp_echo.*" + conf = re.sub(rep, 'net.ipv4.icmp_echo_ignore_all=' + status, conf) + else: + conf += "\nnet.ipv4.icmp_echo_ignore_all=" + status + + mw.writeFile(filename, conf) + mw.execShell('sysctl -p') + return mw.returnData(True, '设置成功!') + + def setSshPort(self, port): + if int(port) < 22 or int(port) > 65535: + return mw.returnData(False, '端口范围必需在22-65535之间!') + + ports = ['21', '25', '80', '443', '888'] + if port in ports: + return mw.returnData(False, '(' + port + ')' + '特殊端口不可设置!') + + file = '/etc/ssh/sshd_config' + conf = mw.readFile(file) + + rep = r"#*Port\s+([0-9]+)\s*\n" + conf = re.sub(rep, "Port " + port + "\n", conf) + mw.writeFile(file, conf) + + self.addAcceptPort(port, 'SSH端口修改', 'port') + self.reload() + + if not self.reloadSshd(): + return mw.returnData(False, '重启sshd失败,尝试手动重启:service ssh restart!') + return mw.returnData(True, '修改成功!') + + def setFw(self, status): + if self.__isIptables: + self.setFwIptables(status) + return mw.returnData(True, '设置成功!') + + if status == '1': + if self.__isUfw: + mw.execShell('/usr/sbin/ufw disable') + elif self.__isFirewalld: + mw.execShell('systemctl stop firewalld.service') + mw.execShell('systemctl disable firewalld.service') + else: + pass + else: + if self.__isUfw: + mw.execShell("echo 'y'| ufw enable") + elif self.__isFirewalld: + mw.execShell('systemctl start firewalld.service') + mw.execShell('systemctl enable firewalld.service') + else: + pass + return mw.returnData(True, '设置成功!') + + def addAcceptPortCmd(self, port, protocol ='tcp'): + if self.__isUfw: + if protocol == 'tcp': + mw.execShell('ufw allow ' + port + '/tcp') + if protocol == 'udp': + mw.execShell('ufw allow ' + port + '/udp') + if protocol == 'tcp/udp': + mw.execShell('ufw allow ' + port + '/tcp') + mw.execShell('ufw allow ' + port + '/udp') + elif self.__isFirewalld: + port = port.replace(':', '-') + if protocol == 'tcp': + cmd = 'firewall-cmd --permanent --zone=public --add-port=' + port + '/tcp' + mw.execShell(cmd) + if protocol == 'udp': + cmd = 'firewall-cmd --permanent --zone=public --add-port=' + port + '/udp' + mw.execShell(cmd) + if protocol == 'tcp/udp': + cmd = 'firewall-cmd --permanent --zone=public --add-port=' + port + '/tcp' + mw.execShell(cmd) + cmd = 'firewall-cmd --permanent --zone=public --add-port=' + port + '/udp' + mw.execShell(cmd) + elif self.__isIptables: + if protocol == 'tcp': + cmd = 'iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT' + mw.execShell(cmd) + if protocol == 'udp': + cmd = 'iptables -I INPUT -p udp -m state --state NEW -m udp --dport ' + port + ' -j ACCEPT' + mw.execShell(cmd) + if protocol == 'tcp/udp': + cmd = 'iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT' + mw.execShell(cmd) + cmd = 'iptables -I INPUT -p udp -m state --state NEW -m udp --dport ' + port + ' -j ACCEPT' + mw.execShell(cmd) + else: + pass + return True + + # 添加放行端口 + def addAcceptPort(self, port, ps, stype, + protocol='tcp' + ): + if not self.getFwStatus(): + self.setFw(0) + return mw.returnData(False, '防火墙启动时,才能添加规则!') + + rep = r"^\d{1,5}(:\d{1,5})?$" + if not re.search(rep, port): + return mw.returnData(False, '端口范围不正确!') + + if thisdb.getFirewallCountByPort(port) > 0: + return mw.returnData(False, '您要放行的端口已存在,无需重复放行!') + + thisdb.addFirewall(port, ps=ps,protocol=protocol) + self.addAcceptPortCmd(port, protocol=protocol) + self.reload() + + msg = mw.getInfo('放行端口[{1}][{2}]成功', (port, protocol,)) + mw.writeLog("防火墙管理", msg) + return mw.returnData(True, msg) + + def addPanelPort(self, port): + self.setFw(0) + + protocol = 'tcp' + ps = 'PANEL端口' + + if thisdb.getFirewallCountByPort(port) > 0: + return mw.returnData(False, '您要放行的端口已存在,无需重复放行!') + + thisdb.addFirewall(port, ps=ps,protocol=protocol) + self.addAcceptPortCmd(port, protocol=protocol) + self.reload() + + msg = mw.getInfo('放行端口[{1}][{2}]成功', (port, protocol,)) + mw.writeLog("防火墙管理", msg) + return mw.returnData(True, msg) + + def delAcceptPort(self, firewall_id, port, + protocol='tcp' + ): + panel_port = mw.getPanelPort() + + if port.find(':') > 0: + pass + elif port.find('-') > 0: + pass + else: + if(int(port) == int(panel_port)): + return mw.returnData(False, '失败,不能删除当前面板端口!') + try: + self.delAcceptPortCmd(port, protocol) + mw.M('firewall').where("id=?", (firewall_id,)).delete() + return mw.returnData(True, '删除成功!') + except Exception as e: + return mw.returnData(False, '删除失败!:' + str(e)) + + def delAcceptPortCmd(self, port, + protocol ='tcp' + ): + if self.__isUfw: + if protocol == 'tcp': + mw.execShell('ufw delete allow ' + port + '/tcp') + if protocol == 'udp': + mw.execShell('ufw delete allow ' + port + '/udp') + if protocol == 'tcp/udp': + mw.execShell('ufw delete allow ' + port + '/tcp') + mw.execShell('ufw delete allow ' + port + '/udp') + elif self.__isFirewalld: + port = port.replace(':', '-') + if protocol == 'tcp': + mw.execShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + port + '/tcp') + if protocol == 'udp': + mw.execShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + port + '/udp') + if protocol == 'tcp/udp': + mw.execShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + port + '/tcp') + mw.execShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + port + '/udp') + elif self.__isIptables: + if protocol == 'tcp': + mw.execShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT') + if protocol == 'udp': + mw.execShell( + 'iptables -D INPUT -p udp -m state --state NEW -m udp --dport ' + port + ' -j ACCEPT') + if protocol == 'tcp/udp': + mw.execShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT') + mw.execShell( + 'iptables -D INPUT -p udp -m state --state NEW -m udp --dport ' + port + ' -j ACCEPT') + else: + pass + + mw.M('firewall').where("port=?", (port,)).delete() + msg = mw.getInfo('删除防火墙放行端口[{1}][{2}]成功!', (port, protocol,)) + mw.writeLog("防火墙管理", msg) + self.reload() + return True + + def setSshRootStatus(self, status): + msg = '禁止root登陆成功' + if status == "1": + msg = '开启root登陆成功' + + file = '/etc/ssh/sshd_config' + if not os.path.exists(file): + return mw.returnJson(False, '无法设置!') + + conf = mw.readFile(file) + + root_rep = r"PermitRootLogin\s+(\w*)\s*\n" + root_status = re.search(root_rep, conf) + if not root_status: + rep = r"(#)?PermitRootLogin\s+(\w*)\s*\n" + conf = re.sub(rep, "PermitRootLogin yes\n", conf) + + if status == '1': + rep = r"PermitRootLogin\s+(\w*)\s*\n" + conf = re.sub(rep, "PermitRootLogin yes\n", conf) + else: + rep = r"PermitRootLogin\s+(\w*)\s*\n" + conf = re.sub(rep, "PermitRootLogin no\n", conf) + mw.writeFile(file, conf) + mw.execShell("systemctl restart sshd") + mw.writeLog("SSH管理", msg) + return mw.returnData(True, msg) + + def setSshPassStatus(self, status): + msg = '禁止密码登陆成功' + if status == "1": + msg = '开启密码登陆成功' + + file = '/etc/ssh/sshd_config' + if not os.path.exists(file): + return mw.returnJson(False, '无法设置!') + + conf = mw.readFile(file) + + pass_rep = r"PasswordAuthentication\s+(\w*)\s*\n" + pass_status = re.search(pass_rep, conf) + if not pass_status: + rep = r"(#)?PasswordAuthentication\s+(\w*)\s*\n" + conf = re.sub(rep, "PasswordAuthentication yes\n", conf) + + if status == '1': + rep = r"PasswordAuthentication\s+(\w*)\s*\n" + conf = re.sub(rep, "PasswordAuthentication yes\n", conf) + else: + rep = r"PasswordAuthentication\s+(\w*)\s*\n" + conf = re.sub(rep, "PasswordAuthentication no\n", conf) + mw.writeFile(file, conf) + mw.execShell("systemctl restart sshd") + mw.writeLog("SSH管理", msg) + return mw.returnData(True, msg) + + def setSshPubkeyStatus(self, status): + msg = '禁止密钥登陆成功' + if status == "1": + msg = '开启密钥登陆成功' + + file = '/etc/ssh/sshd_config' + if not os.path.exists(file): + return mw.returnJson(False, '无法设置!') + + content = mw.readFile(file) + + pubkey_rep = r"PubkeyAuthentication\s+(\w*)\s*\n" + pubkey_status = re.search(pubkey_rep, content) + if not pubkey_status: + rep = r"(#)?PubkeyAuthentication\s+(\w*)\s*\n" + content = re.sub(rep, "PubkeyAuthentication yes\n", content) + + if status == '1': + rep = r"PubkeyAuthentication\s+(\w*)\s*\n" + content = re.sub(rep, "PubkeyAuthentication yes\n", content) + else: + rep = r"PubkeyAuthentication\s+(\w*)\s*\n" + content = re.sub(rep, "PubkeyAuthentication no\n", content) + mw.writeFile(file, content) + mw.execShell("systemctl restart sshd") + mw.writeLog("SSH管理", msg) + return mw.returnData(True, msg) + + diff --git a/web/utils/page.py b/web/utils/page.py new file mode 100755 index 000000000..e2ccdb8a1 --- /dev/null +++ b/web/utils/page.py @@ -0,0 +1,245 @@ +# coding: utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 分页库操作 +# --------------------------------------------------------------------------------- + + +import math +import string + + +class Page(): + #-------------------------- + # 分页类 - JS回调版 + #-------------------------- + __PREV = '上一页' + __NEXT = '下一页' + __START = '首页' + __END = '尾页' + __COUNT_START = '共' + __COUNT_END = '条数据' + __FO = '从' + __LINE = '条' + __LIST_NUM = 4 + SHIFT = None # 偏移量 + ROW = None # 每页行数 + __C_PAGE = None # 当前页 + __COUNT_PAGE = None # 总页数 + __COUNT_ROW = None # 总行数 + __URI = None # URI + __RTURN_JS = False # 是否返回JS回调 + __START_NUM = None # 起始行 + __END_NUM = None # 结束行 + __ARGS_TPL = '' + + def __init__(self): + if False: + self.__PREV = tmp['PREV'] + self.__NEXT = tmp['NEXT'] + self.__START = tmp['START'] + self.__END = tmp['END'] + self.__COUNT_START = tmp['COUNT_START'] + self.__COUNT_END = tmp['COUNT_END'] + self.__FO = tmp['FO'] + self.__LINE = tmp['LINE'] + + def getPageNum(self, num): + return str(num) + self.__ARGS_TPL + + def GetPage(self, pageInfo, limit='1,2,3,4,5,6,7,8'): + # 取分页信息 + # @param pageInfo 传入分页参数字典 + # @param limit 返回系列 + self.__RTURN_JS = pageInfo['return_js'] + self.__COUNT_ROW = pageInfo['count'] + + if 'args_tpl' in pageInfo: + self.__ARGS_TPL = pageInfo['args_tpl'] + + self.ROW = pageInfo['row'] + self.__C_PAGE = self.__GetCpage(pageInfo['p']) + self.__START_NUM = self.__StartRow() + self.__END_NUM = self.__EndRow() + self.__COUNT_PAGE = self.__GetCountPage() + self.__URI = self.__SetUri(pageInfo['uri']) + self.SHIFT = self.__START_NUM - 1 + + keys = limit.split(',') + pages = {} + # 起始页 + pages['1'] = self.__GetStart() + # 上一页 + pages['2'] = self.__GetPrev() + # 分页 + pages['3'] = self.__GetPages() + # 下一页 + pages['4'] = self.__GetNext() + # 尾页 + pages['5'] = self.__GetEnd() + + # 当前显示页与总页数 + pages['6'] = "" + \ + str(self.__C_PAGE) + "/" + \ + str(self.__COUNT_PAGE) + "" + + # 本页显示开始与结束行 + pages['7'] = "" + str(self.__FO) + \ + str(self.__START_NUM) + "-" + \ + str(self.__END_NUM) + str(self.__LINE) + "" + # 行数 + pages['8'] = "" + str(self.__COUNT_START) + \ + str(self.__COUNT_ROW) + str(self.__COUNT_END) + "" + + # 构造返回数据 + retuls = '
                                            ' + for value in keys: + retuls += pages[value] + retuls += '
                                            ' + + # 返回分页数据 + return retuls + + def __GetEnd(self): + # 构造尾页 + endStr = "" + if self.__C_PAGE >= self.__COUNT_PAGE: + endStr = '' + else: + if self.__RTURN_JS == "": + endStr = "" + str(self.__END) + "" + else: + endStr = "" + \ + str(self.__END) + "" + return endStr + + def __GetNext(self): + # 构造下一页 + nextStr = "" + if self.__C_PAGE >= self.__COUNT_PAGE: + nextStr = '' + else: + if self.__RTURN_JS == "": + nextStr = "" + str(self.__NEXT) + "" + else: + nextStr = "" + \ + str(self.__NEXT) + "" + + return nextStr + + def __GetPages(self): + # 构造分页 + pages = '' + num = 0 + # 当前页之前 + if (self.__COUNT_PAGE - self.__C_PAGE) < self.__LIST_NUM: + num = self.__LIST_NUM + \ + (self.__LIST_NUM - (self.__COUNT_PAGE - self.__C_PAGE)) + else: + num = self.__LIST_NUM + n = 0 + for i in range(num): + n = num - i + page = self.__C_PAGE - n + if page > 0: + if self.__RTURN_JS == "": + pages += "" + str(page) + "" + else: + pages += "" + str(page) + "" + + # 当前页 + if self.__C_PAGE > 0: + pages += "" + \ + str(self.__C_PAGE) + "" + + # 当前页之后 + if self.__C_PAGE <= self.__LIST_NUM: + num = self.__LIST_NUM + (self.__LIST_NUM - self.__C_PAGE) + 1 + else: + num = self.__LIST_NUM + for i in range(num): + if i == 0: + continue + page = self.__C_PAGE + i + if page > self.__COUNT_PAGE: + break + if self.__RTURN_JS == "": + pages += "" + str(page) + "" + else: + pages += "" + str(page) + "" + + return pages + + def __GetPrev(self): + # 构造上一页 + startStr = '' + if self.__C_PAGE == 1: + startStr = '' + else: + if self.__RTURN_JS == "": + startStr = "" + str(self.__PREV) + "" + else: + startStr = "" + \ + str(self.__PREV) + "" + return startStr + + def __GetStart(self): + # 构造起始分页 + startStr = '' + if self.__C_PAGE == 1: + startStr = '' + else: + if self.__RTURN_JS == "": + startStr = "" + str(self.__START) + "" + else: + startStr = "" + str(self.__START) + "" + return startStr + + def __GetCpage(self, p): + # 取当前页 + if p: + return p + return 1 + + def __StartRow(self): + # 从多少行开始 + return (self.__C_PAGE - 1) * self.ROW + 1 + + def __EndRow(self): + # 从多少行结束 + if self.ROW > self.__COUNT_ROW: + return self.__COUNT_ROW + return self.__C_PAGE * self.ROW + + def __GetCountPage(self): + # 取总页数 + return int(math.ceil(self.__COUNT_ROW / float(self.ROW))) + + def __SetUri(self, sinput): + # 构造URI + uri = '?' + for key in sinput: + if key == 'p': + continue + uri += key + '=' + sinput[key] + '&' + return str(uri) diff --git a/web/utils/php/fcgi_client.py b/web/utils/php/fcgi_client.py new file mode 100644 index 000000000..f1e0c67e7 --- /dev/null +++ b/web/utils/php/fcgi_client.py @@ -0,0 +1,408 @@ +# Copyright (c) 2006 Allan Saddi +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $Id$ +# +# Copyright (c) 2011 Vladimir Rusinov + +__author__ = 'Allan Saddi ' +__version__ = '$Revision$' +import sys +import select +import struct +import socket +import errno +import types + + +__all__ = ['FCGIApp'] + +# Constants from the spec. +FCGI_LISTENSOCK_FILENO = 0 + +FCGI_HEADER_LEN = 8 + +FCGI_VERSION_1 = 1 + +FCGI_BEGIN_REQUEST = 1 +FCGI_ABORT_REQUEST = 2 +FCGI_END_REQUEST = 3 +FCGI_PARAMS = 4 +FCGI_STDIN = 5 +FCGI_STDOUT = 6 +FCGI_STDERR = 7 +FCGI_DATA = 8 +FCGI_GET_VALUES = 9 +FCGI_GET_VALUES_RESULT = 10 +FCGI_UNKNOWN_TYPE = 11 +FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE + +FCGI_NULL_REQUEST_ID = 0 + +FCGI_KEEP_CONN = 1 + +FCGI_RESPONDER = 1 +FCGI_AUTHORIZER = 2 +FCGI_FILTER = 3 + +FCGI_REQUEST_COMPLETE = 0 +FCGI_CANT_MPX_CONN = 1 +FCGI_OVERLOADED = 2 +FCGI_UNKNOWN_ROLE = 3 + +FCGI_MAX_CONNS = 'FCGI_MAX_CONNS' +FCGI_MAX_REQS = 'FCGI_MAX_REQS' +FCGI_MPXS_CONNS = 'FCGI_MPXS_CONNS' + +FCGI_Header = '!BBHHBx' +FCGI_BeginRequestBody = '!HB5x' +FCGI_EndRequestBody = '!LB3x' +FCGI_UnknownTypeBody = '!B7x' + +FCGI_BeginRequestBody_LEN = struct.calcsize(FCGI_BeginRequestBody) +FCGI_EndRequestBody_LEN = struct.calcsize(FCGI_EndRequestBody) +FCGI_UnknownTypeBody_LEN = struct.calcsize(FCGI_UnknownTypeBody) + +if __debug__: + import time + + # Set non-zero to write debug output to a file. + DEBUG = 0 + DEBUGLOG = '/www/server/mdserver-web/logs/fastcgi.log' + + def _debug(level, msg): + if DEBUG < level: + return + + try: + f = open(DEBUGLOG, 'a') + f.write('%sfcgi: %s\n' % (time.ctime()[4:-4], msg)) + f.close() + except: + pass + + +def decode_pair(s, pos=0): + """ + Decodes a name/value pair. + + The number of bytes decoded as well as the name/value pair + are returned. + """ + nameLength = ord(s[pos]) + if nameLength & 128: + nameLength = struct.unpack('!L', s[pos:pos + 4])[0] & 0x7fffffff + pos += 4 + else: + pos += 1 + + valueLength = ord(s[pos]) + if valueLength & 128: + valueLength = struct.unpack('!L', s[pos:pos + 4])[0] & 0x7fffffff + pos += 4 + else: + pos += 1 + + name = s[pos:pos + nameLength] + pos += nameLength + value = s[pos:pos + valueLength] + pos += valueLength + + return (pos, (name, value)) + + +def encode_pair(name, value): + """ + Encodes a name/value pair. + + The encoded string is returned. + """ + nameLength = len(name) + if nameLength < 128: + s = chr(nameLength).encode() + else: + s = struct.pack('!L', nameLength | 0x80000000) + + valueLength = len(value) + if valueLength < 128: + s += chr(valueLength).encode() + else: + s += struct.pack('!L', valueLength | 0x80000000) + + return s + name + value + + +class Record(object): + """ + A FastCGI Record. + + Used for encoding/decoding records. + """ + + def __init__(self, type=FCGI_UNKNOWN_TYPE, requestId=FCGI_NULL_REQUEST_ID): + self.version = FCGI_VERSION_1 + self.type = type + self.requestId = requestId + self.contentLength = 0 + self.paddingLength = 0 + self.contentData = '' + + def _recvall(sock, length): + """ + Attempts to receive length bytes from a socket, blocking if necessary. + (Socket may be blocking or non-blocking.) + """ + dataList = [] + recvLen = 0 + while length: + try: + data = sock.recv(length) + except socket.error as e: + if e[0] == errno.EAGAIN: + select.select([sock], [], []) + continue + else: + raise + if not data: # EOF + break + dataList.append(data) + dataLen = len(data) + recvLen += dataLen + length -= dataLen + return b''.join(dataList), recvLen + _recvall = staticmethod(_recvall) + + def read(self, sock): + """Read and decode a Record from a socket.""" + try: + header, length = self._recvall(sock, FCGI_HEADER_LEN) + except: + raise EOFError + + if length < FCGI_HEADER_LEN: + raise EOFError + + self.version, self.type, self.requestId, self.contentLength, \ + self.paddingLength = struct.unpack(FCGI_Header, header) + + if __debug__: + _debug(9, 'read: fd = %d, type = %d, requestId = %d, ' + 'contentLength = %d' % + (sock.fileno(), self.type, self.requestId, + self.contentLength)) + + if self.contentLength: + try: + self.contentData, length = self._recvall(sock, + self.contentLength) + except: + raise EOFError + + if length < self.contentLength: + raise EOFError + + if self.paddingLength: + try: + self._recvall(sock, self.paddingLength) + except: + raise EOFError + + def _sendall(sock, data): + """ + Writes data to a socket and does not return until all the data is sent. + """ + length = len(data) + while length: + try: + sent = sock.send(data) + except socket.error as e: + if e[0] == errno.EAGAIN: + select.select([], [sock], []) + continue + else: + raise + data = data[sent:] + length -= sent + _sendall = staticmethod(_sendall) + + def write(self, sock): + """Encode and write a Record to a socket.""" + self.paddingLength = -self.contentLength & 7 + + if __debug__: + _debug(9, 'write: fd = %d, type = %d, requestId = %d, ' + 'contentLength = %d' % + (sock.fileno(), self.type, self.requestId, + self.contentLength)) + + header = struct.pack(FCGI_Header, self.version, self.type, + self.requestId, self.contentLength, + self.paddingLength) + self._sendall(sock, header) + if self.contentLength: + self._sendall(sock, self.contentData) + if self.paddingLength: + self._sendall(sock, b'\x00' * self.paddingLength) + + +class FCGIApp(object): + + def __init__(self, connect=None, host=None, port=None, filterEnviron=True): + if host is not None: + assert port is not None + connect = (host, port) + + self._connect = connect + self._filterEnviron = filterEnviron + + def __call__(self, environ, io, start_response=None): + # For sanity's sake, we don't care about FCGI_MPXS_CONN + # (connection multiplexing). For every request, we obtain a new + # transport socket, perform the request, then discard the socket. + # This is, I believe, how mod_fastcgi does things... + + sock = self._getConnection() + + # Since this is going to be the only request on this connection, + # set the request ID to 1. + requestId = 1 + + # Begin the request + rec = Record(FCGI_BEGIN_REQUEST, requestId) + rec.contentData = struct.pack(FCGI_BeginRequestBody, FCGI_RESPONDER, 0) + rec.contentLength = FCGI_BeginRequestBody_LEN + rec.write(sock) + + # Filter WSGI environ and send it as FCGI_PARAMS + if self._filterEnviron: + params = self._defaultFilterEnviron(environ) + else: + params = self._lightFilterEnviron(environ) + # TODO: Anything not from environ that needs to be sent also? + # return '200 OK',[],str(params),'' + self._fcgiParams(sock, requestId, params) + self._fcgiParams(sock, requestId, {}) + + # Transfer wsgi.input to FCGI_STDIN + content_length = int(environ.get('CONTENT_LENGTH') or 0) + s = '' + #io = StringIO(stdin) + while True: + if not io: + break + chunk_size = min(content_length, 4096) + s = io.read(chunk_size) + content_length -= len(s) + rec = Record(FCGI_STDIN, requestId) + rec.contentData = s + rec.contentLength = len(s) + rec.write(sock) + if not s: + break + # Empty FCGI_DATA stream + rec = Record(FCGI_DATA, requestId) + rec.write(sock) + return sock + + def _getConnection(self): + if self._connect is not None: + # The simple case. Create a socket and connect to the + # application. + if isinstance(self._connect, str): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(self._connect) + elif hasattr(socket, 'create_connection'): + sock = socket.create_connection(self._connect) + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(self._connect) + return sock + + # To be done when I have more time... + # , 'Launching and managing FastCGI programs not yet implemented' + raise NotImplementedError + + def _fcgiGetValues(self, sock, vars): + # Construct FCGI_GET_VALUES record + outrec = Record(FCGI_GET_VALUES) + data = [] + for name in vars: + data.append(encode_pair(name, '')) + data = ''.join(data) + outrec.contentData = data + outrec.contentLength = len(data) + outrec.write(sock) + + # Await response + inrec = Record() + inrec.read(sock) + result = {} + if inrec.type == FCGI_GET_VALUES_RESULT: + pos = 0 + while pos < inrec.contentLength: + pos, (name, value) = decode_pair(inrec.contentData, pos) + result[name] = value + return result + + def _fcgiParams(self, sock, requestId, params): + rec = Record(FCGI_PARAMS, requestId) + data = [] + for name, value in params.items(): + data.append(encode_pair(name.encode( + 'latin-1'), value.encode('latin-1'))) + data = b''.join(data) + rec.contentData = data + rec.contentLength = len(data) + rec.write(sock) + + _environPrefixes = ['SERVER_', 'HTTP_', 'REQUEST_', 'REMOTE_', 'PATH_', + 'CONTENT_', 'DOCUMENT_', 'SCRIPT_'] + _environCopies = ['SCRIPT_NAME', 'QUERY_STRING', 'AUTH_TYPE'] + _environRenames = [] + + def _defaultFilterEnviron(self, environ): + result = {} + for n in environ.keys(): + iv = False + for p in self._environPrefixes: + if n.startswith(p): + result[n] = environ[n] + iv = True + if n in self._environCopies: + result[n] = environ[n] + iv = True + if n in self._environRenames: + result[self._environRenames[n]] = environ[n] + iv = True + if not iv: + result[n] = environ[n] + + return result + + def _lightFilterEnviron(self, environ): + result = {} + for n in environ.keys(): + if n.upper() == n: + result[n] = environ[n] + return result diff --git a/web/utils/php/fpm.py b/web/utils/php/fpm.py new file mode 100644 index 000000000..66f72494e --- /dev/null +++ b/web/utils/php/fpm.py @@ -0,0 +1,209 @@ +# coding:utf-8 + + +import json +import os +import time +import re +import sys +import time +import struct + +from .fcgi_client import FCGIApp + +FCGI_Header = '!BBHHBx' + +if sys.version_info[0] == 2: + try: + from cStringIO import StringIO + except: + from StringIO import StringIO +else: + from io import BytesIO as StringIO + + +def get_header_data(sock): + ''' + @name 获取头部32KB数据 + @param sock socketobject(fastcgi套接字对象) + @return bytes + ''' + headers_data = b'' + total_len = 0 + header_len = 1024 * 128 + while True: + fastcgi_header = sock.recv(8) + if not fastcgi_header: + break + if len(fastcgi_header) != 8: + headers_data += fastcgi_header + break + fast_pack = struct.unpack(FCGI_Header, fastcgi_header) + if fast_pack[1] == 3: + break + + tlen = fast_pack[3] + while tlen > 0: + sd = sock.recv(tlen) + if not sd: + break + headers_data += sd + tlen -= len(sd) + + total_len += fast_pack[3] + if fast_pack[4]: + sock.recv(fast_pack[4]) + if total_len > header_len: + break + return headers_data + + +def format_header_data(headers_data): + ''' + @name 格式化响应头 + @param headers_data bytes(fastcgi头部32KB数据) + @return status int(响应状态), headers dict(响应头), bdata bytes(格式化响应头后的多余数据) + ''' + status = '200 OK' + headers = {} + pos = 0 + while True: + eolpos = headers_data.find(b'\n', pos) + if eolpos < 0: + break + line = headers_data[pos:eolpos - 1] + pos = eolpos + 1 + line = line.strip() + if len(line) < 2: + break + if line.find(b':') == -1: + continue + header, value = line.split(b':', 1) + header = header.strip() + value = value.strip() + if isinstance(header, bytes): + header = header.decode() + value = value.decode() + if header == 'Status': + status = value + if status.find(' ') < 0: + status += ' BTPanel' + else: + headers[header] = value + bdata = headers_data[pos:] + status = int(status.split(' ')[0]) + return status, headers, bdata + + +def resp_sock(sock, bdata): + ''' + @name 以流的方式发送剩余数据 + @param sock socketobject(fastcgi套接字对象) + @param bdata bytes(格式化响应头后的多余数据) + @return yield bytes + ''' + # 发送除响应头以外的多余头部数据 + yield bdata + while True: + fastcgi_header = sock.recv(8) + if not fastcgi_header: + break + if len(fastcgi_header) != 8: + yield fastcgi_header + break + fast_pack = struct.unpack(FCGI_Header, fastcgi_header) + if fast_pack[1] == 3: + break + tlen = fast_pack[3] + while tlen > 0: + sd = sock.recv(tlen) + if not sd: + break + tlen -= len(sd) + if sd: + yield sd + + if fast_pack[4]: + sock.recv(fast_pack[4]) + sock.close() + + +class fpm(object): + + def __init__(self, sock=None, document_root='', last_path=''): + ''' + @name 实例化FPM对象 + @param sock string(unixsocket路径) + @param document_root string(PHP文档根目录) + @return FPM + ''' + if sock: + self.fcgi_sock = sock + if document_root[-1:] != '/': + document_root += '/' + self.document_root = document_root + self.last_path = last_path + + def load_url_public(self, url, content=b'', method='GET', content_type='application/x-www-form-urlencoded'): + ''' + @name 转发URL到PHP-FPM 公共 + @param url string(URI地址) + @param content stream(POST数据io对象) + @return fastcgi-socket + ''' + fcgi = FCGIApp(connect=self.fcgi_sock) + try: + script_name, query_string = url.split('?') + except ValueError: + script_name = url + query_string = '' + + content_length = len(content) + if content: + content = StringIO(content) + + env = { + 'SCRIPT_FILENAME': '%s%s' % (self.document_root, script_name), + 'QUERY_STRING': query_string, + 'REQUEST_METHOD': method, + 'SCRIPT_NAME': self.last_path + script_name, + 'REQUEST_URI': url, + 'GATEWAY_INTERFACE': 'CGI/1.1', + 'SERVER_SOFTWARE': 'MW-PANEL', + 'REDIRECT_STATUS': '200', + 'CONTENT_TYPE': content_type, + 'CONTENT_LENGTH': str(content_length), + 'DOCUMENT_URI': script_name, + 'DOCUMENT_ROOT': self.document_root, + 'SERVER_PROTOCOL': 'HTTP/1.1', + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '7200', + 'SERVER_ADDR': '127.0.0.1', + 'SERVER_PORT': '80', + 'SERVER_NAME': 'MW-Panel' + } + + fpm_sock = fcgi(env, content) + + _data = b'' + while True: + fastcgi_header = fpm_sock.recv(8) + if not fastcgi_header: + break + if len(fastcgi_header) != 8: + _data += fastcgi_header + break + fast_pack = struct.unpack(FCGI_Header, fastcgi_header) + if fast_pack[1] == 3: + break + tlen = fast_pack[3] + while tlen > 0: + sd = fpm_sock.recv(tlen) + if not sd: + break + tlen -= len(sd) + _data += sd + if fast_pack[4]: + fpm_sock.recv(fast_pack[4]) + status, headers, data = format_header_data(_data) + return data diff --git a/web/utils/plugin.py b/web/utils/plugin.py new file mode 100644 index 000000000..359e88a8d --- /dev/null +++ b/web/utils/plugin.py @@ -0,0 +1,902 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import sys +import json +import threading +import multiprocessing + +import core.mw as mw +import thisdb + +class pg_thread(threading.Thread): + + def __init__(self, func, args, name=''): + threading.Thread.__init__(self) + self.name = name + self.func = func + self.args = args + self.result = self.func(*self.args) + + def getResult(self): + try: + return self.result + except Exception: + return None + +class plugin(object): + + def_plugin_type = [ + { + "title":"全部", + "type":0, + "ps":"" + }, + { + "title":"已安装", + "type":-1, + "ps":"" + }, + { + "title":"运行环境", + "type":1, + "ps":"" + }, + { + "title":"数据软件", + "type":2, + "ps":"" + }, + { + "title":"代码管理", + "type":3, + "ps":"" + }, + { + "title":"系统工具", + "type":4, + "ps":"" + }, + { + "title":"其他插件", + "type":5, + "ps":"" + }, + { + "title":"辅助插件", + "type":6, + "ps":"" + } + ] + + __plugin_dir = 'plugins' + __tasks = None + + __plugin_status_cachekey = 'plugin_list_status' + __plugin_status_data = None + + # lock + _instance_lock = threading.Lock() + + @classmethod + def instance(cls, *args, **kwargs): + if not hasattr(plugin, "_instance"): + with plugin._instance_lock: + if not hasattr(plugin, "_instance"): + plugin._instance = plugin(*args, **kwargs) + return plugin._instance + + """插件类初始化""" + def __init__(self): + self.__plugin_dir = mw.getPluginDir() + + + def getIndexList(self): + def build_index_list(plugins): + index_list = [] + for plugin in plugins: + version = plugin.get('setup_version') + if not version: + versions = plugin.get('versions') + if isinstance(versions, list): + if versions: + version = versions[0] + else: + version = versions + if version: + index_list.append('{0}-{1}'.format(plugin['name'], version)) + return index_list + + indexList = thisdb.getOptionByJson('display_index', default=[]) + if not isinstance(indexList, list): + indexList = [] + if not indexList: + installed_plugins, _ = self.getAllPluginList(type='-1', keyword=None, page=1, size=12) + indexList = build_index_list(installed_plugins) + if indexList: + thisdb.setOption('display_index', json.dumps(indexList)) + plist = [] + for i in indexList: + tmp = i.split('-') + tmp_len = len(tmp) + plugin_name = tmp[0] + plugin_ver = tmp[1] + if tmp_len > 2: + tmpArr = tmp[0:tmp_len - 1] + plugin_name = '-'.join(tmpArr) + plugin_ver = tmp[tmp_len - 1] + + read_json_file = self.__plugin_dir + '/' + plugin_name + '/info.json' + if os.path.exists(read_json_file): + content = mw.readFile(read_json_file) + try: + data = json.loads(content) + data = self.makeCoexistList(data) + for index in range(len(data)): + if data[index]['coexist']: + if data[index]['versions'] == plugin_ver or plugin_ver in data[index]['versions']: + data[index]['display'] = True + plist.append(data[index]) + continue + else: + data[index]['display'] = True + plist.append(data[index]) + + except Exception as e: + print('getIndexList:', mw.getTracebackInfo()) + + if not plist: + installed_plugins, _ = self.getAllPluginList(type='-1', keyword=None, page=1, size=12) + if installed_plugins: + plist = installed_plugins + refreshed_index = build_index_list(installed_plugins) + if refreshed_index: + thisdb.setOption('display_index', json.dumps(refreshed_index)) + + plist = self.checkStatusMThreadsByCache(plist) + return mw.returnData(True, 'ok', plist) + + def init(self): + plugin_names = { + 'openresty': '1.27.1', + 'php': '56', + 'swap': '1.1', + 'mysql': '5.7', + 'phpmyadmin': '4.4.15', + } + + pn_dir = mw.getPluginDir() + pn_server_dir = mw.getServerDir() + pn_list = [] + for pn in plugin_names: + info = {} + pn_json = pn_dir + '/' + pn + '/info.json' + pn_server = pn_server_dir + '/' + pn + if not os.path.exists(pn_server): + + tmp = mw.readFile(pn_json) + tmp = json.loads(tmp) + + info['title'] = tmp['title'] + info['name'] = tmp['name'] + info['versions'] = tmp['versions'] + info['default_ver'] = plugin_names[pn] + pn_list.append(info) + else: + return mw.returnData(False, 'ok') + + return mw.returnData(True, 'ok', pn_list) + + def initInstall(self, plugin_list): + try: + pn_list = json.loads(plugin_list) + for pn in pn_list: + name = pn['name'] + version = pn['version'] + info_file = self.__plugin_dir + '/' + name + '/' + 'info.json' + pluginInfo = json.loads(mw.readFile(info_file)) + self.hookInstall(pluginInfo) + + cmd = 'cd {0} && bash {1} install {2}'.format( + mw.getPluginDir() + '/'+name, + pluginInfo['shell'], + version + ) + title = '安装[' + name + '-' + version + ']' + thisdb.addTask(name=title,cmd=cmd) + os.mkdir(mw.getServerDir() + '/php') + # 任务执行相关 + mw.triggerTask() + return mw.returnData(True, '添加成功') + except Exception as e: + return mw.returnData(False, mw.getTracebackInfo()) + + def menuGetAbsPath(self, tag, path): + if path[0:1] == '/': + return path + else: + return mw.getPluginDir() + '/' + tag + '/' + path + + def addIndex(self, name, version): + vname = name + '-' + version + indexList = thisdb.getOptionByJson('display_index',default=[]) + + if vname in indexList: + return mw.returnData(False, '请不要重复添加!') + if len(indexList) > 12: + return mw.returnData(False, '首页最多只能显示12个软件!') + + indexList.append(vname) + + thisdb.setOption('display_index', json.dumps(indexList)) + return mw.returnData(True, '添加成功!') + + def removeIndex(self, name, version): + vname = name + '-' + version + indexList = thisdb.getOptionByJson('display_index') + if not vname in indexList: + return mw.returnData(True, '删除成功!!') + indexList.remove(vname) + thisdb.setOption('display_index', json.dumps(indexList)) + return mw.returnData(True, '删除成功!') + + def hookInstallOption(self, hook_name, info): + hn_name = 'hook_'+hook_name + src_data = thisdb.getOptionByJson(hn_name,type='hook',default=[]) + isNeedAdd = True + for x in range(len(src_data)): + if src_data[x]['title'] == info['title'] and src_data[x]['name'] == info['name']: + isNeedAdd = False + + if isNeedAdd: + src_data.append(info) + + thisdb.setOption(hn_name, json.dumps(src_data), type='hook') + return True + + def hookUninstallOption(self, hook_name, info): + hn_name = 'hook_'+hook_name + src_data = thisdb.getOptionByJson(hn_name,type='hook',default=[]) + for idx in range(len(src_data)): + if src_data[idx]['name'] == info['name']: + src_data.remove(src_data[idx]) + break + thisdb.setOption(hn_name, json.dumps(src_data), type='hook') + return True + + def hookInstall(self, info): + valid_hook = ['backup', 'database'] + valid_list_hook = ['menu', 'global_static', 'site_cb'] + if 'hook' in info: + hooks = info['hook'] + for h in hooks: + hooks_type = type(h) + if hooks_type == dict: + tag = h['tag'] + if tag in valid_list_hook: + self.hookInstallOption(tag, h[tag]) + elif hooks_type == str: + for x in hooks: + if x in valid_hook: + self.hookInstallOption(x, info) + return True + return False + + def hookUninstall(self, info): + valid_hook = ['backup', 'database'] + valid_list_hook = ['menu', 'global_static', 'site_cb'] + if 'hook' in info: + hooks = info['hook'] + for h in hooks: + hooks_type = type(h) + if hooks_type == dict: + tag = h['tag'] + if tag in valid_list_hook: + self.hookUninstallOption(tag, h[tag]) + elif hooks_type == str: + for x in hooks: + if x in valid_hook: + self.hookUninstallOption(x, info) + return True + return False + + def install(self, name, version, + upgrade = None + ): + if name.strip() == '': + return mw.returnData(False, '缺少插件名称!', ()) + + if version.strip() == '': + return mw.returnData(False, '缺少版本信息!', ()) + + msg_head = '安装' + if upgrade is not None and upgrade is True: + mtype = 'update' + msg_head = '更新' + + info_file = self.__plugin_dir + '/' + name + '/' + 'info.json' + if not os.path.exists(info_file): + return mw.returnData(False, "配置文件不存在!", ()) + + info_data = json.loads(mw.readFile(info_file)) + + exec_bash = 'cd {0} && bash {1} install {2}'.format( + mw.getPluginDir() + '/'+name, + info_data['shell'], + version + ) + + self.hookInstall(info_data) + title = '{0}[{1}-{2}]'.format(msg_head,name,version) + thisdb.addTask(name=title,cmd=exec_bash, status=0) + mw.triggerTask() + # 调式日志 + mw.debugLog(exec_bash) + return mw.returnData(True, '已将安装任务添加到队列!') + + # 卸载插件 + def uninstall(self, name, version): + if name.strip() == '': + return mw.returnData(False, "缺少插件名称!", ()) + + if version.strip() == '': + return mw.returnData(False, "缺少版本信息!", ()) + + info_file = self.__plugin_dir + '/' + name + '/' + 'info.json' + if not os.path.exists(info_file): + return mw.returnData(False, "配置文件不存在!", ()) + + info_data = json.loads(mw.readFile(info_file)) + + exec_bash = "cd {0} && /bin/bash {1} uninstall {2}".format( + mw.getPluginDir() + '/'+name, + info_data['shell'], + version + ) + self.hookUninstall(info_data) + data = mw.execShell(exec_bash) + self.removeIndex(name, version) + mw.debugLog(exec_bash, data) + return mw.returnData(True, '卸载执行成功!') + + # 插件搜索匹配 + def searchKey(self, info, + keyword = None, + ): + if keyword == None: + return True + try: + if info['title'].lower().find(keyword) > -1: + return True + if info['ps'].lower().find(keyword) > -1: + return True + if info['name'].lower().find(keyword) > -1: + return True + except Exception as e: + return False + + def getVersion(self, path): + version_pl = path + '/version.pl' + if os.path.exists(version_pl): + return mw.readFile(version_pl).strip() + return '' + + def checkIndexList(self, name, version): + indexList = thisdb.getOptionByJson('display_index',default=[]) + for i in indexList: + t = i.split('-') + tlen = len(t) + plugin_name = t[0] + plugin_ver = t[1] + if tlen > 2: + tArr = t[0:tlen - 1] + plugin_name = '-'.join(tArr) + plugin_ver = t[tlen - 1] + if plugin_name == name: + return True + return False + + def checkSetupTask(self, name, version, coexist): + self.__tasks = thisdb.getTaskRunAll() + isTask = '1' + for task in self.__tasks: + tmpt = mw.getStrBetween('[', ']', task['name']) + if not tmpt: + continue + task_sign = tmpt.split('-') + task_len = len(task_sign) + if task_len<2: + continue + + task_name = task_sign[0].lower() + task_ver = task_sign[1] + if task_len > 2: + nameArr = task_sign[0:task_len - 1] + task_name = '-'.join(nameArr).lower() + task_ver = task_sign[task_len - 1] + if coexist: + if task_name == name and task_ver == version: + isTask = task['status'] + else: + if task_name == name: + isTask = task['status'] + return isTask + + def checkDisplayIndex(self, name, version, coexist): + indexList = thisdb.getOptionByJson('display_index',default=[]) + if coexist: + if type(version) == list: + for index in range(len(version)): + vname = name + '-' + version[index] + if vname in indexList: + return True + else: + vname = name + '-' + version + if vname in indexList: + return True + else: + if type(version) == list: + for index in range(len(version)): + return self.checkIndexList(name, version) + else: + return self.checkIndexList(name, version) + return False + + def makeCoexist(self, data): + plugins_info = [] + for index in range(len(data['versions'])): + data_t = data.copy() + data_t['title'] = data_t['title'] + '-' + data['versions'][index] + data_t['versions'] = data['versions'][index] + pg = self.makePluginInfo(data_t) + plugins_info.append(pg) + + return plugins_info + + # 构造插件基本信息 + def makePluginInfo(self, info): + checks = '' + path = '' + coexist = False + + if info["checks"].startswith('/'): + checks = info["checks"] + else: + checks = mw.getFatherDir() + '/' + info['checks'] + + if 'path' in info: + path = info['path'] + + if not path.startswith('/'): + path = mw.getFatherDir() + '/' + path + + if 'coexist' in info and info['coexist']: + coexist = True + + pInfo = { + "id": 10000, + "sort": 10000, + "pid": info['pid'], + "type": 1000, + "name": info['name'], + "title": info['title'], + "ps": info['ps'], + "dependnet": "", + "mutex": "", + "icon": "", + "path": path, + "install_checks": checks, + "uninsatll_checks": checks, + "coexist": coexist, + "versions": info['versions'], + # "updates": info['updates'], + "task": True, + "display": False, + "setup": False, + "setup_version": "", + "status": False, + "install_pre_inspection": False, + "uninstall_pre_inspection": False, + } + + if 'icon' in info: + pInfo['icon'] = info['icon'] + + if 'sort' in info: + pInfo['sort'] = info['sort'] + + if checks.find('VERSION') > -1: + pInfo['install_checks'] = checks.replace('VERSION', info['versions']) + + if path.find('VERSION') > -1: + pInfo['path'] = path.replace('VERSION', info['versions']) + + pInfo['task'] = self.checkSetupTask(pInfo['name'], info['versions'], coexist) + pInfo['display'] = self.checkDisplayIndex(info['name'], pInfo['versions'], coexist) + pInfo['setup'] = os.path.exists(pInfo['install_checks']) + + + if coexist and pInfo['setup']: + pInfo['setup_version'] = info['versions'] + elif pInfo['setup']: + if os.path.isdir(pInfo['install_checks']): + pInfo['setup_version'] = self.getVersion(pInfo['install_checks']) + else: + pInfo['setup_version'] = mw.readFile(pInfo['install_checks']).strip() + + if 'install_pre_inspection' in info: + pInfo['install_pre_inspection'] = info['install_pre_inspection'] + if 'uninstall_pre_inspection' in info: + pInfo['uninstall_pre_inspection'] = info['uninstall_pre_inspection'] + + return pInfo + + def makeCoexistData(self, data): + plugins = [] + if type(data['versions']) == list and 'coexist' in data and data['coexist']: + data_t = self.makeCoexist(data) + for index in range(len(data_t)): + plugins.append(data_t[index]) + else: + pg = self.makePluginInfo(data) + plugins.append(pg) + return plugins + + def makeCoexistDataInstalled(self, data): + plugins = [] + if type(data['versions']) == list and 'coexist' in data and data['coexist']: + data_t = self.makeCoexist(data) + for index in range(len(data_t)): + if data_t[index]['setup']: + plugins.append(data_t[index]) + else: + pg = self.makePluginInfo(data) + if pg['setup']: + plugins.append(pg) + return plugins + + # 对多版本共存进行处理 + def makeCoexistList(self, data, + plugin_type = None, + ): + plugins_t = [] + # 返回指定类型 + if plugin_type != None and data['pid'] == plugin_type: + return self.makeCoexistData(data) + + # 全部 + if plugin_type == None or plugin_type == '0': + return self.makeCoexistData(data) + # 已经安装 + if str(plugin_type) == '-1': + return self.makeCoexistDataInstalled(data) + return plugins_t + + def getPluginInfo(self, name): + info = {} + path = self.__plugin_dir + '/' + name + info_path = path + '/info.json' + if not os.path.exists(info_path): + return info + try: + data = json.loads(mw.readFile(info_path)) + return data + except Exception as e: + return info + + def getPluginList(self, name, + keyword = None, + type = None, + ): + infos = [] + data = self.getPluginInfo(name) + if data is None or len(data) == 0: + return infos + + # 判断是否搜索 + if keyword != '' and not self.searchKey(data, keyword): + return infos + + plugin_t = self.makeCoexistList(data, type) + for index in range(len(plugin_t)): + infos.append(plugin_t[index]) + return infos + + # 检查插件状态 + def checkStatusThreads(self, info, i): + if not info['setup']: + return False + data = self.run(info['name'], 'status', info['setup_version']) + if data[0].strip() == 'start': + return True + else: + return False + + # 检查插件状态 + def checkStatusThreadsByCache(self, info, i): + # 初始化db + if not info['setup']: + return False + + plugin_list_status = self.__plugin_status_data + if plugin_list_status is not None: + k = info['name'] + if 'coexist' in info and info['coexist']: + k = info['title'] + # print(k) + if k in plugin_list_status: + if plugin_list_status[k]: + return True + else: + return False + + data = self.run(info['name'], 'status', info['setup_version']) + if data[0].strip() == 'start': + return True + else: + return False + + # 多线程检查插件状态[cache] + def checkStatusMThreadsByCache(self, info): + try: + self.__plugin_status_data = thisdb.getOptionByJson(self.__plugin_status_cachekey, default=None) + threads = [] + ntmp_list = range(len(info)) + for i in ntmp_list: + t = pg_thread(self.checkStatusThreadsByCache,(info[i], i)) + threads.append(t) + + for i in ntmp_list: + threads[i].start() + for i in ntmp_list: + threads[i].join() + + for i in ntmp_list: + t = threads[i].getResult() + k = info[i]['name'] + self.__plugin_status_data[k] = t + info[i]['status'] = t + + thisdb.setOption(self.__plugin_status_cachekey, json.dumps(self.__plugin_status_data)) + except Exception as e: + print(mw.getTracebackInfo()) + print('checkStatusMThreadsByCache:', str(e)) + return info + + def autoCachePluginStatus(self): + info = [] + for name in os.listdir(self.__plugin_dir): + if name.startswith('.'): + continue + t = self.getPluginList(name) + for index in range(len(t)): + info.append(t[index]) + + self.__plugin_status_data = {} + for x in info: + if not x['setup']: + continue + data = self.run(x['name'], 'status', x['setup_version']) + k = x['name'] + if 'coexist' in x and x['coexist']: + k = x['title'] + if data[0].strip() == 'start': + self.__plugin_status_data[k] = True + else: + self.__plugin_status_data[k] = False + thisdb.setOption(self.__plugin_status_cachekey, json.dumps(self.__plugin_status_data)) + return True + + # 多线程检查插件状态 + def checkStatusMThreads(self, info): + try: + threads = [] + ntmp_list = range(len(info)) + for i in ntmp_list: + t = pg_thread(self.checkStatusThreads,(info[i], i)) + threads.append(t) + + for i in ntmp_list: + threads[i].start() + for i in ntmp_list: + threads[i].join() + + for i in ntmp_list: + t = threads[i].getResult() + info[i]['status'] = t + except Exception as e: + print('checkStatusMThreads:', str(e)) + + return info + + def getAllPluginList( + self, + type = None, + keyword = None, + page = 1, + size = 10, + ): + info = [] + for name in os.listdir(self.__plugin_dir): + if name.startswith('.'): + continue + t = self.getPluginList(name, keyword, type=type) + for index in range(len(t)): + info.append(t[index]) + + info = sorted(info, key=lambda f: int(f['sort']), reverse=False) + + start = (page - 1) * size + end = start + size + x = info[start:end] + + x = self.checkStatusMThreadsByCache(x) + return (x, len(info)) + + def getList( + self, + type = None, + keyword = None, + page = 1, + size = 10, + ) -> object: + ''' + # print(type,keyword,page,size) + ''' + rdata = {} + rdata['type'] = self.def_plugin_type + + data = self.getAllPluginList(type, keyword, page, size) + rdata['data'] = data[0] + rdata['list'] = mw.getPage({'count':data[1],'p':page,'tojs':'getSList','row':size}) + return rdata + + def updateZip(self, request_zip): + tmp_path = mw.getPanelDir() + '/temp' + if not os.path.exists(tmp_path): + os.makedirs(tmp_path) + mw.execShell("rm -rf " + tmp_path + '/*') + + tmp_file = tmp_path + '/plugin_tmp.zip' + if request_zip.filename[-4:] != '.zip': + return mw.returnData(False, '仅支持zip文件!') + + request_zip.save(tmp_file) + mw.execShell('cd ' + tmp_path + ' && unzip ' + tmp_file) + os.remove(tmp_file) + + p_info = tmp_path + '/info.json' + if not os.path.exists(p_info): + d_path = None + for df in os.walk(tmp_path): + if len(df[2]) < 3: + continue + if not 'info.json' in df[2]: + continue + if not 'install.sh' in df[2]: + continue + if not os.path.exists(df[0] + '/info.json'): + continue + d_path = df[0] + if d_path: + tmp_path = d_path + p_info = tmp_path + '/info.json' + try: + data = json.loads(mw.readFile(p_info)) + data['size'] = mw.getPathSize(tmp_path) + if not 'author' in data: + data['author'] = '未知' + if not 'home' in data: + data['home'] = 'https://github.com/midoks/mdserver-web' + plugin_path = mw.getPluginDir() + data['name'] + '/info.json' + data['old_version'] = '0' + data['tmp_path'] = tmp_path + if os.path.exists(plugin_path): + try: + old_info = json.loads(mw.readFile(plugin_path)) + data['old_version'] = old_info['versions'] + except: + pass + except: + mw.execShell("rm -rf " + tmp_path) + return mw.returnData(False, '在压缩包中没有找到插件信息,请检查插件包!') + protectPlist = ('openresty', 'mysql', 'php', 'redis', 'memcached' + 'mongodb', 'swap', 'gogs', 'pureftp') + if data['name'] in protectPlist: + return mw.returnData(False, '[' + data['name'] + '],重要插件不可修改!') + return data + + def inputZipApi(self, plugin_name,tmp_path): + if not os.path.exists(tmp_path): + return mw.returnData(False, '临时文件不存在,请重新上传!') + plugin_path = mw.getPluginDir() + '/' + plugin_name + if not os.path.exists(plugin_path): + print(mw.execShell('mkdir -p ' + plugin_path)) + mw.execShell("cp -rf " + tmp_path + '/* ' + plugin_path + '/') + mw.execShell('chmod -R 755 ' + plugin_path) + p_info = mw.readFile(plugin_path + '/info.json') + if p_info: + mw.writeLog('软件管理', '安装第三方插件[%s]' %json.loads(p_info)['title']) + return mw.returnData(True, '安装成功!') + mw.execShell("rm -rf " + plugin_path) + return mw.returnData(False, '安装失败!') + + # [start|stop]操作,删除缓存! + def runByCache(self, name, func, version): + ppos = mw.getServerDir()+'/'+name + if not os.path.exists(ppos): + return + data = thisdb.getOptionByJson(self.__plugin_status_cachekey, default={}) + info = self.getPluginInfo(name) + if 'coexist' in info and info['coexist']: + name = info['title'] + '-'+ version + if name in data: + del(data[name]) + thisdb.setOption(self.__plugin_status_cachekey, json.dumps(data)) + + # shell/bash方式调用 + def run(self, name, func, + version = '', + args = '', + script = 'index', + ): + + if mw.inArray(['start','stop','restart','reload','uninstall_pre_inspection','install','uninstall'], func): + self.runByCache(name, func, version) + + path = self.__plugin_dir + '/' + name + '/' + script + '.py' + if not os.path.exists(path): + path = self.__plugin_dir + '/' + name + '/' + name + '.py' + + py = 'python3 ' + path + if args == '': + py_cmd = py + ' ' + func + ' ' + version + else: + py_cmd = py + ' ' + func + ' ' + version + ' ' + args + + if not os.path.exists(path): + return ('', '') + py_cmd = 'cd ' + mw.getPanelDir() + " && "+ py_cmd + data = mw.execShell(py_cmd) + + # print(data) + if mw.isDebugMode(): + print('run:', py_cmd) + print(data) + # print os.path.exists(py_cmd) + return (data[0].strip(), data[1].strip()) + + # 映射包调用 + def callback(self, name, func, + args = '', + script = 'index', + ): + package = self.__plugin_dir + '/' + name + if not os.path.exists(package): + return (False, "插件不存在!") + if not package in sys.path: + sys.path.append(package) + + cmd = "__import__('" + script + "')." + func + '(' + args + ')' + if mw.isDebugMode(): + print('callback', cmd) + + data = None + try: + data = eval(cmd) + except Exception as e: + print(mw.getTracebackInfo()) + return (False, mw.getTracebackInfo()) + return (True, data) + + + + diff --git a/web/utils/setting.py b/web/utils/setting.py new file mode 100644 index 000000000..c6dd8aedc --- /dev/null +++ b/web/utils/setting.py @@ -0,0 +1,294 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import re +import threading +import re +import time +import json + +import core.mw as mw +import thisdb + +class setting(object): + + # lock + _instance_lock = threading.Lock() + + @classmethod + def instance(cls, *args, **kwargs): + if not hasattr(setting, "_instance"): + with setting._instance_lock: + if not hasattr(setting, "_instance"): + setting._instance = setting(*args, **kwargs) + return setting._instance + + def __init__(self): + pass + + + # 保存面板证书 + def savePanelSsl(self, choose, cert_pem, private_key): + if not mw.inArray(['local','nginx'], choose): + return mw.returnData(True, '保存错误面板SSL类型!') + + pdir = mw.getPanelDir() + keyPath = pdir+'/ssl/'+choose+'/private.pem' + certPath = pdir+'/ssl/'+choose+'/cert.pem' + check_cert_pl = '/tmp/cert.pl' + + if not os.path.exists(keyPath): + return mw.returnData(False, '【'+choose+'】SSL类型不存在,先申请!') + + if(private_key.find('KEY') == -1): + return mw.returnData(False, '秘钥错误,请检查!') + if(cert_pem.find('CERTIFICATE') == -1): + return mw.returnData(False, '证书错误,请检查!') + + mw.writeFile(check_cert_pl, cert_pem) + if private_key: + mw.writeFile(keyPath, private_key) + if cert_pem: + mw.writeFile(certPath, cert_pem) + if not mw.checkCert(check_cert_pl): + os.remove(check_cert_pl) + return mw.returnData(False, '证书错误,请检查!') + os.remove(check_cert_pl) + return mw.returnData(True, '证书已保存!') + + + def getPanelSsl(self): + rdata = {} + rdata['choose'] = 'local' + + + pdir = mw.getPanelDir() + + keyPath = pdir+'/ssl/local/private.pem' + certPath = pdir+'/ssl/local/cert.pem' + + if not os.path.exists(certPath): + mw.createLocalSSL() + + cert = {} + cert['privateKey'] = mw.readFile(keyPath) + cert['is_https'] = '' + cert['certPem'] = mw.readFile(certPath) + cert['info'] = mw.getCertName(certPath) + rdata['local'] = cert + + panel_ssl = mw.getServerDir() + "/web_conf/nginx/vhost/panel.conf" + if not os.path.exists(panel_ssl): + cert['is_https'] = '' + else: + ssl_data = mw.readFile(panel_ssl) + if ssl_data.find('$server_port !~ 443') != -1: + cert['is_https'] = 'checked' + + keyPath = pdir+'/ssl/nginx/private.pem' + certPath = pdir+'/ssl/nginx/cert.pem' + + cert = {} + cert['privateKey'] = '' + cert['certPem'] = '' + cert['info'] = {} + if os.path.exists(keyPath): + cert['privateKey'] = mw.readFile(keyPath) + + if os.path.exists(keyPath): + cert['certPem'] = mw.readFile(certPath) + cert['info'] = mw.getCertName(certPath) + + rdata['nginx'] = cert + + return rdata + + # 删除面板证书 + def delPanelSsl(self, choose): + ip = mw.getLocalIp() + if mw.isAppleSystem(): + ip = '127.0.0.1' + + + if not mw.inArray(['local','nginx'], choose): + return mw.returnData(True, '删除错误面板SSL类型!') + + port = mw.getPanelPort() + + to_panel_url = 'http://'+ip+":"+port+'/config' + + if choose == 'local': + dst_path = mw.getPanelDir() + '/ssl/local' + if os.path.exists(dst_path): + mw.execShell('rm -rf ' + dst_path) + mw.restartMw(); + return mw.returnData(True, '删除本地面板SSL成功!',to_panel_url) + else: + return mw.returnData(True, '已经删除本地面板SSL!',to_panel_url) + + if choose == 'nginx': + + bind_domain = self.__file['bind_domain'] + if not os.path.exists(bind_domain): + return mw.returnData(False, '未绑定域名!') + + siteName = mw.readFile(bind_domain).strip() + + src_path = mw.getServerDir() + '/web_conf/letsencrypt/' + siteName + + dst_path = mw.getPanelDir() + '/ssl/nginx' + dst_csrpath = dst_path + '/cert.pem' + dst_keypath = dst_path + '/private.pem' + + if os.path.exists(src_path) or os.path.exists(dst_path): + if os.path.exists(src_letpath): + mw.execShell('rm -rf ' + src_letpath) + if os.path.exists(dst_csrpath): + mw.execShell('rm -rf ' + dst_csrpath) + if os.path.exists(dst_keypath): + mw.execShell('rm -rf ' + dst_keypath) + mw.restartNginx() + return mw.returnData(True, '删除面板SSL成功!') + + mw.restartNginx() + mw.restartMw() + return mw.returnData(False, '已经删除面板SSL!') + return mw.returnData(False, '未知类型!') + + # 面板本地SSL设置 + def setPanelLocalSsl(self, cert_type): + panel_ssl_data = thisdb.getOptionByJson('panel_ssl', default={'open':False}) + + if not panel_ssl_data['open']: + panel_ssl_data['open'] = True + + pdir = mw.getPanelDir() + cert = {} + keyPath = pdir+'/ssl/local/private.pem' + certPath = pdir+'/ssl/local/cert.pem' + if not os.path.exists(certPath): + mw.createLocalSSL() + + panel_ssl_data['choose'] = 'local' + thisdb.setOption('panel_ssl', json.dumps(panel_ssl_data)) + mw.restartMw() + return mw.returnData(True, '设置成功') + + def closePanelSsl(self): + panel_ssl_data = thisdb.getOptionByJson('panel_ssl', default={'open':False}) + + if panel_ssl_data['open']: + panel_ssl_data['open'] = False + + thisdb.setOption('panel_ssl', json.dumps(panel_ssl_data)) + mw.restartMw() + return mw.returnData(True, '设置成功') + + + # 申请面板let证书 + # def applyPanelAcmeSsl(self): + # bind_domain = self.__file['bind_domain'] + # if not os.path.exists(bind_domain): + # return mw.returnJson(False, '先要绑定域名!') + + # # 生成nginx配置 + # domain = mw.readFile(bind_domain) + # panel_tpl = mw.getRunDir() + "/data/tpl/nginx_panel.conf" + # dst_panel_path = mw.getServerDir() + "/web_conf/nginx/vhost/panel.conf" + # if not os.path.exists(dst_panel_path): + # reg = r"^([\w\-\*]{1,100}\.){1,4}(\w{1,10}|\w{1,10}\.\w{1,10})$" + # if not re.match(reg, domain): + # return mw.returnJson(False, '主域名格式不正确') + + # op_dir = mw.getServerDir() + "/openresty" + # if not os.path.exists(op_dir): + # return mw.returnJson(False, '依赖OpenResty,先安装启动它!') + + # content = mw.readFile(panel_tpl) + # content = content.replace("{$PORT}", "80") + # content = content.replace("{$SERVER_NAME}", domain) + # content = content.replace("{$PANAL_PORT}", mw.readFile('data/port.pl')) + # content = content.replace("{$LOGPATH}", mw.getRunDir() + '/logs') + # content = content.replace("{$PANAL_ADDR}", mw.getRunDir()) + # mw.writeFile(dst_panel_path, content) + # mw.restartNginx() + + # siteName = mw.readFile(bind_domain).strip() + # auth_to = mw.getRunDir() + "/tmp" + # to_args = { + # 'domains': [siteName], + # 'auth_type': 'http', + # 'auth_to': auth_to, + # } + + # src_path = mw.getServerDir() + '/web_conf/letsencrypt/' + siteName + # src_csrpath = src_path + "/fullchain.pem" # 生成证书路径 + # src_keypath = src_path + "/privkey.pem" # 密钥文件路径 + + # dst_path = mw.getRunDir() + '/ssl/nginx' + # dst_csrpath = dst_path + '/cert.pem' + # dst_keypath = dst_path + '/private.pem' + + # is_already_apply = False + + # if not os.path.exists(src_path): + # import cert_api + # data = cert_api.cert_api().applyCertApi(to_args) + # if not data['status']: + # msg = data['msg'] + # if type(data['msg']) != str: + # msg = data['msg'][0] + # emsg = data['msg'][1]['challenges'][0]['error'] + # msg = msg + '

                                            响应状态:' + str(emsg['status']) + '

                                            错误类型:' + emsg[ + # 'type'] + '

                                            错误代码:' + emsg['detail'] + '

                                            ' + # return mw.returnJson(data['status'], msg, data['msg']) + # else: + # is_already_apply = True + + # mw.buildSoftLink(src_csrpath, dst_csrpath, True) + # mw.buildSoftLink(src_keypath, dst_keypath, True) + # mw.execShell('echo "acme" > "' + dst_path + '/README"') + + # tmp_well_know = auth_to + '/.well-known' + # if os.path.exists(tmp_well_know): + # mw.execShell('rm -rf ' + tmp_well_know) + + # if os.path.exists(dst_path): + # choose_file = self.__file['ssl'] + # mw.writeFile(choose_file, 'nginx') + + # data = self.getPanelSslData() + + # if is_already_apply: + # return mw.returnJson(True, '重复申请!', data) + # return mw.returnJson(True, '申请成功!', data) + + def setPanelDomain(self, domain): + port = mw.getPanelPort() + + panel_domain = thisdb.getOption('panel_domain', default='') + if domain == '': + ip = mw.getLocalIp() + client_ip = mw.getClientIp() + if client_ip in ['127.0.0.1', 'localhost', '::1']: + ip = client_ip + + to_panel_url = 'http://'+ip+":"+str(port)+'/setting/index' + thisdb.setOption('panel_domain', '') + mw.restartMw() + return mw.returnData(True, '清空域名成功!', to_panel_url) + + thisdb.setOption('panel_domain', domain) + to_panel_url = 'http://'+domain+":"+str(port)+'/setting/index' + mw.restartMw() + return mw.returnData(True, '设置域名成功!',to_panel_url) + + diff --git a/web/utils/site.py b/web/utils/site.py new file mode 100644 index 000000000..a8fa1fc9e --- /dev/null +++ b/web/utils/site.py @@ -0,0 +1,2686 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import sys +import re +import json +import time +import threading +import multiprocessing + +import core.mw as mw +import thisdb + + +class sites(object): + # lock + _instance_lock = threading.Lock() + + @classmethod + def instance(cls, *args, **kwargs): + if not hasattr(sites, "_instance"): + with sites._instance_lock: + if not hasattr(sites, "_instance"): + sites._instance = sites(*args, **kwargs) + return sites._instance + + def __init__(self): + # nginx conf + self.setupPath = mw.getServerDir() + '/web_conf' + self.logsPath = mw.getLogsDir() + + self.vhostPath = vhost = self.setupPath + '/nginx/vhost' + if not os.path.exists(vhost): + mw.execShell("mkdir -p " + vhost + " && chmod -R 755 " + vhost) + self.rewritePath = rewrite = self.setupPath + '/nginx/rewrite' + if not os.path.exists(rewrite): + mw.execShell("mkdir -p " + rewrite + " && chmod -R 755 " + rewrite) + + self.passPath = passwd = self.setupPath + '/nginx/pass' + if not os.path.exists(passwd): + mw.execShell("mkdir -p " + passwd + " && chmod -R 755 " + passwd) + + self.redirectPath = redirect = self.setupPath + '/nginx/redirect' + if not os.path.exists(redirect): + mw.execShell("mkdir -p " + redirect +" && chmod -R 755 " + redirect) + + self.proxyPath = proxy = self.setupPath + '/nginx/proxy' + if not os.path.exists(proxy): + mw.execShell("mkdir -p " + proxy + " && chmod -R 755 " + proxy) + + # ssl conf + self.sslDir = self.setupPath + '/ssl' + self.sslLetsDir = self.setupPath + '/letsencrypt' + if not os.path.exists(self.sslLetsDir): + mw.execShell("mkdir -p " + self.sslLetsDir +" && chmod -R 755 " + self.sslLetsDir) + + + def runHook(self, hook_name, func_name): + # 站点操作Hook + hook_cfg = thisdb.getOptionByJson('hook_site_cb',type='hook',default=[]) + hook_num = len(hook_cfg) + if hook_num == 0: + return + + from utils.plugin import plugin as MwPlugin + pa = MwPlugin.instance() + + for x in range(hook_num): + hook_data = hook_cfg[x] + if func_name in hook_data: + app_name = hook_data["name"] + run_func = hook_data[func_name]['func'] + pa.run(app_name, run_func) + return True + + # 域名编码转换 + def toPunycode(self, domain): + if sys.version_info[0] == 2: + domain = domain.encode('utf8') + tmp = domain.split('.') + newdomain = '' + for dkey in tmp: + # 匹配非ascii字符 + match = re.search(u"[\x80-\xff]+", dkey) + if not match: + newdomain += dkey + '.' + else: + newdomain += 'xn--' + dkey.decode('utf-8').encode('punycode') + '.' + return newdomain[0:-1] + + # 中文路径处理 + def toPunycodePath(self, path): + if sys.version_info[0] == 2: + path = path.encode('utf-8') + if os.path.exists(path): + return path + match = re.search(u"[\x80-\xff]+", path) + if not match: + return path + npath = '' + for ph in path.split('/'): + npath += '/' + self.toPunycode(ph) + return npath.replace('//', '/') + + def getHostConf(self, siteName): + return self.vhostPath + '/' + siteName + '.conf' + + def saveHostConf(self, path, data, encoding): + import utils.file as file + mw.backFile(path) + rdata = file.saveBody(path, data, encoding) + + if rdata['status']: + isError = mw.checkWebConfig() + if isError != True: + mw.restoreFile(path) + msg = 'ERROR: 检测到配置文件有错误,请先排除后再操作

                                            ' + isError.replace("\n", '
                                            ') + '
                                            ' + return mw.returnData(False, msg) + mw.restartWeb() + mw.removeBackFile(path) + return rdata + + def getRewriteConf(self, site_name): + return self.rewritePath + '/' + site_name + '.conf' + + def getRedirectDataPath(self, site_name): + return "{}/{}/data.json".format(self.redirectPath, site_name) + + def getRedirectPath(self, site_name): + return "{}/{}".format(self.redirectPath, site_name) + + def getProxyDataPath(self, site_name): + return "{}/{}/data.json".format(self.proxyPath, site_name) + + def getProxyPath(self, site_name): + return "{}/{}".format(self.proxyPath, site_name) + + def getDirBindRewrite(self, site_name, dir_name): + return self.rewritePath + '/' + site_name + '_' + dir_name + '.conf' + + def getIndexConf(self): + return mw.getServerDir() + '/openresty/nginx/conf/nginx.conf' + + + # 路径处理 + def getPath(self, path): + if path[-1] == '/': + return path[0:-1] + return path + + def getSitePath(self, site_name): + file = self.getHostConf(site_name) + if os.path.exists(file): + conf = mw.readFile(file) + rep = r'\s*root\s*(.+);' + find_cnf = re.search(rep, conf) + if not find_cnf: + return '' + path = find_cnf.groups()[0] + return path + return '' + + def createRootDir(self, path): + autoInit = False + if not os.path.exists(path): + autoInit = True + os.makedirs(path) + if not mw.isAppleSystem(): + mw.execShell('chown -R www:www ' + path) + + if autoInit: + mw.writeFile(path + '/index.html', 'Work has started!!!') + mw.execShell('chmod -R 755 ' + path) + + def add(self, site_info, port, ps, path, version): + site_root_dir = mw.getWwwDir() + if site_root_dir == path.rstrip('/'): + return mw.returnData(False, '不要以网站根目录创建站点!') + + site_info = json.loads(site_info) + + self.siteName = self.toPunycode(site_info['domain'].strip().split(':')[0]).strip() + self.sitePath = self.toPunycodePath(self.getPath(path.replace(' ', ''))) + self.sitePort = port.strip().replace(' ', '') + self.phpVersion = version + + if thisdb.isSitesExist(self.siteName): + return mw.returnData(False, '您添加的站点[%s]已存在!' % self.siteName) + + site_id = thisdb.addSites(self.siteName, self.sitePath) + if site_id < 1: + return mw.returnData(False, '添加失败!') + + self.createRootDir(self.sitePath) + self.nginxAddConf() + + # 主域名配置 + thisdb.addDomain(site_id, self.siteName, self.sitePort) + # 添加更多域名 + for domain in site_info['domainlist']: + self.addDomain(site_id, self.siteName, domain) + + mw.restartWeb() + + self.runHook('site_cb', 'add') + return mw.returnData(True, '添加成功') + + def stop(self, site_id): + site_info = thisdb.getSitesById(site_id) + + path = self.setupPath + '/stop' + if not os.path.exists(path): + os.makedirs(path) + default_text = 'The website has been closed!!!' + mw.writeFile(path + '/index.html', default_text) + + binding = thisdb.getBindingListBySiteId(site_id) + for b in binding: + bpath = path + '/' + b['path'] + if not os.path.exists(bpath): + mw.execShell('mkdir -p ' + bpath) + mw.execShell('ln -sf ' + path +'/index.html ' + bpath + '/index.html') + + + # nginx + file = self.getHostConf(site_info['name']) + conf = mw.readFile(file) + if conf: + conf = conf.replace(site_info['path'], path) + mw.writeFile(file, conf) + + thisdb.setSitesData(site_id, status='0') + msg = mw.getInfo('网站[{1}]已被停用!', (site_info['name'],)) + mw.writeLog('网站管理', msg) + mw.restartWeb() + return mw.returnData(True, '站点已停用!') + + def start(self, site_id): + site_info = thisdb.getSitesById(site_id) + + path = self.setupPath + '/stop' + # nginx + file = self.getHostConf(site_info['name']) + conf = mw.readFile(file) + if conf: + conf = conf.replace(path, site_info['path']) + mw.writeFile(file, conf) + + thisdb.setSitesData(site_id, status='1') + + msg = mw.getInfo('网站[{1}]已被启用!', (site_info['name'],)) + mw.writeLog('网站管理', msg) + mw.restartWeb() + return mw.returnData(True, '站点已启用!') + + def nginxAddDomainFilter(self, site_id, site_name, domain, port): + self.nginxAddDomain(site_name, domain, port) + return True + + def nginxAddDomain(self, site_name, domain, port): + file = self.getHostConf(site_name) + conf = mw.readFile(file) + if not conf: + return + + # 添加域名 + rep = r"server_name\s*(.*);" + tmp = re.search(rep, conf).group() + domains = tmp.split(' ') + if not mw.inArray(domains, domain): + newServerName = tmp.replace(';', ' ' + domain + ';') + conf = conf.replace(tmp, newServerName) + + # 添加端口 + rep = r"listen\s+([0-9]+)\s*[default_server]*\s*;" + tmp = re.findall(rep, conf) + if not mw.inArray(tmp, port): + listen = re.search(rep, conf).group() + conf = conf.replace( + listen, listen + "\n\tlisten " + port + ';') + # 保存配置文件 + mw.writeFile(file, conf) + return True + + def addDomain(self, site_id, site_name, domain): + isError = mw.checkWebConfig() + if isError != True: + msg = 'ERROR: 检测到配置文件有错误,请先排除后再操作

                                            ' + isError.replace("\n", '
                                            ') + '
                                            ' + return mw.returnData(False, msg) + + domains = domain.split(',') + for d in domains: + if d == '': + continue + d = d.split(':') + name = self.toPunycode(d[0]) + port = '80' + if len(d) == 2: + port = d[1] + + if not mw.checkPort(port): + return mw.returnData(False, '端口范围不合法!') + + reg = r"^([\w\-\*]{1,100}\.){1,4}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$" + if not re.match(reg, name): + return mw.returnData(False, '域名格式不正确!') + + if thisdb.checkSitesDomainIsExist(name, port): + return mw.returnData(False, '您添加的域名[{}:{}],已使用。请仔细检查!'.format(name, port)) + + self.nginxAddDomainFilter(site_id, site_name, name, port) + + msg = mw.getInfo('网站[{1}]添加域名[{2}]成功!', (site_name, name)) + mw.writeLog('网站管理', msg) + thisdb.addDomain(site_id, name, port) + + mw.restartWeb() + self.runHook('site_cb', 'add') + return mw.returnData(True, '域名添加成功!') + + def delDomain(self, site_id, site_name, domain, port): + domain_nums = thisdb.getDomainCountBySiteId(site_id) + if domain_nums == 1: + return mw.returnData(False, '最后一个域名不能删除!') + + info = mw.M('domain').field('id,name').where("pid=? AND name=? AND port=?",(site_id, domain, port)).find() + + file = self.getHostConf(site_name) + conf = mw.readFile(file) + if conf: + # 删除域名 + rep = r"server_name\s+(.+);" + tmp = re.search(rep, conf).group() + newServerName = tmp.replace(' ' + domain + ';', ';') + newServerName = newServerName.replace(' ' + domain + ' ', ' ') + conf = conf.replace(tmp, newServerName) + + # 删除端口 + rep = r"listen\s+([0-9]+);" + tmp = re.findall(rep, conf) + port_nums = mw.M('domain').where('pid=? AND port=?', (site_id, port)).count() + if mw.inArray(tmp, port) == True and port_nums < 2: + rep = r"\n*\s+listen\s+" + port + ";" + conf = re.sub(rep, '', conf) + # 保存配置 + mw.writeFile(file, conf) + + thisdb.deleteDomainId(info['id']) + msg = mw.getInfo('网站[{1}]删除域名[{2}:{3}]成功!', (site_name, domain, port)) + mw.writeLog('网站管理', msg) + mw.restartWeb() + return mw.returnData(True, '站点删除成功!') + + def deleteALlLogs(self, webname): + assLogPath = mw.getLogsDir() + '/' + webname + '.log' + errLogPath = mw.getLogsDir() + '/' + webname + '.error.log' + confFile = self.setupPath + '/nginx/vhost/' + webname + '.conf' + rewriteFile = self.setupPath + '/nginx/rewrite/' + webname + '.conf' + passFile = self.setupPath + '/nginx/pass/' + webname + '.conf' + keyPath = self.sslDir + webname + '/privkey.pem' + certPath = self.sslDir + webname + '/fullchain.pem' + logs = [assLogPath, + errLogPath, + confFile, + rewriteFile, + passFile, + keyPath, + certPath] + for i in logs: + mw.deleteFile(i) + + # 重定向目录 + redirectDir = self.setupPath + '/nginx/redirect/' + webname + if os.path.exists(redirectDir): + mw.execShell('rm -rf ' + redirectDir) + # 代理目录 + proxyDir = self.setupPath + '/nginx/proxy/' + webname + if os.path.exists(proxyDir): + mw.execShell('rm -rf ' + proxyDir) + + def delete(self, site_id, path): + info = thisdb.getSitesById(site_id) + webname = info['name'] + self.deleteALlLogs(webname) + + if path == '1': + web_root_path = mw.getWwwDir() + '/' + webname + mw.execShell('rm -rf ' + web_root_path) + + # ssl + ssl_dir = self.sslDir + '/' + webname + if os.path.exists(ssl_dir): + mw.execShell('rm -rf ' + ssl_dir) + + ssl_lets_dir = self.sslLetsDir + '/' + webname + if os.path.exists(ssl_lets_dir): + mw.execShell('rm -rf ' + ssl_lets_dir) + + ssl_acme_dir = mw.getAcmeDir() + '/' + webname + if os.path.exists(ssl_acme_dir): + mw.execShell('rm -rf ' + ssl_acme_dir) + + thisdb.deleteSitesById(site_id) + thisdb.deleteDomainBySiteId(site_id) + + binding_list = thisdb.getBindingListBySiteId(site_id) + for x in binding_list: + tag = mw.getLogsDir() + "/" + webname + "_" + x['domain'] + site_log = tag + ".log" + site_error = tag + ".error.log" + + if os.path.exists(site_log): + mw.execShell('rm -rf ' + site_log) + if os.path.exists(site_error): + mw.execShell('rm -rf ' + site_error) + + thisdb.deleteBindingBySiteId(site_id) + mw.restartWeb() + + self.runHook('site_cb', 'delete') + return mw.returnData(True, '站点【%s】删除成功!' % webname) + + def nginxAddConf(self): + + source_tpl = self.getNgxTplDir() + '/nginx.conf' + vhost_file = self.getHostConf(self.siteName) + content = mw.readFile(source_tpl) + + content = content.replace('{$PORT}', self.sitePort) + content = content.replace('{$SERVER_NAME}', self.siteName) + content = content.replace('{$ROOT_DIR}', self.sitePath) + content = content.replace('{$PHP_DIR}', self.setupPath + '/php') + content = content.replace('{$PHPVER}', self.phpVersion) + content = content.replace('{$OR_REWRITE}', self.rewritePath) + # content = content.replace('{$OR_REDIRECT}', self.redirectPath) + # content = content.replace('{$OR_PROXY}', self.proxyPath) + + logsPath = mw.getLogsDir() + content = content.replace('{$LOGPATH}', logsPath) + mw.writeFile(vhost_file, content) + + # 和反代配置冲突 && 默认伪静态为空 +# rewrite_content = ''' +# location /{ +# if ($PHP_ENV != "1"){ +# break; +# } + +# if (!-e $request_filename) { +# rewrite ^(.*)$ /index.php/$1 last; +# break; +# } +# } +# ''' + rewrite_file = self.getRewriteConf(self.siteName) + mw.writeFile(rewrite_file, '') + + # 设置网站过期 + def setEndDate(self, site_id, end_date): + info = thisdb.getSitesById(site_id) + thisdb.setSitesData(site_id, edate=end_date) + mw.writeLog('网站管理', '设置成功,站点【{1}】到期【{2}】后将自动停止!', (info['name'], end_date,)) + return mw.returnData(True, '设置成功,站点到期后将自动停止!') + + def setSsl(self, site_name, key, csr): + path = self.sslDir + '/' + site_name + if not os.path.exists(path): + mw.execShell('mkdir -p ' + path) + + csrpath = path + "/fullchain.pem" # 生成证书路径 + keypath = path + "/privkey.pem" # 密钥文件路径 + + if(key.find('KEY') == -1): + return mw.returnJson(False, '秘钥错误,请检查!') + if(csr.find('CERTIFICATE') == -1): + return mw.returnJson(False, '证书错误,请检查!') + + tmp_cert = '/tmp/cert.pl' + mw.writeFile(tmp_cert, csr) + if not mw.checkCert(tmp_cert): + os.remove(tmp_cert) + return mw.returnData(False, '证书错误,请粘贴正确的PEM格式证书!') + os.remove(tmp_cert) + + mw.backFile(keypath) + mw.backFile(csrpath) + + mw.writeFile(keypath, key) + mw.writeFile(csrpath, csr) + + # 写入配置文件 + result = self.setSslConf(site_name) + if not result['status']: + return result + + isError = mw.checkWebConfig() + if(type(isError) == str): + mw.restoreFile(keypath) + mw.restoreFile(csrpath) + + msg = 'ERROR:
                                            ' + isError.replace("\n", '
                                            ') + '
                                            ' + return mw.returnData(False, msg) + + mw.writeLog('网站管理', '证书已保存!') + mw.restartWeb() + return mw.returnData(True, '证书已保存!') + + # ssl相关方法 start + def setSslConf(self, site_name): + file = self.getHostConf(site_name) + conf = mw.readFile(file) + if not conf: + return mw.returnData(False, '站点[%s]配置异常!'.format(site_name)) + + version = mw.getOpVer() + keyPath = self.sslDir + '/' + site_name + '/privkey.pem' + certPath = self.sslDir + '/' + site_name + '/fullchain.pem' + + if conf.find('ssl_certificate') == -1: + # ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; + # add_header Alt-Svc 'h3=":443";ma=86400,h3-29=":443";ma=86400'; + http3Header = """ + add_header Strict-Transport-Security "max-age=63072000"; + add_header Alt-Svc 'h3=":443";ma=86400'; +""" + if not version.startswith('1.25') or version.startswith('1.27'): + http3Header = ''; + + sslStr = """#error_page 404/404.html; + ssl_certificate %s; + ssl_certificate_key %s; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + %s + error_page 497 https://$host$request_uri;""" % (certPath, keyPath, http3Header) + if(conf.find('ssl_certificate') != -1): + return mw.returnData(True, 'SSL开启成功!') + + conf = conf.replace('#error_page 404/404.html;', sslStr) + + rep = r"listen\s+([0-9]+)\s*[default_server|reuseport]*;" + tmp = re.findall(rep, conf) + if not mw.inArray(tmp, '443'): + listen = re.search(rep, conf).group() + + if version.startswith('1.25') or version.startswith('1.27'): + http_ssl = "\n\tlisten 443 ssl;" + http_ssl = http_ssl + "\n\tlisten [::]:443 ssl;" + http_ssl = http_ssl + "\n\thttp2 on;" + else: + http_ssl = "\n\tlisten 443 ssl;" + http_ssl = http_ssl + "\n\tlisten [::]:443 ssl;" + + + conf = conf.replace(listen, listen + http_ssl) + + mw.backFile(file) + mw.writeFile(file, conf) + isError = mw.checkWebConfig() + if not isError: + mw.restoreFile(file) + return mw.returnData(False, '证书错误:
                                            ' + isError.replace("\n", '
                                            ') + '
                                            ') + + self.saveCert(site_name, keyPath, certPath) + msg = mw.getInfo('网站[{1}]开启SSL成功!', (site_name,)) + mw.writeLog('网站管理', msg) + + mw.restartWeb() + return mw.returnData(True, 'SSL开启成功!') + + # 设置网站备注 + def setPs(self, site_id, ps): + if thisdb.setSitesData(site_id, ps=ps): + return mw.returnData(True, '修改成功!') + return mw.returnData(False, '修改失败!') + + # 获取默认站点 + def getDefaultSite(self): + data = {} + data['sites'] = mw.M('sites').field('name').order('id desc').select() + data['default_site'] = thisdb.getOption('default_site', default='') + return data + + # 获取域名列表 + def getDomain(self, site_id): + data = thisdb.getDomainBySiteId(site_id) + return mw.returnData(True, 'ok', data) + + # 获取日志内容 + def getLogs(self, siteName): + logPath = mw.getLogsDir() + '/' + siteName + '.log' + if not os.path.exists(logPath): + return mw.returnData(False, '日志为空') + return mw.returnData(True, mw.getLastLine(logPath, 100)) + + # 获取错误日志内容 + def getErrorLogs(self, siteName): + logPath = mw.getLogsDir() + '/' + siteName + '.error.log' + if not os.path.exists(logPath): + return mw.returnData(False, '日志为空') + return mw.returnData(True, mw.getLastLine(logPath, 100)) + + def getNgxRewriteDir(self): + return mw.getPanelDir() + '/web/misc/nginx/rewrite' + + def getNgxTplDir(self): + return mw.getPanelDir() + '/web/misc/nginx/tpl' + + # 获取模版名内容 + def getRewriteTpl(self, name): + path = self.getNgxRewriteDir() +'/'+ name + ".conf" + if not os.path.exists(path): + return mw.returnData(False, '模版不存在!') + return mw.returnData(True, 'OK', path) + + def setRewrite(self,path,data,encoding): + if not os.path.exists(path): + mw.writeFile(path, '') + + mw.backFile(path) + mw.writeFile(path, data) + isError = mw.checkWebConfig() + if(type(isError) == str): + mw.restoreFile(path) + msg = 'ERROR:
                                            ' + isError.replace("\n", '
                                            ') + '
                                            ' + return mw.returnJson(False, msg) + mw.restartWeb() + return mw.returnData(True, '设置成功!') + + def setRewriteTpl(self,name,data): + path = self.getNgxRewriteDir() +'/'+ name + ".conf" + if os.path.exists(path): + return mw.returnData(False, '模版已经存在!') + + if data == "": + return mw.returnData(False, '模版内容不能为空!') + ok = mw.writeFile(path, data) + if not ok: + return mw.returnData(False, '模版保持失败!') + + return mw.returnData(True, '设置模板成功!') + + def getRewriteList(self): + rewriteList = {} + rewriteList['rewrite'] = [] + rewriteList['rewrite'].append('0.当前') + rewrite_nginx_dir = self.getNgxRewriteDir() + for ds in os.listdir(rewrite_nginx_dir): + if ds.startswith('.'): + continue + if ds.endswith('conf'): + rewriteList['rewrite'].append(ds[0:len(ds) - 5]) + rewriteList['rewrite'] = sorted(rewriteList['rewrite']) + return rewriteList + + # 取日志状态 + def getLogsStatus(self, siteName): + filename = self.getHostConf(siteName) + conf = mw.readFile(filename) + if conf.find('#ErrorLog') != -1: + return False + if conf.find("access_log off") != -1: + return False + return True + + # 取目录加密状态 + def getHasPwd(self, siteName): + filename = self.getHostConf(siteName) + conf = mw.readFile(filename) + if conf.find('#AUTH_START') != -1: + return True + return False + + # 取当站点前运行目录 + def getSiteRunPath(self, site_name, site_path): + filename = self.getHostConf(site_name) + if os.path.exists(filename): + conf = mw.readFile(filename) + rep = r'\s*root\s*(.+);' + path = re.search(rep, conf).groups()[0] + + data = {} + if site_path == path: + data['path'] = '/' + else: + data['path'] = path.replace(site_path, '') + + dirnames = [] + dirnames.append('/') + + if os.path.exists(site_path): + for filename in os.listdir(site_path): + try: + file_path = site_path + '/' + filename + if os.path.islink(file_path): + continue + if os.path.isdir(file_path): + dirnames.append('/' + filename) + except: + pass + + data['dirs'] = dirnames + return data + + def getDirUserIni(self, site_id): + + info = thisdb.getSitesById(site_id) + + path = info['path'] + name = info['name'] + data = {} + data['logs'] = self.getLogsStatus(name) + data['run_path'] = self.getSiteRunPath(name, path) + + data['user_ini'] = False + if os.path.exists(path + '/.user.ini'): + data['user_ini'] = True + + if data['run_path']['path'] != '/': + user_ini = path + data['run_path']['path'] + '/.user.ini' + if os.path.exists(user_ini): + data['userini'] = True + + data['pass'] = self.getHasPwd(name) + data['path'] = path + data['name'] = name + return mw.returnData(True, 'OK', data) + + # 清除多余user.ini + def delUserInI(self, path, up=0): + filename = path + '/.user.ini' + if os.path.exists(filename): + mw.execShell("which chattr && chattr -i " + filename) + os.remove(filename) + + for f in os.listdir(path): + try: + npath = path + '/' + f + if os.path.isdir(npath): + if up < 100: + self.delUserInI(npath, up + 1) + + user_ini = npath + '/.user.ini' + print('ff:',user_ini) + if not os.path.exists(user_ini): + continue + mw.execShell('which chattr && chattr -i ' + user_ini) + os.remove(user_ini) + except: + continue + return True + + + # 设置目录防御 + def addDirUserIni(self, site_path, run_path): + new_path = site_path + run_path + filename = new_path + '/.user.ini' + if os.path.exists(filename): + return mw.returnData(True, '已打开防跨站设置!') + + open_path = 'open_basedir={}/:{}/'.format(new_path, site_path) + if run_path == '/' or run_path == '': + open_path = 'open_basedir={}/'.format(site_path) + + mw.writeFile(filename, open_path + ':/www/server/php:/tmp/:/proc/') + mw.execShell("which chattr && chattr +i " + filename) + + def setDirUserIni(self, site_path, run_path): + filename = site_path + '/.user.ini' + if os.path.exists(filename): + self.delUserInI(site_path) + return mw.returnData(True, '已清除防跨站设置!') + self.addDirUserIni(site_path, run_path) + return mw.returnData(True, '已打开防跨站设置!') + + + def getDirBinding(self, site_id): + info = thisdb.getSitesById(site_id) + path = info['path'] + if not os.path.exists(path): + checks = ['/', '/usr', '/etc'] + if path in checks: + data = {} + data['dirs'] = [] + data['binding'] = [] + return mw.returnData(True, 'OK', data) + os.system('mkdir -p ' + path) + os.system('chmod 755 ' + path) + os.system('chown www:www ' + path) + siteName = info['name'] + mw.writeLog('网站管理', '站点[' + siteName + '],根目录[' + path + ']不存在,已重新创建!') + + dirnames = [] + for filename in os.listdir(path): + try: + filePath = path + '/' + filename + if os.path.islink(filePath): + continue + if os.path.isdir(filePath): + dirnames.append(filename) + except: + pass + + data = {} + data['dirs'] = dirnames + data['binding'] = thisdb.getBindingListBySiteId(site_id) + return mw.returnJson(True, 'OK', data) + + def addDirBind(self, site_id, domain, dir_name): + domain_split = domain.split(':') + domain = domain_split[0] + port = '80' + if len(domain_split) > 1: + port = domain_split[1] + if dir_name == '': + mw.returnData(False, '目录不能为空!') + + reg = r"^([\w\-\*]{1,100}\.){1,4}(\w{1,10}|\w{1,10}\.\w{1,10})$" + if not re.match(reg, domain): + return mw.returnData(False, '主域名格式不正确!') + + info = thisdb.getSitesById(site_id) + webdir = info['path'] + '/' + dir_name + + if thisdb.getBindingCountByDomain(domain): + return mw.returnData(False, '您添加的域名在子目录已存在!') + + if thisdb.getDomainCountByName(domain) > 0: + return mw.returnData(False, '您添加的域名已存在!') + + filename = self.getHostConf(info['name']) + conf = mw.readFile(filename) + if conf: + rep = r"enable-php-([0-9]{2,3})\.conf" + domain_split = re.search(rep, conf).groups() + version = domain_split[0] + + source_dirbind_tpl = self.getNgxTplDir() + '/nginx_dirbind.conf' + content = mw.readFile(source_dirbind_tpl) + content = content.replace('{$PORT}', port) + content = content.replace('{$PHPVER}', version) + content = content.replace('{$DIRBIND}', domain) + content = content.replace('{$ROOT_DIR}', webdir) + content = content.replace('{$SERVER_MAIN}', info['name']) + content = content.replace('{$OR_REWRITE}', self.rewritePath) + content = content.replace('{$PHP_DIR}', self.setupPath + '/php') + content = content.replace('{$LOGPATH}', mw.getLogsDir()) + + conf += "\r\n" + content + mw.backFile(filename) + mw.writeFile(filename, conf) + conf = mw.readFile(filename) + + # 检查配置是否有误 + isError = mw.checkWebConfig() + if isError != True: + mw.restoreFile(filename) + msg = 'ERROR:
                                            ' + isError.replace("\n", '
                                            ') + '
                                            ' + return mw.returnData(False, msg) + + thisdb.addBinding(site_id,domain,port,dir_name) + msg = mw.getInfo('网站[{1}]子目录[{2}]绑定到[{3}]',(info['name'], dir_name, domain)) + mw.writeLog('网站管理', msg) + mw.restartWeb() + mw.removeBackFile(filename) + return mw.returnData(True, '添加成功!') + + # 取子目录Rewrite + def getDirBindingRewrite(self, binding_id, add): + binding_info = thisdb.getBindingById(binding_id) + info = thisdb.getSitesById(binding_info['pid']) + + filename = self.getDirBindRewrite(info['name'], binding_info['path']) + if add == '1': + mw.writeFile(filename, '') + file = self.getHostConf(info['name']) + conf = mw.readFile(file) + domain = binding_info['domain'] + rep = "\n#BINDING-" + domain + "-START(.|\n)+BINDING-" + domain + "-END" + tmp = re.search(rep, conf).group() + dirConf = tmp.replace('rewrite/' + info['name'] + '.conf;', 'rewrite/' + info['name'] + '_' + binding_info['path'] + '.conf;') + conf = conf.replace(tmp, dirConf) + mw.writeFile(file, conf) + data = {} + data['rewrite_dir'] = self.rewritePath + data['status'] = False + if os.path.exists(filename): + data['status'] = True + data['data'] = mw.readFile(filename) + data['rlist'] = [] + for ds in os.listdir(self.rewritePath): + if ds[0:1] == '.': + continue + if ds == 'list.txt': + continue + data['rlist'].append(ds[0:len(ds) - 5]) + data['filename'] = filename + return data + + def delDirBinding(self, binding_id): + + binding_info = thisdb.getBindingById(binding_id) + info = thisdb.getSitesById(binding_info['pid']) + + filename = self.getHostConf(info['name']) + conf = mw.readFile(filename) + if conf: + rep = r"\s*.+BINDING-" + binding_info['domain'] + "-START(.|\n)+BINDING-" + binding_info['domain'] + "-END" + conf = re.sub(rep, '', conf) + mw.writeFile(filename, conf) + + filename = self.getDirBindRewrite(info['name'], binding_info['path']) + if os.path.exists(filename): + os.remove(filename) + + msg = mw.getInfo('删除网站[{1}]子目录[{2}]绑定',(info['name'], binding_info['path'])) + mw.writeLog('网站管理', msg) + mw.restartWeb() + + thisdb.deleteBindingById(binding_id) + return mw.returnJson(True, '删除成功!') + + + def logsOpen(self, site_id): + info = thisdb.getSitesById(site_id) + name = info['name'] + + filename = self.getHostConf(name) + if os.path.exists(filename): + conf = mw.readFile(filename) + rep = self.logsPath + '/' + name + '.log' + if conf.find(rep) != -1: + conf = conf.replace(rep + ' main', 'off') + else: + conf = conf.replace('access_log off', 'access_log ' + rep + ' main') + mw.writeFile(filename, conf) + + mw.restartWeb() + return mw.returnData(True, '操作成功!') + + def setSitePath(self, site_id, path): + path = self.getPath(path) + if path == "" or site_id == '0': + return mw.returnData(False, "目录不能为空!") + + import utils.file as file + if not file.checkDir(path): + return mw.returnData(False, "不能以系统关键目录作为站点目录") + + info = thisdb.getSitesById(site_id) + if info['path'] == path: + return mw.returnData(False, "与原路径一致,无需修改!") + host_conf = self.getHostConf(info['name']) + content = mw.readFile(host_conf) + if content: + content = content.replace(info['path'], path) + mw.writeFile(host_conf, content) + + thisdb.setSitesData(site_id, path=path) + msg = mw.getInfo('修改网站[{1}]物理路径成功!', (info['name'],)) + mw.writeLog('网站管理', msg) + mw.restartWeb() + return mw.returnData(True, "设置成功!") + + # 设置当前站点运行目录 + def setSiteRunPath(self, site_id, run_path): + info = thisdb.getSitesById(site_id) + site_name = info['name'] + site_path = info['path'] + + new_path = site_path + run_path + # 处理Nginx + site_host = self.getHostConf(site_name) + if os.path.exists(site_host): + content = mw.readFile(site_host) + rep = r'\s*root\s*(.+);' + path = re.search(rep, content).groups()[0] + content = content.replace(path, new_path) + mw.writeFile(site_host, content) + + mw.restartWeb() + return mw.returnData(True, '设置成功!') + + # 设置目录加密 + def setHasPwd(self, site_id, username, password): + if len(username.strip()) == 0 or len(password.strip()) == 0: + return mw.returnData(False, '用户名或密码不能为空!') + + info = thisdb.getSitesById(site_id) + siteName = info['name'] + filename = self.passPath + '/' + siteName + '.pass' + passconf = username + ':' + mw.hasPwd(password) + + configFile = self.getHostConf(siteName) + # 处理Nginx配置 + conf = mw.readFile(configFile) + if conf: + rep = '#error_page 404 /404.html;' + if conf.find(rep) == -1: + rep = '#error_page 404/404.html;' + data = ''' + #AUTH_START + auth_basic "Authorization"; + auth_basic_user_file %s; + #AUTH_END''' % (filename,) + conf = conf.replace(rep, rep + data) + mw.writeFile(configFile, conf) + # 写密码配置 + passDir = self.passPath + if not os.path.exists(passDir): + mw.execShell('mkdir -p ' + passDir) + mw.writeFile(filename, passconf) + + msg = mw.getInfo('设置网站[{1}]为需要密码认证!', (siteName,)) + mw.writeLog("网站管理", msg) + mw.restartWeb() + return mw.returnData(True, '设置成功!') + + # 取消目录加密 + def closeHasPwd(self, site_id): + info = thisdb.getSitesById(site_id) + siteName = info['name'] + configFile = self.getHostConf(siteName) + if os.path.exists(configFile): + conf = mw.readFile(configFile) + rep = r"\n\s*#AUTH_START(.|\n){1,200}#AUTH_END" + conf = re.sub(rep, '', conf) + mw.writeFile(configFile, conf) + + msg = mw.getInfo('清除网站[{1}]的密码认证!', (siteName,)) + mw.writeLog("网站管理", msg) + mw.restartWeb() + return mw.returnData(True, '设置成功!') + + def getSecurity(self, site_id): + info = thisdb.getSitesById(site_id) + name = info['name'] + filename = self.getHostConf(name) + conf = mw.readFile(filename) + data = {} + if conf.find('SECURITY-START') != -1: + rep = "#SECURITY-START(\n|.){1,500}#SECURITY-END" + tmp = re.search(rep, conf).group() + data['fix'] = re.search(r"\(.+\)\$", tmp).group().replace('(', '').replace(')$', '').replace('|', ',') + + data['status'] = False + data['none'] = False + + valid_referers = re.search(r"valid_referers\s+(.+);\n", tmp) + valid_referers_none = re.search(r"valid_referers\s+none\s+blocked\s+(.+);\n", tmp) + + if valid_referers or valid_referers_none: + data['status'] = True + + if valid_referers_none: + domain_t = valid_referers_none.groups()[0].split() + data['domains'] = ','.join(domain_t) + data['none'] = True + elif valid_referers: + domain_t = valid_referers.groups()[0].split() + data['domains'] = ','.join(domain_t) + data['none'] = False + else: + data['fix'] = 'gif|jpg|jpeg|png|bmp|swf|js|css|ttf|woff2' + domains = thisdb.getDomainBySiteId(site_id) + tmp = [] + for domain in domains: + tmp.append(domain['name']) + data['domains'] = ','.join(tmp) + data['status'] = False + data['none'] = False + return data + + def setSecurity(self, site_id, fix, domains, status, none=''): + info = thisdb.getSitesById(site_id) + name = info['name'] + if len(fix) < 2: + return mw.returnData(False, 'URL后缀不能为空!') + + file = self.getHostConf(name) + if os.path.exists(file): + conf = mw.readFile(file) + if status == 'false': + rep = r"\s{0,4}#SECURITY-START(\n|.){1,500}#SECURITY-END\n?" + conf = re.sub(rep, '', conf) + mw.writeLog('网站管理', '站点[' + name + ']已关闭防盗链设置!') + else: + rep = r"\s{0,4}#SECURITY-START(\n|.){1,500}#SECURITY-END\n?" + conf = re.sub(rep, '', conf) + + valid_referers = domains.strip().replace(',', ' ') + if none == 'true': + valid_referers = 'none blocked ' + valid_referers + + pre_path = self.setupPath + "/php/conf" + re_path = r"include\s+" + pre_path + "/enable-php-" + rconf = r'''#SECURITY-START 防盗链配置 + location ~ .*\.(%s)$ + { + expires 30d; + access_log /dev/null; + valid_referers %s; + if ($invalid_referer){ + return 404; + } + } + #SECURITY-END + include %s/enable-php-''' % (fix.strip().replace(',', '|'), valid_referers, pre_path) + conf = re.sub(re_path, rconf, conf) + mw.writeLog('网站管理', '站点[' + name + ']已开启防盗链!') + mw.writeFile(file, conf) + mw.restartWeb() + return mw.returnData(True, '设置成功!') + + def getSitePhpVersion(self, siteName): + conf = mw.readFile(self.getHostConf(siteName)) + rep = r"enable-php-(.*)\.conf" + find_php_cnf = re.search(rep, conf) + + def_pver = '00' + if find_php_cnf: + tmp = find_php_cnf.groups() + def_pver = tmp[0] + + data = {} + data['phpversion'] = def_pver + return data + + def httpToHttps(self, site_name): + file = self.getHostConf(site_name) + conf = mw.readFile(file) + if not conf: + return mw.returnData(False, '站点[{}]配置异常!'.format(site_name)) + + if conf.find('ssl_certificate') == -1: + return mw.returnData(False, '当前未开启SSL') + to = "#error_page 404/404.html;\n\ + #HTTP_TO_HTTPS_START\n\ + if ($server_port !~ 443){\n\ + rewrite ^(/.*)$ https://$host$1 permanent;\n\ + }\n\ + #HTTP_TO_HTTPS_END" + conf = conf.replace('#error_page 404/404.html;', to) + mw.writeFile(file, conf) + + mw.restartWeb() + return mw.returnData(True, '设置成功!') + + def closeToHttps(self, site_name): + file = self.getHostConf(site_name) + conf = mw.readFile(file) + if not conf: + return mw.returnData(False, '站点[{}]配置异常!'.format(site_name)) + rep = r"\n\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" + conf = re.sub(rep, '', conf) + rep = r"\s+if.+server_port.+\n.+\n\s+\s*}" + conf = re.sub(rep, '', conf) + mw.writeFile(file, conf) + + mw.restartWeb() + return mw.returnData(True, '关闭HTTPS跳转成功!') + + def getIndex(self, site_id): + info = thisdb.getSitesById(site_id) + file = self.getHostConf(info['name']) + conf = mw.readFile(file) + rep = r"\s+index\s+(.+);" + tmp = re.search(rep, conf).groups() + return tmp[0].replace(' ', ',') + + def setIndex(self, site_id, index): + if index.find('.') == -1: + return mw.returnData(False, '默认文档格式不正确,例:index.html') + + index = index.replace(' ', '') + index = index.replace(',,', ',') + + if len(index) < 3: + return mw.returnData(False, '默认文档不能为空!') + + info = thisdb.getSitesById(site_id) + siteName = info['name'] + index_l = index.replace(",", " ") + file = self.getHostConf(siteName) + conf = mw.readFile(file) + if conf: + rep = r"\s+index\s+.+;" + conf = re.sub(rep, "\n\tindex " + index_l + ";", conf) + mw.writeFile(file, conf) + + mw.writeLog('网站管理', '站点[{1}]设置{2}成功', (siteName, index_l)) + return mw.returnData(True, '设置成功!') + + def getLimitNet(self, site_id): + info = thisdb.getSitesById(site_id) + siteName = info['name'] + filename = self.getHostConf(siteName) + # 站点总并发 + data = {} + conf = mw.readFile(filename) + try: + rep = r"\s+limit_conn\s+perserver\s+([0-9]+);" + tmp = re.search(rep, conf).groups() + data['perserver'] = int(tmp[0]) + + # IP并发限制 + rep = r"\s+limit_conn\s+perip\s+([0-9]+);" + tmp = re.search(rep, conf).groups() + data['perip'] = int(tmp[0]) + + # 请求并发限制 + rep = r"\s+limit_rate\s+([0-9]+)\w+;" + tmp = re.search(rep, conf).groups() + data['limit_rate'] = int(tmp[0]) + except: + data['perserver'] = 0 + data['perip'] = 0 + data['limit_rate'] = 0 + return data + + def setLimitNet(self, site_id, perserver, perip, limit_rate): + str_perserver = 'limit_conn perserver ' + perserver + ';' + str_perip = 'limit_conn perip ' + perip + ';' + str_limit_rate = 'limit_rate ' + limit_rate + 'k;' + + info = thisdb.getSitesById(site_id) + siteName = info['name'] + + filename = self.getHostConf(siteName) + conf = mw.readFile(filename) + if(conf.find('limit_conn perserver') != -1): + # 替换总并发 + rep = r"limit_conn\s+perserver\s+([0-9]+);" + conf = re.sub(rep, str_perserver, conf) + + # 替换IP并发限制 + rep = r"limit_conn\s+perip\s+([0-9]+);" + conf = re.sub(rep, str_perip, conf) + + # 替换请求流量限制 + rep = r"limit_rate\s+([0-9]+)\w+;" + conf = re.sub(rep, str_limit_rate, conf) + else: + conf = conf.replace('#error_page 404/404.html;', "#error_page 404/404.html;\n "+\ + str_perserver+"\n "+\ + str_perip+"\n "+\ + str_limit_rate) + + mw.writeFile(filename, conf) + mw.restartWeb() + mw.writeLog('网站管理', '网站[{1}]流量限制已开启!', (siteName,)) + return mw.returnData(True, '设置成功!') + + def closeLimitNet(self, site_id): + info = thisdb.getSitesById(site_id) + siteName = info['name'] + + filename = self.getHostConf(siteName) + conf = mw.readFile(filename) + # 清理总并发 + rep = r"\s+limit_conn\s+perserver\s+([0-9]+);" + conf = re.sub(rep, '', conf) + + # 清理IP并发限制 + rep = r"\s+limit_conn\s+perip\s+([0-9]+);" + conf = re.sub(rep, '', conf) + + # 清理请求流量限制 + rep = r"\s+limit_rate\s+([0-9]+)\w+;" + conf = re.sub(rep, '', conf) + mw.writeFile(filename, conf) + mw.restartWeb() + mw.writeLog('网站管理', '网站[{1}]流量限制已关闭!', (siteName,)) + return mw.returnData(True, '已关闭流量限制!') + + + # 获取重定向配置 + def getRedirect(self, site_name): + redirect_file = self.getRedirectDataPath(site_name) + if not os.path.exists(redirect_file): + mw.execShell("mkdir " + self.getRedirectPath(site_name)) + return mw.returnData(True, "no exists!", {"result": [], "count": 0}) + + content = mw.readFile(redirect_file) + data = json.loads(content) + + for i in range(len(data)): + redirect_dir = self.getRedirectPath(site_name) + redirect_file = redirect_dir + '/' + data[i]['id'] + '.conf' + if os.path.exists(redirect_file): + data[i]['status'] = True + else: + data[i]['status'] = False + return mw.returnData(True, "ok", {"result": data, "count": len(data)}) + + def setRedirectStatus(self, site_name, redirect_id, status): + if status == '' or site_name == '' or redirect_id == '': + return mw.returnData(False, "必填项不能为空!") + + conf_file = "{}/{}/{}.conf".format(self.redirectPath, site_name, redirect_id) + conf_txt = "{}/{}/{}.conf.txt".format(self.redirectPath, site_name, redirect_id) + + if status == '1': + mw.execShell('mv ' + conf_txt + ' ' + conf_file) + else: + mw.execShell('mv ' + conf_file + ' ' + conf_txt) + + mw.restartWeb() + return mw.returnData(True, "OK") + + # 操作 重定向配置 + def operateRedirectConf(self, siteName, method='start'): + vhost_file = self.getHostConf(siteName) + content = mw.readFile(vhost_file) + + cnf_301 = '''#301-START + include %s/*.conf; + #301-END''' % (self.getRedirectPath(siteName,)) + + cnf_301_source = '#301-START' + # print('operateRedirectConf', content.find('#301-END')) + if content.find('#301-END') != -1: + if method == 'stop': + rep = '#301-START(\n|.){1,500}#301-END' + content = re.sub(rep, '#301-START', content) + else: + if method == 'start': + content = re.sub(cnf_301_source, cnf_301, content) + + mw.writeFile(vhost_file, content) + + # get redirect status + def setRedirect(self, site_name, site_from, to, type, r_type, keep_path): + if site_name == '' or site_from == '' or to == '' or type == '' or r_type == '': + return mw.returnData(False, "必填项不能为空!") + + redirect_file = self.getRedirectDataPath(site_name) + content = mw.readFile(redirect_file) if os.path.exists(redirect_file) else "" + data = json.loads(content) if content != "" else [] + + _r_type = 0 if r_type == "301" else 1 + _type_code = 0 if type == "path" else 1 + _keep_path = 1 if keep_path == "1" else 0 + + # check if domain exists in site + if _type_code == 1: + domain_list = mw.M('domain').where("name=?", (site_name,)).field('id,pid,name,port,add_time').select() + site_domain_lists = mw.M('domain').where("pid=?", (domain_list[0]['pid'],)).field('name').select() + found = False + for item in site_domain_lists: + if item['name'] == site_from: + found = True + break + if found == False: + return mw.returnData(False, "域名不存在!") + + file_content = "" + # path + if _type_code == 0: + redirect_type = "permanent" if _r_type == 0 else "redirect" + if not site_from.startswith("/"): + site_from = "/{}".format(site_from) + if _keep_path == 1: + to = "{}$1".format(to) + site_from = "{}(.*)".format(site_from) + file_content = "rewrite ^{} {} {};".format(site_from, to, redirect_type) + # domain + else: + if _keep_path == 1: + _to = "{}$request_uri".format(to) + + redirect_type = "301" if _r_type == 0 else "302" + _if = "if ($host ~ '^{}')".format(site_from) + _return = "return {} {}; ".format(redirect_type, to) + file_content = _if + "{\r\n " + _return + "\r\n}" + + _id = mw.md5("{}+{}".format(file_content, site_name)) + + # 防止规则重复 + for item in data: + if item["r_from"] == site_from: + return mw.returnData(False, "重复的规则!") + + rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" + if not re.match(rep, to): + return mw.returnData(False, "错误的目标地址") + + # write data json file + data.append({"r_from": site_from, "type": _type_code, "r_type": _type_code,"r_to": to, 'keep_path': _keep_path, 'id': _id}) + mw.writeFile(redirect_file, json.dumps(data)) + mw.writeFile("{}/{}.conf".format(self.getRedirectPath(site_name), _id), file_content) + + self.operateRedirectConf(site_name, 'start') + mw.restartWeb() + return mw.returnData(True, "设置成功") + + def getRedirectConf(self, site_name, redirect_id): + if redirect_id == '' or site_name == '': + return mw.returnData(False, "必填项不能为空!") + + path = self.getRedirectPath(site_name) + conf = "{}/{}.conf".format(path, redirect_id) + data = mw.readFile(conf) + if data == False: + return mw.returnData(False, "获取失败!") + return mw.returnData(True, "ok", {"result": data}) + + # 删除指定重定向 + def delRedirect(self,siteName, rid): + if rid == '' or siteName == '': + return mw.returnData(False, "必填项不能为空!") + + try: + data_path = self.getRedirectDataPath(siteName) + data_content = mw.readFile(data_path) if os.path.exists(data_path) else "" + data = json.loads(data_content) if data_content != "" else [] + for item in data: + if item["id"] == rid: + data.remove(item) + break + # write database + mw.writeFile(data_path, json.dumps(data)) + # data is empty ,should stop + if len(data) == 0: + self.operateRedirectConf(siteName, 'stop') + # remove conf file + mw.execShell("rm -rf {}/{}.conf".format(self.getRedirectPath(siteName), rid)) + except Exception as e: + return mw.returnData(False, "删除失败:"+str(e)) + return mw.returnData(True, "删除成功!") + + # 读取 网站 反向代理列表 + def getProxyList(self, site_name): + data_path = self.getProxyDataPath(site_name) + + if not os.path.exists(data_path): + mw.execShell("mkdir {}/{}".format(self.proxyPath, site_name)) + return mw.returnData(True, "", {"result": [], "count": 0}) + + content = mw.readFile(data_path) + data = json.loads(content) + tmp = [] + for proxy in data: + proxy_dir = "{}/{}".format(self.proxyPath, site_name) + proxy_dir_file = proxy_dir + '/' + proxy['id'] + '.conf' + if os.path.exists(proxy_dir_file): + proxy['status'] = True + else: + proxy['status'] = False + tmp.append(proxy) + return mw.returnData(True, "ok", {"result": data, "count": len(data)}) + + # 操作 反向代理配置 + def operateProxyConf(self, site_name, method='start'): + vhost_file = self.getHostConf(site_name) + content = mw.readFile(vhost_file) + + proxy_cnf = '''#PROXY-START + include %s/*.conf; + #PROXY-END''' % (self.getProxyPath(site_name)) + + proxy_cnf_source = '#PROXY-START' + + if content.find('#PROXY-END') != -1: + if method == 'stop': + rep = '#PROXY-START(\n|.){1,500}#PROXY-END' + content = re.sub(rep, '#PROXY-START', content) + else: + if method == 'start': + content = re.sub(proxy_cnf_source, proxy_cnf, content) + + mw.writeFile(vhost_file, content) + + # 设置 网站 反向代理列表 + def setProxy(self, site_name, site_from, to, host, name, open_proxy, open_cors, open_cache, cache_time, proxy_id): + from urllib.parse import urlparse + if site_name == "" or site_from == "" or to == "" or host == "" or name == "": + return mw.returnData(False, "必填项不能为空") + + rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" + if not re.match(rep, to): + return mw.returnData(False, "错误的目标地址!") + + # get host from url + # try: + # if host == "$host": + # host_tmp = urlparse(to) + # host = host_tmp.netloc + + # except Exception as e: + # return mw.returnData(False, "错误的目标地址") + # print(host) + + proxy_site_path = self.getProxyDataPath(site_name) + data_content = mw.readFile(proxy_site_path) if os.path.exists(proxy_site_path) else "" + data = json.loads(data_content) if data_content != "" else [] + + proxy_action = 'add' + if proxy_id == "": + proxy_id = mw.md5("{}".format(name)) + else: + proxy_action = 'edit' + + if proxy_action == "add": + for item in data: + if item["name"] == name: + return mw.returnData(False, "名称重复!!") + if item["from"] == site_from: + return mw.returnData(False, "代理目录已存在!!") + + tpl = "#PROXY-START\n\ +location ^~ {from} {\n\ + add_header X-Cache $upstream_cache_status;\n\ + {cors}\n\ + proxy_pass {to};\n\ + proxy_set_header Host {host};\n\ + proxy_ssl_server_name on;\n\ + proxy_set_header X-Real-IP $remote_addr;\n\ + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\ + proxy_set_header REMOTE-HOST $remote_addr;\n\ + proxy_set_header Upgrade $http_upgrade;\n\ + proxy_set_header Connection $connection_upgrade;\n\ + proxy_http_version 1.1;\n\ + \n\ + {proxy_cache}\n\ +}\n\ +# PROXY-END" + + tpl_proxy_cache = "\n\ + if ( $uri ~* \\.(gif|png|jpg|jpeg|css|js|ttf|woff|woff2)$ )\n\ + {\n\ + expires {cache_time}m;\n\ + }\n\ + proxy_ignore_headers Set-Cookie Cache-Control expires;\n\ + proxy_cache mw_cache;\n\ + proxy_cache_key \"$host$uri$is_args$args\";\n\ + proxy_cache_valid 200 304 301 302 {cache_time}m;\n\ +" + tpl_proxy_nocache_bak = "\n\ + set $static_files_app 0; \n\ + if ( $uri ~* \\.(gif|png|jpg|jpeg|css|js|ttf|woff|woff2)$ )\n\ + {\n\ + set $static_files_app 1;\n\ + expires 12h;\n\ + }\n\ + if ( $static_files_app = 0 )\n\ + {\n\ + add_header Cache-Control no-cache;\n\ + }\n\ +" + + tpl_proxy_nocache = "\n\ + add_header Cache-Control no-cache;\n\ +" + tpl_proxy_cors = "\n\ + add_header Access-Control-Allow-Origin *;\n\ + add_header Access-Control-Allow-Headers *;\n\ + add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';\n\ + if ($request_method = 'OPTIONS') {\n\ + return 204;\n\ + }\n\ +" + + # replace + if site_from[0] != '/': + site_from = '/' + site_from + tpl = tpl.replace("{from}", site_from, 999) + tpl = tpl.replace("{to}", to) + tpl = tpl.replace("{host}", host, 999) + tpl = tpl.replace("{cache_time}", cache_time, 999) + + if open_cache == 'on': + tpl_proxy_cache = tpl_proxy_cache.replace("{cache_time}", cache_time, 999) + tpl = tpl.replace("{proxy_cache}", tpl_proxy_cache, 999) + else: + tpl = tpl.replace("{proxy_cache}", tpl_proxy_nocache, 999) + + if open_cors == 'on': + tpl = tpl.replace("{cors}", tpl_proxy_cors, 999) + else: + tpl = tpl.replace("{cors}", '', 999) + + + conf_proxy = "{}/{}.conf".format(self.getProxyPath(site_name), proxy_id) + conf_bk = "{}/{}.conf.txt".format(self.getProxyPath(site_name), proxy_id) + mw.writeFile(conf_proxy, tpl) + + rule_test = mw.checkWebConfig() + if rule_test != True: + os.remove(conf_proxy) + return mw.returnData(False, "OpenResty配置测试不通过, 请重试: {}".format(rule_test)) + + if proxy_action == "add": + # 添加代理 + proxy_id = mw.md5("{}".format(name)) + for item in data: + if item["name"] == name: + return mw.returnData(False, "名称重复!") + if item["from"] == site_from: + return mw.returnData(False, "代理目录已存在!") + data.append({ + "name": name, + "from": site_from, + "to": to, + "host": host, + "open_cache": open_cache, + "cache_time": cache_time, + "open_proxy": open_proxy, + "open_cors": open_cors, + "id": proxy_id, + }) + else: + # 修改代理 + dindex = -1 + for x in range(len(data)): + if data[x]["id"] == proxy_id: + dindex = x + break + if dindex < 0: + return mw.returnData(False, "异常请求") + data[dindex]['from'] = site_from + data[dindex]['to'] = to + data[dindex]['host'] = host + data[dindex]['open_cache'] = open_cache + data[dindex]['cache_time'] = cache_time + data[dindex]['open_proxy'] = open_proxy + data[dindex]['open_cors'] = open_cors + + if open_proxy != 'on': + os.rename(conf_proxy, conf_bk) + + mw.writeFile(proxy_site_path, json.dumps(data)) + self.operateProxyConf(site_name, 'start') + mw.restartWeb() + return mw.returnData(True, "ok", {"hash": proxy_id}) + + def setProxyStatus(self, site_name, proxy_id, status): + if status == '' or site_name == '' or proxy_id == '': + return mw.returnData(False, "必填项不能为空!") + + conf_file = "{}/{}/{}.conf".format(self.proxyPath, site_name, proxy_id) + conf_txt = "{}/{}/{}.conf.txt".format(self.proxyPath, site_name, proxy_id) + + if status == '1': + mw.execShell('mv ' + conf_txt + ' ' + conf_file) + else: + mw.execShell('mv ' + conf_file + ' ' + conf_txt) + + mw.restartWeb() + return mw.returnData(True, "OK") + + + def closeProxyAll(self, site_name): + self.close_proxy = [] + proxy_path = self.getProxyDataPath(site_name) + if os.path.exists(proxy_path): + content = mw.readFile(proxy_path) + data = json.loads(content) + for proxy in data: + proxy_dir = "{}/{}".format(self.proxyPath, site_name) + proxy_conf = proxy_dir + '/' + proxy['id'] + '.conf' + proxy_txt = "{}/{}/{}.conf.txt".format(self.proxyPath, site_name, proxy['id']) + if os.path.exists(proxy_conf): + self.close_proxy.append(proxy['id']) + mw.execShell('mv ' + proxy_conf + ' ' + proxy_txt) + mw.restartWeb() + return True + + def openProxyByOpen(self, site_name): + for proxy_id in self.close_proxy: + proxy_dir = "{}/{}".format(self.proxyPath, site_name) + proxy_conf = proxy_dir + '/' + proxy_id + '.conf' + proxy_txt = "{}/{}/{}.conf.txt".format(self.proxyPath, site_name, proxy_id) + if os.path.exists(proxy_txt): + mw.execShell('mv ' + proxy_txt + ' ' + proxy_conf) + + if len(self.close_proxy) > 0: + mw.restartWeb() + self.close_proxy = [] + return True + + def closeRedirectAll(self, site_name): + self.close_redirect = [] + redirect_path = self.getRedirectDataPath(site_name) + if os.path.exists(redirect_path): + content = mw.readFile(redirect_path) + data = json.loads(content) + for redirect_data in data: + redirect_dir = "{}/{}".format(self.redirectPath, site_name) + redirect_conf = redirect_dir + '/' + redirect_data['id'] + '.conf' + redirect_txt = "{}/{}/{}.conf.txt".format(self.redirectPath, site_name, redirect_data['id']) + if os.path.exists(redirect_conf): + self.close_redirect.append(redirect_data['id']) + mw.execShell('mv ' + redirect_conf + ' ' + redirect_txt) + mw.restartWeb() + + def openRedirectByOpen(self, site_name): + for redirect_id in self.close_redirect: + redirect_dir = "{}/{}".format(self.redirectPath, site_name) + redirect_conf = redirect_dir + '/' + redirect_id + '.conf' + redirect_txt = "{}/{}/{}.conf.txt".format(self.redirectPath, site_name, redirect_id) + if os.path.exists(redirect_txt): + mw.execShell('mv ' + redirect_txt + ' ' + redirect_conf) + + if len(self.close_redirect) > 0: + mw.restartWeb() + self.close_redirect = [] + return True + + def saveRedirectConf(self, site_name, redirect_id, config): + if redirect_id == '' or site_name == '': + return mw.returnData(False, "必填项不能为空!") + + _old_config = mw.readFile("{}/{}/{}.conf".format(self.redirectPath, site_name, redirect_id)) + if _old_config == False: + return mw.returnData(False, "非法操作") + + mw.writeFile("{}/{}/{}.conf".format(self.redirectPath, site_name, redirect_id), config) + rule_test = mw.checkWebConfig() + if rule_test != True: + mw.writeFile("{}/{}/{}.conf".format(self.redirectPath,site_name, redirect_id), _old_config) + return mw.returnData(False, "OpenResty 配置测试不通过, 请重试: {}".format(rule_test)) + + self.operateRedirectConf(site_name, 'start') + mw.restartWeb() + return mw.returnData(True, "ok") + + + def getProxyConf(self, site_name, proxy_id): + if proxy_id == '' or site_name == '': + return mw.returnData(False, "必填项不能为空!") + + conf_file = "{}/{}/{}.conf".format(self.proxyPath, site_name, proxy_id) + if not os.path.exists(conf_file): + conf_file = "{}/{}/{}.conf.txt".format(self.proxyPath, site_name, proxy_id) + + if not os.path.exists(conf_file): + return mw.returnData(False, "获取失败!") + + data = mw.readFile(conf_file) + return mw.returnData(True, "ok", {"result": data}) + + def saveProxyConf(self, site_name, proxy_id, config): + + if proxy_id == '' or site_name == '': + return mw.returnData(False, "必填项不能为空!") + + proxy_file = "{}/{}/{}.conf".format(self.proxyPath, site_name, proxy_id) + mw.backFile(proxy_file) + mw.writeFile(proxy_file, config) + rule_test = mw.checkWebConfig() + if rule_test != True: + mw.restoreFile(proxy_file) + mw.removeBackFile(proxy_file) + return mw.returnData(False, "OpenResty 配置测试不通过, 请重试: {}".format(rule_test)) + + mw.removeBackFile(proxy_file) + self.operateRedirectConf(site_name, 'start') + mw.restartWeb() + return mw.returnData(True, "ok") + + def delProxy(self, site_name, proxy_id): + if proxy_id == '' or site_name == '': + return mw.returnData(False, "必填项不能为空!") + + try: + data_path = self.getProxyDataPath(site_name) + data_content = mw.readFile(data_path) if os.path.exists(data_path) else "" + data = json.loads(data_content) if data_content != "" else [] + for item in data: + if item["id"] == proxy_id: + data.remove(item) + break + # write database + mw.writeFile(data_path, json.dumps(data)) + + # data is empty,should stop + if len(data) == 0: + self.operateProxyConf(site_name, 'stop') + # remove conf file + cmd = "rm -rf {}/{}.conf*".format(self.getProxyPath(site_name), proxy_id) + mw.execShell(cmd) + except: + return mw.returnData(False, "删除反代失败!") + + mw.restartWeb() + return mw.returnData(True, "删除反代成功!") + + # 是否跳转到https + def isToHttps(self, site_name): + file = self.getHostConf(site_name) + conf = mw.readFile(file) + if conf: + if conf.find('$server_port !~ 443') != -1: + return True + return False + + def getSsl(self, site_name, ssl_type): + file = self.getHostConf(site_name) + content = mw.readFile(file) + + key_text = 'ssl_certificate' + status = True + stype = 0 + if content.find(key_text) == -1: + status = False + stype = -1 + + to_https = self.isToHttps(site_name) + + site_info = thisdb.getSitesByName(site_name) + domains = thisdb.getDomainBySiteId(site_info['id']) + + path = self.sslDir + '/' + site_name + if ssl_type == 'lets': + csr_path = self.sslLetsDir + '/' + site_name + '/fullchain.pem' # Let生成证书路径 + key_path = self.sslLetsDir + '/' + site_name + '/privkey.pem' # Let密钥文件路径 + elif ssl_type == 'acme': + csr_path = path + '/fullchain.pem' + key_path = path + '/privkey.pem' + # acme_dir = mw.getAcmeDomainDir(site_name) + # csr_path = acme_dir + '/fullchain.cer' # ACME生成证书路径 + # key_path = acme_dir + '/' + site_name + '.key' # ACME密钥文件路径 + else: + csr_path = path + '/fullchain.pem' # 生成证书路径 + key_path = path + '/privkey.pem' # 密钥文件路径 + + key = '' + if os.path.exists(key_path): + key = mw.readFile(key_path) + + csr = '' + if os.path.exists(csr_path): + csr = mw.readFile(csr_path) + + cert_data = mw.getCertName(csr_path) + # print(csr_path,cert_data) + data = { + 'status': status, + 'domain': domains, + 'key': key, + 'csr': csr, + 'type': stype, + 'httpTohttps': to_https, + 'cert_data': cert_data, + } + return mw.returnData(True, 'OK', data) + + def saveCert(self, site_name, keyPath, certPath): + try: + certInfo = mw.getCertName(certPath) + if not certInfo: + return mw.returnData(False, '证书解析失败!') + vpath = self.sslDir + '/' + site_name + if not os.path.exists(vpath): + os.system('mkdir -p ' + vpath) + mw.writeFile(vpath + '/privkey.pem', mw.readFile(keyPath)) + mw.writeFile(vpath + '/fullchain.pem', mw.readFile(certPath)) + mw.writeFile(vpath + '/info.json', json.dumps(certInfo)) + return mw.returnData(True, '证书保存成功!') + except Exception as e: + return mw.returnData(False, '证书保存失败!') + + def getCertList(self): + try: + vpath = self.sslDir + if not os.path.exists(vpath): + os.system('mkdir -p ' + vpath) + data = [] + for d in os.listdir(vpath): + + # keyPath = self.sslDir + siteName + '/privkey.pem' + # certPath = self.sslDir + siteName + '/fullchain.pem' + + keyPath = vpath + '/' + d + '/privkey.pem' + certPath = vpath + '/' + d + '/fullchain.pem' + if os.path.exists(keyPath) and os.path.exists(certPath): + self.saveCert(d, keyPath, certPath) + + mpath = vpath + '/' + d + '/info.json' + if not os.path.exists(mpath): + continue + + tmp = mw.readFile(mpath) + if not tmp: + continue + tmp1 = json.loads(tmp) + data.append(tmp1) + return mw.returnData(True, 'OK', data) + except: + return mw.returnData(True, 'OK', []) + + + def setPhpVersion(self, siteName, version): + # nginx + file = self.getHostConf(siteName) + conf = mw.readFile(file) + if conf: + rep = r"enable-php-(.*)\.conf" + tmp = re.search(rep, conf).group() + conf = conf.replace(tmp, 'enable-php-' + version + '.conf') + mw.writeFile(file, conf) + + msg = mw.getInfo('成功切换网站[{1}]的PHP版本为PHP-{2}', (siteName, version)) + mw.writeLog("网站管理", msg) + mw.restartWeb() + return mw.returnData(True, msg) + + + def setDefaultSite(self, name): + # 清理旧的 + default_site = thisdb.getOption('default_site', default='') + if default_site: + path = self.getHostConf(default_site) + if os.path.exists(path): + conf = mw.readFile(path) + rep = r"listen\s+80.+;" + conf = re.sub(rep, 'listen 80;', conf, 1) + rep = r"listen\s+443.+;" + conf = re.sub(rep, 'listen 443 ssl;', conf, 1) + mw.writeFile(path, conf) + + path = self.getHostConf(name) + if os.path.exists(path): + conf = mw.readFile(path) + rep = r"listen\s+80\s*;" + conf = re.sub(rep, 'listen 80 default_server;', conf, 1) + rep = r"listen\s+443\s*ssl\s*\w*\s*;" + conf = re.sub(rep, 'listen 443 ssl default_server;', conf, 1) + mw.writeFile(path, conf) + + thisdb.setOption('default_site', name) + mw.restartWeb() + return mw.returnData(True, '设置成功!') + + def setCliPhpVersion(self, version): + php_bin = '/usr/bin/php' + php_bin_src = "/www/server/php/%s/bin/php" % version + php_ize = '/usr/bin/phpize' + php_ize_src = "/www/server/php/%s/bin/phpize" % version + php_fpm = '/usr/bin/php-fpm' + php_fpm_src = "/www/server/php/%s/sbin/php-fpm" % version + php_pecl = '/usr/bin/pecl' + php_pecl_src = "/www/server/php/%s/bin/pecl" % version + php_pear = '/usr/bin/pear' + php_pear_src = "/www/server/php/%s/bin/pear" % version + if not os.path.exists(php_bin_src): + return mw.returnData(False, '指定PHP版本未安装!') + + is_chattr = mw.execShell('lsattr /usr|grep /usr/bin')[0].find('-i-') + if is_chattr != -1: + mw.execShell('chattr -i /usr/bin') + mw.execShell("rm -f " + php_bin + ' ' + php_ize + ' ' + php_fpm + ' ' + php_pecl + ' ' + php_pear) + mw.execShell("ln -sf %s %s" % (php_bin_src, php_bin)) + mw.execShell("ln -sf %s %s" % (php_ize_src, php_ize)) + mw.execShell("ln -sf %s %s" % (php_fpm_src, php_fpm)) + mw.execShell("ln -sf %s %s" % (php_pecl_src, php_pecl)) + mw.execShell("ln -sf %s %s" % (php_pear_src, php_pear)) + if is_chattr != -1: + mw.execShell('chattr +i /usr/bin') + mw.writeLog('面板设置', '设置PHP-CLI版本为: %s' % version) + return mw.returnData(True, '设置成功!') + + def addSiteTypes(name): + if not name: + return mw.returnData(False, "分类名称不能为空") + if len(name) > 18: + return mw.returnData(False, "分类名称长度不能超过6个汉字或18位字母") + + all_count = thisdb.getSiteTypesCount() + if all_count >= 10: + return mw.returnData(False, '最多添加10个分类!') + + name_count = thisdb.getSiteTypesCountByName(name) + if name_count > 0: + return mw.returnData(False, "指定分类名称已存在!") + + thisdb.addSiteTypes(name) + return mw.returnData(True, '添加成功!') + + def getDnsapi(self): + dnsapi_data = thisdb.getOptionByJson('dnsapi', default={}) + dnsapi_option = [ + {"name":"none", "title":'手动解析', 'key':'', 'help':''}, + {"name":"dns_ali", "title":'Aliyun', 'key':'Ali_Key:Ali_Secret', 'help':'阿里云控制台》用户头像》accesskeys按指引获取AccessKey/SecretKey'}, + {"name":"dns_huaweicloud", "title":'华为云', 'key':'HUAWEICLOUD_Username:HUAWEICLOUD_Password:HUAWEICLOUD_DomainName'}, + {"name":"dns_cf", "title":'cloudflare', 'key':'CF_Key:CF_Email:CF_Token:CF_Account_ID:CF_Zone_ID', 'help':'CloudFlare后台获取Global API Key'}, + {"name":"dns_dp", "title":'dnspod/国内', 'key':'DP_Id:DP_Key','help':'DnsPod后台》用户中心》安全设置,开启API Token'}, + {"name":"dns_dpi", "title":'dnspod/国际', 'key':'DPI_Id:DPI_Key','help':'DnsPod后台》用户中心》安全设置,开启API Token'}, + {"name":"dns_tencent", "title":"腾讯云DNS", 'key':'Tencent_SecretId:Tencent_SecretKey', 'help':'腾讯云后台获取通行证'}, + {"name":"dns_gd", "title":'GoDaddy', 'key':'GD_Key:GD_Secret'}, + # {"name":"dns_pdns", "title":'PowerDNS', 'key':'PDNS_Url:PDNS_ServerId:PDNS_Token:PDNS_Ttl'}, + # {"name":"dns_lua", "title":'LuaDNS', 'key':'LUA_Key:LUA_Email'}, + # {"name":"dns_me", "title":'DNSMadeEasy', 'key':'ME_Key:ME_Secret'}, + {"name":"dns_aws", "title":'Amazon Route53', 'key':'AWS_ACCESS_KEY_ID:AWS_SECRET_ACCESS_KEY'}, + # {"name":"dns_ispconfig", "title":'ISPConfig', 'key':'ISPC_User:ISPC_Password:ISPC_Api:ISPC_Api_Insecure'}, + # {"name":"dns_ad", "title":'Alwaysdata', 'key':'AD_API_KEY'}, + {"name":"dns_linode_v4", "title":'Linode', 'key':'LINODE_V4_API_KEY'}, + # {"name":"dns_freedns", "title":'FreeDNS', 'key':'FREEDNS_User:FREEDNS_Password'}, + # {"name":"dns_cyon", "title":'cyon.ch', 'key':'CY_Username:CY_Password:CY_OTP_Secret'}, + # {"name":"dns_gandi_livedns", "title":'LiveDNS', 'key':'GANDI_LIVEDNS_TOKEN'}, + # {"name":"dns_knot", "title":'Knot', 'key':'KNOT_SERVER:KNOT_KEY'}, + {"name":"dns_dgon", "title":'DigitalOcean', 'key':'DO_API_KEY'}, + # {"name":"dns_cloudns", "title":'ClouDNS.net', 'key':'CLOUDNS_SUB_AUTH_ID:CLOUDNS_AUTH_PASSWORD'}, + {"name":"dns_namesilo", "title":'Namesilo', 'key':'Namesilo_Key'}, + {"name":"dns_azure", "title":'Azure', 'key':'AZUREDNS_SUBSCRIPTIONID:AZUREDNS_TENANTID:AZUREDNS_APPID:AZUREDNS_CLIENTSECRET'}, + # {"name":"dns_selectel", "title":'selectel.com', 'key':'SL_Key'}, + # {"name":"dns_zonomi", "title":'zonomi.com', 'key':'ZM_Key'}, + # {"name":"dns_kinghost", "title":'KingHost', 'key':'KINGHOST_Username:KINGHOST_Password'}, + # {"name":"dns_zilore", "title":'Zilore', 'key':'Zilore_Key'}, + {"name":"dns_gcloud", "title":'Google Cloud DNS', 'key':'CLOUDSDK_ACTIVE_CONFIG_NAME'}, + # {"name":"dns_mydnsjp", "title":'MyDNS.JP', 'key':'MYDNSJP_MasterID:MYDNSJP_Password'}, + # {"name":"dns_doapi", "title":'do.de', 'key':'DO_LETOKEN'}, + # {"name":"dns_online", "title":'Online', 'key':'ONLINE_API_KEY'}, + # {"name":"dns_cn", "title":'Core-Networks', 'key':'CN_User:CN_Password'}, + # {"name":"dns_ultra", "title":'UltraDNS', 'key':'ULTRA_USR:ULTRA_PWD'}, + # {"name":"dns_hetzner", "title":'Hetzner', 'key':'HETZNER_Token'}, + # {"name":"dns_ddnss", "title":'DDNSS.de', 'key':'DDNSS_Token'}, + ]; + + for i in range(len(dnsapi_option)): + dval = dnsapi_option[i]['key'] + dname = dnsapi_option[i]['name'] + if dname == 'none': + continue + + keys = dval.split(':') + data = {} + if dname in dnsapi_data: + dnsapi_option[i]['data'] = dnsapi_data[dname] + dnsapi_option[i]['title'] = dnsapi_option[i]['title'] + ' - [已配置]' + else: + t = {} + for field in keys: + t[field] = '' + dnsapi_option[i]['data'] = t + return dnsapi_option + + def setDnsapi(self, type, data): + dnsapi_data = thisdb.getOptionByJson('dnsapi', default={}) + dnsapi_data[type] = json.loads(data) + thisdb.setOption('dnsapi',json.dumps(dnsapi_data)) + return mw.returnData(True, '设置成功!') + + def acmeLogFile(self): + return mw.getPanelDir() + '/logs/acme.log' + + def writeAcmeLog(self,msg): + log_file = self.acmeLogFile() + mw.writeFile(log_file, msg+"\n", 'w+') + return True + + def letLogFile(self): + return mw.getPanelDir() + '/logs/letsencrypt.log' + + def writeLetLog(self,msg): + log_file = self.letLogFile() + mw.writeFile(log_file, msg+"\n", "wb+") + return True + + def closeSslConf(self, site_name): + file = self.getHostConf(site_name) + conf = mw.readFile(file) + + if conf: + rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_certificate\\s+.+;\\s+ssl_certificate_key\\s+.+;" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_protocols\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_ciphers\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_prefer_server_ciphers\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_session_cache\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = r"\s+ssl_session_timeout\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = r"\s+ssl_ecdh_curve\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = r"\s+ssl_session_tickets\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = r"\s+ssl_stapling\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = r"\s+ssl_stapling_verify\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = r"\s+add_header\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = r"\s+add_header\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = r"\s+ssl\s+on;" + conf = re.sub(rep, '', conf) + rep = r"\s+error_page\s497.+;" + conf = re.sub(rep, '', conf) + rep = r"\s+if.+server_port.+\n.+\n\s+\s*}" + conf = re.sub(rep, '', conf) + rep = r"\s+listen\s+443.*;" + conf = re.sub(rep, '', conf) + rep = r"\s+listen\s+\[\:\:\]\:443.*;" + conf = re.sub(rep, '', conf) + rep = r"\s+http2\s+on;" + conf = re.sub(rep, '', conf) + mw.writeFile(file, conf) + + msg = mw.getInfo('网站[{1}]关闭SSL成功!', (site_name,)) + mw.writeLog('网站管理', msg) + mw.restartWeb() + return mw.returnData(True, 'SSL已关闭!') + + def deleteSsl(self,site_name,ssl_type): + path = self.sslDir + '/' + site_name + csr_path = path + '/fullchain.pem' + + file = self.getHostConf(site_name) + content = mw.readFile(file) + key_text = 'ssl_certificate' + status = True + if content.find(key_text) == -1: + status = False + + if ssl_type == 'now': + if status: + return mw.returnData(False, '使用中,先关闭再删除') + if os.path.exists(path): + mw.execShell('rm -rf ' + path) + else: + return mw.returnData(False, '还未申请!') + elif ssl_type == 'lets': + ssl_lets_dir = self.sslLetsDir + '/' + site_name + csr_lets_path = ssl_lets_dir + '/fullchain.pem' # 生成证书路径 + if mw.md5(mw.readFile(csr_lets_path)) == mw.md5(mw.readFile(csr_path)): + return mw.returnData(False, '使用中,先关闭再删除') + mw.execShell('rm -rf ' + ssl_lets_dir) + elif ssl_type == 'acme': + ssl_acme_dir = mw.getAcmeDomainDir(site_name) + csr_acme_path = ssl_acme_dir + '/fullchain.cer' # 生成证书路径 + if mw.md5(mw.readFile(csr_acme_path)) == mw.md5(mw.readFile(csr_path)): + return mw.returnData(False, '使用中,先关闭再删除') + mw.execShell('rm -rf ' + ssl_acme_dir) + + mw.restartWeb() + return mw.returnData(True, '删除成功') + + def createAcmeFile(self, site_name, apply_ca, domains, email, force, renew): + site_conf = self.getHostConf(site_name) + if not os.path.exists(site_conf): + return mw.returnData(False, '配置异常!') + + # 关闭反向代理 + self.closeProxyAll(site_name) + # 关闭重定向 + self.closeRedirectAll(site_name) + + site_info = thisdb.getSitesByName(site_name) + path = self.getSitePath(site_name) + if path == '': + return mw.returnData(False, '【'+site_name+'】配置文件,异常!') + + src_path = site_info['path'] + acme_dir = mw.getAcmeDir() + + if force == 'true': + force_bool = True + + if renew == 'true': + cmd = acme_dir + "/acme.sh --renew --yes-I-know-dns-manual-mode-enough-go-ahead-please" + else: + cmd = acme_dir + "/acme.sh --issue --force" + + # 确定主域名顺序 + t = [] + if site_name in domains: + t.append(site_name) + for dd in domains: + if dd == site_name: + continue + t.append(dd) + domains = t + + domain_nums = 0 + for d in domains: + if mw.checkIp(d): + continue + if d.find('*.') != -1: + return mw.returnData(False, '泛域名不能使用【文件验证】的方式申请证书!') + cmd += ' -w ' + path + cmd += ' -d ' + d + domain_nums += 1 + if domain_nums == 0: + return mw.returnData(False, '请选择域名(不包括IP地址与泛域名)!') + + self.writeAcmeLog('开始ACME申请...') + log_file = self.acmeLogFile() + + if apply_ca == "let": + cmd = cmd + " --server letsencrypt " + elif apply_ca == "zerossl": + cmd = cmd + " --server zerossl " + elif apply_ca == "buypass": + cmd = cmd + " --server buypass " + + cmd = 'export ACCOUNT_EMAIL=' + email + ' && ' + cmd + ' >> ' + log_file + # print(cmd) + result = mw.execShell(cmd) + + # 开启代理 + self.openProxyByOpen(site_name) + # 开启重定向 + self.openRedirectByOpen(site_name) + + src_path = mw.getAcmeDomainDir(domains[0]) + src_cert = src_path + '/fullchain.cer' + src_key = src_path + '/' + domains[0] + '.key' + src_cert.replace("\\*", "*") + + msg = '签发失败,您尝试申请证书的失败次数已达上限!

                                            1、检查域名是否绑定到对应站点

                                            \ +

                                            2、检查域名是否正确解析到本服务器,或解析还未完全生效

                                            \ +

                                            3、如果您的站点设置了反向代理,或使用了CDN,请先将其关闭

                                            \ +

                                            4、如果您的站点设置了301重定向,请先将其关闭

                                            \ +

                                            5、如果以上检查都确认没有问题,请尝试更换DNS服务商

                                            ' + if not os.path.exists(src_cert): + data = {} + data['err'] = result + data['out'] = result[0] + data['msg'] = msg + data['result'] = {} + if result[1].find('new-authz error:') != -1: + data['result'] = json.loads(re.search("{.+}", result[1]).group()) + if data['result']['status'] == 429: + data['msg'] = msg + data['status'] = False + return data + + dst_path = self.sslDir + '/' + site_name + dst_cert = dst_path + "/fullchain.pem" # 生成证书路径 + dst_key = dst_path + "/privkey.pem" # 密钥文件路径 + + if not os.path.exists(dst_path): + mw.execShell("mkdir -p " + dst_path) + + mw.buildSoftLink(src_cert, dst_cert, True) + mw.buildSoftLink(src_key, dst_key, True) + mw.execShell('echo "acme" > "' + dst_path + '/README"') + + # 写入配置文件 + result = self.setSslConf(site_name) + if not result['status']: + return result + result['csr'] = mw.readFile(src_cert) + result['key'] = mw.readFile(src_key) + + mw.restartWeb() + return mw.returnData(True, '证书已更新!', result) + + def getDnsapiExportVar(self, data): + def_var = '' + for k in data: + def_var += 'export '+k+'="'+data[k]+'"\n' + return def_var + + # def getDomainRootName(self, domain): + # s = domain.split('.',1) + # return s[1] + + def getDomainRootName(self, domain): + import tldextract + extracted = tldextract.extract(domain) + # 组合注册域名和顶级域名 + return f"{extracted.domain}.{extracted.suffix}" + + def getDomainRootName_Old(self, domain): + s = domain.split('.') + count = len(s) + last_index = count - 1 + top_domain = s[last_index-1]+'.'+s[last_index] + return top_domain + + # 查找手动验证,需要改动域名dns的配置 + # nslookup -q=txt _acme-challenge.xx.com + def findAcmeHandDnsNotice(self, top_domain): + log_file = self.acmeLogFile() + info = mw.readFile(log_file) + txt_rep = r"TXT value: \'(.*)\'" + txt_value = re.finditer(txt_rep, info) + + rdata = [] + for text in txt_value: + t = {} + t['domain'] = '_acme-challenge.'+top_domain + t['val'] = text.groups()[0] + t['type'] = 'TXT' + t['must'] = True + rdata.append(t) + return rdata + + # acme手动申请方式 + # https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode + def createAcmeDnsTypeNone(self, site_name, domains, email, dnspai, wildcard_domain, force, renew, dns_alias): + # print(site_name, domains, email, dnspai, wildcard_domain, force, renew) + acme_dir = mw.getAcmeDir() + log_file = self.acmeLogFile() + + for d in domains: + top_domain = self.getDomainRootName(d) + cmd = ''' +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin:%s +export PATH +''' % (acme_dir,) + cmd += "acme.sh --register-account -m " + email + " \n" + if wildcard_domain == 'true': + cmd += 'acme.sh --issue -d '+top_domain+' -d "*.'+top_domain+'"' + d = top_domain + else: + cmd += "acme.sh --issue -d " + d + " " + cmd += " --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please" + + if dns_alias != '': + cmd += ' --domain-alias '+str(dns_alias) + + if renew == 'true': + cmd += " --renew" + cmd += ' > ' + log_file + # print(cmd) + result = mw.execShell(cmd) + # print(result) + + # acme源的ssl证书 + src_path = mw.getAcmeDomainDir(d) + src_cert = src_path + '/fullchain.cer' + src_key = src_path + '/' + d + '.key' + + if not os.path.exists(src_cert): + info = self.findAcmeHandDnsNotice(top_domain) + if len(info) != 0: + return mw.returnData(True, '手动解析', info) + + # acme源建立软链接(目标) + dst_path = self.sslDir + '/' + site_name + dst_cert = dst_path + "/fullchain.pem" # 生成证书路径 + dst_key = dst_path + "/privkey.pem" # 密钥文件路径 + + if not os.path.exists(dst_path): + mw.execShell("mkdir -p " + dst_path) + + mw.buildSoftLink(src_cert, dst_cert, True) + mw.buildSoftLink(src_key, dst_key, True) + mw.execShell('echo "acme" > "' + dst_path + '/README"') + + # 写入配置文件 + result = self.setSslConf(site_name) + if not result['status']: + return result + result['csr'] = mw.readFile(src_cert) + result['key'] = mw.readFile(src_key) + + mw.restartWeb() + return mw.returnData(True, '证书已更新!', result) + + def createAcmeDns(self, site_name, apply_ca, domains, email, dnspai, wildcard_domain, force, renew, dns_alias): + dnsapi_option = thisdb.getOptionByJson('dnsapi', default={}) + log_file = self.acmeLogFile() + cmd = 'echo "..." > '+ log_file + mw.execShell(cmd) + + # 手动方式申请 + if dnspai == 'none': + return self.createAcmeDnsTypeNone(site_name, domains, email, dnspai, wildcard_domain, force, renew, dns_alias) + + if not dnspai in dnsapi_option: + return mw.returnData(False, '['+dnspai+']未设置!') + + dnsapi_data = dnsapi_option[dnspai] + for k in dnsapi_data: + if dnsapi_data[k] == '': + return mw.returnData(False, k+'为空!') + + acme_dir = mw.getAcmeDir() + + for d in domains: + cmd = ''' +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin:%s +export PATH +''' % (acme_dir,) + cmd += "acme.sh --register-account -m "+email+" \n" + cmd += self.getDnsapiExportVar(dnsapi_data) + if wildcard_domain == 'true': + top_domain = self.getDomainRootName(d) + cmd += 'acme.sh --issue --dns '+str(dnspai)+' -d '+top_domain+' -d "*.'+top_domain+'"' + d = top_domain + else: + cmd += 'acme.sh --issue --dns '+str(dnspai)+' -d '+d + + if dns_alias != '': + cmd += ' --domain-alias '+str(dns_alias) + + + if apply_ca == "let": + cmd = cmd + " --server letsencrypt " + elif apply_ca == "zerossl": + cmd = cmd + " --server zerossl " + elif apply_ca == "buypass": + cmd = cmd + " --server buypass " + + cmd += ' > ' + log_file + # print(cmd) + result = mw.execShell(cmd) + # print(result) + + # acme源的ssl证书 + src_path = mw.getAcmeDomainDir(d) + src_cert = src_path + '/fullchain.cer' + src_key = src_path + '/' + d + '.key' + + msg = '签发失败,您尝试申请证书的失败次数已达上限!\ +

                                            1、检查域名是否正确解析到本服务器,或解析还未完全生效

                                            \ +

                                            2、如果以上检查都确认没有问题,请尝试更换DNS服务商

                                            ' + if not os.path.exists(src_cert): + data = {} + data['err'] = result + data['out'] = result[0] + data['msg'] = msg + data['result'] = {} + if result[1].find('new-authz error:') != -1: + data['result'] = json.loads(re.search("{.+}", result[1]).group()) + if data['result']['status'] == 429: + data['msg'] = msg + data['status'] = False + return data + + # acme源建立软链接(目标) + dst_path = self.sslDir + '/' + site_name + dst_cert = dst_path + "/fullchain.pem" # 生成证书路径 + dst_key = dst_path + "/privkey.pem" # 密钥文件路径 + + if not os.path.exists(dst_path): + mw.execShell("mkdir -p " + dst_path) + + mw.buildSoftLink(src_cert, dst_cert, True) + mw.buildSoftLink(src_key, dst_key, True) + mw.execShell('echo "acme" > "' + dst_path + '/README"') + + # 写入配置文件 + result = self.setSslConf(site_name) + if not result['status']: + return result + result['csr'] = mw.readFile(src_cert) + result['key'] = mw.readFile(src_key) + + mw.restartWeb() + return mw.returnData(True, '证书已更新!', result) + + def createAcme(self, site_name, domains, force, renew, apply_type, apply_ca, dnspai, email, wildcard_domain, dns_alias): + domains = json.loads(domains) + if len(domains) < 1: + return mw.returnData(False, '请选择域名') + if email.strip() != '': + thisdb.setOption('ssl_email', email) + + if email.strip() == '': + email = mw.getRandomString(10)+"."+mw.getRandomString(3) + '@gmail.com' + + # 检测acme是否安装 + acme_dir = mw.getAcmeDir() + if not os.path.exists(acme_dir): + try: + mw.execShell("curl -sS curl https://get.acme.sh | sh") + except: + pass + if not os.path.exists(acme_dir): + return mw.returnData(False, '尝试自动安装ACME失败,请通过以下命令尝试手动安装

                                            安装命令: curl https://get.acme.sh | sh

                                            ') + + # 避免频繁执行 + checkAcmeRun = mw.execShell('ps -ef|grep acme.sh |grep -v grep') + if checkAcmeRun[0] != '': + return mw.returnData(False, '正在申请或更新SSL中...') + + if apply_type == 'file': + return self.createAcmeFile(site_name, apply_ca, domains, email,force,renew) + elif apply_type == 'dns': + return self.createAcmeDns(site_name, apply_ca,domains, email, dnspai, wildcard_domain,force, renew, dns_alias) + return mw.returnData(False, '异常请求') + + def createLet(self, site_name, domains, force, renew, apply_type, dnspai, email, wildcard_domain): + domains = json.loads(domains) + if len(domains) < 1: + return mw.returnData(False, '请选择域名') + if email.strip() != '': + thisdb.setOption('ssl_email', email) + + + host_conf_file = self.getHostConf(site_name) + if os.path.exists(host_conf_file): + siteConf = mw.readFile(host_conf_file) + if siteConf.find('301-END') != -1: + return mw.returnJson(False, '检测到您的站点做了301重定向设置,请先关闭重定向!') + + # 检测存在反向代理 + data_path = self.getProxyDataPath(site_name) + data_content = mw.readFile(data_path) + if data_content != False: + try: + data = json.loads(data_content) + except: + pass + for proxy in data: + proxy_dir = "{}/{}".format(self.proxyPath, site_name) + proxy_dir_file = proxy_dir + '/' + proxy['id'] + '.conf' + if os.path.exists(proxy_dir_file): + return mw.returnJson(False, '检测到您的站点做了反向代理设置,请先关闭反向代理!') + + # fix binddir domain ssl apply question + mw.backFile(host_conf_file) + auth_to = self.getSitePath(site_name) + rep = r"\s*root\s*(.+);" + replace_root = "\n\troot " + auth_to + ";" + siteConf = re.sub(rep, replace_root, siteConf) + mw.writeFile(host_conf_file, siteConf) + mw.restartWeb() + + to_args = { + 'domains': domains, + 'auth_type': 'http', + 'auth_to': auth_to, + } + + src_letpath = mw.getServerDir() + '/web_conf/letsencrypt/' + site_name + src_csrpath = src_letpath + "/fullchain.pem" # 生成证书路径 + src_keypath = src_letpath + "/privkey.pem" # 密钥文件路径 + + dst_letpath = self.sslDir + '/' + site_name + dst_csrpath = dst_letpath + '/fullchain.pem' + dst_keypath = dst_letpath + '/privkey.pem' + + if not os.path.exists(src_letpath): + import cert_api + data = cert_api.cert_api().applyCertApi(to_args) + mw.restoreFile(host_conf_file) + if not data['status']: + msg = data['msg'] + if type(data['msg']) != str: + msg = data['msg'][0] + emsg = data['msg'][1]['challenges'][0]['error'] + msg = msg + '

                                            响应状态:' + str(emsg['status']) + '

                                            错误类型:' + emsg[ + 'type'] + '

                                            错误代码:' + emsg['detail'] + '

                                            ' + return mw.returnData(data['status'], msg, data['msg']) + + mw.execShell('mkdir -p ' + dst_letpath) + mw.buildSoftLink(src_csrpath, dst_csrpath, True) + mw.buildSoftLink(src_keypath, dst_keypath, True) + mw.execShell('echo "lets" > "' + dst_letpath + '/README"') + + # 写入配置文件 + result = self.setSslConf(site_name) + if not result['status']: + return result + + result['csr'] = mw.readFile(src_csrpath) + result['key'] = mw.readFile(src_keypath) + + mw.restartWeb() + return mw.returnData(data['status'], data['msg'], result) + + def setCertToSite(self, site_name, cert_name): + try: + path = self.sslDir + '/' + site_name.strip() + if not os.path.exists(path): + return mw.returnData(False, '证书不存在!') + + result = self.setSslConf(site_name) + if not result['status']: + return result + + mw.restartWeb() + mw.writeLog('网站管理', '证书已部署!') + return mw.returnData(True, '证书已部署!') + except Exception as ex: + return mw.returnData(False, '设置错误:' + str(ex)) + + def removeCert(self, cert_name): + try: + path = self.sslDir + '/' + cert_name + if not os.path.exists(path): + return mw.returnData(False, '证书已不存在!') + os.system("rm -rf " + path) + return mw.returnData(True, '证书已删除!') + except Exception as ex: + return mw.returnData(False, '证书删除失败:'+str(ex)) + + def getBackup(self,site_id,page=1,size=10): + site_info = thisdb.getSitesById(site_id) + info = thisdb.getBackupPage(site_id, page, size) + + data = {} + data['data'] = info['list'] + data['site'] = site_info + data['page'] = mw.getPage({'count':info['count'],'tojs':'getBackup','p':page, 'row':size}) + return data + + def toBackup(self, site_id): + site_info = thisdb.getSitesById(site_id) + + filename = site_info['name'] + '_' + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.zip' + backup_path = mw.getBackupDir() + '/site' + zip_name = backup_path + '/' + filename + if not (os.path.exists(backup_path)): + os.makedirs(backup_path) + exec_log = mw.getPanelDir() + '/logs/panel_exec.log' + cmd = "cd '" + site_info['path'] + "' && zip '" + zip_name + "' -r ./* > " + exec_log + " 2>&1" + mw.execShell(cmd) + + fsize = 0 + if os.path.exists(zip_name): + fsize = os.path.getsize(zip_name) + + thisdb.addBackup(site_id,filename,zip_name,fsize) + + msg = mw.getInfo('备份网站[{1}]成功!', (site_info['name'],)) + mw.writeLog('网站管理', msg) + return mw.returnData(True, '备份成功!') + + def delBackup(self,backup_id): + info = thisdb.getBackupById(backup_id) + if os.path.exists(info['filename']): + os.remove(info['filename']) + msg = mw.getInfo('删除网站[{1}]的备份[{2}]成功!', (info['name'], info['filename'])) + mw.writeLog('网站管理', msg) + + thisdb.deleteBackupById(backup_id) + return mw.returnData(True, '站点删除成功!') + + + def getPhpVersion(self): + phpVersions = ('00', '52', '53', '54', '55', '56', + '70', '71', '72', '73', '74', '80', + '81', '82', '83', '84', '85') + data = [] + for val in phpVersions: + tmp = {} + if val == '00': + tmp['version'] = '00' + tmp['name'] = '纯静态' + data.append(tmp) + + # 标准判断 + checkPath = mw.getServerDir() + '/php/' + val + '/bin/php' + if os.path.exists(checkPath): + tmp['version'] = val + tmp['name'] = 'PHP-' + val + data.append(tmp) + + # 其他PHP安装类型 + conf_dir = mw.getServerDir() + "/web_conf/php/conf" + if not os.path.exists(conf_dir): + return data + conf_list = os.listdir(conf_dir) + l = len(conf_list) + rep = r"enable-php-(.*?)\.conf" + for name in conf_list: + tmp = {} + try: + matchVer = re.search(rep, name).groups()[0] + except Exception as e: + continue + + if matchVer in phpVersions: + continue + + tmp['version'] = matchVer + tmp['name'] = 'PHP-' + matchVer + data.append(tmp) + return mw.returnData(True, 'ok', data) + # return data diff --git a/web/utils/site_reflect.py b/web/utils/site_reflect.py new file mode 100644 index 000000000..8d8f95a76 --- /dev/null +++ b/web/utils/site_reflect.py @@ -0,0 +1,95 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import sys +import re +import json +import time +import threading +import multiprocessing + +import core.mw as mw +import thisdb + +def getVhostDir(): + sdir = mw.getServerDir() + nginx_conf = sdir + "/web_conf/nginx" + vhosts = nginx_conf+"/vhost" + return vhosts + +def getRootDir(content): + pattern = r'\s*root\s*(.+);' + match = re.search(pattern, content) + if match: + return match.group(1) + return '' + +def getServerName(content): + pattern = r'\s*server_name\s*(.+);' + match = re.search(pattern, content) + if match: + content = match.group(1) + clist = content.strip().split(" "); + return clist + return [] + +def addDomain(site_id, site_name, domain): + # print(site_id, site_name, domain) + d = domain.split(':') + port = '80' + name = d[0] + if len(d) == 2: + port = d[1] + if thisdb.checkSitesDomainIsExist(name, port): + print('您添加的域名[{}:{}],已使用。请仔细检查!'.format(name, port)) + return True + thisdb.addDomain(site_id, name, port) + return True + +def parse(): + vhosts = getVhostDir() + vh_list = os.listdir(vhosts) + vail_list = [] + for f in vh_list: + if f.startswith("0."): + continue + if f.endswith("_bak"): + continue + if f.startswith("phpmyadmin"): + continue + if f.startswith("webstats"): + continue + if f.startswith("panel"): + continue + vail_list.append(f) + + for vail_domain in vail_list: + parseSite(vail_domain) + +def parseSite(d): + vhosts = getVhostDir() + domain = d.replace(".conf","") + + dconf = vhosts + '/' + d + content = mw.readFile(dconf) + + root_dir = getRootDir(content) + sn_list = getServerName(content) + + if thisdb.isSitesExist(domain): + print('您添加的站点[%s]已存在!' % domain) + else: + thisdb.addSites(domain, root_dir) + info = thisdb.getSitesByName(domain) + site_id = info['id'] + + for sn in sn_list: + addDomain(site_id, d, sn) diff --git a/web/utils/ssh/ssh_local.py b/web/utils/ssh/ssh_local.py new file mode 100644 index 000000000..13d2795a5 --- /dev/null +++ b/web/utils/ssh/ssh_local.py @@ -0,0 +1,132 @@ +# coding: utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# SSH终端操作 +# --------------------------------------------------------------------------------- + +import json +import time +import os +import sys +import socket +import threading +import re + +from io import BytesIO, StringIO + +import core.mw as mw +import paramiko + +from flask_socketio import SocketIO, emit, send + + +class ssh_local(object): + + __debug_file = 'logs/ssh_local.log' + __log_type = 'SSH终端' + + __ssh = None + __lock = False + + # lock + _instance_lock = threading.Lock() + + def __init__(self): + self.__debug_file = mw.getPanelDir()+ '/logs/ssh_terminal.log' + + @classmethod + def instance(cls, *args, **kwargs): + if not hasattr(ssh_local, "_instance"): + with ssh_local._instance_lock: + if not hasattr(ssh_local, "_instance"): + ssh_local._instance = ssh_local(*args, **kwargs) + return ssh_local._instance + + def debug(self, msg): + msg = "{} - {}:{} => {} \n".format(mw.formatDate(), + self.__host, self.__port, msg) + if not mw.isDebugMode(): + return + mw.writeFile(self.__debug_file, msg, 'a+') + + def returnMsg(self, status, msg): + return {'status': status, 'msg': msg} + + + def connectSsh(self): + if self.__lock : + return False + self.__lock = True + + import paramiko + ssh = paramiko.SSHClient() + mw.createSshInfo() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + port = mw.getSSHPort() + try: + ssh.connect('127.0.0.1', 22, timeout=5) + except Exception as e: + ssh.connect('127.0.0.1', port, timeout=5) + except Exception as e: + ssh.connect('localhost', port, timeout=5) + except Exception as e: + ssh.connect(mw.getHostAddr(), port, timeout=30) + except Exception as e: + return False + + shell = ssh.invoke_shell(term='xterm', width=83, height=21) + shell.setblocking(0) + + self.__lock = False + return shell + + def send(self): + pass + + def close(self): + try: + if self.__ssh: + self.__ssh.close() + except: + pass + + def wsSend(self, recv): + try: + t = recv.decode("utf-8") + return emit('server_response', {'data': t}) + except Exception as e: + return emit('server_response', {'data': recv}) + + def wsSendConnect(self): + return emit('connect', {'data': 'ok'}) + + def wsSendReConnect(self): + return emit('reconnect', {'data': 'ok'}) + + + def run(self, info): + if not self.__ssh: + self.__ssh = self.connectSsh() + + if self.__ssh: + if self.__ssh.exit_status_ready(): + self.__ssh = self.connectSsh() + + self.__ssh.send(info) + try: + time.sleep(0.005) + recv = self.__ssh.recv(8192) + return self.wsSend(recv) + except Exception as ex: + return self.wsSend('') + else: + return self.wsSend("连接中...\r\n") \ No newline at end of file diff --git a/web/utils/ssh/ssh_terminal.py b/web/utils/ssh/ssh_terminal.py new file mode 100644 index 000000000..32c8b99e6 --- /dev/null +++ b/web/utils/ssh/ssh_terminal.py @@ -0,0 +1,480 @@ +# coding: utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# SSH终端操作 +# --------------------------------------------------------------------------------- + +import json +import time +import os +import sys +import socket +import threading +import re + +from io import BytesIO, StringIO + +import core.mw as mw +import paramiko + +from flask_socketio import SocketIO, emit, send + + +class ssh_terminal(object): + + __debug_file = 'logs/ssh_terminal.log' + __log_type = 'SSH终端' + + # websocketio 唯一标识 + __sid = '' + + __host = None + __type = '0' + __port = 22 + __user = None + __pass = None + __pkey = None + __key_passwd = None + + __rep_ssh_config = False + __rep_ssh_service = False + __sshd_config_backup = None + + __ssh = None + __tp = None + __ps = None + + __ssh_list = {} + __ssh_last_request_time = {} + + # lock + _instance_lock = threading.Lock() + + def __init__(self): + self.__debug_file = mw.getPanelDir()+ '/logs/ssh_terminal.log' + ht = threading.Thread(target=self.heartbeat) + ht.start() + + @classmethod + def instance(cls, *args, **kwargs): + if not hasattr(ssh_terminal, "_instance"): + with ssh_terminal._instance_lock: + if not hasattr(ssh_terminal, "_instance"): + ssh_terminal._instance = ssh_terminal(*args, **kwargs) + return ssh_terminal._instance + + def debug(self, msg): + msg = "{} - {}:{} => {} \n".format(mw.formatDate(), + self.__host, self.__port, msg) + if not mw.isDebugMode(): + return + mw.writeFile(self.__debug_file, msg, 'a+') + + def returnMsg(self, status, msg): + return {'status': status, 'msg': msg} + + def restartSsh(self, act='reload'): + ''' + 重启ssh 无参数传递 + ''' + version = mw.readFile('/etc/redhat-release') + if not os.path.exists('/etc/redhat-release'): + mw.execShell('service ssh ' + act) + elif version.find(' 7.') != -1 or version.find(' 8.') != -1: + mw.execShell("systemctl " + act + " sshd.service") + else: + mw.execShell("/etc/init.d/sshd " + act) + + def isRunning(self, rep=False): + try: + if rep and self.__rep_ssh_service: + self.restartSsh('stop') + return True + + status = self.getSshStatus() + if not status: + self.restartSsh('start') + self.__rep_ssh_service = True + return True + return False + except: + return False + + def setSshdConfig(self, rep=False): + self.isRunning(rep) + if rep and not self.__rep_ssh_config: + return False + + try: + sshd_config_file = '/etc/ssh/sshd_config' + if not os.path.exists(sshd_config_file): + return False + + sshd_config = mw.readFile(sshd_config_file) + if not sshd_config: + return False + + if rep: + if self.__sshd_config_backup: + mw.writeFile(sshd_config_file, self.__sshd_config_backup) + self.restartSsh() + return True + + pin = r'^\s*PubkeyAuthentication\s+(yes|no)' + pubkey_status = re.findall(pin, sshd_config, re.I) + if pubkey_status: + if pubkey_status[0] == 'yes': + pubkey_status = True + else: + pubkey_status = False + + pin = r'^\s*RSAAuthentication\s+(yes|no)' + rsa_status = re.findall(pin, sshd_config, re.I) + if rsa_status: + if rsa_status[0] == 'yes': + rsa_status = True + else: + rsa_status = False + + self._sshd_config_backup = sshd_config + is_write = False + if not pubkey_status: + sshd_config = re.sub( + r'\n#?PubkeyAuthentication\s\w+', '\nPubkeyAuthentication yes', sshd_config) + is_write = True + if not rsa_status: + sshd_config = re.sub( + r'\n#?RSAAuthentication\s\w+', '\nRSAAuthentication yes', sshd_config) + is_write = True + + if is_write: + mw.writeFile(sshd_config_file, sshd_config) + self.__rep_ssh_config = True + self.restartSsh() + else: + self.__sshd_config_backup = None + return True + except: + return False + + def setSid(self, sid): + self.__sid = sid + + def connect(self, sid): + # self.connectBySocket() + if self.__host in ['127.0.0.1', 'localhost']: + return self.connectLocalSsh(sid) + else: + return self.connectBySocket(sid) + + __lock = False + + def connectLocalSsh(self, sid): + if self.__lock : + return False + self.__lock = True + + mw.createSshInfo() + self.__ps = paramiko.SSHClient() + self.__ps.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + self.__port = mw.getSSHPort() + try: + self.__ps.connect(self.__host, self.__port, timeout=60) + except Exception as e: + self.__ps.connect('127.0.0.1', self.__port) + except Exception as e: + self.__ps.connect('localhost', self.__port) + except Exception as e: + self.setSshdConfig(True) + self.__ps.close() + e = str(e) + if e.find('websocket error!') != -1: + return self.returnMsg(True, '连接成功') + if e.find('Authentication timeout') != -1: + self.debug("认证超时{}".format(e)) + return self.returnMsg(False, '认证超时,请按回车重试!{}'.format(e)) + if e.find('Connection reset by peer') != -1: + self.debug('目标服务器主动拒绝连接') + return self.returnMsg(False, '目标服务器主动拒绝连接') + if e.find('Error reading SSH protocol banner') != -1: + self.debug('协议头响应超时') + return self.returnMsg(False, '协议头响应超时,与目标服务器之间的网络质量太糟糕:' + e) + if not e: + self.debug('SSH协议握手超时') + return self.returnMsg(False, "SSH协议握手超时,与目标服务器之间的网络质量太糟糕") + err = mw.getTracebackInfo() + self.debug(err) + return self.returnMsg(False, "未知错误: {}".format(err)) + + self.debug('local-ssh:认证成功,正在构建会话通道') + ssh = self.__ps.invoke_shell( + term='xterm', width=83, height=21) + ssh.setblocking(0) + self.__ssh_list[sid] = ssh + mw.writeLog(self.__log_type, '成功登录到SSH服务器 [{}:{}]'.format( + self.__host, self.__port)) + self.debug('local-ssh:通道已构建') + + self.__lock = False + return self.returnMsg(True, '连接成功!') + + def connectBySocket(self, sid): + if self.__lock : + return False + self.__lock = True + if not self.__host: + return self.returnMsg(False, '错误的连接地址') + if not self.__user: + self.__user = 'root' + if not self.__port: + self.__port = 22 + + self.setSshdConfig(True) + num = 0 + while num < 5: + num += 1 + try: + self.debug('正在尝试第{}次连接'.format(num)) + if self.__rep_ssh_config: + time.sleep(0.1) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2 + num) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8192) + sock.connect((self.__host, self.__port)) + break + except Exception as e: + if num == 5: + self.setSshdConfig(True) + self.debug('重试连接失败,{}'.format(e)) + return self.returnMsg(False, '连接目标服务器失败, {}:{}'.format(self.__host, self.__port)) + else: + time.sleep(0.2) + + # print(self.__host, sock) + self.__tp = paramiko.Transport(sock) + try: + self.__tp.start_client() + self.__tp.banner_timeout = 60 + if not self.__pass and not self.__pkey: + return self.returnMsg(False, '密码或私钥不能都为空: {}:{}'.format(self.__host, self.__port)) + + if self.__pkey != '' and self.__type != '0': + self.debug('正在认证私钥') + p_file = StringIO(str(self.__pkey.replace('\\n', '\n'))) + # p_file = "/tmp/t_ssh_pkey.txt" + # mw.writeFile(p_file, self.__pkey.replace('\\n', '\n')) + # mw.execShell('chmod 600 ' + p_file) + try: + p_file.seek(0) + pkey = paramiko.RSAKey.from_private_key(p_file) + except: + try: + p_file.seek(0) # 重置游标 + pkey = paramiko.Ed25519Key.from_private_key( + p_file) + except: + try: + p_file.seek(0) + pkey = paramiko.ECDSAKey.from_private_key(p_file) + except: + p_file.seek(0) + pkey = paramiko.DSSKey.from_private_key(p_file) + + self.__tp.auth_publickey(username=self.__user, key=pkey) + else: + try: + self.__tp.auth_none(self.__user) + except Exception as e: + e = str(e) + if e.find('keyboard-interactive') >= 0: + self._auth_interactive() + else: + self.debug('正在认证密码') + self.__tp.auth_password( + username=self.__user, password=self.__pass) + except Exception as e: + self.setSshdConfig(True) + self.__tp.close() + e = str(e) + # print(e) + if e.find('Authentication timeout') != -1: + self.debug("认证超时{}".format(e)) + return self.returnMsg(False, '认证超时,请按回车重试!{}'.format(e)) + if e.find('Authentication failed') != -1: + self.debug('认证失败{}'.format(e)) + return self.returnMsg(False, '帐号或密码错误: {}'.format(e + "," + self.__user + "@" + self.__host + ":" + str(self.__port))) + if e.find('Bad authentication type; allowed types') != -1: + self.debug('认证失败{}'.format(e)) + if self.__host in ['127.0.0.1', 'localhost'] and self.__pass == 'none': + return self.returnMsg(False, '帐号或密码错误: {}'.format("Authentication failed ," + self.__user + "@" + self.__host + ":" + str(self.__port))) + return self.returnMsg(False, '不支持的身份验证类型: {}'.format(e)) + if e.find('Connection reset by peer') != -1: + self.debug('目标服务器主动拒绝连接') + return self.returnMsg(False, '目标服务器主动拒绝连接') + if e.find('Error reading SSH protocol banner') != -1: + self.debug('协议头响应超时') + return self.returnMsg(False, '协议头响应超时,与目标服务器之间的网络质量太糟糕:' + e) + if not e: + self.debug('SSH协议握手超时') + return self.returnMsg(False, "SSH协议握手超时,与目标服务器之间的网络质量太糟糕") + err = mw.getTracebackInfo() + self.debug(err) + return self.returnMsg(False, "未知错误: {}".format(err)) + + self.debug('认证成功,正在构建会话通道') + + ssh = self.__tp.open_session() + ssh.get_pty(term='xterm', width=100, height=34) + ssh.invoke_shell() + self.__ssh_list[sid] = ssh + mw.writeLog(self.__log_type, '成功登录到SSH服务器 [{}:{}]'.format( + self.__host, self.__port)) + self.debug('通道已构建') + self.__lock = False + return self.returnMsg(True, '连接成功.') + + def getSshInfo(self, file): + rdata = mw.readFile(file) + destr = mw.deDoubleCrypt('mdserver-web', rdata) + return json.loads(destr) + + def setAttr(self, sid, info): + self.__host = info['host'].strip() + + # 外部连接获取 + if not self.__host in ['127.0.0.1', 'localhost']: + dst_info = mw.getServerDir() + '/webssh/host/' + self.__host + '/info.json' + if os.path.exists(dst_info): + info = self.getSshInfo(dst_info) + + if 'type' in info: + self.__type = info['type'] + + if 'port' in info: + self.__port = int(info['port']) + if 'username' in info: + self.__user = info['username'] + if 'pkey' in info: + self.__pkey = info['pkey'] + if 'password' in info: + self.__pass = info['password'] + if 'pkey_passwd' in info: + self.__key_passwd = info['pkey_passwd'] + + # print(self.__host, self.__pass, self.__key_passwd) + try: + result = self.connect(sid) + # print(result) + except Exception as ex: + if str(ex).find("NoneType") == -1: + raise ex + return result + + def send(self): + pass + + def close(self): + try: + if self.__ssh: + self.__ssh.close() + if self.__tp: # 关闭宿主服务 + self.__tp.close() + if self.__ps: + self.__ps.close() + except: + pass + + def resize(self, sid, data): + try: + self.__ssh_list[sid].resize_pty( + width=data['cols'], height=data['rows']) + return True + except: + return False + + def wsSend(self, recv): + try: + t = recv.decode("utf-8") + return emit('server_response', {'data': t}) + except Exception as e: + return emit('server_response', {'data': recv}) + + def wsSendConnect(self): + return emit('connect', {'data': 'ok'}) + + def wsSendReConnect(self): + return emit('reconnect', {'data': 'ok'}) + + def heartbeat(self): + # limit_cos = 10 + while True: + time.sleep(3) + cur_time = time.time() + for x in list(self.__ssh_list.keys()): + ssh_last_time = self.__ssh_last_request_time[x] + sid_off_cos = cur_time - ssh_last_time + + # print("heartbeat off cos :", x, sid_off_cos) + if sid_off_cos > 3: + cur_ssh = self.__ssh_list[x] + if not cur_ssh: + del(self.__ssh_list[x]) + del(self.__ssh_last_request_time[x]) + continue + + if cur_ssh.exit_status_ready(): + del(self.__ssh_list[x]) + del(self.__ssh_last_request_time[x]) + continue + + cur_ssh.send("exit\r\n") + del(self.__ssh_list[x]) + del(self.__ssh_last_request_time[x]) + + def run(self, sid, info): + self.__sid = sid + if not self.__sid: + return self.wsSend('WebSocketIO无效') + + self.__ssh_last_request_time[sid] = time.time() + if not sid in self.__ssh_list: + if type(info) == dict and 'host' in info: + result = self.setAttr(sid, info) + if result['status']: + return self.wsSendConnect() + else: + return self.wsSend(result['msg']) + else: + return self.wsSendReConnect() + + result = self.returnMsg(False, '') + if sid in self.__ssh_list: + if 'resize' in info: + self.resize(sid, info) + result = self.returnMsg(True, '已连接') + if result['status']: + if type(info) == str: + time.sleep(0.1) + cur_ssh = self.__ssh_list[sid] + if cur_ssh.exit_status_ready(): + self.wsSend("logout\r\n") + del(self.__ssh_list[sid]) + return + cur_ssh.send(info) + try: + time.sleep(0.005) + recv = cur_ssh.recv(8192) + return self.wsSend(recv) + except Exception as ex: + return self.wsSend('') + else: + return self.wsSend(result['msg']) diff --git a/web/utils/system/__init__.py b/web/utils/system/__init__.py new file mode 100644 index 000000000..b73ae550e --- /dev/null +++ b/web/utils/system/__init__.py @@ -0,0 +1,16 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +from .main import * +from .query import * +from .stats import * +from .monitor import monitor + +from .update import * \ No newline at end of file diff --git a/web/utils/system/main.py b/web/utils/system/main.py new file mode 100644 index 000000000..a64f6a25e --- /dev/null +++ b/web/utils/system/main.py @@ -0,0 +1,227 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import sys +import re +import time +import math +import psutil + +import core.mw as mw + +from threading import Thread +from time import sleep + +def mw_async(f): + def wrapper(*args, **kwargs): + thr = Thread(target=f, args=args, kwargs=kwargs) + thr.start() + return wrapper + +@mw_async +def restartServer(): + if not mw.isRestart(): + return mw.returnData(False, '请等待所有安装任务完成再执行!') + mw.execShell("sync && init 6 &") + return mw.returnData(True, '命令发送成功!') + +@mw_async +def shutdownServer(): + if not mw.isRestart(): + return mw.returnData(False, '请等待所有安装任务完成再执行!') + mw.execShell("sync && init 0 &") + return mw.returnData(True, '命令发送成功!') + +def getPid(self, pname): + try: + pids = psutil.pids() + for pid in pids: + if psutil.Process(pid).name() == pname: + return True + return False + except: + return False + +def getEnvInfo(): + data = {} + data['status'] = True + sdir = mw.getServerDir() + + data['webserver'] = '未安装' + if os.path.exists(sdir + '/openresty/nginx/sbin/nginx'): + data['webserver'] = 'OpenResty' + data['php'] = [] + phpversions = ['52', '53', '54', '55', '56', '70', '71', '72', '73', '74', '80', '81', '82', '83', '84'] + phpPath = sdir + '/php/' + for pv in phpversions: + if not os.path.exists(phpPath + pv + '/bin/php'): + continue + data['php'].append(pv) + data['mysql'] = False + if os.path.exists(sdir + '/mysql/bin/mysql'): + data['mysql'] = True + try: + diskInfo = psutil.disk_usage('/www') + except: + diskInfo = psutil.disk_usage('/') + data['disk'] = diskInfo[2] + return mw.returnData(True, 'ok', data) + +def getDiskInfo(): + # 取磁盘分区信息 + temp = mw.execShell("df -h -P|grep '/'|grep -v tmpfs | grep -v devfs")[0] + tempInodes = mw.execShell("df -i -P|grep '/'|grep -v tmpfs | grep -v devfs")[0] + temp1 = temp.split('\n') + tempInodes1 = tempInodes.split('\n') + diskInfo = [] + n = 0 + cuts = ['/mnt/cdrom', '/boot', '/boot/efi', '/dev', + '/dev/shm', '/zroot', '/run/lock', '/run', '/run/shm', '/run/user'] + for tmp in temp1: + n += 1 + inodes = tempInodes1[n - 1].split() + disk = tmp.split() + if len(disk) < 5: + continue + if disk[1].find('M') != -1: + continue + if disk[1].find('K') != -1: + continue + if len(disk[5].split('/')) > 4: + continue + if disk[5] in cuts: + continue + arr = {} + arr['path'] = disk[5] + tmp1 = [disk[1], disk[2], disk[3], disk[4]] + arr['size'] = tmp1 + arr['inodes'] = [inodes[1], inodes[2], inodes[3], inodes[4]] + diskInfo.append(arr) + return diskInfo + + +def getLoadAverage(): + c = os.getloadavg() + data = {} + data['one'] = round(float(c[0]), 2) + data['five'] = round(float(c[1]), 2) + data['fifteen'] = round(float(c[2]), 2) + data['max'] = psutil.cpu_count() * 2 + data['limit'] = data['max'] + data['safe'] = data['max'] * 0.75 + return data + +def getSystemVersion(): + # 取操作系统版本 + current_os = mw.getOs() + # sys_temper = self.getSystemDeviceTemperature() + # print(sys_temper) + # mac + if current_os == 'darwin': + data = mw.execShell('sw_vers')[0] + data_list = data.strip().split("\n") + mac_version = '' + for x in data_list: + xlist = x.split("\t") + mac_version += xlist[len(xlist)-1] + ' ' + + arch_ver = mw.execShell("arch") + return mac_version + " (" + arch_ver[0].strip() + ")" + + # freebsd + if current_os.startswith('freebsd'): + version = mw.execShell( + "cat /etc/*-release | grep PRETTY_NAME | awk -F = '{print $2}' | awk -F '\"' '{print $2}'") + arch_ver = mw.execShell( + "sysctl -a | egrep -i 'hw.machine_arch' | awk -F ':' '{print $2}'") + return version[0].strip() + " (" + arch_ver[0].strip() + ")" + + redhat_series = '/etc/redhat-release' + if os.path.exists(redhat_series): + version = mw.readFile('/etc/redhat-release') + version = version.replace('release ', '').strip() + + arch_ver = mw.execShell("arch") + return version + " (" + arch_ver[0].strip() + ")" + + os_series = '/etc/os-release' + if os.path.exists(os_series): + version = mw.execShell( + "cat /etc/*-release | grep PRETTY_NAME | awk -F = '{print $2}' | awk -F '\"' '{print $2}'") + + arch_ver = mw.execShell("arch") + return version[0].strip() + " (" + arch_ver[0].strip() + ")" + +def getBootTime(): + # 取系统启动时间 + if os.path.exists('/proc/uptime'): + uptime = mw.readFile('/proc/uptime') + run_time = uptime.split()[0] + else: + start_time = psutil.boot_time() + run_time = time.time() - start_time + + tStr = float(run_time) + min = tStr / 60 + hours = min / 60 + days = math.floor(hours / 24) + hours = math.floor(hours - (days * 24)) + min = math.floor(min - (days * 60 * 24) - (hours * 60)) + return mw.getInfo('已不间断运行: {1}天{2}小时{3}分钟', (str(int(days)), str(int(hours)), str(int(min)))) + +def getCpuInfo(interval=1): + # 取CPU信息 + cpuCount = psutil.cpu_count() + cpuLogicalNum = psutil.cpu_count(logical=False) + used = psutil.cpu_percent(interval=interval) + cpuLogicalNum = 0 + if os.path.exists('/proc/cpuinfo'): + c_tmp = mw.readFile('/proc/cpuinfo') + d_tmp = re.findall("physical id.+", c_tmp) + cpuLogicalNum = len(set(d_tmp)) + + used_all = psutil.cpu_percent(percpu=True) + cpu_name = mw.getCpuType() + " * {}".format(cpuLogicalNum) + return used, cpuCount, used_all, cpu_name, cpuCount, cpuLogicalNum + +def getMemInfo(): + # 取内存信息 + mem = psutil.virtual_memory() + if sys.platform == 'darwin': + memInfo = {'memTotal': mem.total} + memInfo['memRealUsed'] = memInfo['memTotal'] * (mem.percent / 100) + else: + memInfo = { + 'memTotal': mem.total, + 'memFree': mem.free, + 'memBuffers': mem.buffers, + 'memCached': mem.cached + } + memInfo['memRealUsed'] = memInfo['memTotal'] - memInfo['memFree'] - memInfo['memBuffers'] - memInfo['memCached'] + return memInfo + +def getMemUsed(): + # 取内存使用率 + try: + import psutil + mem = psutil.virtual_memory() + + if mw.getOs() == 'darwin': + return mem.percent + + memInfo = {'memTotal': mem.total / 1024 / 1024, 'memFree': mem.free / 1024 / 1024, + 'memBuffers': mem.buffers / 1024 / 1024, 'memCached': mem.cached / 1024 / 1024} + tmp = memInfo['memTotal'] - memInfo['memFree'] - \ + memInfo['memBuffers'] - memInfo['memCached'] + tmp1 = memInfo['memTotal'] / 100 + return (tmp / tmp1) + except Exception as ex: + return 1 diff --git a/web/utils/system/monitor.py b/web/utils/system/monitor.py new file mode 100644 index 000000000..9d1508327 --- /dev/null +++ b/web/utils/system/monitor.py @@ -0,0 +1,197 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import sys +import re +import time +import math +import psutil +import threading + + +from .main import getMemUsed + +import core.mw as mw +import core.db as db + + + +# 监控系统数据入库 +class monitor: + + _dbfile = mw.getPanelDataDir() + '/system.db' + _diskinfo = None + _netinfo = None + + def __init__(self): + pass + + # lock + _instance_lock = threading.Lock() + + @classmethod + def instance(cls, *args, **kwargs): + if not hasattr(monitor, "_instance"): + with monitor._instance_lock: + if not hasattr(monitor, "_instance"): + monitor._instance = monitor(*args, **kwargs) + return monitor._instance + + def clearDbFile(self): + os.remove(self._dbfile) + self.initDBFile() + return True + + def initDBFile(self): + is_reload = False + sql_file = mw.getPanelDir() + '/web/admin/setup/sql/system.sql' + sql_file_md5 = mw.getPanelDir() + '/web/admin/setup/sql/system.md5' + content = mw.readFile(sql_file) + content_md5 = mw.md5(content) + if not os.path.exists(sql_file_md5): + mw.writeFile(sql_file_md5, content_md5) + + content_src_md5 = mw.readFile(sql_file) + if content_md5 != content_src_md5: + is_reload = True + + if os.path.exists(self._dbfile) and not is_reload: + return True + + sql = db.Sql().dbPos(mw.getPanelDataDir(),'system') + csql_list = content.split(';') + for index in range(len(csql_list)): + sql.execute(csql_list[index], ()) + return True + + def getMonitorDay(self): + monitor_day = mw.M('option').field('name').where('name=?', ('monitor_day',)).getField('value') + if monitor_day is None: + return 30 + try: + return int(monitor_day) + except (TypeError, ValueError): + return 30 + + def isOnlyNetIoStats(self): + monitor_only_netio = mw.M('option').field('name').where('name=?',('monitor_only_netio',)).getField('value') + if monitor_only_netio == 'open': + return True + return False + + def psutilNetIoCounters(self): + ''' + 统计网卡流量 + ''' + if self.isOnlyNetIoStats(): + local_lo = (0, 0, 0, 0) + ioName = psutil.net_io_counters(pernic=True).keys() + for x in ioName: + if x.find("lo") > -1: + local_lo = psutil.net_io_counters(pernic=True).get(x)[:4] + + all_io = psutil.net_io_counters()[:4] + result_io = tuple([all_io[i] - local_lo[i] for i in range(0, len(all_io))]) + + return result_io + return psutil.net_io_counters()[:4] + + def getDiskInfo(self): + now_diskinfo = psutil.disk_io_counters() + if self._diskinfo is None: + self._diskinfo = now_diskinfo + + info = {} + info['read_count'] = now_diskinfo.read_count - self._diskinfo.read_count + info['write_count'] = now_diskinfo.write_count - self._diskinfo.write_count + info['read_bytes'] = now_diskinfo.read_bytes - self._diskinfo.read_bytes + info['write_bytes'] = now_diskinfo.write_bytes - self._diskinfo.write_bytes + info['read_time'] = now_diskinfo.read_time - self._diskinfo.read_time + info['write_time'] = now_diskinfo.write_time - self._diskinfo.write_time + + self._diskinfo = now_diskinfo + return info + + def getNetIoInfo(self): + # 取当前网络Io + net_io = self.psutilNetIoCounters() + if not self._netinfo: + self._netinfo = net_io + info = {} + info['upTotal'] = net_io[0] + info['downTotal'] = net_io[1] + info['up'] = round(float((net_io[0] - self._netinfo[0]) / 1024), 2) + info['down'] = round(float((net_io[1] - self._netinfo[1]) / 1024), 2) + info['downPackets'] = net_io[3] + info['upPackets'] = net_io[2] + + self._netinfo = net_io + return info + + def getLoadAverage(self): + c = os.getloadavg() + data = {} + data['one'] = round(float(c[0]), 2) + data['five'] = round(float(c[1]), 2) + data['fifteen'] = round(float(c[2]), 2) + data['max'] = psutil.cpu_count() * 2 + data['limit'] = data['max'] + data['safe'] = data['max'] * 0.75 + return data + + def run(self): + self.initDBFile() + monitor_day = self.getMonitorDay() + + info = {} + # 取当前CPU Io + info['used'] = psutil.cpu_percent(interval=1) + info['used'] = round(info['used'], 2) + + info['mem'] = getMemUsed() + info['mem'] = round(info['mem'], 2) + + netio = self.getNetIoInfo() + diskio = self.getDiskInfo() + + addtime = int(time.time()) + deltime = addtime - (monitor_day * 86400) + + # CPU/内存数据入库 + cpu_mem_data = (info['used'], info['mem'], addtime) + cmd_objm = mw.M('cpuio').dbPos(mw.getPanelDataDir(),'system') + cmd_objm.add('pro,mem,addtime', cpu_mem_data) + cmd_objm.where("addtime 100: + lpro = 100 + load_objm = mw.M('load_average').dbPos(mw.getPanelDataDir(),'system') + load_objm.add('pro,one,five,fifteen,addtime', (lpro, load_data['one'], load_data['five'], load_data['fifteen'], addtime)) + load_objm.where("addtime +# --------------------------------------------------------------------------------- + +import os +import sys +import re +import time +import math +import psutil + + +import core.mw as mw + +# -------------------------------------------- 数据查询相关 -------------------------------------------- +# 格式化addtime列 +def toAddtime(data, tomem=False): + import time + if tomem: + import psutil + mPre = (psutil.virtual_memory().total / 1024 / 1024) / 100 + length = len(data) + he = 1 + if length > 100: + he = 1 + if length > 1000: + he = 3 + if length > 10000: + he = 15 + if he == 1: + for i in range(length): + data[i]['addtime'] = time.strftime( + '%m/%d %H:%M', time.localtime(float(data[i]['addtime']))) + if tomem and data[i]['mem'] > 100: + data[i]['mem'] = data[i]['mem'] / mPre + + return data + else: + count = 0 + tmp = [] + for value in data: + if count < he: + count += 1 + continue + value['addtime'] = time.strftime( + '%m/%d %H:%M', time.localtime(float(value['addtime']))) + if tomem and value['mem'] > 100: + value['mem'] = value['mem'] / mPre + tmp.append(value) + count = 0 + return tmp + +# 格式化addtime列 +def toUseAddtime(data): + dlen = len(data) + for i in range(dlen): + data[i]['addtime'] = time.strftime('%m/%d %H:%M:%S', time.localtime(float(data[i]['addtime']))) + return data + +def getLoadAverageByDB(start, end): + # 获取系统的负载统计信息 + data = mw.M('load_average').dbPos(mw.getPanelDataDir(),'system')\ + .where("addtime>=? AND addtime<=?", (start, end,))\ + .field('pro,one,five,fifteen,addtime')\ + .order('id asc').select() + data = toUseAddtime(data) + return data + +def getDiskIoByDB(start, end): + # 获取系统的磁盘IO统计信息 + data = mw.M('diskio').dbPos(mw.getPanelDataDir(),'system')\ + .where("addtime>=? AND addtime<=?", (start, end))\ + .field('read_count,write_count,read_bytes,write_bytes,read_time,write_time,addtime')\ + .order('id asc').select() + data = toUseAddtime(data) + return data + +def getCpuIoByDB(start, end): + # 获取系统的CPU/IO统计信息 + data = mw.M('cpuio').dbPos(mw.getPanelDataDir(),'system')\ + .where("addtime>=? AND addtime<=?",(start, end))\ + .field('pro,mem,addtime')\ + .order('id asc').select() + data = toUseAddtime(data) + return data + +def getNetworkIoByDB(start, end): + # 获取系统网络IO统计信息 + # id, + data = mw.M('network').dbPos(mw.getPanelDataDir(),'system')\ + .where("addtime>=? AND addtime<=?", (start, end))\ + .field('up,down,total_up,total_down,down_packets,up_packets,addtime')\ + .order('id asc').select() + data = toUseAddtime(data) + return data + +# -------------------------------------------- 数据查询相关 -------------------------------------------- diff --git a/web/utils/system/stats.py b/web/utils/system/stats.py new file mode 100644 index 000000000..123e5e80b --- /dev/null +++ b/web/utils/system/stats.py @@ -0,0 +1,161 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import sys +import re +import time +import math +import psutil + +import core.mw as mw + +class stats: + cache = {} + + # 磁盘统计 + def disk(self): + iokey = 'disk_stat' + diskInfo = {} + diskInfo['ALL'] = {} + diskInfo['ALL']['read_count'] = 0 + diskInfo['ALL']['write_count'] = 0 + diskInfo['ALL']['read_bytes'] = 0 + diskInfo['ALL']['write_bytes'] = 0 + diskInfo['ALL']['read_time'] = 0 + diskInfo['ALL']['write_time'] = 0 + diskInfo['ALL']['read_merged_count'] = 0 + diskInfo['ALL']['write_merged_count'] = 0 + + try: + diskio = None + if iokey in self.cache: + diskio = self.cache[iokey] + + mtime = int(time.time()) + if not diskio: + diskio = {} + diskio['info'] = None + diskio['time'] = mtime + + diskio_cache = diskio['info'] + stime = mtime - diskio['time'] + if not stime: stime = 1 + + diskio_group = psutil.disk_io_counters(perdisk=True) + if not diskio_cache: + diskio_cache = diskio_group + + for disk_name in diskio_group.keys(): + diskInfo[disk_name] = {} + # print('disk_name',disk_name) + # print(diskio_group[disk_name].write_time , diskio_cache[disk_name].write_time) + # print(diskio_group[disk_name].write_count , diskio_cache[disk_name].write_count) + + diskInfo[disk_name]['read_count'] = int((diskio_group[disk_name].read_count - diskio_cache[disk_name].read_count) / stime) + diskInfo[disk_name]['write_count'] = int((diskio_group[disk_name].write_count - diskio_cache[disk_name].write_count) / stime) + diskInfo[disk_name]['read_bytes'] = int((diskio_group[disk_name].read_bytes - diskio_cache[disk_name].read_bytes) / stime) + diskInfo[disk_name]['write_bytes'] = int((diskio_group[disk_name].write_bytes - diskio_cache[disk_name].write_bytes) / stime) + diskInfo[disk_name]['read_time'] = int((diskio_group[disk_name].read_time - diskio_cache[disk_name].read_time) / stime) + diskInfo[disk_name]['write_time'] = int((diskio_group[disk_name].write_time - diskio_cache[disk_name].write_time) / stime) + + if 'read_merged_count' in diskio_group[disk_name] and 'read_merged_count' in diskio_cache[disk_name]: + diskInfo[disk_name]['read_merged_count'] = int((diskio_group[disk_name].read_merged_count - diskio_cache[disk_name].read_merged_count) / stime) + if 'write_merged_count' in diskio_group[disk_name] and 'write_merged_count' in diskio_cache[disk_name]: + diskInfo[disk_name]['write_merged_count'] = int((diskio_group[disk_name].write_merged_count - diskio_cache[disk_name].write_merged_count) / stime) + + diskInfo['ALL']['read_count'] += diskInfo[disk_name]['read_count'] + diskInfo['ALL']['write_count'] += diskInfo[disk_name]['write_count'] + diskInfo['ALL']['read_bytes'] += diskInfo[disk_name]['read_bytes'] + diskInfo['ALL']['write_bytes'] += diskInfo[disk_name]['write_bytes'] + if diskInfo['ALL']['read_time'] < diskInfo[disk_name]['read_time']: + diskInfo['ALL']['read_time'] = diskInfo[disk_name]['read_time'] + if diskInfo['ALL']['write_time'] < diskInfo[disk_name]['write_time']: + diskInfo['ALL']['write_time'] = diskInfo[disk_name]['write_time'] + + if 'read_merged_count' in diskInfo[disk_name] and 'read_merged_count' in diskInfo[disk_name]: + diskInfo['ALL']['read_merged_count'] += diskInfo[disk_name]['read_merged_count'] + if 'write_merged_count' in diskInfo[disk_name] and 'write_merged_count' in diskInfo[disk_name]: + diskInfo['ALL']['write_merged_count'] += diskInfo[disk_name]['write_merged_count'] + + self.cache[iokey] = {'info':diskio_group,'time':mtime} + except Exception as e: + pass + + return diskInfo + + # 网络统计 + def network(self): + netInfo = {} + + netInfo['ALL'] = {} + netInfo['ALL']['up'] = 0 + netInfo['ALL']['down'] = 0 + netInfo['ALL']['upTotal'] = 0 + netInfo['ALL']['downTotal'] = 0 + netInfo['ALL']['upPackets'] = 0 + netInfo['ALL']['downPackets'] = 0 + + mtime = time.time() + iokey = 'net_stat' + netio = None + if iokey in self.cache: + netio = self.cache[iokey] + + if not netio: + netio = {} + netio['info'] = None + netio['all_io'] = None + netio['time'] = mtime + + stime = mtime - netio['time'] + if not stime: stime = 1 + + # print("new:",stime) + netio_group = psutil.net_io_counters(pernic=True).keys() + + netio_cache = netio['info'] + allio_cache = netio['all_io'] + if not netio_cache: + netio_cache = {} + + netio_group_t = {} + for name in netio_group: + netInfo[name] = {} + + io_data = psutil.net_io_counters(pernic=True).get(name) + if not name in netio_cache: + netio_cache[name] = io_data + + netio_group_t[name] = io_data + + netInfo[name]['up'] = round(float((io_data[0] - netio_cache[name][0]) / stime), 2) + netInfo[name]['down'] = round(float((io_data[1] - netio_cache[name][1])/ stime), 2) + + netInfo[name]['upTotal'] = io_data[0] + netInfo[name]['downTotal'] = io_data[1] + netInfo[name]['upPackets'] = io_data[2] + netInfo[name]['downPackets'] = io_data[3] + + all_io = psutil.net_io_counters()[:4] + if not allio_cache: + allio_cache = all_io + + netInfo['ALL']['up'] = round(float((all_io[0] - allio_cache[0]) /stime), 2) + netInfo['ALL']['down'] = round(float((all_io[1] - allio_cache[1]) /stime), 2) + netInfo['ALL']['upTotal'] = all_io[0] + netInfo['ALL']['downTotal'] = all_io[1] + netInfo['ALL']['upPackets'] = all_io[2] + netInfo['ALL']['downPackets'] = all_io[3] + + self.cache[iokey] = {'info':netio_group_t,'all_io':all_io,'time':mtime} + return netInfo + + diff --git a/web/utils/system/update.py b/web/utils/system/update.py new file mode 100644 index 000000000..54a78c35f --- /dev/null +++ b/web/utils/system/update.py @@ -0,0 +1,150 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import sys +import re +import time +import math +import psutil +import json + +import core.mw as mw + +def versionDiff(now, new): + ''' + test 测试 + new 有新版本 + none 没有新版本 + ''' + new_list = new.split('.') + if len(new_list) > 3: + return 'test' + + now_list = now.split('.') + ret = 'none' + from distutils.version import LooseVersion + if LooseVersion(new) > LooseVersion(now): + return 'new' + else: + return 'none' + +def getServerInfo(): + import urllib.request + import ssl + upAddr = 'https://api.github.com/repos/midoks/mdserver-web/releases/latest' + try: + context = ssl._create_unverified_context() + req = urllib.request.urlopen(upAddr, context=context, timeout=3) + result = req.read().decode('utf-8') + version = json.loads(result) + return version + except Exception as e: + print(str(e)) + return None + return None + +def updateServer(stype, version=''): + import config + # 更新服务 + try: + if not mw.isRestart(): + return mw.returnData(False, '请等待所有安装任务完成再执行!') + + version_new_info = getServerInfo() + if version_new_info is None: + return mw.returnData(False, '服务器数据或网络有问题!') + + version_now = config.APP_VERSION + new_ver = version_new_info['name'] + if stype == 'check': + diff = versionDiff(version_now, new_ver) + if diff == 'new': + return mw.returnData(True, '有新版本!', new_ver) + elif diff == 'test': + return mw.returnData(True, '有测试版本!', new_ver) + else: + return mw.returnData(False, '已经是最新,无需更新!') + + if stype == 'info': + diff = versionDiff(version_now, new_ver) + data = {} + data['version'] = new_ver + data['content'] = version_new_info['body'].replace("\n", "
                                            ") + return mw.returnData(True, '更新信息!', data) + + if stype == 'update': + if version == '': + return mw.returnData(False, '缺少版本信息!') + + if new_ver != version: + return mw.returnData(False, '更新失败,请重试!') + + toPath = mw.getPanelDir() + '/temp' + if not os.path.exists(toPath): + mw.execShell('mkdir -p ' + toPath) + + newUrl = "https://github.com/midoks/mdserver-web/archive/refs/tags/" + version + ".zip" + + dist_mw = toPath + '/mw.zip' + if not os.path.exists(dist_mw): + mw.execShell('wget --no-check-certificate -O ' + dist_mw + ' ' + newUrl) + + dist_to = toPath + "/mdserver-web-" + version + if not os.path.exists(dist_to): + os.system('unzip -o ' + toPath + '/mw.zip' + ' -d ' + toPath) + + cmd_cp = 'cp -rf ' + toPath + '/mdserver-web-' + version + '/* ' + mw.getServerDir() + '/mdserver-web' + mw.execShell(cmd_cp) + + mw.execShell('rm -rf ' + toPath + '/mdserver-web-' + version) + mw.execShell('rm -rf ' + toPath + '/mw.zip') + + update_env = ''' +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + +P_VER=`python3 -V | awk '{print $2}'` + +if [ ! -f /www/server/mdserver-web/bin/activate ];then +cd /www/server/mdserver-web && python3 -m venv . +cd /www/server/mdserver-web && source /www/server/mdserver-web/bin/activate +else +cd /www/server/mdserver-web && source /www/server/mdserver-web/bin/activate +fi + +cn=$(curl -fsSL -m 10 http://ipinfo.io/json | grep "\"country\": \"CN\"") +PIPSRC="https://pypi.python.org/simple" +if [ ! -z "$cn" ];then +PIPSRC="https://pypi.tuna.tsinghua.edu.cn/simple" +fi + +cd /www/server/mdserver-web && pip3 install -r /www/server/mdserver-web/requirements.txt -i $PIPSRC + +P_VER_D=`echo "$P_VER"|awk -F '.' '{print $1}'` +P_VER_M=`echo "$P_VER"|awk -F '.' '{print $2}'` +NEW_P_VER=${P_VER_D}.${P_VER_M} + +if [ -f /www/server/mdserver-web/version/r${NEW_P_VER}.txt ];then +cd /www/server/mdserver-web && pip3 install -r /www/server/mdserver-web/version/r${NEW_P_VER}.txt -i $PIPSRC +fi +''' + os.system(update_env) + mw.restartMw() + return mw.returnData(True, '安装更新成功!') + + return mw.returnData(False, '已经是最新,无需更新!') + except Exception as ex: + # print('updateServer', ex) + return mw.returnData(False, "连接服务器失败!" + str(ex)) + + + + diff --git a/web/utils/task.py b/web/utils/task.py new file mode 100644 index 000000000..63f9e18df --- /dev/null +++ b/web/utils/task.py @@ -0,0 +1,64 @@ +# coding:utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +import os +import pwd +import time + +import core.mw as mw +import thisdb + +def getTaskPage(page=1,size=10): + info = thisdb.getTaskPage(page=page, size=size) + + rdata = {} + rdata['data'] = info['list'] + rdata['count'] = info['count'] + rdata['page'] = mw.getPage({'count':info['count'],'tojs':'remind','p':page,'row':size}) + return rdata + +# 删除进程下的所有进程 +def removeTaskRecursion(pid): + cmd = "ps -ef|grep %s | grep -v grep |sed -n '2,1p' | awk '{print $2}'" % pid + sub_pid = mw.execShell(cmd) + if sub_pid[0].strip() == '': + return 'ok' + + if sub_pid[0].strip() == pid : + return 'ok' + + removeTaskRecursion(sub_pid[0].strip()) + mw.execShell('kill -9 ' + sub_pid[0].strip()) + return sub_pid[0].strip() + +# 删除任务 +def removeTask(task_id): + try: + name = mw.M('tasks').where('id=?', (task_id,)).getField('name') + status = mw.M('tasks').where('id=?', (task_id,)).getField('status') + mw.M('tasks').delete(task_id) + if str(status) == '-1': + cmd = "ps aux | grep 'panel_task.py' | grep -v grep |awk '{print $2}'" + task_pid = mw.execShell(cmd) + task_list = task_pid[0].strip().split("\n") + for i in range(len(task_list)): + removeTaskRecursion(task_list[i]) + t = mw.execShell('kill -9 ' + task_list[i]) + print(t) + mw.triggerTask() + mw.restartTask() + except Exception as e: + mw.restartTask() + + # 删除日志 + task_log = mw.getPanelDir() + "/tmp/panelTask.pl" + if os.path.exists(task_log): + os.remove(task_log) + return mw.returnData(True, '任务已删除!') \ No newline at end of file diff --git a/web/utils/vilidate.py b/web/utils/vilidate.py new file mode 100755 index 000000000..a2d8ba363 --- /dev/null +++ b/web/utils/vilidate.py @@ -0,0 +1,143 @@ +# coding: utf-8 + +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 验证码操作 +# --------------------------------------------------------------------------------- + + +import random +import math + +from PIL import Image, ImageDraw, ImageFont, ImageFilter + +class vieCode: + __fontSize = 20 # 字体大小 + __width = 120 # 画布宽度 + __heigth = 45 # 画布高度 + __length = 4 # 验证码长度 + __draw = None # 画布 + __img = None # 图片资源 + __code = None # 验证码字符 + __str = None # 自定义验证码字符集 + __inCurve = True # 是否画干扰线 + __inNoise = True # 是否画干扰点 + __type = 2 # 验证码类型 1、纯字母 2、数字字母混合 + __fontPatn = 'static/fonts/2.ttf' # 字体 + + def GetCodeImage(self, size=80, length=4): + '''获取验证码图片 + @param int size 验证码大小 + @param int length 验证码长度 + ''' + # 准备基础数据 + self.__length = length + self.__fontSize = size + self.__width = self.__fontSize * self.__length + self.__heigth = int(self.__fontSize * 1.5) + + # 生成验证码图片 + self.__createCode() + self.__createImage() + self.__createNoise() + self.__printString() + self.__cerateFilter() + + return self.__img, self.__code + + def __cerateFilter(self): + '''模糊处理''' + self.__img = self.__img.filter(ImageFilter.BLUR) + filter = ImageFilter.ModeFilter(8) + self.__img = self.__img.filter(filter) + + def __createCode(self): + '''创建验证码字符''' + # 是否自定义字符集合 + if not self.__str: + # 源文本 + number = "3456789" + srcLetter = "qwertyuipasdfghjkzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM" + srcUpper = srcLetter.upper() + if self.__type == 1: + self.__str = number + else: + self.__str = srcLetter + srcUpper + number + + # 构造验证码 + self.__code = random.sample(self.__str, self.__length) + + def __createImage(self): + '''创建画布''' + bgColor = (random.randint(200, 255), random.randint( + 200, 255), random.randint(200, 255)) + self.__img = Image.new('RGB', (self.__width, self.__heigth), bgColor) + self.__draw = ImageDraw.Draw(self.__img) + + def __createNoise(self): + '''画干扰点''' + if not self.__inNoise: + return + font = ImageFont.truetype(self.__fontPatn, int(self.__fontSize / 1.5)) + for i in range(5): + # 杂点颜色 + noiseColor = (random.randint(150, 200), random.randint( + 150, 200), random.randint(150, 200)) + putStr = random.sample(self.__str, 2) + for j in range(2): + # 绘杂点 + size = (random.randint(-10, self.__width), + random.randint(-10, self.__heigth)) + self.__draw.text(size, putStr[j], font=font, fill=noiseColor) + pass + + def __createCurve(self): + '''画干扰线''' + if not self.__inCurve: + return + x = y = 0 + + # 计算曲线系数 + a = random.uniform(1, self.__heigth / 2) + b = random.uniform(-self.__width / 4, self.__heigth / 4) + f = random.uniform(-self.__heigth / 4, self.__heigth / 4) + t = random.uniform(self.__heigth, self.__width * 2) + xend = random.randint(self.__width / 2, self.__width * 2) + w = (2 * math.pi) / t + + # 画曲线 + color = (random.randint(30, 150), random.randint( + 30, 150), random.randint(30, 150)) + for x in range(xend): + if w != 0: + for k in range(int(self.__heigth / 10)): + y = a * math.sin(w * x + f) + b + self.__heigth / 2 + i = int(self.__fontSize / 5) + while i > 0: + px = x + i + py = y + i + k + self.__draw.point((px, py), color) + i -= i + + def __printString(self): + '''打印验证码字符串''' + font = ImageFont.truetype(self.__fontPatn, self.__fontSize) + x = 0 + # 打印字符到画板 + for i in range(self.__length): + # 设置字体随机颜色 + color = (random.randint(30, 150), random.randint( + 30, 150), random.randint(30, 150)) + # 计算座标 + x = random.uniform(self.__fontSize * i * 0.95, + self.__fontSize * i * 1.1) + y = self.__fontSize * random.uniform(0.3, 0.5) + # 打印字符 + self.__draw.text((x, y), self.__code[i], font=font, fill=color) diff --git a/web/version.py b/web/version.py new file mode 100644 index 000000000..95e803f30 --- /dev/null +++ b/web/version.py @@ -0,0 +1,26 @@ +# --------------------------------------------------------------------------------- +# MW-Linux面板 +# --------------------------------------------------------------------------------- +# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. +# --------------------------------------------------------------------------------- +# Author: midoks +# --------------------------------------------------------------------------------- + +# --------------------------------------------------------------------------------- +# 版本信息 +# --------------------------------------------------------------------------------- + +# 应用程序版本号组件 +APP_RELEASE = 0 +APP_REVISION = 18 +APP_SMALL_VERSION = 4 + +# 应用程序版本后缀,例如"beta1"、"dev"。通常为空字符串GA发布 +APP_SUFFIX = '' + + +#不要改变!由组件构造的应用程序版本字符串 +if not APP_SUFFIX: + APP_VERSION = '%s.%s.%s' % (APP_RELEASE, APP_REVISION, APP_SMALL_VERSION) +else: + APP_VERSION = '%s.%s.%s-%s' % (APP_RELEASE, APP_REVISION, APP_SMALL_VERSION, APP_SUFFIX)